mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): minimize if(foo) bar -> foo && bar (#8121)
This commit is contained in:
parent
72d996709e
commit
f8200a8882
3 changed files with 123 additions and 74 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Statement<'a>> {
|
||||
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<Expression<'a>> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue