[go: nahoru, domu]

Skip to content
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

[css-nesting-1] Should code portability trump coding habits wrt nesting syntax? #8029

Closed
FremyCompany opened this issue Nov 6, 2022 · 4 comments
Assignees
Labels
Closed as Retracted When the person who raised the issue thinks that there's no issue after all. css-nesting-1 Current Work

Comments

@FremyCompany
Copy link
Contributor
FremyCompany commented Nov 6, 2022

As a follow-up from #7834, the question of the "same-brace-but-inconsitent-prefixing" (Proposal 3) vs "other-brace-but-consistent-prefixing" (Proposal 4) was left open. Proposal 3 is described in the current editor draft. The presentation of Proposal 4 and the related discussion can be found here: #7834 (comment). Proposal 5 can be found here: #7970

The main advantage of Proposal 3...

is that this should be very familiar to anyone who might have used nesting through CSS preprocessors, although the requirement to use & or :is() in some cases is new as preprocessors don't limit themselves to the more efficient types of parsing which browsers rely on for performance reasons.

The main advantage of Proposal 4...

is that it provides full interoperability between contexts: you can take any CSS code you have today, wrap it in a block, add a selector prefix, and now you have "nested" the existing code without having to make any change. Similarly, moving from and to CSS Scoping blocks does not require any change at all. Finally, if some code needs to be un-nested (because it is moved to a Web Components CSS file and no longer requires a prefix), this can be done without any change to the CSS.

The main advantages of Proposal 5...

It provides a top-level construct that contains nothing but nested rule. This avoids the need to mix style rules and properties in a single context. It uses nothing but standard CSS syntax and requires no changes to the OM or parsing rules. It's easier to explain and understand.

Apart from the requirement to put the rules in another brace, the syntax between the three proposals looks remakably simliar, as can be seen in the following examples:

Proposal 3Proposal 4Proposal 5
  • Is familiar to users of CSS preprocessors.
  • Every CSSStyleDeclaration block can now support nested rules
  • Code portability between regular stylesheets rules, nested rules, scoped rules, and web component rules.
  • Does not put major syntax restrictions on the declaration block.
  • Code portability between regular stylesheet rules, nested rules, scoped rules, and web component rules.
  • No new syntax, parsing changes or OM changes.
  • Avoids potential conflicts of future property or selector syntax exensions.
  • Preserves the ability to interleave nested rules without reordering in the OM.
  • Rules are invalid if they start with a type selector, requiring them to be rephrased somehow. (Using :is(div), starting with &, etc.)
  • This prevents us from changing property syntax to start with an ascii glyph in the future.
  • Requires another pair of brackets
  • If you are only nesting rules, you still need an empty declaration block ({}), which looks awkward
  • Nesting is not secured by default for the others CSSStyleDeclaration context (e.g. inline styles)
  • Requires adding an additional block of scope to the parent rule
Spec example 1...
table.colortable {
  & td {
    text-align:center;
    &.c { text-transform:uppercase }
    &:first-child, &:first-child + td { ... }
  }
  & th {
    text-align:center;
    background:black;
    color:white;
  }
}
table.colortable {} {
  td { text-align:center; } {
    &.c { text-transform:uppercase }
    &:first-child, &:first-child + td { ... }
  }
  th {
    text-align:center;
    background:black;
    color:white;
  }
}
@nest table.colortable {
  @nest td {
    { text-align:center; }
    &.c { text-transform:uppercase }
    &:first-child, &:first-child + td { ... }
  }
  th {
    text-align:center;
    background:black;
    color:white;
  }
}
Spec example 2...
.foo {
  color: red;
  .bar {
    color: blue;
  }
}
.foo { 
  color: red; } 
  { .bar {
    color: blue;
  }
}
@nest .foo {
  { color: red; }
  .bar { 
    color: blue; 
  }
}
Spec example 3...
div {
  color: red;

  & input { margin: 1em; }
}
div {
  color: red; } {

  input { margin: 1em; }
}
@nest div {
  { color: red; }

  input { margin: 1em; }
}
Integration with layers...
@layer base {
  html {
    block-size: 100%;

    @layer base.support {
      & body {
        min-block-size: 100%;
      }
    }
  }
}
@layer base {
  html {
    block-size: 100%; } {
    
    @layer base.support {
      body {
        min-block-size: 100%;
      }
    }
  }
}
@layer base {
  @nest html {
    { block-size: 100%; }

    @layer base.support {
      body {
        min-block-size: 100%;
      }
    }
  }
}
Integration with scope...
.card {
  inline-size: 40ch;
  aspect-ratio: 3/4;

  @scope (&) to (> header > *) {
    :scope > header {
      border-block-end: 1px solid white;
    }
  }
}
.card {
  inline-size: 40ch;
  aspect-ratio: 3/4; } {

  @scope (&) to (> header > *) {
    :scope > header {
      border-block-end: 1px solid white;
    }
  }
}
@nest .card {
  {
    inline-size: 40ch;
    aspect-ratio: 3/4;
  }

  @scope (&) to (> header > *) {
    :scope > header {
      border-block-end: 1px solid white;
    }
  }
}

Regarding the question of nested rules in the style attribute, I would rather have rules contained in a <style> element where they can receive the space needed. There is a proposal for this already, for instance:

<article>
    <style>
        :style-root { } {
            h1 { color: purple; }
        }
    </style>
    <h1>Title</h1>
    ....
</article>

Otherwise, we can also put them in another attribute named cssRules="..." which contains the content of the second (rule) block.

Anything I missed in the discussion?

@romainmenke
Copy link
Member
romainmenke commented Nov 30, 2022

is that it provides full interoperability between contexts: you can take any CSS code you have today, wrap it in a block, add a selector prefix, and now you have "nested" the existing code without having to make any change.

I am confused by this statement.
It says that you do not need to make any change while outlining the changes you need to make.


} {

I don't think any authors will like writing this at the end of a declaration.

  • if you want to add one more declaration you need to move } { to the next one.
  • if you re-order declarations the block breaks.
  • if you copy/paste that line, it will need to be changed before it will be valid in the next context.

In general I also find that typing } { in that order is awkward.
People will get used to it, but it is uncommon today.

I know that writing } { at the end of a declaration is not required but it is placed there to create the visual illusion of nested curly braces.

It falls apart with different formatting :

div {
  color: red;
} {
  input { margin: 1em; }
}

But I think this style is more realistic with how the feature would be used.

@FremyCompany
Copy link
Contributor Author
FremyCompany commented Dec 5, 2022

is that it provides full interoperability between contexts: you can take any CSS code you have today, wrap it in a block, add a selector prefix, and now you have "nested" the existing code without having to make any change.

I am confused by this statement. It says that you do not need to make any change while outlining the changes you need to make.

I think @argyleink was confused by that statement, too.

Here is an example of HTML/CSS you might find today: (I just took it from the table spec)

<ul class="table">
  <li><b>One</b><i>1</i></li>
  <li><b>Two</b><i>2</i></li>
  <li><b>Three</b><i>3</i></li>
</ul>
<style>
  ul.table { display: table; }
  ul.table > li { display: table-row; }
  ul.table > li > * { display: table-cell; }
</style>

Let's say that you inherit that code, and realise that it's not such a good idea to use the "table" class that way in your entire codebase, and you want to limit to a section of your site that contains the articles from the legacy system.

You can take the CSS code, prepend :where(article.legacy) {} {, indent the entire list of rules, then close the brace with }.

:where(article.legacy) {} {
  ul.table { display: table; }
  ul.table > li { display: table-row; }
  ul.table > li > * { display: table-cell; }
}

Now, with the current option, proposal 3, this is not sufficient. You have to go and find all selectors that were starting with an indent, and fix them.

:where(article.legacy) {
  & ul.table { display: table; }
  & ul.table > li { display: table-row; }
  & ul.table > li > * { display: table-cell; }
}

This is a manual operation that requires O(n) steps for the author. Each selector needs to be inspected, and then prepended with a '&' symbol if needed. This operation is error-prone and takes time, for something which I expect to be a very common refactoring.

Plus, now, if you want to go back to an independant stylesheet (because, say, you moved to web components and isolated the legacy content there), you have to go back and remove all the & symbols since they don't mean anything anymore. If you had used proposal 4 or 5, you would not need to do this.

This is also true if you move from or to @scope statements. This is another very likely scenario, given the features work similarly but have different effects on cascade importance.


I know that writing } { at the end of a declaration is not required but it is placed there to create the visual illusion of nested curly braces (which falls apart with different formatting).

Yeah, that's true. I don't find that other way of writing it that bad, but it does feel less compact. I'd like to note that the examples for the first spec also use artifically-shortened forms though, like &.c { text-transform:uppercase } one-liners. I find it interesting that most spec examples also all contain an empty line to separate the declarations from the rules, where the two braces could go. A smart IDE could unemphasize these two braces with a different (less-contrasting) coloring, and achieve minimal visual difference between the two proposals that way.

Proposal 3Proposal 4

image

image

I would also like to point out that with autocompletion these days, you hardly ever write a } yourself. They are auto-inserted. The actual workflow when adding nested rules is more likely to be "click after the } and type { then your rules".

@romainmenke
Copy link
Member

You can take the CSS code, prepend :where(article.legacy) {} {, indent the entire list of rules, then close the brace with }.

Thank you for elaborating, I know understand what the statement intended :)

This operation is error-prone and takes time, for something which I expect to be a very common refactoring.

I find it difficult to weight these correctly.
From my perspective this is a very uncommon case.

Much more common to have existing blocks and add something nested inside of it.
The outermost element of a component rarely changes.
Adding or removing extra bits inside seems to happen much more frequently.


Plus, now, if you want to go back to an independant stylesheet (because, say, you moved to web components and isolated the legacy content there), you have to go back and remove all the & symbols since they don't mean anything anymore. If you had used proposal 4 or 5, you would not need to do this.

There are proposals to give & meaning everywhere and to allow relative selectors everywhere.

Not saying that that is a reason not to pick 4 or 5.
But it is something that isn't made impossible simply by choosing 3.


I would also like to point out that with autocompletion these days, you hardly ever write a } yourself. They are auto-inserted. The actual workflow when adding nested rules is more likely to be "click after the } and type { then your rules".

Ah, yes, I forget about that because I disable this behavior.

A smart IDE could unemphasize these two braces with a different (less-contrasting) coloring,

Could even be a ligature like we have for !==

Screenshot 2022-12-05 at 12 05 33

@FremyCompany
Copy link
Contributor Author
FremyCompany commented Dec 22, 2022

We closed this issue after polling developer's opinion on the issue.

Only around 20% of developers prefered not keeping the SASS-like syntax to avoid suffering from code portability issues, and about half of the remaining 80% advocate for a mandatory prefix before selectors, which destroys any hope of code portability. Based on this, we can assume a 20% vs 40% ratio against trade-offs for code portability, which makes the efforts of option 4 and 5 moot on this aspect.

I'm closing this issue as a result.

@FremyCompany FremyCompany added the Closed as Retracted When the person who raised the issue thinks that there's no issue after all. label Dec 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed as Retracted When the person who raised the issue thinks that there's no issue after all. css-nesting-1 Current Work
Projects
None yet
Development

No branches or pull requests

6 participants