feat(minifier): process of return/throw in non last position and fix async * inlining (#26214)

This Pr brings couple optimizations and refactor,

- Disable merging: `if (a) return x; return;` and `if (a) return; return
x;` when is descendant of `async* function`
- Handle empty chains: `if (a) return; return;` → `a; return;`
- Process returns/throw anywhere in block via handler merge `if (a)
return; return; var b` → `a; return; var b`

fixes #26255
ref #24331
This commit is contained in:
Armano authored and GitHub committed 2026-09-03 23:05:40 +08:00
1 parent f6b75b4a6b
commit a17de58e30
4 files changed
+161 -168

No files matched your search

@@ -110,120 +110,6 @@ impl<'a> PeepholeOptimizations {
let dropped = stmts.pop().unwrap();
ctx.drop_statement(&dropped);
}
// Merge certain statements in reverse order
if stmts.len() >= 2 && ctx.options().sequences {
if let Some(Statement::ReturnStatement(_)) = stmts.last() {
'return_loop: while stmts.len() >= 2 {
let prev_index = stmts.len() - 2;
let prev_stmt = &stmts[prev_index];
match prev_stmt {
// Merge the last two statements
Statement::IfStatement(if_stmt) => {
// The previous statement must be an if statement with no else clause
if if_stmt.alternate.is_some() {
break 'return_loop;
}
// The then clause must be a return
let Statement::ReturnStatement(_) = &if_stmt.consequent else {
break 'return_loop;
};
if let Some(Statement::ReturnStatement(last_return)) = stmts.last()
&& let Some(arg) = &last_return.argument
&& Self::conditional_expression_count_exceeded(arg)
{
break 'return_loop;
}
ctx.notice_change();
let last_stmt = stmts.pop().unwrap();
let Statement::ReturnStatement(last_return) = last_stmt else {
unreachable!()
};
let prev_stmt = stmts.pop().unwrap();
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
let prev_if = prev_if.unbox();
let Statement::ReturnStatement(prev_return) = prev_if.consequent else {
unreachable!()
};
let left_span = prev_return.span;
let right_span = last_return.span;
// "if (a) return; return b;" => "return a ? void 0 : b;"
let left = prev_return
.unbox()
.argument
.unwrap_or_else(|| Expression::new_void_0(left_span, ctx));
// "if (a) return a; return;" => "return a ? b : void 0;"
let right = last_return
.unbox()
.argument
.unwrap_or_else(|| Expression::new_void_0(right_span, ctx));
let argument = Self::minimize_conditional_after_if(
prev_if.span,
prev_if.test,
left,
right,
ctx,
);
let last_return_stmt =
Statement::new_return_statement(right_span, Some(argument), ctx);
stmts.push(last_return_stmt);
}
_ => break 'return_loop,
}
}
} else if let Some(Statement::ThrowStatement(_)) = stmts.last() {
'throw_loop: while stmts.len() >= 2 {
let prev_index = stmts.len() - 2;
let prev_stmt = &stmts[prev_index];
match prev_stmt {
// Merge the last two statements
Statement::IfStatement(if_stmt) => {
// The previous statement must be an if statement with no else clause
if if_stmt.alternate.is_some() {
break 'throw_loop;
}
// The then clause must be a throw
let Statement::ThrowStatement(_) = &if_stmt.consequent else {
break 'throw_loop;
};
if let Some(Statement::ThrowStatement(last_throw)) = stmts.last()
&& Self::conditional_expression_count_exceeded(&last_throw.argument)
{
break 'throw_loop;
}
ctx.notice_change();
let last_stmt = stmts.pop().unwrap();
let Statement::ThrowStatement(last_throw) = last_stmt else {
unreachable!()
};
let prev_stmt = stmts.pop().unwrap();
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
let prev_if = prev_if.unbox();
let Statement::ThrowStatement(prev_throw) = prev_if.consequent else {
unreachable!()
};
let right_span = last_throw.span;
let argument = Self::minimize_conditional_after_if(
prev_if.span,
prev_if.test,
prev_throw.unbox().argument,
last_throw.unbox().argument,
ctx,
);
let last_throw_stmt =
Statement::new_throw_statement(right_span, argument, ctx);
stmts.push(last_throw_stmt);
}
_ => break 'throw_loop,
}
}
}
}
}
/// Some parsers cannot parse long conditional expressions.
@@ -781,26 +667,12 @@ impl<'a> PeepholeOptimizations {
if !if_stmt.alternate.as_ref().is_none_or(Self::statement_cares_about_scope)
&& if_stmt.consequent.is_terminated()
&& let Some(stmt) = if_stmt.alternate.take()
{
// "if (a) return b; else if (c) return d; else return e;" => "if (a) return b; if (c) return d; return e;"
ctx.notice_change();
result.push(Statement::IfStatement(if_stmt));
loop {
if let Some(Statement::IfStatement(if_stmt)) = result.last_mut()
&& !if_stmt.alternate.as_ref().is_none_or(Self::statement_cares_about_scope)
&& if_stmt.consequent.is_terminated()
&& let Some(stmt) = if_stmt.alternate.take()
{
if let Statement::BlockStatement(block_stmt) = stmt {
Self::handle_block(result, block_stmt, ctx);
} else {
result.push(stmt);
ctx.notice_change();
}
continue;
}
break;
}
return ControlFlow::Continue(());
return Self::minimize_statement(stmt, i, stmts, result, ctx);
}
}
@@ -816,6 +688,8 @@ impl<'a> PeepholeOptimizations {
) {
if let Some(ret_argument_expr) = &mut ret_stmt.argument {
Self::substitute_single_use_symbol_in_statement(ret_argument_expr, result, ctx, false);
// `var a; return a = b(), c;` => `var a = b(); return c;`
Self::merge_leading_assignments_to_declaration(ret_argument_expr, false, result, ctx);
}
if let Some(argument) = &mut ret_stmt.argument
@@ -839,13 +713,9 @@ impl<'a> PeepholeOptimizations {
if let Some(old) = ret_stmt.argument.take() {
ctx.drop_expression(&old);
}
result.push(Statement::ReturnStatement(ret_stmt));
return;
}
if ctx.options().sequences
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
} else if ctx.options().sequences
&& let Some(argument) = &mut ret_stmt.argument
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
{
let a = &mut prev_expr_stmt.expression;
let new_arg = Self::join_sequence(a, argument, ctx);
@@ -853,9 +723,86 @@ impl<'a> PeepholeOptimizations {
result.pop();
}
// "var a; return a = b(), c;" => "var a = b(); return c;"
if let Some(argument) = &mut ret_stmt.argument {
Self::merge_leading_assignments_to_declaration(argument, false, result, ctx);
// `if (a) return b; return c;` => `return a ? b : c;`
// `if (a) return; return;` => `a; return;`
if ctx.options().sequences {
'return_loop: while !result.is_empty() {
if let Some(Statement::IfStatement(if_stmt)) = result.last()
&& if_stmt.alternate.is_none()
&& let Statement::ReturnStatement(prev_return) = &if_stmt.consequent
{
let prev_has_arg = prev_return.argument.is_some();
let ret_has_arg = ret_stmt.argument.is_some();
if !prev_has_arg && !ret_has_arg {
// `if (a) return; return;` => `a; return;`
ctx.notice_change();
let prev_stmt = result.pop().unwrap();
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
let prev_if = prev_if.unbox();
let test_expr = prev_if.test;
result.push(Statement::new_expression_statement(
test_expr.span(),
test_expr,
ctx,
));
break 'return_loop;
}
// do not collapse if conditional count exceeded
if ret_stmt
.argument
.as_ref()
.is_some_and(Self::conditional_expression_count_exceeded)
{
break 'return_loop;
}
// do not collapse if return could be removed
if !ret_has_arg && ctx.parent().is_function_body() {
break 'return_loop;
}
// do not collapse if parent is async generator and any of arguments is empty
if (!prev_has_arg || !ret_has_arg)
&& ctx.is_closest_function_scope_an_async_generator()
{
break 'return_loop;
}
// `if (a) return b; return c;` => `return a ? b : c;`
ctx.notice_change();
let prev_stmt = result.pop().unwrap();
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
let prev_if = prev_if.unbox();
let Statement::ReturnStatement(mut prev_return) = prev_if.consequent else {
unreachable!()
};
let left_span = prev_return.span();
let right_span = ret_stmt.span();
// "if (a) return; return b;" => "return a ? void 0 : b;"
let left = prev_return
.argument
.take()
.unwrap_or_else(|| Expression::new_void_0(left_span, ctx));
// "if (a) return a; return;" => "return a ? b : void 0;"
let right = ret_stmt
.argument
.take()
.unwrap_or_else(|| Expression::new_void_0(right_span, ctx));
let argument = Self::minimize_conditional_after_if(
prev_if.span,
prev_if.test,
left,
right,
ctx,
);
ret_stmt.argument = Some(argument);
} else {
break 'return_loop;
}
}
}
result.push(Statement::ReturnStatement(ret_stmt));
@@ -874,6 +821,7 @@ impl<'a> PeepholeOptimizations {
false,
);
// `a; throw x` => `throw a, x`
if ctx.options().sequences
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
{
@@ -884,7 +832,7 @@ impl<'a> PeepholeOptimizations {
ctx.drop_statement(&dropped);
}
// "var a; throw a = b(), c;" => "var a = b(); throw c;"
// `var a; throw a = b(), c;` => `var a = b(); throw c;`
Self::merge_leading_assignments_to_declaration(
&mut throw_stmt.argument,
false,
@@ -892,6 +840,37 @@ impl<'a> PeepholeOptimizations {
ctx,
);
// `if (a) throw b; throw c;` => `throw a ? b : c;`
if ctx.options().sequences {
'Loop: while !result.is_empty() {
if let Some(Statement::IfStatement(if_stmt)) = result.last()
&& if_stmt.alternate.is_none()
&& matches!(&if_stmt.consequent, Statement::ThrowStatement(_))
&& !Self::conditional_expression_count_exceeded(&throw_stmt.argument)
{
ctx.notice_change();
let prev_stmt = result.pop().unwrap();
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
let prev_if = prev_if.unbox();
let Statement::ThrowStatement(prev_throw) = prev_if.consequent else {
unreachable!()
};
ctx.replace_expression_with(&mut throw_stmt.argument, |expr, ctx| {
Self::minimize_conditional_after_if(
prev_if.span,
prev_if.test,
prev_throw.unbox().argument,
expr,
ctx,
)
});
} else {
break 'Loop;
}
}
}
result.push(Statement::ThrowStatement(throw_stmt));
}
@@ -110,13 +110,10 @@ fn test_function_return_optimization() {
"function f(){g:if(a()){return;}else{return;} return;}",
"function f(){g:if(a())return;else return}",
); // function f(){g:a()}
test(
"function f(){g:{if(a()){return;}else{return;} return;}}",
"function f(){g:return a(),void 0}",
); // function f(){g:a()}
test("function f(){g:{if(a()){return;}else{return;} return;}}", "function f(){g:{a();return}}"); // function f(){g:a()}
test(
"function f(){g:{a();if(b()){return;}else{return;} return;}}",
"function f(){g:return a(),b(),void 0}",
"function f(){g:{a(),b();return}}",
); // function f(){g:a(),b()}
test(
"function f(){try{g:if(a()){throw 9;} return;}finally{return}}",
@@ -126,6 +123,23 @@ fn test_function_return_optimization() {
"function g(a,b){if(a){}else if(b){return()=>typeof f}else function f(){}}",
"function g(a,b){if(!a){if(b)return()=>typeof f;else function f(){}}}",
);
test("function g(){if(a)return b;return c;var x}", "function g(){return a?b:c;var x;}");
test("function g(){if(a)return;return;var x}", "function g(){if(!a){return;var x;}}");
test("function g(){if(a)throw b;throw c;var x}", "function g(){throw a?b:c;var x;}");
test(
"function g(){if(d){if(a)return b;return;}return c;}",
"function g(){return d?a?b:void 0:c;}",
);
}
#[test]
fn test_async_gen_function_return() {
test("async function* foo(){if(a)return b;return c;}", "async function* foo(){return a?b:c;}");
test("async function* foo(){if(a)return;return c;}", "async function* foo(){if(!a)return c;}");
test("async function* foo(){if(a)return b;return;}", "async function* foo(){if(a)return b;}");
test("async function* foo(){if(a)return;return;}", "async function* foo(){a;}");
test_same("async function* foo(){if(a){if(b)return;return d;}return e;}");
}
#[test]
+4 -4
View File
@@ -7,21 +7,21 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
287.63 kB | 89.01 kB | 90.07 kB | 30.85 kB | 31.95 kB | 2 | jquery.js
342.15 kB | 114.81 kB | 118.14 kB | 42.71 kB | 44.37 kB | 2 | vue.js
342.15 kB | 114.80 kB | 118.14 kB | 42.71 kB | 44.37 kB | 2 | vue.js
544.10 kB | 69.88 kB | 72.48 kB | 25.58 kB | 26.20 kB | 2 | lodash.js
555.77 kB | 262.75 kB | 270.13 kB | 87.66 kB | 90.80 kB | 2 | d3.js
1.01 MB | 436.04 kB | 458.89 kB | 121.78 kB | 126.71 kB | 2 | bundle.min.js
1.01 MB | 435.99 kB | 458.89 kB | 121.78 kB | 126.71 kB | 2 | bundle.min.js
1.25 MB | 634.39 kB | 646.76 kB | 158.43 kB | 163.73 kB | 2 | three.js
2.14 MB | 707.63 kB | 724.14 kB | 160.08 kB | 181.07 kB | 2 | victory.js
3.20 MB | 958.92 kB | 1.01 MB | 317.07 kB | 331.56 kB | 2 | echarts.js
3.20 MB | 958.89 kB | 1.01 MB | 317.07 kB | 331.56 kB | 2 | echarts.js
6.69 MB | 2.12 MB | 2.31 MB | 453.44 kB | 488.28 kB | 5 | antd.js
10.95 MB | 3.31 MB | 3.49 MB | 850.04 kB | 915.50 kB | 4 | typescript.js
10.95 MB | 3.31 MB | 3.49 MB | 849.97 kB | 915.50 kB | 4 | typescript.js
@@ -5,9 +5,9 @@ checker.ts:
sys deallocs: 150
sys alloc bytes: 14996600 # 15.00 MB
sys peak growth: 10169560 # 10.17 MB
arena allocs: 144024
arena reallocs: 28468
arena size: 19127248 # 19.13 MB
arena allocs: 143560
arena reallocs: 28269
arena size: 19100024 # 19.10 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -16,9 +16,9 @@ App.tsx:
sys deallocs: 61
sys alloc bytes: 2128555 # 2.13 MB
sys peak growth: 1612427 # 1.61 MB
arena allocs: 15675
arena reallocs: 2721
arena size: 2880752 # 2.88 MB
arena allocs: 15652
arena reallocs: 2709
arena size: 2879680 # 2.88 MB
RadixUIAdoptionSection.jsx:
file size: 2520 # 2.52 kB
@@ -38,9 +38,9 @@ pdf.mjs:
sys deallocs: 2427
sys alloc bytes: 5334247 # 5.33 MB
sys peak growth: 3693355 # 3.69 MB
arena allocs: 44801
arena reallocs: 7718
arena size: 6306768 # 6.31 MB
arena allocs: 44756
arena reallocs: 7699
arena size: 6304256 # 6.30 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -49,9 +49,9 @@ antd.js:
sys deallocs: 1038
sys alloc bytes: 29976031 # 29.98 MB
sys peak growth: 19934725 # 19.93 MB
arena allocs: 351645
arena reallocs: 76248
arena size: 48770232 # 48.77 MB
arena allocs: 351162
arena reallocs: 75941
arena size: 48738712 # 48.74 MB
binder.ts:
file size: 193077 # 193.08 kB
@@ -60,9 +60,9 @@ binder.ts:
sys deallocs: 33
sys alloc bytes: 963008 # 963.01 kB
sys peak growth: 657404 # 657.40 kB
arena allocs: 6809
arena reallocs: 842
arena size: 1164432 # 1.16 MB
arena allocs: 6789
arena reallocs: 834
arena size: 1162400 # 1.16 MB
kitchen-sink.tsx:
file size: 732898 # 732.90 kB
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
sys deallocs: 1854
sys alloc bytes: 5307512 # 5.31 MB
sys peak growth: 3707425 # 3.71 MB
arena allocs: 64956
arena reallocs: 12252
arena size: 9393120 # 9.39 MB
arena allocs: 64844
arena reallocs: 12187
arena size: 9385624 # 9.39 MB