From c364ad195afcf9c1b34182a5974db0b32f25e512 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:55:38 +0000 Subject: [PATCH] feat(minifier): support ForStatements for single use variable inlining (#13755) Inline single use variables in for statements: ```js function wrapper1() { var x = foo; for (var i = x; i < 10; i++) console.log(i) } function wrapper2() { var i, x = foo; for (i = x; i < 10; i++) console.log(i) } // to function wrapper1() { for (var i = foo; i < 10; i++) console.log(i) } function wrapper2() { var i; for (i = foo; i < 10; i++) console.log(i) } ``` ```js function wrapper() { var x = {}; for (var a in x) console.log(a) } // to function wrapper() { for (var a in {}) console.log(a) } ``` ```js function wrapper() { var x = []; for (var a of x) console.log(a) } // to function wrapper() { for (var a of []) console.log(a) } ``` refs #13277 --- .../src/peephole/minimize_statements.rs | 48 +++++++++++++++++++ .../peephole/inline_single_use_variable.rs | 21 ++++++++ tasks/minsize/minsize.snap | 8 ++-- .../allocs_minifier.snap | 4 +- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 0f1fc15894184..df16cf75e853d 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -824,6 +824,33 @@ impl<'a> PeepholeOptimizations { ctx: &mut Ctx<'a, '_>, ) { + if let Some(init) = &mut for_stmt.init { + match init { + ForStatementInit::VariableDeclaration(var_decl) => { + if let Some(first_decl) = var_decl.declarations.first_mut() + && let Some(first_decl_init) = first_decl.init.as_mut() + { + let changed = Self::substitute_single_use_symbol_in_statement( + first_decl_init, + result, + ctx, + ); + if changed { + ctx.state.changed = true; + } + } + } + match_expression!(ForStatementInit) => { + let init = init.to_expression_mut(); + let changed = + Self::substitute_single_use_symbol_in_statement(init, result, ctx); + if changed { + ctx.state.changed = true; + } + } + } + } + if let Some(ForStatementInit::VariableDeclaration(var_decl)) = &mut for_stmt.init { let old_len = var_decl.declarations.len(); var_decl.declarations.retain(|decl| { @@ -890,6 +917,21 @@ impl<'a> PeepholeOptimizations { ctx: &mut Ctx<'a, '_>, ) { + // Annex B.3.5 allows initializers in non-strict mode + // + // That is evaluated before the right hand side is evaluated. So, in that case, skip the single use substitution. + if !matches!(&for_in_stmt.left, ForStatementLeft::VariableDeclaration(var_decl) if var_decl.has_init()) + { + let changed = Self::substitute_single_use_symbol_in_statement( + &mut for_in_stmt.right, + result, + ctx, + ); + if changed { + ctx.state.changed = true; + } + } + if ctx.options().sequences { match result.last_mut() { // "a; for (var b in c) d" => "for (var b in a, c) d" @@ -963,6 +1005,12 @@ impl<'a> PeepholeOptimizations { result: &mut Vec<'a, Statement<'a>>, ctx: &mut Ctx<'a, '_>, ) { + let changed = + Self::substitute_single_use_symbol_in_statement(&mut for_of_stmt.right, result, ctx); + if changed { + ctx.state.changed = true; + } + // "var a; for (a of b) c" => "for (var a of b) c" if let Some(Statement::VariableDeclaration(prev_var_decl)) = result.last_mut() { if let ForStatementLeft::AssignmentTargetIdentifier(id) = &for_of_stmt.left { diff --git a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs index 996ac830813c0..856d9d13da76e 100644 --- a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs +++ b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs @@ -154,6 +154,27 @@ fn test_inline_single_use_variable() { "function wrapper() { let x = function () { console.log() }; foo(x) }", "function wrapper() { foo(function() { console.log() }) }", ); + + test( + "function wrapper() { var x = foo; for (var i = x; i < 10; i++) console.log(i) }", + "function wrapper() { for (var i = foo; i < 10; i++) console.log(i) }", + ); + test( + "function wrapper() { var i, x = foo; for (i = x; i < 10; i++) console.log(i) }", + "function wrapper() { var i; for (i = foo; i < 10; i++) console.log(i) }", + ); + test( + "function wrapper() { var x = {}; for (var a in x) console.log(a) }", + "function wrapper() { for (var a in {}) console.log(a) }", + ); + test( + "function wrapper() { var x = {}; for (var a = 0 in x) console.log(a) }", + "function wrapper() { var x = {}; for (var a = 0 in x) console.log(a) }", + ); + test( + "function wrapper() { var x = []; for (var a of x) console.log(a) }", + "function wrapper() { for (var a of []) console.log(a) }", + ); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 1758968bed3fb..8a97d88657794 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -7,19 +7,19 @@ Original | minified | minified | gzip | gzip | Iterations | Fi 287.63 kB | 89.28 kB | 90.07 kB | 30.94 kB | 31.95 kB | 2 | jquery.js -342.15 kB | 117.01 kB | 118.14 kB | 43.19 kB | 44.37 kB | 2 | vue.js +342.15 kB | 117 kB | 118.14 kB | 43.19 kB | 44.37 kB | 2 | vue.js 544.10 kB | 71.18 kB | 72.48 kB | 25.85 kB | 26.20 kB | 2 | lodash.js 555.77 kB | 270.78 kB | 270.13 kB | 88.19 kB | 90.80 kB | 2 | d3.js -1.01 MB | 439.58 kB | 458.89 kB | 122.15 kB | 126.71 kB | 2 | bundle.min.js +1.01 MB | 439.56 kB | 458.89 kB | 122.14 kB | 126.71 kB | 2 | bundle.min.js 1.25 MB | 645.63 kB | 646.76 kB | 159.54 kB | 163.73 kB | 2 | three.js -2.14 MB | 713.54 kB | 724.14 kB | 161.00 kB | 181.07 kB | 2 | victory.js +2.14 MB | 713.53 kB | 724.14 kB | 160.99 kB | 181.07 kB | 2 | victory.js -3.20 MB | 1.00 MB | 1.01 MB | 323.12 kB | 331.56 kB | 3 | echarts.js +3.20 MB | 1.00 MB | 1.01 MB | 323.10 kB | 331.56 kB | 3 | echarts.js 6.69 MB | 2.22 MB | 2.31 MB | 459.28 kB | 488.28 kB | 4 | antd.js diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index b42731dcfafb4..b7dbe709c2be3 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -1,12 +1,12 @@ File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes ------------------------------------------------------------------------------------------------------------------------------------------- -checker.ts | 2.92 MB || 84073 | 14190 || 153691 | 29463 | 5.625 MB +checker.ts | 2.92 MB || 84058 | 14190 || 153533 | 29395 | 5.615 MB cal.com.tsx | 1.06 MB || 40525 | 3033 || 37074 | 4733 | 1.654 MB RadixUIAdoptionSection.jsx | 2.52 kB || 82 | 8 || 30 | 6 | 992 B -pdf.mjs | 567.30 kB || 19577 | 2900 || 47405 | 7784 | 1.625 MB +pdf.mjs | 567.30 kB || 19572 | 2900 || 47384 | 7770 | 1.623 MB antd.js | 6.69 MB || 99854 | 13518 || 331725 | 70117 | 17.407 MB