Open tabatkins opened 1 year ago
This would be breaking for anyone using the current specification together with something like PostCSS plugins, lightningcss, ...
Given that we have been teaching users to write &div
this is definitely breaking for a lot of people.
Having a use counter in Chrome won't say much. The actual usage is hidden because everyone ships "transpiled" CSS.
Edit :
I think this change will shift the migration pains from users using Sass to users using PostCSS/LightningCSS/...
It will also make the feature harder to teach because it again adds a weird exception.
This would be breaking for anyone using the current specification together with something like PostCSS plugins, lightningcss, ...
This is the cost of transpiling ahead of shipping implementations, yes.
Given that we have been teaching users to write &div this is definitely breaking for a lot of people.
Depends on how common that actually is.
It will also make the feature harder to teach because it again adds a weird exception.
What do you mean by this? This returns us to the prior rules for compound selectors, where a type selector, if present, must occur first.
This is the cost of transpiling ahead of shipping implementations, yes.
The same is true for sass. Any argument goes both ways here :)
If we are arguing that the spec should be changed because sass tanspiles in a specific way we can also argue that it shouldn't be changed because PostCSS transpiles in a specific way.
In principle I do agree that any polyfill that is shipped before implementations is likely to see breaking changes. But this specific change is being made to accommodate sass, not to help authors or implementations.
What do you mean by this? This returns us to the prior rules for compound selectors, where a type selector, if present, must occur first.
Maybe it is not yet too late to take this back and it won't be seen as an exception. I am not the right person to judge this given how used I am to all the iterations this specification has seen :) To me it now seems like a weird exception, but it might not be.
The same is true for sass. Any argument goes both ways here :)
Somewhat. Sass far predates CSS Nesting, and we're invading the same syntax space. This is different from projects that intentionally followed our spec, intending to match what browsers eventually ship with - there was always the chance that the spec changes before browsers ship. (It's already happened once, with the removal of @nest
and loosening of restrictions on selectors. Anyone using @nest
in those processors was "broken" to the exact same degree that anyone using &div
today would be.) Following the specs ahead of browsers is inherently risky, and we intentionally do not account for this type of thing when deciding whether to change features in specs.
The type of breakage is also significant. Any project following the current Nesting spec which allows &div
can just switch to outputting div&
- &div
being a syntax error in raw CSS means that there's no harm in recognizing it and correcting the output.
This is distinct from the Sass problem, where &div
is already a recognized syntax with a distinct behavior, which is not reproducible in pure CSS.
(In either case, these tools all output non-nested CSS, which means their output is going to continue to be correct. The only compat problem would be if they started outputting nested CSS for newer browsers, and spat out &div
, as that would be invalid after the change.)
One must also realistically consider the scale of the userbases between the projects. We're talking compat here, so actual number-of-devs-affected is more than a theoretical concern.
One must also realistically consider the scale of the userbases between the projects. We're talking compat here, so actual number-of-devs-affected is more than a theoretical concern.
PostCSS Nesting for example has ±7 million weekly installs. Whereas Sass has about ±11million. There are other projects like LightningCSS which adhere to the same principles and there are other distributions of Sass.
Neither group is small enough to simply ignore :) There will be real compat pains here.
This is different from projects that intentionally followed our spec, intending to match what browsers eventually ship with - there was always the chance that the spec changes before browsers ship. (It's already happened once, with the removal of
@nest
and loosening of restrictions on selectors.
Correct, we have and always will follow the specification. We might challenge proposed changes, but we will always follow the specification.
This is the only thing that makes sense long term. Any migration pains are short term.
PostCSS Nesting for example has ±7 million weekly installs. Whereas Sass has about ±11million.
Note that Sass's npm distribution is not the whole story—Sass is also commonly installed via Homebrew, Chocolatey, GitHub downloads, the occasional Linux distro, and so on. Sass's support for &foo
is also much older than postcss-nesting
's (Sass added support in 2014). On top of all of that, using &foo
as a suffix is likely to be much more popular among Sass users than using it to add a type selector is among postcss-nesting
users: &-suffix
is a critical part of BEM-style design methodologies that use modifiers of base class names to communicate semantic relationships, while needing to add a type selector to a compound selector in a nested context is so rare that we only had a small handful of support requests about it in the entire lifetime of Sass.
It's also worth noting that Less (~5 million weekly installs on npm) and Stylus (~3 million) both also use Sass's semantics for &foo
here, and have also done so for around a decade. The postcss-nested
plugin (~6 million) also implements these semantics. So all told, the difference is more like ~8 million to ~25 million, even before considering the longevity and relative usefulness of the two forms.
So all told, the difference is more like ~8 million to ~25 million, even before considering the longevity and relative usefulness of the two forms.
Sure, but these are still two sizable user groups.
I didn't mean to imply that both were equivalent in size, only that this isn't a less than 1%
kind of thing.
I had two concerns, one of which tabatkins addressed.
1 : That the feature would be harder to teach.
But I agree with tabatkins on this. Restoring that restriction makes sense to me on it's own, while technically it is a "breaking change".
Authors who absolutely want to start every selector with &
and want to write &div
can also write &:is(div)
.
2 : That the adoption/usage of &div
will only be determined by looking at a use counter in Chrome, ignoring that PostCSS and others exist. And ignoring the reality that people will continue to transpile their nested source to non-nested CSS for years to come.
They wrote CSS that works in the current Chrome version. That they transpile their source to support other browsers and older versions of Chrome should be taken into account.
Just an acknowledgment that some/any weight will given to the migration pains of these CSS authors would be sufficient.
The type of breakage is also significant. Any project following the current Nesting spec which allows
&div
can just switch to outputtingdiv&
-&div
being a syntax error in raw CSS means that there's no harm in recognizing it and correcting the output.
Indeed, we will handle this similarly as we will handle the removal of @nest
.
This combination is the least disruptive.
That the adoption/usage of &div will only be determined by looking at a use counter in Chrome, ignoring that PostCSS and others exist. And ignoring the reality that people will continue to transpile their nested source to non-nested CSS for years to come.
Yes, we intentionally do not pay attention to preprocessors that attempt to lead the spec before browser support is solidified, because "ships in a major browser and sees measurable use" is the point at which we consider a feature stable. Going ahead of browsers means you're working with Explicitly Unstable And Possibly Bad Ideas.
I'm being very firm here for a reason - it's incredibly hostile to all other parties to attempt to unilaterally thrust an early, unstable version of our work into "frozen in practice" stability. The browsers all have explicit steps in their launch processes for seeking and ensuring consensus and stability, and advance without those guarantees very carefully and deliberately. PostCSS and related tools do not meet that bar.
It is also the case that, as a general rule, such tools do not impose nearly the "frozen in practice" weight that a browser does. They generate valid old-style code, which will continue to work as intended regardless of how we change the feature the preprocessor is implementing. The tool itself will continue to accept CSS written for it, regardless of how we change the spec, so long as authors don't update the version. The only issue arises when authors are updating their tool version but not maintaining their code; then their sites will break. But that's the case for every tool they use, for any purpose whatsoever. A syntax change is a major-version bump in semver; you must be ready to fix breakage if you accept the bump.
Separate from the above, as Natalie said, if we make this change and PostCSS/etc follow, then the &div
syntax will just become invalid in them. This means those tools can offer migration tools: auto fixup, warnings, source rewriting, etc.
This is not the case for Sass/etc here - if we leave the spec as-is, then it conflicts with valid Sass/etc code that has specific, unreproducable-in-CSS behavior. And we know that it's pretty common behavior in Sass/etc.
Luckily it's not the end of the world for these tools - div&
is legal to write today in CSS (so long as it's not at the start of the selector, but that restriction'll drop), so they could just continue to say that &div
has its current meaning in those tools (concatenation) while div&
means the same as CSS. This would be a behavior divergence from CSS, but as Natalie noted, it's a pretty rare situation in practice anyway.
But, since the only reason for relaxing the "type selectors must go in front" restriction was to deal with the parsing restrictions that earlier versions of Nesting imposed, and those restrictions no longer apply, and reverting to the old type-selector behavior would avoid a behavior difference with a decade-old Sass syntax that we're intentionally invading the syntax space of, it'll be nice to make the change if we can.
I'm being very firm here for a reason - it's incredibly hostile to all other parties to attempt to unilaterally thrust an early, unstable version of our work into "frozen in practice" stability. The browsers all have explicit steps in their launch processes for seeking and ensuring consensus and stability, and advance without those guarantees very carefully and deliberately. PostCSS and related tools do not meet that bar.
I strongly agree with this and have added some guidelines to prevent this going forward. It is ok for anyone to create a tool to play around with a proposed feature before implementation, it is not ok for a tool with a large user base to act as if these are "ready for use" before they ship in actual browsers.
It think it is even worse than what you are describing. By going ahead of browsers we also shape the perception of a feature and the mental model around a feature before it is ready. Every poll to gather CSS Author feedback around nesting was biased because of PostCSS Nesting and similar tools.
My argument wasn't that some random PostCSS plugin should be given equal or similar weight as a browser implementation. Only that we "obfuscate" adoption of a feature in source code by transpiling it. But I agree that this is actually a good thing in this scenario.
Real usage in browsers will be extremely low because anyone not writing a demo on nesting itself will use a transpiler. And these tools, as you said, have semver, ...
hehe, this escalated nicely, sorry about that :)
Alternative solution to nesting that does not change existing syntax grammar.
1 To introduce style blocks :
@set MyComponent {
:root { border: 1px solid; } /* root element of the set, or :scope */
:root > div { ... } /* immediate child of the root */
div { ... } /* any element inside the root */
}
and one property named style-set: ... nameOfTheSet ...
:
widget.myComponent {
style-set: MyComponent;
}
style-set is an inheritable by default property.
Element that has explicit declaration of style-set
is the root.
This variant solves problem of CSS declarations modularity 1) without breaking changes
and 2) that is more valuable IMO, reduces complexity of style resolution - selectors inside the block are checked only for elements that have style-set
defined. Therefore @set declaration will not increase time needed for style resolution.
As a bonus we can add @styleset attribute to HTML
<widget styleset="cssfile#MyComponent"> ... </widget>
to make declarations local to components.
Fun new information: it turns out that Chrome's impl never actually supported &div
in the first place - our impl never relaxed the restriction that type selectors have to be the first component of a compound selector. (There aren't any tests for this specific behavior right now...)
So there's zero compat issues from Chrome, at least, in returning to the previous restriction. (Thanks for tracking this info down, @andruud !)
The CSS Working Group just discussed [css-nesting] Require `div&`, disallow `&div`, for Sass compat
, and agreed to the following:
RESOLVED: type selector remains required first; &div is invalid
All right, spec should be updated to this condition now. We'll need to update the tests (and probably add some additional ones).
Given the very heartening news that infinite-lookahead is viable in Chrome, I'd like to try and revisit one of our syntax changes caused by Nesting.
In the original version of the spec, you had to start a selector with
&
, so to handle the case where you want to add a type selector, we relaxed the restriction that a type selector had to be the first thing in a compound selector. This allowed&div
to work, thodiv&
was theoretically okay (in cases like@nest div&
).In the current version of the spec, you still can't start a selector with an ident, so
&div
is still the right way to spell things most of the time (but again, still okay in theory to do the other way, like.foo div&
).Neither of these restrictions will apply anymore if #7961 goes thru. Which is good, because
&div
is terrible for Sass.Sass essentially uses text concatenation for its nesting feature. If you write
.foo { &div {...}}
in Sass, it generates a.foodiv {...}
rule - a single larger class selector. In CSS Nesting, this is instead equivalent todiv.foo {...}
- a type selector plus a class selector.This mismatch in syntax was always going to be an enormous pain for Sass to migrate to CSS Nesting (possibly a straight-up blocker, requiring Sass users to explicitly opt into CSS Nesting instead), but when other factors made
&div
the preferred form, I accepted that it was just one of those painful situations that'll be worth it in the long term.But given #7961, there's no longer any reason to prefer
&div
overdiv&
. So we can simultaneously (a) remove a syntax change, preserving the original syntax of Selectors that has been stable for a long time, and (b) make Sass's task of migrating to CSS Nesting natively massively easier (along with any other preprocessor that has a similar feature, but I'm familiar with Sass's syntax here).I've talked with @andruud about this and he's dropping a Use Counter into Chrome preemptively, to make sure we'll actually be able to make this change. (Since we're shipping the current Nesting spec in Chrome 112 which is just now releasing to Stable.)
/cc @nex3