-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add proposal for @sheet to enable multiple stylesheets per file #931
base: main
Are you sure you want to change the base?
Changes from all commits
15aee25
6c1e94f
b5e5d00
a311c6e
13c1fa3
5000e0a
ffb3ae2
e3f8be4
2c691f9
eeec132
0f7311d
977a53e
d65d1b9
6a3bdf6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# `@sheet` | ||
|
||
## Authors: | ||
|
||
- Andy Luhrs | ||
- Kurt Catti-Schmidt | ||
|
||
Much of this explainer is consolidating and iterating on a CSSWG discussion around [Justin Fagnani](https://github.com/justinfagnani)'s proposal for multiple stylesheets in a single file [here](https://github.com/w3c/csswg-drafts/issues/5629). | ||
|
||
## Participate | ||
- [Issue tracker](https://github.com/w3c/csswg-drafts/issues/5629) | ||
- [Discussion forum](https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/AtSheet) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these two links might be backwards (i.e. the first is linked to the discussion forum, and the second one is actually linked to the Issue tracker) |
||
|
||
## Status of this Document | ||
|
||
This document is intended as a starting point for engaging the community and | ||
standards bodies in developing collaborative solutions fit for standardization. | ||
As the solutions to problems described in this document progress along the | ||
standards-track, we will retain this document as an archive and use this section | ||
to keep the community up-to-date with the most current standards venue and | ||
content location of future work and discussions. | ||
|
||
* This document status: **Active** | ||
* Expected venue: [CSS Working Group](https://www.w3.org/Style/CSS/) | ||
* Current version: this document | ||
|
||
## Introduction | ||
When developing web components, web authors often encounter challenges with distributing global styles into shadow roots and sharing styles across different shadow roots. Declarative shadow DOM (DSD) enables creation of shadow DOM without JS, but adding styles to DSD requires the developer to either use JS to put a shared stylesheet into `adoptedStyleSheets`, or to duplicate the styles in a `<style>` element for each component instance. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might split the second sentence up into two. i.e. "Declarative shadow DOM (DSD) enables creation of shadow DOM without JS. However, ..." |
||
|
||
Additionally, bundling of stylesheets is difficult for developers who are distributing web components. They either need to ship many small stylesheets, or use workarounds like `@import url("data...")` which are suboptimal for performance and don't interact well with other patterns. | ||
|
||
We propose an enhancement to allow declaration of new stylesheets via an `@sheet` CSS block, and using existing mechanisims such as `@import`, `<link>`, and CSS module script `import` to apply those shared styles to DSDs without the use of Javascript. | ||
|
||
We're currently investigating this and [Declarative CSS modules](/ShadowDOM/explainer.md) in parallel, and anticipate that we'll be prioritizing only one of these two in the immediate future. | ||
|
||
## Goals | ||
* Allow the reuse of styles in markup-based shadow DOM without requiring JavaScript. | ||
* Allow reuse of styles in markup-based shadow DOM without requiring external network requests. | ||
* Allow web authors to selectively pass in global styles from the parent document. | ||
* Allow component authors to bundle their CSS into a single file. | ||
* Allow named `@sheet` references to fully integrate with existing CSS inclusion methods such as `@import` statements and `<link>` tags. | ||
|
||
|
||
## Non-goals | ||
Some developers have expressed interest in CSS selectors crossing through the Shadow DOM, as discussed in [issue 909](https://github.com/WICG/webcomponents/issues/909#issuecomment-1977487651). While this scenario is related to sharing styles with Shadow DOM elements, it is solving a different problem and should be addressed separately. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to include a section with motivating use cases similar to https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md? Or do we think that would be redundant? |
||
## Proposal - `@sheet` | ||
Create a new `@sheet` CSS block, for separating style sheets with named identifiers. | ||
|
||
All examples use a stylesheet cased as `sheet.css` with the following contents: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to explain the example briefly. i.e. maybe something like, "As illustrated in the below example, which we will consider to be the contents of a stylesheet called And then I might add the note about the remaining examples utilizing this stylesheet till after the code block. |
||
```css | ||
div { | ||
color: blue; | ||
} | ||
|
||
@sheet foo { | ||
div { | ||
color: red; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: inconsistent spacing between the blocks |
||
@sheet bar { | ||
div { | ||
font-family: sans-serif; | ||
} | ||
} | ||
``` | ||
|
||
## Proposal - Importing a specific sheet via `@import` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I might remove the "Proposal" prefix on the ones below |
||
```html | ||
<style> | ||
@import sheet("sheet.css#foo"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a note about what would happen if you do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I guess that would import everything, so maybe that is obvious. |
||
</style> | ||
``` | ||
|
||
This will import only this rules for `foo` - in this case, the `div { color: red; }` rule, and will *not* import any rules from `sheet.css` outside of "foo". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. " only this rules" -> " only the rules" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also might move everything after "and" into a separate sentence There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These comments apply to the section below, too |
||
|
||
## Proposal - Importing a specific sheet via the `<link>` tag | ||
```html | ||
<link rel="stylesheet" href="sheet.css#foo" /> | ||
``` | ||
|
||
This will also import only this rules for "foo" - in this case, the `div { color: red; }` rule, and will *not* import any rules from `sheet.css` outside of "foo". | ||
|
||
## Proposal - Importing a base set of inline styles into a Declarative Shadow DOM | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also note that DSD could also use the same methodology above to reference a specific sheet from an external stylesheet, or is that implied? |
||
Shadow DOM isolates styles, but fragment identifiers are global. This enables Declarative Shadow DOM to import `@sheet` references from the light DOM. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "fragment identifiers are global" Were we able to confirm this statement is true? |
||
|
||
```html | ||
<style> | ||
@sheet foo { | ||
div { | ||
color: red; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should likely mention somewhere that this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although I see this was mentioned in the open issues section |
||
</style> | ||
<template shadowrootmode="open"> | ||
<link rel="stylesheet" href="#foo" /> | ||
<span>I'm in the shadow DOM</span> | ||
</template> | ||
``` | ||
or imported from JS: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sentence above this was completed with punctuation. Do we want this to be a separate sentence or a continuation of the last? |
||
```html | ||
<script> | ||
import {foo} from './sheet.css' with {type: 'css'}; | ||
... | ||
shadow.adoptedStyleSheets = [foo]; | ||
</script> | ||
``` | ||
|
||
## Detailed design discussion | ||
|
||
#### Named Imports with Imperative Shadow DOM | ||
|
||
`sheet.js` can also be imported via Javascript as follows: | ||
|
||
```js | ||
import baz, { bar } from 'sheet.css' with { type: 'css' } | ||
``` | ||
|
||
`baz` will reference style rules outside of any `@sheet` blocks as a Default Import (in this case, the `div { color: blue; } ` rule). | ||
|
||
`bar` will reference style rules within the `@sheet bar` block as a Named Import (in this case, the `div { color: red; } ` rule). | ||
|
||
Named imports may be renamed as part of this import process: | ||
|
||
```js | ||
import baz, { bar as renamed } from 'sheet.css' with { type: 'css' } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we explicitly state what gets renamed to what to make this clear to those who might not be familiar with the renaming syntax? |
||
``` | ||
|
||
The default import may be omitted, importing only the named `@sheet`: | ||
|
||
```js | ||
import { bar } from 'sheet.css' with { type: 'css' } | ||
``` | ||
|
||
Any of these `import` examples can be then used to set the `adoptedStyleSheets` attribute on a Shadow DOM node: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "can be then" -> "can then be" |
||
|
||
```js | ||
import { bar } from 'sheet.css' with { type: 'css' } | ||
document.adoptedStyleSheets = [bar]; | ||
shadowRoot.adoptedStyleSheets = [bar]; | ||
``` | ||
|
||
#### Performance | ||
|
||
This will be a performance-neutral feature, and use of it may allow for developers to reduce the number of network requests. We should ensure that multiple imports of different sheets from the same file produce a single network request. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can make the argument that eliminating stylesheet duplication is a performance opportunity. Having a bunch of duplicated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "and use of it may allow for developers" -> "but developers can utilize this feature to reduce..." |
||
|
||
```js | ||
// The following two imports should only make a single network request. | ||
import { foo } from 'sheet.css' with { type: 'css' }; | ||
import { bar } from 'sheet.css' with { type: 'css' } | ||
``` | ||
|
||
```html | ||
<style> | ||
/* The following two imports should only make a single network request. */ | ||
@import "sheet.css#foo"; | ||
@import "sheet.css#bar"; | ||
</style> | ||
``` | ||
|
||
```html | ||
<!-- The following two link tags should only make a single network request. --> | ||
<link rel="stylesheet" href="sheet.css#foo" /> | ||
<link rel="stylesheet" href="sheet.css#bar" /> | ||
``` | ||
|
||
#### Interaction with CSSOM | ||
|
||
|
||
Named `@sheet` references augment the [existing](https://drafts.csswg.org/cssom/#stylesheet) `StyleSheet` interface with an optional `name` attribute reflecting the `@sheet` identifier: | ||
|
||
``` | ||
[Exposed=Window] | ||
interface StyleSheet { | ||
readonly attribute DOMString? name; | ||
}; | ||
``` | ||
*Open issue: Should this overload the existing `title` attribute instead?* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we leave all open issues for the open issues section? |
||
|
||
This also expands the [existing](https://drafts.csswg.org/cssom/#cssstylesheet) CSSOM `CSSStyleSheet` definition with a `StyleSheetList` of nested `CSSStyleSheet` objects to access nested `@sheet` references: | ||
|
||
``` | ||
[Exposed=Window] | ||
interface CSSStyleSheet : StyleSheet { | ||
[SameObject] readonly attribute StyleSheetList nestedStyleSheets; | ||
}; | ||
``` | ||
*Open issue: The name `nestedStyleSheets` is up for discussion* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
|
||
## Considered alternatives | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we include all those mentioned in https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md#alternate-proposals (minus |
||
|
||
1. [Declarative CSS Modules](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md) are another mechanism for sharing styles between Declarative Shadow DOM and light DOM without the use of Javascript. | ||
|
||
## Open Issues | ||
|
||
1. Whether rules are applied automatically for `@sheet` definitions, or whether they need to be imported to apply. The CSS Working Group did not have a consensus. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I expect that we'll want them not to apply, to satisfy the scenario where a page wants to define some styles at the top level that will be shared among components used on the page but not used by the base page. |
||
2. Fragment-only identifiers (without a URL) should allow inline `@sheet` references on the same document to be included globally (even within shadow roots). This wasn't brought up in the CSSWG discussions at all, but is important for DSD without requiring an external file (to avoid FOUC). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an open issue since this is what we are proposing? |
||
3. Behavior of `@import` - should this be possible within `@sheet` at all, should it be allowed if it's the first/only statement, or should it be blocked? There was discussion of this in the CSSWG, but no conclusion was reached. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any context we can link to for this one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "This one" being item 3. Behavior of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might reword this to be clearer what we mean by "this" |
||
4. What happens with multiple `@sheet` definitions with the same identifier? First-definition wins, or do they get merged like `@layer`? Again, this was brought up in the CSSWG but not resolved. Note that it's possible to have a "Flash of other-styled content" if it's last-defintion-wins, as the first definition may apply, then a later definition may override it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would this lead to flash of other styled content? Since we apply the full cascade before painting, could that actually happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i.e. I would presume if a DSD uses the same name for the sheet, we would want the last to win instead of the first (unless they combine) |
||
5. Do we want to be able to access sheets declared in shadow DOM from light DOM? For example: | ||
```html | ||
<template shadowrootmode="open"> | ||
<style> | ||
@sheet foo { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing indentation |
||
div { | ||
color: red; | ||
} | ||
} | ||
</style> | ||
<link rel="stylesheet" href="#foo" /> | ||
<span>I'm in the shadow DOM</span> | ||
</template> | ||
|
||
<link rel="stylesheet" href="#foo" /> | ||
<span>I'm in the light DOM</span> | ||
``` | ||
## References & acknowledgements | ||
Many thanks for valuable feedback and advice from: | ||
|
||
- Alison Maher | ||
- Daniel Clark | ||
- Justin Fagnani | ||
- Tab Atkins Jr. | ||
- Tien Mai | ||
- Westbrook Johnson |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like these links might be reversed.