Skip to content

Instantly share code, notes, and snippets.

@developit
Last active September 4, 2021 01:38
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save developit/689aa4415bd688f3fce923cb8ae9abe7 to your computer and use it in GitHub Desktop.
Save developit/689aa4415bd688f3fce923cb8ae9abe7 to your computer and use it in GitHub Desktop.

Proposal: Importable Constructable Stylesheets

We're getting Constructable Stylesheets. This seems like an intuitive value to obtain when importing CSS from JavaScript, since it's the DOM's representation of a Stylesheet:

import stylesheet from './style.css';
console.log(stylesheet);  // CSSStyleSheet

No such system is in place to allow this to work (see whatwg/loader), however frontend build tooling has congregated around this approach as a mechanism for bringing CSS assets into the JavaScript module graph. There are many benefits to be obtained from moving CSS into this graph, however the most important is that imported CSS can be attributed to the consuming JS Module. This allows it to be bundled, optimized, and potentially dead-code-eliminated leveraging static analysis performed on the surrounding module graph.

However, given the existence of CSSStyleSheet as a 1:1 representation of a stylesheet, it's possible we now have a case for why CSS deserves special treatment.

import sheet from './style.css';

// global CSS:
document.adoptedStyleSheets = [sheet];

const node = document.createElement('div');
const shadow = node.attachShadow({ mode: 'open' });
// scoped CSS:
shadow.adoptedStyleSheets = [sheet];

// Updates! (propagates out to all affected trees)
sheet.insertRule('.foo { color: red; }');

// "hot CSS replacement":
module.hot.accept('style.css', req => {
  sheet.replace(req('style.css'));
});
// in your ServiceWorker: importScripts('polyfill.js')
addEventListener('fetch', e => {
if (e.request.destination === 'script' && e.request.url.endsWith('.css')) {
let res;
e.respondWith(
fetch(e.request)
.then(r => (res = r).text())
.then(css => {
const headers = new Headers(res.headers);
headers.set('content-type', 'application/javascript');
headers.delete('content-length');
return new Response(`
let s = new CSSStyleSheet();
s.replaceSync(${JSON.stringify(css)});
export default s;
`, { headers });
})
);
}
});
@developit
Copy link
Author

developit commented Jan 31, 2019

@littledan
Copy link

littledan commented Mar 6, 2019

Impressive! I'm excited for CSS imports. Nit: Is this form of the CSSStyleSheet constructor, of being called with a string argument, standards-track? I just see an options bag as the argument in the draft spec, and calls without arguments in the explainer.

@developit
Copy link
Author

developit commented Apr 18, 2019

@littledan - oops, that was a mistake! Fixed.

@Jack-Works
Copy link

https://github.com/Jack-Works/loader-with-esmodule-example I made an example (not follow the spec so strict)

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