Closed LeaVerou closed 4 months ago
@fantasai
@andruud
That means it won't round-trip "structurally", like we talked about before. But maybe that's not so bad.
It would, why wouldn't it?
If you do e.g.:
.a {
--x: 1;
.b { ... }
--y: 2;
}
If you now deleteRule
the .b
, then we presumably have two CSSNestingDeclarations
that will collapse into one when round-tripped.
I think whether the first declaration block is represented as a rule is up for discussion too. Perhaps there's less to settle if we don't do that.
I think this makes many of the problems go away, yes, but probably not all ...
And the reason we made this trade-off is because we are willing to accept a slightly less nice CSSOM (which is already quite terrible with all its string-based APIs and reportedly only used by 1% of web developers) to the benefit of all other web developers.
Is there really no rule-like form @nest
can take which makes it OK for the other 99% to occasionally encounter it [1]? @group
? Or even a prelude-less {}
? Additionally, we can reduce the exposure with Tab's serialization proposal, making the flattened representation the canonical one.
[1] Which can only happen if they write it themselves, or if they're reading the CSS of someone else who did.
I think if we can agree that insertRule()
can modify an existing CSSNestingDeclarations
, deleteRule()
should maybe be able to do the same thing?
To us it's the mere idea of adding syntax to CSS purely for CSSOM that seems backwards so I don't think alternative ways of writing that would remove the objection.
I think if we can agree that insertRule() can modify an existing CSSNestingDeclarations, deleteRule() should maybe be able to do the same thing?
I would rather just accept that CSSNestingDeclarations can exist next to each other and that they'll collapse into one if round-tripped. Then we can keep insertRule
etc. about as dumb as they are today.
That's fair. One thing that makes me wonder about is if that would end up invalidating some implementation assumptions. Such as that
background:red;
background:lime;
would only give a single background
declaration within what is conceptually a single declaration block. Seems manageable though.
And the reason we made this trade-off is because we are willing to accept a slightly less nice CSSOM (which is already quite terrible with all its string-based APIs and reportedly only used by 1% of web developers) to the benefit of all other web developers.
I don't quite understand this reasoning, tho - the effects of this are, as far as I can tell, only really observed by people doing the same sort of CSSOM manipulation. You'd only see it if you're stringifying rules and looking at the result. Do you have an example use-case where the stringification helps other people outside of CSSOM-manipulators?
This is the reason I keep harping on (a) the audience and (b) the benefit - I still don't think this has been adequately argued for.
If you introduce a new at-rule, it will show up in lists of at-rules, and people will wonder what it's for. People will encounter it here and there and have to look up what it means, etc. I.e., you add to the complexity of CSS syntax.
Right, so we're judging the balance of one authoring cost (having to bear the psychic weight of knowing this do-nothing rule exists, if you're the sort of person who reads lists of at-rules) vs another authoring cost (having to deal with more complicated/magical behavior for several OM methods), plus the impl/spec cost of that magic. (And the future spec cost of having to engineer around this magic; as I said above, I think this would block us changing to an ObservableArray for .cssRules
, or at least make such a switch way more complicated and fragile.)
I think "knowing a do-nothing rule exists" is a lot less pain than "dealing with weird behavior when doing rule-tree manipulation".
(I speak from experience here; I reimplemented most of the DOM for myself in Python, even with all its warts, because the XML library I'm using in Bikeshed has a terrible data model where text is different from other nodes, and many of its methods have odd magic behavior in the same vein as what's being suggested here. I kept accidentally writing broken code as a direct result of the magic (text would get deleted, or duplicated, or moved in unpredictable ways), so using the DOM and its shitty but simple and consistent "everything is a node" behavior was way better.)
I imagine tutorials would just look something like:
@nest
rules: sometimes implicitly produced by the CSS parser when you're mixing declarations after rules. You can write it yourself, but it doesn't actually do anything. Most of the time CSS doesn't serialize them, either, so you'll only see them if you're using them manually, or manipulating the CSSOM directly.
We can also make the name much less attractive and more obviously internal, if that would help, like @implicit-nested-properties-group {...}
or something. Slightly on the ridiculous side for length, so it's not something an author would ever reach for when writing code manually, but also a very descriptive name that suggests what it's doing immediately, and is much easier to google for.
If WebKit keeps objecting to @nest
, instead of making CSSOM nonsensical, I would prefer revisiting https://github.com/w3c/csswg-drafts/issues/10234#issuecomment-2079994176:
@nest
, use an existing at-rule like @media
or @supports
CSSStyleRule
)If WebKit keeps objecting to
@nest
Both Blink and WebKit are objecting here (to different things). If only one was objecting, we would be able to move forwards. Blink is also being inflexible here.
At this point, I'd be fine with anything, because we're fast approaching the point where we'd be stuck with the hoisting behavior, which is worse than any proposed solution.
If we go the route of a new @-rule, I did propose naming it @group
which is something not tied to nesting, that we can layer behavior on top of later.
I still like the @group
proposal but I also wonder if we would design this exact thing if we didn't need it to patch the wart for nesting.
Would we actually spec @group tobikeshed { }
with the IACVT behavior?
Or would we pick something more like @fallback { }
, @catch { }
or even extend @try { }
?
If @group <ident> {}
is something we want anyway then it would be good if WebKit could give feedback on this proposal. They only gave feedback on @nest {}
and specifically on it having no purpose. Exactly this feedback was already addressed by the @group {}
proposal.
My understanding is that Blink and Gecko are both on the same page, in terms of how to best move forward...
@romainmenke This is about the name, any <ident>
behavior would be added later. L1 would be just @group {}
.
@emilio My understanding from the conversations so far though is that it's Blink that is being inflexible though; Gecko agrees with Blink, but is willing to go a different route if we have consensus for it.
The objection from WebKit is that @nest {}
doesn't have a purpose other than fixing this wart. @group {}
ultimately has the same issue unless we already commit to giving it a purpose later. I am not sure we should be designing new syntax in this order.
In this way I also agree with @tabatkins when they said:
We can also make the name much less attractive and more obviously internal, if that would help, like
@implicit-nested-properties-group {...}
or something. Slightly on the ridiculous side for length, so it's not something an author would ever reach for when writing code manually, but also a very descriptive name that suggests what it's doing immediately, and is much easier to google for.
Precisely, the objection is that @nest {}
doesn't have any purpose other than fixing this wart and will not have one in the future neither because the name is very specific (and confusing with the nesting selector).
EDIT: This doesn't imply at all that we would accept @group
or @very-weird-name
(because as Romain said, they have the same issue of extending the CSS syntax for no reason except this wart). My comment just reiterates the multiple reasons we have rejected @nest
.
@LeaVerou To clarify, I wasn't trying to blame WebKit for not being able to move forward. With "keeps objecting" I meant if possible mitigations like https://github.com/w3c/csswg-drafts/issues/10234#issuecomment-2123407797 don't convince them to drop the objection to @nest
.
I think I prefer @some-weird-name
to @group
, because if authors somehow do find a use for it that is different or more specific than grouping or nesting, we can replace/alias the weird name with something more closely matching the discovered use.
Could someone who believes @nest
is a good solution, please answer this question… pretend a community college professor is emailing you, and asks: "I want to teach my students to use CSS Nesting. Could you explain when / how / where they should use the new @nest rule? What does that nested CSS look like?"
I want to teach my students to use CSS Nesting. Could you explain when / how / where they should use the new
@nest
rule? What does that nested CSS look like?
I don't see what the issue would be with an answer like "You should never need to write @nest
manually, it gets added where needed by the CSS parser."
@emilio It's necessary to write manually if you're using .insertRule()
though, isn't it?
Also people still write </p>
even though it has always been added where needed by the HTML parser. I think we can expect the same for @nest
and similar once it exists.
Sure, it would if you're calling insertRule or so.
I don't see how this compares to </p>
. Omitting closing tags in HTML is the exception, while the intuitive syntax for CSS nesting would be the rule.
It's necessary to write manually if you're using .insertRule() though, isn't it?
As I keep arguing, that's a very advanced usage. You don't teach people how to use .insertRule() (or really any of the OM except .style
, probably) in a beginner CSS course. It's something needed extremely rarely, for advanced CSS tooling use-cases only. (Personally, I have never, not a single time, used it in my own webdev.)
And, as I've argued, the cost of "when I, a CSS tooling author, use .insertRule()
, I have to insert a rule of some sort (and @nest
, or whatever we call it, is the easiest) to insert a block of declarations between two other rules" is more than outweighed by the benefit of "when I, a CSS tooling author, do arbitrary manipulations to the OM, everything works in a simple and expected way, and the structures appear in close correspondence with the method calls I make".
(Plus, we could certainly still add .insertDeclarations()
, which creates the rule for you automatically. If that's a use-case we want to make easier, I think that's a very reasonable thing to do. It's roughly neutral on typing (longer method name, but you don't need to add the @nest{
and }
prefix/suffix to your string), but it does communicate intent well.)
Could someone who believes @nest is a good solution, please answer this question… pretend a community college professor is emailing you, and asks: "I want to teach my students to use CSS Nesting. Could you explain when / how / where they should use the new
@nest
rule? What does that nested CSS look like?"
I wrote something to this effect in my previous comment.
I think I prefer
@some-weird-name
to@group
, because if authors somehow do find a use for it that is different or more specific than grouping or nesting, we can replace/alias the weird name with something more closely matching the discovered use.
Not sure about getting stuck with @some-weird-name
for time immemorial though. I think @group
is sufficiently generic that it can work decently with any use.
The CSS Working Group just discussed [css-syntax][css-nesting] Design of `@nest` rule
, and agreed to the following:
RESOLVED: 1. Introduce a CSSNestedDeclarations object inheriting from CSSRule and having a .style accessor, and use that to represent the declaration lists in a CSSStyleRule. It serializes as a raw declaration list. 2. Extend .insertRule() to parse declarations (or, if Web-compat requires, add .insertDeclarations()) into a CSSNestedDeclarations Object. 3. Open a new issue wrt the first declarations block.
Possibly too late to change the design, but I just had an idea: What if we do introduce the rule, call it @group
, and instead of introducing identifiers in the preamble, we have a revert-group
value that would work similarly to revert-layer
, i.e. would revert the declaration to what it would have been if the group was not applied.
Then groups have a purpose that is not turned on by default (making them suitable for representing nested declarations) but is still super useful: they allow authors to override the IACVT behavior with an actual fallback! This is especially useful in combination with something like if()
:
@group {
border-radius: if(style(--button-shape: pill), infinity, revert-group);
}
Opening this as requested by @astearns
In #8738 we resolved to stop hoisting interleaved declarations and introduce an
@nest
rule that means "exactly the same thing as the parent" instead of wrapping in:is()
, which is how interleaved declarations will be represented in the CSS OM. Since we were not able to get consensus on the specifics, but we had consensus that any solution along these lines is better than the status quo, we agreed that Tab would spec whatever (commit here), and we'd discuss the details later, since fixing the specifics is more web compatible than changing the current behavior after even longer.The issues around which we could not reach consensus were:
@nest
and it’s only introduced to represent interleaved declarations and rules, should they even be able to?@nest
rules are magically added around interleaved declarations should they also be removed during serialization?.cssText
)?@nest
rule? What if we simply use the existingCSSStyleDeclaration
object to represent interleaved rules? (proposed by @mdubet)setProperty()
work if we go with one of the designs that involve more magic?rule.style
?These are not orthogonal decisions: it seems clear that if
@nest
serializes to include an actual@nest {}
rule, that@nest
rule needs to also be valid author code. So essentially there are three possible designs:@nest
(proposed by @tabatkins, supported by @emilio @andruud @Loirooriol): The rule is automatically added around interleaved declarations, but there is no more magic besides that.@nest
(proposed by @LeaVerou, supported by @fantasai @astearns): The rule becomes a CSS OM detail, with no corresponding CSS syntax, and is removed on serialization (regardless of how serialization happens).@nest
, justCSSStyleDeclaration
in the CSSOM (proposed by @mdubet, supported by @LeaVerou @fantasai)..cssRules
will also return non-rules? Would.insertRule()
also acceptCSSStyleDeclaration
?For 2 and 3, there are also design variations based on the answer to 4 and 5 above.
My position:
@nest
or make it an internal detail. That said, there could conceivably be use cases for it. E.g. one of the problems with IACVT is that fallbacks are thrown away by the time the declaration becomes invalid. What if this was a way to preserve fallbacks?@nest
is functionally equivalent toCSSStyleDeclaration
and we should not be introducing new interfaces with philosophical purity being the only motivation (i.e. "but otherwise.cssRules
would be returning a non-rule?!?")setProperty()
on the base rule should just work, without them having to do tree walking to find the last nested rule. Like,setProperty()
is an incredibly common operation, and an API where calls tosetProperty()
have no effect are exactly the kind of dreadful APIs that make utility libraries proliferate.So I would propose a design that would minimize author exposure to all of this, and would just try to do what's reasonable when reading and modifying the CSS OM:
.cssRules
would containCSSStyleDeclaration
objects with interleaved declarationsinsertRule()
would acceptCSSStyleDeclaration
objectsCSSStyleDeclaration
objects would be merged.CSSStyleDeclaration
object cannot be at the start ofcssRules
. Inserting one should simply merge the declarations withrule.style
.Should
rule.style
be magic?One thing I'm ambivalent about is whether
rule.style
should be magic too. This would mean:rule.style
returns the union of all interleaved declarations (we can introduce another property to only get the non-interleaved ones)rule.style.setProperty()
(and accessors) adds to the last interleavedCSSStyleDeclaration
(if any are present). The third argument is turned into a dictionary with apriority
property and another property (name TBD) to reverse this behavior and actually add it to the first block of declarations.Pros & Cons:
If we decide to avoid magic here, we can make the API more palatable by:
rule
property that returns aCSSStyleDeclaration
for the union ofrule.style
and interleaved declarationsrule.setProperty()
method that would add the property at the end of the last interleaved declaration.rule.style.setProperty()
would continue to do what it currently does. Same forremoveProperty()
,getPropertyValue()
etc.