Skip to content

Commit 782bc26

Browse files
Migrate keyframes from JS to CSS (tailwindlabs#14666)
This PR adds support for rewriting JS theme config `keyframes` to CSS as part of the JS config to CSS migration. Example: ```ts import { type Config } from 'tailwindcss' module.exports = { theme: { extend: { 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', }, }, }, } satisfies Config ``` Will be printed as: ```css @theme { --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); } } } ```
1 parent 468cb5e commit 782bc26

File tree

5 files changed

+64
-5
lines changed

5 files changed

+64
-5
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
1717
- _Upgrade (experimental)_: Automatically discover JavaScript config files ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
1818
- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643))
19-
- _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))
19+
- _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))
2020
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))
2121
- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635))
2222
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))

integrations/upgrade/js-config.test.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ test(
4848
borderRadius: {
4949
'4xl': '2rem',
5050
},
51+
keyframes: {
52+
'spin-clockwise': {
53+
'0%': { transform: 'rotate(0deg)' },
54+
'100%': { transform: 'rotate(360deg)' },
55+
},
56+
'spin-counterclockwise': {
57+
'0%': { transform: 'rotate(0deg)' },
58+
'100%': { transform: 'rotate(-360deg)' },
59+
},
60+
},
61+
animation: {
62+
'spin-clockwise': 'spin-clockwise 1s linear infinite',
63+
'spin-counterclockwise': 'spin-counterclockwise 1s linear infinite',
64+
},
5165
},
5266
},
5367
plugins: [],
@@ -91,9 +105,30 @@ test(
91105
--font-size-base--line-height: 2rem;
92106
93107
--font-family-sans: Inter, system-ui, sans-serif;
94-
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
108+
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif,
109+
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
95110
96111
--radius-4xl: 2rem;
112+
113+
--animate-spin-clockwise: spin-clockwise 1s linear infinite;
114+
--animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite;
115+
116+
@keyframes spin-clockwise {
117+
0% {
118+
transform: rotate(0deg);
119+
}
120+
100% {
121+
transform: rotate(360deg);
122+
}
123+
}
124+
@keyframes spin-counterclockwise {
125+
0% {
126+
transform: rotate(0deg);
127+
}
128+
100% {
129+
transform: rotate(-360deg);
130+
}
131+
}
97132
}
98133
"
99134
`)

packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function migrateConfig(
3030
if (!sheet.file) return
3131

3232
let cssConfig = new AtRule()
33-
cssConfig.raws.tailwind_pretty = true
3433

3534
if (jsConfigMigration === null) {
3635
// Skip if there is already a `@config` directive
@@ -85,6 +84,10 @@ export function migrateConfig(
8584
return WalkAction.Skip
8685
})
8786

87+
for (let node of cssConfig?.nodes ?? []) {
88+
node.raws.tailwind_pretty = true
89+
}
90+
8891
if (!locationNode) {
8992
root.prepend(cssConfig.nodes)
9093
} else if (locationNode.name === 'import') {

packages/@tailwindcss-upgrade/src/migrate-js-config.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import type { Config } from 'tailwindcss'
44
import defaultTheme from 'tailwindcss/defaultTheme'
55
import { fileURLToPath } from 'url'
66
import { loadModule } from '../../@tailwindcss-node/src/compile'
7+
import { toCss, type AstNode } from '../../tailwindcss/src/ast'
78
import {
89
keyPathToCssProperty,
910
themeableValues,
1011
} from '../../tailwindcss/src/compat/apply-config-to-theme'
12+
import { applyKeyframesToAst } from '../../tailwindcss/src/compat/apply-keyframes-to-ast'
1113
import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge'
1214
import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config'
1315
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
@@ -89,7 +91,11 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<
8991
}
9092
}
9193

92-
let themeValues = deepMerge({}, [overwriteTheme, extendTheme], mergeThemeExtension)
94+
let themeValues: Record<string, Record<string, unknown>> = deepMerge(
95+
{},
96+
[overwriteTheme, extendTheme],
97+
mergeThemeExtension,
98+
)
9399

94100
let prevSectionKey = ''
95101

@@ -99,6 +105,10 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<
99105
if (typeof value !== 'string' && typeof value !== 'number') {
100106
continue
101107
}
108+
109+
if (key[0] === 'keyframes') {
110+
continue
111+
}
102112
containsThemeKeys = true
103113

104114
let sectionKey = createSectionKey(key)
@@ -115,6 +125,11 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<
115125
css += ` --${keyPathToCssProperty(key)}: ${value};\n`
116126
}
117127

128+
if ('keyframes' in themeValues) {
129+
containsThemeKeys = true
130+
css += '\n' + keyframesToCss(themeValues.keyframes)
131+
}
132+
118133
if (!containsThemeKeys) {
119134
return null
120135
}
@@ -232,3 +247,9 @@ function onlyUsesAllowedTopLevelKeys(theme: ThemeConfig): boolean {
232247
}
233248
return true
234249
}
250+
251+
function keyframesToCss(keyframes: Record<string, unknown>): string {
252+
let ast: AstNode[] = []
253+
applyKeyframesToAst(ast, { theme: { keyframes } })
254+
return toCss(ast).trim() + '\n'
255+
}

packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { rule, type AstNode } from '../ast'
22
import type { ResolvedConfig } from './config/types'
33
import { objectToAst } from './plugin-api'
44

5-
export function applyKeyframesToAst(ast: AstNode[], { theme }: ResolvedConfig) {
5+
export function applyKeyframesToAst(ast: AstNode[], { theme }: Pick<ResolvedConfig, 'theme'>) {
66
if ('keyframes' in theme) {
77
for (let [name, keyframe] of Object.entries(theme.keyframes)) {
88
ast.push(rule(`@keyframes ${name}`, objectToAst(keyframe as any)))

0 commit comments

Comments
 (0)