Robin Malfait 02cfc45057
Ensure @utility is processed before using them (#15542)
This PR fixes an issue where using an `@utility` before it is defined,
and _if_ that `@utility` contains `@apply`, that it won't result in the
expected output. But results in an `@apply` rule that is not
substituted. Additionally, if you have multiple levels of `@apply`, we
have to make sure that everything is applied (no pun intended) in the
right order.

Right now, the following steps are taken:

1. Collect all the `@utility` at-rules (and register them in the system
as utilities).
2. Substitute `@apply` on the AST (including `@utility`'s ASTs) with the
content of the utility.
3. Delete the `@utility` at-rules such that they are removed from the
CSS output itself.

The reason we do it in this order is because handling `@apply` during
`@utility`
handling means that we could rely on another `@utility` that is defined
later
and therefore the order of the utilities starts to matter. This is not a
bad
thing, but the moment you import multiple CSS files or plugins, this
could
become hard to manage.

Another important step is that when using `@utility foo`, the
implementation creates a `structuredClone` from its AST when first using
the utility. The reason we do that is because `foo` and `foo!` generate
different output and we don't want to accidentally mutate the same AST.
This structured clone is the start of the problem in the linked issue
(#15501).

If we don't do the structured clone, then substituting the `@apply`
rules would work, but then `foo` and `foo!` will generate the same
output, which is bad.

The linked issue has this structure:
```css
.foo {
  @apply bar;
}

@utility bar {
  @apply flex;
}
```

If we follow the steps above, this would substitute `@apply bar` first,
which
results in:

```css
.foo {
  @apply flex;
}
```

But the `bar` utility, was already cloned (and cached) so now we end up
with an `@apply` rule that is not substituted.

To properly solve this problem, we have to make sure that we collect all
the `@apply` at-rules, and apply them in the correct order. To do this,
we run a topological sort on them which ensures that all the
dependencies are applied before substituting the current `@apply`.

This means that in the above example, in order to process `@apply bar`,
we have to process the `bar` utility first.

If we run into a circular dependency, then we will throw an error like
before. You'll notice that the error message in this PR is updated to a
different spot. This one is a bit easier to grasp because it shows the
error where the circular dependency _starts_ not where it _ends_ (and
completes the circle). The previous message was not wrong (since it's a
circle), but now it's a bit easier to reason about.

Fixes: #15501
2025-01-07 15:19:37 +00:00
..
2024-12-11 15:27:20 +01:00