Skip to content

Commit f040289

Browse files
Template migrations: Add automatic var injection codemods
1 parent 89f0047 commit f040289

5 files changed

Lines changed: 122 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Add support for prefixes ([#14501](https://github.com/tailwindlabs/tailwindcss/pull/14501))
13+
- _Experimental_: Add template codemods for removal of automatic `var(…)` injection
1314

1415
### Fixed
1516

integrations/upgrade/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ test(
1919
`,
2020
'src/index.html': html`
2121
<h1>🤠👋</h1>
22-
<div class="!flex sm:!block"></div>
22+
<div class="!flex sm:!block bg-[--my-red]"></div>
2323
`,
2424
'src/input.css': css`
2525
@tailwind base;
@@ -35,7 +35,7 @@ test(
3535
'src/index.html',
3636
html`
3737
<h1>🤠👋</h1>
38-
<div class="flex! sm:block!"></div>
38+
<div class="flex! sm:block! bg-[var(--my-red)]"></div>
3939
`,
4040
)
4141

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
2+
import { expect, test } from 'vitest'
3+
import { printCandidate } from '../candidates'
4+
import { automaticVarInjection } from './automatic-var-injection'
5+
6+
test.each([
7+
// Arbitrary utilities
8+
['[color:--my-color]', '[color:var(--my-color)]'],
9+
10+
['bg-[--my-color]', 'bg-[var(--my-color)]'],
11+
['bg-[color:--my-color]', 'bg-[color:var(--my-color)]'],
12+
['border-[length:--my-length]', 'border-[length:var(--my-length)]'],
13+
['border-[line-width:--my-width]', 'border-[line-width:var(--my-width)]'],
14+
15+
// Does not add var() if there is a _ before the variable name
16+
['bg-[_--my-color]', null],
17+
['bg-[color:_--my-color]', null],
18+
['border-[length:_--my-length]', null],
19+
['border-[line-width:_--my-width]', null],
20+
21+
// Arbitrary modifiers
22+
['[color:--my-color]/[--my-opacity]', '[color:var(--my-color)]/[var(--my-opacity)]'],
23+
['bg-red-500/[--my-opacity]', 'bg-red-500/[var(--my-opacity)]'],
24+
['bg-[--my-color]/[--my-opacity]', 'bg-[var(--my-color)]/[var(--my-opacity)]'],
25+
['bg-[color:--my-color]/[--my-opacity]', 'bg-[color:var(--my-color)]/[var(--my-opacity)]'],
26+
27+
['[color:--my-color]/[_--my-opacity]', '[color:var(--my-color)]/[_--my-opacity]'],
28+
['bg-red-500/[_--my-opacity]', null],
29+
['bg-[--my-color]/[_--my-opacity]', 'bg-[var(--my-color)]/[_--my-opacity]'],
30+
['bg-[color:--my-color]/[_--my-opacity]', 'bg-[color:var(--my-color)]/[_--my-opacity]'],
31+
32+
// Arbitrary variants
33+
['supports-[--test]:flex', 'supports-[var(--test)]:flex'],
34+
['supports-[_--test]:flex', null],
35+
])('%s => %s', async (candidate, result) => {
36+
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
37+
base: __dirname,
38+
})
39+
40+
let migrated = automaticVarInjection(designSystem.parseCandidate(candidate)[0]!)
41+
expect(migrated ? printCandidate(migrated) : migrated).toEqual(result)
42+
})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { Candidate, Variant } from '../../../../tailwindcss/src/candidate'
2+
3+
export function automaticVarInjection(candidate: Candidate): Candidate | null {
4+
let didChange = false
5+
6+
// Add `var(…)` in modifier position, e.g.:
7+
//
8+
// `bg-red-500/[--my-opacity]` => `bg-red-500/[var(--my-opacity)]`
9+
if (
10+
'modifier' in candidate &&
11+
candidate.modifier?.kind === 'arbitrary' &&
12+
candidate.modifier.value.startsWith('--')
13+
) {
14+
candidate.modifier.value = `var(${candidate.modifier.value})`
15+
didChange = true
16+
}
17+
18+
// Add `var(…)` to all variants, e.g.:
19+
//
20+
// `supports-[--test]:flex'` => `supports-[var(--test)]:flex`
21+
for (let variant of candidate.variants) {
22+
let didChangeVariant = injectVarIntoVariant(variant)
23+
if (didChangeVariant) {
24+
didChange = true
25+
}
26+
}
27+
28+
// Add `var(…)` to arbitrary candidates, e.g.:
29+
//
30+
// `[color:--my-color]` => `[color:var(--my-color)]`
31+
if (candidate.kind === 'arbitrary' && candidate.value.startsWith('--')) {
32+
candidate.value = `var(${candidate.value})`
33+
didChange = true
34+
}
35+
36+
// Add `var(…)` to arbitrary values for functional candidates, e.g.:
37+
//
38+
// `bg-[--my-color]` => `bg-[var(--my-color)]`
39+
if (
40+
candidate.kind === 'functional' &&
41+
candidate.value &&
42+
candidate.value.kind === 'arbitrary' &&
43+
candidate.value.value.startsWith('--')
44+
) {
45+
candidate.value.value = `var(${candidate.value.value})`
46+
didChange = true
47+
}
48+
49+
if (didChange) {
50+
return candidate
51+
}
52+
return null
53+
}
54+
55+
function injectVarIntoVariant(variant: Variant): boolean {
56+
let didChange = false
57+
if (
58+
variant.kind === 'functional' &&
59+
variant.value &&
60+
variant.value.kind === 'arbitrary' &&
61+
variant.value.value.startsWith('--')
62+
) {
63+
variant.value.value = `var(${variant.value.value})`
64+
didChange = true
65+
}
66+
67+
if (variant.kind === 'compound') {
68+
let compoundDidChange = injectVarIntoVariant(variant.variant)
69+
if (compoundDidChange) {
70+
didChange = true
71+
}
72+
}
73+
74+
return didChange
75+
}

packages/@tailwindcss-upgrade/src/template/migrate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import path from 'node:path'
33
import type { Candidate } from '../../../tailwindcss/src/candidate'
44
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
55
import { extractCandidates, printCandidate, replaceCandidateInContent } from './candidates'
6+
import { automaticVarInjection } from './codemods/automatic-var-injection'
67
import { migrateImportant } from './codemods/migrate-important'
78

89
export type Migration = (candidate: Candidate) => Candidate | null
910

1011
export default async function migrateContents(
1112
designSystem: DesignSystem,
1213
contents: string,
13-
migrations: Migration[] = [migrateImportant],
14+
migrations: Migration[] = [migrateImportant, automaticVarInjection],
1415
): Promise<string> {
1516
let candidates = await extractCandidates(designSystem, contents)
1617

0 commit comments

Comments
 (0)