diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5742b7eb40..737c3034bcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Vite: Don't crash when importing a virtual module in JavaScript that ends in `.css` ([#16780](https://github.com/tailwindlabs/tailwindcss/pull/16780)) +- Ensure `@reference "…"` does not emit CSS variables ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774)) +- Fix an issue where `@reference "…"` would sometimes omit keyframe animations ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774)) ## [4.0.8] - 2025-02-21 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 2eee0413dc4f..bc8fb433ddf2 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -643,7 +643,7 @@ test( } `, 'index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* (1) */ /* - Only './src' should be auto-scanned, not the current working directory */ @@ -774,7 +774,7 @@ test( } `, 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* Run auto-content detection in ../../project-b */ @import 'tailwindcss/utilities' source('../../project-b'); @@ -1132,7 +1132,7 @@ test( } `, 'index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* (1) */ /* - Only './src' should be auto-scanned, not the current working directory */ diff --git a/integrations/cli/standalone.test.ts b/integrations/cli/standalone.test.ts index a40d69fc95b7..c190134d307b 100644 --- a/integrations/cli/standalone.test.ts +++ b/integrations/cli/standalone.test.ts @@ -35,7 +35,7 @@ test(
`, 'src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @plugin '@tailwindcss/forms'; diff --git a/integrations/postcss/index.test.ts b/integrations/postcss/index.test.ts index 1c8d1c301716..524752b217f7 100644 --- a/integrations/postcss/index.test.ts +++ b/integrations/postcss/index.test.ts @@ -659,7 +659,7 @@ test( } `, 'index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* (1) */ /* - Only './src' should be auto-scanned, not the current working directory */ @@ -799,7 +799,7 @@ test( } `, 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* Run auto-content detection in ../../project-b */ @import 'tailwindcss/utilities' source('../../project-b'); @@ -1163,7 +1163,7 @@ test( } `, 'index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; /* (1) */ /* - Only './src' should be auto-scanned, not the current working directory */ diff --git a/integrations/postcss/multi-root.test.ts b/integrations/postcss/multi-root.test.ts index 327becdfef99..c2c52c7e4dbd 100644 --- a/integrations/postcss/multi-root.test.ts +++ b/integrations/postcss/multi-root.test.ts @@ -25,7 +25,7 @@ test(
`, 'src/shared.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, 'src/root1.css': css` diff --git a/integrations/postcss/next.test.ts b/integrations/postcss/next.test.ts index 48c6a18dfcc0..a6fb26f8e6ad 100644 --- a/integrations/postcss/next.test.ts +++ b/integrations/postcss/next.test.ts @@ -19,21 +19,13 @@ test( } `, 'postcss.config.mjs': js` - /** @type {import('postcss-load-config').Config} */ - const config = { + export default { plugins: { '@tailwindcss/postcss': {}, }, } - - export default config - `, - 'next.config.mjs': js` - /** @type {import('next').NextConfig} */ - const nextConfig = {} - - export default nextConfig `, + 'next.config.mjs': js`export default {}`, 'app/layout.js': js` import './globals.css' @@ -46,12 +38,21 @@ test( } `, 'app/page.js': js` + import styles from './page.module.css' export default function Page() { - return

Hello, Next.js!

+ return ( +

Hello, Next.js!

+ ) + } + `, + 'app/page.module.css': css` + @reference './globals.css'; + .heading { + @apply text-red-500 animate-ping; } `, 'app/globals.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, }, @@ -60,14 +61,26 @@ test( await exec('pnpm next build') let files = await fs.glob('.next/static/css/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] + expect(files).toHaveLength(2) - await fs.expectFileToContain(filename, [ + let globalCss: string | null = null + let moduleCss: string | null = null + for (let [filename, content] of files) { + if (content.includes('@keyframes page_ping')) moduleCss = filename + else globalCss = filename + } + + await fs.expectFileToContain(globalCss!, [ candidate`underline`, candidate`font-bold`, candidate`text-3xl`, ]) + + await fs.expectFileToContain(moduleCss!, [ + 'color:var(--color-red-500,oklch(.637 .237 25.331)', + 'animation:var(--animate-ping,ping 1s cubic-bezier(0,0,.2,1) infinite)', + /@keyframes page_ping.*{75%,to{transform:scale\(2\);opacity:0}/, + ]) }, ) @@ -90,21 +103,13 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => { } `, 'postcss.config.mjs': js` - /** @type {import('postcss-load-config').Config} */ - const config = { + export default { plugins: { '@tailwindcss/postcss': {}, }, } - - export default config - `, - 'next.config.mjs': js` - /** @type {import('next').NextConfig} */ - const nextConfig = {} - - export default nextConfig `, + 'next.config.mjs': js`export default {}`, 'app/layout.js': js` import './globals.css' @@ -117,12 +122,19 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => { } `, 'app/page.js': js` + import styles from './page.module.css' export default function Page() { - return

Hello, Next.js!

+ return

Hello, Next.js!

+ } + `, + 'app/page.module.css': css` + @reference './globals.css'; + .heading { + @apply text-red-500 animate-ping content-['module']; } `, 'app/globals.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, }, @@ -142,13 +154,16 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => { await retryAssertion(async () => { let css = await fetchStyles(url) expect(css).toContain(candidate`underline`) + expect(css).toContain('content: var(--tw-content)') + expect(css).toContain('@keyframes') }) await fs.write( 'app/page.js', js` + import styles from './page.module.css' export default function Page() { - return

Hello, Next.js!

+ return

Hello, Next.js!

} `, ) @@ -157,7 +172,9 @@ describe.each(['turbo', 'webpack'])('%s', (bundler) => { await retryAssertion(async () => { let css = await fetchStyles(url) expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`text-red-500`) + expect(css).toContain(candidate`bg-red-500`) + expect(css).toContain('content: var(--tw-content)') + expect(css).toContain('@keyframes') }) }, ) @@ -181,21 +198,13 @@ test( } `, 'postcss.config.mjs': js` - /** @type {import('postcss-load-config').Config} */ - const config = { + export default { plugins: { '@tailwindcss/postcss': {}, }, } - - export default config - `, - 'next.config.mjs': js` - /** @type {import('next').NextConfig} */ - const nextConfig = {} - - export default nextConfig `, + 'next.config.mjs': js`export default {}`, 'app/a/[slug]/page.js': js` export default function Page() { return

Hello, Next.js!

diff --git a/integrations/postcss/plugins.test.ts b/integrations/postcss/plugins.test.ts index 3081b7bfee9f..c36e8fd14edd 100644 --- a/integrations/postcss/plugins.test.ts +++ b/integrations/postcss/plugins.test.ts @@ -26,7 +26,7 @@ test(
`, 'src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @plugin '@headlessui/tailwindcss'; `, @@ -65,7 +65,7 @@ test(
`, 'src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @plugin '@headlessui/tailwindcss'; `, diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index afc58c558bf5..59b304cf4079 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -62,7 +62,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { } `, 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @config '../tailwind.config.js'; @source '../../project-b/src/**/*.html'; @@ -147,7 +147,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { } `, 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @config '../tailwind.config.js'; @source '../../project-b/src/**/*.html'; @@ -291,7 +291,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { } `, 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @import './custom-theme.css'; @config '../tailwind.config.js'; diff --git a/integrations/vite/multi-root.test.ts b/integrations/vite/multi-root.test.ts index b407f7da0bc4..d1a1337a4f5d 100644 --- a/integrations/vite/multi-root.test.ts +++ b/integrations/vite/multi-root.test.ts @@ -43,7 +43,7 @@ test( `, 'src/shared.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, 'src/root1.css': css` @@ -119,7 +119,7 @@ test( `, 'src/shared.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, 'src/root1.css': css` diff --git a/integrations/vite/other-transforms.test.ts b/integrations/vite/other-transforms.test.ts index 6aef096a7853..b1cb5819c1a9 100644 --- a/integrations/vite/other-transforms.test.ts +++ b/integrations/vite/other-transforms.test.ts @@ -47,7 +47,7 @@ function createSetup(transformer: 'postcss' | 'lightningcss') { `, 'src/index.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; .foo { @@ -111,7 +111,7 @@ describe.each(['postcss', 'lightningcss'] as const)('%s', (transformer) => { await fs.write( 'src/index.css', css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; .foo { @@ -155,7 +155,7 @@ describe.each(['postcss', 'lightningcss'] as const)('%s', (transformer) => { await fs.write( 'src/index.css', css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; .foo { diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts index 040f843912fd..6072c37da51b 100644 --- a/integrations/vite/resolvers.test.ts +++ b/integrations/vite/resolvers.test.ts @@ -49,7 +49,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { @plugin '#js-alias'; `, 'src/alias.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, 'src/plugin.js': js` @@ -117,7 +117,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { @plugin '#js-alias'; `, 'src/alias.css': css` - @import 'tailwindcss/theme' theme(reference); + @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; `, 'src/plugin.js': js` diff --git a/integrations/vite/svelte.test.ts b/integrations/vite/svelte.test.ts index 9055406d60ba..053819c73e20 100644 --- a/integrations/vite/svelte.test.ts +++ b/integrations/vite/svelte.test.ts @@ -100,8 +100,8 @@ test( await fs.expectFileToContain(files[0][0], [ candidate`underline`, - '.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}', - /\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', + /\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, /@keyframes globalKeyframes\{/, /@keyframes svelte-.*-localKeyframes\{/, ]) @@ -213,10 +213,10 @@ test( let [, css] = files[0] expect(css).toContain(candidate`underline`) expect(css).toContain( - '.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}', + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', ) expect(css).toMatch( - /\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, ) expect(css).toMatch(/@keyframes globalKeyframes\{/) expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/) @@ -238,14 +238,16 @@ test( let [, css] = files[0] expect(css).toContain(candidate`font-bold`) expect(css).toContain( - '.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}', + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', ) expect(css).toMatch( - /\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, ) expect(css).toMatch(/@keyframes globalKeyframes\{/) expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/) - expect(css).toMatch(/\.bar.svelte-.*\{color:var\(--color-pink-500\)\}/) + expect(css).toMatch( + /\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(\.656 \.241 354\.308\)\)\}/, + ) }) }, ) diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts index 3a7e1b8b896c..fa9d5abbb3d4 100644 --- a/packages/@tailwindcss-postcss/src/index.test.ts +++ b/packages/@tailwindcss-postcss/src/index.test.ts @@ -90,7 +90,7 @@ test('@apply can be used without emitting the theme in the CSS file', async () = let result = await processor.process( css` - @import 'tailwindcss/theme.css' theme(reference); + @reference 'tailwindcss/theme.css'; .foo { @apply text-red-500; } @@ -100,7 +100,7 @@ test('@apply can be used without emitting the theme in the CSS file', async () = expect(result.css.trim()).toMatchInlineSnapshot(` ".foo { - color: var(--color-red-500); + color: var(--color-red-500, oklch(.637 .237 25.331)); }" `) }) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 5a23f4e48fb2..bd99347974f1 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -256,11 +256,7 @@ export function walkDepth( // Optimize the AST for printing where all the special nodes that require custom // handling are handled such that the printing is a 1-to-1 transformation. -export function optimizeAst( - ast: AstNode[], - designSystem: DesignSystem, - firstThemeRule: StyleRule | null, -) { +export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { let atRoots: AstNode[] = [] let seenAtProperties = new Set() let cssThemeVariables = new DefaultMap< @@ -396,27 +392,8 @@ export function optimizeAst( // Context else if (node.kind === 'context') { + // Remove reference imports from printing if (node.context.reference) { - if (firstThemeRule) { - let path = findNode(node.nodes, (node) => node === firstThemeRule) - if (path) { - let newPathToFirstThemeRule = path - .filter((node) => node.kind === 'at-rule') - .map((node) => ({ ...node, nodes: [] })) - .reverse() as AtRule[] - - let child = firstThemeRule as AstNode - for (let node of newPathToFirstThemeRule) { - node.nodes = [child] - child = node - } - - let newParent: AstNode[] = [] - transform(child, newParent, { ...context, ...node.context, reference: false }, depth) - parent.push(...newParent) - } - } - return } else { for (let child of node.nodes) { diff --git a/packages/tailwindcss/src/at-import.test.ts b/packages/tailwindcss/src/at-import.test.ts index 81ab16eda1fa..32a203d5166d 100644 --- a/packages/tailwindcss/src/at-import.test.ts +++ b/packages/tailwindcss/src/at-import.test.ts @@ -421,7 +421,7 @@ test('supports theme(reference) imports', async () => { ), ).resolves.toMatchInlineSnapshot(` ".text-red-500 { - color: var(--color-red-500); + color: var(--color-red-500, red); } " `) @@ -580,12 +580,8 @@ test('resolves @reference as `@import "…" reference`', async () => { { loadStylesheet, candidates: ['text-red-500'] }, ), ).resolves.toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .text-red-500 { - color: var(--color-red-500); + ".text-red-500 { + color: var(--color-red-500, red); } " `) diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 55b91ec394e0..f856a557d8b6 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1389,7 +1389,7 @@ test('blocklisted candidates are not generated', async () => { } .md\\:bg-white { @media (width >= 48rem) { - background-color: var(--color-white); + background-color: var(--color-white, #fff); } } " diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index 5d3abc9ffa9e..c765b1c3fbf0 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -94,7 +94,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { }, }) - astNodes = optimizeAst(astNodes, designSystem, null) + astNodes = optimizeAst(astNodes, designSystem) if (astNodes.length === 0 || wasInvalid) { result.push(null) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index d5b71c6b6a5c..b6f1e4781637 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -325,12 +325,6 @@ describe('@apply', () => { } } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - @property --tw-translate-x { syntax: "*"; inherits: false; @@ -347,6 +341,12 @@ describe('@apply', () => { syntax: "*"; inherits: false; initial-value: 0; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } }" `) }) @@ -1822,7 +1822,7 @@ describe('Parsing theme values from CSS', () => { `) }) - test('theme values added as reference are not included in the output as variables', async () => { + test('theme values added as reference are not included in the output as variables but emit fallback values', async () => { expect( await compileCss( css` @@ -1842,7 +1842,7 @@ describe('Parsing theme values from CSS', () => { } .bg-potato { - background-color: var(--color-potato); + background-color: var(--color-potato, #ac855b); } .bg-tomato { @@ -1874,7 +1874,17 @@ describe('Parsing theme values from CSS', () => { ), ).toMatchInlineSnapshot(` ".animate-foo { - animation: var(--animate-foo); + animation: var(--animate-foo, foo 1s infinite); + } + + @keyframes foo { + 0%, 100% { + color: red; + } + + 50% { + color: #00f; + } }" `) }) @@ -1911,11 +1921,21 @@ describe('Parsing theme values from CSS', () => { } .animate-foo { - animation: var(--animate-foo); + animation: var(--animate-foo, foo 1s infinite); } .bg-pink { background-color: var(--color-pink); + } + + @keyframes foo { + 0%, 100% { + color: red; + } + + 50% { + color: #00f; + } }" `) }) @@ -1936,7 +1956,7 @@ describe('Parsing theme values from CSS', () => { ), ).toMatchInlineSnapshot(` ".bg-potato { - background-color: var(--color-potato); + background-color: var(--color-potato, #c794aa); }" `) }) @@ -1991,11 +2011,11 @@ describe('Parsing theme values from CSS', () => { } .bg-avocado { - background-color: var(--color-avocado); + background-color: var(--color-avocado, #c0cc6d); } .bg-potato { - background-color: var(--color-potato); + background-color: var(--color-potato, #ac855b); } .bg-tomato { @@ -2323,7 +2343,7 @@ describe('Parsing theme values from CSS', () => { ), ).toMatchInlineSnapshot(` ".bg-potato { - background-color: var(--color-potato); + background-color: var(--color-potato, #efb46b); }" `) }) @@ -3696,7 +3716,7 @@ it("should error when `layer(…)` is used, but it's not the first param", async ) }) -describe('`@reference "…" reference`', () => { +describe('`@reference "…" imports`', () => { test('recursively removes styles', async () => { let loadStylesheet = async (id: string, base: string) => { if (id === './foo/baz.css') { @@ -3849,14 +3869,8 @@ describe('`@reference "…" reference`', () => { }, ), ).resolves.toMatchInlineSnapshot(` - "@layer theme { - :root, :host { - --animate-spin: spin 1s linear infinite; - } - } - - .bar { - animation: var(--animate-spin); + ".bar { + animation: var(--animate-spin, spin 1s linear infinite); } @keyframes spin { @@ -3867,7 +3881,7 @@ describe('`@reference "…" reference`', () => { `) }) - test('emits theme variables and keyframes defined inside @reference-ed files', async () => { + test('emits CSS variable fallback and keyframes defined inside @reference-ed files', async () => { let loadStylesheet = async (id: string, base: string) => { switch (id) { case './one.css': { @@ -3924,18 +3938,9 @@ describe('`@reference "…" reference`', () => { { loadStylesheet }, ), ).resolves.toMatchInlineSnapshot(` - "@layer two { - @layer three { - :root, :host { - --color-red: red; - --animate-wiggle: wiggle 1s ease-in-out infinite; - } - } - } - - .bar { - animation: var(--animate-wiggle); - color: var(--color-red); + ".bar { + animation: var(--animate-wiggle, wiggle 1s ease-in-out infinite); + color: var(--color-red, red); } @keyframes wiggle { diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 1fc32c6076ee..65d1379da1c6 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -448,6 +448,10 @@ async function parseCss( if (node.name === '@theme') { let [themeOptions, themePrefix] = parseThemeOptions(node.params) + if (context.reference) { + themeOptions |= ThemeOptions.REFERENCE + } + if (themePrefix) { if (!IS_VALID_PREFIX.test(themePrefix)) { throw new Error( @@ -463,11 +467,6 @@ 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) { - return WalkAction.Skip - } - theme.addKeyframes(child) return WalkAction.Skip } @@ -540,8 +539,9 @@ async function parseCss( let keyframesRules = designSystem.theme.getKeyframes() for (let keyframes of keyframesRules) { // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when - // printing. - nodes.push(atRoot([keyframes])) + // printing. We push it to the top-level of the AST so that an eventual + // `@reference` does not cut it out when printing the document. + ast.push(context({ theme: true }, [atRoot([keyframes])])) } firstThemeRule.nodes = [context({ theme: true }, nodes)] @@ -603,7 +603,6 @@ async function parseCss( root, utilitiesNode, features, - firstThemeRule, } } @@ -616,10 +615,7 @@ export async function compileAst( features: Features build(candidates: string[]): AstNode[] }> { - let { designSystem, ast, globs, root, utilitiesNode, features, firstThemeRule } = await parseCss( - input, - opts, - ) + let { designSystem, ast, globs, root, utilitiesNode, features } = await parseCss(input, opts) if (process.env.NODE_ENV !== 'test') { ast.unshift(comment(`! tailwindcss v${version} | MIT License | https://tailwindcss.com `)) @@ -647,7 +643,7 @@ export async function compileAst( } if (!utilitiesNode) { - compiled ??= optimizeAst(ast, designSystem, firstThemeRule) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -669,7 +665,7 @@ export async function compileAst( // If no new candidates were added, we can return the original CSS. This // currently assumes that we only add new candidates and never remove any. if (!didChange) { - compiled ??= optimizeAst(ast, designSystem, firstThemeRule) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -681,7 +677,7 @@ export async function compileAst( // CSS. This currently assumes that we only add new ast nodes and never // remove any. if (previousAstNodeCount === newNodes.length) { - compiled ??= optimizeAst(ast, designSystem, firstThemeRule) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -689,7 +685,7 @@ export async function compileAst( utilitiesNode.nodes = newNodes - compiled = optimizeAst(ast, designSystem, firstThemeRule) + compiled = optimizeAst(ast, designSystem) return compiled }, } diff --git a/packages/tailwindcss/src/prefix.test.ts b/packages/tailwindcss/src/prefix.test.ts index 020575ce2f87..65e234276994 100644 --- a/packages/tailwindcss/src/prefix.test.ts +++ b/packages/tailwindcss/src/prefix.test.ts @@ -228,7 +228,7 @@ test('a prefix can be configured via @import theme(…)', async () => { ]), ).toMatchInlineSnapshot(` ".tw\\:bg-potato { - background-color: var(--tw-color-potato); + background-color: var(--tw-color-potato, #7a4724); } .tw\\:custom { color: red; diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 0cc80f3b82fc..d38d5918b33d 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -177,11 +177,20 @@ export class Theme { } #var(themeKey: string) { - if (!this.values.has(themeKey)) { + let value = this.values.get(themeKey) + if (!value) { return null } - return `var(${escape(this.#prefixKey(themeKey))})` + // Since @theme blocks in reference mode do not emit the CSS variables, we can not assume that + // the values will eventually be set up in the browser (e.g. when using `@apply` inside roots + // that use `@reference`). Ensure we set up a fallback in these cases. + let fallback = null + if (value.options & ThemeOptions.REFERENCE) { + fallback = value.value + } + + return `var(${escape(this.#prefixKey(themeKey))}${fallback ? `, ${fallback}` : ''})` } markUsedVariable(themeKey: string) { diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 12ded9596138..0530b7fc954e 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -10649,15 +10649,15 @@ test('bg', async () => { ), ).toMatchInlineSnapshot(` ".bg-current\\/custom { - background-color: color-mix(in oklab, currentColor var(--opacity-custom), transparent); + background-color: color-mix(in oklab, currentColor var(--opacity-custom, var(--custom-opacity)), transparent); } .bg-current\\/half { - background-color: color-mix(in oklab, currentColor var(--opacity-half), transparent); + background-color: color-mix(in oklab, currentColor var(--opacity-half, .5), transparent); } .\\[color\\:red\\]\\/half { - color: color-mix(in oklab, red var(--opacity-half), transparent); + color: color-mix(in oklab, red var(--opacity-half, .5), transparent); }" `) }) @@ -16734,8 +16734,8 @@ describe('custom utilities', () => { expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` "@layer utilities { .text-sm { - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); + font-size: var(--text-sm, .875rem); + line-height: var(--tw-leading, var(--text-sm--line-height, 1.25rem)); font-size: var(--text-sm, .8755rem); line-height: var(--text-sm--line-height, 1.255rem); text-rendering: optimizeLegibility; @@ -16771,7 +16771,7 @@ describe('custom utilities', () => { } .rounded-xl { - border-radius: var(--radius-xl); + border-radius: var(--radius-xl, 16px); } }" `) @@ -17013,19 +17013,19 @@ describe('custom utilities', () => { expect(await compileCss(input, ['tab-1', 'tab-2', 'tab-4', 'tab-github'])) .toMatchInlineSnapshot(` ".tab-1 { - tab-size: var(--tab-size-1); + tab-size: var(--tab-size-1, 1); } .tab-2 { - tab-size: var(--tab-size-2); + tab-size: var(--tab-size-2, 2); } .tab-4 { - tab-size: var(--tab-size-4); + tab-size: var(--tab-size-4, 4); } .tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) expect(await compileCss(input, ['tab-3', 'tab-gitlab'])).toEqual('') @@ -17054,19 +17054,19 @@ describe('custom utilities', () => { expect(await compileCss(input, ['tab-1', 'tab-2', 'tab-4', 'tab-github'])) .toMatchInlineSnapshot(` ".tab-1 { - tab-size: var(--tab-size-1); + tab-size: var(--tab-size-1, 1); } .tab-2 { - tab-size: var(--tab-size-2); + tab-size: var(--tab-size-2, 2); } .tab-4 { - tab-size: var(--tab-size-4); + tab-size: var(--tab-size-4, 4); } .tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) expect(await compileCss(input, ['tab-3', 'tab-gitlab'])).toEqual('') @@ -17091,19 +17091,19 @@ describe('custom utilities', () => { expect(await compileCss(input, ['tab-1', 'tab-2', 'tab-4', 'tab-github'])) .toMatchInlineSnapshot(` ".tab-1 { - tab-size: var(--tab-size-1); + tab-size: var(--tab-size-1, 1); } .tab-2 { - tab-size: var(--tab-size-2); + tab-size: var(--tab-size-2, 2); } .tab-4 { - tab-size: var(--tab-size-4); + tab-size: var(--tab-size-4, 4); } .tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) expect(await compileCss(input, ['tab-3', 'tab-gitlab'])).toEqual('') @@ -17377,7 +17377,7 @@ describe('custom utilities', () => { } .tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) expect(await compileCss(input, ['tab-[#0088cc]', 'tab-[1px]'])).toEqual('') @@ -17409,7 +17409,7 @@ describe('custom utilities', () => { } .example-full { - --value: var(--example-full); + --value: var(--example-full, 100%); }" `) expect(await compileCss(input, ['example-half', 'example-[#0088cc]'])).toEqual('') @@ -17453,7 +17453,7 @@ describe('custom utilities', () => { } .example-full { - --value: var(--example-full); + --value: var(--example-full, 100%); } .tab-76 { @@ -17465,7 +17465,7 @@ describe('custom utilities', () => { } .tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) expect( @@ -17509,7 +17509,7 @@ describe('custom utilities', () => { } .-example-full { - --value: calc(var(--example-full) * -1); + --value: calc(var(--example-full, 100%) * -1); } .example-\\[10px\\] { @@ -17521,7 +17521,7 @@ describe('custom utilities', () => { } .example-full { - --value: var(--example-full); + --value: var(--example-full, 100%); }" `) expect(await compileCss(input, ['example-10'])).toEqual('') @@ -17618,9 +17618,9 @@ describe('custom utilities', () => { } .example-sm\\/7 { - --value: var(--value-sm); - --modifier: var(--modifier-7); - --modifier-with-calc: calc(var(--modifier-7) * 2); + --value: var(--value-sm, 14px); + --modifier: var(--modifier-7, 28px); + --modifier-with-calc: calc(var(--modifier-7, 28px) * 2); } .example-\\[12px\\] { @@ -17628,7 +17628,7 @@ describe('custom utilities', () => { } .example-sm { - --value: var(--value-sm); + --value: var(--value-sm, 14px); }" `) expect( @@ -17660,7 +17660,7 @@ describe('custom utilities', () => { } .example-video { - --value: var(--example-video); + --value: var(--example-video, 16 / 9); }" `) expect(await compileCss(input, ['example-foo'])).toEqual('') @@ -17684,14 +17684,14 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(` ".example-xs\\/6 { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); line-height: 6; } .example-xs { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); }" `) expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('') @@ -17715,14 +17715,14 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(` ".example-xs\\/6 { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); line-height: 6; } .example-xs { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); }" `) expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('') @@ -17746,14 +17746,14 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(` ".example-xs\\/6 { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); line-height: 6; } .example-xs { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); }" `) expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('') @@ -17777,14 +17777,14 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(` ".example-xs\\/6 { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); line-height: 6; } .example-xs { - font-size: var(--text-xs); - line-height: var(--text-xs--line-height); + font-size: var(--text-xs, .75rem); + line-height: var(--text-xs--line-height, calc(1 / .75)); }" `) expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('') @@ -17880,7 +17880,7 @@ describe('custom utilities', () => { expect(await compileCss(input, ['tab-github'])).toMatchInlineSnapshot(` ".tab-github { - tab-size: var(--tab-size-github); + tab-size: var(--tab-size-github, 8); }" `) }) @@ -17945,7 +17945,7 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs'])).toMatchInlineSnapshot(` ".example-xs { - font-size: var(--text-xs); + font-size: var(--text-xs, .75rem); line-height: 1.33333; }" `) @@ -17972,7 +17972,7 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-xs'])).toMatchInlineSnapshot(` ".example-xs { font-size: .75rem; - line-height: var(--text-xs--line-height); + line-height: var(--text-xs--line-height, calc(1 / .75)); }" `) })