Closed mirisuzanne closed 2 years ago
I wanna add that I believe this is absolutely crucial for the adoption of Cascade Layers. Authors are conditioned to avoid the @import
syntax for performance reasons (example A, example B). It is already possible to conditionally link stylesheets based on media type/media query, being also able to also define a layer makes this also more congruent to their definition:
Authors can create layers to represent element defaults, third-party libraries, themes, components, overrides, and other styling concerns—and are able to re-order the cascade of layers in an explicit way, without altering selectors or specificity within each layer, or relying on source-order to resolve conflicts across layers.
Stylesheet authors may not be able to use @import at all, or they might not be able to change how a different external stylesheet enters the cascade all together, either because they don't have access to the processes that provide the stylesheets, or because the created stylesheet just doesn't exist to be imported, because it is being created as part of an obfuscated, optimised build-process, that will end up linking stylesheets in an automated process as well.
As the order of the cascade layers can be declared in the stylesheet that authors can author, according to spec, this would allow authors to clearly separate incoming, external rulesets.
This is important, because most authors currently will rely on a preprocessor such as SASS to split code into partials because authors are engrained with negative connotation for @import's, as mentioned at the beginning. Because of that, switching to cascade layers will feel like a big cost that many authors might not want to pay. But they might be, if they can do it with <link>
.
I personally also believe that there is a specific case to be made for refactoring sites that rely on multiple libraries. As approaches change with trends and needs, allowing Authors to assign existing, incoming stylesheets to a dedicated layer from where they are linked may allow authors to better differentiate between existing code and new code. Tooling will adapt to working with cascade layers, maybe even providing a visual support to aid with it.
I wonder if the as
attribute could be repurposed for cascade layers?
Right now, as
can only be used if the rel
value of the link
element is preload
or prefetch
, which frees it up for use on stylesheet
.
<link rel="stylesheet" href="reset.css" as="reset">
<link rel="stylesheet" href="default.css" as="default">
<link rel="stylesheet" href="bootstrap.css" as="framework">
<link rel="stylesheet" href="theme.css" as="theme">
<link rel="stylesheet" href="utilities.css" as="utilities">
On the one hand, it seems handy to re-use an existing attribute rather than minting a new one.
On the other hand, it could be quite confusing for the same attribute to be used for two different purposes:
<link rel="stylesheet" href="bootstrap.css" as="framework">
<link rel="prefetch" href="https://fonts.googleapis.com/css?family=Roboto:400,600" as="style">
Heya, thanks for opening this. I appreciate the focus on use cases. Let's talk about how to solve them, and in particular how far we are from a solution in today's world.
From what I understand, the functionality is available today with <style>
+ @import
. The problems with that are:
@import
rules tends to be less performant than the HTML <link>
tag")<style>@import(x) layer(y)</style>
to their page, but they may be allowed to add <link rel="stylesheet" href="x" layer="y">
(or some other not-currently-standardized <link>
syntax) to their page? This seems to be what @nachtfunke is communicating with his paragraph starting "Stylesheet authors may not be able to use @import
at all...", but I admit it doesn't make a lot of sense to me.Is that right?
If so, the traditional next steps in this sort of situation would be to clarify the performance issue, since that seems to be at the root of most of the problems (except for the last?).
For that, I'd suggest benchmarks comparing a prototype implementation based on <link>
(even if it has bad backward-compat issues and thus is not shippable) versus the current implementation based on <style>
+ @import
. Alternately or additionally, we'd want browser performance and/or rendering engineers to chime in with whether they think the performance issue is a matter of optimization work, or is fundamental to the design. (My guess is if there is such a delta, it's not fundamental to the design---<style>@import url(x) layer(y)</style>
seems like it should be easy to treat the same as <link rel="style" href="x" layer="y">
---but I am not one of those types of browser engineers!)
For example, I know Chromium did some implementation work that was supposed to ensure that <style>
+ @import
is as fast as <link>
, at least with regards to the preload scanner. If that has closed the gap, it might mean this is just a matter of ensuring that all engines do similar work!
There might be some interesting advantages to encouraging more use of the <style>
tag (and making it more performant) - since that would also allow authors to specify the layer-order easily up-front, making import order less important, and order more clear:
<style>
/* establish layer order */
@layer reset, default, theme, framework, utilities;
/* import styles into layers */
@import url(theme.css) layer(theme);
@import url(utilities.css) layer(utilities);
/* etc… */
</style>
I'm not sure which browser engineers to ping here for input on that approach, and the performance issues. In the CSSWG issue, @yoavweiss suggests that:
The Chromium/WebKit preload scanner for CSS imports is significantly more fragile than its HTML equivalent, as it doesn't perform "real" tokenization. It'd be better to not put the weight of this feature's performance on it. Also, Firefox doesn't have such a preload scanner AFAIK.
@adactio, an attribute solution (new or existing) would need to invalidate the entire import for browsers without layer support - otherwise the fallback path is very unpredictable. Would that be true with as
?
I know there's also been suggestions of having a new <style src='url'>
syntax, which would certainly allow us to add or re-use an attribute. I like the symmetry that has with script
, while being similar to current link
. If we went that rout, tho, I think we might want to consider how we plan for similar feature additions down the road. Can't pick a new element every time.
Is that right?
That is right. I am sorry I couldn't form a usecase that makes sense enough or if it just didn't make much sense. Maybe I can draw up a more comprehensive case, and once I do, I will follow up here.
I am myself currently working on a projects that generates CSS without having any control over how it comes into the markup itself. The point I am trying to make is that we cannot assume that authors will @import external stylesheets. From the point of view of an Author, I see no reason for why I shouldn't be able to also define a layer when liking a stylesheet.
It just seems incongruent to limit this feature to the stylesheet itself. Existing projects may have several stylesheets linked in a given document (global declarations, font-face declarations, declarations dedicated to theming), reworking the architecture and maybe even build-processes may seem too costly to support this feature. But I might be able to assign these existing stylesheets to layers, so that I can more clearly separate their concerns, especially for future reworks.
Let me know if this makes a bit more sense to you. English is not my first language and I find it a bit difficult to describe this topic sufficiently enough so that it makes sense. Luckily I think the other points raised by @mirisuzanne are already strong enough on their own - at least I hope so! :)
I am myself currently working on a projects that generates CSS without having any control over how it comes into the markup itself.
In that case it seems like you could not use any new markup-based solution (e.g. <link rel="stylesheet" layer="...">
). Correct? You can only use @import
, since you can only control the generated CSS, not how it comes into the markup.
I'm not sure which browser engineers to ping here for input on that approach, and the performance issues. In the CSSWG issue, @yoavweiss suggests that:
Ah yeah, that is a great thing to bring up. My person from Gecko to ping for style issues is @emilio; for WebKit I think @smfr? Anyway, if such folks generally agree that the fragility/difficulty of tokenizing CSS (instead of HTML) in the preload scanner is significant enough that we'd want to invent a new markup pattern, then that's a very helpful signal.
The biggest costs I can think of a new markup pattern, beyond the usual costs of implementation/tests/developer outreach, are the security interactions. E.g. if people are using dumb HTML sanitizers that somehow the new markup pattern would bypass, that can be tricky. And we'd need to think through and test integrations with existing security primitives like CSP. These are not blockers, but just costs to be weighed.
an attribute solution (new or existing) would need to invalidate the entire import for browsers without layer support - otherwise the fallback path is very unpredictable. Would that be true with as?
Unfortunately no; as=""
is currently ignored for rel="stylesheet"
, so using it would be the same as using a new element, and would not invalidate the import in existing browsers.
I know there's also been suggestions of having a new
<style src='url'>
syntax, which would certainly allow us to add or re-use an attribute. I like the symmetry that has with script, while being similar to current link. If we went that rout, tho, I think we might want to consider how we plan for similar feature additions down the road. Can't pick a new element every time.
Agreed, we should definitely solve the future-additions problem if we go that route. Any ideas in that direction would be welcome.
Agreed, we should definitely solve the future-additions problem if we go that route. Any ideas in that direction would be welcome.
One idea might be having one or two more generic attributes that get @import
syntax, rather than splitting out individual attributes for each aspect of that syntax? That could go a few different ways:
media
attribute on link
already supports both media and support queries. It would be possible to 'just add layers' to that existing attribute, and then even 'just add container queries' if we need that down the road. The main downside is that the attribute name media
no longer makes much sense.import
attribute that takes the entire @import
syntax in one go. Upside is it can continue to be updated to support the latest syntax without any need for changes in HTML. Downside is it might be less readable?src
(anything related to finding the file), conditions
or when
(any conditional queries - maybe even @when
syntax), and ??? (I'm not actually sure what to call a group that contains things like layers, maybe as
works here)…While it's nice to have things split into more individually meaningful attributes, this seems like the clearest way to make it forward-proof, and allows authors to match syntax between the two types of importing.
[...] since you can only control the generated CSS, not how it comes into the markup.
Yes that is correct, but - and maybe I am overthinking this - in that scenario, the tool that doesn't write the actual css but does split it up and does write the HTML code could also add layer="vendor"
or something of the sorts to its links. I am not sure how this affects layer ordering, as the spec says:
Cascade layers are sorted by the order in which they first are declared
But example 31 also says:
The statement syntax allows establishing a layer order in advance, regardless of the order in which style rules are added to each layer.
The way that I see it, is that allowing tools which automatically add stylesheets via <link>
elements to the document to also declare them as part of a layer, even if that layer name itself cannot be defined, could be valuable - maybe in a way we are not yet aware of.
I think I can provide two examples to make my case, though whether or not layers actually are a viable solution in these scenarios I can only determine hypothetically:
In the first example, I worked on a large dynamic website that was built on top of the Zend framework. Forms where generated using the Zend Form library. Now as soon as forms where used and client-side validation was set, it also added its own stylesheet (and javascript) to render validation error messages. We couldn't touch the file without essentially ripping the plugin out of its package manager, but zend did provide us with ways to define how stylesheets like these did converge in the final document (it's been a minute, but I think it was called headLink or something), which accepted a list of parameters. In that case, we could have declared a layer
attribute on it, saving us lots of complex selectors to override them.
In the second example, which is more recent, I worked on a Vite powered project. Vite asserts full control over an index.html file and in the case of this project, it was crucial, that it also controlled how stylesheets where split, merged and linked with the document in the HTML. It didn't write any actual CSS, but it did process stylesheets from npm managed packages, which we can't just @import in css, but the build tool can process it. It did however make decisions that ended up affecting the styles based on whether it ran in production or development mode. In development, whatever order was defined by the authors was left intact, because it didn't fully process the referenced files. But in production, it did - and it also all of a sudden changed the source order of the stylesheets. It also merged two files together, I guess because it determined it to be better so - but the point is, that because of the changed source order, some page-specific styles didn't override global styles and some global styles didn't override vendor styles. With the capability of simply declaring a layer
on a linked resource, the styles would have still behaved like defined. (Of course I realise that layers are not supposed to fix this issue, I am trying to say that having this capability might be relevant because build tools can use it)
Firefox definitely has a preload scanner of sorts, fwiw, and we do scan for @import
rules: https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/layout/style/ImportScanner.h
You can already put ~whatever at the right of an import rule, and I think right now we'd preload it. It's of course less precise than <link media=
etc where we can discard non-matching media queries and so on, but I don't think that should matter for layer()
?
So we can just ignore @layer
before @import
like we do for @charset
and we'll preload the right uris afaict.
I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1750917 for that fwiw.
So, in https://github.com/w3c/csswg-drafts/issues/2463 we just resolved to add @supports at-rule(...)
to test for support of a given at-rule.
I don't know if <link>
can currently take supports queries, but if it doesn't, I suspect we could just extend media=
to take the same syntax as the @import rule, where you can use a supports()
function that takes a supports query, alongside the naked MQ syntax.
This is layering unimplemented features on top of unimplemented features, which is normally a problem, but in this case it's fine for things to fail if they're not understood or if they're understood but @layer isn't supported, and so that should work I believe.
@tabatkins are you proposing that we would then add a new attribute for layer
, but have authors manually add the 'only if layers are supported' condition in the media
attribute?
Yes.
(Notably, this is only going to be required until layer
is widely supported.)
I would be ok with that approach. But I may have been wrong about the media
attribute allowing support queries?
So to make sure I'm understanding, the proposal is something like:
<link rel="stylesheet" media="@supports at-rule(@layer)" layer="foo" href="bar.css">
which will fail to parse the value of media=""
in old browsers, and thus do nothing there; but it will work in new browsers, and thus be good there. Then, when the transition period is over, could become simply
<link rel="stylesheet" layer="foo" href="bar.css">
That does seem pretty nice. And I think it generalizes to other features in the future, as long as they're @supports()
-detectable.
I guess the fact that media=""
fails-closed in this way does provide another possibility...
<link rel="stylesheet" media="@layer(foo)" href="bar.css">
if we are comfortable abusing media=""
for something that isn't a media query. But I think that could be pretty bad also if you want to actually use media=""
for its intended purpose, e.g. media="print"
or similar.
Following the grammar of @import, it would be media="supports(at-rule(@layer))"
, but otherwise yeah.
I don't think we should invent a novel microsyntax here.
This only requires extending media
to accept supports()
(edit: this would also require a new layer
attr), and then encouraging browsers to implement the already-approved at-rule()
support function. I assume that extension would happen in the HTML spec?
I agree we don't want a micro-syntax for layers only, but the other similar solution along these lines - maybe even a bit more extensible - would be to say that media
accepts 'all @import
syntax after the url'. Then it would continue to support any new options added to @import
over time? (edit: this would not require a new layer
attr, since that would be part of the import syntax)
say that media accepts 'all @import syntax after the url'
Strong agree; anything we can do to @import we're going to want to be able to do for <link>
as well.
However, I know that media=
has some legacy parsing constraints, so I'm curious how feasible that actually is.
Which legacy parsing constraints?
The html spec says <media-query-list>
and the Blink implementation does not seem to have any legacy quirks for the link element at least.
To summarize then, it sounds like the preferred direction would be extending the media
attribute to support all @import
syntax after the url. For layers, that would look like:
<link rel="stylesheet" media="layer(foo)" href="bar.css">
From an author perspective, I think the ability to share a single syntax between CSS & HTML would be amazing. The only downside I see is the name of the attribute, but that feels workable/teachable. (If there is ever a move to e.g. <style src=''>
, then a similar attribute could be supported with a new name.)
I'd be interested in thoughts from @emilio and @smfr.
(Also curious what the next steps are here in terms of WHATWG process?)
(Also curious what the next steps are here in terms of WHATWG process?)
https://whatwg.org/working-mode#changes and in particular https://whatwg.org/working-mode#additions may be helpful.
I'd say the two biggest to-dos are confirming multi-implementer interest (lots of implementers seem involved in the above discussions but they haven't explicitly said "yes we'd implement this particular solution") and writing a spec PR/web platform tests PR. It may be the case that writing the spec PR helps implementers be more confident about what they're agreeing to, so I would personally tackle that first, but on the other hand it could end up wasted effort if they are not interested. So the exact ordering between those is up to the contributors.
Late to the party...
Firefox definitely has a preload scanner of sorts, fwiw, and we do scan for
@import
rules: https://searchfox.org/mozilla-central/rev/d4b9c457db637fde655592d9e2048939b7ab2854/layout/style/ImportScanner.h
@emilio - thanks! I stand corrected. My opinion stands that for Chromium, it seems unwise to rely on the CSSPreloadScanner implementation for this feature without revamping it. I'll let @smfr speak for WebKit, but from looking at their implementation, it doesn't look significantly sturdier..
All this to say that I'd significantly prefer the proposed markup solutions.
/cc @xiaochengh - for potential opinions on implementing the above.
<link rel="stylesheet" media="@supports(at-rule(@layer))" layer="foo" href="bar.css">
This idea looks the best to me
say that media accepts 'all
@import
syntax after the url'
This has a con that it makes the media
attribute on link
elements inconsistent with media
on the other elements (meta
, source
and style
). And this means that what we need to get from media
is not just a boolean but also a layer name, which is... ugly.
say that media accepts 'all @import syntax after the url'
This has a con that it makes the media attribute on link elements inconsistent with media on the other elements (meta, source and style). And this means that what we need to get from media is not just a boolean but also a layer name, which is... ugly.
I'm not sure what you mean by this, Miriam is just saying that we define it in a way that allows for CSS to add more conditional types to @import and have them automatically work, rather than manually copying over the current @import grammar and freezing it until we manually update it again. Maybe you're confusing this with the suggestion from Domenic that we add a media="@layer(foo)"
microsyntax?
Which legacy parsing constraints?
The html spec says
and the Blink implementation does not seem to have any legacy quirks for the link element at least.
Oh good, I had a half-remembered idea that media=""
did some funky parsing stuff around just dropping anything after the first unrecognized part of the query, but now that I think about it more I think there was a legacy parsing thing that resulted in us defining the useless only
keyword you can put before the type - it would cause older UAs to ignore the whole thing and treat it as false.
say that media accepts 'all @import syntax after the url'
This has a con that it makes the media attribute on link elements inconsistent with media on the other elements (meta, source and style). And this means that what we need to get from media is not just a boolean but also a layer name, which is... ugly.
I'm not sure what you mean by this, Miriam is just saying that we define it in a way that allows for CSS to add more conditional types to @import and have them automatically work, rather than manually copying over the current @import grammar and freezing it until we manually update it again. Maybe you're confusing this with the suggestion from Domenic that we add a media="@layer(foo)" microsyntax?
I'm talking about the media="layer(foo)"
microsyntax. I'm fine with extending it with more CSS conditional types (like @supports
, if that's what Miriam means), but I'm not a fan of putting layer in media
, because layer is not a conditional.
Yeah, media="layer()"
definitely feels off to me.
Good, because that was an offhand suggestion by Domenic, and both me and Miriam said we didn't want it. ^_^
Which legacy parsing constraints? The html spec says
<media-query-list>
and the Blink implementation does not seem to have any legacy quirks for the link element at least.Oh good, I had a half-remembered idea that
media=""
did some funky parsing stuff around just dropping anything after the first unrecognized part of the query, but now that I think about it more I think there was a legacy parsing thing that resulted in us defining the uselessonly
keyword you can put before the type - it would cause older UAs to ignore the whole thing and treat it as false.
HTML4 did that. I don't know when it was dropped from the living standard.
@tabatkins That's not a new microsyntax they're responding to, the layer()
keyword and function are part of the @import
syntax now. So if we allow @import
syntax, that would include a syntax for layers.
Oh! Okay, right, so then I agree that should be left out; we just want to carry the conditions over. The layer()
syntax isn't part of the import condition, it's an unrelated bit of functionality, and is covered analogously by the layer=
attribute.
We can easily rearrange the @import definition to name the condition part in a way that makes it easy to reference from HTML.
Then the proposed spec changes would be:
media
attribute to support all 'import conditions' (I suppose we could clarify that term as a part of the CSS syntax to be referenced)layer
attribute…
The long-term syntax is:
<link rel="stylesheet" layer="foo" href="bar.css">
And the transitional syntax for handling browser support is:
<link rel="stylesheet" layer="foo" media="supports(at-rule(@layer))" href="bar.css">
Should this thread be moved to whatwg/html or will you start a new one there with a summary? Maybe the latter is better at this point?
<link rel="stylesheet" layer="foo" media="supports(at-rule(@layer))" href="bar.css">
So... am I understanding this right that with this new syntax, not only can we declare layers for linked stylesheets, but we can even detect support for them, which we can't do in regular stylesheets at the moment?
@nachtfunke The at-rule(@layer)
part is a new addition to CSS that was approved just a few days ago. So you will be able to do it in regular stylesheets as well. I've got a post up on my blog that digs into it.
By having [media]
accept all “import conditions”, this new at-rule()
function also becomes available to use there.
Should this thread be moved to whatwg/html or will you start a new one there with a summary? Maybe the latter is better at this point?
@annevk that's my mistake, being new to the WHATWG repos. Happy to move or summarize in the other repo. I can do that this afternoon.
I think at this point we've reached a general consensus on the approach, from people participating? Even though I'm not sure we have official sign-off from implementors, we at least have good input. I'm happy to start on a spec PR (and then platform tests), to get more detailed feedback there.
Thank you all!
@mirisuzanne sounds great. (If you're interested in a general introduction to the WHATWG, https://whatwg.org/working-mode and https://whatwg.org/faq might be useful.)
Let's continue discussion in https://github.com/whatwg/html/issues/7540. Thanks @mirisuzanne!
The CSSWG recently added a feature to CSS called Cascade Layers. It allows authors to more explicitly manage the cascade behavior of styles from different parts of a design system. That can be done using
@layer
blocks in the code, but in many cases authors will want to wrap entire (sometimes third-party) stylesheets into layers at the time they are linked/imported. The new CSS spec makes that possible with additinoal@import
syntax - but the@import
rules tends to be less performant than the HTML<link>
tag. For now, the best authors can do is use@import
inside HTML<style>
:Use cases
A few variations on the layer use-case include:
Cascade Layers are already supported in preview/beta versions of Safari, Chrome, and Firefox - and we expect them to appear in stable releases over the next few months. But the lack of HTML link support has been one of the largest points of concern/feedback from authors, and one that we can't address from the CSSWG.
Constraints
At first glance it may seem like this could be solved with a new
layer
attribute on the<link>
tag, but it would cause problems for old browsers that simply ignore the attribute, and continue to the load the stylesheets without any layering. Whatever solution we land on needs to allow authors more control over the fallback path for old browsers.It might also be good to plan for this sort of situation down the road, if we're able to find a solution that can potentially be used again for new features in the future?
(I'm sure I missed some useful information here, so feel free to ask questions!)