feat(minifier): inline const value for read-only vars (#22593)

## Summary

The minifier skipped constant-inlining for **every** `var`, blaming TDZ. The real constraint is narrower — `var` *hoisting* — and most `var`s are still safe to inline. This lifts the blanket skip and inlines a `var`'s constant whenever no read can observe its hoisted `undefined`. Closes #13051.

## Why

A `var x = <literal>;` binds `x` to `undefined` at the top of its scope and assigns the literal only in source order. A read in that window sees `undefined`, so inlining the literal everywhere would change behavior there — which is why the old code skipped all `var`s. But that also blocked a common, perfectly safe pattern from optimizing in DCE mode:

```js
var DEBUG = false;
function check() { if (DEBUG) console.log('debug'); }
console.log(check());
```

vite 8's `topLevelVar: true` rewrites top-level `let`/`const` to `var`, so users' flag patterns silently stopped tree-shaking — vitejs/vite#22363 → rolldown/rolldown#9279.

## The safety principle

Inline only when the value at the read is provably the assigned literal, never the hoisted `undefined`. Concretely, no user code may run between scope entry and the assignment that could read `x`. A `var x = <literal>;` qualifies when all of:

- **It is in a still-declarative body.** It sits directly in the current function/program body, and every preceding statement is *declarative* — runs no user code (`is_declarative_body_statement`: function/empty/type-only declarations, module re-exports, and literal-initialised simple `var`s). The first non-declarative statement ends the body's "declarative prelude"; this is also enforced per-declarator for multi-declarator statements.
- **Its initializer is a `ConstantValue`.**
- **It is not a top-level script var** (those alias the global object).
- **At program scope, the module loads no other module** (`import` / `export … from` / `export * from`) — a cyclic importer could call into our exports and observe a captured var before assignment, regardless of export status.
- **It has exactly one read, crossing a function boundary** — the gap that `substitute_single_use_symbol` (same call frame) and the small-value inliner don't fill. This also keeps the change byte-positive: dropping `var x = L;` and replacing one read with `L` always saves bytes.

The prelude flag is a per-body stack pushed/popped at function boundaries, so the analysis runs independently for nested functions. Reassignment is handled by the existing `write_references_count` guard, and direct-eval scopes are excluded.

**Verification.** A battery of hoisting / closure / reassignment programs was run through the minifier and then *executed*, checking every output reproduces the original's observable behavior. All pass — output is semantically equal to source on each, matching Terser.

## Comparison with other minifiers

Behaviour checked against Terser 5.48, esbuild 0.24, and SWC 1.15:

- **esbuild** only propagates `const` primitives — it never inlines a `var` cross-scope. So `const DEBUG = false; …` folds to `console.log(void 0)` but `var DEBUG = false; …` stays. That is exactly the regression path: `topLevelVar: true` rewrites `const`→`var`, defeating const-only propagation. oxc had the same blanket-`var` skip; this PR lifts it for the provably-safe subset.
- **Terser / SWC** reach the same result (and more — flow-sensitive reassignment, call inlining) via multi-pass `reduce_vars`/`collapse_vars`. This PR recovers the common flag pattern with one syntactic prelude check instead of that machinery, staying single-pass.

## Results

`minsize` shows small gzip improvements on lodash / victory / antd / typescript; jquery is byte-identical.

Closes #13051
This commit is contained in:
Dunqing committed 2026-06-16 02:09:14 +00:00
1 parent bcb3894d76
commit 09762d9224
6 files changed
+406 -17

No files matched your search

+65 -3
View File
@@ -2,6 +2,7 @@ use crate::generated::ancestor::Ancestor;
use oxc_ast::ast::*;
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ConstantValue};
use oxc_span::GetSpan;
use oxc_syntax::{scope::ScopeId, symbol::SymbolId};
use crate::TraverseCtx;
@@ -11,9 +12,12 @@ impl<'a> PeepholeOptimizations {
pub fn init_symbol_value(decl: &VariableDeclarator<'a>, ctx: &mut TraverseCtx<'a>) {
let BindingPattern::BindingIdentifier(ident) = &decl.id else { return };
let Some(symbol_id) = ident.symbol_id.get() else { return };
let value = if decl.kind.is_var() || Self::is_for_statement_init(ctx) {
// - Skip constant value inlining for `var` declarations, due to TDZ problems.
// - Set None for for statement initializers as the value of these are set by the for statement.
let value = if Self::is_for_statement_init(ctx) {
// for-statement initializers have their value set by the for statement itself.
None
} else if decl.kind.is_var() && !Self::is_hoisted_var_inlineable(decl, symbol_id, ctx) {
// `var` is hoisted: reads before the initializer line see `undefined`.
// Skip unless the safety predicate proves no such read exists.
None
} else {
decl.init.as_ref().map_or(Some(ConstantValue::Undefined), |e| e.evaluate_value(ctx))
@@ -22,6 +26,64 @@ impl<'a> PeepholeOptimizations {
ctx.init_value(symbol_id, value, is_fresh_value);
}
/// Predicate for inlining a hoisted `var x = <literal>;`. True when no read
/// can observe `x` as its hoisted `undefined`:
/// - the declarator sits at the current body's top scope and that body is
/// still in its declarative prelude;
/// - it has an initializer (uninitialized `var foo;` would inline to
/// `undefined`, which churns existing tests for marginal benefit);
/// - script-mode top-level vars are excluded (they alias the global object);
/// - at program scope, if the module loads any other module (`import`,
/// `export … from`, `export * from`), skip: a cyclic importer can call
/// into our exports and observe any var our exported functions/classes
/// close over, regardless of export status;
/// - exactly one read, and it sits inside a nested function/arrow body
/// (multi-use and same-call-frame reads are handled by
/// `inline_identifier_reference`'s small-value rule or by
/// `substitute_single_use_symbol`).
///
/// Limitation: the constant is recorded here at the declarator's exit, so a
/// reader in a function declared *before* the var in source order has
/// already been visited and won't be inlined. Safe but suboptimal; the
/// common "flag declared at the top" pattern is unaffected.
fn is_hoisted_var_inlineable(
decl: &VariableDeclarator<'a>,
symbol_id: SymbolId,
ctx: &TraverseCtx<'a>,
) -> bool {
if decl.init.is_none() || Self::keep_top_level_var_in_script_mode(ctx) {
return false;
}
// `body_unsafe` is set by a preceding non-declarative statement, and the
// program root additionally starts unsafe when the module has loaders
// (see `enter_program`) — so this one check covers the cyclic-import gate.
let &(body_scope, body_unsafe) = ctx.state.body_unsafe_stack.last();
if body_unsafe || ctx.current_scope_id() != body_scope {
return false;
}
// Exactly one read, and it crosses a function boundary.
let mut reads = ctx.scoping().get_resolved_references(symbol_id).filter(|r| r.is_read());
let Some(read) = reads.next() else { return false };
reads.next().is_none()
&& Self::read_crosses_function_boundary(read.scope_id(), body_scope, ctx)
}
/// True if the scope chain from `read_scope` to `body_scope` (exclusive of
/// `body_scope`) crosses any `Function` scope. `substitute_single_use_symbol`
/// only walks adjacent statements within the same call frame, so this is
/// the gap our path fills.
fn read_crosses_function_boundary(
read_scope: ScopeId,
body_scope: ScopeId,
ctx: &TraverseCtx<'a>,
) -> bool {
let scoping = ctx.scoping();
scoping
.scope_ancestors(read_scope)
.take_while(|&s| s != body_scope)
.any(|s| scoping.scope_flags(s).is_function())
}
/// Check if an expression creates a fresh value that cannot alias another binding
/// and has no setters/getters that could trigger side effects on property writes.
fn is_fresh_value_expression(expr: &Expression<'a>) -> bool {
+110 -1
View File
@@ -70,6 +70,78 @@ impl<'a> PeepholeOptimizations {
None
}
/// A body-level statement is "declarative" if executing it cannot run user
/// code that observes a subsequent hoisted `var x = <literal>;` as
/// `undefined`. Module loaders (`import`, `export * from`, `export … from`)
/// can evaluate foreign modules but only observe our bindings on an actual
/// cycle — handled at program scope by starting the root prelude unsafe when
/// the module has loaders (see `enter_program`).
/// Type-only declarations (`type`, `interface`) are erased and never run.
fn is_declarative_body_statement(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::EmptyStatement(_)
| Statement::ImportDeclaration(_)
| Statement::ExportAllDeclaration(_) => true,
// `export { foo }`, `export { foo } from './x'`, `export type T = …` —
// no executable code at the statement itself. The cyclic-eval hazard
// from a `from` source is gated separately at program scope (see
// `enter_program`).
Statement::ExportNamedDeclaration(e) => {
e.declaration.as_ref().is_none_or(Self::is_declarative_declaration)
}
// `export default function() {}` is hoisted; `export default <expr>`
// or `export default class C extends … {}` runs user code.
Statement::ExportDefaultDeclaration(e) => {
matches!(&e.declaration, ExportDefaultDeclarationKind::FunctionDeclaration(_))
}
// Bare declarations route through the shared classifier; anything else
// (blocks, expressions, control flow) can run user code.
_ => stmt.as_declaration().is_some_and(Self::is_declarative_declaration),
}
}
/// A `Declaration` runs no user code at evaluation: function/type/interface
/// declarations are inert, and a `var`/`let`/`const` is declarative only when
/// every declarator is a simple binding with a literal (or no) initializer.
/// Classes, enums, and TS modules run user code, so they are not declarative.
fn is_declarative_declaration(decl: &Declaration<'a>) -> bool {
match decl {
Declaration::FunctionDeclaration(_)
| Declaration::TSTypeAliasDeclaration(_)
| Declaration::TSInterfaceDeclaration(_) => true,
Declaration::VariableDeclaration(decl) => {
Self::is_declarative_variable_declaration(decl)
}
_ => false,
}
}
/// A `VariableDeclaration` is declarative when every declarator is a simple
/// `BindingIdentifier` (no destructuring / defaults / computed keys, all of
/// which can run user code) with either no initializer or a primitive
/// literal initializer.
fn is_declarative_variable_declaration(decl: &VariableDeclaration<'a>) -> bool {
decl.declarations.iter().all(Self::is_declarative_variable_declarator)
}
/// Note: only AST `Literal`s qualify. Constant-but-non-literal initializers
/// (`-1`, `void 0`, `1 + 2`) run no user code either, but conservatively end
/// the prelude here — a missed optimization, never a correctness risk.
fn is_declarative_variable_declarator(decl: &VariableDeclarator<'a>) -> bool {
matches!(decl.id, BindingPattern::BindingIdentifier(_))
&& decl.init.as_ref().is_none_or(Expression::is_literal)
}
/// Mark the current function/program body as no longer in its declarative
/// prelude. No-op if the flag is already set, or if `current_scope_id` is
/// some inner scope (a block/for/etc.) — those don't end the prelude.
fn mark_current_body_unsafe(ctx: &mut TraverseCtx<'a>) {
let &(body_scope, body_unsafe) = ctx.state.body_unsafe_stack.last();
if !body_unsafe && body_scope == ctx.current_scope_id() {
ctx.state.body_unsafe_stack.last_mut().1 = true;
}
}
/// Checks if a member expression's base object may be mutated.
///
/// This is used to prevent incorrect transformations like:
@@ -360,13 +432,36 @@ impl<'a> PeepholeOptimizations {
}
impl<'a> Traverse<'a> for PeepholeOptimizations {
fn enter_program(&mut self, _program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
ctx.state.symbol_values.reset();
ctx.state.proto_write_symbols.clear();
// Any module loader (`import`, `export * from`, `export … from`) can, on a
// cycle, evaluate a foreign module that observes a not-yet-assigned binding
// our exports close over. So the program root starts its prelude "unsafe"
// when the body has any loader — bailing every program-scope var inline.
// Loaders are hoisted, so scan the whole body (an import may follow a
// leading var); the result never changes across passes.
let module_has_loaders = program
.body
.iter()
.any(|s| s.as_module_declaration().is_some_and(|m| m.source().is_some()));
// `enter`/`exit_function_body` are balanced, so the stack is back to its
// single program-root entry by the next pass; reset it in place rather
// than reallocating (matching the `reset`/`clear` above).
*ctx.state.body_unsafe_stack.last_mut() =
(ctx.scoping().root_scope_id(), module_has_loaders);
// `PassDirty` is managed by the `Compressor` driver via
// `flush_pass_dirty`, not reset per traversal.
}
fn enter_function_body(&mut self, _body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
ctx.state.body_unsafe_stack.push((ctx.current_scope_id(), false));
}
fn exit_function_body(&mut self, _body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
ctx.state.body_unsafe_stack.pop();
}
fn exit_program(&mut self, _program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
// Only check class_symbols_stack in full optimization mode (not DCE mode)
debug_assert!(ctx.state.dce || ctx.state.class_symbols_stack.is_exhausted());
@@ -441,6 +536,12 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
}
Self::try_fold_expression_stmt(stmt, ctx);
}
// Maintain the per-body declarative-prelude flag used by
// `is_hoisted_var_inlineable`.
if !Self::is_declarative_body_statement(stmt) {
Self::mark_current_body_unsafe(ctx);
}
}
fn exit_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
@@ -475,6 +576,14 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
ctx: &mut TraverseCtx<'a>,
) {
Self::init_symbol_value(decl, ctx);
// Per-declarator update of the body-unsafe flag. Catches multi-declarator
// statements (`var [x=call()] = '', flag = true;`, possibly produced by
// join-vars) where an earlier declarator runs user code via a
// destructuring default or non-literal init — the per-statement check
// would fire too late for subsequent declarators' `init_symbol_value`.
if !Self::is_declarative_variable_declarator(decl) {
Self::mark_current_body_unsafe(ctx);
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
+13 -1
View File
@@ -6,7 +6,7 @@ use oxc_data_structures::stack::NonEmptyStack;
use oxc_semantic::Scoping;
use oxc_span::SourceType;
use oxc_str::Str;
use oxc_syntax::symbol::SymbolId;
use oxc_syntax::{scope::ScopeId, symbol::SymbolId};
use crate::{CompressOptions, symbol_value::SymbolValues};
@@ -66,6 +66,17 @@ pub struct MinifierState<'a> {
/// setters that make subsequent property writes side-effectful.
pub proto_write_symbols: FxHashSet<SymbolId>,
/// One frame per enclosing function body (program root at the bottom).
/// `(body_scope, body_unsafe)`. While `body_unsafe` is false, the next
/// `var x = <literal>;` whose declarator sits at `body_scope` is safe to
/// inline despite hoisting. A preceding non-declarative statement sets it;
/// the program root additionally starts unsafe when the module has any
/// loader (`import` / `export … from` / `export * from`), since a cyclic
/// importer could observe a not-yet-assigned binding our exports close over.
/// Pushed by `enter_function_body`, popped by `exit_function_body`. See
/// `init_symbol_value`.
pub body_unsafe_stack: NonEmptyStack<(ScopeId, bool)>,
/// Set when a typed helper mutates the AST. Private by design: the only
/// writers are the helpers on `MinifierTraverseCtx`; the only reader is
/// the fixed-point loop driver via `take_mutated()`.
@@ -98,6 +109,7 @@ impl<'a> MinifierState<'a> {
symbol_values: SymbolValues::new(scoping.symbols_len()),
class_symbols_stack: ClassSymbolsStack::new(),
proto_write_symbols: FxHashSet::default(),
body_unsafe_stack: NonEmptyStack::new((scoping.root_scope_id(), false)),
mutated: false,
dirty: PassDirty::new(scoping.references_len(), allocator),
concat_scratch: String::new(),
+207 -1
View File
@@ -1,4 +1,210 @@
use crate::{CompressOptions, test_options, test_same_options};
use oxc_span::SourceType;
use crate::{
CompressOptions, test_options, test_options_source_type, test_same_options, test_smallest,
};
// https://github.com/oxc-project/oxc/issues/13051
#[test]
fn readonly_var() {
// Top-level `var` with constant initializer, only read from inside a hoisted
// function that is called after the declaration. Safe to inline because no
// statement before the `var` can run code that reads it.
test_smallest(
"var used = false; function test() { if (used) return 123; return 321; } log(test());",
"function test() { return 321; } log(test());",
);
// Multiple readonly vars in a row — every preceding statement is itself a
// safe `var = literal` so each one is inlineable in turn.
test_smallest(
"var a = 1; var b = 2; function f() { return a + b; } log(f());",
"function f() { return 3; } log(f());",
);
}
#[test]
fn readonly_var_unsafe_preceding_call() {
// sapphi-red's case: a preceding call could invoke `output` before `foo` is
// assigned. The read inside `output` would see `undefined`, so inlining
// `foo` to `true` would change observable behavior — `foo` must stay.
test_smallest(
"output(); var foo = true; function output() { if (!foo) log('foo'); }",
"output(); var foo = !0; function output() { foo || log('foo'); }",
);
}
#[test]
fn readonly_var_unsafe_preceding_read() {
// A preceding statement reads the var before its initializer runs;
// the read must observe `undefined`, not the constant.
test_smallest("var y = foo; var foo = 1; log(y);", "var y = foo, foo = 1; log(y);");
// Canonical hoisting trap: the name is used directly before its own `var`
// declaration, so the read sees the hoisted `undefined`. `console.log(a)`
// must print `undefined`, never `0`. Doubly guarded — the read is in the
// same call frame (does not cross a function boundary) and the preceding
// call ends the declarative prelude.
test_smallest("console.log(a); var a = 0;", "console.log(a); var a = 0;");
}
#[test]
fn readonly_var_reassigned() {
// `foo` has a write reference, so even though `var foo = 1;` is at the top,
// inlining is unsafe.
test_smallest("var foo = 1; foo = 2; log(foo);", "var foo = 1; foo = 2, log(foo);");
}
#[test]
fn readonly_var_reassigned_cross_function_read() {
// The read crosses a function boundary (the gap this path targets), but
// `foo` is also reassigned. The predicate's read-loop ignores writes, so it
// relies on the downstream `write_references_count` guard in
// `inline_identifier_reference` to block inlining — substituting `1` would be
// wrong once `foo = 2` runs before `f()` is called.
test_smallest(
"var foo = 1; foo = 2; function f() { return foo; } log(f());",
"var foo = 1; foo = 2; function f() { return foo; } log(f());",
);
}
#[test]
fn readonly_var_reader_declared_before_var() {
// Known limitation: when the reading function is declared *before* the var,
// the symbol's constant isn't recorded until `exit_variable_declarator` —
// after `f`'s body (and its `foo` reference) was already visited in source
// order. The in-pass design can't reach back, so this otherwise-safe case is
// conservatively left un-inlined. Asserted to make any future improvement a
// conscious change rather than a silent one.
test_smallest(
"function f() { return foo; } var foo = true; log(f());",
"function f() { return foo; } var foo = !0; log(f());",
);
}
#[test]
fn readonly_var_with_imports_present() {
// A circular importer can observe ANY module-private var our exported
// functions/classes close over, regardless of export status. As long as the
// module has any static import, skip program-scope inlining outright.
// Non-exported var captured by an exported function — the cyclic-closure
// hazard Codex flagged. Must NOT inline.
test_smallest(
"import './b.js'; var flag = true; export function check() { return flag; }",
"import './b.js'; var flag = !0; export function check() { return flag; }",
);
// Even with no export, an import anywhere disqualifies — we can't tell
// statically whether a cycle exists.
test_smallest(
"import './side-effect.js'; var DEBUG = false; function log(x) { if (DEBUG) console.log(x); } log('hi');",
"import './side-effect.js'; var DEBUG = !1; function log(x) { DEBUG && console.log(x); } log('hi');",
);
// Imports are hoisted, so an import appearing *after* the var in source
// still triggers the gate — the pre-scan checks the whole body.
test_smallest(
"var flag = true; import './b.js'; export function check() { return flag; }",
"var flag = !0; import './b.js'; export function check() { return flag; }",
);
}
#[test]
fn readonly_var_with_reexports_present() {
// `export * from` and `export { … } from` are module loaders too — they
// evaluate foreign modules and create the same cyclic-eval hazard.
test_smallest(
"export * from './other.js'; var flag = true; export function check() { return flag; }",
"export * from './other.js'; var flag = !0; export function check() { return flag; }",
);
test_smallest(
"export { y } from './y.js'; var flag = true; export function check() { return flag; }",
"export { y } from './y.js'; var flag = !0; export function check() { return flag; }",
);
}
#[test]
fn readonly_var_through_declarative_exports() {
// `export function …`, `export var <literal>`, `export { … }`, and
// `export default function …` are declarative wrappers — no user code runs
// at module init, so the body's declarative prelude continues through them
// and a later var stays inlineable.
test_smallest(
"export function helper() {} export var A = 1; var b = 2; function f() { return b; } log(f());",
"export function helper() {} export var A = 1; function f() { return 2; } log(f());",
);
test_smallest(
"export default function helper() {} var b = 2; function f() { return b; } log(f());",
"export default function helper() {} function f() { return 2; } log(f());",
);
}
#[test]
fn readonly_var_export_default_expression_breaks_prelude() {
// `export default <expr>` evaluates the expression at module init, which
// can call user code. A later var must not inline.
test_smallest(
"export default sideEffect(); var b = 2; function f() { return b; } log(f());",
"export default sideEffect(); var b = 2; function f() { return b; } log(f());",
);
}
#[test]
fn readonly_var_unsafe_destructuring_default_prelude() {
// A preceding destructuring var with a default-call evaluates the call
// before `flag = true`. If `flag` were inlined inside `inner`, the call
// would observe `true` instead of the required hoisted `undefined`.
test_smallest(
"var [x = inner()] = ''; var flag = true; function inner() { return flag; } log(x);",
"var [x = inner()] = '', flag = !0; function inner() { return flag; } log(x);",
);
}
#[test]
fn readonly_var_in_function_body() {
// #2: same declarative-prelude analysis, applied to function bodies. The
// var sits at the function's body scope and is read from a nested function,
// which substitute_single_use_symbol can't reach.
test_smallest(
"function outer() { var flag = false; function inner() { return flag ? 1 : 2; } return inner(); } log(outer());",
"function outer() { function inner() { return 2; } return inner(); } log(outer());",
);
}
#[test]
fn readonly_var_in_function_body_unsafe_preceding_call() {
// Same hoisting hazard as top-level: an observable call before the var
// inside the function body could invoke a hoisted inner function that
// reads `flag` as `undefined`. Skip.
test_smallest(
"function outer() { sideEffect(); var flag = true; function inner() { return flag; } return inner(); } log(outer());",
"function outer() { sideEffect(); var flag = !0; function inner() { return flag; } return inner(); } log(outer());",
);
}
#[test]
fn readonly_var_script_mode() {
// Top-level `var` in script mode creates a property on the global object;
// another script can mutate it between this line and a later function call.
// Don't inline.
test_options_source_type(
"var used = false; function test() { if (used) return 123; return 321; } log(test());",
"var used = !1; function test() { return used ? 123 : 321; } log(test());",
SourceType::cjs().with_script(true),
&CompressOptions::smallest(),
);
}
#[test]
fn readonly_var_after_type_declaration() {
// Type-only declarations (`type`, `interface`) are erased and run no code,
// so they don't end the declarative prelude — a following readonly var
// stays inlineable.
test_options_source_type(
"type T = number; interface I {} var b = 2; function f() { return b; } log(f());",
"type T = number; interface I {} function f() { return 2; } log(f());",
SourceType::ts().with_module(true),
&CompressOptions::smallest(),
);
}
#[test]
fn r#const() {
+4 -4
View File
@@ -9,7 +9,7 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
342.15 kB | 116.97 kB | 118.14 kB | 43.18 kB | 44.37 kB | 2 | vue.js
544.10 kB | 71.04 kB | 72.48 kB | 25.77 kB | 26.20 kB | 2 | lodash.js
544.10 kB | 70.96 kB | 72.48 kB | 25.77 kB | 26.20 kB | 2 | lodash.js
555.77 kB | 267.40 kB | 270.13 kB | 88.01 kB | 90.80 kB | 2 | d3.js
@@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
1.25 MB | 642.65 kB | 646.76 kB | 159.41 kB | 163.73 kB | 2 | three.js
2.14 MB | 711.10 kB | 724.14 kB | 160.44 kB | 181.07 kB | 2 | victory.js
2.14 MB | 711.07 kB | 724.14 kB | 160.41 kB | 181.07 kB | 2 | victory.js
3.20 MB | 1.00 MB | 1.01 MB | 322.61 kB | 331.56 kB | 2 | echarts.js
6.69 MB | 2.22 MB | 2.31 MB | 458.45 kB | 488.28 kB | 4 | antd.js
6.69 MB | 2.22 MB | 2.31 MB | 458.44 kB | 488.28 kB | 4 | antd.js
10.95 MB | 3.33 MB | 3.49 MB | 853.35 kB | 915.50 kB | 4 | typescript.js
10.95 MB | 3.33 MB | 3.49 MB | 853.11 kB | 915.50 kB | 4 | typescript.js
@@ -1,16 +1,16 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs
---------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 165 | 51 || 152742 | 28244
checker.ts | 2.92 MB || 166 | 52 || 152742 | 28244
App.tsx | 415.34 kB || 114 | 48 || 17012 | 2702
App.tsx | 415.34 kB || 115 | 49 || 17012 | 2702
RadixUIAdoptionSection.jsx | 2.52 kB || 23 | 20 || 32 | 6
RadixUIAdoptionSection.jsx | 2.52 kB || 24 | 20 || 32 | 6
pdf.mjs | 567.30 kB || 2613 | 248 || 47485 | 7740
pdf.mjs | 567.30 kB || 2614 | 249 || 47485 | 7740
antd.js | 6.69 MB || 3083 | 104 || 337251 | 69349
antd.js | 6.69 MB || 3084 | 106 || 337233 | 69333
binder.ts | 193.08 kB || 40 | 36 || 7083 | 824
binder.ts | 193.08 kB || 41 | 36 || 7083 | 824
kitchen-sink.tsx | 732.90 kB || 2639 | 216 || 90554 | 15823
kitchen-sink.tsx | 732.90 kB || 2640 | 218 || 90554 | 15823