Skip to content

Commit 2e87288

Browse files
Migrate at-apply utilites with template migrations (#14574)
This PR extracts all _candidate migrations_ from the existing _template migrations_ and reuses these in the `@apply` CSS migration. Seems like this _JustWorks✨_. --------- Co-authored-by: Adam Wathan <[email protected]>
1 parent aa343a9 commit 2e87288

File tree

7 files changed

+98
-20
lines changed

7 files changed

+98
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Add support for `blocklist` in config files ([#14556](https://github.com/tailwindlabs/tailwindcss/pull/14556))
1515
- Add `color-scheme` utilities ([#14567](https://github.com/tailwindlabs/tailwindcss/pull/14567))
1616
- _Experimental_: Migrate `@import "tailwindcss/tailwind.css"` to `@import "tailwindcss"` ([#14514](https://github.com/tailwindlabs/tailwindcss/pull/14514))
17+
- _Experimental_: Migrate `@apply` utilities with the template codemods ([#14574](https://github.com/tailwindlabs/tailwindcss/pull/14574))
1718
- _Experimental_: Add template codemods for migrating variant order ([#14524](https://github.com/tailwindlabs/tailwindcss/pull/14524]))
1819
- _Experimental_: Add template codemods for migrating `bg-gradient-*` utilities to `bg-linear-*` ([#14537](https://github.com/tailwindlabs/tailwindcss/pull/14537]))
1920
- _Experimental_: Add template codemods for migrating prefixes ([#14557](https://github.com/tailwindlabs/tailwindcss/pull/14557]))

integrations/upgrade/index.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ test(
6969
@tailwind base;
7070
@tailwind components;
7171
@tailwind utilities;
72+
73+
.btn {
74+
@apply !tw__rounded-md tw__px-2 tw__py-1 tw__bg-blue-500 tw__text-white;
75+
}
7276
`,
7377
},
7478
},
@@ -83,7 +87,15 @@ test(
8387
`,
8488
)
8589

86-
await fs.expectFileToContain('src/input.css', css`@import 'tailwindcss' prefix(tw);`)
90+
await fs.expectFileToContain('src/input.css', css` @import 'tailwindcss' prefix(tw); `)
91+
await fs.expectFileToContain(
92+
'src/input.css',
93+
css`
94+
.btn {
95+
@apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white;
96+
}
97+
`,
98+
)
8799
},
88100
)
89101

packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
12
import dedent from 'dedent'
23
import postcss from 'postcss'
34
import { expect, it } from 'vitest'
45
import { migrateAtApply } from './migrate-at-apply'
56

67
const css = dedent
78

8-
function migrate(input: string) {
9+
function migrateWithoutConfig(input: string) {
910
return postcss()
1011
.use(migrateAtApply())
1112
.process(input, { from: expect.getState().testPath })
@@ -14,7 +15,7 @@ function migrate(input: string) {
1415

1516
it('should not migrate `@apply`, when there are no issues', async () => {
1617
expect(
17-
await migrate(css`
18+
await migrateWithoutConfig(css`
1819
.foo {
1920
@apply flex flex-col items-center;
2021
}
@@ -28,7 +29,7 @@ it('should not migrate `@apply`, when there are no issues', async () => {
2829

2930
it('should append `!` to each utility, when using `!important`', async () => {
3031
expect(
31-
await migrate(css`
32+
await migrateWithoutConfig(css`
3233
.foo {
3334
@apply flex flex-col !important;
3435
}
@@ -43,7 +44,7 @@ it('should append `!` to each utility, when using `!important`', async () => {
4344
// TODO: Handle SCSS syntax
4445
it.skip('should append `!` to each utility, when using `#{!important}`', async () => {
4546
expect(
46-
await migrate(css`
47+
await migrateWithoutConfig(css`
4748
.foo {
4849
@apply flex flex-col #{!important};
4950
}
@@ -57,7 +58,7 @@ it.skip('should append `!` to each utility, when using `#{!important}`', async (
5758

5859
it('should move the legacy `!` prefix, to the new `!` postfix notation', async () => {
5960
expect(
60-
await migrate(css`
61+
await migrateWithoutConfig(css`
6162
.foo {
6263
@apply !flex flex-col! hover:!items-start items-center;
6364
}
@@ -68,3 +69,36 @@ it('should move the legacy `!` prefix, to the new `!` postfix notation', async (
6869
}"
6970
`)
7071
})
72+
73+
it('should apply all candidate migration when migrating with a config', async () => {
74+
async function migrateWithConfig(input: string) {
75+
return postcss()
76+
.use(
77+
migrateAtApply({
78+
designSystem: await __unstable__loadDesignSystem(
79+
css`
80+
@import 'tailwindcss' prefix(tw);
81+
`,
82+
{ base: __dirname },
83+
),
84+
userConfig: {
85+
prefix: 'tw_',
86+
},
87+
}),
88+
)
89+
.process(input, { from: expect.getState().testPath })
90+
.then((result) => result.css)
91+
}
92+
93+
expect(
94+
await migrateWithConfig(css`
95+
.foo {
96+
@apply !tw_flex [color:--my-color] tw_bg-gradient-to-t;
97+
}
98+
`),
99+
).toMatchInlineSnapshot(`
100+
".foo {
101+
@apply tw:flex! tw:[color:var(--my-color)] tw:bg-linear-to-t;
102+
}"
103+
`)
104+
})

packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import type { AtRule, Plugin } from 'postcss'
2+
import type { Config } from 'tailwindcss'
3+
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
24
import { segment } from '../../../tailwindcss/src/utils/segment'
5+
import { migrateCandidate } from '../template/migrate'
36

4-
export function migrateAtApply(): Plugin {
7+
export function migrateAtApply({
8+
designSystem,
9+
userConfig,
10+
}: { designSystem?: DesignSystem; userConfig?: Config } = {}): Plugin {
511
function migrate(atRule: AtRule) {
612
let utilities = atRule.params.split(/(\s+)/)
713
let important =
@@ -30,6 +36,12 @@ export function migrateAtApply(): Plugin {
3036
return [...variants, utility].join(':')
3137
})
3238

39+
// If we have a valid designSystem and config setup, we can run all
40+
// candidate migrations on each utility
41+
if (designSystem && userConfig) {
42+
params = params.map((param) => migrateCandidate(designSystem, userConfig, param))
43+
}
44+
3345
atRule.params = params.join('').trim()
3446
}
3547

packages/@tailwindcss-upgrade/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ async function run() {
112112
// Migrate each file
113113
await Promise.allSettled(
114114
files.map((file) =>
115-
migrateStylesheet(file, { newPrefix: parsedConfig?.newPrefix ?? undefined }),
115+
migrateStylesheet(file, {
116+
newPrefix: parsedConfig?.newPrefix ?? undefined,
117+
designSystem: parsedConfig?.designSystem,
118+
userConfig: parsedConfig?.userConfig,
119+
}),
116120
),
117121
)
118122

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import fs from 'node:fs/promises'
22
import path from 'node:path'
33
import postcss from 'postcss'
4+
import type { Config } from 'tailwindcss'
5+
import type { DesignSystem } from '../../tailwindcss/src/design-system'
46
import { formatNodes } from './codemods/format-nodes'
57
import { migrateAtApply } from './codemods/migrate-at-apply'
68
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
@@ -9,11 +11,13 @@ import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directive
911

1012
export interface MigrateOptions {
1113
newPrefix?: string
14+
designSystem?: DesignSystem
15+
userConfig?: Config
1216
}
1317

1418
export async function migrateContents(contents: string, options: MigrateOptions, file?: string) {
1519
return postcss()
16-
.use(migrateAtApply())
20+
.use(migrateAtApply(options))
1721
.use(migrateAtLayerUtilities())
1822
.use(migrateMissingLayers())
1923
.use(migrateTailwindDirectives(options))

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,29 @@ export type Migration = (
1515
rawCandidate: string,
1616
) => string
1717

18+
export const DEFAULT_MIGRATIONS: Migration[] = [
19+
prefix,
20+
important,
21+
automaticVarInjection,
22+
bgGradient,
23+
variantOrder,
24+
]
25+
26+
export function migrateCandidate(
27+
designSystem: DesignSystem,
28+
userConfig: Config,
29+
rawCandidate: string,
30+
): string {
31+
for (let migration of DEFAULT_MIGRATIONS) {
32+
rawCandidate = migration(designSystem, userConfig, rawCandidate)
33+
}
34+
return rawCandidate
35+
}
36+
1837
export default async function migrateContents(
1938
designSystem: DesignSystem,
2039
userConfig: Config,
2140
contents: string,
22-
migrations: Migration[] = [prefix, important, bgGradient, automaticVarInjection, variantOrder],
2341
): Promise<string> {
2442
let candidates = await extractRawCandidates(contents)
2543

@@ -28,17 +46,10 @@ export default async function migrateContents(
2846

2947
let output = contents
3048
for (let { rawCandidate, start, end } of candidates) {
31-
let needsMigration = false
32-
for (let migration of migrations) {
33-
let candidate = migration(designSystem, userConfig, rawCandidate)
34-
if (rawCandidate !== candidate) {
35-
rawCandidate = candidate
36-
needsMigration = true
37-
}
38-
}
49+
let migratedCandidate = migrateCandidate(designSystem, userConfig, rawCandidate)
3950

40-
if (needsMigration) {
41-
output = replaceCandidateInContent(output, rawCandidate, start, end)
51+
if (migratedCandidate !== rawCandidate) {
52+
output = replaceCandidateInContent(output, migratedCandidate, start, end)
4253
}
4354
}
4455

0 commit comments

Comments
 (0)