From ea249957497c095ff8d228a3c913f608afbb9b28 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 29 Jan 2025 20:56:23 +0100 Subject: [PATCH 01/15] Only generate positive `grid-cols-*` and `grid-rows-*` utilities (#16020) This PR fixes an issue where `grid-cols-0` and `grid-rows-0` generated invalid CSS. We now ensure that the value is any positive integer (except 0). Fixes: #16012 --- CHANGELOG.md | 4 +++- packages/tailwindcss/src/utilities.test.ts | 2 ++ packages/tailwindcss/src/utilities.ts | 5 +++-- packages/tailwindcss/src/utils/infer-data-type.ts | 5 +++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c60d4490700e..e3481c52a7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) ## [4.0.1] - 2025-01-29 diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 8e40d3816317..d57bffeaabee 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -6995,6 +6995,7 @@ test('grid-cols', async () => { expect( await run([ 'grid-cols', + 'grid-cols-0', '-grid-cols-none', '-grid-cols-subgrid', 'grid-cols--12', @@ -7043,6 +7044,7 @@ test('grid-rows', async () => { expect( await run([ 'grid-rows', + 'grid-rows-0', '-grid-rows-none', '-grid-rows-subgrid', 'grid-rows--12', diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 2fb717fae418..57f39e021358 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -17,6 +17,7 @@ import { DefaultMap } from './utils/default-map' import { inferDataType, isPositiveInteger, + isStrictPositiveInteger, isValidOpacityValue, isValidSpacingMultiplier, } from './utils/infer-data-type' @@ -1752,7 +1753,7 @@ export function createUtilities(theme: Theme) { functionalUtility('grid-cols', { themeKeys: ['--grid-template-columns'], handleBareValue: ({ value }) => { - if (!isPositiveInteger(value)) return null + if (!isStrictPositiveInteger(value)) return null return `repeat(${value}, minmax(0, 1fr))` }, handle: (value) => [decl('grid-template-columns', value)], @@ -1763,7 +1764,7 @@ export function createUtilities(theme: Theme) { functionalUtility('grid-rows', { themeKeys: ['--grid-template-rows'], handleBareValue: ({ value }) => { - if (!isPositiveInteger(value)) return null + if (!isStrictPositiveInteger(value)) return null return `repeat(${value}, minmax(0, 1fr))` }, handle: (value) => [decl('grid-template-rows', value)], diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts index 56ab9c60cdde..9496986557e5 100644 --- a/packages/tailwindcss/src/utils/infer-data-type.ts +++ b/packages/tailwindcss/src/utils/infer-data-type.ts @@ -341,6 +341,11 @@ export function isPositiveInteger(value: any) { return Number.isInteger(num) && num >= 0 && String(num) === String(value) } +export function isStrictPositiveInteger(value: any) { + let num = Number(value) + return Number.isInteger(num) && num > 0 && String(num) === String(value) +} + export function isValidSpacingMultiplier(value: any) { return isMultipleOf(value, 0.25) } From 0d5e2be3125bd0d2fda7f36d2f30d078361d83c7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 30 Jan 2025 16:29:08 +0100 Subject: [PATCH 02/15] Ensure we process Tailwind CSS features when using `@reference` (#16057) This PR fixes an issue where if you only used `@reference` that we didn't process Tailwind CSS features. We have a 'quick bail check', in the PostCSS plugin to quickly bail if we _konw_ that we don't need to handle any Tailwind CSS features. This is useful in Next.js applications where every single CSS file will be passed to the PostCSS plugin. If you use custom font ins Next.js, each of those fonts will have a CSS file as well. Before we introduced `@reference`, we used `@import "tailwindcss" reference`, which passed the bail check because `@import` was being used. Now we have `@reference` which wasn't included in the list. This is now solved. Fixes: #16056 ### Test plan Added a failing test that is now failing after the fix. --- CHANGELOG.md | 1 + .../@tailwindcss-postcss/src/index.test.ts | 50 +++++++++++++++++++ packages/@tailwindcss-postcss/src/index.ts | 2 + 3 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3481c52a7ba..c13589a9d1e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) +- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057)) ## [4.0.1] - 2025-01-29 diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts index 97eedb95d183..97eee3c1c5f0 100644 --- a/packages/@tailwindcss-postcss/src/index.test.ts +++ b/packages/@tailwindcss-postcss/src/index.test.ts @@ -248,6 +248,56 @@ test('bail early when Tailwind is not used', async () => { `) }) +test('handle CSS when only using a `@reference` (we should not bail early)', async () => { + let processor = postcss([ + tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }), + ]) + + let result = await processor.process( + css` + @reference "tailwindcss/theme.css"; + + .foo { + @variant md { + bar: baz; + } + } + `, + { from: inputCssFilePath() }, + ) + + expect(result.css.trim()).toMatchInlineSnapshot(` + "@media (width >= 48rem) { + .foo { + bar: baz; + } + }" + `) +}) + +test('handle CSS when using a `@variant` using variants that do not rely on the `@theme`', async () => { + let processor = postcss([ + tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }), + ]) + + let result = await processor.process( + css` + .foo { + @variant data-is-hoverable { + bar: baz; + } + } + `, + { from: inputCssFilePath() }, + ) + + expect(result.css.trim()).toMatchInlineSnapshot(` + ".foo[data-is-hoverable] { + bar: baz; + }" + `) +}) + test('runs `Once` plugins in the right order', async () => { let before = '' let after = '' diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index 084f3671586f..d027fbd9ca67 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -77,7 +77,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { root.walkAtRules((node) => { if ( node.name === 'import' || + node.name === 'reference' || node.name === 'theme' || + node.name === 'variant' || node.name === 'config' || node.name === 'plugin' || node.name === 'apply' From 224294122b9743410dcb24d94ecf5d1468dde066 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 30 Jan 2025 16:46:29 +0100 Subject: [PATCH 03/15] Refactor gradient implementation to work around prettier/prettier#17058 (#16072) This PR fixes an issue where tools like Prettier remove important trailing commas in CSS variables, making gradients invalid. We encoded the `,` in the `--tw-gradient-position` to ensure that _if_ the `var(--tw-gradient-position)` is used, that the `,` was there. And if the variable was _not_ used that we didn't end up with a double `,,` rendering the gradient invalid. However, when running Prettier (there might be other tools that do this as well), the trailing comma in the `--tw-gradient-position` was removed which made the entire gradient invalid. E.g.: ```diff .bg-gradient-to-r { - --tw-gradient-position: to right in oklab,; + --tw-gradient-position: to right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } ``` Notice how the `,` is removed. This PR fixes that, by moving the `,` to where the variable is being used. The only side effect is that we have to guarantee that the `--tw-gradient-position` is always present. In our testing (and using UI tests) this should be the case. Related Prettier issue: https://github.com/prettier/prettier/issues/17058 Fixes: #16037 --- CHANGELOG.md | 1 + .../src/compat/legacy-utilities.test.ts | 16 +- .../src/compat/legacy-utilities.ts | 2 +- packages/tailwindcss/src/utilities.test.ts | 174 +++++++++--------- packages/tailwindcss/src/utilities.ts | 22 +-- packages/tailwindcss/tests/ui.spec.ts | 4 + 6 files changed, 112 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c13589a9d1e4..5ae4dd48f276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) - Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057)) +- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) ## [4.0.1] - 2025-01-29 diff --git a/packages/tailwindcss/src/compat/legacy-utilities.test.ts b/packages/tailwindcss/src/compat/legacy-utilities.test.ts index 4c5dd99d0816..9a828d673b78 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.test.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.test.ts @@ -22,42 +22,42 @@ test('bg-gradient-*', async () => { ), ).toMatchInlineSnapshot(` ".bg-gradient-to-b { - --tw-gradient-position: to bottom in oklab, ; + --tw-gradient-position: to bottom in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-bl { - --tw-gradient-position: to bottom left in oklab, ; + --tw-gradient-position: to bottom left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-br { - --tw-gradient-position: to bottom right in oklab, ; + --tw-gradient-position: to bottom right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-l { - --tw-gradient-position: to left in oklab, ; + --tw-gradient-position: to left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-r { - --tw-gradient-position: to right in oklab, ; + --tw-gradient-position: to right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-t { - --tw-gradient-position: to top in oklab, ; + --tw-gradient-position: to top in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-tl { - --tw-gradient-position: to top left in oklab, ; + --tw-gradient-position: to top left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-gradient-to-tr { - --tw-gradient-position: to top right in oklab, ; + --tw-gradient-position: to top right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); }" `) diff --git a/packages/tailwindcss/src/compat/legacy-utilities.ts b/packages/tailwindcss/src/compat/legacy-utilities.ts index d86b1770ab6e..5711892d7359 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.ts @@ -14,7 +14,7 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { ['tl', 'top left'], ]) { designSystem.utilities.static(`bg-gradient-to-${value}`, () => [ - decl('--tw-gradient-position', `to ${direction} in oklab,`), + decl('--tw-gradient-position', `to ${direction} in oklab`), decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), ]) } diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index d57bffeaabee..10720b167759 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -10160,257 +10160,257 @@ test('bg', async () => { } .-bg-conic-45\\/oklab { - --tw-gradient-position: from calc(45 * -1) in oklab, ; + --tw-gradient-position: from calc(45 * -1) in oklab; background-image: conic-gradient(var(--tw-gradient-stops)); } .-bg-linear-45, .-bg-linear-45\\/oklab { - --tw-gradient-position: calc(45deg * -1) in oklab, ; + --tw-gradient-position: calc(45deg * -1) in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .-bg-linear-\\[1\\.3rad\\] { - --tw-gradient-position: calc(74.4845deg * -1), ; + --tw-gradient-position: calc(74.4845deg * -1); background-image: linear-gradient(var(--tw-gradient-stops, calc(74.4845deg * -1))); } .-bg-linear-\\[125deg\\] { - --tw-gradient-position: calc(125deg * -1), ; + --tw-gradient-position: calc(125deg * -1); background-image: linear-gradient(var(--tw-gradient-stops, calc(125deg * -1))); } .bg-conic-45\\/\\[in_hsl_longer_hue\\] { - --tw-gradient-position: from 45deg in hsl longer hue, ; + --tw-gradient-position: from 45deg in hsl longer hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic-45\\/oklab { - --tw-gradient-position: from 45deg in oklab, ; + --tw-gradient-position: from 45deg in oklab; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic-45\\/shorter { - --tw-gradient-position: from 45deg in oklch shorter hue, ; + --tw-gradient-position: from 45deg in oklch shorter hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/\\[in_hsl_longer_hue\\] { - --tw-gradient-position: in hsl longer hue, ; + --tw-gradient-position: in hsl longer hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/decreasing { - --tw-gradient-position: in oklch decreasing hue, ; + --tw-gradient-position: in oklch decreasing hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/hsl { - --tw-gradient-position: in hsl, ; + --tw-gradient-position: in hsl; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/increasing { - --tw-gradient-position: in oklch increasing hue, ; + --tw-gradient-position: in oklch increasing hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/longer { - --tw-gradient-position: in oklch longer hue, ; + --tw-gradient-position: in oklch longer hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/oklab { - --tw-gradient-position: in oklab, ; + --tw-gradient-position: in oklab; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/oklch { - --tw-gradient-position: in oklch, ; + --tw-gradient-position: in oklch; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/shorter { - --tw-gradient-position: in oklch shorter hue, ; + --tw-gradient-position: in oklch shorter hue; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-conic\\/srgb { - --tw-gradient-position: in srgb, ; + --tw-gradient-position: in srgb; background-image: conic-gradient(var(--tw-gradient-stops)); } .bg-linear-45 { - --tw-gradient-position: 45deg in oklab, ; + --tw-gradient-position: 45deg in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-45\\/\\[in_hsl_longer_hue\\] { - --tw-gradient-position: 45deg in hsl longer hue, ; + --tw-gradient-position: 45deg in hsl longer hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-45\\/oklab { - --tw-gradient-position: 45deg in oklab, ; + --tw-gradient-position: 45deg in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-45\\/shorter { - --tw-gradient-position: 45deg in oklch shorter hue, ; + --tw-gradient-position: 45deg in oklch shorter hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-\\[1\\.3rad\\] { - --tw-gradient-position: 74.4845deg, ; + --tw-gradient-position: 74.4845deg; background-image: linear-gradient(var(--tw-gradient-stops, 74.4845deg)); } .bg-linear-\\[125deg\\] { - --tw-gradient-position: 125deg, ; + --tw-gradient-position: 125deg; background-image: linear-gradient(var(--tw-gradient-stops, 125deg)); } .bg-linear-\\[to_bottom\\] { - --tw-gradient-position: to bottom, ; + --tw-gradient-position: to bottom; background-image: linear-gradient(var(--tw-gradient-stops, to bottom)); } .bg-linear-to-b { - --tw-gradient-position: to bottom in oklab, ; + --tw-gradient-position: to bottom in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-bl { - --tw-gradient-position: to bottom left in oklab, ; + --tw-gradient-position: to bottom left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-br { - --tw-gradient-position: to bottom right in oklab, ; + --tw-gradient-position: to bottom right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-l { - --tw-gradient-position: to left in oklab, ; + --tw-gradient-position: to left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r { - --tw-gradient-position: to right in oklab, ; + --tw-gradient-position: to right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/\\[in_hsl_longer_hue\\] { - --tw-gradient-position: to right in hsl longer hue, ; + --tw-gradient-position: to right in hsl longer hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/\\[longer\\] { - --tw-gradient-position: to right longer, ; + --tw-gradient-position: to right longer; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/decreasing { - --tw-gradient-position: to right in oklch decreasing hue, ; + --tw-gradient-position: to right in oklch decreasing hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/hsl { - --tw-gradient-position: to right in hsl, ; + --tw-gradient-position: to right in hsl; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/increasing { - --tw-gradient-position: to right in oklch increasing hue, ; + --tw-gradient-position: to right in oklch increasing hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/longer { - --tw-gradient-position: to right in oklch longer hue, ; + --tw-gradient-position: to right in oklch longer hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/oklab { - --tw-gradient-position: to right in oklab, ; + --tw-gradient-position: to right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/oklch { - --tw-gradient-position: to right in oklch, ; + --tw-gradient-position: to right in oklch; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/shorter { - --tw-gradient-position: to right in oklch shorter hue, ; + --tw-gradient-position: to right in oklch shorter hue; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-r\\/srgb { - --tw-gradient-position: to right in srgb, ; + --tw-gradient-position: to right in srgb; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-t { - --tw-gradient-position: to top in oklab, ; + --tw-gradient-position: to top in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-tl { - --tw-gradient-position: to top left in oklab, ; + --tw-gradient-position: to top left in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-linear-to-tr { - --tw-gradient-position: to top right in oklab, ; + --tw-gradient-position: to top right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .bg-radial-\\[circle_at_center\\] { - --tw-gradient-position: circle at center, ; + --tw-gradient-position: circle at center; background-image: radial-gradient(var(--tw-gradient-stops, circle at center)); } .bg-radial\\/\\[in_hsl_longer_hue\\] { - --tw-gradient-position: in hsl longer hue, ; + --tw-gradient-position: in hsl longer hue; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/decreasing { - --tw-gradient-position: in oklch decreasing hue, ; + --tw-gradient-position: in oklch decreasing hue; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/hsl { - --tw-gradient-position: in hsl, ; + --tw-gradient-position: in hsl; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/increasing { - --tw-gradient-position: in oklch increasing hue, ; + --tw-gradient-position: in oklch increasing hue; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/longer { - --tw-gradient-position: in oklch longer hue, ; + --tw-gradient-position: in oklch longer hue; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/oklab { - --tw-gradient-position: in oklab, ; + --tw-gradient-position: in oklab; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/oklch { - --tw-gradient-position: in oklch, ; + --tw-gradient-position: in oklch; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/shorter { - --tw-gradient-position: in oklch shorter hue, ; + --tw-gradient-position: in oklch shorter hue; background-image: radial-gradient(var(--tw-gradient-stops)); } .bg-radial\\/srgb { - --tw-gradient-position: in srgb, ; + --tw-gradient-position: in srgb; background-image: radial-gradient(var(--tw-gradient-stops)); } @@ -10710,62 +10710,62 @@ test('from', async () => { .from-\\[\\#0088cc\\] { --tw-gradient-from: #08c; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-\\[\\#0088cc\\]\\/50, .from-\\[\\#0088cc\\]\\/\\[0\\.5\\], .from-\\[\\#0088cc\\]\\/\\[50\\%\\] { --tw-gradient-from: oklab(59.9824% -.06725 -.12414 / .5); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-\\[color\\:var\\(--my-color\\)\\] { --tw-gradient-from: var(--my-color); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-\\[color\\:var\\(--my-color\\)\\]\\/50, .from-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .from-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-from: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-\\[var\\(--my-color\\)\\] { --tw-gradient-from: var(--my-color); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-\\[var\\(--my-color\\)\\]\\/50, .from-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .from-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-from: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-current { --tw-gradient-from: currentColor; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-current\\/50, .from-current\\/\\[0\\.5\\], .from-current\\/\\[50\\%\\] { --tw-gradient-from: color-mix(in oklab, currentColor 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-inherit { --tw-gradient-from: inherit; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-red-500 { --tw-gradient-from: var(--color-red-500); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-red-500\\/50, .from-red-500\\/\\[0\\.5\\], .from-red-500\\/\\[50\\%\\] { --tw-gradient-from: color-mix(in oklab, var(--color-red-500) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-transparent { --tw-gradient-from: transparent; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .from-0\\% { @@ -10929,73 +10929,73 @@ test('via', async () => { .via-\\[\\#0088cc\\] { --tw-gradient-via: #08c; - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-\\[\\#0088cc\\]\\/50, .via-\\[\\#0088cc\\]\\/\\[0\\.5\\], .via-\\[\\#0088cc\\]\\/\\[50\\%\\] { --tw-gradient-via: oklab(59.9824% -.06725 -.12414 / .5); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-\\[color\\:var\\(--my-color\\)\\] { --tw-gradient-via: var(--my-color); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-\\[color\\:var\\(--my-color\\)\\]\\/50, .via-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .via-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-via: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-\\[var\\(--my-color\\)\\] { --tw-gradient-via: var(--my-color); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-\\[var\\(--my-color\\)\\]\\/50, .via-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .via-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-via: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-current { --tw-gradient-via: currentColor; - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-current\\/50, .via-current\\/\\[0\\.5\\], .via-current\\/\\[50\\%\\] { --tw-gradient-via: color-mix(in oklab, currentColor 50%, transparent); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-inherit { --tw-gradient-via: inherit; - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-red-500 { --tw-gradient-via: var(--color-red-500); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-red-500\\/50, .via-red-500\\/\\[0\\.5\\], .via-red-500\\/\\[50\\%\\] { --tw-gradient-via: color-mix(in oklab, var(--color-red-500) 50%, transparent); - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } .via-transparent { --tw-gradient-via: transparent; - --tw-gradient-via-stops: var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); + --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); } @@ -11158,62 +11158,62 @@ test('to', async () => { .to-\\[\\#0088cc\\] { --tw-gradient-to: #08c; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-\\[\\#0088cc\\]\\/50, .to-\\[\\#0088cc\\]\\/\\[0\\.5\\], .to-\\[\\#0088cc\\]\\/\\[50\\%\\] { --tw-gradient-to: oklab(59.9824% -.06725 -.12414 / .5); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-\\[color\\:var\\(--my-color\\)\\] { --tw-gradient-to: var(--my-color); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-\\[color\\:var\\(--my-color\\)\\]\\/50, .to-\\[color\\:var\\(--my-color\\)\\]\\/\\[0\\.5\\], .to-\\[color\\:var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-to: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-\\[var\\(--my-color\\)\\] { --tw-gradient-to: var(--my-color); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-\\[var\\(--my-color\\)\\]\\/50, .to-\\[var\\(--my-color\\)\\]\\/\\[0\\.5\\], .to-\\[var\\(--my-color\\)\\]\\/\\[50\\%\\] { --tw-gradient-to: color-mix(in oklab, var(--my-color) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-current { --tw-gradient-to: currentColor; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-current\\/50, .to-current\\/\\[0\\.5\\], .to-current\\/\\[50\\%\\] { --tw-gradient-to: color-mix(in oklab, currentColor 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-inherit { --tw-gradient-to: inherit; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-red-500 { --tw-gradient-to: var(--color-red-500); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-red-500\\/50, .to-red-500\\/\\[0\\.5\\], .to-red-500\\/\\[50\\%\\] { --tw-gradient-to: color-mix(in oklab, var(--color-red-500) 50%, transparent); - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-transparent { --tw-gradient-to: transparent; - --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position, ) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-0\\% { diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 57f39e021358..9f4a1039be95 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -2370,7 +2370,7 @@ export function createUtilities(theme: Theme) { value = negative ? `calc(${value} * -1)` : `${value}` return [ - decl('--tw-gradient-position', `${value},`), + decl('--tw-gradient-position', value), decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), ] } @@ -2378,7 +2378,7 @@ export function createUtilities(theme: Theme) { if (negative) return return [ - decl('--tw-gradient-position', `${value},`), + decl('--tw-gradient-position', value), decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), ] } @@ -2398,7 +2398,7 @@ export function createUtilities(theme: Theme) { let interpolationMethod = resolveInterpolationModifier(candidate.modifier) return [ - decl('--tw-gradient-position', `${value} ${interpolationMethod},`), + decl('--tw-gradient-position', `${value} ${interpolationMethod}`), decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), ] } @@ -2425,7 +2425,7 @@ export function createUtilities(theme: Theme) { if (candidate.modifier) return let value = candidate.value.value return [ - decl('--tw-gradient-position', `${value},`), + decl('--tw-gradient-position', value), decl('background-image', `conic-gradient(var(--tw-gradient-stops,${value}))`), ] } @@ -2434,7 +2434,7 @@ export function createUtilities(theme: Theme) { if (!candidate.value) { return [ - decl('--tw-gradient-position', `${interpolationMethod},`), + decl('--tw-gradient-position', interpolationMethod), decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), ] } @@ -2446,7 +2446,7 @@ export function createUtilities(theme: Theme) { value = negative ? `calc(${value} * -1)` : `${value}deg` return [ - decl('--tw-gradient-position', `from ${value} ${interpolationMethod},`), + decl('--tw-gradient-position', `from ${value} ${interpolationMethod}`), decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), ] } @@ -2471,7 +2471,7 @@ export function createUtilities(theme: Theme) { if (!candidate.value) { let interpolationMethod = resolveInterpolationModifier(candidate.modifier) return [ - decl('--tw-gradient-position', `${interpolationMethod},`), + decl('--tw-gradient-position', interpolationMethod), decl('background-image', `radial-gradient(var(--tw-gradient-stops))`), ] } @@ -2480,7 +2480,7 @@ export function createUtilities(theme: Theme) { if (candidate.modifier) return let value = candidate.value.value return [ - decl('--tw-gradient-position', `${value},`), + decl('--tw-gradient-position', value), decl('background-image', `radial-gradient(var(--tw-gradient-stops,${value}))`), ] } @@ -2655,7 +2655,7 @@ export function createUtilities(theme: Theme) { decl('--tw-gradient-from', value), decl( '--tw-gradient-stops', - 'var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))', + 'var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))', ), ], position: (value) => [gradientStopProperties(), decl('--tw-gradient-from-position', value)], @@ -2668,7 +2668,7 @@ export function createUtilities(theme: Theme) { decl('--tw-gradient-via', value), decl( '--tw-gradient-via-stops', - 'var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)', + 'var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)', ), decl('--tw-gradient-stops', 'var(--tw-gradient-via-stops)'), ], @@ -2681,7 +2681,7 @@ export function createUtilities(theme: Theme) { decl('--tw-gradient-to', value), decl( '--tw-gradient-stops', - 'var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))', + 'var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))', ), ], position: (value) => [gradientStopProperties(), decl('--tw-gradient-to-position', value)], diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index 2595d9982b93..53d8a71c9a48 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -157,6 +157,10 @@ for (let [classes, expected] of [ 'bg-radial-[at_0%_0%,var(--color-red),transparent]', 'radial-gradient(at 0% 0%, rgb(255, 0, 0), rgba(0, 0, 0, 0))', ], + [ + 'bg-radial-[at_center] from-red to-green', + 'radial-gradient(rgb(255, 0, 0) 0%, rgb(0, 255, 0) 100%)', + ], ]) { test(`radial gradient, "${classes}"`, async ({ page }) => { let { getPropertyValue } = await render( From c09bb5e2562293c14248ad8fbad5bdb5b9b22058 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 30 Jan 2025 16:58:20 +0100 Subject: [PATCH 04/15] Fix Vite issues with SolidStart (#16052) Fixes #16045 This PR fixes two Vite issues found with SolidStart: - SolidStart seems to emit an empty HTML chunk (where the content is literally just `/`) with _no pathname_. Since we use the path to generate an `id` for HTML chunks, this would currently cause a crash. This was reported in #16045 - While testing the fix for the above, we also found that hot reloading was not working in SolidStart since `4.0.0-alpha.22`. After doing some bisecting we found that this is happening as SolidStart has the same module ID in different servers and we were invalidating the root when we shouldn't. After trying to restructure this code so that it only cleans up the root when it is _no longer part of any server_, we noticed some other compatibility issues with Nuxt and SvelteKit. It seems that the safest bet is to no longer update a root at all during rebuilds in the SSR step. This makes `invalidateAllRoots` a function that only notifiers the servers about a change which is conceptually also less confusing. ## Test plan - Added an integration test for SolidStart dev mode - Manually tested the dev mode across all Vite based templates in https://github.com/philipp-spiess/tailwindcss-playgrounds: Astro, Nuxt, Remix, Solid, SvelteKit, and Vue. --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 2 + integrations/vite/solidstart.test.ts | 108 ++++++++++++++++++++++++ packages/@tailwindcss-vite/src/index.ts | 28 +++--- 3 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 integrations/vite/solidstart.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae4dd48f276..82bb2a70551c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) - Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057)) - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) +- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) +- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/vite/solidstart.test.ts b/integrations/vite/solidstart.test.ts new file mode 100644 index 000000000000..bbe9ccc8868d --- /dev/null +++ b/integrations/vite/solidstart.test.ts @@ -0,0 +1,108 @@ +import { candidate, css, fetchStyles, js, json, retryAssertion, test, ts } from '../utils' + +const WORKSPACE = { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@solidjs/start": "^1", + "solid-js": "^1", + "vinxi": "^0", + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + } + } + `, + 'jsconfig.json': json` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js" + } + } + `, + 'app.config.js': ts` + import { defineConfig } from '@solidjs/start/config' + import tailwindcss from '@tailwindcss/vite' + + export default defineConfig({ + vite: { + plugins: [tailwindcss()], + }, + }) + `, + 'src/entry-server.jsx': js` + // @refresh reload + import { createHandler, StartServer } from '@solidjs/start/server' + + export default createHandler(() => ( + ( + + {assets} + +
{children}
+ {scripts} + + + )} + /> + )) + `, + 'src/entry-client.jsx': js` + // @refresh reload + import { mount, StartClient } from '@solidjs/start/client' + + mount(() => , document.getElementById('app')) + `, + 'src/app.jsx': js` + import './app.css' + export default function App() { + return

Hello world!

+ } + `, + 'src/app.css': css`@import 'tailwindcss';`, +} + +test( + 'dev mode', + { + fs: WORKSPACE, + }, + async ({ fs, spawn, expect }) => { + let process = await spawn('pnpm vinxi dev', { + env: { + TEST: 'false', // VERY IMPORTANT OTHERWISE YOU WON'T GET OUTPUT + NODE_ENV: 'development', + }, + }) + + let url = '' + await process.onStdout((m) => { + let match = /Local:\s*(http.*)\//.exec(m) + if (match) url = match[1] + return Boolean(url) + }) + + await retryAssertion(async () => { + let css = await fetchStyles(url) + expect(css).toContain(candidate`underline`) + }) + + await retryAssertion(async () => { + await fs.write( + 'src/app.jsx', + js` + import './app.css' + export default function App() { + return

Hello world!

+ } + `, + ) + + let css = await fetchStyles(url) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + }) + }, +) diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index c8976c0276da..86d5d6142d5c 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -63,7 +63,7 @@ export default function tailwindcss(): Plugin[] { ) }) - function scanFile(id: string, content: string, extension: string, isSSR: boolean) { + function scanFile(id: string, content: string, extension: string) { for (let dependency of IGNORED_DEPENDENCIES) { // We validated that Vite IDs always use posix style path separators, even on Windows. // In dev build, Vite precompiles dependencies @@ -83,26 +83,16 @@ export default function tailwindcss(): Plugin[] { } if (updated) { - invalidateAllRoots(isSSR) + invalidateAllRoots() } } - function invalidateAllRoots(isSSR: boolean) { + function invalidateAllRoots() { for (let server of servers) { let updates: Update[] = [] - for (let [id, root] of roots.entries()) { + for (let [id] of roots.entries()) { let module = server.moduleGraph.getModuleById(id) - if (!module) { - // Note: Removing this during SSR is not safe and will produce - // inconsistent results based on the timing of the removal and - // the order / timing of transforms. - if (!isSSR) { - // It is safe to remove the item here since we're iterating on a copy - // of the keys. - roots.delete(id) - } - continue - } + if (!module) continue roots.get(id).requiresRebuild = false server.moduleGraph.invalidateModule(module) @@ -113,7 +103,6 @@ export default function tailwindcss(): Plugin[] { timestamp: Date.now(), }) } - if (updates.length > 0) { server.hot.send({ type: 'update', updates }) } @@ -210,12 +199,15 @@ export default function tailwindcss(): Plugin[] { // Scan all non-CSS files for candidates transformIndexHtml(html, { path }) { - scanFile(path, html, 'html', isSSR) + // SolidStart emits HTML chunks with an undefined path and the html content of `\`. + if (!path) return + + scanFile(path, html, 'html') }, transform(src, id, options) { let extension = getExtension(id) if (isPotentialCssRootFile(id)) return - scanFile(id, src, extension, options?.ssr ?? false) + scanFile(id, src, extension) }, }, From 0589d7d38600ebe6d2bb70fc8b1c68d42f769080 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:22:45 +0100 Subject: [PATCH 05/15] =?UTF-8?q?Update=20@playwright/test=201.49.1=20?= =?UTF-8?q?=E2=86=92=201.50.0=20(minor)=20(#16091)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- pnpm-lock.yaml | 46 ++++++++++++++++++---------------------------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 047f461c5200..6ca67e553fad 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.49.1", + "@playwright/test": "^1.50.0", "@types/node": "catalog:", "postcss": "8.5.1", "postcss-import": "^16.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d70043b4ac5..da5335af5802 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: .: devDependencies: '@playwright/test': - specifier: ^1.49.1 - version: 1.49.1 + specifier: ^1.50.0 + version: 1.50.0 '@types/node': specifier: 'catalog:' version: 20.14.13 @@ -406,7 +406,7 @@ importers: version: 3.3.3 next: specifier: 15.1.4 - version: 15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -440,7 +440,7 @@ importers: dependencies: next: specifier: 15.1.4 - version: 15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -1612,8 +1612,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.49.1': - resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + '@playwright/test@1.50.0': + resolution: {integrity: sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==} engines: {node: '>=18'} hasBin: true @@ -3301,13 +3301,13 @@ packages: pkg-types@1.3.0: resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} - playwright-core@1.49.1: - resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + playwright-core@1.50.0: + resolution: {integrity: sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==} engines: {node: '>=18'} hasBin: true - playwright@1.49.1: - resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + playwright@1.50.0: + resolution: {integrity: sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==} engines: {node: '>=18'} hasBin: true @@ -3396,10 +3396,6 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.1: resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} engines: {node: ^10 || ^12 || >=14} @@ -4792,9 +4788,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.49.1': + '@playwright/test@1.50.0': dependencies: - playwright: 1.49.1 + playwright: 1.50.0 '@rollup/rollup-android-arm-eabi@4.20.0': optional: true @@ -6632,7 +6628,7 @@ snapshots: natural-compare@1.4.0: {} - next@15.1.4(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.1.4(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.1.4 '@swc/counter': 0.1.3 @@ -6652,7 +6648,7 @@ snapshots: '@next/swc-linux-x64-musl': 15.1.4 '@next/swc-win32-arm64-msvc': 15.1.4 '@next/swc-win32-x64-msvc': 15.1.4 - '@playwright/test': 1.49.1 + '@playwright/test': 1.50.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -6798,11 +6794,11 @@ snapshots: mlly: 1.7.3 pathe: 1.1.2 - playwright-core@1.49.1: {} + playwright-core@1.50.0: {} - playwright@1.49.1: + playwright@1.50.0: dependencies: - playwright-core: 1.49.1 + playwright-core: 1.50.0 optionalDependencies: fsevents: 2.3.2 @@ -6890,12 +6886,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.4.49: - dependencies: - nanoid: 3.3.7 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.1: dependencies: nanoid: 3.3.8 @@ -7532,7 +7522,7 @@ snapshots: vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.1(patch_hash=gkqcezdn4goium3e3s43dhy4by))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0): dependencies: esbuild: 0.24.0 - postcss: 8.4.49 + postcss: 8.5.1 rollup: 4.27.4 optionalDependencies: '@types/node': 20.14.13 From d85e9cfcab27f6e42a7e8a791979e072015790a3 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:01:13 +0000 Subject: [PATCH 06/15] =?UTF-8?q?Update=20turbo=202.3.3=20=E2=86=92=202.3.?= =?UTF-8?q?4=20(patch)=20(#16084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request. ### What changed? #### ✳️ turbo (2.3.3 → 2.3.4) · [Repo](https://github.com/turborepo/turbo) Sorry, we couldn't find anything useful about this release. --- ![Depfu Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg) [Depfu](https://depfu.com) will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with `@depfu rebase`.
All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 58 +++++++++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 6ca67e553fad..4266843fffef 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "prettier-plugin-embed": "^0.4.15", "prettier-plugin-organize-imports": "^4.0.0", "tsup": "^8.2.4", - "turbo": "^2.3.3", + "turbo": "^2.3.4", "typescript": "^5.5.4", "vitest": "^2.0.5" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da5335af5802..4f4852d1daa2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,8 +53,8 @@ importers: specifier: ^8.2.4 version: 8.2.4(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0) turbo: - specifier: ^2.3.3 - version: 2.3.3 + specifier: ^2.3.4 + version: 2.3.4 typescript: specifier: ^5.5.4 version: 5.5.4 @@ -3791,38 +3791,38 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.3.3: - resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==} + turbo-darwin-64@2.3.4: + resolution: {integrity: sha512-uOi/cUIGQI7uakZygH+cZQ5D4w+aMLlVCN2KTGot+cmefatps2ZmRRufuHrEM0Rl63opdKD8/JIu+54s25qkfg==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.3.3: - resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==} + turbo-darwin-arm64@2.3.4: + resolution: {integrity: sha512-IIM1Lq5R+EGMtM1YFGl4x8Xkr0MWb4HvyU8N4LNoQ1Be5aycrOE+VVfH+cDg/Q4csn+8bxCOxhRp5KmUflrVTQ==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.3.3: - resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==} + turbo-linux-64@2.3.4: + resolution: {integrity: sha512-1aD2EfR7NfjFXNH3mYU5gybLJEFi2IGOoKwoPLchAFRQ6OEJQj201/oNo9CDL75IIrQo64/NpEgVyZtoPlfhfA==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.3.3: - resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==} + turbo-linux-arm64@2.3.4: + resolution: {integrity: sha512-MxTpdKwxCaA5IlybPxgGLu54fT2svdqTIxRd0TQmpRJIjM0s4kbM+7YiLk0mOh6dGqlWPUsxz/A0Mkn8Xr5o7Q==} cpu: [arm64] os: [linux] - turbo-windows-64@2.3.3: - resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==} + turbo-windows-64@2.3.4: + resolution: {integrity: sha512-yyCrWqcRGu1AOOlrYzRnizEtdkqi+qKP0MW9dbk9OsMDXaOI5jlWtTY/AtWMkLw/czVJ7yS9Ex1vi9DB6YsFvw==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.3.3: - resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==} + turbo-windows-arm64@2.3.4: + resolution: {integrity: sha512-PggC3qH+njPfn1PDVwKrQvvQby8X09ufbqZ2Ha4uSu+5TvPorHHkAbZVHKYj2Y+tvVzxRzi4Sv6NdHXBS9Be5w==} cpu: [arm64] os: [win32] - turbo@2.3.3: - resolution: {integrity: sha512-DUHWQAcC8BTiUZDRzAYGvpSpGLiaOQPfYXlCieQbwUvmml/LRGIe3raKdrOPOoiX0DYlzxs2nH6BoWJoZrj8hA==} + turbo@2.3.4: + resolution: {integrity: sha512-1kiLO5C0Okh5ay1DbHsxkPsw9Sjsbjzm6cF85CpWjR0BIyBFNDbKqtUyqGADRS1dbbZoQanJZVj4MS5kk8J42Q==} hasBin: true type-check@0.4.0: @@ -7378,32 +7378,32 @@ snapshots: fsevents: 2.3.3 optional: true - turbo-darwin-64@2.3.3: + turbo-darwin-64@2.3.4: optional: true - turbo-darwin-arm64@2.3.3: + turbo-darwin-arm64@2.3.4: optional: true - turbo-linux-64@2.3.3: + turbo-linux-64@2.3.4: optional: true - turbo-linux-arm64@2.3.3: + turbo-linux-arm64@2.3.4: optional: true - turbo-windows-64@2.3.3: + turbo-windows-64@2.3.4: optional: true - turbo-windows-arm64@2.3.3: + turbo-windows-arm64@2.3.4: optional: true - turbo@2.3.3: + turbo@2.3.4: optionalDependencies: - turbo-darwin-64: 2.3.3 - turbo-darwin-arm64: 2.3.3 - turbo-linux-64: 2.3.3 - turbo-linux-arm64: 2.3.3 - turbo-windows-64: 2.3.3 - turbo-windows-arm64: 2.3.3 + turbo-darwin-64: 2.3.4 + turbo-darwin-arm64: 2.3.4 + turbo-linux-64: 2.3.4 + turbo-linux-arm64: 2.3.4 + turbo-windows-64: 2.3.4 + turbo-windows-arm64: 2.3.4 type-check@0.4.0: dependencies: From 88c890615a529cc8f7f253d75e25bd19bcf7e306 Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Fri, 31 Jan 2025 18:44:30 +0700 Subject: [PATCH 07/15] Prevent modifying CSS variables in plugins (#16103) closes #16100 --------- Co-authored-by: Robin Malfait Co-authored-by: Philipp Spiess --- CHANGELOG.md | 1 + .../tailwindcss/src/compat/plugin-api.test.ts | 32 +++++++++++++++++++ packages/tailwindcss/src/compat/plugin-api.ts | 13 +++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82bb2a70551c..7a95136a0e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) +- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) ## [4.0.1] - 2025-01-29 diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 539c04a3c66c..4502118f458f 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -1534,6 +1534,38 @@ describe('addBase', () => { " `) }) + + test('does not modify CSS variables', async () => { + let input = css` + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: plugin(function ({ addBase }) { + addBase({ + ':root': { + '--PascalCase': '1', + '--camelCase': '1', + '--UPPERCASE': '1', + }, + }) + }), + base: '/root', + }), + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { + :root { + --PascalCase: 1; + --camelCase: 1; + --UPPERCASE: 1; + } + } + " + `) + }) }) describe('addVariant', () => { diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index 13673280769b..8db4d68973fb 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -499,15 +499,18 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { for (let [name, value] of entries) { if (typeof value !== 'object') { - if (!name.startsWith('--') && value === '@slot') { - ast.push(rule(name, [atRule('@slot')])) - } else { + if (!name.startsWith('--')) { + if (value === '@slot') { + ast.push(rule(name, [atRule('@slot')])) + continue + } + // Convert camelCase to kebab-case: // https://github.com/postcss/postcss-js/blob/b3db658b932b42f6ac14ca0b1d50f50c4569805b/parser.js#L30-L35 name = name.replace(/([A-Z])/g, '-$1').toLowerCase() - - ast.push(decl(name, String(value))) } + + ast.push(decl(name, String(value))) } else if (Array.isArray(value)) { for (let item of value) { if (typeof item === 'string') { From 3aa0e494bf9b913a8e6f461fafe74e5a5c50e5e9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 31 Jan 2025 15:13:17 +0100 Subject: [PATCH 08/15] Do not emit `@keyframes` in `@theme reference` (#16120) This PR fixes na issue where `@keyframes` were emitted if they wre in a `@theme reference` and anothe `@theme {}` (that is not a reference) was present. E.g.: ```css @reference "tailwindcss"; @theme { /* ... */ } ``` Produces: ```css :root, :host { } @keyframes spin { to { transform: rotate(360deg); } } @keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } } @keyframes pulse { 50% { opacity: 0.5; } } @keyframes bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: none; animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } } ``` With this PR, the produced CSS looks like this instead: ```css :root, :host { } ``` Note: the empty `:root, :host` will be solved in a subsequent PR. ### Test plan Added some unit tests, and a dedicated integration test. --- CHANGELOG.md | 1 + integrations/cli/index.test.ts | 62 +++++++++++++++++++++++ packages/tailwindcss/src/index.test.ts | 69 ++++++++++++++++++++++++++ packages/tailwindcss/src/index.ts | 6 +++ 4 files changed, 138 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a95136a0e91..361db062e60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) +- Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 60fc03e421f6..2e6b9ad03549 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1196,3 +1196,65 @@ test( `) }, ) + +test( + '@theme reference should never emit values', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'src/index.css': css` + @reference "tailwindcss"; + + .keep-me { + color: red; + } + `, + }, + }, + async ({ fs, spawn, expect }) => { + let process = await spawn( + `pnpm tailwindcss --input src/index.css --output dist/out.css --watch`, + ) + await process.onStderr((m) => m.includes('Done in')) + + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + .keep-me { + color: red; + } + " + `) + + await fs.write( + './src/index.css', + css` + @reference "tailwindcss"; + + /* Not a reference! */ + @theme { + --color-pink: pink; + } + + .keep-me { + color: red; + } + `, + ) + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + .keep-me { + color: red; + } + " + `) + }, +) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 8fba63cfccef..9f7962916d90 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1530,6 +1530,75 @@ describe('Parsing themes values from CSS', () => { `) }) + test('`@keyframes` added in `@theme reference` should not be emitted', async () => { + return expect( + await compileCss( + css` + @theme reference { + --animate-foo: foo 1s infinite; + + @keyframes foo { + 0%, + 100% { + color: red; + } + 50% { + color: blue; + } + } + } + @tailwind utilities; + `, + ['animate-foo'], + ), + ).toMatchInlineSnapshot(` + ".animate-foo { + animation: var(--animate-foo); + }" + `) + }) + + test('`@keyframes` added in `@theme reference` should not be emitted, even if another `@theme` block exists', async () => { + return expect( + await compileCss( + css` + @theme reference { + --animate-foo: foo 1s infinite; + + @keyframes foo { + 0%, + 100% { + color: red; + } + 50% { + color: blue; + } + } + } + + @theme { + --color-pink: pink; + } + + @tailwind utilities; + `, + ['bg-pink', 'animate-foo'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --color-pink: pink; + } + + .animate-foo { + animation: var(--animate-foo); + } + + .bg-pink { + background-color: var(--color-pink); + }" + `) + }) + test('theme values added as reference that override existing theme value suppress the output of the original theme value as a variable', async () => { expect( await compileCss( diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index df6e09f90649..5947e73b810c 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -454,6 +454,12 @@ async function parseCss( // Collect `@keyframes` rules to re-insert with theme variables later, // since the `@theme` rule itself will be removed. if (child.kind === 'at-rule' && child.name === '@keyframes') { + // Do not track/emit `@keyframes`, if they are part of a `@theme reference`. + if (themeOptions & ThemeOptions.REFERENCE) { + replaceWith([]) + return WalkAction.Skip + } + theme.addKeyframes(child) replaceWith([]) return WalkAction.Skip From 60e61950b9bb62d9290f2a9df3330f2e36fc3b8e Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 31 Jan 2025 15:20:18 +0100 Subject: [PATCH 09/15] Ensure escaped theme variables are handled correctly (#16064) This PR ensures that escaped theme variables are properly handled. We do this by moving the `escape`/`unescape` responsibility back into the main tailwindcss entrypoint that reads and writes from the CSS and making sure that _all internal state of the `Theme` class are unescaped classes. However, due to us accidentally shipping the part where a dot in the theme variable would translate to an underscore in CSS already, this logic is going to stay as-is for now. Here's an example test that visualizes the new changes: ```ts expect( await compileCss( css` @theme { --spacing-*: initial; --spacing-1\.5: 2.5rem; --spacing-foo\/bar: 3rem; } @tailwind utilities; `, ['m-1.5', 'm-foo/bar'], ), ).toMatchInlineSnapshot(` ":root, :host { --spacing-1\.5: 2.5rem; --spacing-foo\\/bar: 3rem; } .m-1\\.5 { margin: var(--spacing-1\.5); } .m-foo\\/bar { margin: var(--spacing-foo\\/bar); }" `) ``` ## Test plan - Added a unit test - Ensure this works end-to-end using the Vite playground: Screenshot 2025-01-30 at 14 51 05 --- CHANGELOG.md | 1 + .../src/compat/apply-config-to-theme.ts | 3 +- packages/tailwindcss/src/index.test.ts | 43 +++++++++++++++++++ packages/tailwindcss/src/index.ts | 5 ++- packages/tailwindcss/src/theme.ts | 23 ++++++---- 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 361db062e60b..60a55a08131c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) +- Ensure escaped theme variables are handled correctly ([#16064](https://github.com/tailwindlabs/tailwindcss/pull/16064)) - Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057)) - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts index 3f18711c7f16..c57e4308fcb7 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts @@ -1,6 +1,5 @@ import type { DesignSystem } from '../design-system' import { ThemeOptions } from '../theme' -import { escape } from '../utils/escape' import type { ResolvedConfig } from './config/types' function resolveThemeValue(value: unknown, subValue: string | null = null): string | null { @@ -55,7 +54,7 @@ export function applyConfigToTheme( if (!name) continue designSystem.theme.add( - `--${escape(name)}`, + `--${name}`, '' + value, ThemeOptions.INLINE | ThemeOptions.REFERENCE | ThemeOptions.DEFAULT, ) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 9f7962916d90..ec4671e97268 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -152,6 +152,49 @@ describe('compiling CSS', () => { `) }) + test('unescapes theme variables and handles dots as underscore', async () => { + expect( + await compileCss( + css` + @theme { + --spacing-*: initial; + --spacing-1\.5: 1.5px; + --spacing-2_5: 2.5px; + --spacing-3\.5: 3.5px; + --spacing-3_5: 3.5px; + --spacing-foo\/bar: 3rem; + } + @tailwind utilities; + `, + ['m-1.5', 'm-2.5', 'm-2_5', 'm-3.5', 'm-foo/bar'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --spacing-1\\.5: 1.5px; + --spacing-2_5: 2.5px; + --spacing-3\\.5: 3.5px; + --spacing-3_5: 3.5px; + --spacing-foo\\/bar: 3rem; + } + + .m-1\\.5 { + margin: var(--spacing-1\\.5); + } + + .m-2\\.5, .m-2_5 { + margin: var(--spacing-2_5); + } + + .m-3\\.5 { + margin: var(--spacing-3\\.5); + } + + .m-foo\\/bar { + margin: var(--spacing-foo\\/bar); + }" + `) + }) + test('adds vendor prefixes', async () => { expect( await compileCss( diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 5947e73b810c..d594397ede6e 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -27,6 +27,7 @@ import * as CSS from './css-parser' import { buildDesignSystem, type DesignSystem } from './design-system' import { Theme, ThemeOptions } from './theme' import { createCssUtility } from './utilities' +import { escape, unescape } from './utils/escape' import { segment } from './utils/segment' import { compoundsForSelectors, IS_VALID_VARIANT_NAME } from './variants' export type Config = UserConfig @@ -467,7 +468,7 @@ async function parseCss( if (child.kind === 'comment') return if (child.kind === 'declaration' && child.property.startsWith('--')) { - theme.add(child.property, child.value ?? '', themeOptions) + theme.add(unescape(child.property), child.value ?? '', themeOptions) return } @@ -526,7 +527,7 @@ async function parseCss( for (let [key, value] of theme.entries()) { if (value.options & ThemeOptions.REFERENCE) continue - nodes.push(decl(key, value.value)) + nodes.push(decl(escape(key), value.value)) } let keyframesRules = theme.getKeyframes() diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index a2d3c205fa4a..0b0ca96c8d11 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -41,10 +41,6 @@ export class Theme { ) {} add(key: string, value: string, options = ThemeOptions.NONE): void { - if (key.endsWith('\\*')) { - key = key.slice(0, -2) + '*' - } - if (key.endsWith('-*')) { if (value !== 'initial') { throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``) @@ -149,11 +145,20 @@ export class Theme { #resolveKey(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { for (let namespace of themeKeys) { let themeKey = - candidateValue !== null - ? (escape(`${namespace}-${candidateValue.replaceAll('.', '_')}`) as ThemeKey) - : namespace + candidateValue !== null ? (`${namespace}-${candidateValue}` as ThemeKey) : namespace + + if (!this.values.has(themeKey)) { + // If the exact theme key is not found, we might be trying to resolve a key containing a dot + // that was registered with an underscore instead: + if (candidateValue !== null && candidateValue.includes('.')) { + themeKey = `${namespace}-${candidateValue.replaceAll('.', '_')}` as ThemeKey + + if (!this.values.has(themeKey)) continue + } else { + continue + } + } - if (!this.values.has(themeKey)) continue if (isIgnoredThemeKey(themeKey, namespace)) continue return themeKey @@ -167,7 +172,7 @@ export class Theme { return null } - return `var(${this.#prefixKey(themeKey)})` + return `var(${escape(this.#prefixKey(themeKey))})` } resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null { From deb33a93abbd94fd40fd2471f47df2e075a2107c Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 31 Jan 2025 15:23:32 +0100 Subject: [PATCH 10/15] Vite: Don't rebase urls that appear to be aliases (#16078) Closes #16039 This PR changes our URL rebasing logic used with Vite so that it does not rebase URLs that look like common alias paths (e.g. urls starting in `~`, `@` or `#`, etc.). Unfortunately this is only an approximation and you can configure an alias for a path that starts with a regular alphabetical character (e.g. `foo` => `./my/foo`) so this isn't a perfect fix, however in practice most aliases will be prefixed with a symbol to make it clear that it's an alias anyways. One alternative we have considered is to only rebase URLs that we know are relative (so they need to start with a `.`). This, however, will break common CSS use cases where urls are loaded like this: ```css background: image-set( url('image1.jpg') 1x, url('image2.jpg') 2x ); ``` So making this change felt like we only trade one GitHub issue for another one. In a more ideal scenario we try to resolve the URL with the Vite resolver (we have to run the resolver and can't rely on the `resolve` setting alone due to packages like [`vite-tsconfig-paths`](https://www.npmjs.com/package/vite-tsconfig-paths)), however even then we can have relative paths being resolvable to different files based on wether they were rebased or not (e.g. when an image with the same filename exists in two different paths). So ultimately we settled on extending the already existing blocklist (which we have taken from the Vite implementation) for now. ## Test plan - Added unit test and it was tested with the Vite playground. --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + packages/@tailwindcss-node/src/urls.test.ts | 26 +++++++++++++++++++++ packages/@tailwindcss-node/src/urls.ts | 7 ++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a55a08131c..77bdc10f48bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) +- Vite: Don't rebase urls that appear to be aliases ([#16078](https://github.com/tailwindlabs/tailwindcss/pull/16078)) - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) diff --git a/packages/@tailwindcss-node/src/urls.test.ts b/packages/@tailwindcss-node/src/urls.test.ts index 3378e45edbdc..16ba352a7f66 100644 --- a/packages/@tailwindcss-node/src/urls.test.ts +++ b/packages/@tailwindcss-node/src/urls.test.ts @@ -24,6 +24,20 @@ test('URLs can be rewritten', async () => { background: url('/image.jpg'); background: url("/image.jpg"); + /* Potentially Vite-aliased URLs: ignored */ + background: url(~/image.jpg); + background: url(~/foo/image.jpg); + background: url('~/image.jpg'); + background: url("~/image.jpg"); + background: url(#/image.jpg); + background: url(#/foo/image.jpg); + background: url('#/image.jpg'); + background: url("#/image.jpg"); + background: url(@/image.jpg); + background: url(@/foo/image.jpg); + background: url('@/image.jpg'); + background: url("@/image.jpg"); + /* External URL: ignored */ background: url(http://example.com/image.jpg); background: url('http://example.com/image.jpg'); @@ -109,6 +123,18 @@ test('URLs can be rewritten', async () => { background: url(/foo/image.jpg); background: url('/image.jpg'); background: url("/image.jpg"); + background: url(~/image.jpg); + background: url(~/foo/image.jpg); + background: url('~/image.jpg'); + background: url("~/image.jpg"); + background: url(#/image.jpg); + background: url(#/foo/image.jpg); + background: url('#/image.jpg'); + background: url("#/image.jpg"); + background: url(@/image.jpg); + background: url(@/foo/image.jpg); + background: url('@/image.jpg'); + background: url("@/image.jpg"); background: url(http://example.com/image.jpg); background: url('http://example.com/image.jpg'); background: url("http://example.com/image.jpg"); diff --git a/packages/@tailwindcss-node/src/urls.ts b/packages/@tailwindcss-node/src/urls.ts index c4d56deb7e60..e35b9d280a06 100644 --- a/packages/@tailwindcss-node/src/urls.ts +++ b/packages/@tailwindcss-node/src/urls.ts @@ -149,9 +149,12 @@ async function doUrlReplace( return `${funcName}(${wrap}${newUrl}${wrap})` } -function skipUrlReplacer(rawUrl: string) { +function skipUrlReplacer(rawUrl: string, aliases?: string[]) { return ( - isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl[0] === '#' || functionCallRE.test(rawUrl) + isExternalUrl(rawUrl) || + isDataUrl(rawUrl) || + !rawUrl[0].match(/[\.a-zA-Z0-9_]/) || + functionCallRE.test(rawUrl) ) } From 95722020fe91d3b28737487e17c0fcbc6ac8a4b6 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 31 Jan 2025 15:26:45 +0100 Subject: [PATCH 11/15] Vite: Transform ` + + + `, + }, + }, + async ({ fs, exec, expect }) => { + await exec('pnpm vite build') + + expect(await fs.dumpFiles('dist/*.html')).toMatchInlineSnapshot(` + " + --- dist/index.html --- + + + +
+ + + + " + `) + }, +) diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 86d5d6142d5c..c7cc111fad05 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -8,6 +8,7 @@ import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite const DEBUG = env.DEBUG const SPECIAL_QUERY_RE = /[?&](raw|url)\b/ +const INLINE_STYLE_ID_RE = /[?&]index\=\d+\.css$/ const IGNORED_DEPENDENCIES = ['tailwind-merge'] @@ -312,7 +313,7 @@ function isPotentialCssRootFile(id: string) { if (id.includes('/.vite/')) return let extension = getExtension(id) let isCssFile = - (extension === 'css' || id.includes('&lang.css')) && + (extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) && // Don't intercept special static asset resources !SPECIAL_QUERY_RE.test(id) From 35a5e8cb64fde0b8611ea1968ad25e5d2dafe544 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 31 Jan 2025 09:53:02 -0500 Subject: [PATCH 12/15] Discard invalid declarations when parsing CSS (#16093) I discovered this when triaging an error someone had on Tailwind Play. 1. When we see a `;` we often assume a valid declaration precedes it but that may not be the case 2. When we see the name of a custom property we assume everything that follows will be a valid declaration but that is not necessarily the case 3. A bare identifier inside of a rule is treated as a declaration which is not the case This PR fixes all three of these by ignoring these invalid cases. Though some should probably be turned into errors. --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + packages/tailwindcss/src/css-parser.test.ts | 56 +++++++++++++++++++++ packages/tailwindcss/src/css-parser.ts | 18 ++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0ec536b880..114412e42d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Vite: Transform `` blocks in HTML files ([#16069](https://github.com/tailwindlabs/tailwindcss/pull/16069)) - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) +- Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093)) ## [4.0.1] - 2025-01-29 diff --git a/packages/tailwindcss/src/css-parser.test.ts b/packages/tailwindcss/src/css-parser.test.ts index a4b123b28fde..25d5b3a454ca 100644 --- a/packages/tailwindcss/src/css-parser.test.ts +++ b/packages/tailwindcss/src/css-parser.test.ts @@ -329,6 +329,28 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { ]) }) + it('should parse a custom property with an empty value', () => { + expect(parse('--foo:;')).toEqual([ + { + kind: 'declaration', + property: '--foo', + value: '', + important: false, + }, + ]) + }) + + it('should parse a custom property with a space value', () => { + expect(parse('--foo: ;')).toEqual([ + { + kind: 'declaration', + property: '--foo', + value: '', + important: false, + }, + ]) + }) + it('should parse a custom property with a block including nested "css"', () => { expect( parse(css` @@ -1097,5 +1119,39 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => { `), ).toThrowErrorMatchingInlineSnapshot(`[Error: Unterminated string: "Hello world!;"]`) }) + + it('should error when incomplete custom properties are used', () => { + expect(() => parse('--foo')).toThrowErrorMatchingInlineSnapshot( + `[Error: Invalid custom property, expected a value]`, + ) + }) + + it('should error when incomplete custom properties are used inside rules', () => { + expect(() => parse('.foo { --bar }')).toThrowErrorMatchingInlineSnapshot( + `[Error: Invalid custom property, expected a value]`, + ) + }) + + it('should error when a declaration is incomplete', () => { + expect(() => parse('.foo { bar }')).toThrowErrorMatchingInlineSnapshot( + `[Error: Invalid declaration: \`bar\`]`, + ) + }) + + it('should error when a semicolon exists after an at-rule with a body', () => { + expect(() => parse('@plugin "foo" {} ;')).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected semicolon]`, + ) + }) + + it('should error when consecutive semicolons exist', () => { + expect(() => parse(';;;')).toThrowErrorMatchingInlineSnapshot(`[Error: Unexpected semicolon]`) + }) + + it('should error when consecutive semicolons exist after a declaration', () => { + expect(() => parse('.foo { color: red;;; }')).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected semicolon]`, + ) + }) }) }) diff --git a/packages/tailwindcss/src/css-parser.ts b/packages/tailwindcss/src/css-parser.ts index c12c63051cee..d80bb3e79807 100644 --- a/packages/tailwindcss/src/css-parser.ts +++ b/packages/tailwindcss/src/css-parser.ts @@ -286,6 +286,8 @@ export function parse(input: string) { } let declaration = parseDeclaration(buffer, colonIdx) + if (!declaration) throw new Error(`Invalid custom property, expected a value`) + if (parent) { parent.nodes.push(declaration) } else { @@ -337,6 +339,11 @@ export function parse(input: string) { closingBracketStack[closingBracketStack.length - 1] !== ')' ) { let declaration = parseDeclaration(buffer) + if (!declaration) { + if (buffer.length === 0) throw new Error('Unexpected semicolon') + throw new Error(`Invalid declaration: \`${buffer.trim()}\``) + } + if (parent) { parent.nodes.push(declaration) } else { @@ -435,7 +442,10 @@ export function parse(input: string) { // Attach the declaration to the parent. if (parent) { - parent.nodes.push(parseDeclaration(buffer, colonIdx)) + let node = parseDeclaration(buffer, colonIdx) + if (!node) throw new Error(`Invalid declaration: \`${buffer.trim()}\``) + + parent.nodes.push(node) } } } @@ -543,7 +553,11 @@ export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule { return atRule(buffer.trim(), '', nodes) } -function parseDeclaration(buffer: string, colonIdx: number = buffer.indexOf(':')): Declaration { +function parseDeclaration( + buffer: string, + colonIdx: number = buffer.indexOf(':'), +): Declaration | null { + if (colonIdx === -1) return null let importantIdx = buffer.indexOf('!important', colonIdx + 1) return decl( buffer.slice(0, colonIdx).trim(), From 7f1d0970c3bd91da6c860a77b0b63f12d18d5a9d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 31 Jan 2025 17:56:52 +0100 Subject: [PATCH 13/15] Do not emit empty rules/at-rules (#16121) This PR is an optimization where it will not emit empty rules and at-rules. I noticed this while working on https://github.com/tailwindlabs/tailwindcss/pull/16120 where we emitted: ```css :root, :host { } ``` There are some exceptions for "empty" at-rules, such as: ```css @charset "UTF-8"; @layer foo, bar, baz; @custom-media --modern (color), (hover); @namespace "http://www.w3.org/1999/xhtml"; ``` These don't have a body, but they still have a purpose and therefore they will be emitted. However, if you look at this: ```css /* Empty rule */ .foo { } /* Empty rule, with nesting */ .foo { .bar { } .baz { } } /* Empty rule, with special case '&' rules */ .foo { & { &:hover { } &:focus { } } } /* Empty at-rule */ @media (min-width: 768px) { } /* Empty at-rule with nesting*/ @media (min-width: 768px) { .foo { } @media (min-width: 1024px) { .bar { } } } ``` None of these will be emitted. --------- Co-authored-by: Jordan Pittman --- CHANGELOG.md | 1 + integrations/cli/index.test.ts | 7 +- packages/tailwindcss/src/ast.test.ts | 90 +++++++++++++++++++ packages/tailwindcss/src/ast.ts | 18 +++- .../tailwindcss/src/compat/plugin-api.test.ts | 2 - .../src/compat/screens-config.test.ts | 6 +- 6 files changed, 108 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 114412e42d3c..45e54de0830b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) - Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093)) +- Do not emit empty CSS rules and at-rules ([#16121](https://github.com/tailwindlabs/tailwindcss/pull/16121)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 2e6b9ad03549..0e3aba4c4e88 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -368,12 +368,7 @@ describe.each([ `, ) - await fs.expectFileToContain('project-a/dist/out.css', [ - css` - :root, :host { - } - `, - ]) + await fs.expectFileToContain('project-a/dist/out.css', [css``]) }, ) diff --git a/packages/tailwindcss/src/ast.test.ts b/packages/tailwindcss/src/ast.test.ts index c3d9b2f159bf..174971ea541d 100644 --- a/packages/tailwindcss/src/ast.test.ts +++ b/packages/tailwindcss/src/ast.test.ts @@ -2,6 +2,8 @@ import { expect, it } from 'vitest' import { context, decl, optimizeAst, styleRule, toCss, walk, WalkAction } from './ast' import * as CSS from './css-parser' +const css = String.raw + it('should pretty print an AST', () => { expect(toCss(optimizeAst(CSS.parse('.foo{color:red;&:hover{color:blue;}}')))) .toMatchInlineSnapshot(` @@ -95,3 +97,91 @@ it('should stop walking when returning `WalkAction.Stop`', () => { } `) }) + +it('should not emit empty rules once optimized', () => { + let ast = CSS.parse(css` + /* Empty rule */ + .foo { + } + + /* Empty rule, with nesting */ + .foo { + .bar { + } + .baz { + } + } + + /* Empty rule, with special case '&' rules */ + .foo { + & { + &:hover { + } + &:focus { + } + } + } + + /* Empty at-rule */ + @media (min-width: 768px) { + } + + /* Empty at-rule with nesting*/ + @media (min-width: 768px) { + .foo { + } + + @media (min-width: 1024px) { + .bar { + } + } + } + + /* Exceptions: */ + @charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + `) + + expect(toCss(ast)).toMatchInlineSnapshot(` + ".foo { + } + .foo { + .bar { + } + .baz { + } + } + .foo { + & { + &:hover { + } + &:focus { + } + } + } + @media (min-width: 768px); + @media (min-width: 768px) { + .foo { + } + @media (min-width: 1024px) { + .bar { + } + } + } + @charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + " + `) + + expect(toCss(optimizeAst(ast))).toMatchInlineSnapshot(` + "@charset "UTF-8"; + @layer foo, bar, baz; + @custom-media --modern (color), (hover); + @namespace 'http://www.w3.org/1999/xhtml'; + " + `) +}) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 6052724f33b9..7f67fdda5f57 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -261,7 +261,9 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { let nodes: AstNode[] = [] transform(child, nodes, depth + 1) - parent.push(...nodes) + if (nodes.length > 0) { + parent.push(...nodes) + } } } @@ -271,7 +273,9 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { transform(child, copy.nodes, depth + 1) } - parent.push(copy) + if (copy.nodes.length > 0) { + parent.push(copy) + } } } @@ -297,7 +301,15 @@ export function optimizeAst(ast: AstNode[]) { for (let child of node.nodes) { transform(child, copy.nodes, depth + 1) } - parent.push(copy) + if ( + copy.nodes.length > 0 || + copy.name === '@layer' || + copy.name === '@charset' || + copy.name === '@custom-media' || + copy.name === '@namespace' + ) { + parent.push(copy) + } } // AtRoot diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 4502118f458f..9eb65ccb954a 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -3167,8 +3167,6 @@ describe('addUtilities()', () => { color: red; } } - } - :root, :host { }" `, ) diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts index 09ab97fc9f8a..e8f96ee55fd3 100644 --- a/packages/tailwindcss/src/compat/screens-config.test.ts +++ b/packages/tailwindcss/src/compat/screens-config.test.ts @@ -655,9 +655,5 @@ test('JS config `screens` can overwrite default CSS `--breakpoint-*`', async () // currently. expect( compiler.build(['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex']), - ).toMatchInlineSnapshot(` - ":root, :host { - } - " - `) + ).toMatchInlineSnapshot(`""`) }) From 4052eb24bfd7f027300beed6f0485893deceeb4c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 31 Jan 2025 13:18:11 -0500 Subject: [PATCH 14/15] Allow `@variant` to be used at the top-level (#16129) This makes it so `@variant` is replaced at the top level and not just within rules. This also fixes a bug where `@variant` wasn't handled when inside an `@media` at-rule. --- CHANGELOG.md | 1 + packages/tailwindcss/src/index.test.ts | 64 ++++++++++++++++++++++++++ packages/tailwindcss/src/index.ts | 12 +++++ 3 files changed, 77 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e54de0830b..c403badef818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) - Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093)) - Do not emit empty CSS rules and at-rules ([#16121](https://github.com/tailwindlabs/tailwindcss/pull/16121)) +- Handle `@variant` when at the top-level of a stylesheet ([#16129](https://github.com/tailwindlabs/tailwindcss/pull/16129)) ## [4.0.1] - 2025-01-29 diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index ec4671e97268..67a0a3d64dab 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -3510,6 +3510,38 @@ describe('@variant', () => { background: white; } } + + @variant hover { + @variant landscape { + .btn2 { + color: red; + } + } + } + + @variant hover { + .foo { + color: red; + } + @variant landscape { + .bar { + color: blue; + } + } + .baz { + @variant portrait { + color: green; + } + } + } + + @media something { + @variant landscape { + @page { + color: red; + } + } + } `, [], ), @@ -3522,6 +3554,38 @@ describe('@variant', () => { .btn { background: #fff; } + } + + @media (hover: hover) { + @media (orientation: landscape) { + :scope:hover .btn2 { + color: red; + } + } + + :scope:hover .foo { + color: red; + } + + @media (orientation: landscape) { + :scope:hover .bar { + color: #00f; + } + } + + @media (orientation: portrait) { + :scope:hover .baz { + color: green; + } + } + } + + @media something { + @media (orientation: landscape) { + @page { + color: red; + } + } }" `) }) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index d594397ede6e..2baed9acf6a5 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -244,6 +244,11 @@ async function parseCss( return WalkAction.Stop } }) + + // No `@slot` found, so this is still a regular `@variant` at-rule + if (node.name === '@variant') { + variantNodes.push(node) + } } } @@ -429,6 +434,13 @@ async function parseCss( replaceWith(node.nodes) } + walk(node.nodes, (node) => { + if (node.kind !== 'at-rule') return + if (node.name !== '@variant') return + + variantNodes.push(node) + }) + return WalkAction.Skip } From 50bafce75623a5a5b7171ef278b445c437c6be48 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 31 Jan 2025 19:30:36 +0100 Subject: [PATCH 15/15] Prepare for v4.0.2 release (#16131) Co-authored-by: Adam Wathan --- CHANGELOG.md | 11 ++++++++--- crates/node/npm/android-arm-eabi/package.json | 2 +- crates/node/npm/android-arm64/package.json | 2 +- crates/node/npm/darwin-arm64/package.json | 2 +- crates/node/npm/darwin-x64/package.json | 2 +- crates/node/npm/freebsd-x64/package.json | 2 +- crates/node/npm/linux-arm-gnueabihf/package.json | 2 +- crates/node/npm/linux-arm64-gnu/package.json | 2 +- crates/node/npm/linux-arm64-musl/package.json | 2 +- crates/node/npm/linux-x64-gnu/package.json | 2 +- crates/node/npm/linux-x64-musl/package.json | 2 +- crates/node/npm/win32-arm64-msvc/package.json | 2 +- crates/node/npm/win32-x64-msvc/package.json | 2 +- crates/node/package.json | 2 +- packages/@tailwindcss-browser/package.json | 2 +- packages/@tailwindcss-cli/package.json | 2 +- packages/@tailwindcss-node/package.json | 2 +- packages/@tailwindcss-postcss/package.json | 2 +- packages/@tailwindcss-standalone/package.json | 2 +- packages/@tailwindcss-upgrade/package.json | 2 +- packages/@tailwindcss-vite/package.json | 2 +- packages/tailwindcss/package.json | 2 +- 22 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c403badef818..d61bfc1eec4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Nothing yet! + +## [4.0.2] - 2025-01-31 + ### Fixed - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) @@ -15,9 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) - Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) - Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) -- Vite: Don't rebase urls that appear to be aliases ([#16078](https://github.com/tailwindlabs/tailwindcss/pull/16078)) +- Vite: Don't rebase URLs that appear to be aliases ([#16078](https://github.com/tailwindlabs/tailwindcss/pull/16078)) - Vite: Transform `` blocks in HTML files ([#16069](https://github.com/tailwindlabs/tailwindcss/pull/16069)) -- Prevent camelCasing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) +- Prevent camel-casing CSS custom properties added by JavaScript plugins ([#16103](https://github.com/tailwindlabs/tailwindcss/pull/16103)) - Do not emit `@keyframes` in `@theme reference` ([#16120](https://github.com/tailwindlabs/tailwindcss/pull/16120)) - Discard invalid declarations when parsing CSS ([#16093](https://github.com/tailwindlabs/tailwindcss/pull/16093)) - Do not emit empty CSS rules and at-rules ([#16121](https://github.com/tailwindlabs/tailwindcss/pull/16121)) @@ -3365,7 +3369,8 @@ No release notes - Everything! -[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.1...HEAD +[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.2...HEAD +[4.0.2]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.1...v4.0.2 [4.0.1]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.0...v4.0.1 [4.0.0]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.0-beta.10...v4.0.0 [4.0.0-beta.10]: https://github.com/tailwindlabs/tailwindcss/compare/v4.0.0-beta.9...v4.0.0-beta.10 diff --git a/crates/node/npm/android-arm-eabi/package.json b/crates/node/npm/android-arm-eabi/package.json index f786a9acea9e..2cf959be2d15 100644 --- a/crates/node/npm/android-arm-eabi/package.json +++ b/crates/node/npm/android-arm-eabi/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-android-arm-eabi", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/android-arm64/package.json b/crates/node/npm/android-arm64/package.json index adc7a56c43c6..7250076a4c74 100644 --- a/crates/node/npm/android-arm64/package.json +++ b/crates/node/npm/android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-android-arm64", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/darwin-arm64/package.json b/crates/node/npm/darwin-arm64/package.json index 2f20b3232fa0..ecf4abe3062f 100644 --- a/crates/node/npm/darwin-arm64/package.json +++ b/crates/node/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-darwin-arm64", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/darwin-x64/package.json b/crates/node/npm/darwin-x64/package.json index 3169e58dc9fc..a558603ca15e 100644 --- a/crates/node/npm/darwin-x64/package.json +++ b/crates/node/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-darwin-x64", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/freebsd-x64/package.json b/crates/node/npm/freebsd-x64/package.json index 1d7f1390bd64..1c3597a0dc0f 100644 --- a/crates/node/npm/freebsd-x64/package.json +++ b/crates/node/npm/freebsd-x64/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-freebsd-x64", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/linux-arm-gnueabihf/package.json b/crates/node/npm/linux-arm-gnueabihf/package.json index 38a82b6baa62..928fd27eab13 100644 --- a/crates/node/npm/linux-arm-gnueabihf/package.json +++ b/crates/node/npm/linux-arm-gnueabihf/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-linux-arm-gnueabihf", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/linux-arm64-gnu/package.json b/crates/node/npm/linux-arm64-gnu/package.json index 25f2012c7744..9302d248b43c 100644 --- a/crates/node/npm/linux-arm64-gnu/package.json +++ b/crates/node/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-linux-arm64-gnu", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/linux-arm64-musl/package.json b/crates/node/npm/linux-arm64-musl/package.json index eec1330699b2..a31e0d7e08f7 100644 --- a/crates/node/npm/linux-arm64-musl/package.json +++ b/crates/node/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-linux-arm64-musl", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/linux-x64-gnu/package.json b/crates/node/npm/linux-x64-gnu/package.json index 8df51808885c..27f7d982cb48 100644 --- a/crates/node/npm/linux-x64-gnu/package.json +++ b/crates/node/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-linux-x64-gnu", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/linux-x64-musl/package.json b/crates/node/npm/linux-x64-musl/package.json index 0013c4b287b5..f71adb6a03c5 100644 --- a/crates/node/npm/linux-x64-musl/package.json +++ b/crates/node/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-linux-x64-musl", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/win32-arm64-msvc/package.json b/crates/node/npm/win32-arm64-msvc/package.json index c8721d3ba78c..bce805bd7f6c 100644 --- a/crates/node/npm/win32-arm64-msvc/package.json +++ b/crates/node/npm/win32-arm64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-win32-arm64-msvc", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/npm/win32-x64-msvc/package.json b/crates/node/npm/win32-x64-msvc/package.json index b135728598b5..8b4b652eb32e 100644 --- a/crates/node/npm/win32-x64-msvc/package.json +++ b/crates/node/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide-win32-x64-msvc", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/crates/node/package.json b/crates/node/package.json index d1588c89247b..ead18df43cb0 100644 --- a/crates/node/package.json +++ b/crates/node/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/oxide", - "version": "4.0.1", + "version": "4.0.2", "repository": { "type": "git", "url": "git+https://github.com/tailwindlabs/tailwindcss.git", diff --git a/packages/@tailwindcss-browser/package.json b/packages/@tailwindcss-browser/package.json index 5758c89547eb..c1d96c5584c1 100644 --- a/packages/@tailwindcss-browser/package.json +++ b/packages/@tailwindcss-browser/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/browser", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "./dist/index.global.js", diff --git a/packages/@tailwindcss-cli/package.json b/packages/@tailwindcss-cli/package.json index 63b35d2fb3c8..a496c6b01e30 100644 --- a/packages/@tailwindcss-cli/package.json +++ b/packages/@tailwindcss-cli/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/cli", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { diff --git a/packages/@tailwindcss-node/package.json b/packages/@tailwindcss-node/package.json index fb5333b8e72b..a70b59c404a8 100644 --- a/packages/@tailwindcss-node/package.json +++ b/packages/@tailwindcss-node/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/node", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { diff --git a/packages/@tailwindcss-postcss/package.json b/packages/@tailwindcss-postcss/package.json index 00b49bdf5783..cb0d24cd2ad1 100644 --- a/packages/@tailwindcss-postcss/package.json +++ b/packages/@tailwindcss-postcss/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/postcss", - "version": "4.0.1", + "version": "4.0.2", "description": "PostCSS plugin for Tailwind CSS, a utility-first CSS framework for rapidly building custom user interfaces", "license": "MIT", "repository": { diff --git a/packages/@tailwindcss-standalone/package.json b/packages/@tailwindcss-standalone/package.json index d45ad2037f5e..6343af214a24 100644 --- a/packages/@tailwindcss-standalone/package.json +++ b/packages/@tailwindcss-standalone/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/standalone", - "version": "4.0.1", + "version": "4.0.2", "private": true, "description": "Standalone CLI for Tailwind CSS", "license": "MIT", diff --git a/packages/@tailwindcss-upgrade/package.json b/packages/@tailwindcss-upgrade/package.json index 2274dd358018..bc7ce6e8b047 100644 --- a/packages/@tailwindcss-upgrade/package.json +++ b/packages/@tailwindcss-upgrade/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/upgrade", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json index bd8682a9ddfb..810a734f6df1 100644 --- a/packages/@tailwindcss-vite/package.json +++ b/packages/@tailwindcss-vite/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/vite", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json index 29b92bef2394..74df33960b3d 100644 --- a/packages/tailwindcss/package.json +++ b/packages/tailwindcss/package.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "4.0.1", + "version": "4.0.2", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": {