Skip to content

Commit

Permalink
feat(minifier): minimize block statements
Browse files Browse the repository at this point in the history
fixes #8708
  • Loading branch information
Boshen committed Feb 3, 2025
1 parent d6d13dd commit d42a9b2
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 38 deletions.
8 changes: 4 additions & 4 deletions crates/oxc_minifier/src/peephole/minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
44 changes: 43 additions & 1 deletion crates/oxc_minifier/src/peephole/minimize_statements.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -473,4 +474,45 @@ impl<'a> PeepholeOptimizations {
}
false
}

/// `appendIfOrLabelBodyPreservingScope`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_parser.go#L9852>
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`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_parser.go#L9767>
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,
}
}
}
22 changes: 17 additions & 5 deletions crates/oxc_minifier/src/peephole/remove_dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()");

Expand All @@ -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());");
Expand All @@ -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]
Expand Down Expand Up @@ -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) {}", "");
}
Expand Down
18 changes: 6 additions & 12 deletions crates/oxc_minifier/src/peephole/statement_fusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand All @@ -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; } } }",
Expand Down
16 changes: 8 additions & 8 deletions crates/oxc_minifier/tests/peephole/esbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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';");
Expand Down Expand Up @@ -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; }}",
Expand Down
16 changes: 8 additions & 8 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d42a9b2

Please sign in to comment.