From d684733d804a0b8951d13c94fe27350271e076b6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 7 Feb 2025 18:12:47 +0100 Subject: [PATCH] Only expose used CSS variables (#16211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR only exposes used CSS variables. My initial approach was to track the used variables, this was a bit messy because it meant that we had to walk part of the AST(s) in multiple places. We also had to be careful because sometimes if a variable exists in an AST, that doesn't mean that it's actually used. E.g.: ```css h1 { color: var(--color-red-500); /* Definitely used, so let's keep it */ } @utility foo { color: var(--color-blue-500); /* Hmm, used? */ } ``` In this last case, the `--color-blue-500` is part of the CSS AST, but as long as `foo` the utility is not used, it won't end up in your actual CSS file, therefore the variable is **not** used. Alternatively, if the `foo` utility is used with an invalid variant (e.g.: `group-[>.foo]:foo`, then the `@utility foo` code will still run internally because variants are applied on top of the utility. This means that it looks like `var(--color-blue-500)` is being used. Another annoying side effect was that because variables are conditionally generated, that the `@theme` -> `:root, :host` conversion had to happen for every build, instead of once in the `compile(…)` step. --- To prevent all the messy rules and additional booking while walking of ASTs I thought about a different approach. We are only interested in variables that are actually used. The only way we know for sure, is right before the `toCss(…)` step. Any step before that could still throw away AST nodes. However, we do have an `optimizeAst` step right before printing to simplify and optimize the AST. So the idea was to keep all the CSS variables in the AST, and only in the `optimizeAst` step we perform a kind of mark-and-sweep algorithm where we can first check which variables are _actually_ used (these are the ones that are left in the AST), and later we removed the ones that weren't part of known used list. Moving the logic to this step feels a natural spot for this to happen, because we are in fact optimizing the AST. We were already walking the AST, so we can just handle these cases while we are walking without additional walks. Last but not least, this also means that there is only a single spot where need to track and remove variables. Now, there is a different part to this story. If you use a variable in JS land for example, we also want to make sure that we keep the CSS variable in the CSS. To do this, we can mark variables as being used in the internal `Theme`. The Oxide scanner will also emit used variables that it can find such as `var(--color-red-500)` and will emit `--color-red-500` as a "candidate". We can then proactively mark this one as used even though it may not be used anyway in the actual AST. --- ### Always including all variables Some users might make heavy use of JavaScript and string interpolation where they _need_ all the variables to be present. Similar to the `inline` and `reference` theme options, this also exposes a new `static` option. This ensures that all the CSS variables will always be generated regardless of whether it's used or not. One handy feature is that you have granular control over this: ```css /* These will always be generated */ @theme static { --color-primary: red; --color-secondary: blue; } /* Only generated when used */ @theme { --color-maybe: pink; } ``` ### Performance considerations: Now that we are tracking which variables are being used, it means that we will produce a smaller CSS file, but we are also doing more work (the mark-and-sweep part). That said, ran some benchmarks and the changes look like this: Running it on Catalyst: image _(probably within margin of error)_ Running it on Tailwind UI: image ### Test plan - Tests have been updated with the removed CSS variables - Added a dedicated integration test to show that Oxide can find variables and mark them as used (so they are included) - Ran the code on Catalyst, and verified that all the removed variables are in fact not used anywhere in the codebase. The diff on Catalyst looks like this:
```diff diff --git a/templates/catalyst/out.css b/templates/catalyst/out.css index f2b364ea..240d1d90 100644 --- a/templates/catalyst/out.css +++ b/templates/catalyst/out.css @@ -29,218 +29,111 @@ @layer theme { :root, :host { --font-sans: Inter, sans-serif; - --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(0.971 0.013 17.38); - --color-red-100: oklch(0.936 0.032 17.717); --color-red-200: oklch(0.885 0.062 18.334); --color-red-300: oklch(0.808 0.114 19.571); --color-red-400: oklch(0.704 0.191 22.216); --color-red-500: oklch(0.637 0.237 25.331); --color-red-600: oklch(0.577 0.245 27.325); --color-red-700: oklch(0.505 0.213 27.518); - --color-red-800: oklch(0.444 0.177 26.899); --color-red-900: oklch(0.396 0.141 25.723); - --color-red-950: oklch(0.258 0.092 26.042); - --color-orange-50: oklch(0.98 0.016 73.684); - --color-orange-100: oklch(0.954 0.038 75.164); --color-orange-200: oklch(0.901 0.076 70.697); --color-orange-300: oklch(0.837 0.128 66.29); --color-orange-400: oklch(0.75 0.183 55.934); --color-orange-500: oklch(0.705 0.213 47.604); --color-orange-600: oklch(0.646 0.222 41.116); --color-orange-700: oklch(0.553 0.195 38.402); - --color-orange-800: oklch(0.47 0.157 37.304); --color-orange-900: oklch(0.408 0.123 38.172); - --color-orange-950: oklch(0.266 0.079 36.259); - --color-amber-50: oklch(0.987 0.022 95.277); - --color-amber-100: oklch(0.962 0.059 95.617); - --color-amber-200: oklch(0.924 0.12 95.746); - --color-amber-300: oklch(0.879 0.169 91.605); --color-amber-400: oklch(0.828 0.189 84.429); --color-amber-500: oklch(0.769 0.188 70.08); --color-amber-600: oklch(0.666 0.179 58.318); --color-amber-700: oklch(0.555 0.163 48.998); - --color-amber-800: oklch(0.473 0.137 46.201); - --color-amber-900: oklch(0.414 0.112 45.904); --color-amber-950: oklch(0.279 0.077 45.635); - --color-yellow-50: oklch(0.987 0.026 102.212); - --color-yellow-100: oklch(0.973 0.071 103.193); - --color-yellow-200: oklch(0.945 0.129 101.54); --color-yellow-300: oklch(0.905 0.182 98.111); --color-yellow-400: oklch(0.852 0.199 91.936); - --color-yellow-500: oklch(0.795 0.184 86.047); --color-yellow-600: oklch(0.681 0.162 75.834); --color-yellow-700: oklch(0.554 0.135 66.442); - --color-yellow-800: oklch(0.476 0.114 61.907); - --color-yellow-900: oklch(0.421 0.095 57.708); --color-yellow-950: oklch(0.286 0.066 53.813); - --color-lime-50: oklch(0.986 0.031 120.757); - --color-lime-100: oklch(0.967 0.067 122.328); - --color-lime-200: oklch(0.938 0.127 124.321); --color-lime-300: oklch(0.897 0.196 126.665); --color-lime-400: oklch(0.841 0.238 128.85); - --color-lime-500: oklch(0.768 0.233 130.85); --color-lime-600: oklch(0.648 0.2 131.684); --color-lime-700: oklch(0.532 0.157 131.589); - --color-lime-800: oklch(0.453 0.124 130.933); - --color-lime-900: oklch(0.405 0.101 131.063); --color-lime-950: oklch(0.274 0.072 132.109); - --color-green-50: oklch(0.982 0.018 155.826); - --color-green-100: oklch(0.962 0.044 156.743); - --color-green-200: oklch(0.925 0.084 155.995); - --color-green-300: oklch(0.871 0.15 154.449); --color-green-400: oklch(0.792 0.209 151.711); --color-green-500: oklch(0.723 0.219 149.579); --color-green-600: oklch(0.627 0.194 149.214); --color-green-700: oklch(0.527 0.154 150.069); - --color-green-800: oklch(0.448 0.119 151.328); --color-green-900: oklch(0.393 0.095 152.535); - --color-green-950: oklch(0.266 0.065 152.934); - --color-emerald-50: oklch(0.979 0.021 166.113); - --color-emerald-100: oklch(0.95 0.052 163.051); - --color-emerald-200: oklch(0.905 0.093 164.15); - --color-emerald-300: oklch(0.845 0.143 164.978); --color-emerald-400: oklch(0.765 0.177 163.223); --color-emerald-500: oklch(0.696 0.17 162.48); --color-emerald-600: oklch(0.596 0.145 163.225); --color-emerald-700: oklch(0.508 0.118 165.612); - --color-emerald-800: oklch(0.432 0.095 166.913); --color-emerald-900: oklch(0.378 0.077 168.94); - --color-emerald-950: oklch(0.262 0.051 172.552); - --color-teal-50: oklch(0.984 0.014 180.72); - --color-teal-100: oklch(0.953 0.051 180.801); - --color-teal-200: oklch(0.91 0.096 180.426); --color-teal-300: oklch(0.855 0.138 181.071); --color-teal-400: oklch(0.777 0.152 181.912); --color-teal-500: oklch(0.704 0.14 182.503); --color-teal-600: oklch(0.6 0.118 184.704); --color-teal-700: oklch(0.511 0.096 186.391); - --color-teal-800: oklch(0.437 0.078 188.216); --color-teal-900: oklch(0.386 0.063 188.416); - --color-teal-950: oklch(0.277 0.046 192.524); - --color-cyan-50: oklch(0.984 0.019 200.873); - --color-cyan-100: oklch(0.956 0.045 203.388); - --color-cyan-200: oklch(0.917 0.08 205.041); --color-cyan-300: oklch(0.865 0.127 207.078); --color-cyan-400: oklch(0.789 0.154 211.53); --color-cyan-500: oklch(0.715 0.143 215.221); - --color-cyan-600: oklch(0.609 0.126 221.723); --color-cyan-700: oklch(0.52 0.105 223.128); - --color-cyan-800: oklch(0.45 0.085 224.283); - --color-cyan-900: oklch(0.398 0.07 227.392); --color-cyan-950: oklch(0.302 0.056 229.695); - --color-sky-50: oklch(0.977 0.013 236.62); - --color-sky-100: oklch(0.951 0.026 236.824); - --color-sky-200: oklch(0.901 0.058 230.902); --color-sky-300: oklch(0.828 0.111 230.318); - --color-sky-400: oklch(0.746 0.16 232.661); --color-sky-500: oklch(0.685 0.169 237.323); --color-sky-600: oklch(0.588 0.158 241.966); --color-sky-700: oklch(0.5 0.134 242.749); - --color-sky-800: oklch(0.443 0.11 240.79); --color-sky-900: oklch(0.391 0.09 240.876); - --color-sky-950: oklch(0.293 0.066 243.157); - --color-blue-50: oklch(0.97 0.014 254.604); - --color-blue-100: oklch(0.932 0.032 255.585); - --color-blue-200: oklch(0.882 0.059 254.128); --color-blue-300: oklch(0.809 0.105 251.813); --color-blue-400: oklch(0.707 0.165 254.624); --color-blue-500: oklch(0.623 0.214 259.815); --color-blue-600: oklch(0.546 0.245 262.881); --color-blue-700: oklch(0.488 0.243 264.376); - --color-blue-800: oklch(0.424 0.199 265.638); --color-blue-900: oklch(0.379 0.146 265.522); - --color-blue-950: oklch(0.282 0.091 267.935); - --color-indigo-50: oklch(0.962 0.018 272.314); - --color-indigo-100: oklch(0.93 0.034 272.788); --color-indigo-200: oklch(0.87 0.065 274.039); --color-indigo-300: oklch(0.785 0.115 274.713); --color-indigo-400: oklch(0.673 0.182 276.935); --color-indigo-500: oklch(0.585 0.233 277.117); --color-indigo-600: oklch(0.511 0.262 276.966); --color-indigo-700: oklch(0.457 0.24 277.023); - --color-indigo-800: oklch(0.398 0.195 277.366); --color-indigo-900: oklch(0.359 0.144 278.697); - --color-indigo-950: oklch(0.257 0.09 281.288); - --color-violet-50: oklch(0.969 0.016 293.756); - --color-violet-100: oklch(0.943 0.029 294.588); --color-violet-200: oklch(0.894 0.057 293.283); --color-violet-300: oklch(0.811 0.111 293.571); --color-violet-400: oklch(0.702 0.183 293.541); --color-violet-500: oklch(0.606 0.25 292.717); --color-violet-600: oklch(0.541 0.281 293.009); --color-violet-700: oklch(0.491 0.27 292.581); - --color-violet-800: oklch(0.432 0.232 292.759); --color-violet-900: oklch(0.38 0.189 293.745); - --color-violet-950: oklch(0.283 0.141 291.089); - --color-purple-50: oklch(0.977 0.014 308.299); - --color-purple-100: oklch(0.946 0.033 307.174); --color-purple-200: oklch(0.902 0.063 306.703); --color-purple-300: oklch(0.827 0.119 306.383); --color-purple-400: oklch(0.714 0.203 305.504); --color-purple-500: oklch(0.627 0.265 303.9); --color-purple-600: oklch(0.558 0.288 302.321); --color-purple-700: oklch(0.496 0.265 301.924); - --color-purple-800: oklch(0.438 0.218 303.724); --color-purple-900: oklch(0.381 0.176 304.987); - --color-purple-950: oklch(0.291 0.149 302.717); - --color-fuchsia-50: oklch(0.977 0.017 320.058); - --color-fuchsia-100: oklch(0.952 0.037 318.852); --color-fuchsia-200: oklch(0.903 0.076 319.62); --color-fuchsia-300: oklch(0.833 0.145 321.434); --color-fuchsia-400: oklch(0.74 0.238 322.16); --color-fuchsia-500: oklch(0.667 0.295 322.15); --color-fuchsia-600: oklch(0.591 0.293 322.896); --color-fuchsia-700: oklch(0.518 0.253 323.949); - --color-fuchsia-800: oklch(0.452 0.211 324.591); --color-fuchsia-900: oklch(0.401 0.17 325.612); - --color-fuchsia-950: oklch(0.293 0.136 325.661); - --color-pink-50: oklch(0.971 0.014 343.198); - --color-pink-100: oklch(0.948 0.028 342.258); --color-pink-200: oklch(0.899 0.061 343.231); --color-pink-300: oklch(0.823 0.12 346.018); --color-pink-400: oklch(0.718 0.202 349.761); --color-pink-500: oklch(0.656 0.241 354.308); --color-pink-600: oklch(0.592 0.249 0.584); --color-pink-700: oklch(0.525 0.223 3.958); - --color-pink-800: oklch(0.459 0.187 3.815); --color-pink-900: oklch(0.408 0.153 2.432); - --color-pink-950: oklch(0.284 0.109 3.907); - --color-rose-50: oklch(0.969 0.015 12.422); - --color-rose-100: oklch(0.941 0.03 12.58); --color-rose-200: oklch(0.892 0.058 10.001); --color-rose-300: oklch(0.81 0.117 11.638); --color-rose-400: oklch(0.712 0.194 13.428); --color-rose-500: oklch(0.645 0.246 16.439); --color-rose-600: oklch(0.586 0.253 17.585); --color-rose-700: oklch(0.514 0.222 16.935); - --color-rose-800: oklch(0.455 0.188 13.697); --color-rose-900: oklch(0.41 0.159 10.272); - --color-rose-950: oklch(0.271 0.105 12.094); - --color-slate-50: oklch(0.984 0.003 247.858); - --color-slate-100: oklch(0.968 0.007 247.896); - --color-slate-200: oklch(0.929 0.013 255.508); - --color-slate-300: oklch(0.869 0.022 252.894); - --color-slate-400: oklch(0.704 0.04 256.788); - --color-slate-500: oklch(0.554 0.046 257.417); - --color-slate-600: oklch(0.446 0.043 257.281); - --color-slate-700: oklch(0.372 0.044 257.287); - --color-slate-800: oklch(0.279 0.041 260.031); - --color-slate-900: oklch(0.208 0.042 265.755); - --color-slate-950: oklch(0.129 0.042 264.695); - --color-gray-50: oklch(0.985 0.002 247.839); - --color-gray-100: oklch(0.967 0.003 264.542); - --color-gray-200: oklch(0.928 0.006 264.531); - --color-gray-300: oklch(0.872 0.01 258.338); - --color-gray-400: oklch(0.707 0.022 261.325); - --color-gray-500: oklch(0.551 0.027 264.364); - --color-gray-600: oklch(0.446 0.03 256.802); - --color-gray-700: oklch(0.373 0.034 259.733); - --color-gray-800: oklch(0.278 0.033 256.848); - --color-gray-900: oklch(0.21 0.034 264.665); - --color-gray-950: oklch(0.13 0.028 261.692); --color-zinc-50: oklch(0.985 0 0); --color-zinc-100: oklch(0.967 0.001 286.375); --color-zinc-200: oklch(0.92 0.004 286.32); @@ -252,38 +145,9 @@ --color-zinc-800: oklch(0.274 0.006 286.033); --color-zinc-900: oklch(0.21 0.006 285.885); --color-zinc-950: oklch(0.141 0.005 285.823); - --color-neutral-50: oklch(0.985 0 0); - --color-neutral-100: oklch(0.97 0 0); - --color-neutral-200: oklch(0.922 0 0); - --color-neutral-300: oklch(0.87 0 0); - --color-neutral-400: oklch(0.708 0 0); - --color-neutral-500: oklch(0.556 0 0); - --color-neutral-600: oklch(0.439 0 0); - --color-neutral-700: oklch(0.371 0 0); - --color-neutral-800: oklch(0.269 0 0); - --color-neutral-900: oklch(0.205 0 0); - --color-neutral-950: oklch(0.145 0 0); - --color-stone-50: oklch(0.985 0.001 106.423); - --color-stone-100: oklch(0.97 0.001 106.424); - --color-stone-200: oklch(0.923 0.003 48.717); - --color-stone-300: oklch(0.869 0.005 56.366); - --color-stone-400: oklch(0.709 0.01 56.259); - --color-stone-500: oklch(0.553 0.013 58.071); - --color-stone-600: oklch(0.444 0.011 73.639); - --color-stone-700: oklch(0.374 0.01 67.558); - --color-stone-800: oklch(0.268 0.007 34.298); - --color-stone-900: oklch(0.216 0.006 56.043); - --color-stone-950: oklch(0.147 0.004 49.25); --color-black: #000; --color-white: #fff; --spacing: 0.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; @@ -302,92 +166,23 @@ --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: -0.05em; - --tracking-tight: -0.025em; - --tracking-normal: 0em; - --tracking-wide: 0.025em; - --tracking-wider: 0.05em; - --tracking-widest: 0.1em; - --leading-tight: 1.25; - --leading-snug: 1.375; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - --leading-loose: 2; - --radius-xs: 0.125rem; --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; - --radius-4xl: 2rem; - --shadow-2xs: 0 1px rgb(0 0 0 / 0.05); - --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), - 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), - 0 4px 6px -4px rgb(0 0 0 / 0.1); - --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), - 0 8px 10px -6px rgb(0 0 0 / 0.1); - --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); - --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); - --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); - --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05); - --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05); - --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15); - --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); - --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); - --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1); - --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15); --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); - --animate-spin: spin 1s linear infinite; - --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; - --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 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: 150ms; --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); ```
If you have `ripgrep` installed, you can use this command to verify that these variables are indeed not used anywhere:
```shell rg "\-\-font-serif\b" rg "\-\-color-red-50\b" rg "\-\-color-red-100\b" rg "\-\-color-red-800\b" rg "\-\-color-red-950\b" rg "\-\-color-orange-50\b" rg "\-\-color-orange-100\b" rg "\-\-color-orange-800\b" rg "\-\-color-orange-950\b" rg "\-\-color-amber-50\b" rg "\-\-color-amber-100\b" rg "\-\-color-amber-200\b" rg "\-\-color-amber-300\b" rg "\-\-color-amber-800\b" rg "\-\-color-amber-900\b" rg "\-\-color-yellow-50\b" rg "\-\-color-yellow-100\b" rg "\-\-color-yellow-200\b" rg "\-\-color-yellow-500\b" rg "\-\-color-yellow-800\b" rg "\-\-color-yellow-900\b" rg "\-\-color-lime-50\b" rg "\-\-color-lime-100\b" rg "\-\-color-lime-200\b" rg "\-\-color-lime-500\b" rg "\-\-color-lime-800\b" rg "\-\-color-lime-900\b" rg "\-\-color-green-50\b" rg "\-\-color-green-100\b" rg "\-\-color-green-200\b" rg "\-\-color-green-300\b" rg "\-\-color-green-800\b" rg "\-\-color-green-950\b" rg "\-\-color-emerald-50\b" rg "\-\-color-emerald-100\b" rg "\-\-color-emerald-200\b" rg "\-\-color-emerald-300\b" rg "\-\-color-emerald-800\b" rg "\-\-color-emerald-950\b" rg "\-\-color-teal-50\b" rg "\-\-color-teal-100\b" rg "\-\-color-teal-200\b" rg "\-\-color-teal-800\b" rg "\-\-color-teal-950\b" rg "\-\-color-cyan-50\b" rg "\-\-color-cyan-100\b" rg "\-\-color-cyan-200\b" rg "\-\-color-cyan-600\b" rg "\-\-color-cyan-800\b" rg "\-\-color-cyan-900\b" rg "\-\-color-sky-50\b" rg "\-\-color-sky-100\b" rg "\-\-color-sky-200\b" rg "\-\-color-sky-400\b" rg "\-\-color-sky-800\b" rg "\-\-color-sky-950\b" rg "\-\-color-blue-50\b" rg "\-\-color-blue-100\b" rg "\-\-color-blue-200\b" rg "\-\-color-blue-800\b" rg "\-\-color-blue-950\b" rg "\-\-color-indigo-50\b" rg "\-\-color-indigo-100\b" rg "\-\-color-indigo-800\b" rg "\-\-color-indigo-950\b" rg "\-\-color-violet-50\b" rg "\-\-color-violet-100\b" rg "\-\-color-violet-800\b" rg "\-\-color-violet-950\b" rg "\-\-color-purple-50\b" rg "\-\-color-purple-100\b" rg "\-\-color-purple-800\b" rg "\-\-color-purple-950\b" rg "\-\-color-fuchsia-50\b" rg "\-\-color-fuchsia-100\b" rg "\-\-color-fuchsia-800\b" rg "\-\-color-fuchsia-950\b" rg "\-\-color-pink-50\b" rg "\-\-color-pink-100\b" rg "\-\-color-pink-800\b" rg "\-\-color-pink-950\b" rg "\-\-color-rose-50\b" rg "\-\-color-rose-100\b" rg "\-\-color-rose-800\b" rg "\-\-color-rose-950\b" rg "\-\-color-slate-50\b" rg "\-\-color-slate-100\b" rg "\-\-color-slate-200\b" rg "\-\-color-slate-300\b" rg "\-\-color-slate-400\b" rg "\-\-color-slate-500\b" rg "\-\-color-slate-600\b" rg "\-\-color-slate-700\b" rg "\-\-color-slate-800\b" rg "\-\-color-slate-900\b" rg "\-\-color-slate-950\b" rg "\-\-color-gray-50\b" rg "\-\-color-gray-100\b" rg "\-\-color-gray-200\b" rg "\-\-color-gray-300\b" rg "\-\-color-gray-400\b" rg "\-\-color-gray-500\b" rg "\-\-color-gray-600\b" rg "\-\-color-gray-700\b" rg "\-\-color-gray-800\b" rg "\-\-color-gray-900\b" rg "\-\-color-gray-950\b" rg "\-\-color-neutral-50\b" rg "\-\-color-neutral-100\b" rg "\-\-color-neutral-200\b" rg "\-\-color-neutral-300\b" rg "\-\-color-neutral-400\b" rg "\-\-color-neutral-500\b" rg "\-\-color-neutral-600\b" rg "\-\-color-neutral-700\b" rg "\-\-color-neutral-800\b" rg "\-\-color-neutral-900\b" rg "\-\-color-neutral-950\b" rg "\-\-color-stone-50\b" rg "\-\-color-stone-100\b" rg "\-\-color-stone-200\b" rg "\-\-color-stone-300\b" rg "\-\-color-stone-400\b" rg "\-\-color-stone-500\b" rg "\-\-color-stone-600\b" rg "\-\-color-stone-700\b" rg "\-\-color-stone-800\b" rg "\-\-color-stone-900\b" rg "\-\-color-stone-950\b" rg "\-\-breakpoint-sm\b" rg "\-\-breakpoint-md\b" rg "\-\-breakpoint-lg\b" rg "\-\-breakpoint-xl\b" rg "\-\-breakpoint-2xl\b" rg "\-\-container-3xs\b" rg "\-\-container-2xs\b" rg "\-\-text-lg--line-height\b" rg "\-\-text-xl--line-height\b" rg "\-\-text-2xl--line-height\b" rg "\-\-text-3xl\b" rg "\-\-text-3xl--line-height\b" rg "\-\-text-4xl\b" rg "\-\-text-4xl--line-height\b" rg "\-\-text-5xl\b" rg "\-\-text-5xl--line-height\b" rg "\-\-text-6xl\b" rg "\-\-text-6xl--line-height\b" rg "\-\-text-7xl\b" rg "\-\-text-7xl--line-height\b" rg "\-\-text-8xl\b" rg "\-\-text-8xl--line-height\b" rg "\-\-text-9xl\b" rg "\-\-text-9xl--line-height\b" rg "\-\-font-weight-thin\b" rg "\-\-font-weight-extralight\b" rg "\-\-font-weight-light\b" rg "\-\-font-weight-extrabold\b" rg "\-\-font-weight-black\b" rg "\-\-tracking-tighter\b" rg "\-\-tracking-tight\b" rg "\-\-tracking-normal\b" rg "\-\-tracking-wide\b" rg "\-\-tracking-wider\b" rg "\-\-tracking-widest\b" rg "\-\-leading-tight\b" rg "\-\-leading-snug\b" rg "\-\-leading-normal\b" rg "\-\-leading-relaxed\b" rg "\-\-leading-loose\b" rg "\-\-radius-xs\b" rg "\-\-radius-4xl\b" rg "\-\-shadow-2xs\b" rg "\-\-shadow-xs\b" rg "\-\-shadow-sm\b" rg "\-\-shadow-md\b" rg "\-\-shadow-lg\b" rg "\-\-shadow-xl\b" rg "\-\-shadow-2xl\b" rg "\-\-inset-shadow-2xs\b" rg "\-\-inset-shadow-xs\b" rg "\-\-inset-shadow-sm\b" rg "\-\-drop-shadow-xs\b" rg "\-\-drop-shadow-sm\b" rg "\-\-drop-shadow-md\b" rg "\-\-drop-shadow-lg\b" rg "\-\-drop-shadow-xl\b" rg "\-\-drop-shadow-2xl\b" rg "\-\-animate-spin\b" rg "\-\-animate-ping\b" rg "\-\-animate-pulse\b" rg "\-\-animate-bounce\b" rg "\-\-blur-xs\b" rg "\-\-blur-sm\b" rg "\-\-blur-lg\b" rg "\-\-blur-2xl\b" rg "\-\-blur-3xl\b" rg "\-\-perspective-dramatic\b" rg "\-\-perspective-near\b" rg "\-\-perspective-normal\b" rg "\-\-perspective-midrange\b" rg "\-\-perspective-distant\b" rg "\-\-aspect-video\b" ```
The only exception I noticed is that we have this: ```css src/typography.utilities.css 10: @media (width >= theme(--breakpoint-sm)) { ``` But this is not a variable, but it's replaced at build time with the actual value, so this is not a real issue. Testing on other templates: image Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16145 --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> --- CHANGELOG.md | 5 +- crates/oxide/src/parser.rs | 59 ++- integrations/cli/index.test.ts | 61 +++ .../src/__snapshots__/index.test.ts.snap | 383 ----------------- .../@tailwindcss-postcss/src/index.test.ts | 12 +- .../src/__snapshots__/index.test.ts.snap | 391 ------------------ .../src/__snapshots__/utilities.test.ts.snap | 27 -- packages/tailwindcss/src/ast.ts | 95 ++++- .../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 | 208 ++-------- packages/tailwindcss/src/design-system.ts | 26 +- packages/tailwindcss/src/index.test.ts | 273 ++++++++++-- packages/tailwindcss/src/index.ts | 55 ++- packages/tailwindcss/src/prefix.test.ts | 23 +- packages/tailwindcss/src/theme.ts | 20 +- packages/tailwindcss/src/utilities.test.ts | 142 ++++--- packages/tailwindcss/src/value-parser.ts | 8 + packages/tailwindcss/src/variants.test.ts | 80 +--- 21 files changed, 688 insertions(+), 1409 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a233dcd54ef1..796a8669a083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ 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)) +- Only expose used `@keyframes` ([#16211](https://github.com/tailwindlabs/tailwindcss/pull/16211)) ## [4.0.4] - 2025-02-06 diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index 12791ae780ac..9cd702d477f3 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -340,13 +340,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"--") { @@ -1679,6 +1678,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!( diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 0e3aba4c4e88..2eee0413dc4f 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1253,3 +1253,64 @@ 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 { + --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 + } + `, + ) + + fs.expectFileToContain( + './dist/out.css', + css` + :root, + :host { + --color-blue-500: blue; + } + `, + ) + }, +) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index d401ace41bed..dbf73d755e12 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -4,363 +4,11 @@ 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); @@ -630,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-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..0b32f5ed7588 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -3,369 +3,9 @@ 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 { @@ -387,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/__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/ast.ts b/packages/tailwindcss/src/ast.ts index 0e9215bbec0f..559accd020cc 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -1,4 +1,7 @@ import { parseAtRule } from './css-parser' +import type { DesignSystem } from './design-system' +import { ThemeOptions } from './theme' +import { DefaultMap } from './utils/default-map' const AT_SIGN = 0x40 @@ -252,13 +255,20 @@ 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()) + let keyframes = new Set() + let usedKeyframeNames = new Set() function transform( node: AstNode, parent: Extract['nodes'], + context: Record = {}, depth = 0, ) { // Declaration @@ -266,6 +276,23 @@ export function optimizeAst(ast: AstNode[]) { if (node.property === '--tw-sort' || node.value === undefined || node.value === null) { 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) + } + + // Track used animation names + if (node.property === 'animation') { + let parts = node.value.split(/\s+/) + for (let part of parts) usedKeyframeNames.add(part) + } + parent.push(node) } @@ -275,7 +302,7 @@ export function optimizeAst(ast: AstNode[]) { 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) } @@ -286,7 +313,7 @@ export function optimizeAst(ast: AstNode[]) { 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) @@ -305,7 +332,7 @@ export function optimizeAst(ast: AstNode[]) { 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) } @@ -314,8 +341,15 @@ export function optimizeAst(ast: AstNode[]) { 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) } + + // Only track `@keyframes` that could be removed, when they were defined + // inside of a `@theme`. + if (node.name === '@keyframes' && context.theme) { + keyframes.add(copy) + } + if ( copy.nodes.length > 0 || copy.name === '@layer' || @@ -332,7 +366,7 @@ export function optimizeAst(ast: AstNode[]) { 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) } @@ -347,7 +381,7 @@ export function optimizeAst(ast: AstNode[]) { } for (let child of node.nodes) { - transform(child, parent, depth) + transform(child, parent, { ...context, ...node.context }, depth) } } @@ -364,7 +398,52 @@ export function optimizeAst(ast: AstNode[]) { 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 | ThemeOptions.USED)) { + if (declaration.property.startsWith('--animate-')) { + let parts = declaration.value!.split(/\s+/) + for (let part of parts) usedKeyframeNames.add(part) + } + + 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 + } + } + } + + // Remove unused keyframes + for (let keyframe of keyframes) { + if (!usedKeyframeNames.has(keyframe.params)) { + let idx = atRoots.indexOf(keyframe) + atRoots.splice(idx, 1) + } } return newAst.concat(atRoots) 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..4fa7ec540081 100644 --- a/packages/tailwindcss/src/css-functions.test.ts +++ b/packages/tailwindcss/src/css-functions.test.ts @@ -94,11 +94,7 @@ describe('--spacing(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing: .25rem; - } - - .foo { + ".foo { margin: 1rem; }" `) @@ -157,11 +153,7 @@ describe('--theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -197,11 +189,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -218,11 +206,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -239,11 +223,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -260,11 +240,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -281,11 +257,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -302,11 +274,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -323,11 +291,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -344,11 +308,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -365,11 +325,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .75); }" `) @@ -386,11 +342,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: color-mix(in oklab, red var(--opacity), transparent); }" `) @@ -408,11 +360,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: color-mix(in oklab, red var(--opacity, 50%), transparent); }" `) @@ -429,11 +377,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-12: 3rem; - } - - .space-on-the-left { + ".space-on-the-left { margin-left: 3rem; }" `) @@ -450,11 +394,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --spacing-2_5: .625rem; - } - - .space-on-the-left { + ".space-on-the-left { margin-left: .625rem; }" `) @@ -471,11 +411,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 +428,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --radius-lg: .5rem; - } - - .radius { + ".radius { border-radius: .5rem; }" `) @@ -514,11 +446,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --blur: 8px; - } - - .default-blur { + ".default-blur { filter: blur(8px); }" `) @@ -537,12 +465,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 +549,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .25); }" `) @@ -684,12 +603,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - --color-foo: red; - } - - .red { + ".red { color: red; }" `) @@ -707,12 +621,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 +640,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -752,11 +657,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: oklab(62.7955% .22486 .12584 / .5); }" `) @@ -773,11 +674,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --color-red-500: red; - } - - .red { + ".red { color: red; }" `) @@ -796,11 +693,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --blur: 8px; - } - - .blur { + ".blur { filter: blur(8px); }" `) @@ -859,11 +752,6 @@ describe('theme(…)', () => { .sm\\:\\[--color\\:theme\\(colors\\.red\\[500\\]\\)\\] { --color: red; } - } - - :root, :host { - --breakpoint-sm: 40rem; - --color-red-500: red; }" `) }) @@ -921,12 +809,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 +831,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 +854,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - } - - @media (width >= 48rem) { + "@media (width >= 48rem) { .red { color: red; } @@ -1001,11 +875,7 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ":root, :host { - --breakpoint-md: 48rem; - } - - @container (width > 48rem) { + "@container (width > 48rem) { .red { color: red; } @@ -1026,11 +896,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 +1053,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 +1074,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/design-system.ts b/packages/tailwindcss/src/design-system.ts index a414f1d23848..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, @@ -84,7 +104,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { }, }) - astNodes = optimizeAst(astNodes) + astNodes = optimizeAst(astNodes, designSystem) if (astNodes.length === 0 || wasInvalid) { result.push(null) @@ -159,6 +179,10 @@ export function buildDesignSystem(theme: Theme): DesignSystem { return themeValue }, + + trackUsedVariables(raw: string) { + trackUsedVariables.get(raw) + }, } return designSystem diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 497ccc034173..f7c2084d6995 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 { @@ -173,7 +172,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; } @@ -294,7 +292,6 @@ describe('@apply', () => { --color-blue-500: #3b82f6; --color-green-200: #bbf7d0; --color-green-500: #22c55e; - --breakpoint-md: 768px; --animate-spin: spin 1s linear infinite; } @@ -1150,7 +1147,6 @@ describe('Parsing themes values from CSS', () => { ).toMatchInlineSnapshot(` ":root, :host { --color-red: red; - --animate-foo: foo 1s infinite; --text-lg: 20px; } @@ -1160,12 +1156,6 @@ describe('Parsing themes values from CSS', () => { .accent-red { accent-color: var(--color-red); - } - - @keyframes foo { - to { - opacity: 1; - } }" `) }) @@ -1392,7 +1382,6 @@ describe('Parsing themes values from CSS', () => { ), ).toMatchInlineSnapshot(` ":root, :host { - --inset-shadow-sm: inset 0 2px 4px #0000000d; --inset-md: 50px; } @@ -1583,6 +1572,219 @@ 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 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( + 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('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( @@ -1798,13 +2000,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; } @@ -1818,6 +2014,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( @@ -1835,13 +2058,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 +2171,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/index.ts b/packages/tailwindcss/src/index.ts index 14bcee2fe104..07956ab421ce 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, @@ -63,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) } @@ -454,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 } @@ -488,7 +489,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,35 +524,25 @@ async function parseCss( customUtility(designSystem) } - // Output final set of theme variables at the position of the first `@theme` - // rule. + // 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()) { + for (let [key, value] of designSystem.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])) - } + let keyframesRules = designSystem.theme.getKeyframes() + for (let keyframes of keyframesRules) { + // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when + // printing. + nodes.push(atRoot([keyframes])) } - firstThemeRule.nodes = nodes + + firstThemeRule.nodes = [context({ theme: true }, nodes)] } // Replace the `@tailwind utilities` node with a context since it prints @@ -650,7 +641,7 @@ export async function compileAst( } if (!utilitiesNode) { - compiled ??= optimizeAst(ast) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -660,7 +651,11 @@ export async function compileAst( let prevSize = allValidCandidates.size for (let candidate of newRawCandidates) { if (!designSystem.invalidCandidates.has(candidate)) { - allValidCandidates.add(candidate) + if (candidate[0] === '-' && candidate[1] === '-') { + designSystem.theme.markUsedVariable(candidate) + } else { + allValidCandidates.add(candidate) + } didChange ||= allValidCandidates.size !== prevSize } } @@ -668,7 +663,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 } @@ -680,7 +675,7 @@ export async function compileAst( // CSS. This currently assumes that we only add new ast nodes and never // remove any. if (previousAstNodeCount === newNodes.length) { - compiled ??= optimizeAst(ast) + compiled ??= optimizeAst(ast, designSystem) return compiled } @@ -688,7 +683,7 @@ export async function compileAst( utilitiesNode.nodes = newNodes - compiled = optimizeAst(ast) + compiled = optimizeAst(ast, designSystem) return compiled }, } 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/theme.ts b/packages/tailwindcss/src/theme.ts index 0b0ca96c8d11..0cc80f3b82fc 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -1,11 +1,14 @@ -import type { AtRule } from './ast' -import { escape } from './utils/escape' +import { type AtRule } from './ast' +import { escape, unescape } from './utils/escape' export const enum ThemeOptions { NONE = 0, 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 @@ -106,6 +109,7 @@ export class Theme { } getOptions(key: string) { + key = unescape(this.#unprefixKey(key)) return this.values.get(key)?.options ?? ThemeOptions.NONE } @@ -123,6 +127,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) ?? [] @@ -175,6 +184,13 @@ export class Theme { return `var(${escape(this.#prefixKey(themeKey))})` } + markUsedVariable(themeKey: string) { + let key = unescape(this.#unprefixKey(themeKey)) + let value = this.values.get(key) + if (!value) return + value.options |= ThemeOptions.USED + } + resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { let themeKey = this.#resolveKey(candidateValue, themeKeys) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 4da13ed6469d..56eaca4f880c 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; } @@ -3162,10 +3161,6 @@ describe('container', () => { ).toMatchInlineSnapshot(` ":root, :host { --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; } .container { @@ -3305,10 +3300,6 @@ describe('container', () => { ).toMatchInlineSnapshot(` ":root, :host { --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; } .container { @@ -4045,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); } @@ -4173,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); } @@ -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 { @@ -16500,6 +16460,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 { @@ -17650,6 +17644,56 @@ 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; + --color-red-500: red; + } + + .example-foo { + color: var(--color-red-500); + background-color: var(--example-foo); + }" + `) + }) }) test('resolve value based on `@theme`', async () => { @@ -17710,11 +17754,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/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.: 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; }