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

render Svelte components inside shadow dom #5869

Closed
ivanhofer opened this issue Jan 8, 2021 · 9 comments
Closed

render Svelte components inside shadow dom #5869

ivanhofer opened this issue Jan 8, 2021 · 9 comments

Comments

@ivanhofer
Copy link
Contributor

Currently it is difficult to take advantage of the style encapsulation from the shadow dom when using Svelte.
The problem is that Svelte always appends styles to document.head. When your component is renderen into a shadow dom, the styles will be appended outside the shadow root and therefore will have no effect in your Svelte-Application.

There are already ways to use Svelte in a shadow dom - however, they all have their drawbacks:

  • compile the Svelte application to WebComponents with the customElement: true compiler option:
    There are some important differnces: see Custom element API from the official Svelte docs.
  • emit a css and a js file and write a custom loader that creates a shadow root and injects the css and js files into the shadow dom:
    I already tried this approach. It worked great until I started caching the ressources. It sometimes happened that the new js file was loaded with the old css file (yes, I have set both cache headers the same). In the new js file, all class-hashes were different and so no styles could be applied.

I have developed a tool that renders an overlay on any given Website. In my use case style encapsulation is necessary to deliver a good-looking product. I already have tried a few workarounds to increase the specificity of my css-selectors:

  • append !important on every style definition.
  • prepend every selector with the ID selector of my root
  • use IDs and class-names that no website uses e.g. I prefixed all my classes with 'n:' => 'n:button' or 'n:card'
  • write a "reset"-CSS on the application-root that reverts every possible user modifications e.g. background on a button
  • and some more small hacks

It was working for most Websites but still, every few weeks I encountered a new Issue, where the styles of the website was overriding the styles of my overlay. The size of my bundle was uneccessary bigger and it also made it impossible to use the svelte-animations, due to the high specificity of my style selectors.

I came to I point where I have given up on building workarounds.

It would be really helpful for use-cases like this to be able to render a Svelte application inside a shadow dom.

@stale
Copy link

stale bot commented Jun 26, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@Conduitry
Copy link
Member

In 3.40.0, you can now specify a ShadowRoot as the target when creating a component.

jacobmischka added a commit to mcw-anesthesiology/adverse-events-analyzer that referenced this issue Oct 7, 2021
Doing so requires compiling _all_ components as custom elements, which
for some reason makes child components quite slow in some naive testing.

Would like to revisit if sveltejs/svelte#5869
is resolved.
@Obehi
Copy link

Obehi commented Oct 15, 2022

This problem still exists for me in version 3.50.1. When rendering Svelte inside a shadow dom, the styles are appended to document.head of the html element outside the shadow dom even when using a shadow dom element as target. am I missing something here?

@artemjackson
Copy link

@Obehi, check out the #5870 (comment). You need to pass emitCss: false to the svelte plugin to have the styles appended to shadowRoot instead of document.head:

// suppose you are using vite and vite-plugin-svelte
 
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default {
   plugins: [
      svelte({
         emitCss: false,
      }),
   ]
}

@psociety
Copy link

@Obehi, check out the #5870 (comment). You need to pass emitCss: false to the svelte plugin to have the styles appended to shadowRoot instead of document.head:

// suppose you are using vite and vite-plugin-svelte
 
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default {
   plugins: [
      svelte({
         emitCss: false,
      }),
   ]
}

Nice, this resolves Svelte styles! What about css imports? Is there any way to make sure they're also attached in the same component rather than going to head?

@fallaciousreasoning
Copy link
Contributor

We've created a function for wrapping a Svelte component in a web component and mounting it in a shadowRoot. It seems to work fairly well (significantly better than the built in customElement: true). I wonder if it's worth just removing the built in customElement support as it seems a bit half baked at the moment, and just letting people make their own wrappers (perhaps with an official template?).

FWIW, this is our wrapper:
https://github.com/brave/leo/blob/main/src/components/svelte-web.ts

@ivanhofer
Copy link
Contributor Author

If you take a look at the version 4 milestone, you see that a lot of issues relate to the customElement build:
https://github.com/sveltejs/svelte/milestone/6

I was also wondering why Svelte maintains two different build outputs. In the end a wrapper at the entry point should be good enough.
The only downside I see is that if you output multiple smaller components, wrapping each of them will increase their bundle size.

@reedHam
Copy link

reedHam commented Apr 3, 2023

@Obehi, check out the #5870 (comment). You need to pass emitCss: false to the svelte plugin to have the styles appended to shadowRoot instead of document.head:

// suppose you are using vite and vite-plugin-svelte
 
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default {
   plugins: [
      svelte({
         emitCss: false,
      }),
   ]
}

Nice, this resolves Svelte styles! What about css imports? Is there any way to make sure they're also attached in the same component rather than going to head?

You can do something like this to scope imports to the shadow root.

import Component from 'Component.svelte';
import css from './theme.css?inline';
const shadowRoot = document.body.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<style>${css}</style>`;
new Component({ target: shadowRoot });

@Metehan-Altuntekin
Copy link

Metehan-Altuntekin commented Nov 20, 2023

Shadow method works well. Here is how I did it:

 // create a shadow dom container to prevent css conflicts from the page
 const container = document.createElement("shadow-dom-container");
 const shadowRoot = container.attachShadow({ mode: "open" });
 document.body.appendChild(container);

 // fonts should be loaded in the page
 const fontStyle = document.createElement("style");
 fontStyle.innerHTML = `@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap');`;
 document.head.appendChild(fontStyle);

 const comp = new MySvelteComponent({
   target: shadowRoot,
   props: {
    ...
   },
 });
 

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

10 participants