mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Don't swallow @utility declarations when @apply is used in nested rules (#16940)
Fixes #16935 This PR fixes an issue where the order of how `@apply` was resolved was incorrect for nested rules. Consider this example: ```css .rule { @apply underline; .nested-rule { @apply custom-utility; } } @utility custom-utility { @apply flex; } ``` The way we topologically sort these, we end up with a list that looks roughly like this: ```css .rule { @apply underline; .nested-rule { @apply custom-utility; } } @utility custom-utility { @apply flex; } .nested-rule { @apply custom-utility; } ``` As you can see here the nested rule is now part of the top-level list. This is correct because we first have to substitute the `@apply` inside the `@utility custom-utility` before we can apply the `custom-utility` inside `.nested-rule`. However, because we were using a regular AST walk and because the initial `.rule` also contains the `.nested-rule` as child, we would first substitute the `@apply` inside the `.nested-rule`, causing the design-system to force resolve (and cache) the wrong value for `custom-utility`. Because the list is already flattened, we do not need to recursively look into child declarations when we traverse the sorted list. This PR changes it to use a regular `for` loop instead of the `walk`. ## Test plan - Added a regression test - Rest of tests still green
This commit is contained in:
parent
b0aa20c30e
commit
db405304f4
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Ensure `not-*` does not remove `:is(…)` from variants ([#16825](https://github.com/tailwindlabs/tailwindcss/pull/16825))
|
||||
- Ensure `@keyframes` are correctly emitted when using a prefixed setup ([#16850](https://github.com/tailwindlabs/tailwindcss/pull/16850))
|
||||
- Don't swallow `@utility` declarations when `@apply` is used in nested rules ([#16940](https://github.com/tailwindlabs/tailwindcss/pull/16940))
|
||||
- Ensure `outline-hidden` behaves like `outline-none` in non-`forced-colors` mode ([#](https://github.com/tailwindlabs/tailwindcss/pull/))
|
||||
|
||||
## [4.0.9] - 2025-02-25
|
||||
|
||||
@ -146,39 +146,45 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||
visit(node)
|
||||
}
|
||||
|
||||
// Substitute the `@apply` at-rules in order
|
||||
walk(sorted, (node, { replaceWith }) => {
|
||||
if (node.kind !== 'at-rule' || node.name !== '@apply') return
|
||||
let candidates = node.params.split(/\s+/g)
|
||||
// Substitute the `@apply` at-rules in order. Note that the list is going to
|
||||
// be flattened so we do not have to recursively walk over child rules
|
||||
for (let parent of sorted) {
|
||||
if (!('nodes' in parent)) continue
|
||||
|
||||
// Replace the `@apply` rule with the actual utility classes
|
||||
{
|
||||
// Parse the candidates to an AST that we can replace the `@apply` rule
|
||||
// with.
|
||||
let candidateAst = compileCandidates(candidates, designSystem, {
|
||||
onInvalidCandidate: (candidate) => {
|
||||
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
|
||||
},
|
||||
}).astNodes
|
||||
for (let i = 0; i < parent.nodes.length; i++) {
|
||||
let node = parent.nodes[i]
|
||||
if (node.kind !== 'at-rule' || node.name !== '@apply') continue
|
||||
|
||||
// Collect the nodes to insert in place of the `@apply` rule. When a rule
|
||||
// was used, we want to insert its children instead of the rule because we
|
||||
// don't want the wrapping selector.
|
||||
let newNodes: AstNode[] = []
|
||||
for (let candidateNode of candidateAst) {
|
||||
if (candidateNode.kind === 'rule') {
|
||||
for (let child of candidateNode.nodes) {
|
||||
newNodes.push(child)
|
||||
let candidates = node.params.split(/\s+/g)
|
||||
|
||||
// Replace the `@apply` rule with the actual utility classes
|
||||
{
|
||||
// Parse the candidates to an AST that we can replace the `@apply` rule
|
||||
// with.
|
||||
let candidateAst = compileCandidates(candidates, designSystem, {
|
||||
onInvalidCandidate: (candidate) => {
|
||||
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
|
||||
},
|
||||
}).astNodes
|
||||
|
||||
// Collect the nodes to insert in place of the `@apply` rule. When a rule
|
||||
// was used, we want to insert its children instead of the rule because we
|
||||
// don't want the wrapping selector.
|
||||
let newNodes: AstNode[] = []
|
||||
for (let candidateNode of candidateAst) {
|
||||
if (candidateNode.kind === 'rule') {
|
||||
for (let child of candidateNode.nodes) {
|
||||
newNodes.push(child)
|
||||
}
|
||||
} else {
|
||||
newNodes.push(candidateNode)
|
||||
}
|
||||
} else {
|
||||
newNodes.push(candidateNode)
|
||||
}
|
||||
|
||||
parent.nodes.splice(i, 1, ...newNodes)
|
||||
}
|
||||
|
||||
replaceWith(newNodes)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
|
||||
@ -530,6 +530,41 @@ describe('@apply', () => {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/16935
|
||||
it('should now swallow @utility declarations when @apply is used in nested rules', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
@tailwind utilities;
|
||||
|
||||
.ignore-me {
|
||||
@apply underline;
|
||||
div {
|
||||
@apply custom-utility;
|
||||
}
|
||||
}
|
||||
|
||||
@utility custom-utility {
|
||||
@apply flex;
|
||||
}
|
||||
`,
|
||||
['custom-utility'],
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".custom-utility {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ignore-me {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.ignore-me div {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('arbitrary variants', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user