sass/accepted/ordered-comments.md
2023-08-22 21:10:27 +00:00

5.4 KiB

Ordered Comments: Draft 1.0

(Issue)

Table of Contents

Background

This section is non-normative

When Sass introduced its new module system, the model of how stylesheets were compiled changed as well. Instead of every source file adding CSS to one global output stylesheet, each module produced its own CSS which was then stitched together using more complex logic. This allowed us to support important features like only emitting each module's CSS once, @extend scoping, and meta.load-css(), but it did have one undesirable consequence: the order of comments changed.

Because each module had its own separate CSS output, any loud comments that appeared before or between that module's @use or @forward rules would end up all together at the beginning of that module. Because that module's CSS was always emitted in turn after its dependencies', these comments would appear to be pushed later in the output than authors expected.

Although comment ordering doesn't affect the semantics of compiled CSS, it can be confusing and undesirable for users. This is especially true when using meaningful comments like license headers or postprocessor directives.

Summary

This section is non-normative

This proposal adjusts the order that comments are emitted relative to CSS so that the resulting stylesheet is in essentially the same order it would have been when using @import, without repeating stylesheets that are loaded multiple times.

Design Decisions

Traversal Order

There are two potential orders that could be chosen for comments. Either would be better than the current ordering, and either can be better than the other depending on the user's needs.

The order we propose here is "traversal order", in which comments are emitted in the same order that they're evaluated. The other plausible ordering is "linked order", in which comments that appear directly above a @use or @forward rule are always emitted before the module loaded by that rule.

These two orderings result in the same output when each module is loaded once, but once a given module is loaded multiple times with comments around it they become different. For example:

// _upstream.scss
a {b: c}
// styles.scss
/* before @use */
@use 'upstream';

/* before @forward */
@forward 'upstream';

produces the following outputs:

// In traversal order
/* before @use */
a {
  b: c;
}

/* before @forward */
// In linked order
/* before @use */
/* before @forward */
a {
  b: c;
}

Linked order makes sense when using comments to annotate information about dependencies, but it's counterproductive when a user wants to annotate the end of a module, since that comment would be considered linked to the next module load. Traversal order handles that case better and matches the old @import behavior, so we chose to use it instead.

Procedures

Resolving a Module's Extensions

Adjust the definition of Resolving a Module's Extensions by replacing the definition of the traversing procedure with the following:

  • Define a mutating recursive procedure, traversing, which takes a module domestic:

    • If domestic has already been traversed, do nothing.

    • For each module upstream in domestic's dependencies:

      • For each unmarked comment in domestic's CSS, if that comment originally appeared before the @use or @forward rule that loaded upstream, add a copy of that comment to css and then mark it.

      • Traverse upstream.

      Because this traverses modules depth-first, it emits CSS in reverse topological order.

    • Let initial-imports be the longest initial subsequence of top-level statements in domestic's CSS tree that contains only comments and @import rules and that ends with an @import rule.

    • Insert a copy of initial-imports in css after the longest initial subsequence of comments and @import rules in css.

      If there are no comments or @import rules in css, this initial subsequence is empty and initial-imports is inserted at the beginning of css.

    • For each top-level statement statement in domestic's CSS tree after initial-imports:

      • If statement is a marked comment, ignore it.

      • Otherwise, add a copy of statement to the end of css, with any style rules' selectors replaced with the corresponding selectors in new-selectors.

Semantics

@import

Replace the line of the @import semantics that reads:

with one that reads:

  • Add an @import with the evaluated modifiers to the current module's CSS AST after the longest initial subsequence of comments and @import rules.

This ensures that each module's CSS individually raises plain CSS @imports to the top, and allows the procedure for combining CSS to be a bit simpler by only having to handle leading @imports.