-
Notifications
You must be signed in to change notification settings - Fork 756
Description
When I wrote the Nesting spec originally, I specifically defined it so that & was an ordinary selector, that happened to match the elements matched by the parent rule. This leveraged the powers that only a live browser could have, which seemed useful. Preprocessors, unable to do the same, instead have to make their "nesting selector" more of a macro, that is substituted with the parent selector.
That is, in a rule like
.a .b {
.c & {...}
}In the Nesting spec, the & matches "a .b element with a .a ancestor", and the nested rule imposes an additional "...and has a .c ancestor", so it's the equivalent of .c :is(.a .b). It'll match all the following markup structures:
<div class=a>
<div class=c>
<span class=b></span>
</div>
</div>
<div class=c>
<div class=a>
<span class=b></span>
</div>
</div>
<div class="a c">
<span class=b></span>
</div>Preprocessors can't do this (or rather, couldn't do this before :is() was standardized, and still can't widely do it since support for :is() is still in the low 90%s). Expanding such a selector fully (to .a .c .b, .c .a .b, .a.c .b) would mean a combinatorial explosion in selector length, and gets drastically worse the more combinators are in either selector. (Sass has a similar situation with its @extend rule, where it uses heuristics to select only a few likely expansions, rather than all possible ones.) Instead, they substitute the selector directly, giving .c .a .b, and only matching the second of those three markup structures.
I was discussing Nesting with @nex3 a few days ago, and she expressed surprise and some dismay that Nesting had this behavior. She believes that a large amount of CSS written with Sass nesting expects and depends on the current Sass behavior, not accidentally but quite intentionally - they expect this sort of nesting pattern to be expressing "and then a .c container around the previous stuff" rather than just "and then a .c container somewhere around the specific previous elements". She believes this would actually be a significant burden to people migrating from preprocessor nesting to native nesting, and even for people who are using native Nesting fresh, believes it'll be a common point of confusion for people using this pattern.
As well, under Sass's behavior, an author can recover the current spec's behavior by wrapping the & in an :is(), like .c :is(&) (or wrapping the parent selector). However, there is no way to recover Sass's behavior from the current spec's behavior.
In the majority of cases it doesn't make a difference which way we go. The most common relative selector case (a nested selector like .c or + .c) is unchanged, and many non-relative selectors are also unchanged, such as & + & or :not(&). Only cases like the above, where there is a descendant combinator in the parent selector and a descendant combinator in the nested selector with an & following it, will change their behavior.
I know this is a pretty significant semantic change to be coming at this late time, but Natalie felt it was pretty important, and @mirisuzanne backed her up, and the arguments were reasonably convincing to me.
(Note: we'd still not be doing selector concatenation - .foo { &bar {...}} is still equivalent to bar.foo, not .foobar.)