diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c5023d7611..f4db57d6e436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) - _Upgrade (experimental)_: Automatically discover JavaScript config files ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) - _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643)) -- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650), [#14648](https://github.com/tailwindlabs/tailwindcss/pull/14648)) +- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650), [#14648](https://github.com/tailwindlabs/tailwindcss/pull/14648), [#14666](https://github.com/tailwindlabs/tailwindcss/pull/14666)) - _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603)) - _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635)) - _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644)) diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index e19681b4c91a..9225da5f857e 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -48,6 +48,20 @@ test( borderRadius: { '4xl': '2rem', }, + keyframes: { + 'spin-clockwise': { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, + }, + 'spin-counterclockwise': { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(-360deg)' }, + }, + }, + animation: { + 'spin-clockwise': 'spin-clockwise 1s linear infinite', + 'spin-counterclockwise': 'spin-counterclockwise 1s linear infinite', + }, }, }, plugins: [], @@ -91,9 +105,30 @@ test( --font-size-base--line-height: 2rem; --font-family-sans: Inter, system-ui, sans-serif; - --font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --radius-4xl: 2rem; + + --animate-spin-clockwise: spin-clockwise 1s linear infinite; + --animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite; + + @keyframes spin-clockwise { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + @keyframes spin-counterclockwise { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } + } } " `) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts index 178122f1d05e..158432d6b92f 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts @@ -30,7 +30,6 @@ export function migrateConfig( if (!sheet.file) return let cssConfig = new AtRule() - cssConfig.raws.tailwind_pretty = true if (jsConfigMigration === null) { // Skip if there is already a `@config` directive @@ -85,6 +84,10 @@ export function migrateConfig( return WalkAction.Skip }) + for (let node of cssConfig?.nodes ?? []) { + node.raws.tailwind_pretty = true + } + if (!locationNode) { root.prepend(cssConfig.nodes) } else if (locationNode.name === 'import') { diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index bc80bbdf8245..c422720963f0 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -4,10 +4,12 @@ import type { Config } from 'tailwindcss' import defaultTheme from 'tailwindcss/defaultTheme' import { fileURLToPath } from 'url' import { loadModule } from '../../@tailwindcss-node/src/compile' +import { toCss, type AstNode } from '../../tailwindcss/src/ast' import { keyPathToCssProperty, themeableValues, } from '../../tailwindcss/src/compat/apply-config-to-theme' +import { applyKeyframesToAst } from '../../tailwindcss/src/compat/apply-keyframes-to-ast' import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge' import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config' import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types' @@ -89,7 +91,11 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise< } } - let themeValues = deepMerge({}, [overwriteTheme, extendTheme], mergeThemeExtension) + let themeValues: Record> = deepMerge( + {}, + [overwriteTheme, extendTheme], + mergeThemeExtension, + ) let prevSectionKey = '' @@ -99,6 +105,10 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise< if (typeof value !== 'string' && typeof value !== 'number') { continue } + + if (key[0] === 'keyframes') { + continue + } containsThemeKeys = true let sectionKey = createSectionKey(key) @@ -115,6 +125,11 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise< css += ` --${keyPathToCssProperty(key)}: ${value};\n` } + if ('keyframes' in themeValues) { + containsThemeKeys = true + css += '\n' + keyframesToCss(themeValues.keyframes) + } + if (!containsThemeKeys) { return null } @@ -232,3 +247,9 @@ function onlyUsesAllowedTopLevelKeys(theme: ThemeConfig): boolean { } return true } + +function keyframesToCss(keyframes: Record): string { + let ast: AstNode[] = [] + applyKeyframesToAst(ast, { theme: { keyframes } }) + return toCss(ast).trim() + '\n' +} diff --git a/packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts b/packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts index 2ca12ab50256..0115841fe256 100644 --- a/packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts +++ b/packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts @@ -2,7 +2,7 @@ import { rule, type AstNode } from '../ast' import type { ResolvedConfig } from './config/types' import { objectToAst } from './plugin-api' -export function applyKeyframesToAst(ast: AstNode[], { theme }: ResolvedConfig) { +export function applyKeyframesToAst(ast: AstNode[], { theme }: Pick) { if ('keyframes' in theme) { for (let [name, keyframe] of Object.entries(theme.keyframes)) { ast.push(rule(`@keyframes ${name}`, objectToAst(keyframe as any)))