Migrate keyframes from JS to CSS (#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);
}
}
}
```
This commit is contained in:
parent
468cb5e99e
commit
782bc26135
5 changed files with 64 additions and 5 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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<string, Record<string, unknown>> = 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, unknown>): string {
|
||||
let ast: AstNode[] = []
|
||||
applyKeyframesToAst(ast, { theme: { keyframes } })
|
||||
return toCss(ast).trim() + '\n'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ResolvedConfig, 'theme'>) {
|
||||
if ('keyframes' in theme) {
|
||||
for (let [name, keyframe] of Object.entries(theme.keyframes)) {
|
||||
ast.push(rule(`@keyframes ${name}`, objectToAst(keyframe as any)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue