Skip to content

Commit 2e12f39

Browse files
Only emits theme variables that are used outside of being defined by another variable
1 parent 38221ac commit 2e12f39

File tree

7 files changed

+124
-31
lines changed

7 files changed

+124
-31
lines changed

integrations/cli/index.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,9 +1289,7 @@ test(
12891289
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
12901290
"
12911291
--- ./dist/out.css ---
1292-
:root, :host {
1293-
--color-blue-500: blue;
1294-
}
1292+
<EMPTY>
12951293
"
12961294
`)
12971295

packages/@tailwindcss-postcss/src/index.test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,7 @@ test('handle CSS when only using a `@reference` (we should not bail early)', asy
267267
)
268268

269269
expect(result.css.trim()).toMatchInlineSnapshot(`
270-
":root, :host {
271-
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
272-
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
273-
}
274-
275-
@media (width >= 48rem) {
270+
"@media (width >= 48rem) {
276271
.foo {
277272
bar: baz;
278273
}

packages/tailwindcss/src/__snapshots__/index.test.ts.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = `
44
":root, :host {
5-
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
6-
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
75
--color-red-500: oklch(.637 .237 25.331);
86
--spacing: .25rem;
97
}

packages/tailwindcss/src/ast.ts

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { parseAtRule } from './css-parser'
22
import type { DesignSystem } from './design-system'
33
import { enableRemoveUnusedThemeVariables } from './feature-flags'
4-
import { ThemeOptions } from './theme'
4+
import { Theme, ThemeOptions } from './theme'
55
import { DefaultMap } from './utils/default-map'
6+
import { extractUsedVariables } from './utils/variables'
67

78
const AT_SIGN = 0x40
89

@@ -270,6 +271,8 @@ export function optimizeAst(
270271
let keyframes = new Set<AtRule>()
271272
let usedKeyframeNames = new Set()
272273

274+
let variableDependencies = new DefaultMap<string, Set<string>>(() => new Set())
275+
273276
function transform(
274277
node: AstNode,
275278
parent: Extract<AstNode, { nodes: AstNode[] }>['nodes'],
@@ -289,7 +292,15 @@ export function optimizeAst(
289292

290293
// Track used CSS variables
291294
if (node.value.includes('var(')) {
292-
designSystem.trackUsedVariables(node.value)
295+
// Declaring another variable does not count as usage. Instead, we mark
296+
// the relationship
297+
if (context.theme && node.property[0] === '-' && node.property[1] === '-') {
298+
for (let variable of extractUsedVariables(node.value)) {
299+
variableDependencies.get(variable).add(node.property)
300+
}
301+
} else {
302+
designSystem.trackUsedVariables(node.value)
303+
}
293304
}
294305

295306
// Track used animation names
@@ -426,11 +437,17 @@ export function optimizeAst(
426437
}
427438
// Remove unused theme variables
428439
if (enableRemoveUnusedThemeVariables) {
440+
// Remove unused theme variables
429441
next: for (let [parent, declarations] of cssThemeVariables) {
430442
for (let declaration of declarations) {
431-
let options = designSystem.theme.getOptions(declaration.property)
432-
433-
if (options & (ThemeOptions.STATIC | ThemeOptions.USED)) {
443+
// Find out if a variable is either used directly or if any of the
444+
// variables referencing it is used.
445+
let variableUsed = isVariableUsed(
446+
declaration.property,
447+
designSystem.theme,
448+
variableDependencies,
449+
)
450+
if (variableUsed) {
434451
if (declaration.property.startsWith('--animate-')) {
435452
let parts = declaration.value!.split(/\s+/)
436453
for (let part of parts) usedKeyframeNames.add(part)
@@ -570,3 +587,33 @@ function findNode(ast: AstNode[], fn: (node: AstNode) => boolean): AstNode[] | n
570587
})
571588
return foundPath
572589
}
590+
591+
// Find out if a variable is either used directly or if any of the variables that depend on it are
592+
// used
593+
function isVariableUsed(
594+
variable: string,
595+
theme: Theme,
596+
variableDependencies: Map<string, Set<string>>,
597+
alreadySeenVariables: Set<string> = new Set(),
598+
): boolean {
599+
// Break recursions when visiting a variable twice
600+
if (alreadySeenVariables.has(variable)) {
601+
return true
602+
} else {
603+
alreadySeenVariables.add(variable)
604+
}
605+
606+
let options = theme.getOptions(variable)
607+
if (options & (ThemeOptions.STATIC | ThemeOptions.USED)) {
608+
return true
609+
} else {
610+
let dependencies = variableDependencies.get(variable) ?? []
611+
for (let dependency of dependencies) {
612+
if (isVariableUsed(dependency, theme, variableDependencies, alreadySeenVariables)) {
613+
return true
614+
}
615+
}
616+
}
617+
618+
return false
619+
}

packages/tailwindcss/src/design-system.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getClassOrder } from './sort'
77
import type { Theme, ThemeKey } from './theme'
88
import { Utilities, createUtilities, withAlpha } from './utilities'
99
import { DefaultMap } from './utils/default-map'
10-
import * as ValueParser from './value-parser'
10+
import { extractUsedVariables } from './utils/variables'
1111
import { Variants, createVariants } from './variants'
1212

1313
export type DesignSystem = {
@@ -69,19 +69,9 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
6969
})
7070

7171
let trackUsedVariables = new DefaultMap((raw) => {
72-
ValueParser.walk(ValueParser.parse(raw), (node) => {
73-
if (node.kind !== 'function' || node.value !== 'var') return
74-
75-
ValueParser.walk(node.nodes, (child) => {
76-
if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return
77-
78-
theme.markUsedVariable(child.value)
79-
})
80-
81-
return ValueParser.ValueWalkAction.Skip
82-
})
83-
84-
return true
72+
for (let variable of extractUsedVariables(raw)) {
73+
theme.markUsedVariable(variable)
74+
}
8575
})
8676

8777
let designSystem: DesignSystem = {
@@ -104,7 +94,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
10494
},
10595
})
10696

107-
astNodes = optimizeAst(astNodes, designSystem)
97+
astNodes = optimizeAst(astNodes, designSystem, null)
10898

10999
if (astNodes.length === 0 || wasInvalid) {
110100
result.push(null)

packages/tailwindcss/src/index.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,6 +2366,54 @@ describe('Parsing themes values from CSS', () => {
23662366
}"
23672367
`)
23682368
})
2369+
2370+
test('only emits theme variables that are used outside of being defined by another variable', async () => {
2371+
let { build } = await compile(
2372+
css`
2373+
@theme {
2374+
--var-a: var(--var-b);
2375+
--var-b: var(--var-c);
2376+
--var-c: var(--var-d);
2377+
--var-d: red;
2378+
2379+
--var-four: green;
2380+
--var-three: var(--var-four);
2381+
--var-two: var(--var-three);
2382+
--var-one: var(--var-two);
2383+
2384+
--var-eins: var(--var-zwei);
2385+
--var-zwei: var(--var-drei);
2386+
--var-drei: var(--var-vier);
2387+
--var-vier: blue;
2388+
}
2389+
2390+
@utility get-var-* {
2391+
color: --value(--var-\*);
2392+
}
2393+
@tailwind utilities;
2394+
`,
2395+
{},
2396+
)
2397+
2398+
expect(optimizeCss(build(['get-var-b', 'get-var-two'])).trim()).toMatchInlineSnapshot(`
2399+
":root, :host {
2400+
--var-b: var(--var-c);
2401+
--var-c: var(--var-d);
2402+
--var-d: red;
2403+
--var-four: green;
2404+
--var-three: var(--var-four);
2405+
--var-two: var(--var-three);
2406+
}
2407+
2408+
.get-var-b {
2409+
color: var(--var-b);
2410+
}
2411+
2412+
.get-var-two {
2413+
color: var(--var-two);
2414+
}"
2415+
`)
2416+
})
23692417
})
23702418

23712419
describe('plugins', () => {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as ValueParser from '../value-parser'
2+
3+
export function extractUsedVariables(raw: string): string[] {
4+
let variables: string[] = []
5+
ValueParser.walk(ValueParser.parse(raw), (node) => {
6+
if (node.kind !== 'function' || node.value !== 'var') return
7+
8+
ValueParser.walk(node.nodes, (child) => {
9+
if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return
10+
11+
variables.push(child.value)
12+
})
13+
14+
return ValueParser.ValueWalkAction.Skip
15+
})
16+
return variables
17+
}

0 commit comments

Comments
 (0)