Skip to content

Commit a6cbe4c

Browse files
Merge style sharing explainers (#1382)
We have decided to merge the [LinkTagModuleImport explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/LinkTagModuleImport) with the existing [Declarative CSS Modules](https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/ShadowDOM) explainer so they are all in one place. This greatly simplifies the explainer and keeps it focused on the current proposal. Other related explainers that this replaces now refer to [Declarative CSS Modules](https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/ShadowDOM) A new example was also added that demonstrates that import maps are not necessary.
1 parent 1b02c62 commit a6cbe4c

4 files changed

Lines changed: 250 additions & 1287 deletions

File tree

LinkTagModuleImport/explainer.md

Lines changed: 1 addition & 299 deletions
Original file line numberDiff line numberDiff line change
@@ -1,299 +1 @@
1-
# Style Module Imports via `<link>` Elements
2-
3-
## Authors
4-
5-
- Kurt Catti-Schmidt
6-
7-
## Participate
8-
9-
- [Issue tracker](https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/LinkTagImport)
10-
11-
## Status of this Document
12-
13-
This document is intended as a starting point for engaging the community and
14-
standards bodies in developing collaborative solutions fit for standardization.
15-
As the solutions to problems described in this document progress along the
16-
standards-track, we will retain this document as an archive and use this section
17-
to keep the community up-to-date with the most current standards venue and
18-
content location of future work and discussions.
19-
20-
- This document status: **Active**
21-
- Expected venues: [WHATWG](https://whatwg.org/),
22-
[CSS Working Group](https://www.w3.org/Style/CSS/)
23-
- Current version: this document
24-
25-
## Introduction
26-
27-
Modern web development practices have converged towards building reusable
28-
components instead of building monolithic documents. Technologies such as
29-
Shadow DOM allow for style isolation between components, but in practice this
30-
isolation can lead to duplication and inefficiencies when styling components.
31-
32-
CSS module scripts let JavaScript import a stylesheet as a `CSSStyleSheet`
33-
object that can be applied to multiple shadow roots. Import maps can
34-
declaratively control how the module specifiers used by those JavaScript
35-
imports resolve, but the platform currently requires imperative JavaScript to
36-
apply the resulting stylesheet to each tree scope.
37-
38-
This explainer proposes a declarative way to resolve a module specifier, fetch
39-
the result as a CSS module, and apply the resulting shared stylesheet to
40-
multiple tree scopes using the `<link>` element.
41-
42-
## Goals
43-
44-
- Allow developers to declaratively apply the same CSS module stylesheet to
45-
multiple tree scopes.
46-
- Allow the CSS to reside in an externally cacheable resource while reusing the
47-
CSS module instance for the same resolved URL and module type within the
48-
relevant module map.
49-
- Avoid patterns that would cause measurable performance regressions, such as
50-
stylesheet duplication that cannot be deduplicated.
51-
52-
## Non-goals
53-
54-
- Exporting declarative styles (addressed in
55-
[Declarative CSS Modules](../ShadowDOM/explainer.md)).
56-
- Making module `<link rel="stylesheet">` elements identical in every respect
57-
to classic `<link rel="stylesheet">` elements, `<link rel="modulepreload">`
58-
elements, `<style>` elements, or `adoptedStyleSheets` usage.
59-
60-
## Proposal: Module `<link rel="stylesheet">` Elements
61-
62-
We propose a new module mode for `<link rel="stylesheet">`, selected by
63-
`type="module"`. In this mode, the value of `href` is resolved as a module
64-
specifier through import map processing, using the module
65-
`<link rel="stylesheet">` element's base URL. The resolved URL is then imported
66-
as a CSS module. Once the CSS module has loaded, its styles are applied to the
67-
module `<link rel="stylesheet">` element's tree scope.
68-
69-
For clarity, this explainer calls the existing form a "classic
70-
`<link rel="stylesheet">` element" and the proposed form a "module
71-
`<link rel="stylesheet">` element."
72-
73-
```html
74-
<script type="importmap">
75-
{
76-
"imports": {
77-
"foo": "https://example.com/foo.css"
78-
}
79-
}
80-
</script>
81-
<my-element>
82-
<template shadowrootmode="open">
83-
<link rel="stylesheet" type="module" href="foo">
84-
<p>Inside Shadow DOM</p>
85-
</template>
86-
</my-element>
87-
```
88-
89-
Here, `https://example.com/foo.css` contains the following CSS:
90-
91-
```css
92-
p { color: blue; }
93-
```
94-
95-
With this functionality, the text "Inside Shadow DOM" will be styled blue. The
96-
value of the `href` attribute is resolved as a module specifier using the import
97-
map, `https://example.com/foo.css` is fetched as a CSS module, and the resulting
98-
stylesheet is applied to the shadow root. The shared `CSSStyleSheet` appears in
99-
the shadow root's `styleSheets` collection. For more details on why
100-
`styleSheets` is used instead of `adoptedStyleSheets`, see the
101-
[dedicated section](#imported-stylesheet-appears-in-stylesheets) on this
102-
subject.
103-
104-
### Underlying Stylesheet Is Shared Between Tree Scopes
105-
106-
Within a given module map, module `<link rel="stylesheet">` elements whose
107-
`href` values resolve to the same URL use the same module map entry, keyed by
108-
that URL and the CSS module type, and apply the same `CSSStyleSheet` object.
109-
110-
The following example applies one module stylesheet to the root document tree
111-
and to two distinct shadow roots:
112-
113-
```html
114-
<html>
115-
<head>
116-
<script type="importmap">
117-
{
118-
"imports": {
119-
"foo": "https://example.com/foo.css"
120-
}
121-
}
122-
</script>
123-
<link rel="stylesheet" type="module" href="foo">
124-
</head>
125-
<body>
126-
<p>Text in the document tree</p>
127-
<first-element>
128-
<template shadowrootmode="open">
129-
<link rel="stylesheet" type="module" href="foo">
130-
<p>Inside the first shadow root</p>
131-
</template>
132-
</first-element>
133-
<second-element>
134-
<template shadowrootmode="open">
135-
<link rel="stylesheet" type="module" href="foo">
136-
<p>Inside the second shadow root</p>
137-
</template>
138-
</second-element>
139-
</body>
140-
</html>
141-
```
142-
143-
As a result, modifying the shared stylesheet updates every tree scope to which
144-
it is applied. Once all three module `<link rel="stylesheet">` elements have
145-
loaded successfully, executing the following script will resolve to the
146-
existing CSS module instance in the document's module map:
147-
148-
```js
149-
const foo = (await import("foo", { with: { type: "css" } })).default;
150-
foo.replaceSync("p { color: green; }");
151-
```
152-
153-
The import will reuse the existing module map entry without initiating another
154-
fetch and will update all of the text in the example to green because each
155-
module `<link rel="stylesheet">` element applies the same underlying
156-
`CSSStyleSheet` object to each tree scope.
157-
158-
This capability is not possible with classic `<link rel="stylesheet">`
159-
elements, where each `<link>` element has its own associated stylesheet.
160-
The proposal intentionally extends the stylesheet association model: each
161-
module `<link rel="stylesheet">` element associates the same `CSSStyleSheet`
162-
object with its tree scope, and that object appears in each scope's
163-
`styleSheets` collection.
164-
165-
### Imported Stylesheet Appears in `styleSheets`
166-
167-
Although the underlying `CSSStyleSheet` object is shared, it is deliberately
168-
exposed through `styleSheets` instead of `adoptedStyleSheets`. Exposing
169-
declaratively linked sheets through `adoptedStyleSheets` would allow
170-
script to remove or reorder entries independently of the corresponding
171-
module `<link rel="stylesheet">` elements, breaking synchronization between DOM
172-
order and the applied stylesheet list. The presence and state of qualifying
173-
module `<link rel="stylesheet">` elements instead control membership in the
174-
read-only `styleSheets` collection, and their tree order controls the order of
175-
its entries. This distinction concerns the mutability of the collection's
176-
membership; the shared `CSSStyleSheet` object itself remains mutable, as shown
177-
in the examples above. This proposal therefore extends the CSSOM definition of
178-
which sheets are represented by `styleSheets`, introducing the concept of a
179-
DOM-associated constructed stylesheet.
180-
181-
### Compatibility With Classic `<link rel="stylesheet">` Element Behaviors
182-
183-
Adding module imports to `<link rel="stylesheet">` introduces both similarities
184-
to and differences from classic `<link rel="stylesheet">` elements.
185-
186-
This section covers several broad categories of similarities and differences
187-
between module and classic `<link rel="stylesheet">` elements, each of which
188-
has additional implications.
189-
190-
For a full list of differences and discussion of the options, see the
191-
[planning document](https://docs.google.com/document/d/1SkHwxAIBW5I3uqnmmov4D71ZPbj9woouj3RdPqd3X1w).
192-
193-
#### Many Fundamental `HTMLLinkElement` Behaviors Apply to Module Elements
194-
195-
The `<link>` element supports many fundamental behaviors that will also apply
196-
to module imports, creating similarities between classic and module
197-
`<link rel="stylesheet">` elements.
198-
199-
For instance, the `nonce` attribute and the `onload` and `onerror` event
200-
handlers will apply to module `<link rel="stylesheet">` elements.
201-
202-
#### Some Classic `<link rel="stylesheet">` Element Behaviors Do Not Apply to Module Elements
203-
204-
However, not all features supported by classic `<link rel="stylesheet">`
205-
elements apply in the same way to module `<link rel="stylesheet">` elements.
206-
Several fundamental differences cause their behaviors to diverge.
207-
208-
##### Constructed Stylesheets Have Different Behaviors
209-
210-
Because this proposal builds on existing CSS module script imports, the
211-
stylesheet associated with each module `<link rel="stylesheet">` element is
212-
constructed. This results in behavior that differs from classic
213-
`<link rel="stylesheet">` elements in several ways:
214-
215-
- CSS module scripts do not support `@import` CSS rules.
216-
- A constructed stylesheet always has a `null` `ownerNode`.
217-
- An empty-prelude `@scope` CSS rule normally derives its scoping root from the
218-
stylesheet's `ownerNode`. A constructed stylesheet's `null` `ownerNode`
219-
cannot identify the module `<link rel="stylesheet">` element's parent, and
220-
thus CSS rule matching will differ in this scenario from a classic
221-
`<link rel="stylesheet">` element.
222-
223-
In each of these cases, module `<link rel="stylesheet">` elements will apply
224-
the existing constructed stylesheet behavior, rather than the behaviors of
225-
classic `<link rel="stylesheet">` elements.
226-
227-
##### The Difference in Cardinality Changes the Behavior of Some Attributes
228-
229-
Classic `<link rel="stylesheet">` elements have a one-to-one association with
230-
their `CSSStyleSheet` objects. This proposal deliberately allows multiple
231-
module `<link rel="stylesheet">` elements to share one underlying
232-
`CSSStyleSheet`. As a result, attributes such as `media` and `title` cannot be
233-
mapped directly to the shared stylesheet because each module
234-
`<link rel="stylesheet">` element can apply the same `CSSStyleSheet` object
235-
with different per-element state. There are several potential ways to handle
236-
this scenario, including ignoring these attributes entirely (requiring them to
237-
be set directly on the `CSSStyleSheet` object imperatively),
238-
first-defined-wins, or last-defined-wins, each of which comes with tradeoffs.
239-
240-
##### Module Fetches Are Stricter Than Classic Fetches
241-
242-
Fetches for classic `<link rel="stylesheet">` elements and CSS module scripts
243-
differ. A classic `<link rel="stylesheet">` element creates a potential-CORS
244-
request that uses `no-cors` mode by default; its `crossorigin` attribute can opt
245-
into CORS. Module script requests use `cors` mode, so a cross-origin response
246-
must pass a CORS check. Their decoding also differs. CSS loaded by classic
247-
`<link rel="stylesheet">` elements can use encoding information including a
248-
response-provided encoding, a recognized leading byte sequence that resembles
249-
an `@charset` declaration, or a legacy environment encoding. Module script
250-
responses are always decoded as UTF-8. In essence, module
251-
`<link rel="stylesheet">` element fetches will inherit the stricter module
252-
script fetch semantics, with no option to loosen these restrictions back to the
253-
classic `<link rel="stylesheet">` element fetch behaviors.
254-
255-
## Considered Alternatives
256-
257-
1. [Declarative CSS Modules](../ShadowDOM/explainer.md) are another mechanism
258-
for sharing styles between Declarative Shadow DOM and the document tree
259-
without JavaScript. That proposal introduces `shadowrootadoptedstylesheets`,
260-
which serves a similar purpose. Feedback from working group participants
261-
favors a `<link>`-based approach using `styleSheets` over a new attribute
262-
that uses `adoptedStyleSheets`.
263-
2. [Local References In Link Rel](../LocalReferenceLinkRel/explainer.md) allow
264-
shadow roots to reference stylesheet definitions that are visible through
265-
tree-scoped lookup. They do not provide module-specifier resolution or allow
266-
a stylesheet definition inside one shadow root to be exported to unrelated
267-
roots.
268-
3. CSS-encoded data URI references in `<link>` elements. This approach avoids
269-
some of the issues with 2), but data URI encoding results in poor developer
270-
ergonomics.
271-
272-
## Open Issues
273-
274-
1. How should behavior be defined for the full list of differences from
275-
classic `<link rel="stylesheet">` elements? This is tracked in the
276-
[planning document](https://docs.google.com/document/d/1SkHwxAIBW5I3uqnmmov4D71ZPbj9woouj3RdPqd3X1w).
277-
2. Should we attempt to make this feature backward-compatible with
278-
classic `<link rel="stylesheet">` elements, or are there too many
279-
differences?
280-
3. Is `href` the right way to go, even though it goes through import map
281-
processing? Should we use a new attribute such as `moduleimport`?
282-
4. How should the cardinality issues for attributes like `media` be
283-
addressed?
284-
285-
## References & Acknowledgements
286-
287-
Many thanks for valuable feedback and advice from:
288-
289-
- Alison Maher
290-
- Dan Clark
291-
- Emilio Cobos Álvarez
292-
- Hoch Hochkeppel
293-
- Jake Archibald
294-
- Justin Fagnani
295-
- Keith Cirkel
296-
- Lea Verou
297-
- Mason Freed
298-
- Noam Rosenthal
299-
- Steve Orvell
1+
This explainer has been integrated into [Declarative Shadow DOM Style Sharing](../ShadowDOM/explainer.md).

0 commit comments

Comments
 (0)