From 7ebc4ed186cc5e33f000a48296d4868db949bc7e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 14:47:57 +0100 Subject: [PATCH 01/44] track if CSS variable is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This currently only marks it as `used`, _if_ it's resolved via the `#var(…)` theme method. --- packages/tailwindcss/src/theme.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 0b0ca96c8d11..52329b3188bd 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -6,6 +6,7 @@ export const enum ThemeOptions { INLINE = 1 << 0, REFERENCE = 1 << 1, DEFAULT = 1 << 2, + USED = 1 << 3, } // In the future we may want to replace this with just a `Set` of known theme @@ -172,6 +173,7 @@ export class Theme { return null } + this.values.get(themeKey)!.options |= ThemeOptions.USED return `var(${escape(this.#prefixKey(themeKey))})` } From 87de2a575aa5094070f8f0034eeb42a0948fe418 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 14:49:04 +0100 Subject: [PATCH 02/44] =?UTF-8?q?move=20`:root`=20population=20to=20`build?= =?UTF-8?q?(=E2=80=A6)`=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/tailwindcss/src/index.ts | 72 +++++++++++++++++-------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 14bcee2fe104..85b4ca01b4f2 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -488,7 +488,7 @@ async function parseCss( // Keep a reference to the first `@theme` rule to update with the full // theme later, and delete any other `@theme` rules. if (!firstThemeRule && !(themeOptions & ThemeOptions.REFERENCE)) { - firstThemeRule = styleRule(':root, :host', node.nodes) + firstThemeRule = styleRule(':root, :host', []) replaceWith([firstThemeRule]) } else { replaceWith([]) @@ -523,37 +523,6 @@ async function parseCss( customUtility(designSystem) } - // Output final set of theme variables at the position of the first `@theme` - // rule. - if (firstThemeRule) { - let nodes = [] - - for (let [key, value] of theme.entries()) { - if (value.options & ThemeOptions.REFERENCE) continue - nodes.push(decl(escape(key), value.value)) - } - - let keyframesRules = theme.getKeyframes() - if (keyframesRules.length > 0) { - let animationParts = [...theme.namespace('--animate').values()].flatMap((animation) => - animation.split(/\s+/), - ) - - for (let keyframesRule of keyframesRules) { - // Remove any keyframes that aren't used by an animation variable. - let keyframesName = keyframesRule.params - if (!animationParts.includes(keyframesName)) { - continue - } - - // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when - // printing. - nodes.push(atRoot([keyframesRule])) - } - } - firstThemeRule.nodes = nodes - } - // Replace the `@tailwind utilities` node with a context since it prints // children directly. if (utilitiesNode) { @@ -610,6 +579,7 @@ async function parseCss( root, utilitiesNode, features, + firstThemeRule, } } @@ -622,7 +592,10 @@ export async function compileAst( features: Features build(candidates: string[]): AstNode[] }> { - let { designSystem, ast, globs, root, utilitiesNode, features } = await parseCss(input, opts) + let { designSystem, ast, globs, root, utilitiesNode, features, firstThemeRule } = await parseCss( + input, + opts, + ) if (process.env.NODE_ENV !== 'test') { ast.unshift(comment(`! tailwindcss v${version} | MIT License | https://tailwindcss.com `)) @@ -676,6 +649,39 @@ export async function compileAst( onInvalidCandidate, }).astNodes + // Output final set of theme variables at the position of the first + // `@theme` rule. + if (firstThemeRule) { + let nodes = [] + + for (let [key, value] of designSystem.theme.entries()) { + if (value.options & ThemeOptions.REFERENCE) continue + + nodes.push(decl(escape(key), value.value)) + } + + let keyframesRules = designSystem.theme.getKeyframes() + if (keyframesRules.length > 0) { + let animationParts = [...designSystem.theme.namespace('--animate').values()].flatMap( + (animation) => animation.split(/\s+/), + ) + + for (let keyframesRule of keyframesRules) { + // Remove any keyframes that aren't used by an animation variable. + let keyframesName = keyframesRule.params + if (!animationParts.includes(keyframesName)) { + continue + } + + // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when + // printing. + nodes.push(atRoot([keyframesRule])) + } + } + + firstThemeRule.nodes = nodes + } + // If no new ast nodes were generated, then we can return the original // CSS. This currently assumes that we only add new ast nodes and never // remove any. From 3472e7cc7f2db7dd7000bda80a3d53f677ab3bd3 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 14:50:06 +0100 Subject: [PATCH 03/44] do not emit unused theme variables --- packages/tailwindcss/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 85b4ca01b4f2..df4e4219d888 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -656,6 +656,7 @@ export async function compileAst( for (let [key, value] of designSystem.theme.entries()) { if (value.options & ThemeOptions.REFERENCE) continue + if (!(value.options & ThemeOptions.USED)) continue nodes.push(decl(escape(key), value.value)) } From 44367e514a762d4667728196e04beffd5c061cfa Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 14:50:15 +0100 Subject: [PATCH 04/44] update tests --- .../src/__snapshots__/index.test.ts.snap | 358 ----------------- .../@tailwindcss-postcss/src/index.test.ts | 12 +- .../src/__snapshots__/index.test.ts.snap | 360 ------------------ .../src/__snapshots__/utilities.test.ts.snap | 27 -- .../tailwindcss/src/compat/config.test.ts | 108 +----- .../src/compat/container-config.test.ts | 76 +--- .../tailwindcss/src/compat/plugin-api.test.ts | 18 - .../src/compat/screens-config.test.ts | 27 +- .../tailwindcss/src/css-functions.test.ts | 214 ++--------- packages/tailwindcss/src/index.test.ts | 51 +-- packages/tailwindcss/src/prefix.test.ts | 23 +- packages/tailwindcss/src/utilities.test.ts | 58 +-- packages/tailwindcss/src/variants.test.ts | 80 +--- 13 files changed, 86 insertions(+), 1326 deletions(-) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index d401ace41bed..bc3212de4b54 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -3,370 +3,12 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` "@layer theme { :root, :host { - --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --color-red-50: oklch(.971 .013 17.38); - --color-red-100: oklch(.936 .032 17.717); - --color-red-200: oklch(.885 .062 18.334); - --color-red-300: oklch(.808 .114 19.571); - --color-red-400: oklch(.704 .191 22.216); - --color-red-500: oklch(.637 .237 25.331); - --color-red-600: oklch(.577 .245 27.325); - --color-red-700: oklch(.505 .213 27.518); - --color-red-800: oklch(.444 .177 26.899); - --color-red-900: oklch(.396 .141 25.723); - --color-red-950: oklch(.258 .092 26.042); - --color-orange-50: oklch(.98 .016 73.684); - --color-orange-100: oklch(.954 .038 75.164); - --color-orange-200: oklch(.901 .076 70.697); - --color-orange-300: oklch(.837 .128 66.29); - --color-orange-400: oklch(.75 .183 55.934); - --color-orange-500: oklch(.705 .213 47.604); - --color-orange-600: oklch(.646 .222 41.116); - --color-orange-700: oklch(.553 .195 38.402); - --color-orange-800: oklch(.47 .157 37.304); - --color-orange-900: oklch(.408 .123 38.172); - --color-orange-950: oklch(.266 .079 36.259); - --color-amber-50: oklch(.987 .022 95.277); - --color-amber-100: oklch(.962 .059 95.617); - --color-amber-200: oklch(.924 .12 95.746); - --color-amber-300: oklch(.879 .169 91.605); - --color-amber-400: oklch(.828 .189 84.429); - --color-amber-500: oklch(.769 .188 70.08); - --color-amber-600: oklch(.666 .179 58.318); - --color-amber-700: oklch(.555 .163 48.998); - --color-amber-800: oklch(.473 .137 46.201); - --color-amber-900: oklch(.414 .112 45.904); - --color-amber-950: oklch(.279 .077 45.635); - --color-yellow-50: oklch(.987 .026 102.212); - --color-yellow-100: oklch(.973 .071 103.193); - --color-yellow-200: oklch(.945 .129 101.54); - --color-yellow-300: oklch(.905 .182 98.111); - --color-yellow-400: oklch(.852 .199 91.936); - --color-yellow-500: oklch(.795 .184 86.047); - --color-yellow-600: oklch(.681 .162 75.834); - --color-yellow-700: oklch(.554 .135 66.442); - --color-yellow-800: oklch(.476 .114 61.907); - --color-yellow-900: oklch(.421 .095 57.708); - --color-yellow-950: oklch(.286 .066 53.813); - --color-lime-50: oklch(.986 .031 120.757); - --color-lime-100: oklch(.967 .067 122.328); - --color-lime-200: oklch(.938 .127 124.321); - --color-lime-300: oklch(.897 .196 126.665); - --color-lime-400: oklch(.841 .238 128.85); - --color-lime-500: oklch(.768 .233 130.85); - --color-lime-600: oklch(.648 .2 131.684); - --color-lime-700: oklch(.532 .157 131.589); - --color-lime-800: oklch(.453 .124 130.933); - --color-lime-900: oklch(.405 .101 131.063); - --color-lime-950: oklch(.274 .072 132.109); - --color-green-50: oklch(.982 .018 155.826); - --color-green-100: oklch(.962 .044 156.743); - --color-green-200: oklch(.925 .084 155.995); - --color-green-300: oklch(.871 .15 154.449); - --color-green-400: oklch(.792 .209 151.711); - --color-green-500: oklch(.723 .219 149.579); - --color-green-600: oklch(.627 .194 149.214); - --color-green-700: oklch(.527 .154 150.069); - --color-green-800: oklch(.448 .119 151.328); - --color-green-900: oklch(.393 .095 152.535); - --color-green-950: oklch(.266 .065 152.934); - --color-emerald-50: oklch(.979 .021 166.113); - --color-emerald-100: oklch(.95 .052 163.051); - --color-emerald-200: oklch(.905 .093 164.15); - --color-emerald-300: oklch(.845 .143 164.978); - --color-emerald-400: oklch(.765 .177 163.223); - --color-emerald-500: oklch(.696 .17 162.48); - --color-emerald-600: oklch(.596 .145 163.225); - --color-emerald-700: oklch(.508 .118 165.612); - --color-emerald-800: oklch(.432 .095 166.913); - --color-emerald-900: oklch(.378 .077 168.94); - --color-emerald-950: oklch(.262 .051 172.552); - --color-teal-50: oklch(.984 .014 180.72); - --color-teal-100: oklch(.953 .051 180.801); - --color-teal-200: oklch(.91 .096 180.426); - --color-teal-300: oklch(.855 .138 181.071); - --color-teal-400: oklch(.777 .152 181.912); - --color-teal-500: oklch(.704 .14 182.503); - --color-teal-600: oklch(.6 .118 184.704); - --color-teal-700: oklch(.511 .096 186.391); - --color-teal-800: oklch(.437 .078 188.216); - --color-teal-900: oklch(.386 .063 188.416); - --color-teal-950: oklch(.277 .046 192.524); - --color-cyan-50: oklch(.984 .019 200.873); - --color-cyan-100: oklch(.956 .045 203.388); - --color-cyan-200: oklch(.917 .08 205.041); - --color-cyan-300: oklch(.865 .127 207.078); - --color-cyan-400: oklch(.789 .154 211.53); - --color-cyan-500: oklch(.715 .143 215.221); - --color-cyan-600: oklch(.609 .126 221.723); - --color-cyan-700: oklch(.52 .105 223.128); - --color-cyan-800: oklch(.45 .085 224.283); - --color-cyan-900: oklch(.398 .07 227.392); - --color-cyan-950: oklch(.302 .056 229.695); - --color-sky-50: oklch(.977 .013 236.62); - --color-sky-100: oklch(.951 .026 236.824); - --color-sky-200: oklch(.901 .058 230.902); - --color-sky-300: oklch(.828 .111 230.318); - --color-sky-400: oklch(.746 .16 232.661); - --color-sky-500: oklch(.685 .169 237.323); - --color-sky-600: oklch(.588 .158 241.966); - --color-sky-700: oklch(.5 .134 242.749); - --color-sky-800: oklch(.443 .11 240.79); - --color-sky-900: oklch(.391 .09 240.876); - --color-sky-950: oklch(.293 .066 243.157); - --color-blue-50: oklch(.97 .014 254.604); - --color-blue-100: oklch(.932 .032 255.585); - --color-blue-200: oklch(.882 .059 254.128); - --color-blue-300: oklch(.809 .105 251.813); - --color-blue-400: oklch(.707 .165 254.624); - --color-blue-500: oklch(.623 .214 259.815); - --color-blue-600: oklch(.546 .245 262.881); - --color-blue-700: oklch(.488 .243 264.376); - --color-blue-800: oklch(.424 .199 265.638); - --color-blue-900: oklch(.379 .146 265.522); - --color-blue-950: oklch(.282 .091 267.935); - --color-indigo-50: oklch(.962 .018 272.314); - --color-indigo-100: oklch(.93 .034 272.788); - --color-indigo-200: oklch(.87 .065 274.039); - --color-indigo-300: oklch(.785 .115 274.713); - --color-indigo-400: oklch(.673 .182 276.935); - --color-indigo-500: oklch(.585 .233 277.117); - --color-indigo-600: oklch(.511 .262 276.966); - --color-indigo-700: oklch(.457 .24 277.023); - --color-indigo-800: oklch(.398 .195 277.366); - --color-indigo-900: oklch(.359 .144 278.697); - --color-indigo-950: oklch(.257 .09 281.288); - --color-violet-50: oklch(.969 .016 293.756); - --color-violet-100: oklch(.943 .029 294.588); - --color-violet-200: oklch(.894 .057 293.283); - --color-violet-300: oklch(.811 .111 293.571); - --color-violet-400: oklch(.702 .183 293.541); - --color-violet-500: oklch(.606 .25 292.717); - --color-violet-600: oklch(.541 .281 293.009); - --color-violet-700: oklch(.491 .27 292.581); - --color-violet-800: oklch(.432 .232 292.759); - --color-violet-900: oklch(.38 .189 293.745); - --color-violet-950: oklch(.283 .141 291.089); - --color-purple-50: oklch(.977 .014 308.299); - --color-purple-100: oklch(.946 .033 307.174); - --color-purple-200: oklch(.902 .063 306.703); - --color-purple-300: oklch(.827 .119 306.383); - --color-purple-400: oklch(.714 .203 305.504); - --color-purple-500: oklch(.627 .265 303.9); - --color-purple-600: oklch(.558 .288 302.321); - --color-purple-700: oklch(.496 .265 301.924); - --color-purple-800: oklch(.438 .218 303.724); - --color-purple-900: oklch(.381 .176 304.987); - --color-purple-950: oklch(.291 .149 302.717); - --color-fuchsia-50: oklch(.977 .017 320.058); - --color-fuchsia-100: oklch(.952 .037 318.852); - --color-fuchsia-200: oklch(.903 .076 319.62); - --color-fuchsia-300: oklch(.833 .145 321.434); - --color-fuchsia-400: oklch(.74 .238 322.16); - --color-fuchsia-500: oklch(.667 .295 322.15); - --color-fuchsia-600: oklch(.591 .293 322.896); - --color-fuchsia-700: oklch(.518 .253 323.949); - --color-fuchsia-800: oklch(.452 .211 324.591); - --color-fuchsia-900: oklch(.401 .17 325.612); - --color-fuchsia-950: oklch(.293 .136 325.661); - --color-pink-50: oklch(.971 .014 343.198); - --color-pink-100: oklch(.948 .028 342.258); - --color-pink-200: oklch(.899 .061 343.231); - --color-pink-300: oklch(.823 .12 346.018); - --color-pink-400: oklch(.718 .202 349.761); - --color-pink-500: oklch(.656 .241 354.308); - --color-pink-600: oklch(.592 .249 .584); - --color-pink-700: oklch(.525 .223 3.958); - --color-pink-800: oklch(.459 .187 3.815); - --color-pink-900: oklch(.408 .153 2.432); - --color-pink-950: oklch(.284 .109 3.907); - --color-rose-50: oklch(.969 .015 12.422); - --color-rose-100: oklch(.941 .03 12.58); - --color-rose-200: oklch(.892 .058 10.001); - --color-rose-300: oklch(.81 .117 11.638); - --color-rose-400: oklch(.712 .194 13.428); - --color-rose-500: oklch(.645 .246 16.439); - --color-rose-600: oklch(.586 .253 17.585); - --color-rose-700: oklch(.514 .222 16.935); - --color-rose-800: oklch(.455 .188 13.697); - --color-rose-900: oklch(.41 .159 10.272); - --color-rose-950: oklch(.271 .105 12.094); - --color-slate-50: oklch(.984 .003 247.858); - --color-slate-100: oklch(.968 .007 247.896); - --color-slate-200: oklch(.929 .013 255.508); - --color-slate-300: oklch(.869 .022 252.894); - --color-slate-400: oklch(.704 .04 256.788); - --color-slate-500: oklch(.554 .046 257.417); - --color-slate-600: oklch(.446 .043 257.281); - --color-slate-700: oklch(.372 .044 257.287); - --color-slate-800: oklch(.279 .041 260.031); - --color-slate-900: oklch(.208 .042 265.755); - --color-slate-950: oklch(.129 .042 264.695); - --color-gray-50: oklch(.985 .002 247.839); - --color-gray-100: oklch(.967 .003 264.542); - --color-gray-200: oklch(.928 .006 264.531); - --color-gray-300: oklch(.872 .01 258.338); - --color-gray-400: oklch(.707 .022 261.325); - --color-gray-500: oklch(.551 .027 264.364); - --color-gray-600: oklch(.446 .03 256.802); - --color-gray-700: oklch(.373 .034 259.733); - --color-gray-800: oklch(.278 .033 256.848); - --color-gray-900: oklch(.21 .034 264.665); - --color-gray-950: oklch(.13 .028 261.692); - --color-zinc-50: oklch(.985 0 0); - --color-zinc-100: oklch(.967 .001 286.375); - --color-zinc-200: oklch(.92 .004 286.32); - --color-zinc-300: oklch(.871 .006 286.286); - --color-zinc-400: oklch(.705 .015 286.067); - --color-zinc-500: oklch(.552 .016 285.938); - --color-zinc-600: oklch(.442 .017 285.786); - --color-zinc-700: oklch(.37 .013 285.805); - --color-zinc-800: oklch(.274 .006 286.033); - --color-zinc-900: oklch(.21 .006 285.885); - --color-zinc-950: oklch(.141 .005 285.823); - --color-neutral-50: oklch(.985 0 0); - --color-neutral-100: oklch(.97 0 0); - --color-neutral-200: oklch(.922 0 0); - --color-neutral-300: oklch(.87 0 0); - --color-neutral-400: oklch(.708 0 0); - --color-neutral-500: oklch(.556 0 0); - --color-neutral-600: oklch(.439 0 0); - --color-neutral-700: oklch(.371 0 0); - --color-neutral-800: oklch(.269 0 0); - --color-neutral-900: oklch(.205 0 0); - --color-neutral-950: oklch(.145 0 0); - --color-stone-50: oklch(.985 .001 106.423); - --color-stone-100: oklch(.97 .001 106.424); - --color-stone-200: oklch(.923 .003 48.717); - --color-stone-300: oklch(.869 .005 56.366); - --color-stone-400: oklch(.709 .01 56.259); - --color-stone-500: oklch(.553 .013 58.071); - --color-stone-600: oklch(.444 .011 73.639); - --color-stone-700: oklch(.374 .01 67.558); - --color-stone-800: oklch(.268 .007 34.298); - --color-stone-900: oklch(.216 .006 56.043); - --color-stone-950: oklch(.147 .004 49.25); --color-black: #000; - --color-white: #fff; - --spacing: .25rem; - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; - --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - --text-xs: .75rem; - --text-xs--line-height: calc(1 / .75); - --text-sm: .875rem; - --text-sm--line-height: calc(1.25 / .875); - --text-base: 1rem; - --text-base--line-height: calc(1.5 / 1); - --text-lg: 1.125rem; - --text-lg--line-height: calc(1.75 / 1.125); - --text-xl: 1.25rem; - --text-xl--line-height: calc(1.75 / 1.25); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); - --text-3xl: 1.875rem; - --text-3xl--line-height: calc(2.25 / 1.875); - --text-4xl: 2.25rem; - --text-4xl--line-height: calc(2.5 / 2.25); - --text-5xl: 3rem; - --text-5xl--line-height: 1; - --text-6xl: 3.75rem; - --text-6xl--line-height: 1; - --text-7xl: 4.5rem; - --text-7xl--line-height: 1; - --text-8xl: 6rem; - --text-8xl--line-height: 1; - --text-9xl: 8rem; - --text-9xl--line-height: 1; - --font-weight-thin: 100; - --font-weight-extralight: 200; - --font-weight-light: 300; - --font-weight-normal: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; --font-weight-bold: 700; - --font-weight-extrabold: 800; - --font-weight-black: 900; - --tracking-tighter: -.05em; - --tracking-tight: -.025em; - --tracking-normal: 0em; - --tracking-wide: .025em; - --tracking-wider: .05em; - --tracking-widest: .1em; - --leading-tight: 1.25; - --leading-snug: 1.375; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - --leading-loose: 2; - --radius-xs: .125rem; - --radius-sm: .25rem; - --radius-md: .375rem; - --radius-lg: .5rem; - --radius-xl: .75rem; - --radius-2xl: 1rem; - --radius-3xl: 1.5rem; - --radius-4xl: 2rem; - --shadow-2xs: 0 1px #0000000d; - --shadow-xs: 0 1px 2px 0 #0000000d; - --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a; - --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; - --shadow-2xl: 0 25px 50px -12px #00000040; - --inset-shadow-2xs: inset 0 1px #0000000d; - --inset-shadow-xs: inset 0 1px 1px #0000000d; - --inset-shadow-sm: inset 0 2px 4px #0000000d; - --drop-shadow-xs: 0 1px 1px #0000000d; - --drop-shadow-sm: 0 1px 2px #00000026; - --drop-shadow-md: 0 3px 3px #0000001f; - --drop-shadow-lg: 0 4px 4px #00000026; - --drop-shadow-xl: 0 9px 7px #0000001a; - --drop-shadow-2xl: 0 25px 25px #00000026; - --ease-in: cubic-bezier(.4, 0, 1, 1); - --ease-out: cubic-bezier(0, 0, .2, 1); - --ease-in-out: cubic-bezier(.4, 0, .2, 1); - --animate-spin: spin 1s linear infinite; - --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite; - --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; - --animate-bounce: bounce 1s infinite; - --blur-xs: 4px; - --blur-sm: 8px; - --blur-md: 12px; - --blur-lg: 16px; - --blur-xl: 24px; - --blur-2xl: 40px; - --blur-3xl: 64px; - --perspective-dramatic: 100px; - --perspective-near: 300px; - --perspective-normal: 500px; - --perspective-midrange: 800px; - --perspective-distant: 1200px; - --aspect-video: 16 / 9; --default-transition-duration: .15s; --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); - --default-font-family: var(--font-sans); - --default-font-feature-settings: var(--font-sans--font-feature-settings); - --default-font-variation-settings: var(--font-sans--font-variation-settings); - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); } } diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts index 97eee3c1c5f0..3a7e1b8b896c 100644 --- a/packages/@tailwindcss-postcss/src/index.test.ts +++ b/packages/@tailwindcss-postcss/src/index.test.ts @@ -330,11 +330,7 @@ test('runs `Once` plugins in the right order', async () => { ) expect(result.css.trim()).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .custom-css { + ".custom-css { color: red; }" `) @@ -347,11 +343,7 @@ test('runs `Once` plugins in the right order', async () => { }" `) expect(after).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .custom-css { + ".custom-css { color: red; }" `) diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index 9ed291c3908a..a80dc94ed1a9 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -2,370 +2,10 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = ` ":root, :host { - --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --color-red-50: oklch(.971 .013 17.38); - --color-red-100: oklch(.936 .032 17.717); - --color-red-200: oklch(.885 .062 18.334); - --color-red-300: oklch(.808 .114 19.571); - --color-red-400: oklch(.704 .191 22.216); --color-red-500: oklch(.637 .237 25.331); - --color-red-600: oklch(.577 .245 27.325); - --color-red-700: oklch(.505 .213 27.518); - --color-red-800: oklch(.444 .177 26.899); - --color-red-900: oklch(.396 .141 25.723); - --color-red-950: oklch(.258 .092 26.042); - --color-orange-50: oklch(.98 .016 73.684); - --color-orange-100: oklch(.954 .038 75.164); - --color-orange-200: oklch(.901 .076 70.697); - --color-orange-300: oklch(.837 .128 66.29); - --color-orange-400: oklch(.75 .183 55.934); - --color-orange-500: oklch(.705 .213 47.604); - --color-orange-600: oklch(.646 .222 41.116); - --color-orange-700: oklch(.553 .195 38.402); - --color-orange-800: oklch(.47 .157 37.304); - --color-orange-900: oklch(.408 .123 38.172); - --color-orange-950: oklch(.266 .079 36.259); - --color-amber-50: oklch(.987 .022 95.277); - --color-amber-100: oklch(.962 .059 95.617); - --color-amber-200: oklch(.924 .12 95.746); - --color-amber-300: oklch(.879 .169 91.605); - --color-amber-400: oklch(.828 .189 84.429); - --color-amber-500: oklch(.769 .188 70.08); - --color-amber-600: oklch(.666 .179 58.318); - --color-amber-700: oklch(.555 .163 48.998); - --color-amber-800: oklch(.473 .137 46.201); - --color-amber-900: oklch(.414 .112 45.904); - --color-amber-950: oklch(.279 .077 45.635); - --color-yellow-50: oklch(.987 .026 102.212); - --color-yellow-100: oklch(.973 .071 103.193); - --color-yellow-200: oklch(.945 .129 101.54); - --color-yellow-300: oklch(.905 .182 98.111); - --color-yellow-400: oklch(.852 .199 91.936); - --color-yellow-500: oklch(.795 .184 86.047); - --color-yellow-600: oklch(.681 .162 75.834); - --color-yellow-700: oklch(.554 .135 66.442); - --color-yellow-800: oklch(.476 .114 61.907); - --color-yellow-900: oklch(.421 .095 57.708); - --color-yellow-950: oklch(.286 .066 53.813); - --color-lime-50: oklch(.986 .031 120.757); - --color-lime-100: oklch(.967 .067 122.328); - --color-lime-200: oklch(.938 .127 124.321); - --color-lime-300: oklch(.897 .196 126.665); - --color-lime-400: oklch(.841 .238 128.85); - --color-lime-500: oklch(.768 .233 130.85); - --color-lime-600: oklch(.648 .2 131.684); - --color-lime-700: oklch(.532 .157 131.589); - --color-lime-800: oklch(.453 .124 130.933); - --color-lime-900: oklch(.405 .101 131.063); - --color-lime-950: oklch(.274 .072 132.109); - --color-green-50: oklch(.982 .018 155.826); - --color-green-100: oklch(.962 .044 156.743); - --color-green-200: oklch(.925 .084 155.995); - --color-green-300: oklch(.871 .15 154.449); - --color-green-400: oklch(.792 .209 151.711); - --color-green-500: oklch(.723 .219 149.579); - --color-green-600: oklch(.627 .194 149.214); - --color-green-700: oklch(.527 .154 150.069); - --color-green-800: oklch(.448 .119 151.328); - --color-green-900: oklch(.393 .095 152.535); - --color-green-950: oklch(.266 .065 152.934); - --color-emerald-50: oklch(.979 .021 166.113); - --color-emerald-100: oklch(.95 .052 163.051); - --color-emerald-200: oklch(.905 .093 164.15); - --color-emerald-300: oklch(.845 .143 164.978); - --color-emerald-400: oklch(.765 .177 163.223); - --color-emerald-500: oklch(.696 .17 162.48); - --color-emerald-600: oklch(.596 .145 163.225); - --color-emerald-700: oklch(.508 .118 165.612); - --color-emerald-800: oklch(.432 .095 166.913); - --color-emerald-900: oklch(.378 .077 168.94); - --color-emerald-950: oklch(.262 .051 172.552); - --color-teal-50: oklch(.984 .014 180.72); - --color-teal-100: oklch(.953 .051 180.801); - --color-teal-200: oklch(.91 .096 180.426); - --color-teal-300: oklch(.855 .138 181.071); - --color-teal-400: oklch(.777 .152 181.912); - --color-teal-500: oklch(.704 .14 182.503); - --color-teal-600: oklch(.6 .118 184.704); - --color-teal-700: oklch(.511 .096 186.391); - --color-teal-800: oklch(.437 .078 188.216); - --color-teal-900: oklch(.386 .063 188.416); - --color-teal-950: oklch(.277 .046 192.524); - --color-cyan-50: oklch(.984 .019 200.873); - --color-cyan-100: oklch(.956 .045 203.388); - --color-cyan-200: oklch(.917 .08 205.041); - --color-cyan-300: oklch(.865 .127 207.078); - --color-cyan-400: oklch(.789 .154 211.53); - --color-cyan-500: oklch(.715 .143 215.221); - --color-cyan-600: oklch(.609 .126 221.723); - --color-cyan-700: oklch(.52 .105 223.128); - --color-cyan-800: oklch(.45 .085 224.283); - --color-cyan-900: oklch(.398 .07 227.392); - --color-cyan-950: oklch(.302 .056 229.695); - --color-sky-50: oklch(.977 .013 236.62); - --color-sky-100: oklch(.951 .026 236.824); - --color-sky-200: oklch(.901 .058 230.902); - --color-sky-300: oklch(.828 .111 230.318); - --color-sky-400: oklch(.746 .16 232.661); - --color-sky-500: oklch(.685 .169 237.323); - --color-sky-600: oklch(.588 .158 241.966); - --color-sky-700: oklch(.5 .134 242.749); - --color-sky-800: oklch(.443 .11 240.79); - --color-sky-900: oklch(.391 .09 240.876); - --color-sky-950: oklch(.293 .066 243.157); - --color-blue-50: oklch(.97 .014 254.604); - --color-blue-100: oklch(.932 .032 255.585); - --color-blue-200: oklch(.882 .059 254.128); - --color-blue-300: oklch(.809 .105 251.813); - --color-blue-400: oklch(.707 .165 254.624); - --color-blue-500: oklch(.623 .214 259.815); - --color-blue-600: oklch(.546 .245 262.881); - --color-blue-700: oklch(.488 .243 264.376); - --color-blue-800: oklch(.424 .199 265.638); - --color-blue-900: oklch(.379 .146 265.522); - --color-blue-950: oklch(.282 .091 267.935); - --color-indigo-50: oklch(.962 .018 272.314); - --color-indigo-100: oklch(.93 .034 272.788); - --color-indigo-200: oklch(.87 .065 274.039); - --color-indigo-300: oklch(.785 .115 274.713); - --color-indigo-400: oklch(.673 .182 276.935); - --color-indigo-500: oklch(.585 .233 277.117); - --color-indigo-600: oklch(.511 .262 276.966); - --color-indigo-700: oklch(.457 .24 277.023); - --color-indigo-800: oklch(.398 .195 277.366); - --color-indigo-900: oklch(.359 .144 278.697); - --color-indigo-950: oklch(.257 .09 281.288); - --color-violet-50: oklch(.969 .016 293.756); - --color-violet-100: oklch(.943 .029 294.588); - --color-violet-200: oklch(.894 .057 293.283); - --color-violet-300: oklch(.811 .111 293.571); - --color-violet-400: oklch(.702 .183 293.541); - --color-violet-500: oklch(.606 .25 292.717); - --color-violet-600: oklch(.541 .281 293.009); - --color-violet-700: oklch(.491 .27 292.581); - --color-violet-800: oklch(.432 .232 292.759); - --color-violet-900: oklch(.38 .189 293.745); - --color-violet-950: oklch(.283 .141 291.089); - --color-purple-50: oklch(.977 .014 308.299); - --color-purple-100: oklch(.946 .033 307.174); - --color-purple-200: oklch(.902 .063 306.703); - --color-purple-300: oklch(.827 .119 306.383); - --color-purple-400: oklch(.714 .203 305.504); - --color-purple-500: oklch(.627 .265 303.9); - --color-purple-600: oklch(.558 .288 302.321); - --color-purple-700: oklch(.496 .265 301.924); - --color-purple-800: oklch(.438 .218 303.724); - --color-purple-900: oklch(.381 .176 304.987); - --color-purple-950: oklch(.291 .149 302.717); - --color-fuchsia-50: oklch(.977 .017 320.058); - --color-fuchsia-100: oklch(.952 .037 318.852); - --color-fuchsia-200: oklch(.903 .076 319.62); - --color-fuchsia-300: oklch(.833 .145 321.434); - --color-fuchsia-400: oklch(.74 .238 322.16); - --color-fuchsia-500: oklch(.667 .295 322.15); - --color-fuchsia-600: oklch(.591 .293 322.896); - --color-fuchsia-700: oklch(.518 .253 323.949); - --color-fuchsia-800: oklch(.452 .211 324.591); - --color-fuchsia-900: oklch(.401 .17 325.612); - --color-fuchsia-950: oklch(.293 .136 325.661); - --color-pink-50: oklch(.971 .014 343.198); - --color-pink-100: oklch(.948 .028 342.258); - --color-pink-200: oklch(.899 .061 343.231); - --color-pink-300: oklch(.823 .12 346.018); - --color-pink-400: oklch(.718 .202 349.761); - --color-pink-500: oklch(.656 .241 354.308); - --color-pink-600: oklch(.592 .249 .584); - --color-pink-700: oklch(.525 .223 3.958); - --color-pink-800: oklch(.459 .187 3.815); - --color-pink-900: oklch(.408 .153 2.432); - --color-pink-950: oklch(.284 .109 3.907); - --color-rose-50: oklch(.969 .015 12.422); - --color-rose-100: oklch(.941 .03 12.58); - --color-rose-200: oklch(.892 .058 10.001); - --color-rose-300: oklch(.81 .117 11.638); - --color-rose-400: oklch(.712 .194 13.428); - --color-rose-500: oklch(.645 .246 16.439); - --color-rose-600: oklch(.586 .253 17.585); - --color-rose-700: oklch(.514 .222 16.935); - --color-rose-800: oklch(.455 .188 13.697); - --color-rose-900: oklch(.41 .159 10.272); - --color-rose-950: oklch(.271 .105 12.094); - --color-slate-50: oklch(.984 .003 247.858); - --color-slate-100: oklch(.968 .007 247.896); - --color-slate-200: oklch(.929 .013 255.508); - --color-slate-300: oklch(.869 .022 252.894); - --color-slate-400: oklch(.704 .04 256.788); - --color-slate-500: oklch(.554 .046 257.417); - --color-slate-600: oklch(.446 .043 257.281); - --color-slate-700: oklch(.372 .044 257.287); - --color-slate-800: oklch(.279 .041 260.031); - --color-slate-900: oklch(.208 .042 265.755); - --color-slate-950: oklch(.129 .042 264.695); - --color-gray-50: oklch(.985 .002 247.839); - --color-gray-100: oklch(.967 .003 264.542); - --color-gray-200: oklch(.928 .006 264.531); - --color-gray-300: oklch(.872 .01 258.338); - --color-gray-400: oklch(.707 .022 261.325); - --color-gray-500: oklch(.551 .027 264.364); - --color-gray-600: oklch(.446 .03 256.802); - --color-gray-700: oklch(.373 .034 259.733); - --color-gray-800: oklch(.278 .033 256.848); - --color-gray-900: oklch(.21 .034 264.665); - --color-gray-950: oklch(.13 .028 261.692); - --color-zinc-50: oklch(.985 0 0); - --color-zinc-100: oklch(.967 .001 286.375); - --color-zinc-200: oklch(.92 .004 286.32); - --color-zinc-300: oklch(.871 .006 286.286); - --color-zinc-400: oklch(.705 .015 286.067); - --color-zinc-500: oklch(.552 .016 285.938); - --color-zinc-600: oklch(.442 .017 285.786); - --color-zinc-700: oklch(.37 .013 285.805); - --color-zinc-800: oklch(.274 .006 286.033); - --color-zinc-900: oklch(.21 .006 285.885); - --color-zinc-950: oklch(.141 .005 285.823); - --color-neutral-50: oklch(.985 0 0); - --color-neutral-100: oklch(.97 0 0); - --color-neutral-200: oklch(.922 0 0); - --color-neutral-300: oklch(.87 0 0); - --color-neutral-400: oklch(.708 0 0); - --color-neutral-500: oklch(.556 0 0); - --color-neutral-600: oklch(.439 0 0); - --color-neutral-700: oklch(.371 0 0); - --color-neutral-800: oklch(.269 0 0); - --color-neutral-900: oklch(.205 0 0); - --color-neutral-950: oklch(.145 0 0); - --color-stone-50: oklch(.985 .001 106.423); - --color-stone-100: oklch(.97 .001 106.424); - --color-stone-200: oklch(.923 .003 48.717); - --color-stone-300: oklch(.869 .005 56.366); - --color-stone-400: oklch(.709 .01 56.259); - --color-stone-500: oklch(.553 .013 58.071); - --color-stone-600: oklch(.444 .011 73.639); - --color-stone-700: oklch(.374 .01 67.558); - --color-stone-800: oklch(.268 .007 34.298); - --color-stone-900: oklch(.216 .006 56.043); - --color-stone-950: oklch(.147 .004 49.25); - --color-black: #000; - --color-white: #fff; --spacing: .25rem; - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; - --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - --text-xs: .75rem; - --text-xs--line-height: calc(1 / .75); - --text-sm: .875rem; - --text-sm--line-height: calc(1.25 / .875); - --text-base: 1rem; - --text-base--line-height: calc(1.5 / 1); - --text-lg: 1.125rem; - --text-lg--line-height: calc(1.75 / 1.125); - --text-xl: 1.25rem; - --text-xl--line-height: calc(1.75 / 1.25); - --text-2xl: 1.5rem; - --text-2xl--line-height: calc(2 / 1.5); - --text-3xl: 1.875rem; - --text-3xl--line-height: calc(2.25 / 1.875); - --text-4xl: 2.25rem; - --text-4xl--line-height: calc(2.5 / 2.25); - --text-5xl: 3rem; - --text-5xl--line-height: 1; - --text-6xl: 3.75rem; - --text-6xl--line-height: 1; - --text-7xl: 4.5rem; - --text-7xl--line-height: 1; - --text-8xl: 6rem; - --text-8xl--line-height: 1; - --text-9xl: 8rem; - --text-9xl--line-height: 1; - --font-weight-thin: 100; - --font-weight-extralight: 200; - --font-weight-light: 300; - --font-weight-normal: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; - --font-weight-bold: 700; - --font-weight-extrabold: 800; - --font-weight-black: 900; - --tracking-tighter: -.05em; - --tracking-tight: -.025em; - --tracking-normal: 0em; - --tracking-wide: .025em; - --tracking-wider: .05em; - --tracking-widest: .1em; - --leading-tight: 1.25; - --leading-snug: 1.375; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - --leading-loose: 2; - --radius-xs: .125rem; - --radius-sm: .25rem; - --radius-md: .375rem; - --radius-lg: .5rem; - --radius-xl: .75rem; - --radius-2xl: 1rem; - --radius-3xl: 1.5rem; - --radius-4xl: 2rem; - --shadow-2xs: 0 1px #0000000d; - --shadow-xs: 0 1px 2px 0 #0000000d; - --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a; - --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; - --shadow-2xl: 0 25px 50px -12px #00000040; - --inset-shadow-2xs: inset 0 1px #0000000d; - --inset-shadow-xs: inset 0 1px 1px #0000000d; - --inset-shadow-sm: inset 0 2px 4px #0000000d; - --drop-shadow-xs: 0 1px 1px #0000000d; - --drop-shadow-sm: 0 1px 2px #00000026; - --drop-shadow-md: 0 3px 3px #0000001f; - --drop-shadow-lg: 0 4px 4px #00000026; - --drop-shadow-xl: 0 9px 7px #0000001a; - --drop-shadow-2xl: 0 25px 25px #00000026; - --ease-in: cubic-bezier(.4, 0, 1, 1); - --ease-out: cubic-bezier(0, 0, .2, 1); - --ease-in-out: cubic-bezier(.4, 0, .2, 1); - --animate-spin: spin 1s linear infinite; - --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite; - --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; - --animate-bounce: bounce 1s infinite; - --blur-xs: 4px; - --blur-sm: 8px; - --blur-md: 12px; - --blur-lg: 16px; - --blur-xl: 24px; - --blur-2xl: 40px; - --blur-3xl: 64px; - --perspective-dramatic: 100px; - --perspective-near: 300px; - --perspective-normal: 500px; - --perspective-midrange: 800px; - --perspective-distant: 1200px; - --aspect-video: 16 / 9; --default-transition-duration: .15s; --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); - --default-font-family: var(--font-sans); - --default-font-feature-settings: var(--font-sans--font-feature-settings); - --default-font-variation-settings: var(--font-sans--font-variation-settings); - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); } .w-4 { diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap index 0fe26f6582b5..9d35f62cc069 100644 --- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap @@ -2,9 +2,6 @@ exports[`border-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -127,9 +124,6 @@ exports[`border-* 1`] = ` exports[`border-b-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -252,9 +246,6 @@ exports[`border-b-* 1`] = ` exports[`border-e-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -377,9 +368,6 @@ exports[`border-e-* 1`] = ` exports[`border-l-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -502,9 +490,6 @@ exports[`border-l-* 1`] = ` exports[`border-r-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -627,9 +612,6 @@ exports[`border-r-* 1`] = ` exports[`border-s-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -752,9 +734,6 @@ exports[`border-s-* 1`] = ` exports[`border-t-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -877,9 +856,6 @@ exports[`border-t-* 1`] = ` exports[`border-x-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } @@ -1002,9 +978,6 @@ exports[`border-x-* 1`] = ` exports[`border-y-* 1`] = ` ":root, :host { - --radius-none: 0px; - --radius-full: 9999px; - --radius-sm: .125rem; --color-red-500: #ef4444; } diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index eb18cd734f21..55b91ec394e0 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -322,11 +322,7 @@ describe('theme callbacks', () => { expect(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose'])) .toMatchInlineSnapshot(` - ":root, :host { - --text-base: 100rem; - --text-md--line-height: 101rem; - } - .prose { + ".prose { [class~=lead-base] { font-size: 100rem; line-height: 201rem; @@ -562,12 +558,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: Potato Sans; - --default-font-feature-settings: normal; - --default-font-variation-settings: normal; - } - .font-sans { + ".font-sans { font-family: Potato Sans; } " @@ -601,12 +592,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: Potato Sans; - --default-font-feature-settings: "cv06"; - --default-font-variation-settings: normal; - } - .font-sans { + ".font-sans { font-family: Potato Sans; font-feature-settings: "cv06"; } @@ -641,12 +627,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: Potato Sans; - --default-font-feature-settings: normal; - --default-font-variation-settings: "XHGT" 0.7; - } - .font-sans { + ".font-sans { font-family: Potato Sans; font-variation-settings: "XHGT" 0.7; } @@ -684,12 +665,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: Potato Sans; - --default-font-feature-settings: "cv06"; - --default-font-variation-settings: "XHGT" 0.7; - } - .font-sans { + ".font-sans { font-family: Potato Sans; font-feature-settings: "cv06"; font-variation-settings: "XHGT" 0.7; @@ -729,9 +705,6 @@ describe('default font family compatibility', () => { expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` ":root, :host { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); --font-sans: Sandwich Sans; } .font-sans { @@ -768,12 +741,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: Inter, system-ui, sans-serif; - --default-font-feature-settings: normal; - --default-font-variation-settings: normal; - } - .font-sans { + ".font-sans { font-family: Inter, system-ui, sans-serif; } " @@ -806,14 +774,7 @@ describe('default font family compatibility', () => { }), }) - expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` - ":root, :host { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - " - `) + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`""`) }) test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => { @@ -841,12 +802,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` - ":root, :host { - --default-mono-font-family: Potato Mono; - --default-mono-font-feature-settings: normal; - --default-mono-font-variation-settings: normal; - } - .font-mono { + ".font-mono { font-family: Potato Mono; } " @@ -880,12 +836,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` - ":root, :host { - --default-mono-font-family: Potato Mono; - --default-mono-font-feature-settings: "cv06"; - --default-mono-font-variation-settings: normal; - } - .font-mono { + ".font-mono { font-family: Potato Mono; font-feature-settings: "cv06"; } @@ -920,12 +871,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` - ":root, :host { - --default-mono-font-family: Potato Mono; - --default-mono-font-feature-settings: normal; - --default-mono-font-variation-settings: "XHGT" 0.7; - } - .font-mono { + ".font-mono { font-family: Potato Mono; font-variation-settings: "XHGT" 0.7; } @@ -963,12 +909,7 @@ describe('default font family compatibility', () => { }) expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` - ":root, :host { - --default-mono-font-family: Potato Mono; - --default-mono-font-feature-settings: "cv06"; - --default-mono-font-variation-settings: "XHGT" 0.7; - } - .font-mono { + ".font-mono { font-family: Potato Mono; font-feature-settings: "cv06"; font-variation-settings: "XHGT" 0.7; @@ -1008,9 +949,6 @@ describe('default font family compatibility', () => { expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` ":root, :host { - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); --font-mono: Sandwich Mono; } .font-mono { @@ -1046,14 +984,7 @@ describe('default font family compatibility', () => { }), }) - expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` - ":root, :host { - --default-mono-font-family: var(--font-family-mono); - --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings); - } - " - `) + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`""`) }) }) @@ -1179,13 +1110,7 @@ test('merges css breakpoints with js config screens', async () => { expect(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'])) .toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 50rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .sm\\:flex { + ".sm\\:flex { @media (width >= 44rem) { display: flex; } @@ -1333,12 +1258,7 @@ test('Prefixes configured in CSS take precedence over those defined in JS config ) expect(compiler.build(['wat:custom'])).toMatchInlineSnapshot(` - ":root, :host { - --wat-color-red: #f00; - --wat-color-green: #0f0; - --wat-breakpoint-sm: 640px; - } - .wat\\:custom { + ".wat\\:custom { color: red; } " diff --git a/packages/tailwindcss/src/compat/container-config.test.ts b/packages/tailwindcss/src/compat/container-config.test.ts index e2b9a0ca591f..b5618de84135 100644 --- a/packages/tailwindcss/src/compat/container-config.test.ts +++ b/packages/tailwindcss/src/compat/container-config.test.ts @@ -31,14 +31,7 @@ test('creates a custom utility to extend the built-in container', async () => { }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; @@ -96,14 +89,7 @@ test('allows padding to be defined at custom breakpoints', async () => { }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; @@ -164,14 +150,7 @@ test('allows breakpoints to be overwritten', async () => { }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; @@ -237,14 +216,7 @@ test('padding applies to custom `container` screens', async () => { }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; @@ -307,14 +279,7 @@ test("an empty `screen` config will undo all custom media screens and won't appl }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; @@ -380,20 +345,7 @@ test('legacy container component does not interfere with new --container variabl expect(compiler.build(['max-w-sm'])).toMatchInlineSnapshot(` ":root, :host { - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - --container-prose: 65ch; } .max-w-sm { max-width: var(--container-sm); @@ -438,14 +390,7 @@ test('combines custom padding and screen overwrites', async () => { }) expect(compiler.build(['container', '!container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .\\!container { + ".\\!container { width: 100% !important; @media (width >= 40rem) { max-width: 40rem !important; @@ -557,14 +502,7 @@ test('filters out complex breakpoints', async () => { }) expect(compiler.build(['container'])).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .container { + ".container { width: 100%; @media (width >= 40rem) { max-width: 40rem; diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 9eb65ccb954a..218a7eae1ea6 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -293,9 +293,6 @@ describe('theme', async () => { .variable { color: color-mix(in oklab, #ef4444 var(--opacity), transparent); } - :root, :host { - --color-red-500: #ef4444; - } " `) }) @@ -379,9 +376,6 @@ describe('theme', async () => { .js-variable { color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent); } - :root, :host { - --color-custom-css: rgba(255 0 0 / ); - } " `) }) @@ -1422,12 +1416,6 @@ describe('theme', async () => { .my-width-2\\.5 { width: 0.625rem; } - :root, :host { - --width-1: 0.25rem; - --width-1\\/2: 60%; - --width-1\\.5: 0.375rem; - --width-2_5: 0.625rem; - } " `) }) @@ -1479,12 +1467,6 @@ describe('theme', async () => { .my-width-2\\.5 { width: 0.625rem; } - :root, :host { - --width-1: 0.25rem; - --width-1\\/2: 60%; - --width-1\\.5: 0.375rem; - --width-2_5: 0.625rem; - } " `) }) diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts index e8f96ee55fd3..ac73d8216976 100644 --- a/packages/tailwindcss/src/compat/screens-config.test.ts +++ b/packages/tailwindcss/src/compat/screens-config.test.ts @@ -46,13 +46,7 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => { 'print:items-end', ]), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 50rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .max-w-screen-sm { + ".max-w-screen-sm { max-width: 44rem; } .sm\\:flex { @@ -140,10 +134,7 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => { 'print:items-end', ]), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 50rem; - } - .min-xs\\:flex { + ".min-xs\\:flex { @media (width >= 30rem) { display: flex; } @@ -316,14 +307,7 @@ test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => { 'print:items-end', ]), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - .mini\\:flex { + ".mini\\:flex { @media (width >= 40rem) { display: flex; } @@ -584,10 +568,7 @@ describe('complex screen configs', () => { 'print:items-end', ]), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - } - .min-sm\\:flex { + ".min-sm\\:flex { @media (width >= 40rem) { display: flex; } diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts index 9d81b857ef77..8bfaa34b1fed 100644 --- a/packages/tailwindcss/src/css-functions.test.ts +++ b/packages/tailwindcss/src/css-functions.test.ts @@ -72,11 +72,7 @@ describe('--spacing(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing: .25rem; - } - - .foo { + ".foo { margin: calc(var(--spacing) * 4); }" `) @@ -94,11 +90,7 @@ describe('--spacing(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing: .25rem; - } - - .foo { + ".foo { margin: 1rem; }" `) @@ -157,11 +149,7 @@ describe('--theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -197,11 +185,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -218,11 +202,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -239,11 +219,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -260,11 +236,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -281,11 +253,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -302,11 +270,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -323,11 +287,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -344,11 +304,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -365,11 +321,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -386,11 +338,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: color-mix(in oklab, red var(--opacity), transparent); }" `) @@ -408,11 +356,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: color-mix(in oklab, red var(--opacity, 50%), transparent); }" `) @@ -429,11 +373,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-12: 3rem; - } - - .space-on-the-left { + ".space-on-the-left { margin-left: 3rem; }" `) @@ -450,11 +390,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-2_5: .625rem; - } - - .space-on-the-left { + ".space-on-the-left { margin-left: .625rem; }" `) @@ -471,11 +407,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-2_5: .625rem; - } - - .space-on-the-left { + ".space-on-the-left { margin-left: calc(100vh - .625rem); }" `) @@ -492,11 +424,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --radius-lg: .5rem; - } - - .radius { + ".radius { border-radius: .5rem; }" `) @@ -514,11 +442,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --blur: 8px; - } - - .default-blur { + ".default-blur { filter: blur(8px); }" `) @@ -537,12 +461,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --text-xs: 1337.75rem; - --text-xs--line-height: 1337rem; - } - - .text { + ".text { font-size: 1337.75rem; line-height: 1337rem; }" @@ -626,11 +545,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .25); }" `) @@ -684,12 +599,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - --color-foo: red; - } - - .red { + ".red { color: red; }" `) @@ -707,12 +617,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - --color-foo: oklab(62.7955% .22486 .12584 / .5); - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .25); }" `) @@ -731,11 +636,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -752,11 +653,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .5); }" `) @@ -773,11 +670,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -796,11 +689,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --blur: 8px; - } - - .blur { + ".blur { filter: blur(8px); }" `) @@ -859,11 +748,6 @@ describe('theme(…)', () => { .sm\\:\\[--color\\:theme\\(colors\\.red\\[500\\]\\)\\] { --color: red; } - } - - :root, :host { - --breakpoint-sm: 40rem; - --color-red-500: red; }" `) }) @@ -921,12 +805,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - } - - @media (width >= 48rem) and (width <= 64rem) { + "@media (width >= 48rem) and (width <= 64rem) { .red { color: red; } @@ -948,12 +827,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - } - - @media (width >= 48rem) and (width < 64rem) { + "@media (width >= 48rem) and (width < 64rem) { .red { color: red; } @@ -976,11 +850,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - } - - @media (width >= 48rem) { + "@media (width >= 48rem) { .red { color: red; } @@ -1001,11 +871,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - } - - @container (width > 48rem) { + "@container (width > 48rem) { .red { color: red; } @@ -1026,11 +892,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --font-size-xs: .75rem; - } - - @supports (text-stroke: 0.75rem) { + "@supports (text-stroke: 0.75rem) { .red { color: red; } @@ -1187,11 +1049,7 @@ test('replaces CSS theme() function with values inside imported stylesheets', as }, ), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -1212,11 +1070,7 @@ test('resolves paths ending with a 1', async () => { [], ), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-1: .25rem; - } - - .foo { + ".foo { margin: .25rem; }" `) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 497ccc034173..b23d474031b9 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -27,7 +27,6 @@ describe('compiling CSS', () => { ).toMatchInlineSnapshot(` ":root, :host { --color-black: #000; - --breakpoint-md: 768px; } @layer utilities { @@ -133,12 +132,7 @@ describe('compiling CSS', () => { ], ), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-1_5: 1.5rem; - --spacing-2_5: 2.5rem; - } - - .ml-\\[theme\\(--spacing-1_5\\,theme\\(--spacing-2_5\\,_1rem\\)\\)\\)\\] { + ".ml-\\[theme\\(--spacing-1_5\\,theme\\(--spacing-2_5\\,_1rem\\)\\)\\)\\] { margin-left: 1.5rem; } @@ -173,7 +167,6 @@ describe('compiling CSS', () => { --spacing-1\\.5: 1.5px; --spacing-2_5: 2.5px; --spacing-3\\.5: 3.5px; - --spacing-3_5: 3.5px; --spacing-foo\\/bar: 3rem; } @@ -288,17 +281,7 @@ describe('@apply', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-200: #fecaca; - --color-red-500: #ef4444; - --color-blue-500: #3b82f6; - --color-green-200: #bbf7d0; - --color-green-500: #22c55e; - --breakpoint-md: 768px; - --animate-spin: spin 1s linear infinite; - } - - .foo { + ".foo { --tw-translate-x: 100%; translate: var(--tw-translate-x) var(--tw-translate-y); animation: var(--animate-spin); @@ -328,12 +311,6 @@ describe('@apply', () => { } } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - @property --tw-translate-x { syntax: "*"; inherits: false; @@ -1150,7 +1127,6 @@ describe('Parsing themes values from CSS', () => { ).toMatchInlineSnapshot(` ":root, :host { --color-red: red; - --animate-foo: foo 1s infinite; --text-lg: 20px; } @@ -1392,7 +1368,6 @@ describe('Parsing themes values from CSS', () => { ), ).toMatchInlineSnapshot(` ":root, :host { - --inset-shadow-sm: inset 0 2px 4px #0000000d; --inset-md: 50px; } @@ -1798,13 +1773,7 @@ describe('Parsing themes values from CSS', () => { ['bg-tomato', 'bg-potato', 'bg-primary'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --color-tomato: #e10c04; - --color-potato: #ac855b; - --color-primary: var(--primary); - } - - .bg-potato { + ".bg-potato { background-color: #ac855b; } @@ -1835,13 +1804,7 @@ describe('Parsing themes values from CSS', () => { ['bg-tomato', 'bg-potato', 'bg-primary'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --color-tomato: #e10c04; - --color-potato: #ac855b; - --color-primary: var(--primary); - } - - .bg-potato { + ".bg-potato { background-color: #ac855b; } @@ -1954,11 +1917,7 @@ describe('Parsing themes values from CSS', () => { ['bg-potato'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --color-potato: #efb46b; - } - - .bg-potato { + ".bg-potato { background-color: #efb46b; }" `) diff --git a/packages/tailwindcss/src/prefix.test.ts b/packages/tailwindcss/src/prefix.test.ts index 8fb0eec6a48f..020575ce2f87 100644 --- a/packages/tailwindcss/src/prefix.test.ts +++ b/packages/tailwindcss/src/prefix.test.ts @@ -108,8 +108,6 @@ test('CSS variables output by the theme are prefixed', async () => { expect(compiler.build(['tw:text-red'])).toMatchInlineSnapshot(` ":root, :host { --tw-color-red: #f00; - --tw-color-green: #0f0; - --tw-breakpoint-sm: 640px; } .tw\\:text-red { color: var(--tw-color-red); @@ -131,12 +129,7 @@ test('CSS theme functions do not use the prefix', async () => { expect(compiler.build(['tw:[color:theme(--color-red)]', 'tw:text-[theme(--color-red)]'])) .toMatchInlineSnapshot(` - ":root, :host { - --tw-color-red: #f00; - --tw-color-green: #0f0; - --tw-breakpoint-sm: 640px; - } - .tw\\:\\[color\\:theme\\(--color-red\\)\\] { + ".tw\\:\\[color\\:theme\\(--color-red\\)\\] { color: #f00; } .tw\\:text-\\[theme\\(--color-red\\)\\] { @@ -193,12 +186,7 @@ test('JS theme functions do not use the prefix', async () => { ) expect(compiler.build(['tw:my-custom'])).toMatchInlineSnapshot(` - ":root, :host { - --tw-color-red: #f00; - --tw-color-green: #0f0; - --tw-breakpoint-sm: 640px; - } - .tw\\:my-custom { + ".tw\\:my-custom { color: #f00; } " @@ -337,12 +325,7 @@ test('a prefix can be configured via @import prefix(…)', async () => { }, }) - expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(` - ":root, :host { - --tw-color-potato: #7a4724; - } - " - `) + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`""`) }) test('a prefix must be letters only', async () => { diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 4da13ed6469d..5e773cb5c4e9 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -147,7 +147,6 @@ test('inset', async () => { ).toMatchInlineSnapshot(` ":root, :host { --spacing-4: 1rem; - --inset-shadow-sm: inset 0 1px 1px #0000000d; --inset-shadowned: 1940px; } @@ -3160,15 +3159,7 @@ describe('container', () => { ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - - .container { + ".container { width: 100%; } @@ -3303,15 +3294,7 @@ describe('container', () => { ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - - .container { + ".container { width: 100%; } @@ -7734,11 +7717,7 @@ test('divide-x with custom default border width', async () => { ['divide-x'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --default-border-width: 2px; - } - - :where(.divide-x > :not(:last-child)) { + ":where(.divide-x > :not(:last-child)) { --tw-divide-x-reverse: 0; border-inline-style: var(--tw-border-style); border-inline-start-width: calc(2px * var(--tw-divide-x-reverse)); @@ -7840,11 +7819,7 @@ test('divide-y with custom default border width', async () => { ['divide-y'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --default-border-width: 2px; - } - - :where(.divide-y > :not(:last-child)) { + ":where(.divide-y > :not(:last-child)) { --tw-divide-y-reverse: 0; border-bottom-style: var(--tw-border-style); border-top-style: var(--tw-border-style); @@ -9930,11 +9905,7 @@ test('border with custom default border width', async () => { ['border'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --default-border-width: 2px; - } - - .border { + ".border { border-style: var(--tw-border-style); border-width: 2px; } @@ -13870,7 +13841,6 @@ test('transition', async () => { ":root, :host { --default-transition-timing-function: ease; --default-transition-duration: .1s; - --transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; --transition-property-opacity: opacity; } @@ -13936,12 +13906,7 @@ test('transition', async () => { ['transition', 'transition-all', 'transition-colors'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --default-transition-timing-function: ease; - --default-transition-duration: .1s; - } - - .transition { + ".transition { transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; transition-timing-function: var(--tw-ease, ease); transition-duration: var(--tw-duration, .1s); @@ -15227,8 +15192,6 @@ test('shadow', async () => { ).toMatchInlineSnapshot(` ":root, :host { --color-red-500: #ef4444; - --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a; - --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; } .shadow-\\[10px_10px\\] { @@ -15451,8 +15414,6 @@ test('inset-shadow', async () => { ).toMatchInlineSnapshot(` ":root, :host { --color-red-500: #ef4444; - --inset-shadow: inset 0 2px 4px #0000000d; - --inset-shadow-sm: inset 0 1px 1px #0000000d; } .inset-shadow { @@ -16443,7 +16404,6 @@ describe('spacing utilities', () => { expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` ":root, :host { --spacing-sm: 8px; - --container-sm: 256px; } .w-sm { @@ -17710,11 +17670,7 @@ describe('custom utilities', () => { ` expect(await compileCss(input, ['tab-github'])).toMatchInlineSnapshot(` - ":root, :host { - --tab-size-github: 8; - } - - .tab-github { + ".tab-github { tab-size: 8; }" `) diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index be374dbd9196..8348f940063b 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -747,15 +747,7 @@ test('default breakpoints', async () => { ['sm:flex', 'md:flex', 'lg:flex', 'xl:flex', '2xl:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1280px; - --breakpoint-2xl: 1536px; - } - - @media (width >= 640px) { + "@media (width >= 640px) { .sm\\:flex { display: flex; } @@ -815,11 +807,7 @@ test('custom breakpoint', async () => { ['10xl:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-10xl: 5000px; - } - - @media (width >= 5000px) { + "@media (width >= 5000px) { .\\31 0xl\\:flex { display: flex; } @@ -842,13 +830,7 @@ test('max-*', async () => { ['max-lg:flex', 'max-sm:flex', 'max-md:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-lg: 1024px; - --breakpoint-md: 768px; - } - - @media (width < 1024px) { + "@media (width < 1024px) { .max-lg\\:flex { display: flex; } @@ -897,13 +879,7 @@ test('min-*', async () => { ['min-lg:flex', 'min-sm:flex', 'min-md:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-lg: 1024px; - --breakpoint-md: 768px; - } - - @media (width >= 640px) { + "@media (width >= 640px) { .min-sm\\:flex { display: flex; } @@ -954,15 +930,7 @@ test('sorting stacked min-* and max-* variants', async () => { ['min-sm:max-lg:flex', 'min-sm:max-xl:flex', 'min-md:max-lg:flex', 'min-xs:max-sm:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-lg: 1024px; - --breakpoint-md: 768px; - --breakpoint-xl: 1280px; - --breakpoint-xs: 280px; - } - - @media (width >= 280px) { + "@media (width >= 280px) { @media (width < 640px) { .min-xs\\:max-sm\\:flex { display: flex; @@ -1009,13 +977,7 @@ test('stacked min-* and max-* variants should come after unprefixed variants', a ['sm:flex', 'min-sm:max-lg:flex', 'md:flex', 'min-md:max-lg:flex'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-lg: 1024px; - --breakpoint-md: 768px; - } - - @media (width >= 640px) { + "@media (width >= 640px) { .sm\\:flex { display: flex; } @@ -1071,13 +1033,7 @@ test('min, max and unprefixed breakpoints', async () => { ], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-lg: 1024px; - --breakpoint-md: 768px; - } - - @media (width < 1024px) { + "@media (width < 1024px) { .max-lg\\:flex { display: flex; } @@ -1456,11 +1412,7 @@ test('not', async () => { ], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - } - - .not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) { + ".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) { display: flex; } @@ -2002,11 +1954,7 @@ test('container queries', async () => { ], ), ).toMatchInlineSnapshot(` - ":root, :host { - --container-lg: 1024px; - } - - @container name (width < 1024px) { + "@container name (width < 1024px) { .\\@max-lg\\/name\\:flex { display: flex; } @@ -2158,15 +2106,7 @@ test('variant order', async () => { ], ), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-sm: 640px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1280px; - --breakpoint-2xl: 1536px; - } - - @media (hover: hover) { + "@media (hover: hover) { .group-hover\\:flex:is(:where(.group):hover *), .peer-hover\\:flex:is(:where(.peer):hover ~ *) { display: flex; } From b777ba381df57d20d677fcbe5ed601578210479c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 15:14:57 +0100 Subject: [PATCH 05/44] mark variables as used when used in your CSS --- packages/tailwindcss/src/index.ts | 18 ++++++++++++++++++ packages/tailwindcss/src/theme.ts | 10 +++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index df4e4219d888..00c31cfec720 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -29,6 +29,7 @@ import { Theme, ThemeOptions } from './theme' import { createCssUtility } from './utilities' import { escape, unescape } from './utils/escape' import { segment } from './utils/segment' +import * as ValueParser from './value-parser' import { compoundsForSelectors, IS_VALID_VARIANT_NAME } from './variants' export type Config = UserConfig @@ -558,6 +559,23 @@ async function parseCss( features |= substituteFunctions(ast, designSystem) features |= substituteAtApply(ast, designSystem) + // Mark CSS variables as used. Right now they can only be used in + // declarations, because `@media` and `@container` don't support them. + walk(ast, (node) => { + if (node.kind !== 'declaration') return + if (!node.value?.includes('var(')) return + + ValueParser.walk(ValueParser.parse(node.value), (node) => { + if (node.kind !== 'function' || node.value !== 'var') return + + ValueParser.walk(node.nodes, (child) => { + if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return + + designSystem.theme.use(child.value) + }) + }) + }) + // Remove `@utility`, we couldn't replace it before yet because we had to // handle the nested `@apply` at-rules first. walk(ast, (node, { replaceWith }) => { diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 52329b3188bd..b05e7a3e26cd 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -173,10 +173,18 @@ export class Theme { return null } - this.values.get(themeKey)!.options |= ThemeOptions.USED + this.use(themeKey) + return `var(${escape(this.#prefixKey(themeKey))})` } + use(themeKey: string) { + let value = this.values.get(themeKey) + if (!value) return + + value.options |= ThemeOptions.USED + } + resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { let themeKey = this.#resolveKey(candidateValue, themeKeys) From ad68639d453005b57434f9b152275732fbe419c4 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 15:16:15 +0100 Subject: [PATCH 06/44] update tests --- .../src/__snapshots__/index.test.ts.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index bc3212de4b54..9c6e0b80a776 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -9,6 +9,12 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` --font-weight-bold: 700; --default-transition-duration: .15s; --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); + --default-font-family: var(--font-sans); + --default-font-feature-settings: var(--font-sans--font-feature-settings); + --default-font-variation-settings: var(--font-sans--font-variation-settings); + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); } } From b3b693fff35aacb584291aa9c177de643d7d6499 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 16:47:00 +0100 Subject: [PATCH 07/44] do not mark CSS variables in `@utility` and `@custom-variant` as used We can only mark them as being used if the utility or variant itself is being used. --- packages/tailwindcss/src/index.ts | 7 ++++ packages/tailwindcss/src/utilities.test.ts | 49 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 00c31cfec720..4c86b4468282 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -562,6 +562,13 @@ async function parseCss( // Mark CSS variables as used. Right now they can only be used in // declarations, because `@media` and `@container` don't support them. walk(ast, (node) => { + // Variables used in `@utility` and `@custom-variant` at-rules will be + // handled separately, because we only want to mark them as used if the + // utility or variant is used. + if (node.kind === 'at-rule' && (node.name === '@utility' || node.name === '@custom-variant')) { + return WalkAction.Skip + } + if (node.kind !== 'declaration') return if (!node.value?.includes('var(')) return diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 5e773cb5c4e9..1021ca96843f 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -17610,6 +17610,55 @@ describe('custom utilities', () => { `) expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('') }) + + test('variables used in `@utility` will not be emitted if the utility is not used', async () => { + let input = css` + @theme { + --example-foo: red; + --color-red-500: #f00; + } + + @utility example-* { + color: var(--color-red-500); + background-color: --value(--example); + } + + @tailwind utilities; + ` + + expect(await compileCss(input, ['flex'])).toMatchInlineSnapshot(` + ".flex { + display: flex; + }" + `) + }) + + test('variables used in `@utility` will be emitted if the utility is used', async () => { + let input = css` + @theme { + --example-foo: red; + --color-red-500: #f00; + } + + @utility example-* { + color: var(--color-red-500); + background-color: --value(--example); + } + + @tailwind utilities; + ` + + expect(await compileCss(input, ['example-foo'])).toMatchInlineSnapshot(` + ":root, :host { + --example-foo: red; + } + + .example-foo { + color: var(--color-red-500); + background-color: var(--example-foo); + }" + `) + }) }) test('resolve value based on `@theme`', async () => { From 1d6d4fa144cae9d1c869a3d4f63ae9684b5e49bb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 17:04:46 +0100 Subject: [PATCH 08/44] add method to track CSS variables in an AST --- packages/tailwindcss/src/compile.ts | 3 +++ packages/tailwindcss/src/index.ts | 23 +--------------------- packages/tailwindcss/src/theme.ts | 30 ++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index e13f74c01b92..c4b96fc66e49 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -151,6 +151,9 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem if (result === null) return [] } + // Mark CSS variables as used + designSystem.theme.trackUsedVariables([node]) + rules.push({ node, propertySort, diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 4c86b4468282..c0acaa331ed5 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -29,7 +29,6 @@ import { Theme, ThemeOptions } from './theme' import { createCssUtility } from './utilities' import { escape, unescape } from './utils/escape' import { segment } from './utils/segment' -import * as ValueParser from './value-parser' import { compoundsForSelectors, IS_VALID_VARIANT_NAME } from './variants' export type Config = UserConfig @@ -561,27 +560,7 @@ async function parseCss( // Mark CSS variables as used. Right now they can only be used in // declarations, because `@media` and `@container` don't support them. - walk(ast, (node) => { - // Variables used in `@utility` and `@custom-variant` at-rules will be - // handled separately, because we only want to mark them as used if the - // utility or variant is used. - if (node.kind === 'at-rule' && (node.name === '@utility' || node.name === '@custom-variant')) { - return WalkAction.Skip - } - - if (node.kind !== 'declaration') return - if (!node.value?.includes('var(')) return - - ValueParser.walk(ValueParser.parse(node.value), (node) => { - if (node.kind !== 'function' || node.value !== 'var') return - - ValueParser.walk(node.nodes, (child) => { - if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return - - designSystem.theme.use(child.value) - }) - }) - }) + designSystem.theme.trackUsedVariables(ast) // Remove `@utility`, we couldn't replace it before yet because we had to // handle the nested `@apply` at-rules first. diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index b05e7a3e26cd..b0a07160f69a 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -1,5 +1,6 @@ -import type { AtRule } from './ast' +import { walk, WalkAction, type AstNode, type AtRule } from './ast' import { escape } from './utils/escape' +import * as ValueParser from './value-parser' export const enum ThemeOptions { NONE = 0, @@ -178,6 +179,33 @@ export class Theme { return `var(${escape(this.#prefixKey(themeKey))})` } + trackUsedVariables(ast: AstNode[]) { + walk(ast, (node) => { + // Variables used in `@utility` and `@custom-variant` at-rules will be + // handled separately, because we only want to mark them as used if the + // utility or variant is used. + if ( + node.kind === 'at-rule' && + (node.name === '@utility' || node.name === '@custom-variant') + ) { + return WalkAction.Skip + } + + if (node.kind !== 'declaration') return + if (!node.value?.includes('var(')) return + + ValueParser.walk(ValueParser.parse(node.value), (node) => { + if (node.kind !== 'function' || node.value !== 'var') return + + ValueParser.walk(node.nodes, (child) => { + if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return + + this.use(child.value) + }) + }) + }) + } + use(themeKey: string) { let value = this.values.get(themeKey) if (!value) return From 470f493b86fc5b5ce02f9cff2c58d336e804a5be Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 17:07:09 +0100 Subject: [PATCH 09/44] update tests --- packages/tailwindcss/src/index.test.ts | 7 ++++++- packages/tailwindcss/src/utilities.test.ts | 13 +++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index b23d474031b9..f84ef2b000a4 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -132,7 +132,12 @@ describe('compiling CSS', () => { ], ), ).toMatchInlineSnapshot(` - ".ml-\\[theme\\(--spacing-1_5\\,theme\\(--spacing-2_5\\,_1rem\\)\\)\\)\\] { + ":root, :host { + --spacing-1_5: 1.5rem; + --spacing-2_5: 2.5rem; + } + + .ml-\\[theme\\(--spacing-1_5\\,theme\\(--spacing-2_5\\,_1rem\\)\\)\\)\\] { margin-left: 1.5rem; } diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 1021ca96843f..6cbe64dc94c1 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -3159,7 +3159,11 @@ describe('container', () => { ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'], ), ).toMatchInlineSnapshot(` - ".container { + ":root, :host { + --breakpoint-sm: 40rem; + } + + .container { width: 100%; } @@ -3294,7 +3298,11 @@ describe('container', () => { ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'], ), ).toMatchInlineSnapshot(` - ".container { + ":root, :host { + --breakpoint-sm: 40rem; + } + + .container { width: 100%; } @@ -17651,6 +17659,7 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-foo'])).toMatchInlineSnapshot(` ":root, :host { --example-foo: red; + --color-red-500: red; } .example-foo { From a28522287d3a07ccd32b9dcc09290c9a43f13e65 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 3 Feb 2025 17:21:00 +0100 Subject: [PATCH 10/44] fast path: only scan if `var()` is used in the raw candidate --- packages/tailwindcss/src/compile.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index c4b96fc66e49..87edc8237277 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -151,8 +151,14 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem if (result === null) return [] } - // Mark CSS variables as used - designSystem.theme.trackUsedVariables([node]) + // Mark CSS variables as used. Variables resolved internally will already be + // marked as used. This is purely for arbitrary values and properties that use + // variables. E.g.: `[--color:var(--color-red-500)]` + // + // + if (candidate.raw.includes('var(')) { + designSystem.theme.trackUsedVariables([node]) + } rules.push({ node, From 5b7f315522ac1a17684caea6484da3e5eb8c4d20 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 15:10:05 +0100 Subject: [PATCH 11/44] add failing tests for `@utility` --- packages/tailwindcss/src/utilities.test.ts | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 6cbe64dc94c1..c52e24047ec3 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -16468,6 +16468,40 @@ describe('custom utilities', () => { `) }) + test('custom static utility emit CSS variables if the utility is used', async () => { + let { build } = await compile(css` + @layer utilities { + @tailwind utilities; + } + + @theme { + --example-foo: 123px; + } + + @utility foo { + value: var(--example-foo); + } + `) + let compiled = build([]) + + // `foo` is not used yet: + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`"@layer utilities;"`) + + // `foo` is used, and the CSS variable is emitted: + compiled = build(['foo']) + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .foo { + value: var(--example-foo); + } + } + + :root, :host { + --example-foo: 123px; + }" + `) + }) + test('custom static utility (negative)', async () => { let { build } = await compile(css` @layer utilities { From 2e88937d32a4048c8a26cfe6da52f7edec4a50cb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 15:10:34 +0100 Subject: [PATCH 12/44] track CSS variables in `@utility` --- packages/tailwindcss/src/utilities.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 3dadacac113d..e3dfc088d94e 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -4824,6 +4824,8 @@ export function createCssUtility(node: AtRule) { } } + designSystem.theme.trackUsedVariables(atRule.nodes) + return atRule.nodes }) @@ -4844,7 +4846,11 @@ export function createCssUtility(node: AtRule) { if (IS_VALID_STATIC_UTILITY_NAME.test(name)) { return (designSystem: DesignSystem) => { - designSystem.utilities.static(name, () => structuredClone(node.nodes)) + designSystem.utilities.static(name, () => { + let ast = structuredClone(node.nodes) + designSystem.theme.trackUsedVariables(ast) + return ast + }) } } From 2f2faa0cb8d9c3454e5cec9b8607a464f041ae19 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 15:18:11 +0100 Subject: [PATCH 13/44] add Oxide tests to ensure we find CSS variables --- crates/oxide/src/parser.rs | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index 12791ae780ac..4ad4425ee70e 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -1679,6 +1679,54 @@ mod test { ); } + #[test] + fn test_find_css_variables() { + let candidates = run("var(--color-red-500)", false); + assert_eq!(candidates, vec!["var", "--color-red-500"]); + + let candidates = run("
", false); + assert_eq!( + candidates, + vec!["div", "style", "color", "var", "--color-red-500"] + ); + } + + #[test] + fn test_find_css_variables_with_fallback_values() { + let candidates = run("var(--color-red-500, red)", false); + assert_eq!(candidates, vec!["var", "--color-red-500", "red"]); + + let candidates = run("var(--color-red-500,red)", false); + assert_eq!(candidates, vec!["var", "--color-red-500", "red"]); + + let candidates = run( + "
", + false, + ); + assert_eq!( + candidates, + vec!["div", "style", "color", "var", "--color-red-500", "red"] + ); + + let candidates = run( + "
", + false, + ); + assert_eq!( + candidates, + vec!["div", "style", "color", "var", "--color-red-500", "red"] + ); + } + + #[test] + fn test_find_css_variables_with_fallback_css_variable_values() { + let candidates = run("var(--color-red-500, var(--color-blue-500))", false); + assert_eq!( + candidates, + vec!["var", "--color-red-500", "--color-blue-500"] + ); + } + #[test] fn test_is_valid_candidate_string() { assert_eq!( From 7e61d5eae9fc10bb5c8d4f830483580a8f9a49fe Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 15:57:14 +0100 Subject: [PATCH 14/44] add integration test --- integrations/cli/index.test.ts | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 0e3aba4c4e88..18186610d57a 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1253,3 +1253,65 @@ test( `) }, ) + +test( + 'emit CSS variables if used outside of utilities', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'src/index.css': css` + @import 'tailwindcss/utilities'; + @theme { + --*: initial; + --color-blue-500: blue; + } + `, + 'src/index.ts': ts` + function MyComponent() { + return + } + `, + }, + }, + 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')) + + // No CSS variables are used, so nothing should be generated yet. + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + + " + `) + + // Use a CSS variable in JS/TS land, now it should be generated. + await fs.write( + './src/index.ts', + ts` + function MyComponent() { + return + } + `, + ) + await process.onStderr((m) => m.includes('Done in')) + + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + :root, :host { + --color-blue-500: blue; + } + " + `) + }, +) From 34f16e4832446eccf2f61cb2810f062e71dd18e5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 15:57:26 +0100 Subject: [PATCH 15/44] emit CSS variables when new CSS variables are found If only existing variables have been found, then there is nothing to do. --- packages/tailwindcss/src/index.ts | 10 ++++++++-- packages/tailwindcss/src/theme.ts | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index c0acaa331ed5..c25a89b8ff0f 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -632,12 +632,18 @@ export async function compileAst( } let didChange = false + let emitNewCssVariables = false // Add all new candidates unless we know that they are invalid. let prevSize = allValidCandidates.size for (let candidate of newRawCandidates) { if (!designSystem.invalidCandidates.has(candidate)) { - allValidCandidates.add(candidate) + if (candidate[0] === '-' && candidate[1] === '-') { + emitNewCssVariables = designSystem.theme.use(candidate) + didChange ||= emitNewCssVariables + } else { + allValidCandidates.add(candidate) + } didChange ||= allValidCandidates.size !== prevSize } } @@ -690,7 +696,7 @@ export async function compileAst( // If no new ast nodes were generated, then we can return the original // CSS. This currently assumes that we only add new ast nodes and never // remove any. - if (previousAstNodeCount === newNodes.length) { + if (previousAstNodeCount === newNodes.length && !emitNewCssVariables) { compiled ??= optimizeAst(ast) return compiled } diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index b0a07160f69a..045ff7af0e3b 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -208,9 +208,11 @@ export class Theme { use(themeKey: string) { let value = this.values.get(themeKey) - if (!value) return + if (!value) return false // Unknown + if (value.options & ThemeOptions.USED) return false // Already used value.options |= ThemeOptions.USED + return true } resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { From bdfbaeb91fef77af6e906ce41c9993dd1c476029 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 17:10:58 +0100 Subject: [PATCH 16/44] only emit `--foo-bar` if preceded by `var(` --- crates/oxide/src/parser.rs | 41 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index 4ad4425ee70e..c9d8a125e246 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -159,7 +159,7 @@ impl<'a> Extractor<'a> { } while !candidate.is_empty() { - match Extractor::is_valid_candidate_string(candidate) { + match Extractor::is_valid_candidate_string(candidate, self.input, self.idx_start) { ValidationResult::Valid => return ParseAction::SingleCandidate(candidate), ValidationResult::Restart => return ParseAction::RestartAt(self.idx_start + 1), _ => {} @@ -240,7 +240,11 @@ impl<'a> Extractor<'a> { } #[inline(always)] - fn is_valid_candidate_string(candidate: &'a [u8]) -> ValidationResult { + fn is_valid_candidate_string( + candidate: &'a [u8], + input: &[u8], + start_idx: usize, + ) -> ValidationResult { // Reject candidates that start with a capital letter if candidate[0].is_ascii_uppercase() { return ValidationResult::Invalid; @@ -303,6 +307,14 @@ impl<'a> Extractor<'a> { } } + // CSS variables must be preceded by `var(` to be considered a valid CSS variable candidate + if candidate.starts_with(b"--") { + match input.get(start_idx - 4..start_idx) { + Some(b"var(") => return ValidationResult::Valid, + _ => return ValidationResult::Invalid, + } + } + let split_candidate = Extractor::split_candidate(candidate); let mut offset = 0; @@ -1727,31 +1739,44 @@ mod test { ); } + #[test] + fn test_css_variables_must_be_preceded_by_var_open_paren() { + let candidates = run("[--do-not-emit:true]", false); + assert_eq!( + candidates, + // Looks little funky, but `--do-not-emit` on its own is not emitted + vec!["[--do-not-emit:true]", "--do-not-emit:true"] + ); + + let candidates = run("
", false); + assert_eq!(candidates, vec!["div", "style", "true"]); + } + #[test] fn test_is_valid_candidate_string() { assert_eq!( - Extractor::is_valid_candidate_string(b"foo"), + Extractor::is_valid_candidate_string(b"foo", b"", 0), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo-(--color-red-500)"), + Extractor::is_valid_candidate_string(b"foo-(--color-red-500)", b"", 0), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"bg-[url(foo)]"), + Extractor::is_valid_candidate_string(b"bg-[url(foo)]", b"", 0), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"group-foo/(--bar)"), + Extractor::is_valid_candidate_string(b"group-foo/(--bar)", b"", 0), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")"), + Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")", b"", 0), ValidationResult::Restart ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo-("), + Extractor::is_valid_candidate_string(b"foo-(", b"", 0), ValidationResult::Restart ); } From 9212ca2ac6012459a0e4b143ef104f90ce18019b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 17:11:13 +0100 Subject: [PATCH 17/44] run cargo clippy --- crates/oxide/src/parser.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index c9d8a125e246..35ea42757718 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -352,13 +352,12 @@ impl<'a> Extractor<'a> { let start_brace_index = utility.find(b"["); let end_brace_index = utility.find(b"]"); - match (start_brace_index, end_brace_index) { - (Some(start_brace_index), Some(end_brace_index)) => { - if start_brace_index < index && end_brace_index > index { - skip_parens_check = true; - } + if let (Some(start_brace_index), Some(end_brace_index)) = + (start_brace_index, end_brace_index) + { + if start_brace_index < index && end_brace_index > index { + skip_parens_check = true; } - _ => {} } if !skip_parens_check && !utility[index + 1..].starts_with(b"--") { From fc736a1fbdbc87ea1e57ab682fc60c71a8e37b26 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 4 Feb 2025 18:06:57 +0100 Subject: [PATCH 18/44] use `fs.expectFileToContain` This has some retries built-in --- integrations/cli/index.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 18186610d57a..14e8dff861d4 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1303,15 +1303,15 @@ test( } `, ) - await process.onStderr((m) => m.includes('Done in')) - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/out.css --- - :root, :host { - --color-blue-500: blue; - } - " - `) + fs.expectFileToContain( + './dist/out.css', + css` + :root, + :host { + --color-blue-500: blue; + } + `, + ) }, ) From 5b2f9753abc3ebb1312427cabe320f96a88ae34f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 00:10:18 +0100 Subject: [PATCH 19/44] implement different approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of trying to traverse ASTs and tracking unused variables in various spots, let's reverse the algorithm. ALWAYS emit everything, this has the added benefit that emitting CSS variables and keyframes is only done once (in the `compile(…)` step, not in each `build(…)` step). However, the removal of the unused CSS variables is part of the `optimizeAst` step. This has a few benefits: 1. All the logic is done in a single spot, instead of tracking all over the codebase. 2. We are already traversing for the optimization step, so we can hook in without additional walks of the entire AST. 3. It will be more correct, e.g.: if you have a valid utility, but invalid variant, since the utility is handled first it could be that we mark variables as used even though the no CSS will be generated due to the invalid variant. Since this the `optimizeAst` step never even sees the thrown-away variant+utility, it means that we don't even have to worry about this. Had to add a context node, to different between `:root {}` coming from `@theme` or coming from user CSS (which should stay untouched). --- packages/tailwindcss/src/ast.ts | 64 ++++++++++++++++- packages/tailwindcss/src/compile.ts | 9 --- packages/tailwindcss/src/design-system.ts | 2 +- packages/tailwindcss/src/index.ts | 88 +++++++++++------------ packages/tailwindcss/src/theme.ts | 47 +++--------- packages/tailwindcss/src/utilities.ts | 8 +-- 6 files changed, 116 insertions(+), 102 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 0e9215bbec0f..ab11688ebf0d 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -1,4 +1,8 @@ import { parseAtRule } from './css-parser' +import type { DesignSystem } from './design-system' +import { ThemeOptions } from './theme' +import { DefaultMap } from './utils/default-map' +import * as ValueParser from './value-parser' const AT_SIGN = 0x40 @@ -252,9 +256,13 @@ 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[]) { +export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { let atRoots: AstNode[] = [] let seenAtProperties = new Set() + let cssThemeVariables = new DefaultMap< + Extract['nodes'], + Set + >(() => new Set()) function transform( node: AstNode, @@ -266,6 +274,22 @@ export function optimizeAst(ast: AstNode[]) { if (node.property === '--tw-sort' || node.value === undefined || node.value === null) { return } + + // Track used CSS variables + if (node.value.includes('var(')) { + ValueParser.walk(ValueParser.parse(node.value), (node) => { + if (node.kind !== 'function' || node.value !== 'var') return + + ValueParser.walk(node.nodes, (child) => { + if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return + + designSystem.theme.markUsedVariable(child.value) + }) + + return ValueParser.ValueWalkAction.Skip + }) + } + parent.push(node) } @@ -346,6 +370,15 @@ export function optimizeAst(ast: AstNode[]) { return } + if (node.context.theme) { + let declarations = cssThemeVariables.get(parent) + for (let child of node.nodes) { + if (child.kind === 'declaration') { + declarations.add(child) + } + } + } + for (let child of node.nodes) { transform(child, parent, depth) } @@ -367,6 +400,35 @@ export function optimizeAst(ast: AstNode[]) { transform(node, newAst, 0) } + // Remove unused theme variables + next: for (let [parent, declarations] of cssThemeVariables) { + for (let declaration of declarations) { + let options = designSystem.theme.getOptions(declaration.property) + if (options & ThemeOptions.USED) continue + + // Remove the declaration (from its parent) + let idx = parent.indexOf(declaration) + parent.splice(idx, 1) + + // If the parent is now empty, remove it from the AST + if (parent.length === 0) { + for (let [idx, node] of newAst.entries()) { + // Assumption, but right now the `@theme` must be top-level, so we + // don't need to traverse the entire AST to find the parent. + // + // Checking for `rule`, because at this stage the `@theme` is already + // converted to a normal style rule `:root, :host` + if (node.kind === 'rule' && node.nodes === parent) { + newAst.splice(idx, 1) + break + } + } + + continue next + } + } + } + return newAst.concat(atRoots) } diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 87edc8237277..e13f74c01b92 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -151,15 +151,6 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem if (result === null) return [] } - // Mark CSS variables as used. Variables resolved internally will already be - // marked as used. This is purely for arbitrary values and properties that use - // variables. E.g.: `[--color:var(--color-red-500)]` - // - // - if (candidate.raw.includes('var(')) { - designSystem.theme.trackUsedVariables([node]) - } - rules.push({ node, propertySort, diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index a414f1d23848..fdd5581c94be 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -84,7 +84,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { }, }) - astNodes = optimizeAst(astNodes) + astNodes = optimizeAst(astNodes, designSystem) if (astNodes.length === 0 || wasInvalid) { result.push(null) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index c25a89b8ff0f..229dd12f7124 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -4,6 +4,7 @@ import { atRoot, atRule, comment, + context, context as contextNode, decl, optimizeAst, @@ -523,6 +524,39 @@ async function parseCss( customUtility(designSystem) } + // Output final set of theme variables at the position of the first + // `@theme` rule. + if (firstThemeRule) { + let nodes = [] + + for (let [key, value] of designSystem.theme.entries()) { + if (value.options & ThemeOptions.REFERENCE) continue + + nodes.push(decl(escape(key), value.value)) + } + + let keyframesRules = designSystem.theme.getKeyframes() + if (keyframesRules.length > 0) { + let animationParts = [...designSystem.theme.namespace('--animate').values()].flatMap( + (animation) => animation.split(/\s+/), + ) + + for (let keyframesRule of keyframesRules) { + // Remove any keyframes that aren't used by an animation variable. + let keyframesName = keyframesRule.params + if (!animationParts.includes(keyframesName)) { + continue + } + + // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when + // printing. + nodes.push(atRoot([keyframesRule])) + } + } + + firstThemeRule.nodes = [context({ theme: true }, nodes)] + } + // Replace the `@tailwind utilities` node with a context since it prints // children directly. if (utilitiesNode) { @@ -558,10 +592,6 @@ async function parseCss( features |= substituteFunctions(ast, designSystem) features |= substituteAtApply(ast, designSystem) - // Mark CSS variables as used. Right now they can only be used in - // declarations, because `@media` and `@container` don't support them. - designSystem.theme.trackUsedVariables(ast) - // Remove `@utility`, we couldn't replace it before yet because we had to // handle the nested `@apply` at-rules first. walk(ast, (node, { replaceWith }) => { @@ -583,7 +613,6 @@ async function parseCss( root, utilitiesNode, features, - firstThemeRule, } } @@ -596,10 +625,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 `)) @@ -627,7 +653,7 @@ export async function compileAst( } if (!utilitiesNode) { - compiled ??= optimizeAst(ast) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -639,7 +665,7 @@ export async function compileAst( for (let candidate of newRawCandidates) { if (!designSystem.invalidCandidates.has(candidate)) { if (candidate[0] === '-' && candidate[1] === '-') { - emitNewCssVariables = designSystem.theme.use(candidate) + emitNewCssVariables = designSystem.theme.markUsedVariable(candidate) didChange ||= emitNewCssVariables } else { allValidCandidates.add(candidate) @@ -651,7 +677,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) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -659,45 +685,11 @@ export async function compileAst( onInvalidCandidate, }).astNodes - // Output final set of theme variables at the position of the first - // `@theme` rule. - if (firstThemeRule) { - let nodes = [] - - for (let [key, value] of designSystem.theme.entries()) { - if (value.options & ThemeOptions.REFERENCE) continue - if (!(value.options & ThemeOptions.USED)) continue - - nodes.push(decl(escape(key), value.value)) - } - - let keyframesRules = designSystem.theme.getKeyframes() - if (keyframesRules.length > 0) { - let animationParts = [...designSystem.theme.namespace('--animate').values()].flatMap( - (animation) => animation.split(/\s+/), - ) - - for (let keyframesRule of keyframesRules) { - // Remove any keyframes that aren't used by an animation variable. - let keyframesName = keyframesRule.params - if (!animationParts.includes(keyframesName)) { - continue - } - - // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when - // printing. - nodes.push(atRoot([keyframesRule])) - } - } - - firstThemeRule.nodes = nodes - } - // If no new ast nodes were generated, then we can return the original // CSS. This currently assumes that we only add new ast nodes and never // remove any. if (previousAstNodeCount === newNodes.length && !emitNewCssVariables) { - compiled ??= optimizeAst(ast) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -705,7 +697,7 @@ export async function compileAst( utilitiesNode.nodes = newNodes - compiled = optimizeAst(ast) + compiled = optimizeAst(ast, designSystem) return compiled }, } diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 045ff7af0e3b..970f238d6e44 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -1,6 +1,5 @@ -import { walk, WalkAction, type AstNode, type AtRule } from './ast' +import { type AtRule } from './ast' import { escape } from './utils/escape' -import * as ValueParser from './value-parser' export const enum ThemeOptions { NONE = 0, @@ -108,7 +107,7 @@ export class Theme { } getOptions(key: string) { - return this.values.get(key)?.options ?? ThemeOptions.NONE + return this.values.get(this.#unprefixKey(key))?.options ?? ThemeOptions.NONE } entries() { @@ -125,6 +124,11 @@ export class Theme { return `--${this.prefix}-${key.slice(2)}` } + #unprefixKey(key: string) { + if (!this.prefix) return key + return `--${key.slice(3 + this.prefix.length)}` + } + clearNamespace(namespace: string, clearOptions: ThemeOptions) { let ignored = ignoredThemeKeyMap.get(namespace) ?? [] @@ -174,42 +178,13 @@ export class Theme { return null } - this.use(themeKey) - return `var(${escape(this.#prefixKey(themeKey))})` } - trackUsedVariables(ast: AstNode[]) { - walk(ast, (node) => { - // Variables used in `@utility` and `@custom-variant` at-rules will be - // handled separately, because we only want to mark them as used if the - // utility or variant is used. - if ( - node.kind === 'at-rule' && - (node.name === '@utility' || node.name === '@custom-variant') - ) { - return WalkAction.Skip - } - - if (node.kind !== 'declaration') return - if (!node.value?.includes('var(')) return - - ValueParser.walk(ValueParser.parse(node.value), (node) => { - if (node.kind !== 'function' || node.value !== 'var') return - - ValueParser.walk(node.nodes, (child) => { - if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return - - this.use(child.value) - }) - }) - }) - } - - use(themeKey: string) { - let value = this.values.get(themeKey) - if (!value) return false // Unknown - if (value.options & ThemeOptions.USED) return false // Already used + markUsedVariable(themeKey: string) { + let value = this.values.get(this.#unprefixKey(themeKey)) + if (!value) return false // Unknown variable + if (value.options & ThemeOptions.USED) return false // Variable already used value.options |= ThemeOptions.USED return true diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index e3dfc088d94e..3dadacac113d 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -4824,8 +4824,6 @@ export function createCssUtility(node: AtRule) { } } - designSystem.theme.trackUsedVariables(atRule.nodes) - return atRule.nodes }) @@ -4846,11 +4844,7 @@ export function createCssUtility(node: AtRule) { if (IS_VALID_STATIC_UTILITY_NAME.test(name)) { return (designSystem: DesignSystem) => { - designSystem.utilities.static(name, () => { - let ast = structuredClone(node.nodes) - designSystem.theme.trackUsedVariables(ast) - return ast - }) + designSystem.utilities.static(name, () => structuredClone(node.nodes)) } } From 9b1920ee68637a29ee9e9168f264091f83bfe4b5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 00:18:08 +0100 Subject: [PATCH 20/44] update tests to reflect changes --- .../src/__snapshots__/index.test.ts.snap | 4 +-- .../src/__snapshots__/index.test.ts.snap | 4 +-- .../tailwindcss/src/css-functions.test.ts | 6 ++++- packages/tailwindcss/src/index.test.ts | 27 ++++++++++++------- packages/tailwindcss/src/utilities.test.ts | 12 ++------- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index 9c6e0b80a776..0f449d6fb797 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -3,12 +3,12 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` "@layer theme { :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-black: #000; --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); --font-weight-bold: 700; - --default-transition-duration: .15s; - --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); --default-font-family: var(--font-sans); --default-font-feature-settings: var(--font-sans--font-feature-settings); --default-font-variation-settings: var(--font-sans--font-variation-settings); diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index a80dc94ed1a9..5e8a91869859 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -2,10 +2,10 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = ` ":root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-red-500: oklch(.637 .237 25.331); --spacing: .25rem; - --default-transition-duration: .15s; - --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); } .w-4 { diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts index 8bfaa34b1fed..4fa7ec540081 100644 --- a/packages/tailwindcss/src/css-functions.test.ts +++ b/packages/tailwindcss/src/css-functions.test.ts @@ -72,7 +72,11 @@ describe('--spacing(…)', () => { } `), ).toMatchInlineSnapshot(` - ".foo { + ":root, :host { + --spacing: .25rem; + } + + .foo { margin: calc(var(--spacing) * 4); }" `) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index f84ef2b000a4..10f0b20128df 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -169,10 +169,7 @@ describe('compiling CSS', () => { ), ).toMatchInlineSnapshot(` ":root, :host { - --spacing-1\\.5: 1.5px; --spacing-2_5: 2.5px; - --spacing-3\\.5: 3.5px; - --spacing-foo\\/bar: 3rem; } .m-1\\.5 { @@ -286,7 +283,16 @@ describe('@apply', () => { } `), ).toMatchInlineSnapshot(` - ".foo { + ":root, :host { + --color-red-200: #fecaca; + --color-red-500: #ef4444; + --color-blue-500: #3b82f6; + --color-green-200: #bbf7d0; + --color-green-500: #22c55e; + --animate-spin: spin 1s linear infinite; + } + + .foo { --tw-translate-x: 100%; translate: var(--tw-translate-x) var(--tw-translate-y); animation: var(--animate-spin); @@ -316,6 +322,12 @@ describe('@apply', () => { } } + @keyframes spin { + to { + transform: rotate(360deg); + } + } + @property --tw-translate-x { syntax: "*"; inherits: false; @@ -1094,12 +1106,7 @@ describe('Parsing themes values from CSS', () => { ['w-1/2', 'w-75%'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --width-1\\/2: 75%; - --width-75\\%: 50%; - } - - .w-1\\/2 { + ".w-1\\/2 { width: var(--width-1\\/2); } diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index c52e24047ec3..56eaca4f880c 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -4036,11 +4036,7 @@ test('translate-x', async () => { ['translate-x-full', '-translate-x-full', 'translate-x-px', '-translate-x-[var(--value)]'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing: .25rem; - } - - .-translate-x-\\[var\\(--value\\)\\] { + ".-translate-x-\\[var\\(--value\\)\\] { --tw-translate-x: calc(var(--value) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); } @@ -4164,11 +4160,7 @@ test('translate-y', async () => { ['translate-y-full', '-translate-y-full', 'translate-y-px', '-translate-y-[var(--value)]'], ), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing: .25rem; - } - - .-translate-y-\\[var\\(--value\\)\\] { + ".-translate-y-\\[var\\(--value\\)\\] { --tw-translate-y: calc(var(--value) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); } From 34a1fac2b0a241d69383761c9bce0b47e7a59e7b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 00:42:43 +0100 Subject: [PATCH 21/44] handle escaped CSS variables --- packages/tailwindcss/src/theme.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 970f238d6e44..d7829c5e7d72 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -1,5 +1,5 @@ import { type AtRule } from './ast' -import { escape } from './utils/escape' +import { escape, unescape } from './utils/escape' export const enum ThemeOptions { NONE = 0, @@ -107,7 +107,8 @@ export class Theme { } getOptions(key: string) { - return this.values.get(this.#unprefixKey(key))?.options ?? ThemeOptions.NONE + key = unescape(this.#unprefixKey(key)) + return this.values.get(key)?.options ?? ThemeOptions.NONE } entries() { @@ -182,7 +183,8 @@ export class Theme { } markUsedVariable(themeKey: string) { - let value = this.values.get(this.#unprefixKey(themeKey)) + let key = unescape(this.#unprefixKey(themeKey)) + let value = this.values.get(key) if (!value) return false // Unknown variable if (value.options & ThemeOptions.USED) return false // Variable already used From d29bb7f2145ac30a86671655258ff088bc082192 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 00:42:56 +0100 Subject: [PATCH 22/44] update tests --- packages/tailwindcss/src/index.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 10f0b20128df..a5de05bf8fc1 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -169,7 +169,10 @@ describe('compiling CSS', () => { ), ).toMatchInlineSnapshot(` ":root, :host { + --spacing-1\\.5: 1.5px; --spacing-2_5: 2.5px; + --spacing-3\\.5: 3.5px; + --spacing-foo\\/bar: 3rem; } .m-1\\.5 { @@ -1106,7 +1109,12 @@ describe('Parsing themes values from CSS', () => { ['w-1/2', 'w-75%'], ), ).toMatchInlineSnapshot(` - ".w-1\\/2 { + ":root, :host { + --width-1\\/2: 75%; + --width-75\\%: 50%; + } + + .w-1\\/2 { width: var(--width-1\\/2); } From 60335d09461ee76f70462df80d6044cdf16f9348 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 00:43:08 +0100 Subject: [PATCH 23/44] improve ValueParser, handle escaped characters Before this, `--width-1\/2` would be handled as: ```json [ { kind: 'word', value: '--width-\\' } { kind: 'separator', value: '/' } { kind: 'word', value: '2' } ] ``` But now it will be handled as: ```json [ { kind: 'word', value: '--width-\\/2' } ] ``` --- packages/tailwindcss/src/value-parser.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/tailwindcss/src/value-parser.ts b/packages/tailwindcss/src/value-parser.ts index dda1a7d80ab7..07281f95d095 100644 --- a/packages/tailwindcss/src/value-parser.ts +++ b/packages/tailwindcss/src/value-parser.ts @@ -163,6 +163,14 @@ export function parse(input: string) { let currentChar = input.charCodeAt(i) switch (currentChar) { + // Current character is a `\` therefore the next character is escaped, + // consume it together with the next character and continue. + case BACKSLASH: { + buffer += input[i] + input[i + 1] + i++ + break + } + // Space and commas are bundled into separators // // E.g.: From 4cc3ab22006db02b27a1b71473d8a18c743d96d1 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 10:42:00 +0100 Subject: [PATCH 24/44] cleanup unnecessary code --- packages/tailwindcss/src/index.ts | 6 ++---- packages/tailwindcss/src/theme.ts | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 229dd12f7124..221c2be35800 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -658,15 +658,13 @@ export async function compileAst( } let didChange = false - let emitNewCssVariables = false // Add all new candidates unless we know that they are invalid. let prevSize = allValidCandidates.size for (let candidate of newRawCandidates) { if (!designSystem.invalidCandidates.has(candidate)) { if (candidate[0] === '-' && candidate[1] === '-') { - emitNewCssVariables = designSystem.theme.markUsedVariable(candidate) - didChange ||= emitNewCssVariables + designSystem.theme.markUsedVariable(candidate) } else { allValidCandidates.add(candidate) } @@ -688,7 +686,7 @@ export async function compileAst( // If no new ast nodes were generated, then we can return the original // CSS. This currently assumes that we only add new ast nodes and never // remove any. - if (previousAstNodeCount === newNodes.length && !emitNewCssVariables) { + if (previousAstNodeCount === newNodes.length) { compiled ??= optimizeAst(ast, designSystem) return compiled } diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index d7829c5e7d72..3692be16a51e 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -185,11 +185,10 @@ export class Theme { markUsedVariable(themeKey: string) { let key = unescape(this.#unprefixKey(themeKey)) let value = this.values.get(key) - if (!value) return false // Unknown variable - if (value.options & ThemeOptions.USED) return false // Variable already used + if (!value) return // Unknown variable + if (value.options & ThemeOptions.USED) return // Variable already used value.options |= ThemeOptions.USED - return true } resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { From e5a7071abd48bd91dc6414bc302f25d3457ac98e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 16:56:41 +0100 Subject: [PATCH 25/44] cleanup unnecessary check --- packages/tailwindcss/src/theme.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 3692be16a51e..6e24670bd6d0 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -185,9 +185,7 @@ export class Theme { markUsedVariable(themeKey: string) { let key = unescape(this.#unprefixKey(themeKey)) let value = this.values.get(key) - if (!value) return // Unknown variable - if (value.options & ThemeOptions.USED) return // Variable already used - + if (!value) return value.options |= ThemeOptions.USED } From 07a20b93ca8332207449d8ef0871e2ef7257615e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 17:25:10 +0100 Subject: [PATCH 26/44] cache tracking of variables This makes sure that the `optimizeAst` goes from ~11ms back to ~5ms --- packages/tailwindcss/src/ast.ts | 13 +----------- packages/tailwindcss/src/design-system.ts | 24 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index ab11688ebf0d..0dd62e0a0ea8 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -2,7 +2,6 @@ import { parseAtRule } from './css-parser' import type { DesignSystem } from './design-system' import { ThemeOptions } from './theme' import { DefaultMap } from './utils/default-map' -import * as ValueParser from './value-parser' const AT_SIGN = 0x40 @@ -277,17 +276,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { // Track used CSS variables if (node.value.includes('var(')) { - ValueParser.walk(ValueParser.parse(node.value), (node) => { - if (node.kind !== 'function' || node.value !== 'var') return - - ValueParser.walk(node.nodes, (child) => { - if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return - - designSystem.theme.markUsedVariable(child.value) - }) - - return ValueParser.ValueWalkAction.Skip - }) + designSystem.trackUsedVariables(node.value) } parent.push(node) diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index fdd5581c94be..cfa02beaf366 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -7,6 +7,7 @@ import { getClassOrder } from './sort' import type { Theme, ThemeKey } from './theme' import { Utilities, createUtilities, withAlpha } from './utilities' import { DefaultMap } from './utils/default-map' +import * as ValueParser from './value-parser' import { Variants, createVariants } from './variants' export type DesignSystem = { @@ -30,6 +31,8 @@ export type DesignSystem = { getVariantOrder(): Map resolveThemeValue(path: string): string | undefined + trackUsedVariables(raw: string): void + // Used by IntelliSense candidatesToCss(classes: string[]): (string | null)[] } @@ -42,6 +45,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { let parsedCandidates = new DefaultMap((candidate) => Array.from(parseCandidate(candidate, designSystem)), ) + let compiledAstNodes = new DefaultMap((candidate) => { let ast = compileAstNodes(candidate, designSystem) @@ -64,6 +68,22 @@ export function buildDesignSystem(theme: Theme): DesignSystem { return ast }) + let trackUsedVariables = new DefaultMap((raw) => { + ValueParser.walk(ValueParser.parse(raw), (node) => { + if (node.kind !== 'function' || node.value !== 'var') return + + ValueParser.walk(node.nodes, (child) => { + if (child.kind !== 'word' || child.value[0] !== '-' || child.value[1] !== '-') return + + theme.markUsedVariable(child.value) + }) + + return ValueParser.ValueWalkAction.Skip + }) + + return true + }) + let designSystem: DesignSystem = { theme, utilities, @@ -159,6 +179,10 @@ export function buildDesignSystem(theme: Theme): DesignSystem { return themeValue }, + + trackUsedVariables(raw: string) { + trackUsedVariables.get(raw) + }, } return designSystem From 8748021f220de308ba654ce5c88fc6ea9e0a5859 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 Feb 2025 19:21:16 +0100 Subject: [PATCH 27/44] detect all variables that start with `--` This was already the case before, but we were also verifying that it was preceded by `var(`. Let's remove that code to make sure code like this works: ```js let var = '--my-var'; let style = bla.getPropertyValue(var); ``` --- crates/oxide/src/parser.rs | 41 ++++++++------------------------------ 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index 35ea42757718..9cd702d477f3 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -159,7 +159,7 @@ impl<'a> Extractor<'a> { } while !candidate.is_empty() { - match Extractor::is_valid_candidate_string(candidate, self.input, self.idx_start) { + match Extractor::is_valid_candidate_string(candidate) { ValidationResult::Valid => return ParseAction::SingleCandidate(candidate), ValidationResult::Restart => return ParseAction::RestartAt(self.idx_start + 1), _ => {} @@ -240,11 +240,7 @@ impl<'a> Extractor<'a> { } #[inline(always)] - fn is_valid_candidate_string( - candidate: &'a [u8], - input: &[u8], - start_idx: usize, - ) -> ValidationResult { + fn is_valid_candidate_string(candidate: &'a [u8]) -> ValidationResult { // Reject candidates that start with a capital letter if candidate[0].is_ascii_uppercase() { return ValidationResult::Invalid; @@ -307,14 +303,6 @@ impl<'a> Extractor<'a> { } } - // CSS variables must be preceded by `var(` to be considered a valid CSS variable candidate - if candidate.starts_with(b"--") { - match input.get(start_idx - 4..start_idx) { - Some(b"var(") => return ValidationResult::Valid, - _ => return ValidationResult::Invalid, - } - } - let split_candidate = Extractor::split_candidate(candidate); let mut offset = 0; @@ -1738,44 +1726,31 @@ mod test { ); } - #[test] - fn test_css_variables_must_be_preceded_by_var_open_paren() { - let candidates = run("[--do-not-emit:true]", false); - assert_eq!( - candidates, - // Looks little funky, but `--do-not-emit` on its own is not emitted - vec!["[--do-not-emit:true]", "--do-not-emit:true"] - ); - - let candidates = run("
", false); - assert_eq!(candidates, vec!["div", "style", "true"]); - } - #[test] fn test_is_valid_candidate_string() { assert_eq!( - Extractor::is_valid_candidate_string(b"foo", b"", 0), + Extractor::is_valid_candidate_string(b"foo"), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo-(--color-red-500)", b"", 0), + Extractor::is_valid_candidate_string(b"foo-(--color-red-500)"), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"bg-[url(foo)]", b"", 0), + Extractor::is_valid_candidate_string(b"bg-[url(foo)]"), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"group-foo/(--bar)", b"", 0), + Extractor::is_valid_candidate_string(b"group-foo/(--bar)"), ValidationResult::Valid ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")", b"", 0), + Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")"), ValidationResult::Restart ); assert_eq!( - Extractor::is_valid_candidate_string(b"foo-(", b"", 0), + Extractor::is_valid_candidate_string(b"foo-("), ValidationResult::Restart ); } From 63f39178dd6b03518b5c163ef8651a2941bc256b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 6 Feb 2025 15:27:54 +0100 Subject: [PATCH 28/44] update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a233dcd54ef1..39ba3e3067d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Only expose used CSS variables ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211)) ## [4.0.4] - 2025-02-06 From 64ddc5f4b8fd75356e329fdf21b1498652155c94 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 6 Feb 2025 16:41:22 +0100 Subject: [PATCH 29/44] rename `USED` to `STATIC` --- packages/tailwindcss/src/ast.ts | 2 +- packages/tailwindcss/src/theme.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 0dd62e0a0ea8..a2faf0463bbc 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -393,7 +393,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { next: for (let [parent, declarations] of cssThemeVariables) { for (let declaration of declarations) { let options = designSystem.theme.getOptions(declaration.property) - if (options & ThemeOptions.USED) continue + if (options & ThemeOptions.STATIC) continue // Remove the declaration (from its parent) let idx = parent.indexOf(declaration) diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index 6e24670bd6d0..cfbb3f16bcec 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -6,7 +6,7 @@ export const enum ThemeOptions { INLINE = 1 << 0, REFERENCE = 1 << 1, DEFAULT = 1 << 2, - USED = 1 << 3, + STATIC = 1 << 3, } // In the future we may want to replace this with just a `Set` of known theme @@ -186,7 +186,7 @@ export class Theme { let key = unescape(this.#unprefixKey(themeKey)) let value = this.values.get(key) if (!value) return - value.options |= ThemeOptions.USED + value.options |= ThemeOptions.STATIC } resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { From da33751ee95ffd246495916422af0e3d1de60e31 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 6 Feb 2025 16:41:35 +0100 Subject: [PATCH 30/44] allow `theme(static)` This will make sure that all variable are emitted regardless of whether it's used or not. --- packages/tailwindcss/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 221c2be35800..4cd664138968 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -64,6 +64,8 @@ function parseThemeOptions(params: string) { options |= ThemeOptions.INLINE } else if (option === 'default') { options |= ThemeOptions.DEFAULT + } else if (option === 'static') { + options |= ThemeOptions.STATIC } else if (option.startsWith('prefix(') && option.endsWith(')')) { prefix = option.slice(7, -1) } From 1420e9f3526b1c516b807fa7fa9da70e54b1504b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 6 Feb 2025 17:08:31 +0100 Subject: [PATCH 31/44] add test for `@theme static` --- packages/tailwindcss/src/index.test.ts | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index a5de05bf8fc1..2392691dd641 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1807,6 +1807,33 @@ describe('Parsing themes values from CSS', () => { `) }) + test('theme values added as `static` will always be generated, regardless of whether they were used or not', async () => { + expect( + await compileCss( + css` + @theme static { + --color-tomato: #e10c04; + --color-potato: #ac855b; + --color-primary: var(--primary); + } + + @tailwind utilities; + `, + ['bg-tomato'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --color-tomato: #e10c04; + --color-potato: #ac855b; + --color-primary: var(--primary); + } + + .bg-tomato { + background-color: var(--color-tomato); + }" + `) + }) + test('wrapping `@theme` with `@media theme(inline)` behaves like `@theme inline` to support `@import` statements', async () => { expect( await compileCss( From 8f3eb8a744bfd484ca3c45f20ae1c0760033adf2 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 16:54:07 +0100 Subject: [PATCH 32/44] allow accessing `context` in the `optimizeAst` step --- packages/tailwindcss/src/ast.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index a2faf0463bbc..433704ffa469 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -266,6 +266,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { function transform( node: AstNode, parent: Extract['nodes'], + context: Record = {}, depth = 0, ) { // Declaration @@ -288,7 +289,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { if (node.selector === '&') { for (let child of node.nodes) { let nodes: AstNode[] = [] - transform(child, nodes, depth + 1) + transform(child, nodes, context, depth + 1) if (nodes.length > 0) { parent.push(...nodes) } @@ -299,7 +300,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { else { let copy = { ...node, nodes: [] } for (let child of node.nodes) { - transform(child, copy.nodes, depth + 1) + transform(child, copy.nodes, context, depth + 1) } if (copy.nodes.length > 0) { parent.push(copy) @@ -318,7 +319,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { let copy = { ...node, nodes: [] } for (let child of node.nodes) { - transform(child, copy.nodes, depth + 1) + transform(child, copy.nodes, context, depth + 1) } parent.push(copy) } @@ -327,7 +328,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { else if (node.kind === 'at-rule') { let copy = { ...node, nodes: [] } for (let child of node.nodes) { - transform(child, copy.nodes, depth + 1) + transform(child, copy.nodes, context, depth + 1) } if ( copy.nodes.length > 0 || @@ -345,7 +346,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { else if (node.kind === 'at-root') { for (let child of node.nodes) { let newParent: AstNode[] = [] - transform(child, newParent, 0) + transform(child, newParent, context, 0) for (let child of newParent) { atRoots.push(child) } @@ -369,7 +370,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { } for (let child of node.nodes) { - transform(child, parent, depth) + transform(child, parent, { ...context, ...node.context }, depth) } } From 2e7c8dcd7cbeaab35cb4489c0c5d97e13d3d3162 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 16:54:40 +0100 Subject: [PATCH 33/44] track usage of `@keyframes` And delete the `@keyframes` that were not used. --- packages/tailwindcss/src/ast.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 433704ffa469..66946170ca9e 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -262,6 +262,8 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { Extract['nodes'], Set >(() => new Set()) + let keyframes = new Set() + let usedAnimationNames = new Set() function transform( node: AstNode, @@ -280,6 +282,12 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { designSystem.trackUsedVariables(node.value) } + // Track used animation names + if (node.property === 'animation') { + let parts = node.value.split(/\s+/) + for (let part of parts) usedAnimationNames.add(part) + } + parent.push(node) } @@ -330,6 +338,11 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { for (let child of node.nodes) { transform(child, copy.nodes, context, depth + 1) } + + if (node.kind === 'at-rule' && node.name === '@keyframes' && context.theme) { + keyframes.add(copy) + } + if ( copy.nodes.length > 0 || copy.name === '@layer' || @@ -387,14 +400,22 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { let newAst: AstNode[] = [] for (let node of ast) { - transform(node, newAst, 0) + transform(node, newAst, {}, 0) } // Remove unused theme variables next: for (let [parent, declarations] of cssThemeVariables) { for (let declaration of declarations) { let options = designSystem.theme.getOptions(declaration.property) - if (options & ThemeOptions.STATIC) continue + + if (options & ThemeOptions.STATIC) { + if (declaration.property.startsWith('--animate-')) { + let parts = declaration.value!.split(/\s+/) + for (let part of parts) usedAnimationNames.add(part) + } + + continue + } // Remove the declaration (from its parent) let idx = parent.indexOf(declaration) @@ -419,6 +440,14 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { } } + // Remove unused keyframes + for (let keyframe of keyframes) { + if (!usedAnimationNames.has(keyframe.params)) { + let idx = atRoots.indexOf(keyframe) + atRoots.splice(idx, 1) + } + } + return newAst.concat(atRoots) } From 64cd24a19961009d02bab3dfbc73ab503bdcd27b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 16:55:01 +0100 Subject: [PATCH 34/44] remove unnecessary logic This is now handled by he `optimizeAst` step --- packages/tailwindcss/src/index.ts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 4cd664138968..203d3ad3ab12 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -538,22 +538,10 @@ async function parseCss( } let keyframesRules = designSystem.theme.getKeyframes() - if (keyframesRules.length > 0) { - let animationParts = [...designSystem.theme.namespace('--animate').values()].flatMap( - (animation) => animation.split(/\s+/), - ) - - for (let keyframesRule of keyframesRules) { - // Remove any keyframes that aren't used by an animation variable. - let keyframesName = keyframesRule.params - if (!animationParts.includes(keyframesName)) { - continue - } - - // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when - // printing. - nodes.push(atRoot([keyframesRule])) - } + for (let keyframes of keyframesRules) { + // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when + // printing. + nodes.push(atRoot([keyframes])) } firstThemeRule.nodes = [context({ theme: true }, nodes)] From 557406ba780f2bc8b34ef1fbee48a2d7a7925f84 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 16:55:33 +0100 Subject: [PATCH 35/44] reflect removal of unused `@keyframes` in tests --- .../src/__snapshots__/index.test.ts.snap | 31 ----- .../src/__snapshots__/index.test.ts.snap | 31 ----- packages/tailwindcss/src/index.test.ts | 125 +++++++++++++++++- 3 files changed, 119 insertions(+), 68 deletions(-) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index 0f449d6fb797..dbf73d755e12 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -278,37 +278,6 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` } } -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -@keyframes ping { - 75%, 100% { - opacity: 0; - transform: scale(2); - } -} - -@keyframes pulse { - 50% { - opacity: .5; - } -} - -@keyframes bounce { - 0%, 100% { - animation-timing-function: cubic-bezier(.8, 0, 1, 1); - transform: translateY(-25%); - } - - 50% { - animation-timing-function: cubic-bezier(0, 0, .2, 1); - transform: none; - } -} - @property --tw-font-weight { syntax: "*"; inherits: false diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index 5e8a91869859..0b32f5ed7588 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -27,37 +27,6 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using } } -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -@keyframes ping { - 75%, 100% { - opacity: 0; - transform: scale(2); - } -} - -@keyframes pulse { - 50% { - opacity: .5; - } -} - -@keyframes bounce { - 0%, 100% { - animation-timing-function: cubic-bezier(.8, 0, 1, 1); - transform: translateY(-25%); - } - - 50% { - animation-timing-function: cubic-bezier(0, 0, .2, 1); - transform: none; - } -} - @property --tw-shadow { syntax: "*"; inherits: false; diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 2392691dd641..b5b16880e2ab 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1156,12 +1156,6 @@ describe('Parsing themes values from CSS', () => { .accent-red { accent-color: var(--color-red); - } - - @keyframes foo { - to { - opacity: 1; - } }" `) }) @@ -1578,6 +1572,125 @@ describe('Parsing themes values from CSS', () => { `) }) + test('keyframes are generated when used in an animation', async () => { + expect( + await compileCss( + css` + @theme { + --animate-foo: used 1s infinite; + --animate-bar: unused 1s infinite; + + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused { + to { + opacity: 0; + } + } + } + + @tailwind utilities; + `, + ['animate-foo'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --animate-foo: used 1s infinite; + } + + .animate-foo { + animation: var(--animate-foo); + } + + @keyframes used { + to { + opacity: 1; + } + }" + `) + }) + + test('keyframes are generated when used in an animation using `@theme inline`', async () => { + expect( + await compileCss( + css` + @theme inline { + --animate-foo: used 1s infinite; + --animate-bar: unused 1s infinite; + + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused { + to { + opacity: 0; + } + } + } + + @tailwind utilities; + `, + ['animate-foo'], + ), + ).toMatchInlineSnapshot(` + ".animate-foo { + animation: 1s infinite used; + } + + @keyframes used { + to { + opacity: 1; + } + }" + `) + }) + + test('keyframes are generated when used in user CSS', async () => { + expect( + await compileCss( + css` + @theme { + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused { + to { + opacity: 0; + } + } + } + + .foo { + animation: used 1s infinite; + } + + @tailwind utilities; + `, + [], + ), + ).toMatchInlineSnapshot(` + ".foo { + animation: 1s infinite used; + } + + @keyframes used { + to { + opacity: 1; + } + }" + `) + }) + test('theme values added as reference are not included in the output as variables', async () => { expect( await compileCss( From f3439f343eda1e279d9eba06b8984c4c9885ebb6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 16:56:34 +0100 Subject: [PATCH 36/44] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ba3e3067d1..796a8669a083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Only expose used CSS variables ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211)) +- Only expose used `@keyframes` ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211)) ## [4.0.4] - 2025-02-06 From b735291879772b3ac86a6f90ffa68cf1c708248f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:11:02 +0100 Subject: [PATCH 37/44] remove unnecessary check --- packages/tailwindcss/src/ast.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 66946170ca9e..74d38f193c2c 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -339,7 +339,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { transform(child, copy.nodes, context, depth + 1) } - if (node.kind === 'at-rule' && node.name === '@keyframes' && context.theme) { + if (node.name === '@keyframes' && context.theme) { keyframes.add(copy) } From dc7aee82dda61c25f0b1938db5fbdeb2915cc826 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:11:17 +0100 Subject: [PATCH 38/44] add test for `@theme static` --- packages/tailwindcss/src/index.test.ts | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index b5b16880e2ab..e2b33febe43c 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1652,6 +1652,55 @@ describe('Parsing themes values from CSS', () => { `) }) + test('keyframes are generated when used in an animation using `@theme static`', async () => { + expect( + await compileCss( + css` + @theme static { + --animate-foo: used 1s infinite; + --animate-bar: unused-but-kept 1s infinite; + + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused-but-kept { + to { + opacity: 0; + } + } + } + + @tailwind utilities; + `, + ['animate-foo'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --animate-foo: used 1s infinite; + --animate-bar: unused-but-kept 1s infinite; + } + + .animate-foo { + animation: var(--animate-foo); + } + + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused-but-kept { + to { + opacity: 0; + } + }" + `) + }) + test('keyframes are generated when used in user CSS', async () => { expect( await compileCss( From a00210ae76a7720cd58dd4152796e9b28da34a7e Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:11:33 -0500 Subject: [PATCH 39/44] Test that non-theme keyframes are always preserved --- packages/tailwindcss/src/index.test.ts | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index e2b33febe43c..f7c2084d6995 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1740,6 +1740,51 @@ describe('Parsing themes values from CSS', () => { `) }) + test('keyframes outside of `@theme are always preserved', async () => { + expect( + await compileCss( + css` + @theme { + @keyframes used { + to { + opacity: 1; + } + } + } + + @keyframes unused { + to { + opacity: 0; + } + } + + .foo { + animation: used 1s infinite; + } + + @tailwind utilities; + `, + [], + ), + ).toMatchInlineSnapshot(` + "@keyframes unused { + to { + opacity: 0; + } + } + + .foo { + animation: 1s infinite used; + } + + @keyframes used { + to { + opacity: 1; + } + }" + `) + }) + test('theme values added as reference are not included in the output as variables', async () => { expect( await compileCss( From 7cd2443a53e025bd5a7324eeb12103783180bbae Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:12:49 +0100 Subject: [PATCH 40/44] improve naming --- packages/tailwindcss/src/ast.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 74d38f193c2c..c9d35574b9c5 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -263,7 +263,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { Set >(() => new Set()) let keyframes = new Set() - let usedAnimationNames = new Set() + let usedKeyframeNames = new Set() function transform( node: AstNode, @@ -285,7 +285,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { // Track used animation names if (node.property === 'animation') { let parts = node.value.split(/\s+/) - for (let part of parts) usedAnimationNames.add(part) + for (let part of parts) usedKeyframeNames.add(part) } parent.push(node) @@ -339,6 +339,8 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { transform(child, copy.nodes, context, depth + 1) } + // Only track `@keyframes` that could be removed, when they were defined + // inside of a `@theme`. if (node.name === '@keyframes' && context.theme) { keyframes.add(copy) } @@ -411,7 +413,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { if (options & ThemeOptions.STATIC) { if (declaration.property.startsWith('--animate-')) { let parts = declaration.value!.split(/\s+/) - for (let part of parts) usedAnimationNames.add(part) + for (let part of parts) usedKeyframeNames.add(part) } continue @@ -442,7 +444,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { // Remove unused keyframes for (let keyframe of keyframes) { - if (!usedAnimationNames.has(keyframe.params)) { + if (!usedKeyframeNames.has(keyframe.params)) { let idx = atRoots.indexOf(keyframe) atRoots.splice(idx, 1) } From b132705d7d9d5411242f09b66f0684808041e8fd Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:43:40 +0100 Subject: [PATCH 41/44] move tracking of variables up Now that we have access to the `context`, we can handle the variables tracking in the `declaration` handling itself. --- packages/tailwindcss/src/ast.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index c9d35574b9c5..a271f8af1765 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -277,6 +277,11 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { return } + // Track variables defined in `@theme` + if (context.theme && node.property[0] === '-' && node.property[1] === '-') { + cssThemeVariables.get(parent).add(node) + } + // Track used CSS variables if (node.value.includes('var(')) { designSystem.trackUsedVariables(node.value) @@ -375,15 +380,6 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { return } - if (node.context.theme) { - let declarations = cssThemeVariables.get(parent) - for (let child of node.nodes) { - if (child.kind === 'declaration') { - declarations.add(child) - } - } - } - for (let child of node.nodes) { transform(child, parent, { ...context, ...node.context }, depth) } From bf428dde7ebe81bf6c38e947bd8c2e719d23abb9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:46:52 +0100 Subject: [PATCH 42/44] keep `static` and `used` separate This improves readability and keeps the conceptual logic separated. --- packages/tailwindcss/src/ast.ts | 2 +- packages/tailwindcss/src/theme.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index a271f8af1765..559accd020cc 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -406,7 +406,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { for (let declaration of declarations) { let options = designSystem.theme.getOptions(declaration.property) - if (options & ThemeOptions.STATIC) { + if (options & (ThemeOptions.STATIC | ThemeOptions.USED)) { if (declaration.property.startsWith('--animate-')) { let parts = declaration.value!.split(/\s+/) for (let part of parts) usedKeyframeNames.add(part) diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index cfbb3f16bcec..0cc80f3b82fc 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -6,7 +6,9 @@ export const enum ThemeOptions { INLINE = 1 << 0, REFERENCE = 1 << 1, DEFAULT = 1 << 2, + STATIC = 1 << 3, + USED = 1 << 4, } // In the future we may want to replace this with just a `Set` of known theme @@ -186,7 +188,7 @@ export class Theme { let key = unescape(this.#unprefixKey(themeKey)) let value = this.values.get(key) if (!value) return - value.options |= ThemeOptions.STATIC + value.options |= ThemeOptions.USED } resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { From 680d72d53ea4da3610ebac7d520ee91a1f210f35 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 17:49:35 +0100 Subject: [PATCH 43/44] remove unnecessary code We already start with a clean slate when populating `:root, :host`, so this is unnecessary work. --- packages/tailwindcss/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 203d3ad3ab12..07956ab421ce 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -457,18 +457,16 @@ async function parseCss( } // Record all custom properties in the `@theme` declaration - walk(node.nodes, (child, { replaceWith }) => { + walk(node.nodes, (child) => { // 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 } From aafc1d66351640b6b7435c4bb28d066f3328ebf5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 18:09:55 +0100 Subject: [PATCH 44/44] remove unnecessary reset --- integrations/cli/index.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 14e8dff861d4..2eee0413dc4f 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1269,7 +1269,6 @@ test( 'src/index.css': css` @import 'tailwindcss/utilities'; @theme { - --*: initial; --color-blue-500: blue; } `,