Skip to content

Commit 1c4e4a1

Browse files
perf(css): fold hsl() and hwb() channels written as numbers
CSS Color 4 states the two channels after the hue as `<percentage> | <number>`, so `hsl(120 50 50)` is the color `hsl(120 50% 50%)` names. The fold required a `%` on both and declined everything else, leaving 500 css-parsing-tests cases per function — and any stylesheet written in the modern grammar — unminified. The separator decides it: only the space-separated form takes a bare number, where the legacy comma form is percentages alone, so folding `hsl(120, 50, 50)` would hand invalid CSS a color. The byte-boundary guard is untouched, so a channel an engine would round either way is still declined. In minimize-lightningcss-values both spellings now fold to the same rule, so the duplicate elimination keeps the later one — which is why the output moves between two labelled entries there.
1 parent 432e87e commit 1c4e4a1

7 files changed

Lines changed: 30 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Fold an `hsl()` or `hwb()` whose modern grammar states its channels as numbers.

‎lib/css/syntax-parser.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9211,7 +9211,13 @@ const _minifyPolarColorFunction = (
92119211
const first = _COLOR_NUMBER_RE.exec(args[1]);
92129212
const second = _COLOR_NUMBER_RE.exec(args[2]);
92139213
if (hue === null || first === null || second === null) return null;
9214-
if (first[2] !== "%" || second[2] !== "%") return null;
9214+
// WHY: CSS Color 4 admits a bare `<number>` for these two channels in the
9215+
// modern space-separated grammar alone — the legacy comma form takes
9216+
// percentages only. Folding `hsl(120, 50, 50)` would hand invalid CSS a
9217+
// color, so the separator decides whether a missing `%` is readable.
9218+
if (inner.includes(",") && (first[2] !== "%" || second[2] !== "%")) {
9219+
return null;
9220+
}
92159221
const x = Number(first[1]) / 100;
92169222
const y = Number(second[1]) / 100;
92179223
if (x < 0 || x > 1 || y < 0 || y > 1) return null;

‎test/configCases/css/minimize-colors/__snapshots__/ConfigCacheTest.snap‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ Array [
5656
"/*! declined: an angle unit other than \`deg\` needs its own conversion */.h06{color:hsl(0turn,100%,50%)}",
5757
"/*! declined: a substitution, and a saturation out of range */.h07{color:hsl(var(--h),100%,50%);background:hsl(0,150%,50%)}",
5858
"/*! declined: hwb() — esbuild says #00c3ff, lightningcss says #00c4ff */.h08{color:hwb(194 0% 0%)}",
59+
"/*! the modern grammar takes a bare number for the two channels after the hue,
60+
so these name the colors their percentage spellings do */.h09{color:#40bf40;background:#933}",
61+
"/*! declined: the legacy comma form takes percentages alone, so a bare number
62+
there is invalid CSS rather than a color to fold */.h10{color:hsl(120,50,50);background:hwb(0,20,40)}",
5963
"/*! declined: the stops interpolate in the space the gradient names, and a
6064
color converted through sRGB maps back into it as a different point */.g01{background-image:linear-gradient(90deg in lch,red,lch(50 20 30))}.g02{background-image:linear-gradient(in oklch,red,oklch(.7 .1 200))}",
6165
"/*! declined without a space too: a stop outside sRGB is handed back as written

‎test/configCases/css/minimize-colors/__snapshots__/ConfigTest.snap‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ Array [
5656
"/*! declined: an angle unit other than \`deg\` needs its own conversion */.h06{color:hsl(0turn,100%,50%)}",
5757
"/*! declined: a substitution, and a saturation out of range */.h07{color:hsl(var(--h),100%,50%);background:hsl(0,150%,50%)}",
5858
"/*! declined: hwb() — esbuild says #00c3ff, lightningcss says #00c4ff */.h08{color:hwb(194 0% 0%)}",
59+
"/*! the modern grammar takes a bare number for the two channels after the hue,
60+
so these name the colors their percentage spellings do */.h09{color:#40bf40;background:#933}",
61+
"/*! declined: the legacy comma form takes percentages alone, so a bare number
62+
there is invalid CSS rather than a color to fold */.h10{color:hsl(120,50,50);background:hwb(0,20,40)}",
5963
"/*! declined: the stops interpolate in the space the gradient names, and a
6064
color converted through sRGB maps back into it as a different point */.g01{background-image:linear-gradient(90deg in lch,red,lch(50 20 30))}.g02{background-image:linear-gradient(in oklch,red,oklch(.7 .1 200))}",
6165
"/*! declined without a space too: a stop outside sRGB is handed back as written

‎test/configCases/css/minimize-colors/style.css‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@
117117
.h07 { color: hsl(var(--h), 100%, 50%); background: hsl(0, 150%, 50%) }
118118
/*! declined: hwb() — esbuild says #00c3ff, lightningcss says #00c4ff */
119119
.h08 { color: hwb(194 0% 0%) }
120+
/*! the modern grammar takes a bare number for the two channels after the hue,
121+
so these name the colors their percentage spellings do */
122+
.h09 { color: hsl(120 50 50); background: hwb(0 20 40) }
123+
/*! declined: the legacy comma form takes percentages alone, so a bare number
124+
there is invalid CSS rather than a color to fold */
125+
.h10 { color: hsl(120, 50, 50); background: hwb(0, 20, 40) }
120126
/*! declined: the stops interpolate in the space the gradient names, and a
121127
color converted through sRGB maps back into it as a different point */
122128
.g01 { background-image: linear-gradient(90deg in lch, red, lch(50 20 30)) }

‎test/configCases/css/minimize-lightningcss-values/__snapshots__/ConfigCacheTest.snap‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Array [
1515
"/*! test_color: .foo { color: rgb(48% 100% 100% / 50%) } */.foo{color:#7affff80}",
1616
"/*! test_color: .foo { color: hsl(100deg, 100%, 50%) } */",
1717
"/*! test_color: .foo { color: hsl(100, 100%, 50%) } */",
18-
"/*! test_color: .foo { color: hsl(100 100% 50%) } */.foo{color:#5f0}",
19-
"/*! test_color: .foo { color: hsl(100 100 50) } */.foo{color:hsl(100 100 50)}",
18+
"/*! test_color: .foo { color: hsl(100 100% 50%) } */",
19+
"/*! test_color: .foo { color: hsl(100 100 50) } */.foo{color:#5f0}",
2020
"/*! test_color: .foo { color: hsl(100, 100%, 50%, .8) } */",
2121
"/*! test_color: .foo { color: hsl(100 100% 50% / .8) } */",
2222
"/*! test_color: .foo { color: hsla(100, 100%, 50%, .8) } */",

‎test/configCases/css/minimize-lightningcss-values/__snapshots__/ConfigTest.snap‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Array [
1515
"/*! test_color: .foo { color: rgb(48% 100% 100% / 50%) } */.foo{color:#7affff80}",
1616
"/*! test_color: .foo { color: hsl(100deg, 100%, 50%) } */",
1717
"/*! test_color: .foo { color: hsl(100, 100%, 50%) } */",
18-
"/*! test_color: .foo { color: hsl(100 100% 50%) } */.foo{color:#5f0}",
19-
"/*! test_color: .foo { color: hsl(100 100 50) } */.foo{color:hsl(100 100 50)}",
18+
"/*! test_color: .foo { color: hsl(100 100% 50%) } */",
19+
"/*! test_color: .foo { color: hsl(100 100 50) } */.foo{color:#5f0}",
2020
"/*! test_color: .foo { color: hsl(100, 100%, 50%, .8) } */",
2121
"/*! test_color: .foo { color: hsl(100 100% 50% / .8) } */",
2222
"/*! test_color: .foo { color: hsla(100, 100%, 50%, .8) } */",

0 commit comments

Comments
 (0)