feat(minifier): optional catch binding when catch param is unused (#8221)

This commit is contained in:
Boshen 2025-01-02 13:20:49 +00:00
parent 4a29845d82
commit 9c1afa4729
5 changed files with 59 additions and 64 deletions

View file

@ -50,7 +50,7 @@ pub struct PeepholeOptimizations {
}
impl PeepholeOptimizations {
pub fn new(in_fixed_loop: bool, options: CompressOptions) -> Self {
pub fn new(in_fixed_loop: bool, _options: CompressOptions) -> Self {
Self {
x0_statement_fusion: StatementFusion::new(),
x1_minimize_exit_points: MinimizeExitPoints::new(),
@ -59,7 +59,6 @@ impl PeepholeOptimizations {
x4_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
x5_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop),
x6_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
options,
in_fixed_loop,
),
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
@ -185,10 +184,6 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
) {
self.x9_convert_to_dotted_properties.exit_member_expression(expr, ctx);
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
self.x6_peephole_substitute_alternate_syntax.exit_catch_clause(catch, ctx);
}
}
pub struct DeadCodeElimination {

View file

@ -4,20 +4,18 @@ use oxc_ecmascript::{ToInt32, ToJsString};
use oxc_semantic::IsGlobalReference;
use oxc_span::{GetSpan, SPAN};
use oxc_syntax::{
es_target::ESTarget,
number::NumberBase,
operator::{BinaryOperator, UnaryOperator},
};
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use crate::{node_util::Ctx, CompressOptions, CompressorPass};
use crate::{node_util::Ctx, CompressorPass};
/// A peephole optimization that minimizes code by simplifying conditional
/// expressions, replacing IFs with HOOKs, replacing object constructors
/// with literals, and simplifying returns.
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntax.java>
pub struct PeepholeSubstituteAlternateSyntax {
options: CompressOptions,
/// Do not compress syntaxes that are hard to analyze inside the fixed loop.
/// e.g. Do not compress `undefined -> void 0`, `true` -> `!0`.
/// Opposite of `late` in Closure Compiler.
@ -46,10 +44,6 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.compress_return_statement(stmt);
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, _ctx: &mut TraverseCtx<'a>) {
self.compress_catch_clause(catch);
}
fn exit_variable_declaration(
&mut self,
decl: &mut VariableDeclaration<'a>,
@ -116,8 +110,8 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
}
impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
pub fn new(options: CompressOptions, in_fixed_loop: bool) -> Self {
Self { options, in_fixed_loop, in_define_export: false, changed: false }
pub fn new(in_fixed_loop: bool) -> Self {
Self { in_fixed_loop, in_define_export: false, changed: false }
}
/// Test `Object.defineProperty(exports, ...)`
@ -691,20 +685,6 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
fn empty_array_literal(ctx: Ctx<'a, 'b>) -> Expression<'a> {
Self::array_literal(ctx.ast.vec(), ctx)
}
fn compress_catch_clause(&mut self, catch: &mut CatchClause<'a>) {
if catch.body.body.is_empty()
&& !self.in_fixed_loop
&& self.options.target >= ESTarget::ES2019
{
if let Some(param) = &catch.param {
if param.pattern.kind.is_binding_identifier() {
catch.param = None;
self.changed = true;
}
};
}
}
}
/// Port from <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntaxTest.java>
@ -712,12 +692,11 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
mod test {
use oxc_allocator::Allocator;
use crate::{tester, CompressOptions};
use crate::tester;
fn test(source_text: &str, expected: &str) {
let allocator = Allocator::default();
let options = CompressOptions::default();
let mut pass = super::PeepholeSubstituteAlternateSyntax::new(options, false);
let mut pass = super::PeepholeSubstituteAlternateSyntax::new(false);
tester::test(&allocator, source_text, expected, &mut pass);
}
@ -1197,20 +1176,4 @@ mod test {
test("typeof foo !== `number`", "typeof foo != 'number'");
test("`number` !== typeof foo", "'number' != typeof foo");
}
#[test]
fn optional_catch_binding() {
test("try {} catch(e) {}", "try {} catch {}");
test_same("try {} catch([e]) {}");
test_same("try {} catch({e}) {}");
let allocator = Allocator::default();
let options = CompressOptions {
target: oxc_syntax::es_target::ESTarget::ES2018,
..CompressOptions::default()
};
let mut pass = super::PeepholeSubstituteAlternateSyntax::new(options, false);
let code = "try {} catch(e) {}";
tester::test(&allocator, code, code, &mut pass);
}
}

View file

@ -1,15 +1,18 @@
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_syntax::symbol::SymbolId;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use rustc_hash::FxHashSet;
use crate::CompressorPass;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_syntax::{es_target::ESTarget, symbol::SymbolId};
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use crate::{CompressOptions, CompressorPass};
/// Remove Unused Code
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/RemoveUnusedCode.java>
pub struct RemoveUnusedCode {
options: CompressOptions,
pub(crate) changed: bool,
symbol_ids_to_remove: FxHashSet<SymbolId>,
@ -58,11 +61,28 @@ impl<'a> Traverse<'a> for RemoveUnusedCode {
}
}
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, _ctx: &mut TraverseCtx<'a>) {
self.compress_catch_clause(catch);
}
}
impl RemoveUnusedCode {
pub fn new() -> Self {
Self { changed: false, symbol_ids_to_remove: FxHashSet::default() }
pub fn new(options: CompressOptions) -> Self {
Self { options, changed: false, symbol_ids_to_remove: FxHashSet::default() }
}
fn compress_catch_clause(&mut self, catch: &mut CatchClause<'_>) {
if self.options.target >= ESTarget::ES2019 {
if let Some(param) = &catch.param {
if let BindingPatternKind::BindingIdentifier(ident) = &param.pattern.kind {
if self.symbol_ids_to_remove.contains(&ident.symbol_id()) {
catch.param = None;
self.changed = true;
}
}
};
}
}
}
@ -70,11 +90,12 @@ impl RemoveUnusedCode {
mod test {
use oxc_allocator::Allocator;
use crate::tester;
use crate::{tester, CompressOptions};
fn test(source_text: &str, expected: &str) {
let allocator = Allocator::default();
let mut pass = super::RemoveUnusedCode::new();
let options = CompressOptions::default();
let mut pass = super::RemoveUnusedCode::new(options);
tester::test(&allocator, source_text, expected, &mut pass);
}
@ -82,6 +103,22 @@ mod test {
test(source_text, source_text);
}
#[test]
fn optional_catch_binding() {
test("try {} catch(e) {}", "try {} catch {}");
test_same("try {} catch([e]) {}");
test_same("try {} catch({e}) {}");
let allocator = Allocator::default();
let options = CompressOptions {
target: oxc_syntax::es_target::ESTarget::ES2018,
..CompressOptions::default()
};
let mut pass = super::RemoveUnusedCode::new(options);
let code = "try {} catch(e) {}";
tester::test(&allocator, code, code, &mut pass);
}
#[test]
fn simple() {
test("var x", "");

View file

@ -34,7 +34,7 @@ impl<'a> Compressor<'a> {
) {
let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator);
RemoveSyntax::new(self.options).build(program, &mut ctx);
RemoveUnusedCode::new().build(program, &mut ctx);
RemoveUnusedCode::new(self.options).build(program, &mut ctx);
Normalize::new().build(program, &mut ctx);
PeepholeOptimizations::new(true, self.options).run_in_loop(program, &mut ctx);
PeepholeOptimizations::new(false, self.options).build(program, &mut ctx);

View file

@ -5,9 +5,9 @@ Original | minified | minified | gzip | gzip | Fixture
173.90 kB | 59.90 kB | 59.82 kB | 19.43 kB | 19.33 kB | moment.js
287.63 kB | 90.39 kB | 90.07 kB | 32.12 kB | 31.95 kB | jquery.js
287.63 kB | 90.37 kB | 90.07 kB | 32.10 kB | 31.95 kB | jquery.js
342.15 kB | 118.48 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js
342.15 kB | 118.47 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js
544.10 kB | 71.84 kB | 72.48 kB | 26.19 kB | 26.20 kB | lodash.js
@ -15,13 +15,13 @@ Original | minified | minified | gzip | gzip | Fixture
1.01 MB | 460.75 kB | 458.89 kB | 126.88 kB | 126.71 kB | bundle.min.js
1.25 MB | 653.17 kB | 646.76 kB | 163.57 kB | 163.73 kB | three.js
1.25 MB | 653.16 kB | 646.76 kB | 163.57 kB | 163.73 kB | three.js
2.14 MB | 726.70 kB | 724.14 kB | 180.25 kB | 181.07 kB | victory.js
2.14 MB | 726.69 kB | 724.14 kB | 180.25 kB | 181.07 kB | victory.js
3.20 MB | 1.01 MB | 1.01 MB | 332.13 kB | 331.56 kB | echarts.js
6.69 MB | 2.32 MB | 2.31 MB | 493.00 kB | 488.28 kB | antd.js
6.69 MB | 2.32 MB | 2.31 MB | 492.95 kB | 488.28 kB | antd.js
10.95 MB | 3.51 MB | 3.49 MB | 910.06 kB | 915.50 kB | typescript.js
10.95 MB | 3.51 MB | 3.49 MB | 910.02 kB | 915.50 kB | typescript.js