From 3aa0e494bf9b913a8e6f461fafe74e5a5c50e5e9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 31 Jan 2025 15:13:17 +0100 Subject: [PATCH] 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. --- CHANGELOG.md | 1 + integrations/cli/index.test.ts | 62 +++++++++++++++++++++++ packages/tailwindcss/src/index.test.ts | 69 ++++++++++++++++++++++++++ packages/tailwindcss/src/index.ts | 6 +++ 4 files changed, 138 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a95136a0e91..361db062e60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 60fc03e421f6..2e6b9ad03549 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -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; + } + " + `) + }, +) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 8fba63cfccef..9f7962916d90 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -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( diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index df6e09f90649..5947e73b810c 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -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