Skip to content

Commit

Permalink
Add codemod for migrating @variants and @responsive directives (#…
Browse files Browse the repository at this point in the history
…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;
}
```
  • Loading branch information
RobinMalfait committed Oct 22, 2024
1 parent d59f1b3 commit 5bf2efb
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ test(
@tailwind base;
@tailwind components;
@tailwind utilities;
@variants hover, focus {
.foo {
color: red;
}
}
`,
},
},
Expand All @@ -40,6 +46,10 @@ test(
--- ./src/input.css ---
@import 'tailwindcss';
@utility foo {
color: red;
}
"
`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}"
`)
})
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}"
`)
})
Original file line number Diff line number Diff line change
@@ -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
// <div class="focus:foo"></div>
// ```
//
// 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,
}
}
2 changes: 2 additions & 0 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit 5bf2efb

Please sign in to comment.