javascriptcssfirefoxshadow-domnative-web-component

Importing a CSS stylesheet into a WebComponent with Shadow DOM in Firefox


I am using Firefox 141.0.3.

I know there are several ways to have individual CSS properties pierce the Shadow DOM:

In this case I actually want to import an entire stylesheet (and I don't want to write the stylesheet out inline in my .js file).

First, I tried:

import myStyles from '/styles/my-styles.css' with {type: 'css'}

Firefox tells me it doesn't (currently) support assertions:

Uncaught SyntaxError: import assertions are not currently supported

Then, I tried:

import myStyles from '/styles/my-styles.css'

Firefox tells me it doesn't allow the MIME type:

Loading module from “/styles/my-styles.css” was blocked because of a disallowed MIME type (“text/css”)

Then I tried:

const myStyles = new CSSStyleSheet();
myStyles.replace(`@import url('/styles/my-styles.css')`);

Firefox tells me:

@import rules are not yet valid in constructed stylesheets.

What is (currently) the best way to go about importing an external stylesheet into a WebComponent with Shadow DOM in Firefox?


Update:

I came away from this issue for a few days and I've only come back to it this morning.

I needed a better solution, since with the polyfill solution I first implemented below, there were three instances of the WebComponent, which led to the browser fetching the CSS file from the server three times when I hard-refreshed the web page.

But, some time after that, there were twenty-three instances of the WebComponent, which led to the browser fetching the CSS file from the server twenty-three times.

This added 1.5 seconds or more to the initial download time.

I revised my approach and I have gone with the accepted solution below.


Solution

  • N.B. This approach will no longer be necessary once Firefox and Safari support CSS Module Script Imports:

    import myStyles from '/styles/my-styles.css' with {type: 'css'}
    

    See current progress, here:


    A different approach:

    1. The browser loads the WebComponent stylesheet in the <head> of the document, normally:
        <link rel="stylesheet" href="/styles/my-styles.css" />
    
    1. Grab the textual content from that normally-loaded stylesheet:
    const stylesheet = [...document.styleSheets].find((styleSheet) => styleSheet.href.split('/').pop() === 'my-styles.css');
    const stylesheetRules = [...stylesheet.cssRules];
    const stylesheetText = stylesheetRules.reduce((accumulator, rule) => accumulator + rule.cssText, '');
    

    I'd hoped this might be enough, but Firefox errored with:

    Adopted style sheet must be created through the Constructable StyleSheets API

    So...

    1. Construct a StyleSheet and populate it:
    const myStyles = new CSSStyleSheet();
    myStyles.replace(stylesheetText);
    
    1. Finally, have the WebComponent adopt the constructed StyleSheet in the (no longer asynchronous) connectedCallback():
    const myShadow = this.attachShadow({mode: 'open'});
    myShadow.adoptedStyleSheets = [myStyles];
    
    

    Complete Solution:

    <link rel="stylesheet" href="/styles/my-styles.css" />
    
    //**************************************************//
    
    const stylesheet = [...document.styleSheets].find((styleSheet) => styleSheet.href.split('/').pop() === 'my-styles.css');
    const stylesheetRules = [...stylesheet.cssRules];
    const stylesheetText = stylesheetRules.reduce((accumulator, rule) => accumulator + rule.cssText, '');
    
    const myStyles = new CSSStyleSheet();
    myStyles.replace(stylesheetText);
    
    
    class myWebComponent extends HTMLElement {
      constructor() {
        super();
      }
    
      connectedCallback() {
        const myShadow = this.attachShadow({mode: 'open'});
        myShadow.adoptedStyleSheets = [myStyles];
    
    
        [...]
    
      }
    }
    
    

    This approach leads to the stylesheet being loaded once only when the page is hard-refreshed and cached for every soft-reload after that.