From 95b82ddb79b96affa8538aa271833fc4f547a410 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 9 Oct 2025 22:18:34 +0200 Subject: [PATCH 1/7] canonicalize units MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will normalize units to their canonical form, this is an initial step for allowing `rem` to `px` conversion as well. in, cm, mm, q, pc, pt → px grad, rad, turn → deg ms → s khz → hz Already provided the option to pass in a `rem` value. Once passed in, we will convert `rem` to `px` as well. --- .../src/constant-fold-declaration.test.ts | 12 ++-- .../src/constant-fold-declaration.ts | 70 +++++++++++++------ 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/packages/tailwindcss/src/constant-fold-declaration.test.ts b/packages/tailwindcss/src/constant-fold-declaration.test.ts index 1fdbbde81402..b95ed74a340b 100644 --- a/packages/tailwindcss/src/constant-fold-declaration.test.ts +++ b/packages/tailwindcss/src/constant-fold-declaration.test.ts @@ -72,18 +72,18 @@ it.each([ it.each([ ['0deg', '0deg'], - ['0rad', '0rad'], + ['0rad', '0deg'], ['0%', '0%'], - ['0turn', '0turn'], + ['0turn', '0deg'], ['0fr', '0fr'], - ['0ms', '0ms'], + ['0ms', '0s'], ['0s', '0s'], ['-0.0deg', '0deg'], - ['-0.0rad', '0rad'], + ['-0.0rad', '0deg'], ['-0.0%', '0%'], - ['-0.0turn', '0turn'], + ['-0.0turn', '0deg'], ['-0.0fr', '0fr'], - ['-0.0ms', '0ms'], + ['-0.0ms', '0s'], ['-0.0s', '0s'], ])('should not fold non-foldable units to `0`. Constant fold `%s` into `%s`', (input, expected) => { expect(constantFoldDeclaration(input)).toBe(expected) diff --git a/packages/tailwindcss/src/constant-fold-declaration.ts b/packages/tailwindcss/src/constant-fold-declaration.ts index fc2424ef7eba..9cc3feb76d27 100644 --- a/packages/tailwindcss/src/constant-fold-declaration.ts +++ b/packages/tailwindcss/src/constant-fold-declaration.ts @@ -9,33 +9,21 @@ export function constantFoldDeclaration(input: string): string { let valueAst = ValueParser.parse(input) ValueParser.walkDepth(valueAst, (valueNode, { replaceWith }) => { - // Convert `-0`, `+0`, `0.0`, … to `0` - // Convert `-0px`, `+0em`, `0.0rem`, … to `0` + // Canonicalize dimensions to their simplest form. This includes: + // - Convert `-0`, `+0`, `0.0`, … to `0` + // - Convert `-0px`, `+0em`, `0.0rem`, … to `0` + // - Convert units to an equivalent unit if ( valueNode.kind === 'word' && - valueNode.value !== '0' && // Already `0`, nothing to do - ((valueNode.value[0] === '-' && valueNode.value[1] === '0') || // `-0…` - (valueNode.value[0] === '+' && valueNode.value[1] === '0') || // `+0…` - valueNode.value[0] === '0') // `0…` + valueNode.value !== '0' // Already `0`, nothing to do ) { - let dimension = dimensions.get(valueNode.value) - if (dimension === null) return // This shouldn't happen + let canonical = canonicalizeDimension(valueNode.value) + if (canonical === null) return // Couldn't be canonicalized, nothing to do + if (canonical === valueNode.value) return // Already in canonical form, nothing to do - if (dimension[0] !== 0) return // Not a zero value, nothing to do - - // Replace length units with just `0` - if (dimension[1] === null || isLength(valueNode.value)) { - folded = true - replaceWith(ValueParser.word('0')) - return - } - - // Replace other units with `0`, e.g. `0%`, `0fr`, `0s`, … - else if (valueNode.value !== `0${dimension[1]}`) { - folded = true - replaceWith(ValueParser.word(`0${dimension[1]}`)) - return - } + folded = true + replaceWith(ValueParser.word(canonical)) + return } // Constant fold `calc()` expressions with two operands and one operator @@ -124,3 +112,39 @@ export function constantFoldDeclaration(input: string): string { return folded ? ValueParser.toCss(valueAst) : input } + +function canonicalizeDimension(input: string, rem?: number): string | null { + let dimension = dimensions.get(input) + if (dimension === null) return null // This shouldn't happen + + let [value, unit] = dimension + if (unit === null) return `${value}` // Already unitless, nothing to do + + // Replace `0` units with just `0` + if (value === 0 && isLength(input)) return '0' + + // prettier-ignore + switch (unit.toLowerCase()) { + // to px, https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Values_and_units#lengths + case 'in': return `${value * 96}px` // 1in = 96.000px + case 'cm': return `${value * 96 / 2.54}px` // 1cm = 37.795px + case 'mm': return `${value * 96 / 2.54 / 10}px` // 1mm = 3.779px + case 'q': return `${value * 96 / 2.54 / 10 / 4}px` // 1q = 0.945px + case 'pc': return `${value * 96 / 6}px` // 1pc = 16.000px + case 'pt': return `${value * 96 / 72}px` // 1pt = 1.333px + case 'rem': return rem ? `${value * rem}px` : null // 1rem = 16.000px (Assuming root font-size is 16px) + + // to deg, https://developer.mozilla.org/en-US/docs/Web/CSS/angle + case 'grad': return `${value * 0.9}deg` // 1grad = 0.900deg + case 'rad': return `${value * 180 / Math.PI}deg` // 1rad = 57.296deg + case 'turn': return `${value * 360}deg` // 1turn = 360.000deg + + //