1
votes

I am "attempting" to use Sveltejs as a front-end web framework for a project built on Django (relevant as it defines the structure of my application). I am attempting to include Svelte components onto the various templates I have built for my Django application. Now, using the customElement API, I was able to compile and use my Svelte components as custom elements directly in my HTML templates which was great except for one major problem: my global styles weren't propagating to the components. This is, as I found out, because all custom elements are actually compiled as web components, meaning they wrap their internal HTML to prevent styles from affecting them. This is not the behavior I want. However, it seems to be the only real way to use my Svelte components in a way that works cohesively with my HTML templates. How do I solve this problem?

I am using webpack as my module bundler. I can comment a gist link to my webpack.config.js if it would be helpful, but as you can imagine, I am compiling for multiple Django "apps" and so my config is a bit messy. For reference, I have gotten everything including local styling with Sass and custom elements to work as intended. This is simply a question of "how to do something" which despite me spending literal hours Googling, I have been unable to find a clear answer to.

I know you can use the client API directly to access components, but not only is this tedious and messy (especially if I need to compose components on different HTML templates together), but I can't seem to get webpack to expose my Svelte components as Javascript classes. Here is how I am doing this approach (which is not working):

<!-- in the head -->
<script src="bundle.js"></script>

<!-- in the body -->
<script>new bundle.App{target: ...}</script>

It is telling me that App is not defined. Here is what my individual output configs in my webpack.config.js look like:

output: {
    library: 'bundle',
    // I do not understand at all what these two lines do -- I just found 
    // them somewhere on the interwebs as a suggestion to solve this problem
    libraryTarget: 'umd', 
    umdNamedDefine: true,
    // path, filename, etc.
}

To boil this down, I really have three, intertwined questions:

  1. Can I use the customElement API (which I much prefer) and still apply global styles?
  2. If I can't use the customElement API, is there a better approach to this problem that would allow for global styles?
  3. If there is no other option, how do I properly use the client API with webpack?
1
I don't know svelte but just a note that global css doesn't affect web components (except for css variables). You'll need to load the css and insert it as a style node in the template or shadow root of the component I should think.Dominic
I know that. I am asking if there is a way to enable global css propagation to web components -- without messing with the shadow root ideally.ComedicChimera

1 Answers

1
votes

TL;DR: There is no clean/perfect answer for this.

For once, there is no way to inject global styles into the Shadow Dom. Having said that there are few things you can try.

First, if you are not using slots, then you can write your own custom element registration function and use that to register elements. You will have to write your own adapter for web components that extends from HTMLElement class. In this approach, each Svelte component would be independent app that you are simply initializing from your web component. This is the best alternative you can explore

Additionally, you can use Constructable Stylesheets. It allows you to programmatically construct a stylesheet object and attach it to a Shadow DOM. Of course, this work only when you have flat components. When your web components are nested within one-another, each would have its Shadow DOM. You would have to create a common global style as a constructable stylesheet and attach to each component. Look here for the example:

const sheet = new CSSStyleSheet();

// Replace all styles synchronously for this style sheet
sheet.replaceSync('p { color: green; }');

class FancyComponent1 extends HTMLElement {

  constructor() {
    super();

    const shadowRoot = this.attachShadow({ mode: 'open' });

    // Attaching the style sheet to the Shadow DOM of this component
    shadowRoot.adoptedStyleSheets = [sheet];

    shadowRoot.innerHTML = `
      <div>
        <p>Hello World</p>
      </div>
    `;

  }
}

class FancyComponent2 extends HTMLElement {

  constructor() {
    super();

    const shadowRoot = this.attachShadow({ mode: 'open' });

    // Same style sheet can also be used by another web component
    shadowRoot.adoptedStyleSheets = [sheet];

    // You can even manipulate the style sheet with plain JS manipulations
    setTimeout(() => shadowRoot.adoptedStyleSheets = [], 2000);

    shadowRoot.innerHTML = `
      <div>
        <p>Hello World</p>
      </div>
    `;

  }
}

In above example, the sheet is a common stylesheet which is being used in two separate web components. But again, you will have to write you own web component wrapper to achieve this.