Skip to content

Commit

Permalink
Migrate at-apply utilites with template migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
philipp-spiess committed Oct 2, 2024
1 parent dae178e commit f306e98
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for `blocklist` in config files ([#14556](https://github.com/tailwindlabs/tailwindcss/pull/14556))
- Add `color-scheme` utilities ([#14567](https://github.com/tailwindlabs/tailwindcss/pull/14567))
- _Experimental_: Migrate `@import "tailwindcss/tailwind.css"` to `@import "tailwindcss"` ([#14514](https://github.com/tailwindlabs/tailwindcss/pull/14514))
- _Experimental_: Migrate `@apply` utilities with the template codemods ([#14574](https://github.com/tailwindlabs/tailwindcss/pull/14574))
- _Experimental_: Add template codemods for migrating `bg-gradient-*` utilities to `bg-linear-*` ([#14537](https://github.com/tailwindlabs/tailwindcss/pull/14537]))
- _Experimental_: Add template codemods for migrating prefixes ([#14557](https://github.com/tailwindlabs/tailwindcss/pull/14557]))
- _Experimental_: Add template codemods for removal of automatic `var(…)` injection ([#14526](https://github.com/tailwindlabs/tailwindcss/pull/14526))
Expand Down
14 changes: 13 additions & 1 deletion integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ test(
@tailwind base;
@tailwind components;
@tailwind utilities;
.btn {
@apply !tw__rounded-md tw__px-2 tw__py-1 tw__bg-blue-500 tw__text-white;
}
`,
},
},
Expand All @@ -83,7 +87,15 @@ test(
`,
)

await fs.expectFileToContain('src/input.css', css`@import 'tailwindcss' prefix(tw);`)
await fs.expectFileToContain('src/input.css', css` @import 'tailwindcss' prefix(tw); `)
await fs.expectFileToContain(
'src/input.css',
css`
.btn {
@apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white;
}
`,
)
},
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import postcss from 'postcss'
import { expect, it } from 'vitest'
import { migrateAtApply } from './migrate-at-apply'

const css = dedent

function migrate(input: string) {
function migrateWithoutConfig(input: string) {
return postcss()
.use(migrateAtApply())
.process(input, { from: expect.getState().testPath })
Expand All @@ -14,7 +15,7 @@ function migrate(input: string) {

it('should not migrate `@apply`, when there are no issues', async () => {
expect(
await migrate(css`
await migrateWithoutConfig(css`
.foo {
@apply flex flex-col items-center;
}
Expand All @@ -28,7 +29,7 @@ it('should not migrate `@apply`, when there are no issues', async () => {

it('should append `!` to each utility, when using `!important`', async () => {
expect(
await migrate(css`
await migrateWithoutConfig(css`
.foo {
@apply flex flex-col !important;
}
Expand All @@ -43,7 +44,7 @@ it('should append `!` to each utility, when using `!important`', async () => {
// TODO: Handle SCSS syntax
it.skip('should append `!` to each utility, when using `#{!important}`', async () => {
expect(
await migrate(css`
await migrateWithoutConfig(css`
.foo {
@apply flex flex-col #{!important};
}
Expand All @@ -57,7 +58,7 @@ it.skip('should append `!` to each utility, when using `#{!important}`', async (

it('should move the legacy `!` prefix, to the new `!` postfix notation', async () => {
expect(
await migrate(css`
await migrateWithoutConfig(css`
.foo {
@apply !flex flex-col! hover:!items-start items-center;
}
Expand All @@ -68,3 +69,36 @@ it('should move the legacy `!` prefix, to the new `!` postfix notation', async (
}"
`)
})

it('should apply all candidate migration when migrating with a config', async () => {
async function migrateWithConfig(input: string) {
return postcss()
.use(
migrateAtApply({
designSystem: await __unstable__loadDesignSystem(
css`
@import 'tailwindcss' prefix(tw);
`,
{ base: __dirname },
),
userConfig: {
prefix: 'tw_',
},
}),
)
.process(input, { from: expect.getState().testPath })
.then((result) => result.css)
}

expect(
await migrateWithConfig(css`
.foo {
@apply !tw_flex [color:--my-color];
}
`),
).toMatchInlineSnapshot(`
".foo {
@apply tw:flex! tw:[color:var(--my-color)];
}"
`)
})
14 changes: 13 additions & 1 deletion packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { AtRule, Plugin } from 'postcss'
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
import { segment } from '../../../tailwindcss/src/utils/segment'
import { migrateCandidate } from '../template/migrate'

export function migrateAtApply(): Plugin {
export function migrateAtApply({
designSystem,
userConfig,
}: { designSystem?: DesignSystem; userConfig?: Config } = {}): Plugin {
function migrate(atRule: AtRule) {
let utilities = atRule.params.split(/(\s+)/)
let important =
Expand Down Expand Up @@ -30,6 +36,12 @@ export function migrateAtApply(): Plugin {
return [...variants, utility].join(':')
})

// If we have a valid designSystem and config setup, we can run all
// candidate migrations on each utility
if (designSystem && userConfig) {
params = params.map((param) => migrateCandidate(designSystem, userConfig, param))
}

atRule.params = params.join('').trim()
}

Expand Down
6 changes: 5 additions & 1 deletion packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ async function run() {
// Migrate each file
await Promise.allSettled(
files.map((file) =>
migrateStylesheet(file, { newPrefix: parsedConfig?.newPrefix ?? undefined }),
migrateStylesheet(file, {
newPrefix: parsedConfig?.newPrefix ?? undefined,
designSystem: parsedConfig?.designSystem,
userConfig: parsedConfig?.userConfig,
}),
),
)

Expand Down
6 changes: 5 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import postcss from 'postcss'
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { formatNodes } from './codemods/format-nodes'
import { migrateAtApply } from './codemods/migrate-at-apply'
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
Expand All @@ -9,11 +11,13 @@ import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directive

export interface MigrateOptions {
newPrefix?: string
designSystem?: DesignSystem
userConfig?: Config
}

export async function migrateContents(contents: string, options: MigrateOptions, file?: string) {
return postcss()
.use(migrateAtApply())
.use(migrateAtApply(options))
.use(migrateAtLayerUtilities())
.use(migrateMissingLayers())
.use(migrateTailwindDirectives(options))
Expand Down
32 changes: 21 additions & 11 deletions packages/@tailwindcss-upgrade/src/template/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,28 @@ export type Migration = (
rawCandidate: string,
) => string

export const DEFAULT_MIGRATIONS: Migration[] = [
prefix,
important,
automaticVarInjection,
bgGradient,
]

export function migrateCandidate(
designSystem: DesignSystem,
userConfig: Config,
rawCandidate: string,
): string {
for (let migration of DEFAULT_MIGRATIONS) {
rawCandidate = migration(designSystem, userConfig, rawCandidate)
}
return rawCandidate
}

export default async function migrateContents(
designSystem: DesignSystem,
userConfig: Config,
contents: string,
migrations: Migration[] = [prefix, important, automaticVarInjection, bgGradient],
): Promise<string> {
let candidates = await extractRawCandidates(contents)

Expand All @@ -27,17 +44,10 @@ export default async function migrateContents(

let output = contents
for (let { rawCandidate, start, end } of candidates) {
let needsMigration = false
for (let migration of migrations) {
let candidate = migration(designSystem, userConfig, rawCandidate)
if (rawCandidate !== candidate) {
rawCandidate = candidate
needsMigration = true
}
}
let migratedCandidate = migrateCandidate(designSystem, userConfig, rawCandidate)

if (needsMigration) {
output = replaceCandidateInContent(output, rawCandidate, start, end)
if (migratedCandidate !== rawCandidate) {
output = replaceCandidateInContent(output, migratedCandidate, start, end)
}
}

Expand Down

0 comments on commit f306e98

Please sign in to comment.