Don't consider the global important state in @apply (#18404)
Fixes #18400 In v3 when you used `important: true` it did not affect `@apply`. However, in v4 it does and there's no way to make it *not*. This is definitely a bug and would be unexpected for users coming from v3 who use `@apply` and `important` together. Basically, the following code, along with the detected utility `flex` in source files… ```css @import 'tailwindcss/utilities' important; .flex-explicitly-important { @apply flex!; } .flex-not-important { @apply flex; } ``` … would output this: ```css .flex { display: flex !important; } .flex-explicitly-important { display: flex !important; } .flex-not-important { display: flex !important; } ``` But it's expected that `@apply` doesn't consider the "global" important state. This PR addresss this problem and now the output is this: ```css .flex { display: flex !important; } .flex-explicitly-important { display: flex !important; } .flex-not-important { display: flex; /* this line changed */ } ``` If you want to mark a utility as important in `@apply` you can still use `!` after the utility to do so as shown above. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
b716d10129
commit
2030e942bc
6 changed files with 102 additions and 30 deletions
|
|
@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
### Fixed
|
||||
|
||||
- Don't consider the global important state in `@apply` ([#18404](https://github.com/tailwindlabs/tailwindcss/pull/18404))
|
||||
|
||||
## [4.1.11] - 2025-06-26
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { substituteAtApply } from '../../../../tailwindcss/src/apply'
|
|||
import { atRule, styleRule, toCss, walk, type AstNode } from '../../../../tailwindcss/src/ast'
|
||||
import { printArbitraryValue } from '../../../../tailwindcss/src/candidate'
|
||||
import * as SelectorParser from '../../../../tailwindcss/src/compat/selector-parser'
|
||||
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
|
||||
import { CompileAstFlags, type DesignSystem } from '../../../../tailwindcss/src/design-system'
|
||||
import { ThemeOptions } from '../../../../tailwindcss/src/theme'
|
||||
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
|
||||
import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type'
|
||||
|
|
@ -40,7 +40,16 @@ export const computeUtilitySignature = new DefaultMap<
|
|||
// Use `@apply` to normalize the selector to `.x`
|
||||
let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])]
|
||||
|
||||
temporarilyDisableThemeInline(designSystem, () => substituteAtApply(ast, designSystem))
|
||||
temporarilyDisableThemeInline(designSystem, () => {
|
||||
// There's separate utility caches for respect important vs not
|
||||
// so we want to compile them both with `@theme inline` disabled
|
||||
for (let candidate of designSystem.parseCandidate(utility)) {
|
||||
designSystem.compileAstNodes(candidate, CompileAstFlags.None)
|
||||
designSystem.compileAstNodes(candidate, CompileAstFlags.RespectImportant)
|
||||
}
|
||||
|
||||
substituteAtApply(ast, designSystem)
|
||||
})
|
||||
|
||||
// We will be mutating the AST, so we need to clone it first to not affect
|
||||
// the original AST
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
|||
// with.
|
||||
let candidates = Object.keys(candidateOffsets)
|
||||
let compiled = compileCandidates(candidates, designSystem, {
|
||||
respectImportant: false,
|
||||
onInvalidCandidate: (candidate) => {
|
||||
// When using prefix, make sure prefix is used in candidate
|
||||
if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
type StyleRule,
|
||||
} from './ast'
|
||||
import { type Candidate, type Variant } from './candidate'
|
||||
import { type DesignSystem } from './design-system'
|
||||
import { CompileAstFlags, type DesignSystem } from './design-system'
|
||||
import GLOBAL_PROPERTY_ORDER from './property-order'
|
||||
import { asColor, type Utility } from './utilities'
|
||||
import { compare } from './utils/compare'
|
||||
|
|
@ -19,7 +19,10 @@ import type { Variants } from './variants'
|
|||
export function compileCandidates(
|
||||
rawCandidates: Iterable<string>,
|
||||
designSystem: DesignSystem,
|
||||
{ onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {},
|
||||
{
|
||||
onInvalidCandidate,
|
||||
respectImportant,
|
||||
}: { onInvalidCandidate?: (candidate: string) => void; respectImportant?: boolean } = {},
|
||||
) {
|
||||
let nodeSorting = new Map<
|
||||
AstNode,
|
||||
|
|
@ -44,6 +47,12 @@ export function compileCandidates(
|
|||
matches.set(rawCandidate, candidates)
|
||||
}
|
||||
|
||||
let flags = CompileAstFlags.None
|
||||
|
||||
if (respectImportant ?? true) {
|
||||
flags |= CompileAstFlags.RespectImportant
|
||||
}
|
||||
|
||||
let variantOrderMap = designSystem.getVariantOrder()
|
||||
|
||||
// Create the AST
|
||||
|
|
@ -51,7 +60,7 @@ export function compileCandidates(
|
|||
let found = false
|
||||
|
||||
for (let candidate of candidates) {
|
||||
let rules = designSystem.compileAstNodes(candidate)
|
||||
let rules = designSystem.compileAstNodes(candidate, flags)
|
||||
if (rules.length === 0) continue
|
||||
|
||||
found = true
|
||||
|
|
@ -119,10 +128,16 @@ export function compileCandidates(
|
|||
}
|
||||
}
|
||||
|
||||
export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) {
|
||||
export function compileAstNodes(
|
||||
candidate: Candidate,
|
||||
designSystem: DesignSystem,
|
||||
flags: CompileAstFlags,
|
||||
) {
|
||||
let asts = compileBaseUtility(candidate, designSystem)
|
||||
if (asts.length === 0) return []
|
||||
|
||||
let respectImportant = designSystem.important && Boolean(flags & CompileAstFlags.RespectImportant)
|
||||
|
||||
let rules: {
|
||||
node: AstNode
|
||||
propertySort: {
|
||||
|
|
@ -136,7 +151,10 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem
|
|||
for (let nodes of asts) {
|
||||
let propertySort = getPropertySort(nodes)
|
||||
|
||||
if (candidate.important || designSystem.important) {
|
||||
// If the candidate itself is important then we want to always mark
|
||||
// the utility as important. However, at a design system level we want
|
||||
// to be able to opt-out when using things like `@apply`
|
||||
if (candidate.important || respectImportant) {
|
||||
applyImportant(nodes)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ import { DefaultMap } from './utils/default-map'
|
|||
import { extractUsedVariables } from './utils/variables'
|
||||
import { Variants, createVariants } from './variants'
|
||||
|
||||
export const enum CompileAstFlags {
|
||||
None = 0,
|
||||
RespectImportant = 1 << 0,
|
||||
}
|
||||
|
||||
export type DesignSystem = {
|
||||
theme: Theme
|
||||
utilities: Utilities
|
||||
|
|
@ -34,7 +39,7 @@ export type DesignSystem = {
|
|||
|
||||
parseCandidate(candidate: string): Readonly<Candidate>[]
|
||||
parseVariant(variant: string): Readonly<Variant> | null
|
||||
compileAstNodes(candidate: Candidate): ReturnType<typeof compileAstNodes>
|
||||
compileAstNodes(candidate: Candidate, flags?: CompileAstFlags): ReturnType<typeof compileAstNodes>
|
||||
|
||||
printCandidate(candidate: Candidate): string
|
||||
printVariant(variant: Variant): string
|
||||
|
|
@ -57,26 +62,28 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
|
|||
Array.from(parseCandidate(candidate, designSystem)),
|
||||
)
|
||||
|
||||
let compiledAstNodes = new DefaultMap<Candidate>((candidate) => {
|
||||
let ast = compileAstNodes(candidate, designSystem)
|
||||
let compiledAstNodes = new DefaultMap<number>((flags) => {
|
||||
return new DefaultMap<Candidate>((candidate) => {
|
||||
let ast = compileAstNodes(candidate, designSystem, flags)
|
||||
|
||||
// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
|
||||
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
|
||||
// calls so we need evaluate any functions we find there that weren't in
|
||||
// the source CSS.
|
||||
try {
|
||||
substituteFunctions(
|
||||
ast.map(({ node }) => node),
|
||||
designSystem,
|
||||
)
|
||||
} catch (err) {
|
||||
// If substitution fails then the candidate likely contains a call to
|
||||
// `theme()` that is invalid which may be because of incorrect usage,
|
||||
// invalid arguments, or a theme key that does not exist.
|
||||
return []
|
||||
}
|
||||
// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
|
||||
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
|
||||
// calls so we need evaluate any functions we find there that weren't in
|
||||
// the source CSS.
|
||||
try {
|
||||
substituteFunctions(
|
||||
ast.map(({ node }) => node),
|
||||
designSystem,
|
||||
)
|
||||
} catch (err) {
|
||||
// If substitution fails then the candidate likely contains a call to
|
||||
// `theme()` that is invalid which may be because of incorrect usage,
|
||||
// invalid arguments, or a theme key that does not exist.
|
||||
return []
|
||||
}
|
||||
|
||||
return ast
|
||||
return ast
|
||||
})
|
||||
})
|
||||
|
||||
let trackUsedVariables = new DefaultMap((raw) => {
|
||||
|
|
@ -134,8 +141,8 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
|
|||
parseVariant(variant: string) {
|
||||
return parsedVariants.get(variant)
|
||||
},
|
||||
compileAstNodes(candidate: Candidate) {
|
||||
return compiledAstNodes.get(candidate)
|
||||
compileAstNodes(candidate: Candidate, flags = CompileAstFlags.RespectImportant) {
|
||||
return compiledAstNodes.get(flags).get(candidate)
|
||||
},
|
||||
|
||||
printCandidate(candidate: Candidate) {
|
||||
|
|
|
|||
|
|
@ -666,7 +666,7 @@ describe('@apply', () => {
|
|||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/16935
|
||||
it('should now swallow @utility declarations when @apply is used in nested rules', async () => {
|
||||
it('should not swallow @utility declarations when @apply is used in nested rules', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
|
|
@ -777,6 +777,41 @@ describe('@apply', () => {
|
|||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/18400
|
||||
it('should ignore the design systems `important` flag when using @apply', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
@import 'tailwindcss/utilities' important;
|
||||
.flex-explicitly-important {
|
||||
@apply flex!;
|
||||
}
|
||||
.flex-not-important {
|
||||
@apply flex;
|
||||
}
|
||||
`,
|
||||
['flex'],
|
||||
{
|
||||
async loadStylesheet(_, base) {
|
||||
return {
|
||||
content: '@tailwind utilities;',
|
||||
base,
|
||||
path: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".flex, .flex-explicitly-important {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.flex-not-important {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('arbitrary variants', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue