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.
This commit is contained in:
Robin Malfait 2025-01-31 15:13:17 +01:00 • committed by GitHub
parent 88c890615a
commit 3aa0e494bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 138 additions and 0 deletions

View file

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103))
- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120))
## [4.0.1] - 2025-01-29

View file

@ -1196,3 +1196,65 @@ test(
`)
},
)
test(
'@theme reference should never emit values',
{
fs: {
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'src/index.css': css`
@reference "tailwindcss";
.keep-me {
color: red;
}
`,
},
},
async ({ fs, spawn, expect }) => {
let process = await spawn(
`pnpm tailwindcss --input src/index.css --output dist/out.css --watch`,
)
await process.onStderr((m) => m.includes('Done in'))
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
"
--- ./dist/out.css ---
.keep-me {
color: red;
}
"
`)
await fs.write(
'./src/index.css',
css`
@reference "tailwindcss";
/* Not a reference! */
@theme {
--color-pink: pink;
}
.keep-me {
color: red;
}
`,
)
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
"
--- ./dist/out.css ---
.keep-me {
color: red;
}
"
`)
},
)

View file

@ -1530,6 +1530,75 @@ describe('Parsing themes values from CSS', () => {
`)
})
test('`@keyframes` added in `@theme reference` should not be emitted', async () => {
return expect(
await compileCss(
css`
@theme reference {
--animate-foo: foo 1s infinite;
@keyframes foo {
0%,
100% {
color: red;
}
50% {
color: blue;
}
}
}
@tailwind utilities;
`,
['animate-foo'],
),
).toMatchInlineSnapshot(`
".animate-foo {
animation: var(--animate-foo);
}"
`)
})
test('`@keyframes` added in `@theme reference` should not be emitted, even if another `@theme` block exists', async () => {
return expect(
await compileCss(
css`
@theme reference {
--animate-foo: foo 1s infinite;
@keyframes foo {
0%,
100% {
color: red;
}
50% {
color: blue;
}
}
}
@theme {
--color-pink: pink;
}
@tailwind utilities;
`,
['bg-pink', 'animate-foo'],
),
).toMatchInlineSnapshot(`
":root, :host {
--color-pink: pink;
}
.animate-foo {
animation: var(--animate-foo);
}
.bg-pink {
background-color: var(--color-pink);
}"
`)
})
test('theme values added as reference that override existing theme value suppress the output of the original theme value as a variable', async () => {
expect(
await compileCss(

View file

@ -454,6 +454,12 @@ async function parseCss(
// Collect `@keyframes` rules to re-insert with theme variables later,
// since the `@theme` rule itself will be removed.
if (child.kind === 'at-rule' && child.name === '@keyframes') {
// Do not track/emit `@keyframes`, if they are part of a `@theme reference`.
if (themeOptions & ThemeOptions.REFERENCE) {
replaceWith([])
return WalkAction.Skip
}
theme.addKeyframes(child)
replaceWith([])
return WalkAction.Skip