Skip to content

refactor(minifier): remove all the unnecessary fake ast passes#8618

Merged
Boshen merged 1 commit into
mainfrom
01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes
Jan 20, 2025
Merged

refactor(minifier): remove all the unnecessary fake ast passes#8618
Boshen merged 1 commit into
mainfrom
01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes

Conversation

@Boshen

@Boshen Boshen commented Jan 20, 2025

Copy link
Copy Markdown
Member

This also removes handling of making cjs-module-lexer to work.

@graphite-app

graphite-app Bot commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@github-actions github-actions Bot added A-minifier Area - Minifier C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior labels Jan 20, 2025
This also removes handling of making cjs-module-lexer to work.
@Boshen Boshen force-pushed the 01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes branch from b0e99fa to 18ad6d8 Compare January 20, 2025 12:56
@codspeed-hq

codspeed-hq Bot commented Jan 20, 2025

Copy link
Copy Markdown

CodSpeed Performance Report

Merging #8618 will degrade performances by 5.15%

Comparing 01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes (18ad6d8) with main (787aaad)

Summary

❌ 3 regressions
✅ 29 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark main 01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes Change
minifier[antd.js] 197.6 ms 205.1 ms -3.64%
minifier[react.development.js] 1.9 ms 2 ms -5.15%
minifier[typescript.js] 348 ms 363.2 ms -4.18%

@Boshen

Boshen commented Jan 20, 2025

Copy link
Copy Markdown
Member Author

Will fix the performance regression.

@Boshen Boshen merged commit 6f95cd5 into main Jan 20, 2025
@Boshen Boshen deleted the 01-20-refactor_minifier_remove_all_the_unnecessary_fake_ast_passes branch January 20, 2025 14:05
Boshen added a commit that referenced this pull request Jan 24, 2025
## [0.48.0] - 2025-01-24

- 54d0fac span: [**BREAKING**] Remove `PartialEq` impl for `&Atom`
(#8642) (overlookmotel)

### Features

- 2a2ad53 allocator: Add `Allocator::capacity` and `used_bytes` methods
(#8621) (overlookmotel)
- 6801c81 allocator: Add `Allocator::new` and `with_capacity` methods
(#8620) (overlookmotel)
- 99607d3 codegen: Print comments in `TSTypeLiteral` (#8679) (Boshen)
- 4ae568e linter: Add DiagnosticResult to the Reporters for receiving a
sub part result (#8666) (Alexander S.)
- 343690e minifier: Replace `Number.*_SAFE_INTEGER`/`Number.EPSILON`
(#8682) (sapphi-red)
- 0c5bb30 minifier: Replace
`Number.POSITIVE_INFINITY`/`Number.NEGATIVE_INFINITY`/`Number.NaN`
(#8681) (sapphi-red)
- 835b258 minifier: Compress `typeof foo === 'object' && foo !== null`
to `typeof foo == 'object' && !!foo` (#8638) (sapphi-red)
- 2bcbed2 minifier: Compress `(a = b) === null || a === undefined` to
`(a = b) == null` (#8637) (sapphi-red)

### Bug Fixes

- 40316af linter: Fix github `endColumn` output (#8647) (Alexander S.)
- 883d25b minifier: Keep esm in dce (#8677) (Boshen)
- 878ce10 minifier: `void 0` equals to `undefined` (#8673) (Boshen)
- ba201a6 minifier: Remove "non esbuild optimizations" which is
incorrect (#8668) (Boshen)
- 8c8b5fa minifier: Avoid minifing `String(a)` into `"" + a` for symbols
(#8612) (翠 / green)
- 4ff6e85 minifier: Remove expression statement `void 0` (#8602)
(Boshen)
- 93d643e minifier: Keep side effects when folding const conditional
exprs (#8591) (camc314)
- 178c232 parser: Parse `intrinsic` TS keyword (#8627) (Kevin Deng 三咲智子)
- 48717ab parser: Parse `true` as `TSLiteralType` (#8626) (Kevin Deng
三咲智子)
- d1c5dc4 semantic: Fix const assertions in `UnresolvedReferencesStack`
(#8653) (overlookmotel)

### Performance

- 787aaad allocator: Make `String` non-drop (#8617) (overlookmotel)
- d966e0a codegen: Do not check for comments if turned off (#8598)
(Boshen)
- 3fa87ff lexer: Peak 2 bytes after `!` (#8662) (Boshen)
- 9953ac7 minifier: Add `LatePeepholeOptimizations` (#8651) (Boshen)
- 00dc63f minifier: Only substitute typed array constructor once (#8649)
(Boshen)
- 3e19e4e minifier: Remove the useless empty statement removal code in
statement fusion (#8646) (Boshen)
- 5b3c412 minifier: Only run optimizations on local changes (#8644)
(Boshen)

### Documentation

- c1d243b allocator: Improve docs for `Allocator` (#8623)
(overlookmotel)
- 01a5e5d allocator: Improve docs for `HashMap` (#8616) (overlookmotel)
- 87568a1 allocator: Reformat docs (#8615) (overlookmotel)
- 3be0392 lexer: Fix doc comment (#8664) (overlookmotel)
- 5029547 semantic: Fix and reformat doc comments (#8652)
(overlookmotel)

### Refactor

- ae8db53 allocator: Move `Allocator` into own module (#8656)
(overlookmotel)
- 0f85bc6 allocator: Reduce repeat code to prevent `Drop` types in arena
(#8655) (overlookmotel)
- de76eb1 allocator: Reorder `Box` methods (#8654) (overlookmotel)
- 997859c ast: Align `#[estree(via)]` behavior (#8599) (sapphi-red)
- db863a3 codegen: Use `Stack` for `binary_expr_stack` (#8663) (Boshen)
- 8cce69a codegen: Remove `match_member_expression` (#8597) (Boshen)
- a3dc4c3 crates: Clean up snapshot files (#8680) (Boshen)
- e66da9f isolated_declarations, linter, minifier, prettier, semantic,
transformer: Remove unnecessary `ref` / `ref mut` syntax (#8643)
(overlookmotel)
- 23b49a6 linter: Use `cow_to_ascii_lowercase` instead
`cow_to_lowercase` (#8678) (Boshen)
- ce2b9da minifier: Remove `wrap_to_avoid_ambiguous_else` (#8676)
(Boshen)
- 75a579b minifier: Clean up
`has_no_side_effect_for_evaluation_same_target` (#8675) (Boshen)
- 1bb2539 minifier: Move more code into `minimize_conditions` local loop
(#8671) (Boshen)
- 13e4a45 minifier: Move conditional assignment to `minimize_conditions`
(#8669) (Boshen)
- ae895d8 minifier: Use `NonEmptyStack` for function stack (#8661)
(Boshen)
- 3802d28 minifier: Clean up `try_minimize_conditional` (#8660) (Boshen)
- dcc1f2b minifier: Rename `ast_passes` to `peephole` (#8635) (Boshen)
- 52458de minifier: Remove unused code and traits (#8632) (Boshen)
- 6f95cd5 minifier: Remove all the unnecessary fake ast passes (#8618)
(Boshen)
- 712cae0 minifier: Run the compressor on all test cases (#8604)
(Boshen)
- 864b8ef parser: Shorten code (#8640) (overlookmotel)
- b8d9a51 span: Deal only in owned `Atom`s (#8641) (overlookmotel)
- 20f52b1 span: Remove unnecessary lifetimes on `Atom` impls (#8639)
(overlookmotel)
- ac4f98e span: Derive `Copy` on `Atom` (#8596) (branchseer)
- a730f99 transformer: Move `create_prototype_member` to utils module
(#8657) (Dunqing)
- 61d96fd transformer/class-properties: Correct comments (#8636)
(overlookmotel)

### Testing

- 39dbd2d codegen: Fix snapshot file (#8685) (Boshen)
- d9f5e7f minifier: Enable passed esbuild tests (Boshen)

Co-authored-by: Boshen <[email protected]>
Dunqing added a commit that referenced this pull request May 26, 2026
…lexer hint

esbuild emits unreachable `0 && (module.exports = { ... })` statements on
Node platform as parser hints for cjs-module-lexer. The real exports happen
through helper calls the lexer can't trace, so it relies on the hint shape
to discover named CJS exports.

DCE correctly identifies the expression as unreachable and folds it away,
which silently breaks ESM `import { X } from "<cjs-pkg>"` consumers — e.g.
`@tailwindcss/postcss` throws `SyntaxError: no export named 'clearRequireCache'`
after minifying `@tailwindcss/node/dist/require-cache.js`.

Bail out of the fold when the right side of `<falsy> && X` is an assignment
to `module.exports`, `module.exports.X`, or `exports.X`. This restores the
guard added in #4878 and dropped during the #8618 refactor.
Dunqing added a commit that referenced this pull request May 26, 2026
…lexer hint

esbuild emits unreachable `0 && (module.exports = { ... })` statements on
Node platform as parser hints for cjs-module-lexer. The real exports happen
through helper calls the lexer can't trace, so it relies on the hint shape
to discover named CJS exports.

DCE correctly identifies the expression as unreachable and folds it away,
which silently breaks ESM `import { X } from "<cjs-pkg>"` consumers — e.g.
`@tailwindcss/postcss` throws `SyntaxError: no export named 'clearRequireCache'`
after minifying `@tailwindcss/node/dist/require-cache.js`.

Bail out of the fold when the right side of `<falsy> && X` is an assignment
to `module.exports`, `module.exports.X`, or `exports.X`. This restores the
guard added in #4878 and dropped during the #8618 refactor.
Dunqing added a commit that referenced this pull request May 26, 2026
…lexer hint

esbuild emits unreachable `0 && (module.exports = { ... })` statements on
Node platform as parser hints for cjs-module-lexer. The real exports happen
through helper calls the lexer can't trace, so it relies on the hint shape
to discover named CJS exports.

DCE correctly identifies the expression as unreachable and folds it away,
which silently breaks ESM `import { X } from "<cjs-pkg>"` consumers — e.g.
`@tailwindcss/postcss` throws `SyntaxError: no export named 'clearRequireCache'`
after minifying `@tailwindcss/node/dist/require-cache.js`.

Bail out of the fold when the right side of `<falsy> && X` is an assignment
to `module.exports`, `module.exports.X`, or `exports.X`. This restores the
guard added in #4878 and dropped during the #8618 refactor.
Dunqing added a commit that referenced this pull request May 26, 2026
…lexer hint

esbuild emits unreachable `0 && (module.exports = { ... })` statements on
Node platform as parser hints for cjs-module-lexer. The real exports happen
through helper calls the lexer can't trace, so it relies on the hint shape
to discover named CJS exports.

DCE correctly identifies the expression as unreachable and folds it away,
which silently breaks ESM `import { X } from "<cjs-pkg>"` consumers — e.g.
`@tailwindcss/postcss` throws `SyntaxError: no export named 'clearRequireCache'`
after minifying `@tailwindcss/node/dist/require-cache.js`.

Bail out of the fold when the right side of `<falsy> && X` is an assignment
to `module.exports`, `module.exports.X`, or `exports.X`. This restores the
guard added in #4878 and dropped during the #8618 refactor.
graphite-app Bot pushed a commit that referenced this pull request May 26, 2026
…lexer hint (#22729)

## Summary

esbuild emits unreachable `0 && (module.exports = { ... })` statements on Node platform as parse-time hints for [`cjs-module-lexer`](https://github.com/nodejs/cjs-module-lexer). The actual exports happen through helper calls (`__export(...)` or similar) that the lexer can't trace, so it scans for the literal `module.exports = { ... }` shape inside this dead-code expression to discover named exports.

Exact emission site (esbuild v0.28.0): [`internal/linker/linker.go#L5127-L5138`](https://github.com/evanw/esbuild/blob/v0.28.0/internal/linker/linker.go#L5127-L5138). Always `LogicalAnd(0, Assign(module.exports, ObjectExpression))` — no other shape.

oxc's DCE judges the expression by JS-runtime semantics — `false && X` is unreachable, drop it — and is correct on those grounds. But this expression carries **extra-semantic information** the lexer reads. Dropping it silently breaks ESM consumers:

```js
import { clearRequireCache } from "@tailwindcss/node/require-cache";
//       ^^^^^^^^^^^^^^^^^
// SyntaxError: The requested module does not provide an export named 'clearRequireCache'
```

Observed in monitor-oxc CI: https://github.com/oxc-project/monitor-oxc/actions/runs/26427515847/job/77794148798

This is the same *class* of bug as #22475 (string quotes for `require()`) and #22552 (consolidated codegen helpers) — output properties that matter to downstream tooling, lost by minifier passes that only see JS semantics.

## Fix

### Commit 1: bailout in `try_fold_and_or`

Restores the guard added in #4878 and dropped during the #8618 refactor. In the constant-fold shortcut at `fold_constants.rs`, when `lval == false && op == And` and the right operand is a `module.exports = { ... }` assignment, return `None` instead of folding `0 && X → 0`.

### Commit 2: close remaining elimination paths

Code review surfaced two bypasses around the first guard:

1. **`remove_unused_logical_expr` (separate elimination path)** — when `treeshake.property_write_side_effects: false` (a documented rolldown/vite option), the assignment reports no side effects, the whole `0 && (module.exports = {...})` reports no side effects, and `try_fold_expression_stmt` → `remove_unused_expression` → `remove_unused_logical_expr` drops the entire ExpressionStatement before the fold-constants guard ever runs. Added a matching guard at `remove_unused_expression.rs`.

2. **Compound assignments + paren-wrapping** — the helper matched `module.exports ||= X` (never emitted) and didn't strip `ParenthesizedExpression` uniformly. Tightened to require `AssignmentOperator::Assign` and apply `get_inner_expression()` at the top.

## Helper scope

`is_cjs_module_exports_hint` matches **exactly esbuild's emission shape**: an `=` assignment with LHS `module.exports`. Verified against the source — esbuild never emits `exports.X = v`, `module.exports.X = v`, `||` form, or compound operators as hints.

Not handled (intentional):

- `0 && (exports.X = v)` — esbuild doesn't emit it.
- `0 && (module.exports.X.Y = v)` — esbuild doesn't emit it.
- Locally-shadowed `module`/`exports` — the lexical check over-preserves harmlessly. Threading `ctx.is_global_reference` through would expand the helper's surface for no observed bug.

## Tests

`test_preserve_cjs_module_lexer_hint` in `tests/peephole/fold_constants.rs` covers the positive shape + negative cases (compound assignments fold; non-export RHS folds).

Smoke-tested against `@tailwindcss/node/dist/require-cache.js`: hint line survives DCE.

Stacks on #22722.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-minifier Area - Minifier C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant