Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(minifier): minimize if (x) if (y) z -> if (x && y) z #8136

Merged
merged 1 commit into from
Dec 27, 2024
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
16 changes: 16 additions & 0 deletions crates/oxc_ast/src/ast_impl/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,22 @@ impl Statement<'_> {
| Statement::WhileStatement(_)
)
}

/// Returns the single statement from block statement, or self
pub fn get_one_child(&self) -> Option<&Self> {
if let Statement::BlockStatement(block_stmt) = self {
return (block_stmt.body.len() == 1).then(|| &block_stmt.body[0]);
}
Some(self)
}

/// Returns the single statement from block statement, or self
pub fn get_one_child_mut(&mut self) -> Option<&mut Self> {
if let Statement::BlockStatement(block_stmt) = self {
return (block_stmt.body.len() == 1).then_some(&mut block_stmt.body[0]);
}
Some(self)
}
}

impl<'a> FromIn<'a, Expression<'a>> for Statement<'a> {
Expand Down
87 changes: 41 additions & 46 deletions crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_span::GetSpan;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};

use crate::CompressorPass;
Expand Down Expand Up @@ -89,7 +90,9 @@ impl<'a> PeepholeMinimizeConditions {
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
let Statement::IfStatement(if_stmt) = stmt else { unreachable!() };
match &if_stmt.alternate {
let then_branch = &if_stmt.consequent;
let else_branch = &if_stmt.alternate;
match else_branch {
None => {
if Self::is_foldable_express_block(&if_stmt.consequent) {
let right = Self::get_block_expression(&mut if_stmt.consequent, ctx);
Expand All @@ -116,11 +119,31 @@ impl<'a> PeepholeMinimizeConditions {
);
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
}
} else {
// `if (x) if (y) z` -> `if (x && y) z`
if let Some(Statement::IfStatement(then_if_stmt)) = then_branch.get_one_child()
{
if then_if_stmt.alternate.is_none() {
let and_left = ctx.ast.move_expression(&mut if_stmt.test);
let Statement::IfStatement(mut then_if_stmt) =
ctx.ast.move_statement(&mut if_stmt.consequent)
else {
unreachable!()
};
let and_right = ctx.ast.move_expression(&mut then_if_stmt.test);
then_if_stmt.test = ctx.ast.expression_logical(
and_left.span(),
and_left,
LogicalOperator::And,
and_right,
);
return Some(Statement::IfStatement(then_if_stmt));
}
}
}
}
Some(else_branch) => {
let then_branch_is_expression_block =
Self::is_foldable_express_block(&if_stmt.consequent);
let then_branch_is_expression_block = Self::is_foldable_express_block(then_branch);
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 {
Expand Down Expand Up @@ -184,37 +207,18 @@ 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,
}
matches!(stmt.get_one_child(), Some(Statement::ExpressionStatement(_)))
}

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!(),
}
let Some(Statement::ExpressionStatement(s)) = stmt.get_one_child_mut() else {
unreachable!()
};
ctx.ast.move_expression(&mut s.expression)
}

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

fn is_return_expression(stmt: &Statement<'a>) -> bool {
Expand All @@ -235,17 +239,8 @@ impl<'a> PeepholeMinimizeConditions {
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::ReturnStatement(_) = &mut block_stmt.body[0] {
Self::take_return_argument(stmt, ctx)
} else {
unreachable!()
}
}
Statement::ReturnStatement(_) => Self::take_return_argument(stmt, ctx),
_ => unreachable!(),
}
let Some(stmt) = stmt.get_one_child_mut() else { unreachable!() };
Self::take_return_argument(stmt, ctx)
}

fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
Expand Down Expand Up @@ -323,13 +318,13 @@ 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()");

Expand Down
22 changes: 11 additions & 11 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.77 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js
72.14 kB | 23.74 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js

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

287.63 kB | 90.77 kB | 90.07 kB | 32.23 kB | 31.95 kB | jquery.js
287.63 kB | 90.76 kB | 90.07 kB | 32.22 kB | 31.95 kB | jquery.js

342.15 kB | 119.00 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js
342.15 kB | 119.00 kB | 118.14 kB | 44.66 kB | 44.37 kB | vue.js

544.10 kB | 72.54 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js
544.10 kB | 72.53 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js

555.77 kB | 274.27 kB | 270.13 kB | 91.27 kB | 90.80 kB | d3.js
555.77 kB | 274.26 kB | 270.13 kB | 91.26 kB | 90.80 kB | d3.js

1.01 MB | 461.18 kB | 458.89 kB | 126.93 kB | 126.71 kB | bundle.min.js
1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js

1.25 MB | 657.26 kB | 646.76 kB | 164.24 kB | 163.73 kB | three.js
1.25 MB | 657.23 kB | 646.76 kB | 164.23 kB | 163.73 kB | three.js

2.14 MB | 735.73 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js
2.14 MB | 735.70 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js

3.20 MB | 1.01 MB | 1.01 MB | 332.37 kB | 331.56 kB | echarts.js
3.20 MB | 1.01 MB | 1.01 MB | 332.35 kB | 331.56 kB | echarts.js

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

10.95 MB | 3.52 MB | 3.49 MB | 911.00 kB | 915.50 kB | typescript.js
10.95 MB | 3.51 MB | 3.49 MB | 910.94 kB | 915.50 kB | typescript.js

Loading