-
Notifications
You must be signed in to change notification settings - Fork 378
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
Declarative CSS Module Scripts #939
Comments
@justinfagnani is your |
Yes, shadow roots need to be able to reference multiple style sheets. I don't personally see this as that much related to #909 ("open-styleable" shadow roots). Rather than allowing anything from the outside to reach into a shadow root, this is just attempting to replicate JS references to these objects in HTML. it shouldn't be loosening encapsulation at all. |
Another option instead of - or maybe in addition to - cross-scope refs is to allow inlining of all module types, keying them by URL that adds them to the module cache as if they had been imported. This would allow a page to render with the necessary CSS modules, attach them to the correct scopes, and later to load JS modules that import them without double-loading them.
<script type="css" specifier="/foo.css">
:host {
color: red
}
</script> A later import: Full example: <my-element>
<template shadowroot="open" css-modules="/foo.css /bar.css">
<script type="css" specifier="/foo.css">
:host {
color: red
}
</script>
<!-- ... -->
</template>
</my-element>
<my-element>
<template shadowroot="open" css-modules="/foo.css /bar.css">
<!-- ... -->
</template>
</my-element> This would have to be made coherent with import maps, but it would allow a page to be rendered with correct scoped styles w/o FOUC and w/o duplication of CSS text. |
Being
Actually be:
In this wa How would you make these "declarative CSS modules" available in an imperative context? Should there be API to allow for resolution of the actual Stylesheet object from the Inversely, should stylesheets that are registered imperatively have API added by which to associate an ID (a la |
The example of
Seems like a large change for developers, would it make a shorter step from historic practices to apply this concept via
OR
In this way we could leverage already existing blocking mechanisms while informing the browser not to apply these styles until adopted. It's possible that Question that applies to both of these approaches, do you expect that values applied to |
+1, I think the idea of using an attribute to provide a URL / specifier is a really interesting thing to explore because it seems like it would generalize well to basically anything in HTML that describes some other resource and has either inline and external forms. (I'm writing it here as So, not only could you have: <script type="module" cacheas="./some-path">
export let aString = "Hello, World!"
</script>
<script type="module">
import {aString} from "./some-path";
console.log(aString);
</script> and <style cacheas="./some-path">
body::before {
content: 'Hello, World!';
}
</style>
<link rel="stylesheet" href="./some-path"> But also things like: <img cacheas="./some-path" src='data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="120" height="60"><text y="30" x="30">Hello, World!</text></svg>'>
<img src="./some-path"> |
@mfreed7 if we wanted this to be a more general feature, should we move the issue to HTML? |
Am I reading right that this idea is a form of declarative module blocks? A bit off-topic, but I'm wondering how that could play with import assertions and this concept, seems like a lot of the semantics are the same … |
Yeah, URL based namespacing is exactly like JS modules. In theory, you should be able to define an inline (e.g. data URI) style inside import maps. So the only additional mechanism needed here is the ability to adopt it. How about |
@rniwa Would that cc @mfreed7, @justinfagnani what do you think of the idea @rniwa is proposing here? |
@justinfagnani , why to limit the I.e. this proposal has to be converted to something like "custom elements library scope". If agree, the name |
@matthewp @rniwa that seems good to me. My main ask is for a way to emit a style module at the first use site when streaming server rendering a large tree of components, so that further components can use that same style sheet. This is needed because some modern streaming SSR approaches do not know what components will be rendered ahead of time, due to request-specific data and data-dependent templating. My other thought is whether this is a thing that should apply to all types of module scripts - where an inline tag can contain content, but populate the module cache. |
I would prefer a syntax on the The main perf benefit of adopted stylesheets that I've seen in my testing is just bypassing the DOM and avoiding the cost of creating/inserting a |
Update: after discussion, #939 (comment) seems good to me. In SSG (non-streaming) at least, you can hoist the first |
WCCG had their spring F2F in which this was discussed. You can read the full notes of the discussion (#978 (comment)) in which this was discussed, heading entitled "Declarative Adopted Stylesheets". In the discussion, present members of WCCG reached a consensus that this needs further discussion with implementers. I'd like to point out that representatives of Apple, Google, and Mozilla were present in the meeting, but no firmer conclusion was made. This issue will likely require implementer feedback. |
I'm not sure if this is the right place or if it is more related to #909 (open-stylable). For https://github.com/webtides/element-js/blob/main/src/StyledElement.js we are currently duplicating inlined styles to make them adoptable. <html>
<head>
<style id="globalStyles">/* all of TailwindCSS... */</style>
</head>
... const globalStyles = document.getElementById('globalStyles');
class MyElement extends HTMLElement {
constructor() {
const shadow = this.attachShadow({ mode: 'open' });
const sheet = new CSSStyleSheet();
sheet.replaceSync(globalStyles.textContent);
shadow.adoptedStyleSheets = [sheet];
}
} I hope that it will be possible to share styles between the global document and shadow roots instead of duplicating them. |
This is the only thing stopping me to build a perfect solution. It would be great if we had something like: External Moduleindex.html
app.js (when not loaded in main html and dynamically loaded, we want to lazy get the css file)
Internal Moduleindex.html
app.ts (when it is in the dom, we want to add it to the adoptedStyleSheet)
That way the DOM parsing would be blocked until the CSS loads. |
I like the For example, common, rarely changing CSS could be multiple sheets in a single, cacheable file, while individual component css could be inlined. You might have something like this: <link rel="stylesheet" href="design-system.css" specifier="/design-system.css">
<my-element>
<template shadowrootmode="open" css-modules="/design-system.css#typography /my-element.css">
<script type="css" specifier="/my-element.css">
:host {
color: red
}
</script>
<!-- ... -->
</template>
</my-element>
<my-element>
<template shadowrootmode="open" css-modules="/design-system.css#typography /my-element.css">
<!-- ... -->
</template>
</my-element> And in JS you could also do this: import { typography } from "./design-system.css" with { type: "css" };
import styles from "./my-element.css" with { type: "css" };
export class MyElement extends HTMLElement {
constructor() {
super();
// Elided: skip if DSD already exists.
this.attachShadow({ mode: "open" }).adoptedStyleSheets.push(typography, styles);
}
}
customElements.define("my-element", MyElement); This should also work well for component authors who want to distribute their entire design system, providing a single, external CSS file that their customers can easily link, edit, or replace, without component code alterations needed. |
Can someone share the use cases for this thing? I understand the theoretical need for a serializable form of adopted stylesheets, but I have many concerns with the direction this is going. What exactly does this provide that a regular |
Off the top of my head, this provides:
I believe some use cases have already been listed, but:
|
@EisenbergEffect I appreciate your comment, but most of these things should be covered by You mentioned modules, but this proposal doesn't really address that (hence the name "CSS module scripts"). True CSS modules would need to be usable within CSS itself, even before HTML or JS is involved. I'd imagine that CSS modules would allow a It's also worth pointing out that DSD still requires repetition of the shadow markup today. When we get HTML modules (and/or DCE), it would reduce this markup repetition. Since Lastly, I want to highlight w3c/csswg-drafts#10013, which was recently greenlit. It may sound unrelated, but in my mind it opens up the potential for these two syntaxes to refer to the exact same stylesheet instance: <link rel="stylesheet" href="design-system.css#button"> import buttonStyles from "design-system.css#button" with { type: "css" }; |
The main purpose of this propose is the same as declarative shadow DOM: to be able to accurately serialize a valid DOM tree to HTML. Right now there's no way to properly serialize adoptedStyleSheets. During SSR you can convert them to |
The |
It's still non-optimal and breaks semantics. Constructible stylesheets actually share a single stylesheet instance, something that doesn't happen with There is still a performance hit from so many
|
@mayank99 has there explicitly been discussion to date that implied that files included via <link rel="stylesheet" href="design-system.css"> While being added to the module graph so that extended usage of the same in JS would essentially be import { button as buttonStyles } from "design-system.css" with { type: "css" };
import element from '/my-element.css' with { type: 'css' };
class El extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.adoptedStyleSheets = [buttonStyles, elementStyles];
}
} Would be awesome! Passing that same theoretical approach to developers using DSD would be important to ensure that they can share learned concepts between the HTML-first or JS-first implementations of a shadow root. <my-element>
<template shadowrootmode="open" adoped-style-sheets="/design-system.css#button /my-element.css">
<!-- ... -->
</template>
</my-element> Expanding this to include inline <link rel="stylesheet" href="design-system.css">
<style type="module" href="/my-element.css">
// styles
</style>
<my-element>
<template shadowrootmode="open" adoped-style-sheets="/design-system.css#button /my-element.css">
<!-- ... -->
</template>
</my-element>
<script type="module">
import { button as buttonStyles } from "design-system.css" with { type: "css" };
import element from '/my-element.css' with { type: 'css' };
class El extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.adoptedStyleSheets = [buttonStyles, elementStyles];
}
}
</script> 😍 Editors note: APIs and attribute name FPO only! |
@Westbrook Not yet, but that's one of the things I'm hoping will be discussed in CSSWG when we talk about adoptable non-constructed stylesheets and |
Related proposal from the Edge team: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md |
@KurtCattiSchmidt, @dandclark, and Tien Mai have provided an excellent analysis/proposal loosely titled "Declarative shadow DOM style sharing". Framed as "a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization", as I understand it, in my words it envisions:
Many thanks for potentially moving forward multiple issues like #909, #910, #939, #10176 and more. I have made some suggestions in this slide deck with examples on github. |
Declarative shadow roots and constructible stylesheets need to be made to work together so that stylesheets added to shadow roots can be round-tripped through HTML serialization and deserialization.
As of now, SSR code will have to read a shadow root's adopted stylesheets and write them to HTML as
<style>
tags. While this will style they shadow roots correctly on first render, it results in bloated a HTML payload and breaks the shared stylehseet semantics. Hydration code would have to deduplicate<style>
tags and create and attach constructed stylesheets in order to reconstruct the origin DOM structure.To solve this we need a serialized for of constructible stylesheets. These are stylesheets that do not automatically apply to any scope, but can be associated and applied by reference.
Requirements:
One idea is to add a new type for
<style>
tags that accepts CSS text and creates a new constructed stylesheet:This style can then be associated with a declarative shadow root:
Having a type other than text/css means that the styles won't apply to the document scope even in older browsers. It's also what allows it to have an adoptable CSSStyleSheet .sheet, which replace() works on as well.
One problem that immediately come up is scopes and idrefs. If a declarative stylesheet is only addressable from within a single scope, it's no better than a plain
<style>
tag because it would have to be duplicated in every scope that uses it. We need some sort of cross-scope idref mechanism. With that we can write the styles with the first scope that uses them, and reference them in later scopes in the document:Here
xid
is a stand-in for some kind of cross-scope ID."x61h8cys"
is a stand-in for a GUID or other globally unique value (such as a hash of the style text) that the SSR code would be responsible for generating.There are other instances where we need cross-scope references, like ARIA and label/input associations. I'm not sure if these cases are similar enough to use the same mechanism or not.
The text was updated successfully, but these errors were encountered: