-
Notifications
You must be signed in to change notification settings - Fork 736
Description
With Cascading Stylesheet Module scripts on the horizon we will soon have the ability to import CSSStyleSheets directly into JS, like so:
import sheet from './styles.css' assert {type: 'css'};
Problem
The semantics here are fine for unbundled apps, but bundling becomes tricky. If you have two .css files in an app, you can't just combine them. ie:
import sheet1 from './styles1.css' assert {type: 'css'};
import sheet2 from './styles2.css' assert {type: 'css'};
Is not compatible with:
import sheet from './styles1and2.css' assert {type: 'css'};
The current workaround is to compile CSS into JS modules, which defeats some of the performance benefit of having the browser directly load and parse CSS.
Web Bundles might solve this problem generically for multiple file types, though its future on multiple browsers seems unclear right now.
Proposal: @sheet
To fix this and allow bundling of CSS, could we introduce an at-rule that contains an entire style sheet as its contents?
For example, there could be a @sheet
rule which allows files to contain named stylesheets:
styles1and2.css:
@sheet sheet1 {
:host {
display: block;
background: red;
}
}
@sheet sheet2 {
p {
color: blue;
}
}
These could be imported separately from JS:
import {sheet1, sheet2} from './styles1and2.css' assert {type: 'css'};
And also be available on the main style sheet:
import styles, {sheet1, sheet2} from './styles1and2.css' assert {type: 'css'};
styles.sheet1 === sheet1;
styles.sheet2 === sheet2;
Relation to existing approaches
The proposal is most obviously relevant to code that manages CSSStyleSheets in JS - ie, users of Constructible StyleSheets and the API currently named adoptedStyleSheets.
It would also be useful as a bridge to userland CSS loaders that do bundling and scoping via selector rewriting. By standardizing bundling, scoping could be done with client-side utilities:
import {sheet1, sheet2} from './styles.css' assert {type: 'css' };
// doesn't yet exist, but a utility that re-writes class selectors and returns
// an object with a .sheet property and properties for each class
import {scopeSheet} from 'css-module-utilities';
const scopedSheet1 = scopeSheet(sheet1);
const scopedSheet2 = scopeSheet(sheet2);
document.adoptedStyleSheets.push(scopedSheet1.sheet, scopedSheet2.sheet);
document.append(`<div class="${scopedSheet1.fooClass}"></div>`);
document.append(`<div class="${scopedSheet2.barClass}"></div>`);
Activity
justinfagnani commentedon Oct 16, 2020
cc @dandclark @yuzhehan
dandclark commentedon Oct 16, 2020
Early thoughts: I like this, though it might not replace CSS bundlers in some really performance-sensitive cases. There is still one fetch incurred for the
import {sheet1, sheet1} from './styles1and2.css' assert {type: 'css'};
statement that would be eliminated by bundling. If the perf benefit of eliminating this last extra fetch is greater than the perf benefit of parsing everything directly as CSS [1], then there might not be a performance win for using this instead of a bundler.But, it reduces the cost of using CSS modules in production to just 1 extra fetch, which is down from N extra fetches for N stylesheets. So for the cost of the one fetch, you cut out one part of the build/bundling process, get some perf benefit from parsing CSS directly without passing it through the JS parser, and the resulting production code will be easier to read and reason about than production code that had CSS bundled in the JS.
[1] Last year I did some rough experiments to try to measure this potential perf benefit. I observed real differences in both time and memory, although you need a lot of iterations before it starts to be really observable: https://dandclark.github.io/json-css-module-notes/#css-module-performancememory-examples
guybedford commentedon Oct 18, 2020
If there is a way to add this to the polyfill I would be more than happy to include this in the SystemJS module types polyfill as well. It seems a great feature.
dandclark commentedon Oct 23, 2020
@justinfagnani A small typo correction for clarity: you've got
{sheet1, sheet1}
in a few places where I think it should be{sheet1, sheet2}
.justinfagnani commentedon Oct 23, 2020
Thanks @dandclark! Updated
gsnedders commentedon Oct 27, 2020
Dumb question from someone lacking context about CSS modules: these aren't compatible because sheet1 and sheet2 are both
CSSStyleSheet
objects consisting of their respective parts, right? Hence you need some way to split up the concatenated stylesheet back up into its constituent parts?justinfagnani commentedon Oct 27, 2020
@gsnedders correct
tabatkins commentedon Oct 29, 2020
This sounds fairly reasonable to me. Semantically, this is basically a more convenient way to write
@import url("data:...");
, with the potential to hook into CSS Modules a little better.leobalter commentedon Oct 29, 2020
cc @bmeck this seems like a good addition for the arbitrary module names, if my bundling still want to reference the names somehow.
leobalter commentedon Oct 29, 2020
I like this as it also helps on a better usage of dynamic
import()
for css files, IMO.devongovett commentedon Jun 11, 2021
This proposal is very interesting to the Parcel team, and seems to mirror the Module Fragments proposal in the JavaScript world. Current approaches to bundling CSS by simply concatenating them are prone to ordering issues that can cause specificity problems. Sometimes bundling is simply not possible to do correctly due to this. An approach that allows natively combining separate sheets together that can be applied in the correct order would be very useful. 👍
52 remaining items
adoptedstylesheets
attribute whatwg/html#10673Add proposal for @sheet to enable multiple stylesheets per file (#931)
aluhrs13 commentedon Jan 17, 2025
Hey all - We're iterating on this proposal a bit to try and cover the scenarios discussed here as well as making progress on the problem with style sharing into Shadow DOM. We have an explainer here - https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AtSheet/explainer.md
We've opened a new issue to highlight some of the shifts and additions we've made - #11509
We believe this explainer captures everything discussed here, but something might've slipped through the cracks. Would love any and all feedback on it either here or in the other issue I linked.
bramus commentedon Jan 21, 2025
Everything in a style sheet get exported by default, so all contents of all style sheets – using
@sheet
or not – are implicitly wrapped in a (non-existent)@export
, no?@base
statement orbase()
function for relative urls in@sheet
. #11680Add proposal for @sheet to enable multiple stylesheets per file (Micr…