From 2756659b2e012c43cd86e2eda70623861ca48bc2 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 09:55:30 -0400 Subject: [PATCH 1/6] =?UTF-8?q?Allow=20compiling=20utilities=20without=20c?= =?UTF-8?q?onsidering=20the=20design=20systen=E2=80=99s=20important=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/tailwindcss/src/compile.ts | 28 +++++++++--- packages/tailwindcss/src/design-system.ts | 53 +++++++++++++---------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 6530541a0e43..c6dc9e5f29dd 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 || respectImportant === undefined) { + 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 = 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 || (designSystem.important && respectImportant)) { applyImportant(nodes) } diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index 60fe16ecc8c1..cac5e3583ff1 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,10 @@ 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) { + flags ??= CompileAstFlags.RespectImportant + + return compiledAstNodes.get(flags).get(candidate) }, printCandidate(candidate: Candidate) { From 47179940897ad0d8fc6c525813a09c6a3ae95e3c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 10:18:09 -0400 Subject: [PATCH 2/6] Ensure both caches are filled when disabling `@theme inline` for upgrades --- .../src/codemods/template/signatures.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 From 592c959b4e7aba67bbbb4ca98f3b17631260861c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 10:26:39 -0400 Subject: [PATCH 3/6] Fix typo --- packages/tailwindcss/src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index c03c149c2766..d28aee08ee3d 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` From 7fd53f147aef8da3c1ceca093c57c5c49b2f2024 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 10:27:06 -0400 Subject: [PATCH 4/6] Make `@apply` treat utilities as not important unless they have a `!` --- packages/tailwindcss/src/apply.ts | 1 + packages/tailwindcss/src/index.test.ts | 35 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) 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/index.test.ts b/packages/tailwindcss/src/index.test.ts index d28aee08ee3d..430c3a71dff1 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -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', () => { From 928eab339dd517bd91665c42340bff9c2704cdab Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 10:35:10 -0400 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From 4fb41db995ccf871aa9699956d5c5faa78980454 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 27 Jun 2025 11:47:00 -0400 Subject: [PATCH 6/6] Address feedback Co-authored-by: Robin Malfait --- packages/tailwindcss/src/compile.ts | 6 +++--- packages/tailwindcss/src/design-system.ts | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index c6dc9e5f29dd..1331f5d3a2d5 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -49,7 +49,7 @@ export function compileCandidates( let flags = CompileAstFlags.None - if (respectImportant || respectImportant === undefined) { + if (respectImportant ?? true) { flags |= CompileAstFlags.RespectImportant } @@ -136,7 +136,7 @@ export function compileAstNodes( let asts = compileBaseUtility(candidate, designSystem) if (asts.length === 0) return [] - let respectImportant = Boolean(flags & CompileAstFlags.RespectImportant) + let respectImportant = designSystem.important && Boolean(flags & CompileAstFlags.RespectImportant) let rules: { node: AstNode @@ -154,7 +154,7 @@ export function compileAstNodes( // 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 || (designSystem.important && respectImportant)) { + if (candidate.important || respectImportant) { applyImportant(nodes) } diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index cac5e3583ff1..7efebf1a5d26 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -141,9 +141,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { parseVariant(variant: string) { return parsedVariants.get(variant) }, - compileAstNodes(candidate: Candidate, flags?: CompileAstFlags) { - flags ??= CompileAstFlags.RespectImportant - + compileAstNodes(candidate: Candidate, flags = CompileAstFlags.RespectImportant) { return compiledAstNodes.get(flags).get(candidate) },