mirror of
https://github.com/hanzoai/tailwindcss.git
synced 2026-09-24 17:03:14 +00:00
Always produce <length> value when optimizing --spacing(0) (#20319)
This PR fixes a type issue when using `--spacing(0)`. This construction doesn't really make sense, but if you use it, it produces the value `0` instead of `calc(var(--spacing) * 0)` which is fine if you use it in a spot where a `<length>` data type can be used, then the `0` is interpreted as a `<length>`. E.g.: `padding: 0` and `padding: 0px` are equivalent. However, if you use it in a CSS variable, then the `0` will turn in a `<number>` if you don't have an `@property` definition for that CSS variable. This on its own isn't the issue, but if you later use it as part of a `calc(…)` then the `<number>` instead of `<length>` type is being used. As seen in #20315. We could remove the optimization, and use `calc(var(--spacing) * 0)` again, but this is a bit silly since it only makes your CSS file larger. We could try to be smart, and only do it _if_ we're assigning to a CSS variable (which #20317 is doing). But if your CSS variable _does_ use a `<length>` then it's a non-issue. We would run into the same issue if you use `width: calc(100% --spacing(0))`. This value is a bit silly anyway, but it would originally resolve to `calc(100% + calc(var(--spacing) * 0))`, the optimization of `calc(100% - 0)` would make it invalid, so the fix in #20317 would not be enough. Instead I opted for an inbetween solution, by always using `0px`. In most cases we can use `0`, but in the places we can't the `0px` would at least ensure that we are dealing with `<length>` data types. This way we don't have to try to be smart to analyze where we use the value, and `0px` is still better than the long `calc(var(--spacing) * 0)` value. Fixes: #20315 Closes: #20317 ## Test plan 1. Manually tested that now `--spacing(0)` does produce `0px` which has a length type
This commit is contained in:
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix stale `@tailwindcss/postcss` output when changed input CSS is passed from a preprocessor like Sass without updating the input file on disk ([#20310](https://github.com/tailwindlabs/tailwindcss/pull/20310))
|
||||
- Ensure CSS nesting is handled even when Lightning CSS isn't run, like in `@tailwindcss/browser` and Tailwind Play ([#20124](https://github.com/tailwindlabs/tailwindcss/pull/20124))
|
||||
- Prevent achromatic theme colors from shifting hue in `color-mix(…)` with polar color spaces like `oklch` ([#19830](https://github.com/tailwindlabs/tailwindcss/issues/19830))
|
||||
- Ensure `--spacing(0)` is optimized to `0px` instead of `0` so it remains a `<length>` when used in `calc(…)` ([#20319](https://github.com/tailwindlabs/tailwindcss/pull/20319))
|
||||
|
||||
## [4.3.2] - 2026-06-26
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { compile } from '.'
|
||||
import plugin from './plugin'
|
||||
import { compileCss, run } from './test-utils/run'
|
||||
import { compileCss, pretty, run } from './test-utils/run'
|
||||
|
||||
const css = String.raw
|
||||
|
||||
@@ -106,6 +107,14 @@ describe('--spacing(…)', () => {
|
||||
})
|
||||
|
||||
describe('optimizations', () => {
|
||||
// Use custom `compileCss` such that we can use a version without Lightning
|
||||
// CSS. This way we test _our_ optimizations instead of Lightning's
|
||||
// optimizations.
|
||||
async function compileCss(css: string, options: Parameters<typeof compile>[1] = {}) {
|
||||
let { build } = await compile(css, options)
|
||||
return pretty(build([]))
|
||||
}
|
||||
|
||||
test('--spacing(…) optimizes the output when the input is `0`', async () => {
|
||||
expect(
|
||||
await compileCss(css`
|
||||
@@ -121,8 +130,8 @@ describe('--spacing(…)', () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.foo {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
@@ -143,8 +152,8 @@ describe('--spacing(…)', () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.foo {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
@@ -165,9 +174,8 @@ describe('--spacing(…)', () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
:root, :host {
|
||||
--spacing: .25rem;
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.foo {
|
||||
margin: var(--spacing);
|
||||
padding: var(--spacing);
|
||||
@@ -191,8 +199,8 @@ describe('--spacing(…)', () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.foo {
|
||||
margin: .25rem;
|
||||
padding: .25rem;
|
||||
margin: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
"
|
||||
`)
|
||||
|
||||
@@ -70,11 +70,13 @@ function spacing(
|
||||
// - We can assume that the `--spacing` value is not set to a `0`-like value.
|
||||
// Otherwise `p-<anything>` would calculate as `0` which wouldn't make sense.
|
||||
//
|
||||
// - That means that a value of `0` can be replaced by `0`
|
||||
// - That means that a value of `0` can be replaced by `0px`. It's important
|
||||
// to keep the unit so the value stays a `<length>`, e.g. when assigned to
|
||||
// a custom property and later used inside `calc(…)`.
|
||||
// - That means that a value of `1` can be replaced by `multiplier`
|
||||
let valueDimension = dimensions.get(value)
|
||||
if (valueDimension) {
|
||||
if (valueDimension[0] === 0) return '0'
|
||||
if (valueDimension[0] === 0) return '0px'
|
||||
if (valueDimension[0] === 1) return multiplier
|
||||
}
|
||||
|
||||
|
||||
@@ -16145,7 +16145,7 @@ test('mask-t-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-top: linear-gradient(to top, var(--tw-mask-top-from-color) var(--tw-mask-top-from-position), var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));
|
||||
--tw-mask-top-from-position: 0;
|
||||
--tw-mask-top-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -16405,7 +16405,7 @@ test('mask-t-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-top: linear-gradient(to top, var(--tw-mask-top-from-color) var(--tw-mask-top-from-position), var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));
|
||||
--tw-mask-top-to-position: 0;
|
||||
--tw-mask-top-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -16666,7 +16666,7 @@ test('mask-r-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-right: linear-gradient(to right, var(--tw-mask-right-from-color) var(--tw-mask-right-from-position), var(--tw-mask-right-to-color) var(--tw-mask-right-to-position));
|
||||
--tw-mask-right-from-position: 0;
|
||||
--tw-mask-right-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -16927,7 +16927,7 @@ test('mask-r-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-right: linear-gradient(to right, var(--tw-mask-right-from-color) var(--tw-mask-right-from-position), var(--tw-mask-right-to-color) var(--tw-mask-right-to-position));
|
||||
--tw-mask-right-to-position: 0;
|
||||
--tw-mask-right-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -17188,7 +17188,7 @@ test('mask-b-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-bottom: linear-gradient(to bottom, var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position), var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position));
|
||||
--tw-mask-bottom-from-position: 0;
|
||||
--tw-mask-bottom-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -17449,7 +17449,7 @@ test('mask-b-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-bottom: linear-gradient(to bottom, var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position), var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position));
|
||||
--tw-mask-bottom-to-position: 0;
|
||||
--tw-mask-bottom-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -17710,7 +17710,7 @@ test('mask-l-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-left: linear-gradient(to left, var(--tw-mask-left-from-color) var(--tw-mask-left-from-position), var(--tw-mask-left-to-color) var(--tw-mask-left-to-position));
|
||||
--tw-mask-left-from-position: 0;
|
||||
--tw-mask-left-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -17971,7 +17971,7 @@ test('mask-l-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-left: linear-gradient(to left, var(--tw-mask-left-from-color) var(--tw-mask-left-from-position), var(--tw-mask-left-to-color) var(--tw-mask-left-to-position));
|
||||
--tw-mask-left-to-position: 0;
|
||||
--tw-mask-left-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -18240,9 +18240,9 @@ test('mask-x-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-right: linear-gradient(to right, var(--tw-mask-right-from-color) var(--tw-mask-right-from-position), var(--tw-mask-right-to-color) var(--tw-mask-right-to-position));
|
||||
--tw-mask-right-from-position: 0;
|
||||
--tw-mask-right-from-position: 0px;
|
||||
--tw-mask-left: linear-gradient(to left, var(--tw-mask-left-from-color) var(--tw-mask-left-from-position), var(--tw-mask-left-to-color) var(--tw-mask-left-to-position));
|
||||
--tw-mask-left-from-position: 0;
|
||||
--tw-mask-left-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -18547,9 +18547,9 @@ test('mask-x-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-right: linear-gradient(to right, var(--tw-mask-right-from-color) var(--tw-mask-right-from-position), var(--tw-mask-right-to-color) var(--tw-mask-right-to-position));
|
||||
--tw-mask-right-to-position: 0;
|
||||
--tw-mask-right-to-position: 0px;
|
||||
--tw-mask-left: linear-gradient(to left, var(--tw-mask-left-from-color) var(--tw-mask-left-from-position), var(--tw-mask-left-to-color) var(--tw-mask-left-to-position));
|
||||
--tw-mask-left-to-position: 0;
|
||||
--tw-mask-left-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -18854,9 +18854,9 @@ test('mask-y-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-top: linear-gradient(to top, var(--tw-mask-top-from-color) var(--tw-mask-top-from-position), var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));
|
||||
--tw-mask-top-from-position: 0;
|
||||
--tw-mask-top-from-position: 0px;
|
||||
--tw-mask-bottom: linear-gradient(to bottom, var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position), var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position));
|
||||
--tw-mask-bottom-from-position: 0;
|
||||
--tw-mask-bottom-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -19161,9 +19161,9 @@ test('mask-y-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear: var(--tw-mask-left), var(--tw-mask-right), var(--tw-mask-bottom), var(--tw-mask-top);
|
||||
--tw-mask-top: linear-gradient(to top, var(--tw-mask-top-from-color) var(--tw-mask-top-from-position), var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));
|
||||
--tw-mask-top-to-position: 0;
|
||||
--tw-mask-top-to-position: 0px;
|
||||
--tw-mask-bottom: linear-gradient(to bottom, var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position), var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position));
|
||||
--tw-mask-bottom-to-position: 0;
|
||||
--tw-mask-bottom-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -19618,7 +19618,7 @@ test('mask-linear-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear-stops: var(--tw-mask-linear-position), var(--tw-mask-linear-from-color) var(--tw-mask-linear-from-position), var(--tw-mask-linear-to-color) var(--tw-mask-linear-to-position);
|
||||
--tw-mask-linear: linear-gradient(var(--tw-mask-linear-stops));
|
||||
--tw-mask-linear-from-position: 0;
|
||||
--tw-mask-linear-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -19858,7 +19858,7 @@ test('mask-linear-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-linear-stops: var(--tw-mask-linear-position), var(--tw-mask-linear-from-color) var(--tw-mask-linear-from-position), var(--tw-mask-linear-to-color) var(--tw-mask-linear-to-position);
|
||||
--tw-mask-linear: linear-gradient(var(--tw-mask-linear-stops));
|
||||
--tw-mask-linear-to-position: 0;
|
||||
--tw-mask-linear-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -20347,7 +20347,7 @@ test('mask-radial-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-radial-stops: var(--tw-mask-radial-shape) var(--tw-mask-radial-size) at var(--tw-mask-radial-position), var(--tw-mask-radial-from-color) var(--tw-mask-radial-from-position), var(--tw-mask-radial-to-color) var(--tw-mask-radial-to-position);
|
||||
--tw-mask-radial: radial-gradient(var(--tw-mask-radial-stops));
|
||||
--tw-mask-radial-from-position: 0;
|
||||
--tw-mask-radial-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -20601,7 +20601,7 @@ test('mask-radial-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-radial-stops: var(--tw-mask-radial-shape) var(--tw-mask-radial-size) at var(--tw-mask-radial-position), var(--tw-mask-radial-from-color) var(--tw-mask-radial-from-position), var(--tw-mask-radial-to-color) var(--tw-mask-radial-to-position);
|
||||
--tw-mask-radial: radial-gradient(var(--tw-mask-radial-stops));
|
||||
--tw-mask-radial-to-position: 0;
|
||||
--tw-mask-radial-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -21014,7 +21014,7 @@ test('mask-conic-from', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-conic-stops: from var(--tw-mask-conic-position), var(--tw-mask-conic-from-color) var(--tw-mask-conic-from-position), var(--tw-mask-conic-to-color) var(--tw-mask-conic-to-position);
|
||||
--tw-mask-conic: conic-gradient(var(--tw-mask-conic-stops));
|
||||
--tw-mask-conic-from-position: 0;
|
||||
--tw-mask-conic-from-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
@@ -21254,7 +21254,7 @@ test('mask-conic-to', async () => {
|
||||
mask-image: var(--tw-mask-linear), var(--tw-mask-radial), var(--tw-mask-conic);
|
||||
--tw-mask-conic-stops: from var(--tw-mask-conic-position), var(--tw-mask-conic-from-color) var(--tw-mask-conic-from-position), var(--tw-mask-conic-to-color) var(--tw-mask-conic-to-position);
|
||||
--tw-mask-conic: conic-gradient(var(--tw-mask-conic-stops));
|
||||
--tw-mask-conic-to-position: 0;
|
||||
--tw-mask-conic-to-position: 0px;
|
||||
-webkit-mask-composite: source-in;
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
|
||||
Reference in New Issue
Block a user