From d42a9b2cbc631cf61ebd4b110a8bc2222dbc5ff1 Mon Sep 17 00:00:00 2001 From: Boshen Date: Mon, 3 Feb 2025 22:34:37 +0800 Subject: [PATCH] feat(minifier): minimize block statements fixes #8708 --- .../src/peephole/minimize_conditions.rs | 8 ++-- .../src/peephole/minimize_statements.rs | 44 ++++++++++++++++++- .../src/peephole/remove_dead_code.rs | 22 +++++++--- .../src/peephole/statement_fusion.rs | 18 +++----- crates/oxc_minifier/tests/peephole/esbuild.rs | 16 +++---- tasks/minsize/minsize.snap | 16 +++---- 6 files changed, 86 insertions(+), 38 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 8c6e0618d8830..d54cb95b05671 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -574,10 +574,10 @@ mod test { "if (foo) { let bar = 1 } else { let baz = 1 }", ); test_same("if (foo) { let bar = 1 } else { let baz = 1 }"); - // test( - // "if (foo) { var bar = 1 } else { var baz = 1 }", - // "if (foo) var bar = 1; else var baz = 1;", - // ); + test( + "if (foo) { var bar = 1 } else { var baz = 1 }", + "if (foo) var bar = 1; else var baz = 1;", + ); } #[test] diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index a470255403a85..2f6891a415c49 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -1,4 +1,4 @@ -use oxc_allocator::Vec; +use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, Visit}; use oxc_ecmascript::side_effects::MayHaveSideEffects; use oxc_span::{cmp::ContentEq, GetSpan}; @@ -440,6 +440,7 @@ impl<'a> PeepholeOptimizations { } result.push(Statement::ForOfStatement(for_of_stmt)); } + Statement::BlockStatement(block_stmt) => self.handle_block(result, block_stmt), stmt => result.push(stmt), } } @@ -473,4 +474,45 @@ impl<'a> PeepholeOptimizations { } false } + + /// `appendIfOrLabelBodyPreservingScope`: + fn handle_block( + &mut self, + result: &mut Vec<'a, Statement<'a>>, + block_stmt: Box<'a, BlockStatement<'a>>, + ) { + let keep_block = block_stmt.body.iter().any(Self::statement_cares_about_scope); + if keep_block { + result.push(Statement::BlockStatement(block_stmt)); + } else { + result.append(&mut block_stmt.unbox().body); + self.mark_current_function_as_changed(); + } + } + + /// `statementCaresAboutScope`: + fn statement_cares_about_scope(stmt: &Statement<'a>) -> bool { + match stmt { + Statement::BlockStatement(_) + | Statement::EmptyStatement(_) + | Statement::DebuggerStatement(_) + | Statement::ExpressionStatement(_) + | Statement::IfStatement(_) + | Statement::ForStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) + | Statement::DoWhileStatement(_) + | Statement::WhileStatement(_) + | Statement::WithStatement(_) + | Statement::TryStatement(_) + | Statement::SwitchStatement(_) + | Statement::ReturnStatement(_) + | Statement::ThrowStatement(_) + | Statement::BreakStatement(_) + | Statement::ContinueStatement(_) + | Statement::LabeledStatement(_) => false, + Statement::VariableDeclaration(decl) => !decl.kind.is_var(), + _ => true, + } + } } diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index b02732eb75e10..3b13c310a0eb5 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -640,7 +640,7 @@ mod test { test("{{foo()}}", "foo()"); test("{foo();{}}", "foo()"); test("{{foo()}{}}", "foo()"); - // test("{{foo()}{bar()}}", "foo();bar()"); + test("{{foo()}{bar()}}", "foo(), bar()"); test("{if(false)foo(); {bar()}}", "bar()"); test("{if(false)if(false)if(false)foo(); {bar()}}", "bar()"); @@ -650,8 +650,8 @@ mod test { test("{ (function(){x++}) }", ""); test("function f(){return;}", "function f(){}"); test("function f(){return 3;}", "function f(){return 3}"); - // test_same("function f(){if(x)return; x=3; return; }"); - // test("{x=3;;;y=2;;;}", "x=3;y=2"); + test("function f(){if(x)return; x=3; return; }", "function f(){if(x)return; x=3; }"); + test("{x=3;;;y=2;;;}", "x=3, y=2"); // Cases to test for empty block. // test("while(x()){x}", "while(x());"); @@ -662,6 +662,19 @@ mod test { test("for (let x = 1; x <10; x++ ) {}", "for (let x = 1; x <10; x++ );"); test("for (var x = 1; x <10; x++ ) {}", "for (var x = 1; x <10; x++ );"); test("do { } while (true)", "do;while(!0)"); + test( + "function z(a) { + { + for (var i = 0; i < a; i++) {} + foo() + } + bar() + }", + "function z(a) { + for (var i = 0; i < a; i++); + foo(), bar() + }", + ); } #[test] @@ -802,8 +815,7 @@ mod test { #[test] fn test_fold_if_statement() { test("if (foo) {}", "foo"); - // FIXME - // test("if (foo) {} else {}", "foo"); + test("if (foo) {} else {}", "foo"); test("if (false) {}", ""); test("if (true) {}", ""); } diff --git a/crates/oxc_minifier/src/peephole/statement_fusion.rs b/crates/oxc_minifier/src/peephole/statement_fusion.rs index 467268415867f..2b3700922a546 100644 --- a/crates/oxc_minifier/src/peephole/statement_fusion.rs +++ b/crates/oxc_minifier/src/peephole/statement_fusion.rs @@ -83,14 +83,13 @@ mod test { } #[test] - #[ignore] fn fuse_into_block() { test("a;b;c;{d;e;f}", "a,b,c,d,e,f"); - test( - "a;b; label: { if(q) break label; bar(); }", - "label: { if(a,b,q) break label; bar(); }", - ); - test("a;b;c;{var x;d;e;}", "a,b,c;{var x;d,e;}"); + // test( + // "a;b; label: { if(q) break label; bar(); }", + // "label: { if(a,b,q) break label; bar(); }", + // ); + test("a;b;c;{var x;d;e;}", "a,b,c;var x;d,e;"); test("a;b;c;label:{break label;d;e;}", "a,b,c"); } @@ -110,22 +109,17 @@ mod test { } #[test] - #[ignore] fn no_fuse_into_block() { // Never fuse a statement into a block that contains let/const/class declarations, or you risk // colliding variable names. (unless the AST is normalized). test("a; {b;}", "a,b"); - test("a; {b; var a = 1;}", "{a, b; var a = 1;}"); + test("a; {b; var a = 1;}", "a, b; var a = 1;"); test_same("a; { b; let a = 1; }"); test("a; { b; const a = 1; }", "a; { b; let a = 1; }"); test_same("a; { b; class a {} }"); test_same("a; { b; function a() {} }"); test("a; { b; const otherVariable = 1; }", "a; { b; let otherVariable = 1; }"); - // test( - // "function f(a) { if (COND) { a; { b; let a = 1; } } }", - // "function f(a) { if (COND) { { a,b; let a$jscomp$1 = 1; } } }", - // ); // test( // "function f(a) { if (COND) { a; { b; let otherVariable = 1; } } }", // "function f(a) { if (COND) { { a,b; let otherVariable = 1; } } }", diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index 0564591bfa1b4..f9e15d92647ab 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -330,8 +330,8 @@ fn js_parser_test() { test("a = '' + true", "a = 'true';"); test("a = false + ''", "a = 'false';"); test("a = true + ''", "a = 'true';"); - // test("a = 1 + false + ''", "a = 1 + false + '';"); - // test("a = 0 + true + ''", "a = 0 + true + '';"); + test("a = 1 + false + ''", "a = '1';"); + test("a = 0 + true + ''", "a = '1';"); test("a = '' + null", "a = 'null';"); test("a = null + ''", "a = 'null';"); test("a = '' + undefined", "a = 'undefined';"); @@ -539,12 +539,12 @@ fn js_parser_test() { test("a(); if (b) throw c", "if (a(), b) throw c;"); test("if (a) if (b) throw c", "if (a && b) throw c;"); test("if (true) { let a = b; if (c) throw d }", "{ let a = b; if (c) throw d;}"); - // test("if (true) { if (a) throw b; if (c) throw d }", "if (a) throw b;if (c) throw d;"); - // test("if (false) throw a; else { let b = c; if (d) throw e }", "{ let b = c; if (d) throw e;}"); - // test( - // "if (false) throw a; else { if (b) throw c; if (d) throw e }", - // "if (b) throw c;if (d) throw e;", - // ); + test("if (true) { if (a) throw b; if (c) throw d }", "if (a) throw b;if (c) throw d;"); + test("if (false) throw a; else { let b = c; if (d) throw e }", "{ let b = c; if (d) throw e;}"); + test( + "if (false) throw a; else { if (b) throw c; if (d) throw e }", + "if (b) throw c;if (d) throw e;", + ); test( "if (a) { if (b) throw c; else { let d = e; if (f) throw g } }", "if (a) { if (b) throw c; { let d = e; if (f) throw g; }}", diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 08cf3d3d5e58d..50f442a87b3c3 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.56 kB | 23.70 kB | 8.50 kB | 8.54 kB | react.development.js +72.14 kB | 23.51 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js 173.90 kB | 59.55 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js 287.63 kB | 89.45 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js -342.15 kB | 117.67 kB | 118.14 kB | 43.48 kB | 44.37 kB | vue.js +342.15 kB | 117.62 kB | 118.14 kB | 43.45 kB | 44.37 kB | vue.js 544.10 kB | 71.40 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js -555.77 kB | 271.24 kB | 270.13 kB | 88.33 kB | 90.80 kB | d3.js +555.77 kB | 271.20 kB | 270.13 kB | 88.30 kB | 90.80 kB | d3.js -1.01 MB | 440.93 kB | 458.89 kB | 122.53 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.93 kB | 458.89 kB | 122.52 kB | 126.71 kB | bundle.min.js 1.25 MB | 650.33 kB | 646.76 kB | 160.96 kB | 163.73 kB | three.js -2.14 MB | 717.07 kB | 724.14 kB | 162.05 kB | 181.07 kB | victory.js +2.14 MB | 717.02 kB | 724.14 kB | 162.02 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.40 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.33 kB | 331.56 kB | echarts.js -6.69 MB | 2.28 MB | 2.31 MB | 467.77 kB | 488.28 kB | antd.js +6.69 MB | 2.28 MB | 2.31 MB | 467.74 kB | 488.28 kB | antd.js -10.95 MB | 3.36 MB | 3.49 MB | 862.00 kB | 915.50 kB | typescript.js +10.95 MB | 3.36 MB | 3.49 MB | 861.78 kB | 915.50 kB | typescript.js