-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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 Modules and Declarative Shadow DOM adoptedstylesheets
attribute
#10673
Comments
The most alarming part of this proposal to me is introducing the How necessary is that to this idea? Could you instead use, e.g., the |
That's the most noble part of this proposal. It's a feature which allows CSS module to be defined inline.
I don't think we want to use id for this purpose since id's are not shared across shadow boundaries. |
I saw this was discussed at WHATNOT and some new issues were opened. Let it be clear that I don't consider my above objections resolved and I would not be OK with this proceeding further in its current state. This completely new way of manipulating the module map needs significant further discussion and ideally an alternative should be found that either generalizes beyond this specific new feature, or avoids manipulating the module map entirely. If you want to open a new issue to start that discussion that might be good.
This doesn't seem like a blocker to me. Ultimately you have to decide whether you want to pierce shadow boundaries by using a global shared namespace, like the module map / global ID map, or whether you want to use a per-shadow root namespace. The current proposal wants a global shared namespace. So assuming that, some potential workarounds are:
|
The module map behavior is probably the largest piece of this proposal, so I think it makes sense to discuss here.
The module map is already global when done via script, and it already supports stylesheets. The only difference with this proposal is that it's being done with markup instead of script. See https://web.dev/articles/css-module-scripts. @domenic, can you clarify why you're opposed to using the module map for this? Note that this was also discussed in two TPAC breakout sessions, where the discussions included all 3 implementers and people from the WC developer community, and feedback was generally favorable towards the module approach. It's certainly possible that the
The module map is already global when accessed via script (and already supports global stylesheets), so I don't see the advantage of introducing another global map. I would also expect a
Are you referring to shadowrootreferencetarget? It is somewhat similar, but only allows references to propagate inside the shadow root - https://github.com/WICG/webcomponents/blob/gh-pages/proposals/reference-target-explainer.md, so styles defined in shadow roots couldn't be added to the global map (i.e. it could allow the shadow root to access global styles, but it wouldn't allow shadow roots to define styles that can be used globally). We also probably don't want to diverge too far from the existing script-based CSS Module Scripts. Requiring dependencies on other features (especially esoteric ones) seems counter to that. |
Having attended at least one of those I don't think that's an accurate characterization. There was quite a bit of confusion as to how this would work, that it was different from everything else we have in HTML to date, and that this would warrant further discussion. |
There were a number of open questions that we need to continue discussing -- thanks @KurtCattiSchmidt for starting to get issues opened for these -- but my impression was that the module-based approach was the one that had the most energy and positive sentiment around it out of the many ideas Kurt presented for solving this problem. The modules-based approach had gotten particularly positive feedback both during and outside of TPAC from the WebComponents dev community (thanks to folks like @justinfagnani and @Westbrook from sharing their thoughts). And while questions and concerns were raised from implementers, I wasn't picking up on feedback recommending we move away from the overall modules-based approach or pursue a different path. (I definitely don't want to mischaracterize anyone's position; notes from the sessions can be found here and here.) @domenic (and others!) it would be great if you can share the specific concerns you have with the module-based approach so we can discuss. In the meantime we plan to continue thinking through the open questions that have already been raised and working on shoring up some of the more hand-wavy areas of the proposal. |
The idea that: <script type="css-module" specifier="/foo.css">
#content {
color: red;
}
</script>
<my-element>
<template shadowrootmode="open" adoptedstylesheets="/foo.css">
<!-- ... -->
</template>
</my-element> Is an almost 100% declarative translation of: import style from '/foo.css' with { type: 'css' };
class MyElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.adoptedStyleSheets = [ style ];
this.shadowRoot.innerHTML = '...';
}
}
customElements.define('my-element', MyElement); Is incredibly exciting to the Web Components Community Group and the greater community made up by its members' various networks. It's seemingly only a few steps away from fully declarative custom elements, which seem just as interesting to implementors as they do to the developing communities served by the hard work those implementors are putting into browsers. The way it implies a future syntax for other module scriptable content types is also quite nice, and quite needed in the content of each (JSON, CSS, HTML, WASM, et al). If there is anything the WCCG can do to support moving the conversation around this API forward (testing [we've been working on WPT coverage for a number of features as of late and are happy to expand out efforts for such an important API], feedback [point us to the right place], demos [we actually had a series of presentation in the Spring around Declarative Custom Elements concepts, many of which featured the need for an API like this], etc.), please let us know! |
I think I did? I'll try restating:
I appreciate that authors find the connection between these technologies exciting. But we have to look at use cases, and how we can accomplish them without overly complicating or special-casing the platform. So far I have not seen a use case that requires modules as a technology; the use case is roughly "declarative adopted stylesheets", which can be accomplished in many other, less complex and less precedent-breaking ways. I'm sorry that I wasn't in the room where this was discussed at TPAC, but I want to stress that the WHATWG working mode is async-first, and that this is the first thread where this proposal has been presented to the WHATWG community in a way that equitably allows all participants to comment without attending a specific meeting. |
I'm not sure a global map would be beneficial to a page level developer achieve their goals in that not populating the module graph with import style from '/foo.css' with { type: 'css' };
class MyOtherElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.adoptedStyleSheets = [ style ];
this.shadowRoot.innerHTML = '...';
}
}
customElements.define('my-other-element', MyElement); While I could envision paths by which a global ID map were bent to the will of the developer in this case, it's hard to see a path that does not require a developer to more heavily rely on tooling in that the ability to convert the standard code of: import style from '/foo.css' with { type: 'css' };
// elide complexity
this.shadowRoot.adoptedStyleSheets = [ style ]; to something like: this.shadowRoot.adoptedStyleSheets = [ someMagicMapBoundToWindow.get('foo.css') ]; This wide separation of what is written and what is shipped seems like a lot when the module graph already exists exactly for this sort of responsibility. On the topic of other module types, some feel that even including them in the explainer is preparing it for derision (due to an even broader expanse of unanswered questions). However, there is specific reference to HTML module scripts in the explainer. I greatly agree that clarifying that the @domenic does bring up a question I hadn't seen before about the mutability of the
|
Good questions @domenic!
I agree, this is probably the most controversial part of this design. However, it's not decided whether this will not correspond to network requests. See #10711. Regardless of the outcome of that issue though, developers want an alternative that can be done without external CSS files. Good points on import maps - I will need to do some investigation on how these can interact. Also mutating the
This doesn't preclude doing so for other types of modules. We can certainly spec other modules in a similar way.
The module map is already being used as a global map of constructable stylesheets. See https://web.dev/articles/css-module-scripts.
I covered quite a few alternatives (including global ID scoping withing shadow DOM) in my TPAC discussion, and why each of them comes with unfortunate tradeoffs - I would encourage you to take a look at my slides. Happy to discuss alternatives here though, in case those tradeoffs are not dealbreakers.
No need to apologize, and agreed, this is the right place to discuss these kinds of concerns. |
Thanks all for the discussion! I'm glad we're able to get into the technical details like this. @Westbrook: I'm a bit confused by your comment. Before, you were talking about making declarative CSS modules + shadow DOM an easy translation from the imperative versions. Now you are talking about... making one version of imperative CSS modules + shadow DOM into a different imperative version? That doesn't seem related to the goal of this proposal, from what I can tell. But I'll try to engage anyway:
No "some magic map bound to window" necessary. You can use How to get from the element to the
To be clear, adding more complexity to a feature I'm objecting to, by also adding network request support, doesn't ameliorate my objections to the base feature :)
I'd encourage you to take a step back and engage with alternatives, as I think they'll be a lot more feasible to reach consensus by sidestepping a lot of these problems.
In my experience, module features need to be designed holistically. I wouldn't be comfortable with doing something CSS-specific that changes the fundamental nature of the module system in such a way. It would need to support all existing module types (JS, wasm, CSS, JSON) from day 0 in order to reach consensus.
That's not really accurate. (Please try to refer to the spec, instead of third-party articles which can be imprecise.) The module map is used as a global map of modules. Sometimes those modules can be CSS modules. CSS modules are exposed to JavaScript as It is not used a as global map of
I assume you're referring to slide 20? Here are my takes on the problems identified there. TLDR they are not really problems.
I can't understand what point this is concretely making. Remember, we need to stay focused on the use cases and requirements (which your slides list). This seems to be an aesthetic preference between objects vs. strings? Can you give an example of a thing a web developer wants to accomplish where strings vs. objects makes a difference? If this is about just the logistics of how to assign to
There are alternatives that are more complex, based off of defining inline CSS module scripts (#7367) and then accessing their default export (#7415). I would not object to going that route, as long as you did the design work for CSS/JSON/WASM all at the same time. But it's much more complex, so since the above seems like it accomplishes your use cases, I would suggest starting there. (In case it is confusing why I would not object to something based off of inline CSS module scripts, but I do object to the
The
Again, we need to stay focused on the use case, of "declarative adopted stylesheets". Creating over-generic solutions, ahead of the actual use cases, is dangerous. If you want to expand the requirements to include other resources, gather evidence for the web developer need, and solve all of them at the same time, then I'd be cautious but supportive. But we cannot tackle just CSS, and vaguely hope that our solution will work well for other resources in the future. |
Good point :) However, the premise of this feature is to allow
That makes sense. If we do go down this route, I'm happy to incorporate those into the spec.
The current behavior of Your original post said it was "unnecessarily entangling two parts of the platform" - can you clarify which two parts of the platform you're referring to? I assumed you meant 1) stylesheets and 2) the module map, but this response further confirms that they are already entangled.
The biggest issue with any ID-based approach is that ID's are scoped to shadow roots. This makes any ID-based approach fundamentally incompatible with shadow DOM, as one of shadow DOM's main features is ID isolation. You mentioned earlier about a global ID, which would address this issue, but that comes with its own set of tradeoffs. But I'm happy to discuss those in this thread.
My slides didn't really cover this, but in-person I mentioned the asymmetrical nature of things like:
The only existing attribute I can think of that take a list of ID's are the Aria properties like That's an option here, but since the DOM API existed first (unlike with
It's more of a consistency thing, as I don't see any other API's that behave so differently between the DOM API and HTML attribute. But I might wrong here.
By making a stylesheet disabled, it's also disabled when adopted (and thus not applied), so I don't see how it solves this issue. See codepen here. Using a different
This is great advice, much appreciated.
|
This is not accurate; I wrote out the actual specified behavior in more detail above. But, this kind of discussion about exactly what the spec says is not that important, since it's not focused on the use cases and how we can solve them. So I'm happy to drop this.
I mean, inline HTML markup (whether modules or
Please explain this issue. In particular, I've asked repeatedly in this thread for cases where the stylesheet cannot be hosted outside the shadow DOM. Nobody has provided one so far, and all examples (e.g. in the presentation, or in #10673 (comment)) locate the stylesheet outside of the shadow DOM. Maybe there's a misconception that you cannot refer to ID-based elements outside the shadow DOM, from inside the shadow DOM? That's not correct. The shadow DOM design constraints are:
There's no constraint which prevents markup inside the shadow DOM from seeing outside the shadow DOM.
Thanks for explaining. It sounds like we agree that this isn't a requirement.
That is why I included the line in my proposal about modifying the behavior of the
Thanks so much for summarizing the use cases and zooming out. I agree with everything you wrote here, even including "Plugging into modules seems to be the best path forward for solving this holistically, for multiple content types"! The part where we diverge is the use of the module map, vs. using inline modules. So my suggested decision tree would be:
|
Thanks for the questions @domenic I meant to reply earlier, so I'll go back to your first list
I think these modules should generally correspond to importable modules. We're trying to figure out how to serialize a CSS module import that's been adopted to a shadow root. The URL that the inline module uses should match the resolved URL of the CSS module so that the CSS import shares the module map entry.
I agree, and personally want this feature to be a general module inlining feature. I know of use cases for inline JS modules that are sill importable by external modules.
The should really be modules. It shouldn't be a map of stylesheets, but the existing map of modules, some of which are CSS modules.
Working across shadow roots is a critical feature. Without that this whole thing doesn't solve anything wrt the current situation where you have to duplicate all stylesheets used in shadow roots. The point is to deduplicate all the stylesheets that have to be repeated today.
It's not possible to hoist these modules because you only discover them as you render components. You don't know ahead of time which components will be rendered. You could hook module loading on the server and emit every CSS module that's imported in the graph, but that could be wildly inefficient. Highly complex dynamic pages may only render a small fraction of the components in the graph for any given URL. For example, let's say you have root component A that dynamically renders either child component B or C, each of which dynamically render D or E (for B), or F or G (for C). On the server you don't know if you're going to render B or C until you run the actual logic for A, and on down the tree. So you render this: <x-a>
<template shadowrootmode="open">
<x-c> And here is where you need to emit the styles for |
Here is a thought-exercise repo that demos using import maps, which already in Chrome support CSS modules from files and (could?) sidestep or at least scope the specifier attribute issue, while also providing the necessary capabilities of a mutable adopt attribute, modifiable stylesheets, and a working import(). <script type="importmap">
{
"imports": {
"dashedstyles": "./dashedstyles.css",
"solidstyles": "./solidstyles.css",
"toggleBorderSizes": "./toggleBorderSizes.js"
}
}
</script> To me, the "simple" approach of using a script or style tag's id or global id to assign styles to adoptedStyleSheets could simplify the "export" side of this proposal by avoiding the module map altogether. But it would put more complexity on the "import" side, because to build out features like a mutable importing attribute and modifying the underlying CSSStylesheet associated with an exported style would seem to require some kind of parallel tracking and referencing system. So +1 to "we cannot tackle just CSS, and vaguely hope that our solution will work well for other resources in the future" and we should do the "design work for CSS/JSON/WASM all at the same time". To me, import maps, and the multiple import maps work already well underway hint that polymorphic URL module specifiers would be both confusing to web developers and difficult to implement. It might be a more reasonable tradeoff to have a constraint similar to what is already on import maps that declarative inline modules must be must be declared and processed before any Although import maps don't support inline modules (at least today, though this thought exercise implies that might be worth considering), they are at least declarative-ish. And their preprocessor-ish nature parallels in part the need for declarative shadow DOMs to be HTML-parsed early. And like bare specifiers they provide a convenient way to reference, add, modify, and remove adopted stylesheets. From a Web developer point of view, this would be simpler than the above sounds, as the gif in the repo show:
Resources other than CSS do not have an exact equivalent to adoptStylesheets, so that needs to be thought through also. |
For anybody not already following along, there is a parallel discussion about this same topic here: w3ctag/design-reviews#1000 |
This is a really exciting proposal! One potential issue I want to raise is with polyfills: could there be a declarative way to detect support, which would allow for falling back to I ask because a web author using this API will have to support older browsers for some time, and although you could imagine injecting The analogy here would be with <my-element>
<template shadowrootmode="open" adoptedstylesheets="/foo.css">
<link rel="stylesheet" href="/foo.css" noadoptedstylesheets> <!-- no-op on newer browsers -->
</template>
</my-element> |
In my opinion, putting CSS styles inside Just use a set of global |
Using the # symbol as a dedicated inline module specifier could also untangle the race-conditiony dual-use '/foo.css' specifier in the original proposal. In other words, add a fourth type of specifier for inline modules to the three current types of specifiers (relative, absolute, and bare). This would work with dynamic and static imports and import maps, and be consistent with the existing module system. And open up the idea of inline modules to a broader range of use cases. And the # symbol is already used to mean the id of an element:
The # symbol as a reference to a light DOM element by id could also be used for the simpler approach to adoptable style sheets by light DOM element ids. |
I disagree and think it's more consistent for a feature that inlines modules. All inlined modules would be represented with a
This doesn't work because it would require you to know all the styles you want to inline before you emit any of your components. You'd have to emit all possible styles, even if you only use a subset of them for the page. |
@justinfagnani What's the difference between |
An important consideration is what we're gonna do with HTML modules. Using |
Ah, true. And there's no way to escape a |
I think HTML modules and recently here, are due for a back-to-basics TAG design re-review in light of modern developments of import attributes, import map constraints and DSD as well as this current discussion of inline modules and declarative importing. Even back in 2017 (see), an HTML module could be as simple as a document fragment. I sometimes find that a good starting point, particularly given CSS and JSON import attributes solve part of multi-type module packaging as originally envisioned for HTML modules. |
Just a small update here - We've made some additions to the explainer to start to cover some of the things we're discussing here, and we're also looking into TAG's suggestion of |
Good to see in the updated explainer consideration of import attributes like SVG. I note that this already works from inside a shadow DOM: <icon-button>
<template shadowrootmode="open">
<button>
<svg>
<use href="./icons.svg#folder-tree"></use>
</svg>
</button>
</template>
</icon-button> What you can't do now is Being able to use Using a reference to a light DOM element or (inline) specifier might be better, ala |
What problem are you trying to solve?
Developers using Declarative Shadow DOM (DSD) do not have a way to share declarative stylesheets without using script. There are many workarounds, but none are ideal.
What solutions exist today?
If a developer wants to share styles between the light DOM and DSD, they can either:
a. Duplicate individual inline style rules between shadow roots. This is problematic because it often leads to lots of duplicated style definitions.
b. Use
<link rel>
tags for shared CSS files in each shadow root. This is problematic because external stylesheets are an asynchronous render-blocking resource, and could cause an FOUC.c. Use the Javascript
adoptedStyleSheets
property on the declarative shadow root to share stylesheets. This is problematic because it can cause an FOUC and only works with Javascript enabled.None of these options are ideal.
How would you solve it?
Declarative CSS Modules allow developers to define style sheets that by default do not apply to the main document, but instead are stored in the global Module Map. An
adoptedstylesheets
attribute on the<template>
element will allow developers to opt Declarative Shadow DOM elements into sharing these stylesheets via module syntax.Proposed syntax:
The
<script>
tag allows for styles to be defined without impacting the main document. Theadoptedstylesheets
attribute on the<template>
element will look up the module specifier and associate it with the referenced<style>
block.Explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md
Anything else?
No response
The text was updated successfully, but these errors were encountered: