Skip to content
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

Merged
merged 21 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions AtSheet/explainer.md
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)
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
- [Discussion forum](https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/AtSheet)
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

## 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.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

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` and `<link>` to apply those shared styles to DSDs without the use of Javascript.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

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.

aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
## 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:
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
```css
div {
color: blue;
}

@sheet foo {
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
div {
color: red;
}
}
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
@sheet bar {
div {
font-family: sans-serif;
}
}
```

## Proposal - Importing a specific sheet via `@import`
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
```html
<style>
@import sheet("sheet.css#foo");
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
</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".
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

## 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
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
Shadow DOM isolates styles, but fragment identifiers are global. This enables Declarative Shadow DOM to import `@sheet` references from the light DOM.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

```html
<style>
@sheet foo {
div {
color: red;
}
}
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
</style>
<template shadowrootmode="open">
<link rel="stylesheet" href="#foo" />
<span>I'm in the shadow DOM</span>
</template>
```
or imported from JS:
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
```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' }
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
```

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:
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

```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.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

```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. */
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
@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?*
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

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*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe namedStyleSheets?


## Considered alternatives
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved

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.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
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).
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
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.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
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.
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
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 {
aluhrs13 marked this conversation as resolved.
Show resolved Hide resolved
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ we move them into the [Alumni section](#alumni-) below.
| [Handwriting attribute](Handwriting/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Handwriting"> ![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/Handwriting?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=adettenb&labels=Handwriting&template=Handwriting.md&title=%5BHandwriting%5D+Issue) | HTML |
| [AudioContext Interrupted State](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AudioContextInterruptedState/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/AudioContext%20Interrupted%20State">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/AudioContext%20Interrupted%20State?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=gabrielbrito&labels=AudioContext+Interrupted+State&title=%5BAudioContext+Interrupted+State%5D+%3CTITLE+HERE%3E) | WebAudio |
| [IndexedDB getAllRecords()](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/IndexedDbGetAllEntries/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/IndexedDB%20GetAllRecords">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/IndexedDB%20GetAllRecords?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=SteveBeckerMSFT&labels=IndexedDB%20GetAllRecords&title=%5BIndexedDB+getAllRecords()%5D+%3CTITLE+HERE%3E) | IndexedDB |

| [Mulitple Stylesheets Per File (@sheet)](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AtSheet/explainer.md) | | [New Issue...](https://github.com/w3c/csswg-drafts/issues/5629) | CSS |

# Alumni 🎓

Expand Down
2 changes: 2 additions & 0 deletions ShadowDOM/explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ content location of future work and discussions.
## Background
With the use of web components in web development, web authors often encounter challenges in managing styles, such as distributing global styles into shadow roots and sharing styles across different shadow roots. Markup-based shadow DOM, or [Declarative shadow DOM (DSD)](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom), is a new concept that makes it easier and more efficient to create a shadow DOM definition directly in HTML, without needing JavaScript for setup. [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) provides isolation for CSS, JavaScript, and HTML. Each shadow root has its own separate scope, which means styles defined inside one shadow root do not affect another or the main document.

We're currently investigating this and [@sheet](/AtSheet/explainer.md) in parallel, and anticipate that we'll be prioritizing only one of these two in the immediate future.

## Problem
Sites that make use of Declarative Shadow DOM (DSD) have reported that the lack of a way to reference repeated stylesheets creates large payloads that add large amounts of latency. Authors have repeatedly asked for a way to reference stylesheets from other DSD instances in the same way that frameworks leverage internal data structures to share constructable style sheets via `adoptedStyleSheets`. This Explainer explores several potential solutions.

Expand Down