From 5bf2efb52170f7123ae85b9b114414833d82d0eb Mon Sep 17 00:00:00 2001 From: RobinMalfait <1834413+RobinMalfait@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:09:53 +0000 Subject: [PATCH] Add codemod for migrating `@variants` and `@responsive` directives (#14748) This PR migrates the `@variants` and `@responsive` directives. In Tailwind CSS v2, these were used to generate certain variants of responsive variants for the give classes. In Tailwind CSS v3, these still worked but were implemented as a no-op such that these directives don't end up in your final CSS. In Tailwind CSS v4, these don't exist at all anymore, so we can safely get rid of them by replacing them with their contents. Input: ```css @variants hover, focus { .foo { color: red; } } @responsive { .bar { color: blue; } } ``` Output: ```css .foo { color: red; } .bar { color: blue; } ``` --- CHANGELOG.md | 1 + integrations/upgrade/index.test.ts | 10 ++++++ .../migrate-tailwind-directives.test.ts | 16 +++++++++ .../codemods/migrate-tailwind-directives.ts | 12 +++++++ .../migrate-variants-directive.test.ts | 33 +++++++++++++++++ .../codemods/migrate-variants-directive.ts | 35 +++++++++++++++++++ packages/@tailwindcss-upgrade/src/migrate.ts | 2 ++ 7 files changed, 109 insertions(+) create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 437259146..1d0b4831f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Migrate `plugins` with options to CSS ([#14700](https://github.com/tailwindlabs/tailwindcss/pull/14700)) - _Upgrade (experimental)_: Allow JS configuration files with `corePlugins` options to be migrated to CSS ([#14742](https://github.com/tailwindlabs/tailwindcss/pull/14742)) +- _Upgrade (experimental)_: Migrate `@variants` and `@responsive` directives ([#14748](https://github.com/tailwindlabs/tailwindcss/pull/14748)) ### Fixed diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 80005c088..ceec310ae 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -26,6 +26,12 @@ test( @tailwind base; @tailwind components; @tailwind utilities; + + @variants hover, focus { + .foo { + color: red; + } + } `, }, }, @@ -40,6 +46,10 @@ test( --- ./src/input.css --- @import 'tailwindcss'; + + @utility foo { + color: red; + } " `) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts index ba2d9ea9d..7c74b1ed6 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts @@ -400,3 +400,19 @@ it('should drop `@tailwind variants;`', async () => { `), ).toEqual('') }) + +it('should replace `@responsive` with its children', async () => { + expect( + await migrate(css` + @responsive { + .foo { + color: red; + } + } + `), + ).toMatchInlineSnapshot(` + ".foo { + color: red; + }" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts index 0f7143017..d3d74a65e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts @@ -54,6 +54,18 @@ export function migrateTailwindDirectives(options: { newPrefix: string | null }) ) { node.remove() } + + // Replace Tailwind CSS v2 directives that still worked in v3. + else if (node.name === 'responsive') { + if (node.nodes) { + for (let child of node.nodes) { + child.raws.tailwind_pretty = true + } + node.replaceWith(node.nodes) + } else { + node.remove() + } + } }) // Insert default import if all directives are present diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts new file mode 100644 index 000000000..e18a161e6 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts @@ -0,0 +1,33 @@ +import dedent from 'dedent' +import postcss from 'postcss' +import { expect, it } from 'vitest' +import { formatNodes } from './format-nodes' +import { migrateVariantsDirective } from './migrate-variants-directive' + +const css = dedent + +function migrate(input: string) { + return postcss() + .use(migrateVariantsDirective()) + .use(formatNodes()) + .process(input, { from: expect.getState().testPath }) + .then((result) => result.css) +} + +it('should replace `@variants` with `@layer utilities`', async () => { + expect( + await migrate(css` + @variants hover, focus { + .foo { + color: red; + } + } + `), + ).toMatchInlineSnapshot(` + "@layer utilities { + .foo { + color: red; + } + }" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts new file mode 100644 index 000000000..1592c0059 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts @@ -0,0 +1,35 @@ +import { type Plugin, type Root } from 'postcss' + +export function migrateVariantsDirective(): Plugin { + function migrate(root: Root) { + root.walkAtRules('variants', (node) => { + // Migrate `@variants` to `@utility` because `@variants` make the classes + // an actual utility. + // ```css + // @variants hover { + // .foo {} + // } + // ``` + // + // Means that you can do this in your HTML: + // ```html + //
+ // ``` + // + // Notice the `focus:`, even though we _only_ configured the `hover` + // variant. + // + // This means that we can convert it to an `@layer utilities` rule. Later, + // this will get converted to an `@utility` rule. + if (node.name === 'variants') { + node.name = 'layer' + node.params = 'utilities' + } + }) + } + + return { + postcssPlugin: '@tailwindcss/upgrade/migrate-variants-directive', + OnceExit: migrate, + } +} diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 1b72afc6f..4220904c9 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -11,6 +11,7 @@ import { migrateMediaScreen } from './codemods/migrate-media-screen' import { migrateMissingLayers } from './codemods/migrate-missing-layers' import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives' import { migrateThemeToVar } from './codemods/migrate-theme-to-var' +import { migrateVariantsDirective } from './codemods/migrate-variants-directive' import type { JSConfigMigration } from './migrate-js-config' import { Stylesheet, type StylesheetConnection, type StylesheetId } from './stylesheet' import { resolveCssId } from './utils/resolve' @@ -38,6 +39,7 @@ export async function migrateContents( .use(migrateAtApply(options)) .use(migrateThemeToVar(options)) .use(migrateMediaScreen(options)) + .use(migrateVariantsDirective()) .use(migrateAtLayerUtilities(stylesheet)) .use(migrateMissingLayers()) .use(migrateTailwindDirectives(options))