mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
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