Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,47 +89,53 @@ impl<'a> PeepholeMinimizeConditions {
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
let Statement::IfStatement(if_stmt) = stmt else { unreachable!() };
if if_stmt.alternate.is_none() {
if let Some(right) = Self::is_foldable_express_block(&mut if_stmt.consequent, ctx) {
let test = ctx.ast.move_expression(&mut if_stmt.test);
// `if(!x) foo()` -> `x || foo()`
if let Expression::UnaryExpression(unary_expr) = test {
if unary_expr.operator.is_not() {
let left = unary_expr.unbox().argument;
match &if_stmt.alternate {
None => {
if Self::is_foldable_express_block(&if_stmt.consequent) {
let right = Self::get_block_expression(&mut if_stmt.consequent, ctx);
let test = ctx.ast.move_expression(&mut if_stmt.test);
// `if(!x) foo()` -> `x || foo()`
if let Expression::UnaryExpression(unary_expr) = test {
if unary_expr.operator.is_not() {
let left = unary_expr.unbox().argument;
let logical_expr = ctx.ast.expression_logical(
if_stmt.span,
left,
LogicalOperator::Or,
right,
);
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
}
} else {
// `if(x) foo()` -> `x && foo()`
let logical_expr = ctx.ast.expression_logical(
if_stmt.span,
left,
LogicalOperator::Or,
test,
LogicalOperator::And,
right,
);
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
}
} else {
// `if(x) foo()` -> `x && foo()`
let logical_expr =
ctx.ast.expression_logical(if_stmt.span, test, 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
Some(else_branch) => {
let then_branch_is_expression_block =
Self::is_foldable_express_block(&if_stmt.consequent);
let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch);
// `if(foo) bar else baz` -> `foo ? bar : baz`
if then_branch_is_expression_block && else_branch_is_expression_block {
let test = ctx.ast.move_expression(&mut if_stmt.test);
let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx);
let else_branch = if_stmt.alternate.as_mut().unwrap();
let alternate = Self::get_block_expression(else_branch, ctx);
let expr =
ctx.ast.expression_conditional(if_stmt.span, test, consequent, alternate);
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
}
}
Statement::ExpressionStatement(s) => Some(ctx.ast.move_expression(&mut s.expression)),
_ => None,
}

None
}

fn try_replace_if(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
Expand Down Expand Up @@ -177,6 +183,30 @@ impl<'a> PeepholeMinimizeConditions {
}
}

fn is_foldable_express_block(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
matches!(&block_stmt.body[0], Statement::ExpressionStatement(_))
}
Statement::ExpressionStatement(_) => true,
_ => false,
}
}

fn get_block_expression(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
match stmt {
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] {
ctx.ast.move_expression(&mut s.expression)
} else {
unreachable!()
}
}
Statement::ExpressionStatement(s) => ctx.ast.move_expression(&mut s.expression),
_ => unreachable!(),
}
}

fn is_return_block(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
Expand Down Expand Up @@ -271,7 +301,7 @@ mod test {

// 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)}");
Expand All @@ -288,7 +318,7 @@ mod test {
fold_same("function f(){switch(x){case 1:break}}");

// Do while loops stay in a block if that's where they started
// fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}");
fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}");
// Test an obscure case with do and while
// fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()");

Expand Down
24 changes: 12 additions & 12 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.85 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js
72.14 kB | 23.77 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js

173.90 kB | 60.60 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js
173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js

287.63 kB | 91.40 kB | 90.07 kB | 32.36 kB | 31.95 kB | jquery.js
287.63 kB | 90.77 kB | 90.07 kB | 32.23 kB | 31.95 kB | jquery.js

342.15 kB | 120.07 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js
342.15 kB | 119.00 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js

544.10 kB | 72.91 kB | 72.48 kB | 26.27 kB | 26.20 kB | lodash.js
544.10 kB | 72.54 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js

555.77 kB | 275.31 kB | 270.13 kB | 91.45 kB | 90.80 kB | d3.js
555.77 kB | 274.27 kB | 270.13 kB | 91.27 kB | 90.80 kB | d3.js

1.01 MB | 462.98 kB | 458.89 kB | 127.06 kB | 126.71 kB | bundle.min.js
1.01 MB | 461.18 kB | 458.89 kB | 126.93 kB | 126.71 kB | bundle.min.js

1.25 MB | 658.98 kB | 646.76 kB | 164.40 kB | 163.73 kB | three.js
1.25 MB | 657.26 kB | 646.76 kB | 164.24 kB | 163.73 kB | three.js

2.14 MB | 736.98 kB | 724.14 kB | 181.33 kB | 181.07 kB | victory.js
2.14 MB | 735.73 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js

3.20 MB | 1.02 MB | 1.01 MB | 332.77 kB | 331.56 kB | echarts.js
3.20 MB | 1.01 MB | 1.01 MB | 332.37 kB | 331.56 kB | echarts.js

6.69 MB | 2.38 MB | 2.31 MB | 495.91 kB | 488.28 kB | antd.js
6.69 MB | 2.38 MB | 2.31 MB | 495.35 kB | 488.28 kB | antd.js

10.95 MB | 3.52 MB | 3.49 MB | 911.59 kB | 915.50 kB | typescript.js
10.95 MB | 3.52 MB | 3.49 MB | 911.00 kB | 915.50 kB | typescript.js