Ressources π
Before starting I want to acknowledge redradix and Andres Martin, who made the hard work for me in this template https://github.com/redradix/svelte-custom-element-template...
If you are in a hurry, you can directly go to have a look at the code here and play with it: https://github.com/stefanonepa/svelte-component-ts
Why? π€
As explained in the github repo redradix/svelte-custom-element-template:
Building custom elements with Svelte is really easy but have a lot of limitations, in this template I'm trying to show the way I solve most of these limitations.
Svelte current limitations:
They solved a very simple use case which was how to wrap a svelte app inside a web component.
How? π
How can we achieve this miracle (hacks inside):
- Build the entry component as a web component
- Build the sub component as svelte app
- Inject the css of the sub components in the shadowRoot element
- If transition are used replace the injection in the document into the shadow element
1. Build the shadowRoot wrapper web component
// rollup.config.js svelte({ preprocess: sveltePreprocess({ sourceMap: !production }), compilerOptions: { dev: !production, customElement: true, }, emitCss: false, include: './src/ShadowRoot.svelte', }),
2. Build svelte to be injected in the web component wrapper
// rolup.config.js svelte({ preprocess: sveltePreprocess({ sourceMap: !production }), compilerOptions: { dev: !production, }, emitCss: true, exclude: './src/ShadowRoot.svelte', }),
3. inject the generated css into the shadowRoot node
To catch the generated css I modified rollup-plugin-css-only
locally to push the generated css on each changes (rebuild)
// ./.rollup/css-only.js ... generateBundle: function generateBundle(opts, bundle) { // Combine all stylesheets, respecting import order var css = ''; for (var x = 0; x < order.length; x++) { var id = order[x]; css += styles[id] || ''; } // Emit styles through callback if (typeof options.output === 'function') { options.output(css, styles, bundle); return; } ...
Then inject the css right into the bundle (π± Hack alert!) with one important caveat which is that the wrapper web component has to have a style set π₯.
import css from './.rollup/css-only'; // rollup.config.js css({ output(styles, styleNodes, bundle) { const match = production ? `.shadowRoot.innerHTML="` : `.shadowRoot.innerHTML = "`; const currentBundle = bundle[bundleFile]; currentBundle.code = currentBundle.code.replace( match, `${match}<style>${styles}</style>`); }, }),
4. Include svelte transition if used into the shadow dom
Svelte gives use some very nice utilities like transition (cf. https://svelte.dev/tutorial/transition)
For my actual understanding is that svelte will inject dynamically computed styles into the head/document, and this won't allow the transition to apply into the shadow dom. That's why we need to replace the document injection by the shadow dom node.
// rollup.config.js replace({ '.ownerDocument': '.getRootNode()', delimiters: ['', ''], }), replace({ '.head.appendChild': '.appendChild', delimiters: ['', ''], }),
Result π
We have a web component that wraps a svelte app and supports typescript and scss out of the box, with a DX (developer experience) that allows you to change the code and rebuild it automatically.
Svelte-component-ts template π
this template enables svelte to be used with a shadow DOM
entry component and then sub component using the goodness of svelte.
This template has stealen inspiration (hacks) from https://github.com/redradix/svelte-custom-element-template thanks to https://github.com/MonkeyAndres
This template includes:
- typescript support out of the box
- sass support
- babel with a minimal configuration (cf. rollup.config.js)
Recommended tools
Usage
Clone it with degit:
npx degit stefanonepa/svelte-component-ts my-new-component cd my-new-component yarn yarn dev
Constraints
- setup a style in the entry element
ShadowRoot.svelte
. -
β οΈ Styles in the root component are not scoped by svelte, then choose carefully your selectors if you use some thereβ οΈ .
Why?
(from redradix/svelte-custom-element-template
βοΈ)
Building custom elements with Svelte is really easy but have a lot of limitations, is this template I'm trying to show the way I solve most of these limitations.
Svelte current limitations:
TODO π
[ ] support hot reload
Conclusion
I hope this will help everyone trying to create custom element using all the goodness provided by svelte. I would love to find something less hacky provided by the svelte contributors. But I am still very happy of the result.
Feel free to share your experiences with web components and svelte, ideas for improvement or just say hi π
Top comments (1)
For future readers. Took me a while to figure out but this is now built into svelte. Please have a look at; github.com/sveltejs/svelte/issues/.... Just set the target element to the shadow root (don't forget to disable emitCss).