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): minify one child if statement expression #7230

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
92 changes: 80 additions & 12 deletions crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use oxc_ast::ast::*;
use oxc_ecmascript::ToBoolean;
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};

use crate::CompressorPass;
Expand Down Expand Up @@ -35,6 +37,16 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
self.changed = true;
};
}

fn exit_statement(&mut self, node: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if let Statement::IfStatement(if_stmt) = node {
self.try_fold_if_block_one(if_stmt, ctx);
if let Some(new_stmt) = Self::try_fold_if_one_child(if_stmt, ctx) {
*node = new_stmt;
self.changed = true;
}
}
}
}

impl<'a> PeepholeMinimizeConditions {
Expand All @@ -56,6 +68,63 @@ impl<'a> PeepholeMinimizeConditions {
}
None
}

/// Duplicate logic to DCE part.
fn try_fold_if_block_one(&mut self, if_stmt: &mut IfStatement<'a>, ctx: &mut TraverseCtx<'a>) {
if let Statement::BlockStatement(block) = &mut if_stmt.consequent {
if block.body.len() == 1 {
self.changed = true;
if_stmt.consequent = ctx.ast.move_statement(block.body.first_mut().unwrap());
}
}
if let Some(Statement::BlockStatement(block)) = &mut if_stmt.alternate {
if block.body.len() == 1 {
self.changed = true;
if_stmt.alternate = Some(ctx.ast.move_statement(block.body.first_mut().unwrap()));
}
}
}

fn try_fold_if_one_child(
if_stmt: &mut IfStatement<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
if let Statement::ExpressionStatement(expr) = &mut if_stmt.consequent {
// The rest of things for known boolean are tasks for dce instead of here.
(if_stmt.alternate.is_none() && if_stmt.test.to_boolean().is_none()).then(|| {
// Make if (x) y; => x && y;
let (operator, mut test) = match &mut if_stmt.test {
Expression::UnaryExpression(unary) if unary.operator.is_not() => {
let arg = ctx.ast.move_expression(&mut unary.argument);
(LogicalOperator::Or, arg)
}
_ => (LogicalOperator::And, ctx.ast.move_expression(&mut if_stmt.test)),
};
match &mut test {
Expression::BinaryExpression(bin) if bin.operator.is_equality() => {
if !bin.left.is_literal() && bin.right.is_literal() {
test = ctx.ast.expression_binary(
SPAN,
ctx.ast.move_expression(&mut bin.right),
bin.operator,
ctx.ast.move_expression(&mut bin.left),
);
}
}
_ => {}
}
let new_expr = ctx.ast.expression_logical(
SPAN,
test,
operator,
ctx.ast.move_expression(&mut expr.expression),
);
ctx.ast.statement_expression(SPAN, new_expr)
})
} else {
None
}
}
}

/// <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
Expand Down Expand Up @@ -85,7 +154,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}");
Expand All @@ -100,7 +168,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 @@ -117,22 +185,22 @@ 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()");
// 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();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){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(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");
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_minifier/tests/ast_passes/dead_code_elimination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ fn dce_if_statement() {
test("if (!false) { foo }", "foo");
test("if (!true) { foo } else { bar }", "bar");

test("if (!false && xxx) { foo }", "if (xxx) foo");
test("if (!false && xxx) { foo }", "xxx && foo");
test("if (!true && yyy) { foo } else { bar }", "bar");

test("if (true || xxx) { foo }", "foo");
test("if (false || xxx) { foo }", "if (xxx) foo");
test("if (false || xxx) { foo }", "xxx && foo");

test("if ('production' == 'production') { foo } else { bar }", "foo");
test("if ('development' == 'production') { foo } else { bar }", "bar");
Expand Down
24 changes: 12 additions & 12 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
Original | Minified | esbuild | Gzip | esbuild

72.14 kB | 24.12 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js
72.14 kB | 24.08 kB | 23.70 kB | 8.68 kB | 8.54 kB | react.development.js

173.90 kB | 61.67 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js
173.90 kB | 61.56 kB | 59.82 kB | 19.66 kB | 19.33 kB | moment.js

287.63 kB | 92.70 kB | 90.07 kB | 32.26 kB | 31.95 kB | jquery.js
287.63 kB | 92.41 kB | 90.07 kB | 32.50 kB | 31.95 kB | jquery.js

342.15 kB | 121.90 kB | 118.14 kB | 44.59 kB | 44.37 kB | vue.js
342.15 kB | 121.37 kB | 118.14 kB | 44.90 kB | 44.37 kB | vue.js

544.10 kB | 73.48 kB | 72.48 kB | 26.12 kB | 26.20 kB | lodash.js
544.10 kB | 73.41 kB | 72.48 kB | 26.20 kB | 26.20 kB | lodash.js

555.77 kB | 276.49 kB | 270.13 kB | 91.15 kB | 90.80 kB | d3.js
555.77 kB | 276.33 kB | 270.13 kB | 91.52 kB | 90.80 kB | d3.js

1.01 MB | 467.60 kB | 458.89 kB | 126.74 kB | 126.71 kB | bundle.min.js
1.01 MB | 466.82 kB | 458.89 kB | 127.24 kB | 126.71 kB | bundle.min.js

1.25 MB | 662.86 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js
1.25 MB | 662.29 kB | 646.76 kB | 164.78 kB | 163.73 kB | three.js

2.14 MB | 741.57 kB | 724.14 kB | 181.45 kB | 181.07 kB | victory.js
2.14 MB | 741.26 kB | 724.14 kB | 181.86 kB | 181.07 kB | victory.js

3.20 MB | 1.02 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js
3.20 MB | 1.02 MB | 1.01 MB | 333.53 kB | 331.56 kB | echarts.js

6.69 MB | 2.39 MB | 2.31 MB | 496.10 kB | 488.28 kB | antd.js
6.69 MB | 2.39 MB | 2.31 MB | 497.30 kB | 488.28 kB | antd.js

10.95 MB | 3.56 MB | 3.49 MB | 911.23 kB | 915.50 kB | typescript.js
10.95 MB | 3.55 MB | 3.49 MB | 914.18 kB | 915.50 kB | typescript.js

Loading