Repository navigation
feat(css): four more value-level minify transforms - #21629
Conversation
|
| Name | Type |
|---|---|
| webpack | Minor |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 28df2fb. Configure here.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21629 +/- ##
========================================
Coverage 94.22% 94.23%
========================================
Files 628 628
Lines 79590 79952 +362
Branches 23548 23676 +128
========================================
+ Hits 74997 75345 +348
- Misses 4593 4607 +14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
This PR is packaged and the instant preview is available (d0a9320). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@d0a9320
yarn add -D webpack@https://pkg.pr.new/webpack@d0a9320
pnpm add -D webpack@https://pkg.pr.new/webpack@d0a9320 |
Generated code sizeComparing
17 asset(s) changed size
No runtime gained or lost a runtime module. Built |
Merging this PR will regress 1 benchmark
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | benchmark "devtool-eval", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
507.3 KB | 877.8 KB | -42.2% |
| ⚡ | Memory | benchmark "many-modules-interop-runtime", scenario '{"name":"mode-production","mode":"production"}', measure 'exec' |
26.9 KB | 20.6 KB | +30.38% |
| ⚡ | Memory | benchmark "devtool-eval", scenario '{"name":"mode-production","mode":"production"}' |
16.5 MB | 12.6 MB | +30.35% |
| ⚡ | Memory | benchmark "many-chunks-commonjs", scenario '{"name":"mode-production","mode":"production"}' |
10.9 MB | 8.8 MB | +23.73% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/css-minify-selector-and-value-transforms (a1a0ba2) with main (21af4f5)
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
8 similar comments
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
…etch as a percentage
… tail A comma parts a `transition` into layers, each with its own set of slots, so the initial-keyword drop and the slot order now run per layer instead of declining the whole value. A `<position>` whose second value names the centre drops it, which the percentage rewrite could not reach past an offset.
`transform-origin` states both axes longhand and takes a z `<length>` after them, so naming `<position>` never reaches it. Derive those properties from everything the grammar accepts — the five edge keywords and an offset — which a third component keeps clear of the depth.
Grammar matching skips whitespace and a `)` fuses with nothing, so between two calls it separates the same tokens either way. Reading that off the value's shape rather than a property table reaches the prefixed spellings no dataset names, `filter`, a track list and an `@font-face` `src` — and retires the table, which held one name.
…lengths
A generic family is one identifier, so only a lone word can be read as one —
`Apple Color Emoji` is three, whatever the last of them spells. A shadow states
its offsets as `<length>{MIN,MAX}`, and the ones past MIN default to zero, so a
trailing `0` says nothing; the grammar states MIN, so the generator reads it.
Filter Effects 1 states, per function, the amount an omitted argument means, so writing that amount says nothing. The grammar marks which arguments are omittable, so the set is derived and only the amounts are stated. Also drops the shadow zero-length collapse: it saved raw bytes but cost more after gzip, which is what ships.
`import/order` sorts the generated exports, and `FILTER_FUNCTION_OMITTED` belongs before `FLEX_KEYWORDS`.
A shadow states its offsets as `<length>{MIN,MAX}`, and the ones past MIN
default to zero, so a trailing `0` says nothing; the grammar states MIN, so the
generator reads it. Also rewords the filter table's `omittable` to `optional`,
which cspell rejected.
A comma with nothing either side is a layer no shadow fills, so the value is kept as written.
Restarting a jest worker thread because it tripped --workerIdleMemoryLimit segfaults Bun 1.3.14 in thread teardown, a different wild address each time and workers_terminated short of workers_spawned. The runner's heaps sit at the 512MB the other runtimes use, so it fired constantly and the job died ~150s in. Reproduced locally: 30MB and 100MB crash, 4096MB and no limit pass.
9afb58a to
a1a0ba2
Compare
|
You have used all Bugbot PR reviews included in your team's free trial on GitHub. To continue using Bugbot reviews, enable Bugbot for your team in the Cursor dashboard. |
The limit bounds each worker thread's heap; without it the suite grows until the runner is out of memory. Dropping it in #21629 traded Bun's segfault for an OOM.
…om itself (#21646) * ci(bun): restore the worker idle memory limit The limit bounds each worker thread's heap; without it the suite grows until the runner is out of memory. Dropping it in #21629 traded Bun's segfault for an OOM. * ci(bun): end a Bun worker from itself instead of terminating it Bun segfaults when jest terminates a worker thread from the parent on an idle-memory restart, a different wild address each time and workers_terminated short of workers_spawned. Sending the end message jest already uses for shutdown lets the worker leave on its own. Guarded on Bun, so Node and Deno still terminate. Reproduced with a low limit: terminate crashes 5/5, ending 3/3 passes. * ci(bun): exit a Bun worker once it has ended, so the pool replaces it Ending a worker only removes its message listener; the thread leaves only if that was its last handle. A jest worker that leaked one therefore stayed alive in the RESTARTING state forever — reproduced with a worker holding a timer: the idle-memory limit tripped (1.63 MB over a 1 MB limit), the restart was requested, and the next task still ran on the same thread. The limit had become a no-op for that worker, which is the one thing it exists to do. The worker now exits itself after teardown, on Bun only. Same reproduction: the state returns to `ok` and the next task runs on a new thread.

Summary
Four more minify transforms, each one a spelling change that leaves the computed style identical: a
<position>written as edge keywords becomes the percentages it resolves to (background-position:left bottom→0%100%), a<transform-list>drops the whitespace between two functions the)already parts, afont-stretchkeyword becomes the percentage it names (condensed→75%), and a shorthand slot holding the value it already defaults to is dropped (transition:height .35s ease→height.35s).Every table is derived: the position axes are read off
<position>'s own[…] || […]branch, and the droppable shorthand keywords off each shorthand's top-level||of slots, so generation fails if either grammar moves. Only the percentages are stated inSUPPLEMENT, since no dataset carries them.The shorthand drop needs three conditions together, and each one is a rewrite that would otherwise turn a declaration the engine drops into one it accepts: exactly one slot may name the keyword (
animation'snoneis both a name and a fill mode), that slot must take keywords and nothing else (mask:url(a.svg) nonefills<mask-reference>twice and is dropped, so unwriting thenonewould revive it), and it must not be reached through a/. A sibling out of the same slot's keywords declines it at print time for the same reason.Measured on the benchmark fixtures: Bootstrap 230,290 → 230,187, Tailwind app 125,657 → 125,542. Rejected on the evidence:
background-position:left center→0(cssnano and lightningcss both emit it, butleftcomputes to0%and0to0px),flex:0 1 auto→0 auto(safe here, butflex:0 1 0→0 0is not, and the dropped token is a number rather than a keyword), and foldingcolor-mix()to a hex (Chrome computescolor(srgb …), so lightningcss's#fb2c3680is a different colour fromcolor(srgb 0.982628 0.171899 0.21316 / 0.5)).What kind of change does this PR introduce?
feat
Did you add tests for your changes?
Yes —
test/CssSyntax.unittest.jsgains four describes covering each transform and the cases it declines (500 tests in the file); theconfigCases/css/minimize-valuesandminimize-timing-functionssnapshots were updated in bothConfigTestandConfigCacheTest.Does this PR introduce a breaking change?
No. Minified output changes; every transform preserves the computed style.
If relevant, what needs to be documented once your changes are merged or what have you already documented?
n/a — no option or public API changes.
Use of AI
Claude Code was used throughout: to diff webpack's output against cssnano's and lightningcss's to find where the bytes were going, to write the generator collectors and the printer, and to write the tests. Every equivalence was checked in headless Chromium by comparing the element's whole computed style rather than the one property written, and each candidate was measured before and after — three were dropped on the evidence rather than shipped, as described above.
Generated by Claude Code
Note
Medium Risk
Minified CSS output changes across many properties; mistakes could alter rendering, though transforms are guarded and heavily tested for cases that would revive dropped declarations.
Overview
Extends webpack’s CSS minifier with four output-only transforms that keep computed styles the same while shrinking bytes.
<position>edge keywords on properties whose value is purely a position (e.g.background-position,object-position) rewrite to shorter percentages when that saves length (left top→0%0%), with guards for offsets, shorthands, and ambiguous cases.<transform-list>values join adjacent functions without extra space between)and the next function name.font-stretchnamed widths map to their spec percentages when shorter (condensed→75%).Shorthand values drop tokens that only restate a slot’s initial keyword (e.g.
transition:height .35s ease→height .35s), using a generatedSHORTHAND_INITIAL_KEYWORDSmap and strict rules so invalid “double slot” declarations are not revived.Lookup data is generated from mdn-data grammars in
tooling/generate-css-data.jsintolib/css/data.js; logic lives inlib/css/syntax.js. Unit tests and minimize config snapshots are updated accordingly.Reviewed by Cursor Bugbot for commit 425c742. Bugbot is set up for automated code reviews on this repo. Configure here.