Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate custom elements #797

Closed
Rich-Harris opened this issue Aug 29, 2017 · 19 comments
Closed

Generate custom elements #797

Rich-Harris opened this issue Aug 29, 2017 · 19 comments

Comments

@Rich-Harris
Copy link
Member

I'd thought of this as a 'one day' feature, since custom elements aren't universally supported and we already have svelte-custom-elements. But then I started building an Electron app (where custom elements are supported), and started building a UI kit for it (because the nearest thing I could find, photonkit, appears to be abandoned and pertains to an older version of Electron), and it's one of those cases where custom elements really would be quite nice.

svelte-custom-elements sort of works, but it's suboptimal because

  • no true style encapsulation (elements aren't shielded from global styles, and you don't get the perf benefits of a local CSSOM)
  • there's no light DOM — slotted content becomes part of the shadow DOM

So I think we should add one of the following:

compiled = svelte.compile({
  customelement: true,
  name: 'x-foo'
});

compiled = svelte.compile({
  customelement: 'x-foo'
});

Maybe a component should be able to specify its own name, and customelement: true means 'use the component's self-declared name, or error if it doesn't have one', and customelement: 'x-foo' means 'register this component is <x-foo> whatever it calls itself'.

Differences in input/output that I can think of:

  • it would export a class, rather than a constructor function
  • styles wouldn't get transformed. (Not entirely sure how styles work tbh — does the <style> tag need to get attached to every single instance's shadow DOM?)
  • <slot> elements would get rendered, and options.slots would be ignored (because there would be no options)
  • attributes/props need to be part of the component definition (or compiler options, I guess, for situations where the component author didn't anticipate custom element usage)
  • fire might need to wrap dispatchEvent? Not sure if we'd want to merge on and addEventListener (i.e. mix DOM events together with component events). I think we might have to so that <my-thing on:myEvent='doSomething()'> works. I guess we'd just disable bubbling by default, so that things don't get too wacky

A few nice things that would come out of this:

  • Custom elements would still be Svelte components, so you'd still be able to use set, get, observe
  • Svelte components would be elements, so you'd be able to listen for DOM events, style them, add classes, use transitions etc without having to add wrapper elements
  • Svelte apps would be able to compile the custom elements with shared: true so there'd be very little overhead
  • Our components would be smaller (and probably faster) than Stencil's 😀

Any thoughts?

@m59peacemaker
Copy link
Contributor

Are there implications for svelte component npm package developers? Will we distribute more bundles so that there is an es and cjs version of both the webcomponent and non-webcomponent versions?

@Rich-Harris
Copy link
Member Author

Good q. Thinking out loud — we should probably have some templates and 'official' guidelines for distributable components, but basically what I envisage is something like this:

my-thing
|- package.json # { "main: index.js", "module: index.mjs" and "svelte: src/MyThing.html" }
|- src
   |- MyThing.html
|- index.js
|- index.mjs
|- custom-element.js
|- custom-element.mjs
|- rollup.config.js # generates index and custom-element

Then you could use it like so:

// as a standalone component in a non-Svelte app
import MyThing from 'my-thing';
new MyThing({...});
<!-- as a component in a Svelte app (deduplicates helpers etc) -->
<MyThing/>

<script>
  import MyThing from 'my-thing';
  export default {
    components: { MyThing }
  };
</script>
// as a standalone custom element in a non-Svelte app
import 'my-thing/custom-element.mjs'; // or .js
document.body.innerHTML = `<my-thing/>`;
<!-- as a custom element in a Svelte app -->
<my-thing/>

<script>
  import 'my-thing';
</script>

The last of those is probably the most confusing. Basically your app's build config would need to specify that some of your components needed to be compiled as custom elements:

// rollup.config.js
import svelte from 'rollup-plugin-svelte';

// as an alternative to having two separate instances of the plugin,
// the app could just do `import 'my-thing/custom-element.mjs';` —
// it would just mean missing out on the deduplication
export default {
  // ...
  plugins: [
    svelte({
      include: 'node_modules/**',
      customelement: true
    }),
    svelte({
      exclude: 'node_modules/**',
      customelement: false
    })
  ]
};

I think that covers all the bases. Basically it should 'just work' with that structure, with opt-in further optimisations for Svelte apps.

Rich-Harris added a commit that referenced this issue Sep 3, 2017
Rich-Harris added a commit that referenced this issue Sep 3, 2017
Rich-Harris added a commit that referenced this issue Sep 3, 2017
compile to custom element
@m59peacemaker
Copy link
Contributor

Directory structure dependent modules are pretty mean to my obsessive brain. The svelte component distribution model has been nice to me, since import Thing from 'Thing' automatically imports either src/Thing.html or build/Thing.js dependent upon context, so we don't have to expose some kind of tree where you can find the thing you're looking for. Since ES modules with tree shaking are favorable over needing to put your dist files in a certain place in the directory for publishing, could there be a way to use default and named exports instead of an import path?

// awkward since no value is needed, and there's no way to determine what to shake, right?
import { ThingWebComponent } from 'thing'
// Seems like this should work out
import { Thing } from 'thing' // regular component

import 'thing' // web component

@Rich-Harris
Copy link
Member Author

That wouldn't really work, because the custom element could never get shaken out (because customElement.define is part of the component definition, and has side-effects). You'd always end up with a copy in your app even if you weren't using the custom element, and — worse — you'd need to define customElement in browsers that didn't already support it. Having it in a separate file is the only way.

@Rich-Harris
Copy link
Member Author

Closing this as #811 has been merged, and Svelte 1.37 supports compiling to custom elements

@m59peacemaker
Copy link
Contributor

Oh right. Fortunately, I just had an alternate idea I'm less opposed to.

Build to ./web-component/index.js

import 'thing/web-component'

I'll just think of ./web-component as another ./build directory. It's a good enough alternative to building files to the project root.

@morewry
Copy link

morewry commented Sep 13, 2017

After taking a look at the Svelte 1.37 option, I have a request. Given the requirement that the tag name be provided to the compiler, it's not clear to me if it's possible to later register the resulting custom element under a different user defined tag name. If not, this is important--it's becoming a best practice for web components to allow users the option to register the element with any tag name they want. The reason being that custom elements exist in the global HTML namespace--you can't register the same tag name twice (or the same constructor). Being able to control when and how an element is registered provides a way to solve naming conflicts and adds flexibility around situations where you can't de-duplicate different versions of a component. Since these component based UIs are highly compositional it won't be uncommon to run into these types of situations.

@Rich-Harris
Copy link
Member Author

@morewry thanks — could you open a separate issue with that comment please, so it doesn't get lost? Totally get where you're coming from. My main concern was that it would become unreasonably burdensome for app developers to name each component, and I think that generally these sorts of collisions of will be rare.

You can control the tag name by passing one to the compiler...

compiled = svelte.compile(source, {
  customElement: { tag: 'custom-name' }
});

...but that doesn't really work if you're using the compiler via a bundler plugin. So it would be good to figure out a better solution (perhaps passing tag: null to avoid auto-registering?).

@robdodson
Copy link

@morewry do you have other examples where folks are renaming their elements in this way? You mentioned it becoming a best practice but I've only ever heard of one other team attempting to do this

@morewry
Copy link

morewry commented Oct 26, 2017

@robdodson I actually thought I'd read it in something you'd written! Apparently that's not the case; poked around in some saved material from various sources, but couldn't find it. My guess is my memory mashed together two unrelated things, but I'll keep an eye out in case it was something relevant. So scratch that; I should make sure to verify before saying something so general.

As I haven't had an opportunity to use web components in production, I've made static define methods on all my WC classes which use a default tag name if called without a custom one, but I've done this only in my prototypes, and it's not a relevant approach in this case.

It doesn't come from nowhere, though, but rather as a result of our having run into situations with Angular 1.x directives (which we tried to make in ways heavily influenced by WCs, and of which you also can't have two of anything with the same name) where not being able to just give the gosh-darned thing a custom name and use two different versions of the directive got in our way. It came up often enough and for enough underlying reasons that I was keeping it in mind.

But I hadn't realized until recently that there was a restriction on registering the same constructor twice; so while this might help ease transition in occasional and specific cases, such as different versions with breaking changes, it's not the general panacea as a pattern that I previously thought.

So, @Rich-Harris, if it's alright, I'll make that issue if I have anything more productive, as of now I retract what I said. ;-)

@robdodson
Copy link

I actually thought I'd read it in something you'd written!

I may have floated the idea on a GH thread once. A while back I was thinking something similar but then folks pointed out that it doesn't help you because you still need to rename all the usage of those tags in your transitive deps.

My concern with using two of the same custom element is that it's inherently going to bloat the bundle that you send to the client. Personally I'm wondering if we could get to a place where client side code gets deduped down to just 1 version of everything, and build tools/devDependencies remains nested and multi-versioned. But that's a discussion for a different thread :D

@morewry
Copy link

morewry commented Oct 27, 2017

@robdodson I get you on the flattening. I'm old-school and am constantly pushing for us to avoid bloat when minimal effort allows us to do so. But the experiences I have from both before and after Node.js got all everywhere is the reason I'm looking for ways to make the options we have for working with WCs versus JS-land solutions like React Components more similar. Not because, in either case, we shouldn't try to dedupe, but rather because of the reality of day to day operations with second and third party dependencies and what I find my coworkers who come from more Node-ish backgrounds tend to find frustrating.

It's something I could easily blather on about, but as you say, out of scope for this thread.

@daphen
Copy link

daphen commented Oct 14, 2019

Any ideas on how one would go about implementing a library of web-components written in Svelte in something like Storybook?

This would allow us to write standardized web-components that are possible to just "drop-in" to any frontend project regardless of framework. We have a need for this at my company, we're currently using StencilJS but would like to switch to Svelte.

@kylecordes
Copy link

Since this issue is closed, perhaps there should be another one for discussion of the broader notion of Svelte as a tool to emit a set of web components for consumption elsewhere. Beyond just the ability to do it for folks who are adjusting their own compiler set up, but full out-of-the-box support, bundles that load eagerly or lazily as desired, polyfill loading if desired, etc. Currently Stencil does a great job of all that stuff, for example. I can't think of any reason why a Svelte couldn't bring a similar developer experience.

@daphen
Copy link

daphen commented Oct 14, 2019

Yeah, that's exactly what I'm after. Svelte looks like a much nicer developer experience than Stencil has to offer. But the automatic bundling of web components is worth so much.

@marcus-sa
Copy link

marcus-sa commented Oct 14, 2019

Stencil was created with web components in mind.
It seperates the runtime and only injects it and the component if they don't already exists.
Of course it can be done, but it does require a lot of work and a Svelte component to web components compiler.
As of 2019 all modern browsers supports custom elements out of the box (and tbh, nobody really cares about IE, otherwise polyfills does exist)
I'm +1 for this.

@sawden
Copy link

sawden commented Jun 28, 2021

Any ideas on how one would go about implementing a library of web-components written in Svelte in something like Storybook?

This would allow us to write standardized web-components that are possible to just "drop-in" to any frontend project regardless of framework. We have a need for this at my company, we're currently using StencilJS but would like to switch to Svelte.

Did you find a solution?

@daphen
Copy link

daphen commented Jun 28, 2021

Any ideas on how one would go about implementing a library of web-components written in Svelte in something like Storybook?
This would allow us to write standardized web-components that are possible to just "drop-in" to any frontend project regardless of framework. We have a need for this at my company, we're currently using StencilJS but would like to switch to Svelte.

Did you find a solution?

No, sorry, we're all in on Stencil and it's been alright.

@morewry
Copy link

morewry commented Jun 28, 2021

There are lots of ways to use Storybook with web components. IIRC, I've used:

  • @storybook/html
  • @storybook/web-components

But I don't think those particular modules are especially necessary to effectively use Storybook as a development environment or as a styleguide/documentation tool for web components. @storybook/react can support web components the same as any React app. Knobs work fine. Quality and speed of live reloading is entirely situational. Events and setting DOM properties takes a little wiring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants