Skip to content

Commit 3aa0e49

Browse files
authored
Do not emit @keyframes in @theme reference (#16120)
This PR fixes na issue where `@keyframes` were emitted if they wre in a `@theme reference` and anothe `@theme {}` (that is not a reference) was present. E.g.: ```css @reference "tailwindcss"; @theme { /* ... */ } ``` Produces: ```css :root, :host { } @Keyframes spin { to { transform: rotate(360deg); } } @Keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } } @Keyframes pulse { 50% { opacity: 0.5; } } @Keyframes bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: none; animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } } ``` With this PR, the produced CSS looks like this instead: ```css :root, :host { } ``` Note: the empty `:root, :host` will be solved in a subsequent PR. ### Test plan Added some unit tests, and a dedicated integration test.
1 parent 88c8906 commit 3aa0e49

File tree

4 files changed

+138
-0
lines changed

4 files changed

+138
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
1616
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
1717
- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
18+
- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120))
1819

1920
## [4.0.1] - 2025-01-29
2021

integrations/cli/index.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,3 +1196,65 @@ test(
11961196
`)
11971197
},
11981198
)
1199+
1200+
test(
1201+
'@theme reference should never emit values',
1202+
{
1203+
fs: {
1204+
'package.json': json`
1205+
{
1206+
"dependencies": {
1207+
"tailwindcss": "workspace:^",
1208+
"@tailwindcss/cli": "workspace:^"
1209+
}
1210+
}
1211+
`,
1212+
'src/index.css': css`
1213+
@reference "tailwindcss";
1214+
1215+
.keep-me {
1216+
color: red;
1217+
}
1218+
`,
1219+
},
1220+
},
1221+
async ({ fs, spawn, expect }) => {
1222+
let process = await spawn(
1223+
`pnpm tailwindcss --input src/index.css --output dist/out.css --watch`,
1224+
)
1225+
await process.onStderr((m) => m.includes('Done in'))
1226+
1227+
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
1228+
"
1229+
--- ./dist/out.css ---
1230+
.keep-me {
1231+
color: red;
1232+
}
1233+
"
1234+
`)
1235+
1236+
await fs.write(
1237+
'./src/index.css',
1238+
css`
1239+
@reference "tailwindcss";
1240+
1241+
/* Not a reference! */
1242+
@theme {
1243+
--color-pink: pink;
1244+
}
1245+
1246+
.keep-me {
1247+
color: red;
1248+
}
1249+
`,
1250+
)
1251+
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
1252+
"
1253+
--- ./dist/out.css ---
1254+
.keep-me {
1255+
color: red;
1256+
}
1257+
"
1258+
`)
1259+
},
1260+
)

packages/tailwindcss/src/index.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,6 +1530,75 @@ describe('Parsing themes values from CSS', () => {
15301530
`)
15311531
})
15321532

1533+
test('`@keyframes` added in `@theme reference` should not be emitted', async () => {
1534+
return expect(
1535+
await compileCss(
1536+
css`
1537+
@theme reference {
1538+
--animate-foo: foo 1s infinite;
1539+
1540+
@keyframes foo {
1541+
0%,
1542+
100% {
1543+
color: red;
1544+
}
1545+
50% {
1546+
color: blue;
1547+
}
1548+
}
1549+
}
1550+
@tailwind utilities;
1551+
`,
1552+
['animate-foo'],
1553+
),
1554+
).toMatchInlineSnapshot(`
1555+
".animate-foo {
1556+
animation: var(--animate-foo);
1557+
}"
1558+
`)
1559+
})
1560+
1561+
test('`@keyframes` added in `@theme reference` should not be emitted, even if another `@theme` block exists', async () => {
1562+
return expect(
1563+
await compileCss(
1564+
css`
1565+
@theme reference {
1566+
--animate-foo: foo 1s infinite;
1567+
1568+
@keyframes foo {
1569+
0%,
1570+
100% {
1571+
color: red;
1572+
}
1573+
50% {
1574+
color: blue;
1575+
}
1576+
}
1577+
}
1578+
1579+
@theme {
1580+
--color-pink: pink;
1581+
}
1582+
1583+
@tailwind utilities;
1584+
`,
1585+
['bg-pink', 'animate-foo'],
1586+
),
1587+
).toMatchInlineSnapshot(`
1588+
":root, :host {
1589+
--color-pink: pink;
1590+
}
1591+
1592+
.animate-foo {
1593+
animation: var(--animate-foo);
1594+
}
1595+
1596+
.bg-pink {
1597+
background-color: var(--color-pink);
1598+
}"
1599+
`)
1600+
})
1601+
15331602
test('theme values added as reference that override existing theme value suppress the output of the original theme value as a variable', async () => {
15341603
expect(
15351604
await compileCss(

packages/tailwindcss/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,12 @@ async function parseCss(
454454
// Collect `@keyframes` rules to re-insert with theme variables later,
455455
// since the `@theme` rule itself will be removed.
456456
if (child.kind === 'at-rule' && child.name === '@keyframes') {
457+
// Do not track/emit `@keyframes`, if they are part of a `@theme reference`.
458+
if (themeOptions & ThemeOptions.REFERENCE) {
459+
replaceWith([])
460+
return WalkAction.Skip
461+
}
462+
457463
theme.addKeyframes(child)
458464
replaceWith([])
459465
return WalkAction.Skip

0 commit comments

Comments
 (0)