diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index a1cf75018f63c..b6ffc5bb52dc1 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -66,12 +66,13 @@ impl<'a> Traverse<'a> for CollapsePass { // See `latePeepholeOptimizations` pub struct LatePeepholeOptimizations { x0_statement_fusion: StatementFusion, - x1_peephole_remove_dead_code: PeepholeRemoveDeadCode, + x1_collapse_variable_declarations: CollapseVariableDeclarations, + x2_peephole_remove_dead_code: PeepholeRemoveDeadCode, // TODO: MinimizeExitPoints - x2_peephole_minimize_conditions: PeepholeMinimizeConditions, - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods, - x5_peephole_fold_constants: PeepholeFoldConstants, + x3_peephole_minimize_conditions: PeepholeMinimizeConditions, + x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, + x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods, + x6_peephole_fold_constants: PeepholeFoldConstants, } impl LatePeepholeOptimizations { @@ -79,32 +80,35 @@ impl LatePeepholeOptimizations { let in_fixed_loop = true; Self { x0_statement_fusion: StatementFusion::new(), - x1_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), - x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( + x1_collapse_variable_declarations: CollapseVariableDeclarations::new(), + x2_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), + x3_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), + x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( in_fixed_loop, ), - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), - x5_peephole_fold_constants: PeepholeFoldConstants::new(), + x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), + x6_peephole_fold_constants: PeepholeFoldConstants::new(), } } fn reset_changed(&mut self) { self.x0_statement_fusion.changed = false; - self.x1_peephole_remove_dead_code.changed = false; - self.x2_peephole_minimize_conditions.changed = false; - self.x3_peephole_substitute_alternate_syntax.changed = false; - self.x4_peephole_replace_known_methods.changed = false; - self.x5_peephole_fold_constants.changed = false; + self.x1_collapse_variable_declarations.changed = false; + self.x2_peephole_remove_dead_code.changed = false; + self.x3_peephole_minimize_conditions.changed = false; + self.x4_peephole_substitute_alternate_syntax.changed = false; + self.x5_peephole_replace_known_methods.changed = false; + self.x6_peephole_fold_constants.changed = false; } fn changed(&self) -> bool { self.x0_statement_fusion.changed - || self.x1_peephole_remove_dead_code.changed - || self.x2_peephole_minimize_conditions.changed - || self.x3_peephole_substitute_alternate_syntax.changed - || self.x4_peephole_replace_known_methods.changed - || self.x5_peephole_fold_constants.changed + || self.x1_collapse_variable_declarations.changed + || self.x2_peephole_remove_dead_code.changed + || self.x3_peephole_minimize_conditions.changed + || self.x4_peephole_substitute_alternate_syntax.changed + || self.x5_peephole_replace_known_methods.changed + || self.x6_peephole_fold_constants.changed } pub fn run_in_loop<'a>( @@ -135,14 +139,9 @@ impl<'a> CompressorPass<'a> for LatePeepholeOptimizations { } impl<'a> Traverse<'a> for LatePeepholeOptimizations { - fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_statement(stmt, ctx); - self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx); - } - fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x0_statement_fusion.exit_program(program, ctx); - self.x1_peephole_remove_dead_code.exit_program(program, ctx); + self.x2_peephole_remove_dead_code.exit_program(program, ctx); } fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { @@ -150,7 +149,13 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_statements(stmts, ctx); + self.x1_collapse_variable_declarations.exit_statements(stmts, ctx); + self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx); + } + + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + self.x2_peephole_remove_dead_code.exit_statement(stmt, ctx); + self.x3_peephole_minimize_conditions.exit_statement(stmt, ctx); } fn exit_block_statement(&mut self, block: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -158,7 +163,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { } fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); } fn exit_variable_declaration( @@ -166,23 +171,23 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { decl: &mut VariableDeclaration<'a>, ctx: &mut TraverseCtx<'a>, ) { - self.x3_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx); } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_expression(expr, ctx); - self.x2_peephole_minimize_conditions.exit_expression(expr, ctx); - self.x3_peephole_substitute_alternate_syntax.exit_expression(expr, ctx); - self.x4_peephole_replace_known_methods.exit_expression(expr, ctx); - self.x5_peephole_fold_constants.exit_expression(expr, ctx); + self.x2_peephole_remove_dead_code.exit_expression(expr, ctx); + self.x3_peephole_minimize_conditions.exit_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_expression(expr, ctx); + self.x5_peephole_replace_known_methods.exit_expression(expr, ctx); + self.x6_peephole_fold_constants.exit_expression(expr, ctx); } fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx); } fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 5a2c09b829677..43f090e59be86 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -26,6 +26,17 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions { } impl<'a> Traverse<'a> for PeepholeMinimizeConditions { + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(folded_stmt) = match stmt { + // If the condition is a literal, we'll let other optimizations try to remove useless code. + Statement::IfStatement(s) if !s.test.is_literal() => Self::try_minimize_if(stmt, ctx), + _ => None, + } { + *stmt = folded_stmt; + self.changed = true; + }; + } + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_expr) = match expr { Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx), @@ -56,6 +67,40 @@ impl<'a> PeepholeMinimizeConditions { binary_expr.operator = new_op; Some(ctx.ast.move_expression(&mut expr.argument)) } + + fn try_minimize_if( + stmt: &mut Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; + if if_stmt.alternate.is_none() { + // `if(x)foo();` -> `x&&foo();` + if let Some(right) = Self::is_foldable_express_block(&mut if_stmt.consequent, ctx) { + let left = ctx.ast.move_expression(&mut if_stmt.test); + let logical_expr = + ctx.ast.expression_logical(if_stmt.span, left, LogicalOperator::And, right); + return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + } + } + None + } + + fn is_foldable_express_block( + stmt: &mut Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] { + Some(ctx.ast.move_expression(&mut s.expression)) + } else { + None + } + } + Statement::ExpressionStatement(s) => Some(ctx.ast.move_expression(&mut s.expression)), + _ => None, + } + } } /// @@ -85,7 +130,6 @@ mod test { /** Check that removing blocks with 1 child works */ #[test] - #[ignore] fn test_fold_one_child_blocks() { // late = false; fold("function f(){if(x)a();x=3}", "function f(){x&&a();x=3}"); @@ -94,13 +138,13 @@ mod test { fold("function f(){if(x){a()}x=3}", "function f(){x&&a();x=3}"); fold("function f(){if(x){a?.()}x=3}", "function f(){x&&a?.();x=3}"); - fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); + // fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); fold("function f(){if(x){a()}}", "function f(){x&&a()}"); - fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); + // fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); // Try it out with functions fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); - fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); + // fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); // Try it out with properties and methods fold("function f(){if(x){a.b=1}}", "function f(){x&&(a.b=1)}"); @@ -122,35 +166,35 @@ mod test { // fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); // Play with nested IFs - fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); - fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); - fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); - fold( - "function f(){if(x){if(y)foo();else bar()}else{baz()}}", - "function f(){x?y?foo():bar():baz()}", - ); + // fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); + // fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); + // fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); + // fold( + // "function f(){if(x){if(y)foo();else bar()}else{baz()}}", + // "function f(){x?y?foo():bar():baz()}", + // ); // fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); // fold("if(e1){with(e2){if(e3){foo()}}}else{bar()}", "if(e1)with(e2)e3&&foo();else bar()"); - fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); - fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); + // fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); + // fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); // NOTE - technically we can remove the blocks since both the parent // and child have elses. But we don't since it causes ambiguities in // some cases where not all descendent ifs having elses - fold( - "if(x){ if(y){var x;}else{var z;} }else{var w}", - "if(x)if(y)var x;else var z;else var w", - ); - fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); + // fold( + // "if(x){ if(y){var x;}else{var z;} }else{var w}", + // "if(x)if(y)var x;else var z;else var w", + // ); + // fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); // Here's some of the ambiguous cases - fold( - "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", - "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", - ); + // fold( + // "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", + // "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", + // ); fold_same("function f(){foo()}"); fold_same("switch(x){case y: foo()}"); @@ -160,10 +204,10 @@ mod test { // Lexical declaration cannot appear in a single-statement context. fold_same("if (foo) { const bar = 1 } else { const baz = 1 }"); fold_same("if (foo) { let bar = 1 } else { let baz = 1 }"); - fold( - "if (foo) { var bar = 1 } else { var baz = 1 }", - "if (foo) var bar = 1; else var baz = 1;", - ); + // fold( + // "if (foo) { var bar = 1 } else { var baz = 1 }", + // "if (foo) var bar = 1; else var baz = 1;", + // ); } /** Try to minimize returns */ diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index c7682131d35eb..a311960b71cef 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.94 kB | 23.70 kB | 8.59 kB | 8.54 kB | react.development.js +72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js -173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js +173.90 kB | 61.42 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js -287.63 kB | 92.42 kB | 90.07 kB | 32.32 kB | 31.95 kB | jquery.js +287.63 kB | 92.09 kB | 90.07 kB | 32.46 kB | 31.95 kB | jquery.js -342.15 kB | 121.31 kB | 118.14 kB | 44.69 kB | 44.37 kB | vue.js +342.15 kB | 120.77 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js -544.10 kB | 73.22 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js +544.10 kB | 73.17 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js -555.77 kB | 275.67 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js +555.77 kB | 275.52 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js -1.01 MB | 466.33 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js +1.01 MB | 465.56 kB | 458.89 kB | 127.14 kB | 126.71 kB | bundle.min.js -1.25 MB | 660.39 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js +1.25 MB | 659.76 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js -2.14 MB | 739.97 kB | 724.14 kB | 181.42 kB | 181.07 kB | victory.js +2.14 MB | 739.69 kB | 724.14 kB | 181.77 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.30 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 333.18 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.67 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 496.55 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 910.07 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 912.55 kB | 915.50 kB | typescript.js