docs(minifier): align guides with current implementation (#27584)

This commit is contained in:
Cameron authored and GitHub committed 2026-10-12 19:39:25 +01:00
1 parent 59472c5170
commit c993555ede
8 files changed
+204 -144

No files matched your search

+28 -20
View File
@@ -8,19 +8,22 @@ Build the best JavaScript minifier: smallest output, 100% correct, reasonably fa
### Architecture
- Fixed-point iteration in `PeepholeOptimizations::run_in_loop`
- Fixed-point iteration in `Compressor::run_in_loop`
- Arena allocation via `oxc_allocator`
- 17+ optimizations in `src/peephole/`
- Optimizations dispatched by `src/peephole/mod.rs`
- Pass completion and incremental semantic updates in `src/compression_pass.rs`
### Key Files
```
src/
├── lib.rs # Entry point
├── compressor.rs # Pipeline orchestration
├── ctx.rs # Shared utilities
├── state.rs # Optimization state
├── peephole/
├── lib.rs # Entry point
├── compressor.rs # Pipeline orchestration
├── compression_pass.rs # Pass completion and semantic updates
├── traverse_context/ # Traversal, AST mutation, and ECMAScript utilities
├── state.rs # Configuration and per-pass changes
├── symbol_state.rs # Symbol values, metadata, and liveness
└── peephole/
│ ├── mod.rs # Optimization dispatch
│ └── *.rs # Individual optimizations
```
@@ -53,7 +56,7 @@ Before selecting verification checks, read the [verification guidance](../../.ag
#### Optimization Structure
```rust
pub fn optimize_something(expr: &mut Expression, ctx: &mut Ctx) {
pub fn optimize_something<'a>(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
// Check if optimization applies
if !can_optimize(expr, ctx) {
return;
@@ -65,19 +68,24 @@ pub fn optimize_something(expr: &mut Expression, ctx: &mut Ctx) {
}
```
Always route slot replacements through the typed `ctx.replace_*` helpers —
they are the only way to signal a mutation: the mutation flag is private to
`MinifierState` (compiler-enforced), so a bare `*slot = …` write cannot mark
the pass as changed and will stall the fixed-point loop. For in-place edits
that don't replace a slot (operand swaps, field flips, element removals), call
`ctx.notice_change()`; if a raw slot write is genuinely required (e.g. an enum
slot with no typed helper), pair it with `ctx.notice_change()` and a comment
explaining why.
Use the typed `ctx.replace_*` helpers when replacing a slot. They record
discarded references and direct `eval` calls as well as requesting another pass.
When moving the old node into the replacement, use `replace_expression_with` or
`replace_statement_with`; report discarded subtrees inside the closure with
`ctx.drop_*`, and do not report nodes moved into the result as dropped.
For in-place edits that retain all subtrees (operand swaps or field flips), call
`ctx.notice_change()`. When removing a subtree from a collection or an optional
field, call the corresponding `ctx.drop_*` helper before discarding it.
`notice_change()` alone does not update semantic references. If a raw slot write
is required because no typed helper exists, record discarded subtrees, call
`ctx.notice_change()`, and explain why.
#### Traversal Hook
```rust
// In peephole/mod.rs
// Within impl<'a> Traverse<'a> for PeepholeOptimizations
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match expr {
Expression::BinaryExpression(_) => {
@@ -119,16 +127,16 @@ fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'
```bash
# Debug conformance failures
cargo coverage --debug
cargo coverage minifier --debug
# Test single file
cargo run -p oxc_minifier --example minifier test.js
cargo run -p oxc_minifier --example minifier -- test.js
# Test idempotency
cargo run -p oxc_minifier --example minifier test.js --twice
cargo run -p oxc_minifier --example minifier -- test.js --twice
```
### Key Utilities in `ctx.rs`
### Key Utilities in `traverse_context/`
- `eval_binary()` - Evaluate binary expressions
- `value_to_expr()` - Convert constant to AST node
+25 -9
View File
@@ -1,6 +1,7 @@
# Oxc Minifier
Next-generation JavaScript/TypeScript minifier achieving best-in-class compression.
JavaScript minifier focused on small output and performance. Transform TypeScript,
TSX, and JSX to JavaScript before minification.
## Inspiration
@@ -12,7 +13,7 @@ Next-generation JavaScript/TypeScript minifier achieving best-in-class compressi
## Key Features
- Maximum compression through exhaustive optimizations
- 100% correctness with comprehensive testing
- Correctness testing under the [documented assumptions](./docs/ASSUMPTIONS.md)
- Fixed-point iteration for optimal size
- Arena allocation for performance
@@ -21,28 +22,43 @@ Next-generation JavaScript/TypeScript minifier achieving best-in-class compressi
See [`tasks/minsize`](../../tasks/minsize) for compression benchmarks.
- Matching/beating esbuild on many libraries
- Full test262, Babel, TypeScript conformance
- Minifier idempotency checks on selected test262 and Babel cases
## Usage
```rust
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_minifier::{Minifier, MinifierOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
let options = MinifierOptions::default();
let minifier = Minifier::new(options);
let result = minifier.minify(&mut program);
let allocator = Allocator::default();
let parsed = Parser::new(&allocator, "const x = 1 + 1; console.log(x);", SourceType::mjs())
.parse();
assert!(parsed.diagnostics.is_empty());
let mut program = parsed.program;
let result = Minifier::new(MinifierOptions::default()).minify(&allocator, &mut program);
let output = Codegen::new()
.with_options(CodegenOptions { minify: true, ..CodegenOptions::default() })
.with_scoping(result.scoping)
.with_private_member_mappings(result.class_private_mappings)
.build(&program);
println!("{}", output.code);
```
## Testing Infrastructure
- `just minsize` - Track compression benchmarks
- `cargo coverage` - Conformance tests (test262, Babel, TypeScript)
- `cargo coverage minifier` - Minifier idempotency tests (test262 and Babel)
- `cargo coverage runtime` - Execute eligible test262 cases with Node.js
- `tasks/e2e` - Real-world E2E testing
## Development
- `just test` - Run all tests
- `cargo run -p oxc_minifier --example minifier` - Try the minifier
- `cargo run -p oxc_minifier --example minifier -- test.js` - Try the minifier
## Key Dependencies
@@ -53,7 +69,7 @@ let result = minifier.minify(&mut program);
## Documentation
- [Architecture](./docs/ARCHITECTURE.md) - Design and components
- [Optimizations](./docs/OPTIMIZATIONS.md) - Complete optimization catalog
- [Optimizations](./docs/OPTIMIZATIONS.md) - Optimization overview
- [Assumptions](./docs/ASSUMPTIONS.md) - Code assumptions for optimization
- [Correctness](./docs/CORRECTNESS.md) - Testing and validation
- [Roadmap](./docs/ROADMAP.md) - Development plan
+41 -39
View File
@@ -6,7 +6,7 @@ Achieve maximum compression through comprehensive optimizations while maintainin
### Size Optimization Strategy
- Fixed-point iteration until no more improvements
- Fixed-point iteration until no more changes or the iteration limit is reached
- Combine multiple optimization techniques
- Learn from all major minifiers
- Apply optimizations exhaustively
@@ -63,7 +63,7 @@ Key responsibilities:
### Peephole Optimizations (`peephole/`)
17+ transformation passes including:
Transformation modules dispatched together during a peephole traversal, including:
- Constant folding
- Dead code elimination
@@ -73,54 +73,56 @@ Key responsibilities:
Each optimization implements transformations in the AST traversal visitor pattern.
### Context (`ctx.rs`)
### Context (`traverse_context/`)
Shared utilities for optimizations:
The crate's `TraverseCtx<'a>` alias refers to `MinifierTraverseCtx<'a>`.
It provides the AST builder, ancestry, scoping, and `MinifierState`.
ECMAScript evaluation and mutation helpers are in `traverse_context/ecma_context.rs`.
- AST manipulation helpers
- Constant evaluation
- Side effect analysis
- Semantic utilities
Use typed `replace_*` helpers for replacements and `drop_*` helpers for discarded
subtrees. They record removed references and dropped direct `eval` calls as well
as requesting another pass. Use `replace_*_with` when moving an old node into its
replacement; the closure must report discarded subtrees. `notice_change()` is
for in-place edits that retain their subtrees.
```rust
pub struct Ctx<'a, 'b> {
// Provides access to:
// - AST builder
// - Scoping information
// - Symbol values
// - Optimization options
}
```
### State Management (`state.rs`, `symbol_state.rs`)
### State Management (`state.rs`)
`MinifierState` stores configuration, per-body state, private-member usage, and
per-pass changes. `SymbolState` groups symbol values, metadata, and liveness.
Tracks optimization state:
### Pass Completion (`compression_pass.rs`)
- Symbol values
- Changed flags
- Pure functions
- Source type
After normalization and each peephole traversal, pass completion prunes removed
semantic references, refreshes direct-eval scope flags when needed, and updates
function reachability. Its outcome drives `Compressor::run_in_loop`; debug
guards check for references that were incorrectly removed or left behind.
## Optimization Pipeline
```text
1. Parse JavaScript (caller)
2. Optional property-name mangling
└─> Collect, assign, and rewrite before compression
3. Compression (if enabled)
├─> Build semantic scoping
├─> Normalize and complete the normalization pass
└─> Repeat peephole traversal and pass completion
until no revisit is needed or the iteration limit is reached
4. Optional identifier/private-member mangling
└─> Rebuild semantics for the compressed AST
5. Code generation (caller)
└─> Pass scoping and class_private_mappings from MinifierReturn to Codegen
```
1. Parse and Build Semantic Model
└─> SemanticBuilder creates scoping and symbols
2. Normalization Pass
└─> Convert to canonical form for optimizations
`Minifier::minify` selects full compression. `Minifier::dce` selects tree-shaking
compression, which keeps folds needed to expose dead branches but skips transforms
whose only purpose is shorter syntax. Mangling remains controlled by the supplied
`MinifierOptions`.
3. Peephole Optimization Loop
└─> Apply all optimizations
└─> Check if any changes made
└─> Repeat until fixed-point (no changes)
4. Optional Mangling
└─> Rename variables for size
5. Code Generation
└─> Output optimized JavaScript
```
Callers of `Compressor::build_with_scoping` or
`dead_code_elimination_with_scoping` must supply scoping whose resolved-reference
lists match the current AST. Added references missing from those lists can cause
incorrect output; stale extra references can prevent optimizations.
## Memory Management
@@ -130,7 +132,7 @@ Tracks optimization state:
## Traversal Strategy
Using `oxc_traverse` for AST walking:
Using the crate's generated traversal through `minifier_traverse.rs`:
- Enter/exit hooks for each node type
- Bottom-up transformations (exit handlers)
+22 -8
View File
@@ -216,9 +216,26 @@ Property rewriting runs before compression. Computed keys that compression later
such as `['f' + 'oo_']`, are not revisited and can diverge from an eligible `obj.foo_` occurrence.
Exclude or reserve property names that are constructed this way.
### No Reliance on Function.prototype.name
### No Reliance on Function or Class Names
Code doesn't depend on function names being preserved. This assumption is held by default. This can be changed by settings `keepNames` option.
Code does not depend on function or class names being preserved by default.
In the Rust API, set `CompressOptions::keep_names` and, when identifier mangling
is enabled, `MangleOptions::keep_names` for the names you need to preserve.
When both stages are enabled in the Node.js API, configure both options:
- `compress.keepNames` takes an object with Boolean `function` and `class` fields.
- `mangle.keepNames` takes a Boolean (for both kinds of names) or an object with
Boolean `function` and `class` fields.
For example, to preserve both function and class names during compression and
identifier mangling:
```javascript
const options = {
compress: { keepNames: { function: true, class: true } },
mangle: { keepNames: true },
};
```
```javascript
// The minifier assumes this never happens:
@@ -231,6 +248,7 @@ if (myFunc.name !== "myFunc") throw Error();
Optional assumptions can be configured in the minifier options if your code requires different behavior.
```rust
// Selected fields; see src/options.rs for the complete definitions.
pub struct CompressOptions {
// Control optimization behavior
pub drop_console: bool,
@@ -261,15 +279,11 @@ pub struct TreeShakeOptions {
To ensure your code works with these assumptions:
1. **Test thoroughly**: Run your test suite on minified code
2. **Use conformance tests**: `cargo coverage` runs test262, Babel, TypeScript tests
3. **Node.js compatibility**: The minifier is validated against Node.js compatibility tables
2. **Check idempotency**: `cargo coverage minifier` checks selected test262 and Babel cases
3. **Check runtime behavior**: `cargo coverage runtime` executes eligible test262 cases with Node.js
4. **Try real-world code**: Test with your actual application
5. **Report issues**: If valid code breaks, file an issue
### Node.js Compatibility Testing
The minifier includes comprehensive validation against Node.js compatibility tables to ensure optimizations work correctly across different Node.js versions and environments.
## When Assumptions Don't Hold
If your code violates these assumptions, you may:
+33 -40
View File
@@ -2,16 +2,19 @@
## Our Commitment
The Oxc minifier maintains correctness through comprehensive testing and validation.
The Oxc minifier aims to preserve JavaScript behavior under the
[documented assumptions](./ASSUMPTIONS.md). Tests provide regression coverage;
they do not prove correctness for every program.
## Validation Infrastructure
### Conformance Testing
- **test262**: Full ECMAScript specification compliance
- **Babel**: Modern JavaScript patterns and transforms
- **TypeScript**: Type-aware transformations
- **Real-world**: Top npm packages and frameworks
`cargo coverage minifier` runs compression/code-generation idempotency checks on
selected test262 and Babel cases. The test262 runner skips parse-negative and
`noStrict` cases; the Babel runner skips expected failures and TypeScript sources.
The minifier has no TypeScript conformance suite or type-aware transformations.
Transform TypeScript, TSX, and JSX to JavaScript before minification.
Run conformance tests:
@@ -41,15 +44,16 @@ just minsize
# See tasks/minsize/minsize.snap for results
```
### Differential Testing
### Runtime Testing
- Input vs output behavioral equivalence
- Cross-validation with other minifiers
- Fuzzing with randomly generated valid JavaScript
`cargo coverage runtime` runs eligible test262 cases through codegen, transformer,
and minifier pipelines and executes the outputs with Node.js (>= 22).
`tasks/e2e` also contains Node.js runtime tests for the transformer and minifier.
Idempotency alone does not establish behavioral equivalence.
### Semantic Preservation
Every optimization preserves:
Within the documented assumptions, optimizations are intended to preserve:
- Variable scoping and bindings
- Execution order and side effects
@@ -61,8 +65,11 @@ Every optimization preserves:
### Temporal Dead Zones (TDZ)
Code must not violate a TDZ. Preserving the resulting `ReferenceError` is not
guaranteed; see [No TDZ Violation](./ASSUMPTIONS.md#no-tdz-violation).
```javascript
// Correctly preserves TDZ semantics
// Outside the minifier's assumptions: this code violates the TDZ.
{
console.log(x); // ReferenceError
let x = 1;
@@ -108,7 +115,8 @@ function* gen() {
### Type Coercion
All JavaScript coercion rules are preserved using `oxc_ecmascript` conversions:
Constant evaluation and side-effect analysis use `oxc_ecmascript` conversions,
subject to the documented assumptions about coercion purity and exceptions:
- String coercion (`ToJsString`)
- Number coercion (`ToNumber`)
@@ -131,35 +139,25 @@ cargo test -p oxc_minifier
Real-world code testing:
```bash
cd tasks/e2e
pnpm test
# From the repository root, build the bindings first
pnpm --filter "./napi/minify" --filter "./napi/transform" run build-test
pnpm --dir tasks/e2e test
```
### Idempotency Testing
Minifying twice produces same result:
The conformance runner checks whether a second compression produces the same
output. The example can also report whether a particular input is idempotent:
```bash
cargo run -p oxc_minifier --example minifier test.js --twice
cargo run -p oxc_minifier --example minifier -- test.js --twice
```
## Correctness Tools
## Semantic Bookkeeping Checks
### Semantic Validator
Validates that transformations preserve semantics.
### AST Differ
Shows exact changes made to the AST.
### Behavior Comparison
Compares runtime behavior of original vs minified code.
### Side Effect Tracker
Ensures side effects occur in correct order.
Debug builds run over-prune and under-prune guards in `src/compression_pass.rs`
to catch inconsistencies between live AST references and semantic scoping.
These guards check bookkeeping, not arbitrary runtime equivalence.
## Known Limitations
@@ -180,11 +178,6 @@ If you find correctness issues:
## Continuous Validation
Our CI runs:
- Full conformance test suite
- Size benchmarks
- Real-world package tests
- Fuzzing tests
Every PR must pass all correctness tests.
Check the current [CI workflow](../../../.github/workflows/ci.yml) for
configured checks. Choose local verification using the repository's
[verification guidance](../../../.agents/verification.md).
+35 -14
View File
@@ -1,10 +1,11 @@
# Optimization Catalog
Complete list of optimizations for maximum size reduction.
Overview of implemented optimization families. Examples illustrate individual
transforms; other passes may further simplify or remove the shown output.
Many optimizations rely on [`oxc_ecmascript`](../../oxc_ecmascript) for ECMAScript operations like constant evaluation, type conversion, and side effect analysis.
## Current Optimizations (18)
## Current Optimizations
### Constant Folding
@@ -72,7 +73,7 @@ a && b();
!(!x || !y);
// After
x && y;
!!(x && y);
// Before
!!true;
@@ -115,7 +116,7 @@ obj.property;
### Template Literal Optimization
**Module**: `inline.rs`
**Module**: `fold_constants.rs`, `substitute_alternate_syntax.rs`
**Size Impact**: Medium
**Description**: Simplifies template literals
@@ -137,11 +138,9 @@ obj.property;
```javascript
// Before
"test".indexOf("e");
Math.pow(2, 3);
// After
"test".indexOf("e"); // or optimized form
2 ** 3;
```
@@ -234,19 +233,21 @@ x ? true : false;
```javascript
// Before
if (x === true) {
if (!!x) {
run();
}
// After
if (x) {
run();
}
```
### Variable Inlining
**Module**: `inline.rs`
**Module**: `inline.rs`, `minimize_statements.rs`
**Size Impact**: Medium
**Description**: Inlines single-use variables
**Description**: Propagates known constants and inlines eligible single-use variables
```javascript
// Before
@@ -257,7 +258,27 @@ console.log(x);
console.log(5);
```
## Planned Optimizations
### Switch Statement Optimization
**Module**: `minimize_switch_statements.rs`
**Description**: Removes unreachable cases for known discriminants; related switch
folding is dispatched from `peephole/mod.rs`.
### Unused Private Member Removal
**Module**: `remove_unused_private_members.rs`
**Description**: Removes eligible unused private class members.
### IIFE Simplification
**Module**: `substitute_alternate_syntax.rs`
**Description**: Simplifies eligible immediately invoked function expressions,
including empty bodies and parameterless single-expression arrow functions.
## Ideas for Further Optimization
These are possible future directions, not implemented features or commitments.
The minifier accepts JavaScript; TypeScript enum lowering belongs in the transformer.
### From Closure Compiler
@@ -269,7 +290,6 @@ console.log(5);
### From Terser
- **Switch statement optimization**: Simplify switch statements
- **Advanced array/object patterns**: Recognize and optimize patterns
- **String optimizations**: Join strings, optimize concatenation
- **RegExp optimizations**: Simplify regular expressions
@@ -278,11 +298,11 @@ console.log(5);
- **CSS-in-JS optimization**: Optimize styled components
- **Import optimization**: Remove unused imports
- **Enum inlining**: Replace enum access with values
- **Enum-like object inlining**: Replace JavaScript object property access with values
## Size Optimization Techniques
1. **Multiple passes**: Run optimizations until fixed-point
1. **Multiple passes**: Run optimizations until fixed-point or the iteration limit
2. **Optimization ordering**: Some optimizations enable others
3. **Pattern matching**: Recognize common code patterns
4. **Syntactic substitution**: Always prefer shorter forms
@@ -297,4 +317,5 @@ Some optimizations enable others:
- **Inlining** → enables further constant folding
- **Dead code removal** → enables more inlining
The fixed-point iteration ensures all optimization opportunities are found.
Repeated passes let implemented optimizations expose further opportunities, subject
to safety checks, options, and the iteration limit.
+14 -10
View File
@@ -1,5 +1,9 @@
# Development Roadmap
This document distinguishes implemented foundations from possible future work.
Unchecked items are ideas, not committed features or delivery dates. The
[optimization catalog](./OPTIMIZATIONS.md) describes current transforms.
## Phase 1: Foundation ✓
- [x] Basic peephole optimizations
@@ -8,7 +12,7 @@
- [x] Test infrastructure (minsize, coverage, e2e)
- [x] Fixed-point iteration
## Phase 2: Complete Optimization Suite ✓
## Phase 2: Optimization Suite
- [x] Port safe optimizations from Closure Compiler
- [x] Port core optimizations from Terser/UglifyJS
@@ -17,24 +21,24 @@
- [x] Template literal optimization
- [x] Array/object patterns
## Phase 3: Advanced Optimizations (Current)
## Phase 3: Further Optimization Ideas
- [ ] Function inlining (when provably safe)
- [ ] Switch statement optimization
- [ ] Broader function inlining (limited IIFE simplification is already implemented)
- [x] Switch statement optimization (known-discriminant case removal and switch folding)
- [ ] Advanced string concatenation (extending `oxc_ecmascript` string operations)
- [ ] Enum unboxing
- [ ] Property collapsing
- [ ] Better RegExp optimization
- [ ] Loop optimizations
- [ ] Broader loop optimizations (while/for normalization and for-statement transforms exist)
- [ ] Cross-module optimization
- [ ] Perfect side effect analysis (extending `oxc_ecmascript` capabilities)
- [ ] More precise side effect analysis (extending `oxc_ecmascript` capabilities)
- [ ] Advanced DCE with escape analysis
- [ ] Type-aware optimizations (from TS types)
- [ ] Additional JavaScript value analysis (the minifier does not consume TypeScript types)
- [ ] Framework-specific optimizations (React, Vue, Angular)
## Phase 4: Production Ready
## Further Infrastructure Ideas
- [ ] Differential testing framework
- [ ] Broader differential testing (test262 runtime checks and Node.js E2E tests already exist)
- [ ] Fuzzing infrastructure
- [ ] Performance optimization
- [ ] Source map improvements
@@ -43,7 +47,7 @@
## Goals
**Size**: Beat Closure Compiler, smallest output for top npm packages
**Correctness**: 100% test262/Babel/TypeScript conformance
**Correctness**: Preserve JavaScript behavior under documented assumptions, with test262/Babel idempotency and runtime regression coverage
**Performance**: < 2x slower than esbuild, < 10x faster than Terser
**Adoption**: Integration in major build tools
+6 -4
View File
@@ -1,6 +1,7 @@
//! # Oxc Minifier
//!
//! High-performance JavaScript/TypeScript minifier focused on maximum compression.
//! High-performance JavaScript minifier focused on maximum compression.
//! Transform TypeScript, TSX, and JSX to JavaScript before minification.
//!
//! ## Overview
//!
@@ -11,9 +12,10 @@
//!
//! ## Features
//!
//! - **Maximum Compression**: Fixed-point iteration ensures all optimization opportunities are found
//! - **Comprehensive Optimizations**: 17+ transformation passes and growing
//! - **100% Correct**: Extensive testing with test262, Babel, and TypeScript test suites
//! - **Compression**: Repeated passes expose further optimization opportunities
//! - **Comprehensive Optimizations**: Constant folding, dead code removal, and syntax simplification
//! - **Correctness Testing**: Selected test262/Babel idempotency checks and Node.js runtime tests
//! under the [documented assumptions](https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/docs/ASSUMPTIONS.md)
//! - **Fast**: Efficient algorithms and arena allocation for performance
//!
//! ## Example