From 4a4be27dc101619e8d97440ef6961b01fd815c3e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 17 Oct 2024 11:50:27 +0200 Subject: [PATCH] =?UTF-8?q?Migrate=20`theme(=E2=80=A6)`=20to=20`var(?= =?UTF-8?q?=E2=80=A6)`=20in=20CSS=20(#14695)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is a follow up from https://github.com/tailwindlabs/tailwindcss/pull/14664 migrates all the `theme(…)` calls in your CSS to `var(…)` if we can. In at-rules, like `@media` we can't use `var(…)` so we have to use the modern version of `theme(…)`. In declarations, we can convert to `var(…)` unless there is a modifier used in the `theme(…)` function, then we can only convert to the new `theme(…)` syntax without dot notation. Input: ```css @media theme(spacing.4) { .foo { background-color: theme(colors.red.900); color: theme(colors.red.900 / 75%); /* With spaces around the `/` */ border-color: theme(colors.red.200/75%); /* Without spaces around the `/` */ } } ``` Output: ```css @media theme(--spacing-4) { /* ^^^^^^^^^^^^^^^^^^ Use `theme(…)` since `var(…)` is invalid in this position*/ .foo { background-color: var(--color-red-900); /* Converted to var(…) */ color: theme(--color-red-900 / 75%); /* Convert to modern theme(…) */ border-color: theme(--color-red-200 / 75%); /* Convert to modern theme(…) — pretty printed*/ } } ``` --- CHANGELOG.md | 2 +- .../src/codemods/migrate-theme-to-var.test.ts | 44 +++++++++++++++++++ .../src/codemods/migrate-theme-to-var.ts | 34 ++++++++++++++ packages/@tailwindcss-upgrade/src/migrate.ts | 2 + .../src/template/codemods/theme-to-var.ts | 7 +-- 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 496c8ae32..b9fcc4bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add first draft of new wide-gamut color palette ([#14693](https://github.com/tailwindlabs/tailwindcss/pull/14693)) -- _Upgrade (experimental)_: Migrate `theme(…)` calls in classes to `var(…)` or to the modern `theme(…)` syntax ([#14664](https://github.com/tailwindlabs/tailwindcss/pull/14664)) +- _Upgrade (experimental)_: Migrate `theme(…)` calls to `var(…)` or to the modern `theme(…)` syntax ([#14664](https://github.com/tailwindlabs/tailwindcss/pull/14664), [#14695](https://github.com/tailwindlabs/tailwindcss/pull/14695)) ### Fixed diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts new file mode 100644 index 000000000..ce102e1f6 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts @@ -0,0 +1,44 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import dedent from 'dedent' +import postcss from 'postcss' +import { expect, it } from 'vitest' +import { formatNodes } from './format-nodes' +import { migrateThemeToVar } from './migrate-theme-to-var' + +const css = dedent + +async function migrate(input: string) { + return postcss() + .use( + migrateThemeToVar({ + designSystem: await __unstable__loadDesignSystem(`@import 'tailwindcss';`, { + base: __dirname, + }), + }), + ) + .use(formatNodes()) + .process(input, { from: expect.getState().testPath }) + .then((result) => result.css) +} + +it('should migrate `theme(…)` to `var(…)`', async () => { + expect( + await migrate(css` + @media theme(spacing.4) { + .foo { + background-color: theme(colors.red.900); + color: theme(colors.red.900 / 75%); + border-color: theme(colors.red.200/75%); + } + } + `), + ).toMatchInlineSnapshot(` + "@media theme(--spacing-4) { + .foo { + background-color: var(--color-red-900); + color: theme(--color-red-900 / 75%); + border-color: theme(--color-red-200 / 75%); + } + }" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts new file mode 100644 index 000000000..558e6d0cb --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts @@ -0,0 +1,34 @@ +import { type Plugin } from 'postcss' +import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import { Convert, createConverter } from '../template/codemods/theme-to-var' + +export function migrateThemeToVar({ + designSystem, +}: { + designSystem?: DesignSystem +} = {}): Plugin { + return { + postcssPlugin: '@tailwindcss/upgrade/migrate-theme-to-var', + OnceExit(root) { + if (!designSystem) return + let convert = createConverter(designSystem, { prettyPrint: true }) + + root.walkDecls((decl) => { + let [newValue] = convert(decl.value) + decl.value = newValue + }) + + root.walkAtRules((atRule) => { + if ( + atRule.name === 'media' || + atRule.name === 'custom-media' || + atRule.name === 'container' || + atRule.name === 'supports' + ) { + let [newValue] = convert(atRule.params, Convert.MigrateThemeOnly) + atRule.params = newValue + } + }) + }, + } +} diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 450fc528c..b6524d1b0 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -10,6 +10,7 @@ import { migrateConfig } from './codemods/migrate-config' 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 type { JSConfigMigration } from './migrate-js-config' import { Stylesheet, type StylesheetConnection, type StylesheetId } from './stylesheet' import { resolveCssId } from './utils/resolve' @@ -35,6 +36,7 @@ export async function migrateContents( return postcss() .use(migrateAtApply(options)) + .use(migrateThemeToVar(options)) .use(migrateMediaScreen(options)) .use(migrateAtLayerUtilities(stylesheet)) .use(migrateMissingLayers()) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts index 4f354394e..4934108e8 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts @@ -80,7 +80,7 @@ export function themeToVar( return rawCandidate } -export function createConverter(designSystem: DesignSystem) { +export function createConverter(designSystem: DesignSystem, { prettyPrint = false } = {}) { function convert(input: string, options = Convert.All): [string, CandidateModifier | null] { let ast = ValueParser.parse(input) @@ -110,7 +110,7 @@ export function createConverter(designSystem: DesignSystem) { } // If we see a `/`, we have a modifier - else if (child.kind === 'separator' && child.value === '/') { + else if (child.kind === 'separator' && child.value.trim() === '/') { themeModifierCount += 1 return ValueParser.ValueWalkAction.Stop } @@ -211,7 +211,8 @@ export function createConverter(designSystem: DesignSystem) { let variable = pathToVariableName(path) if (!variable) return null - let modifier = parts.length > 0 ? `/${parts.join('/')}` : '' + let modifier = + parts.length > 0 ? (prettyPrint ? ` / ${parts.join(' / ')}` : `/${parts.join('/')}`) : '' return fallback ? `theme(${variable}${modifier}, ${fallback})` : `theme(${variable}${modifier})` }