Latest Results
feat(minifier): drop write-only property assignments to unused local bindings by default
Terser parity: `function A() {} A.from = () => {};` and the ubiquitous
bundled-CJS shape `var o = function(){...}; o.displayName = 'X';` now drop
entirely under default options, instead of only under the non-default
`treeshake.property_write_side_effects: false` opt-in. One `displayName`
write no longer keeps a whole module's require-chain alive — antd.js in the
minsize benchmark shrinks 2.22 MB -> 2.13 MB minified (458.44 kB ->
455.53 kB gzip); all other fixtures are unchanged.
Mechanism: a persistent, program-wide hazard set
(`MinifierState::member_write_hazard_symbols`), seeded by `Normalize` before
the fixed-point loop (execution-order independent, unlike the per-pass
`proto_write_symbols` the opt-in path uses) and extended mid-loop when a
pass forms a new compound assignment (`mark_assignment_target_as_read`).
A plain `=` member write on a provably-unused fresh local drops only when
the base symbol carries no hazard:
- read-modify ops (`o.x += 1`, `o.y ||= 2`, `o.x++`) read the property, so
a sibling plain write's value is observable (`o.x = {valueOf: f}` must
keep `f()` reachable);
- bases of chained writes (`a` in `a.b.c = 1`) — dropping `a.b = {}` would
throw at runtime;
- `__proto__` / non-literal computed keys may install setters (including
from a hoisted function that runs before the write).
The write itself must be a single-level member with a key that provably
isn't `__proto__`; `o[k()] = v` bails. Impure RHS is hoisted in place
(`o.x = impure()` -> `impure()`), which also improves the opt-in path.
Full-minify only: DCE mode is rolldown's per-module tree-shaking
preprocess, and `property_write_side_effects` stays rolldown's knob there.
A kind-aware key denylist keeps writes that would themselves throw a
strict-mode `TypeError` or observably coerce, so those are never dropped:
the non-writable own `name` / `length` and the `caller` / `arguments`
poison of a function or class, a class's non-writable `prototype` (a plain
function's `prototype` is writable, so it still drops), an instance-private
brand-check write (`o.#x = 1`), and an `Array` `length` write (which can
throw a `RangeError` or run a `valueOf` coercion; a computed string key
`o["length"]` counts too). The freshness check is tightened so these bases
are recognised: a class with a static block, any decorator, or any static
accessor — getter as well as setter — is not a fresh value.
Accepted unsoundness (documented in docs/ASSUMPTIONS.md): the only remaining
case is a setter installed on `Object.prototype` / `Function.prototype` /
`Array.prototype`, which these writes do not observe — the same class the
opt-in flag already assumes. Keeping the TypeError-throwing writes restores
three test262 strict-mode cases (`function/13.2-*`), reverted in
`runtime.snap`.
Downstream note: rolldown full-minify snapshot churn is expected; DCE-mode
output is unchanged.minifier-write-only-prop-dce Latest Branches
0%
0%
07-07-fix_formatter_print_before_trailing_comments_part_2 0%
liangmiQwQ:fix/handle-vitest-vite-plus © 2026 CodSpeed Technology