diff --git a/crates/oxc_minifier/src/peephole/minimize_for_statement.rs b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs index e3d53abbcd834..3ab1dbb922a4e 100644 --- a/crates/oxc_minifier/src/peephole/minimize_for_statement.rs +++ b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs @@ -8,7 +8,17 @@ 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 { + // Get the first statement in the loop + let mut first = &for_stmt.body; + if let Statement::BlockStatement(block_stmt) = first { + if let Some(b) = block_stmt.body.first() { + first = b; + } else { + return; + } + }; + + let Statement::IfStatement(if_stmt) = first else { return; }; // "for (;;) if (x) break;" => "for (; !x;) ;" @@ -19,20 +29,34 @@ impl<'a> PeepholeOptimizations { if break_stmt.label.is_some() { return; } + + let span = for_stmt.body.span(); + let (first, body) = match ctx.ast.move_statement(&mut for_stmt.body) { + Statement::BlockStatement(mut block_stmt) => ( + ctx.ast.move_statement(block_stmt.body.get_mut(0).unwrap()), + Some(Statement::BlockStatement(block_stmt)), + ), + stmt => (stmt, None), + }; + + let Statement::IfStatement(mut if_stmt) = first else { unreachable!() }; + 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)); + + let alternate = if_stmt.alternate.take(); + for_stmt.body = Self::drop_first_statement(span, body, alternate, ctx); self.mark_current_function_as_changed(); return; } @@ -44,15 +68,53 @@ impl<'a> PeepholeOptimizations { if break_stmt.label.is_some() { return; } + + let span = for_stmt.body.span(); + let (first, body) = match ctx.ast.move_statement(&mut for_stmt.body) { + Statement::BlockStatement(mut block_stmt) => ( + ctx.ast.move_statement(block_stmt.body.get_mut(0).unwrap()), + Some(Statement::BlockStatement(block_stmt)), + ), + stmt => (stmt, None), + }; + + let Statement::IfStatement(mut if_stmt) = first else { unreachable!() }; + 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); + + let consequent = ctx.ast.move_statement(&mut if_stmt.consequent); + for_stmt.body = Self::drop_first_statement(span, body, Some(consequent), ctx); self.mark_current_function_as_changed(); } } + + fn drop_first_statement( + span: Span, + body: Option>, + replace: Option>, + ctx: Ctx<'a, '_>, + ) -> Statement<'a> { + match body { + Some(Statement::BlockStatement(mut block_stmt)) if !block_stmt.body.is_empty() => { + if let Some(replace) = replace { + block_stmt.body[0] = replace; + } else if block_stmt.body.len() == 2 + && !Self::statement_cares_about_scope(&block_stmt.body[1]) + { + return ctx.ast.move_statement(&mut block_stmt.body[1]); + } else { + block_stmt.body.remove(0); + } + Statement::BlockStatement(block_stmt) + } + _ => replace.unwrap_or_else(|| ctx.ast.statement_empty(span)), + } + } } diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 5471e9f4fa383..0a81ce96d8ef9 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -491,7 +491,7 @@ impl<'a> PeepholeOptimizations { } /// `statementCaresAboutScope`: - fn statement_cares_about_scope(stmt: &Statement<'a>) -> bool { + pub fn statement_cares_about_scope(stmt: &Statement<'a>) -> bool { match stmt { Statement::BlockStatement(_) | Statement::EmptyStatement(_) diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index a03b6a18d4164..a39eff447fb07 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -171,19 +171,18 @@ fn js_parser_test() { 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; 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(); z(); }", "for (; !x; ) y(), z();"); - // test("for (; a;) { if (x) break; else y(); z(); }", "for (; a && !x; ) y(), z();"); + 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; z(); }", "for (; x; ) { y(); z();}"); - // test("for (; a;) { if (x) y(); else break; z(); }", "for (; a && x; ) { y(); z();}"); + 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( diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index f454f95bfd91b..857e66339600c 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.54 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js -287.63 kB | 89.46 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js +287.63 kB | 89.45 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js -342.15 kB | 117.62 kB | 118.14 kB | 43.45 kB | 44.37 kB | vue.js +342.15 kB | 117.61 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.12 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js +555.77 kB | 271.11 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js -1.01 MB | 440.94 kB | 458.89 kB | 122.52 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.89 kB | 458.89 kB | 122.52 kB | 126.71 kB | bundle.min.js 1.25 MB | 650.30 kB | 646.76 kB | 160.95 kB | 163.73 kB | three.js -2.14 MB | 716.93 kB | 724.14 kB | 161.98 kB | 181.07 kB | victory.js +2.14 MB | 716.92 kB | 724.14 kB | 161.98 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.33 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.32 kB | 331.56 kB | echarts.js 6.69 MB | 2.28 MB | 2.31 MB | 467.77 kB | 488.28 kB | antd.js -10.95 MB | 3.36 MB | 3.49 MB | 861.79 kB | 915.50 kB | typescript.js +10.95 MB | 3.36 MB | 3.49 MB | 861.77 kB | 915.50 kB | typescript.js