sveltejs/svelte

Implementing a build system for framework-agnostic UI components

pospi opened this issue · 5 comments

pospi commented

I have logged this in valueflows/vf-ui#5 but am reaching out here to see if this is being attempted by anyone else. I'm unsatisfied with the provided options that increase complexity for developers integrating components into their own projects- this thread provides all necessary evidence to suggest that there is a barrier to entry present.

The user story is "As a developer, I can bring vf-ui components into my project of choice, without having to reconfigure my build system or bundler or eject from CRNA". So that means I want:

  • separate NPM packages for the same component in runtime-dependant flavours (mycomponentlib, mycomponentlib-react, mycomponentlib-angular etc)
  • zero-config components written in the native component format of the framework (eg. ReactComponent) without runtime dependencies
  • pluggable styles that work "natively" (at the framework's module resolution layer)
  • file separation between component DOM logic and styles, to promote re-styling
  • a default export of the component which mixes in a theme that is loaded from another file on top of the "unstyled" component; yielding the option to inject a custom theme if desired

I've already started working on this, but before I go too much further I want to see what other options I have. FWIW my next steps will be dealing with the output code that Svelte generates, which I don't see an API for... so expect I am going to have to do some brittle regexing to remove the CSS injection in order that the end-developer's bundler can manage it.

While I'm here I suppose it's worth asking my related pending questions in case I need to go ahead with this:

  • Is there a way to make Webpack generate sane bundle sizes and clean output that is appropriate for drop-in use in a React or Angular project?
  • How heavy is the Svelte runtime, bytesize-wise? Would you advocate embedding SvelteComponent instances within React, Angular etc projects rather than SvelteElement ones?
  • What would be involved with doing this "properly"? It seems like a different template is used for generating the output when customElement is defined. Can I author my own compiler targets?

Hi, see my comment for #4115.

I have logged this in valueflows/vf-ui#5 but am reaching out here to see if this is being attempted by anyone else. I'm unsatisfied with the provided options that increase complexity for developers integrating components into their own projects- this thread provides all necessary evidence to suggest that there is a barrier to entry present.

The user story is "As a developer, I can bring vf-ui components into my project of choice, without having to reconfigure my build system or bundler or eject from CRNA". So that means I want:

  • separate NPM packages for the same component in runtime-dependant flavours (mycomponentlib, mycomponentlib-react, mycomponentlib-angular etc)

  • zero-config components written in the native component format of the framework (eg. ReactComponent) without runtime dependencies

  • pluggable styles that work "natively" (at the framework's module resolution layer)

  • file separation between component DOM logic and styles, to promote re-styling

  • a default export of the component which mixes in a theme that is loaded from another file on top of the "unstyled" component; yielding the option to inject a custom theme if desired

I've already started working on this, but before I go too much further I want to see what other options I have. FWIW my next steps will be dealing with the output code that Svelte generates, which I don't see an API for... so expect I am going to have to do some brittle regexing to remove the CSS injection in order that the end-developer's bundler can manage it.

While I'm here I suppose it's worth asking my related pending questions in case I need to go ahead with this:

  • Is there a way to make Webpack generate sane bundle sizes and clean output that is appropriate for drop-in use in a React or Angular project?
  • How heavy is the Svelte runtime, bytesize-wise? Would you advocate embedding SvelteComponent instances within React, Angular etc projects rather than SvelteElement ones?
  • What would be involved with doing this "properly"? It seems like a different template is used for generating the output when customElement is defined. Can I author my own compiler targets?

Gee, you're not asking for much are you?

Realistically, mixing frameworks together is a TERRIBLE idea, and from the sounds of it you're trying to kludge it all together which is an even worse idea.

Think of it this way, just because you can take a bunch of different puzzles and force the pieces together doesn't mean it's a good idea.

pospi commented

Perhaps you're misinterpreting what I'm proposing. I don't see those as particularly big asks, nor as things outside of the scope of what Svelte proposes to be. Either way I can't say the disparaging way you're approaching this discussion is particularly constructive or inclusive.

I'm not planning on "mixing frameworks together" nor am I attempting to 'kludge' anything. My only proposal is wiring up a bunch of existing well-vetted modules to bind Svelte build output natively into multiple UI frameworks. Since Svelte build output is just raw JS / WebComponents and CSS, doing so with minimal dependencies can mostly be done pretty easily.

In other words— the way Svelte works currently looks like this:

rollup (or webpack)
 ↳ svelte loader / plugin
    ↳ svelte compiler
       ↳ optional CSS injection ('css' option)
          ↳ [Svelte component]─┐
┌──────────────────────────────┘
└→ framework-specific app (Svelte)
    ↳ [bundled app output]
       ↳ [STORAGE ON DISK]

- OR -

rollup (or webpack)
 ↳ svelte loader / plugin
    ↳ svelte compiler
       ↳ CSS injection
          ↳ [customElement component]─┐
┌─────────────────────────────────────┘
└→ framework adapter component
    ↳ framework-specific app (React, Angular, Vue etc)
       ↳ [bundled app output]
          ↳ [STORAGE ON DISK]

My proposal is to improve pluggability with the latter case by allowing more control over the generation of the [customElement component]:

svelte-universal-component-compiler
 ↳ svelte compiler
   ├→ optional CSS injection (enabled)
   │   ↳ [customElement component] 
   │      ↳ [STORAGE ON DISK]────────────→ apps without build system
   └→ optional CSS injection (disabled)
       ↳ [customElement component] 
          ↳ [STORAGE ON DISK]─┐
┌─────────────────────────────┘
└→ generate framework adapter component
    ↳ [STORAGE ON DISK] ───────┐
                               │
                               │
rollup (or webpack)            ░
 ↳ compiled adapter component ←┘
    ↳ framework-specific app (React, Angular, Vue etc)
       ↳ [bundled app output]
          ↳ [STORAGE ON DISK]

Note that this is basically the same workflow and that most of the logic happens outside of Svelte itself. The only internal differences that would affect this library are to enable more intermediary compiler targets (i.e. [STORAGE ON DISK]).

I can see few enhancements being necessary to Svelte itself in order to accomplish this and no need to introduce any breaking changes. In any case, easiest way to prove it is just to go ahead and implement it.


There are two paths I could follow here- would really appreciate advice from @Rich-Harris and other contributors on which option best aligns with the roadmap of Svelte.

Experimentation of option 1 is underway— see next post.

Option 2 is making render_dom & render_ssr pluggable in compile. This actually feels like the most future-proof method of approaching a solution, because it opens up the possibility of Svelte being used natively as a framework-agnostic compilation pipeline, rather than requiring a third-party wrapper module such as svelte-universal-component-compiler to implement compilation targets via runtime customElement wrappers like this one. But... I'm not sure if that would be considered within the domain of Svelte. If the ideological intention is to be only "a WebComponents compiler" and not to be, say, supporting render_react and render_vue compiler output plugins, then maybe this is an undesired degree of flexibility at this point.

All this stated, 1 would probably get my vote at the present time. But I'm curious to hear from Svelte's creators whether they have leanings one way or the other.

pospi commented

I have started work on option 1 as an exploration. Edited the original task list a bit for clarity, added checkboxes to the individual requirements & some others as test cases to check off.

  1. Upgrade compiler options to allow the necessary flexibility:
    • Change build output to use a createStyledElement helper which can dynamically generate SvelteElement sub-classes with injected CSS. Similar to as depicted here except advocating moving the component base class to an argument of the function, and re-using the function as a common helper.
    • Wire up the css: false compiler option such that CSS injection is also ommitted from the customElement build if defined (see #4124).
    • Add a defineCustomElement compiler option (default true) which determines whether to write the customElements.define call into the component source. (Note: you can just use customElement: null here.)
    • These changes would then yield the following results:
      • Svelte runtime becomes very slightly heavier due to the addition of createStyledElement. Since Svelte is not a zero-runtime framework, but a minimal-runtime framework, I think the addition of these half dozen lines of code is acceptable.
        • These helpers could be added to the core runtime as curried helpers in svelte/internal to reduce the size of the per-component boilerplate. const styledElement = (baseElement) => (stylesheetCSS) => /* ... */
      • Use of Svelte in normal compilation mode would function the same as previously.
      • Use of Svelte in customElement compilation mode would yield output depending on the value of the css compiler option:
        • If false, unstyled elements are emitted and may be registered as Custom Elements depending on the value of customElement.
        • If true, a styled element class is created via createStyledElement and the component is optionally registered as a Custom Element.
        • Default behaviour would be to output a styled element which is automatically registered as a Custom Element.