Skip to content

Multiple stylesheets per file #5629

@justinfagnani

Description

@justinfagnani

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

justinfagnani commented on Oct 16, 2020

@justinfagnani
Author
dandclark

dandclark commented on Oct 16, 2020

@dandclark
Contributor

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

guybedford commented on Oct 18, 2020

@guybedford

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

dandclark commented on Oct 23, 2020

@dandclark
Contributor

@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

justinfagnani commented on Oct 23, 2020

@justinfagnani
Author

Thanks @dandclark! Updated

gsnedders

gsnedders commented on Oct 27, 2020

@gsnedders
Member

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'};

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

justinfagnani commented on Oct 27, 2020

@justinfagnani
Author

@gsnedders correct

tabatkins

tabatkins commented on Oct 29, 2020

@tabatkins
Member

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

leobalter commented on Oct 29, 2020

@leobalter

cc @bmeck this seems like a good addition for the arbitrary module names, if my bundling still want to reference the names somehow.

leobalter

leobalter commented on Oct 29, 2020

@leobalter

I like this as it also helps on a better usage of dynamic import() for css files, IMO.

devongovett

devongovett commented on Jun 11, 2021

@devongovett
Contributor

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

added a commit that references this issue on Jan 15, 2025
332890b
aluhrs13

aluhrs13 commented on Jan 17, 2025

@aluhrs13

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

bramus commented on Jan 21, 2025

@bramus
Contributor

I’m wondering if @export would be a name more consistent with the rest of the web platform. I was confused when I first saw @sheet, but at least to those who write JS, export is a familiar concept. We already have @import, so this would be nicely analogous.

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?

added a commit that references this issue on May 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @devongovett@sorvell@mirisuzanne@EisenbergEffect@LeaVerou

        Issue actions

          Multiple stylesheets per file · Issue #5629 · w3c/csswg-drafts