diff --git a/CHANGELOG.md b/CHANGELOG.md index 255584a8afcc..96ed1a524434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts index aacdfd256c00..4c857fdf511a 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts @@ -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 diff --git a/packages/tailwindcss/src/apply.ts b/packages/tailwindcss/src/apply.ts index 691df2920a6e..526a5e000900 100644 --- a/packages/tailwindcss/src/apply.ts +++ b/packages/tailwindcss/src/apply.ts @@ -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)) { diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 6530541a0e43..1331f5d3a2d5 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -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, 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) } diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index 60fe16ecc8c1..7efebf1a5d26 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -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[] parseVariant(variant: string): Readonly | null - compileAstNodes(candidate: Candidate): ReturnType + compileAstNodes(candidate: Candidate, flags?: CompileAstFlags): ReturnType 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) => { - let ast = compileAstNodes(candidate, designSystem) - - // 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 [] - } + let compiledAstNodes = new DefaultMap((flags) => { + return new DefaultMap((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 [] + } - 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) { diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index c03c149c2766..430c3a71dff1 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -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', () => {