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).