diff --git a/crates/oxc_minifier/src/peephole/minimize_for_statement.rs b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs new file mode 100644 index 0000000000000..e3d53abbcd834 --- /dev/null +++ b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs @@ -0,0 +1,58 @@ +use oxc_ast::ast::*; +use oxc_span::GetSpan; + +use crate::ctx::Ctx; + +use super::PeepholeOptimizations; + +impl<'a> PeepholeOptimizations { + /// `mangleFor`: + pub fn minimize_for_statement(&mut self, for_stmt: &mut ForStatement<'a>, ctx: Ctx<'a, '_>) { + let Some(Statement::IfStatement(if_stmt)) = for_stmt.body.get_one_child_mut() else { + return; + }; + // "for (;;) if (x) break;" => "for (; !x;) ;" + // "for (; a;) if (x) break;" => "for (; a && !x;) ;" + // "for (;;) if (x) break; else y();" => "for (; !x;) y();" + // "for (; a;) if (x) break; else y();" => "for (; a && !x;) y();" + if let Some(Statement::BreakStatement(break_stmt)) = if_stmt.consequent.get_one_child() { + if break_stmt.label.is_some() { + return; + } + let expr = match ctx.ast.move_expression(&mut if_stmt.test) { + Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => { + unary_expr.unbox().argument + } + e => Self::minimize_not(e.span(), e, ctx), + }; + if let Some(test) = &mut for_stmt.test { + let e = ctx.ast.move_expression(test); + *test = ctx.ast.expression_logical(test.span(), e, LogicalOperator::And, expr); + } else { + for_stmt.test = Some(expr); + } + for_stmt.body = + if_stmt.alternate.take().unwrap_or_else(|| ctx.ast.statement_empty(if_stmt.span)); + self.mark_current_function_as_changed(); + return; + } + // "for (;;) if (x) y(); else break;" => "for (; x;) y();" + // "for (; a;) if (x) y(); else break;" => "for (; a && x;) y();" + if let Some(Statement::BreakStatement(break_stmt)) = + if_stmt.alternate.as_ref().and_then(|stmt| stmt.get_one_child()) + { + if break_stmt.label.is_some() { + return; + } + let expr = ctx.ast.move_expression(&mut if_stmt.test); + if let Some(test) = &mut for_stmt.test { + let e = ctx.ast.move_expression(test); + *test = ctx.ast.expression_logical(test.span(), e, LogicalOperator::And, expr); + } else { + for_stmt.test = Some(expr); + } + for_stmt.body = ctx.ast.move_statement(&mut if_stmt.consequent); + self.mark_current_function_as_changed(); + } + } +} diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 70f0dd071129b..2a91d64528db2 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -5,6 +5,7 @@ mod minimize_conditional_expression; mod minimize_conditions; mod minimize_exit_points; mod minimize_expression_in_boolean_context; +mod minimize_for_statement; mod minimize_if_statement; mod minimize_not_expression; mod minimize_statements; @@ -160,6 +161,13 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { self.substitute_exit_statement(stmt, Ctx(traverse_ctx)); } + fn exit_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) { + if !self.is_prev_function_changed() { + return; + } + self.minimize_for_statement(stmt, Ctx(ctx)); + } + fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { if !self.is_prev_function_changed() { return; diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index 1380377db82f6..70ab4d933991b 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -167,28 +167,29 @@ fn js_parser_test() { test("const a=0; for (var b;;) ;", "const a = 0;for (var b;;) ;"); test("a(); while (1) ;", "for (a();;) ;"); test("a(); for (b();;) ;", "for (a(), b();;) ;"); - // test("for (; ;) if (x) break;", "for (; !x; ) ;"); - // test("for (; ;) if (!x) break;", "for (; x; ) ;"); - // test("for (; a;) if (x) break;", "for (; a && !x; ) ;"); - // test("for (; a;) if (!x) break;", "for (; a && x; ) ;"); + test("for (; ;) if (x) break;", "for (; !x; ) ;"); + test("for (; ;) if (!x) break;", "for (; x; ) ;"); + test("for (; a;) if (x) break;", "for (; a && !x; ) ;"); + test("for (; a;) if (!x) break;", "for (; a && x; ) ;"); + // TODO: optimizeImplicitJump in `mangleStmts` // test("for (; ;) { if (x) break; y(); }", "for (; !x; ) y();"); // test("for (; a;) { if (x) break; y(); }", "for (; a && !x; ) y();"); - // test("for (; ;) if (x) break; else y();", "for (; !x; ) y();"); - // test("for (; a;) if (x) break; else y();", "for (; a && !x; ) y();"); + test("for (; ;) if (x) break; else y();", "for (; !x; ) y();"); + test("for (; a;) if (x) break; else y();", "for (; a && !x; ) y();"); // test("for (; ;) { if (x) break; else y(); z(); }", "for (; !x; ) y(), z();"); // test("for (; a;) { if (x) break; else y(); z(); }", "for (; a && !x; ) y(), z();"); - // test("for (; ;) if (x) y(); else break;", "for (; x; ) y();"); - // test("for (; ;) if (!x) y(); else break;", "for (; !x; ) y();"); - // test("for (; a;) if (x) y(); else break;", "for (; a && x; ) y();"); - // test("for (; a;) if (!x) y(); else break;", "for (; a && !x; ) y();"); + test("for (; ;) if (x) y(); else break;", "for (; x; ) y();"); + test("for (; ;) if (!x) y(); else break;", "for (; !x; ) y();"); + test("for (; a;) if (x) y(); else break;", "for (; a && x; ) y();"); + test("for (; a;) if (!x) y(); else break;", "for (; a && !x; ) y();"); // test("for (; ;) { if (x) y(); else break; z(); }", "for (; x; ) { y(); z();}"); // test("for (; a;) { if (x) y(); else break; z(); }", "for (; a && x; ) { y(); z();}"); - // test("while (x) { if (1) break; z(); }", "for (; x; ) break;"); - // test("while (x) { if (1) continue; z(); }", "for (; x; ) ;"); - // test( - // "foo: while (a) while (x) { if (1) continue foo; z(); }", - // "foo: for (; a; ) for (; x; ) continue foo;", - // ); + test("while (x) { if (1) break; z(); }", "for (; x; ) break;"); + test("while (x) { if (1) continue; z(); }", "for (; x; ) ;"); + test( + "foo: while (a) while (x) { if (1) continue foo; z(); }", + "foo: for (; a; ) for (; x; ) continue foo;", + ); test("while (x) { y(); if (1) break; z(); }", "for (; x; ) { y(); break;}"); test("while (x) { y(); if (1) continue; z(); }", "for (; x; ) y();"); test("while (x) { y(); debugger; if (1) continue; z(); }", "for (; x; ) { y(); debugger; }"); @@ -206,10 +207,10 @@ fn js_parser_test() { // test("while (x()) continue", "for (; x(); ) ;"); test("while (x) { y(); continue }", "for (; x; ) y();"); test("while (x) { if (y) { z(); continue } }", "for (; x; ) if (y) { z(); continue; }"); - // test( - // "label: while (x) while (y) { z(); continue label }", - // "label: for (; x; ) for (; y; ) { z(); continue label;}", - // ); + test( + "label: while (x) while (y) { z(); continue label }", + "label: for (; x; ) for (; y; ) { z(); continue label;}", + ); // test("while (x) { if (y) continue; z(); }", "for (; x; ) y || z();"); // test("while (x) { if (y) continue; else z(); w(); }", "for (; x; ) y || (z(), w());"); // test("while (x) { t(); if (y) continue; z(); }", "for (; x; ) t(), !y && z();"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 7e77f23b2d860..3c615bd4e2a0b 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 59.55 kB | 59.82 kB | 19.19 kB | 19.33 kB | moment.js -287.63 kB | 89.48 kB | 90.07 kB | 30.95 kB | 31.95 kB | jquery.js +287.63 kB | 89.47 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js -342.15 kB | 117.68 kB | 118.14 kB | 43.56 kB | 44.37 kB | vue.js +342.15 kB | 117.67 kB | 118.14 kB | 43.57 kB | 44.37 kB | vue.js -544.10 kB | 71.43 kB | 72.48 kB | 25.87 kB | 26.20 kB | lodash.js +544.10 kB | 71.42 kB | 72.48 kB | 25.87 kB | 26.20 kB | lodash.js -555.77 kB | 271.25 kB | 270.13 kB | 88.35 kB | 90.80 kB | d3.js +555.77 kB | 271.24 kB | 270.13 kB | 88.34 kB | 90.80 kB | d3.js -1.01 MB | 440.97 kB | 458.89 kB | 122.50 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.93 kB | 458.89 kB | 122.50 kB | 126.71 kB | bundle.min.js -1.25 MB | 650.36 kB | 646.76 kB | 161.01 kB | 163.73 kB | three.js +1.25 MB | 650.33 kB | 646.76 kB | 161.01 kB | 163.73 kB | three.js -2.14 MB | 718.69 kB | 724.14 kB | 162.15 kB | 181.07 kB | victory.js +2.14 MB | 718.69 kB | 724.14 kB | 162.14 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.44 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.45 kB | 331.56 kB | echarts.js -6.69 MB | 2.30 MB | 2.31 MB | 468.54 kB | 488.28 kB | antd.js +6.69 MB | 2.30 MB | 2.31 MB | 468.52 kB | 488.28 kB | antd.js -10.95 MB | 3.36 MB | 3.49 MB | 862.14 kB | 915.50 kB | typescript.js +10.95 MB | 3.36 MB | 3.49 MB | 862.12 kB | 915.50 kB | typescript.js