Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active December 20, 2019 15:19
Show Gist options
  • Save Rich-Harris/eb1726bd0466efbab89e436166ba1bbd to your computer and use it in GitHub Desktop.
Save Rich-Harris/eb1726bd0466efbab89e436166ba1bbd to your computer and use it in GitHub Desktop.
Optimising CSS modules

WICG/webcomponents#759

Suppose you have an app like this, and your bundler turns .css files into strings:

// main.js
import './x-foo.js';
import './x-bar.js';

document.body.innerHTML = `<x-foo></x-foo><x-bar></x-bar>`;
// x-foo.js
import styles from './x-foo.css';

customElements.define('x-foo', class XFoo extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <style>${styles}</style>
      <p>foo</p>
    `;
  }
});
// x-bar.js
import styles from './x-bar.css';

customElements.define('x-bar', class XBar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <style>${styles}</style>
      <p>bar</p>
    `;
  }
});
/* x-foo.css */
:host {
  color: red;
}
/* x-bar.css */
:host {
  color: bar;
}

Rollup would create a single JS file...

const styles = `:host {
  color: red;
}`;

const styles$1 = `:host {
  color: blue;
}`;

customElements.define('x-foo', class XFoo extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <style>${styles}</style>
      <p>foo</p>
    `;
  }
});

customElements.define('x-bar', class XBar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `
      <style>${styles$1}</style>
      <p>foo</p>
    `;
  }
});

document.body.innerHTML = `<x-foo></x-foo><x-bar></x-bar>`;

CSS modules would allow us to avoid storing styles in JavaScript string blobs, which is a good thing. But the equivalent app...

// x-foo.js
import styles from './x-foo.css';

customElements.define('x-foo', class XFoo extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.adoptedStyleSheets = [styles];
    this.shadowRoot.innerHTML = '<p>foo</p>';
  }
});
// x-bar.js
import styles from './x-bar.css';

customElements.define('x-bar', class XBar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.adoptedStyleSheets = [styles];
    this.shadowRoot.innerHTML = '<p>bar</p>';
  }
});

...can't get optimised into a single JS file and a single CSS file, as far as I can tell. Instead, the JS bundle would need to import the two .css files separately.

Experience has established that shipping n small JavaScript modules yields suboptimal performance relative to coarse-grained code-split chunks. There's no reason to suspect n small CSS files should be any different. I wonder, therefore, if we should consider whether there's an alternative to one-stylesheet-per-file.

@littledan
Copy link

To bundle multiple stylesheets into one, but make them individually applicable to shadow roots, would it work to generate a class for each stylesheet, require it for each selector in the sheet, concatenate the stylesheets, import them as one CSS module, and then make the shadow root have the generated class of the style sheet it intends to select?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment