diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 844c9db9073ea..a39f4b476c1e3 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -27,8 +27,9 @@ fn main() -> std::io::Result<()> { println!("{printed}"); if twice { - let printed = minify(&printed, source_type, mangle); - println!("{printed}"); + let printed2 = minify(&printed, source_type, mangle); + println!("{printed2}"); + println!("same = {}", printed == printed2); } Ok(()) diff --git a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs index ec7374dab60d1..d16a187660971 100644 --- a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs @@ -9,25 +9,37 @@ use crate::{CompressOptions, CompressorPass}; /// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2` pub struct CollapseVariableDeclarations { options: CompressOptions, + + changed: bool, } -impl<'a> CompressorPass<'a> for CollapseVariableDeclarations {} +impl<'a> CompressorPass<'a> for CollapseVariableDeclarations { + fn changed(&self) -> bool { + self.changed + } + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for CollapseVariableDeclarations { fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - if self.options.join_vars { - self.join_vars(stmts, ctx); - } + self.join_vars(stmts, ctx); } } impl<'a> CollapseVariableDeclarations { pub fn new(options: CompressOptions) -> Self { - Self { options } + Self { options, changed: false } } /// Join consecutive var statements - fn join_vars(&self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + if self.options.join_vars { + return; + } // Collect all the consecutive ranges that contain joinable vars. // This is required because Rust prevents in-place vec mutation. let mut ranges = vec![]; @@ -83,6 +95,7 @@ impl<'a> CollapseVariableDeclarations { } } *stmts = new_stmts; + self.changed = true; } } diff --git a/crates/oxc_minifier/src/ast_passes/exploit_assigns.rs b/crates/oxc_minifier/src/ast_passes/exploit_assigns.rs index d70ccb0bb6481..524b2b487ab4c 100644 --- a/crates/oxc_minifier/src/ast_passes/exploit_assigns.rs +++ b/crates/oxc_minifier/src/ast_passes/exploit_assigns.rs @@ -1,19 +1,31 @@ -use oxc_traverse::Traverse; +use oxc_ast::ast::*; +use oxc_traverse::{Traverse, TraverseCtx}; use crate::CompressorPass; /// Tries to chain assignments together. /// /// -pub struct ExploitAssigns; +pub struct ExploitAssigns { + changed: bool, +} + +impl<'a> CompressorPass<'a> for ExploitAssigns { + fn changed(&self) -> bool { + self.changed + } -impl<'a> CompressorPass<'a> for ExploitAssigns {} + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for ExploitAssigns {} impl ExploitAssigns { pub fn new() -> Self { - Self {} + Self { changed: false } } } diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 13ebd3ec8c1f4..c3bcd8558db51 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -18,7 +18,7 @@ pub use statement_fusion::StatementFusion; use oxc_ast::ast::Program; use oxc_semantic::{ScopeTree, SymbolTable}; -use oxc_traverse::{walk_program, Traverse, TraverseCtx}; +use oxc_traverse::{Traverse, TraverseCtx}; use crate::node_util::NodeUtil; @@ -33,11 +33,7 @@ impl<'a> NodeUtil for TraverseCtx<'a> { } pub trait CompressorPass<'a>: Traverse<'a> { - fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) - where - Self: Traverse<'a>, - Self: Sized, - { - walk_program(self, program, ctx); - } + fn changed(&self) -> bool; + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>); } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 84629360a9915..69fcae854167a 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -22,10 +22,19 @@ use crate::{ /// /// pub struct PeepholeFoldConstants { - evaluate: bool, + changed: bool, } -impl<'a> CompressorPass<'a> for PeepholeFoldConstants {} +impl<'a> CompressorPass<'a> for PeepholeFoldConstants { + fn changed(&self) -> bool { + self.changed + } + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for PeepholeFoldConstants { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { @@ -35,17 +44,12 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants { impl<'a> PeepholeFoldConstants { pub fn new() -> Self { - Self { evaluate: false } - } - - pub fn with_evaluate(mut self, yes: bool) -> Self { - self.evaluate = yes; - self + Self { changed: false } } // [optimizeSubtree](https://github.com/google/closure-compiler/blob/75335a5138dde05030747abfd3c852cd34ea7429/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L72) // TODO: tryReduceOperandsForOp - pub fn fold_expression(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + pub fn fold_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_expr) = match expr { Expression::BinaryExpression(e) => self.try_fold_binary_operator(e, ctx), Expression::LogicalExpression(e) @@ -60,6 +64,7 @@ impl<'a> PeepholeFoldConstants { _ => None, } { *expr = folded_expr; + self.changed = true; }; } @@ -92,13 +97,7 @@ impl<'a> PeepholeFoldConstants { &binary_expr.right, ctx, ), - // NOTE: string concat folding breaks our current evaluation of Test262 tests. The - // minifier is tested by comparing output of running the minifier once and twice, - // respectively. Since Test262Error messages include string concats, the outputs - // don't match (even though the produced code is valid). Additionally, We'll likely - // want to add `evaluate` checks for all constant folding, not just additions, but - // we're adding this here until a decision is made. - BinaryOperator::Addition if self.evaluate => { + BinaryOperator::Addition => { self.try_fold_addition(binary_expr.span, &binary_expr.left, &binary_expr.right, ctx) } _ => None, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index a447c9df706aa..c0db66ddf4855 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -10,9 +10,20 @@ use crate::CompressorPass; /// with `? :` and short-circuit binary operators. /// /// -pub struct PeepholeMinimizeConditions; +pub struct PeepholeMinimizeConditions { + changed: bool, +} + +impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions { + fn changed(&self) -> bool { + self.changed + } -impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions {} + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for PeepholeMinimizeConditions { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { @@ -21,13 +32,14 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions { _ => None, } { *expr = folded_expr; + self.changed = true; }; } } impl<'a> PeepholeMinimizeConditions { pub fn new() -> Self { - Self + Self { changed: false } } /// Try to minimize NOT nodes such as `!(x==y)`. diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index a1939af0b9181..fed59e69deaf3 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -10,17 +10,30 @@ use crate::{keep_var::KeepVar, node_util::NodeUtil, tri::Tri, CompressorPass}; /// Terser option: `dead_code: true`. /// /// See `KeepVar` at the end of this file for `var` hoisting logic. -pub struct PeepholeRemoveDeadCode; +pub struct PeepholeRemoveDeadCode { + changed: bool, +} + +impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode { + fn changed(&self) -> bool { + self.changed + } -impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode {} + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for PeepholeRemoveDeadCode { fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - Self::fold_if_statement(stmt, ctx); + self.fold_if_statement(stmt, ctx); } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); + if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) { + stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); + } self.dead_code_elimination(stmts, ctx); } @@ -30,13 +43,14 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode { _ => None, } { *expr = folded_expr; + self.changed = true; } } } impl<'a> PeepholeRemoveDeadCode { pub fn new() -> Self { - Self {} + Self { changed: false } } /// Removes dead code thats comes after `return` statements after inlining `if` statements @@ -76,6 +90,7 @@ impl<'a> PeepholeRemoveDeadCode { } let mut i = 0; + let len = stmts.len(); stmts.retain(|s| { i += 1; if i - 1 <= index { @@ -88,25 +103,35 @@ impl<'a> PeepholeRemoveDeadCode { false }); + let all_hoisted = keep_var.all_hoisted(); if let Some(stmt) = keep_var.get_variable_declaration_statement() { stmts.push(stmt); + if !all_hoisted { + self.changed = true; + } + } + + if stmts.len() != len { + self.changed = true; } } - fn fold_if_statement(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + fn fold_if_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { let Statement::IfStatement(if_stmt) = stmt else { return }; // Descend and remove `else` blocks first. if let Some(alternate) = &mut if_stmt.alternate { - Self::fold_if_statement(alternate, ctx); + self.fold_if_statement(alternate, ctx); if matches!(alternate, Statement::EmptyStatement(_)) { if_stmt.alternate = None; + self.changed = true; } } match ctx.get_boolean_value(&if_stmt.test) { Tri::True => { *stmt = ctx.ast.move_statement(&mut if_stmt.consequent); + self.changed = true; } Tri::False => { *stmt = if let Some(alternate) = &mut if_stmt.alternate { @@ -119,6 +144,7 @@ impl<'a> PeepholeRemoveDeadCode { .get_variable_declaration_statement() .unwrap_or_else(|| ctx.ast.statement_empty(SPAN)) }; + self.changed = true; } Tri::Unknown => {} } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 9302ce0331713..b6742d0e85467 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -14,9 +14,19 @@ use crate::{node_util::NodeUtil, CompressOptions, CompressorPass}; pub struct PeepholeSubstituteAlternateSyntax { options: CompressOptions, in_define_export: bool, + changed: bool, } -impl<'a> CompressorPass<'a> for PeepholeSubstituteAlternateSyntax {} +impl<'a> CompressorPass<'a> for PeepholeSubstituteAlternateSyntax { + fn changed(&self) -> bool { + self.changed + } + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { fn enter_statement(&mut self, stmt: &mut Statement<'a>, _ctx: &mut TraverseCtx<'a>) { @@ -30,7 +40,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { _ctx: &mut TraverseCtx<'a>, ) { // We may fold `void 1` to `void 0`, so compress it after visiting - Self::compress_return_statement(stmt); + self.compress_return_statement(stmt); } fn enter_variable_declaration( @@ -39,7 +49,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { _ctx: &mut TraverseCtx<'a>, ) { for declarator in decl.declarations.iter_mut() { - Self::compress_variable_declarator(declarator); + self.compress_variable_declarator(declarator); } } @@ -83,7 +93,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { impl<'a> PeepholeSubstituteAlternateSyntax { pub fn new(options: CompressOptions) -> Self { - Self { options, in_define_export: false } + Self { options, in_define_export: false, changed: false } } /* Utilities */ @@ -120,14 +130,14 @@ impl<'a> PeepholeSubstituteAlternateSyntax { /// Remove block from single line blocks /// `{ block } -> block` - #[allow(clippy::only_used_in_recursion)] // `&self` is only used in recursion - fn compress_block(&self, stmt: &mut Statement<'a>) { + fn compress_block(&mut self, stmt: &mut Statement<'a>) { if let Statement::BlockStatement(block) = stmt { // Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different // semantics according to AnnexB, which lead to different semantics. if block.body.len() == 1 && !block.body[0].is_declaration() { *stmt = block.body.remove(0); self.compress_block(stmt); + self.changed = true; } } } @@ -230,18 +240,20 @@ impl<'a> PeepholeSubstituteAlternateSyntax { /// /// `return undefined` -> `return` /// `return void 0` -> `return` - fn compress_return_statement(stmt: &mut ReturnStatement<'a>) { + fn compress_return_statement(&mut self, stmt: &mut ReturnStatement<'a>) { if stmt.argument.as_ref().is_some_and(|expr| expr.is_undefined() || expr.is_void_0()) { stmt.argument = None; + self.changed = true; } } - fn compress_variable_declarator(decl: &mut VariableDeclarator<'a>) { + fn compress_variable_declarator(&mut self, decl: &mut VariableDeclarator<'a>) { if decl.kind.is_const() { return; } if decl.init.as_ref().is_some_and(|init| init.is_undefined() || init.is_void_0()) { decl.init = None; + self.changed = true; } } } diff --git a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs index c6dbaad3fb242..6c9dd4fa5c8e1 100644 --- a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs @@ -13,7 +13,15 @@ pub struct RemoveSyntax { options: CompressOptions, } -impl<'a> CompressorPass<'a> for RemoveSyntax {} +impl<'a> CompressorPass<'a> for RemoveSyntax { + fn changed(&self) -> bool { + false + } + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for RemoveSyntax { fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) { diff --git a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs index ce31d65245dd0..c1d4604c0e904 100644 --- a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs +++ b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs @@ -10,32 +10,43 @@ use crate::CompressorPass; /// Tries to fuse all the statements in a block into a one statement by using COMMAs or statements. /// /// -pub struct StatementFusion; +pub struct StatementFusion { + changed: bool, +} -impl<'a> CompressorPass<'a> for StatementFusion {} +impl<'a> CompressorPass<'a> for StatementFusion { + fn changed(&self) -> bool { + self.changed + } + + fn build(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.changed = false; + oxc_traverse::walk_program(self, program, ctx); + } +} impl<'a> Traverse<'a> for StatementFusion { fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - Self::fuse_statements(&mut program.body, ctx); + self.fuse_statements(&mut program.body, ctx); } fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { - Self::fuse_statements(&mut body.statements, ctx); + self.fuse_statements(&mut body.statements, ctx); } fn exit_block_statement(&mut self, block: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { - Self::fuse_statements(&mut block.body, ctx); + self.fuse_statements(&mut block.body, ctx); } } impl<'a> StatementFusion { pub fn new() -> Self { - Self {} + Self { changed: false } } - fn fuse_statements(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + fn fuse_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { if Self::can_fuse_into_one_statement(stmts) { - Self::fuse_into_one_statement(stmts, ctx); + self.fuse_into_one_statement(stmts, ctx); } } @@ -75,7 +86,11 @@ impl<'a> StatementFusion { } } - fn fuse_into_one_statement(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + fn fuse_into_one_statement( + &mut self, + stmts: &mut Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { let len = stmts.len(); let mut expressions = ctx.ast.vec(); @@ -103,6 +118,7 @@ impl<'a> StatementFusion { Self::fuse_expression_into_control_flow_statement(last, expressions, ctx); *stmts = ctx.ast.vec1(ctx.ast.move_statement(last)); + self.changed = true; } fn fuse_expression_into_control_flow_statement( diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 00b85cd3b7e51..5d7a1135704f0 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -35,69 +35,50 @@ impl<'a> Compressor<'a> { program: &mut Program<'a>, ) { let mut ctx = TraverseCtx::new(scopes, symbols, self.allocator); - self.remove_syntax(program, &mut ctx); + RemoveSyntax::new(self.options).build(program, &mut ctx); if self.options.dead_code_elimination { self.dead_code_elimination(program, &mut ctx); return; } - // earlyPeepholeOptimizations - // TODO: MinimizeExitPoints - self.minimize_conditions(program, &mut ctx); - self.substitute_alternate_syntax(program, &mut ctx); - // TODO: PeepholeReplaceKnownMethods - self.remove_dead_code(program, &mut ctx); - self.fold_constants(program, &mut ctx); - - // latePeepholeOptimizations - // TODO: StatementFusion - self.remove_dead_code(program, &mut ctx); - self.minimize_conditions(program, &mut ctx); - self.substitute_alternate_syntax(program, &mut ctx); - // TODO: PeepholeReplaceKnownMethods - self.fold_constants(program, &mut ctx); - - self.exploit_assigns(program, &mut ctx); - self.collapse_variable_declarations(program, &mut ctx); + ExploitAssigns::new().build(program, &mut ctx); + CollapseVariableDeclarations::new(self.options).build(program, &mut ctx); + + // See `latePeepholeOptimizations` + let mut passes: [&mut dyn CompressorPass; 5] = [ + &mut StatementFusion::new(), + &mut PeepholeRemoveDeadCode::new(), + // TODO: MinimizeExitPoints + &mut PeepholeMinimizeConditions::new(), + &mut PeepholeSubstituteAlternateSyntax::new(self.options), + // TODO: PeepholeReplaceKnownMethods + &mut PeepholeFoldConstants::new(), + ]; + + let mut i = 0; + loop { + let mut changed = false; + for pass in &mut passes { + pass.build(program, &mut ctx); + if pass.changed() { + changed = true; + } + } + if !changed { + break; + } + if i > 50 { + debug_assert!(false, "Ran in a infinite loop."); + break; + } + i += 1; + } } fn dead_code_elimination(self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - self.fold_constants(program, ctx); - self.minimize_conditions(program, ctx); - self.remove_dead_code(program, ctx); - } - - fn remove_syntax(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - RemoveSyntax::new(self.options).build(program, ctx); - } - - fn minimize_conditions(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + PeepholeFoldConstants::new().build(program, ctx); PeepholeMinimizeConditions::new().build(program, ctx); - } - - fn fold_constants(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - PeepholeFoldConstants::new().with_evaluate(self.options.evaluate).build(program, ctx); - } - - fn substitute_alternate_syntax(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - PeepholeSubstituteAlternateSyntax::new(self.options).build(program, ctx); - } - - fn remove_dead_code(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { PeepholeRemoveDeadCode::new().build(program, ctx); } - - #[allow(unused)] - fn statement_fusion(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - StatementFusion::new().build(program, ctx); - } - - fn collapse_variable_declarations(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - CollapseVariableDeclarations::new(self.options).build(program, ctx); - } - - fn exploit_assigns(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - ExploitAssigns::new().build(program, ctx); - } } diff --git a/crates/oxc_minifier/src/keep_var.rs b/crates/oxc_minifier/src/keep_var.rs index ccd10dd7429bd..e7749733821ea 100644 --- a/crates/oxc_minifier/src/keep_var.rs +++ b/crates/oxc_minifier/src/keep_var.rs @@ -4,6 +4,7 @@ use oxc_span::{Atom, Span, SPAN}; pub struct KeepVar<'a> { ast: AstBuilder<'a>, vars: std::vec::Vec<(Atom<'a>, Span)>, + all_hoisted: bool, } impl<'a> Visit<'a> for KeepVar<'a> { @@ -37,6 +38,9 @@ impl<'a> Visit<'a> for KeepVar<'a> { decl.bound_names(&mut |ident| { self.vars.push((ident.name.clone(), ident.span)); }); + if decl.has_init() { + self.all_hoisted = false; + } } } _ => {} @@ -46,7 +50,11 @@ impl<'a> Visit<'a> for KeepVar<'a> { impl<'a> KeepVar<'a> { pub fn new(ast: AstBuilder<'a>) -> Self { - Self { ast, vars: std::vec![] } + Self { ast, vars: std::vec![], all_hoisted: true } + } + + pub fn all_hoisted(&self) -> bool { + self.all_hoisted } pub fn get_variable_declaration_statement(self) -> Option> { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index b80d2ae044200..858312f949565 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,26 +1,26 @@ Original | Minified | esbuild | Gzip | esbuild -72.14 kB | 24.32 kB | 23.70 kB | 8.71 kB | 8.54 kB | react.development.js +72.14 kB | 24.47 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js -173.90 kB | 61.79 kB | 59.82 kB | 19.57 kB | 19.33 kB | moment.js +173.90 kB | 61.71 kB | 59.82 kB | 19.56 kB | 19.33 kB | moment.js -287.63 kB | 92.89 kB | 90.07 kB | 32.32 kB | 31.95 kB | jquery.js +287.63 kB | 92.83 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js -342.15 kB | 122.41 kB | 118.14 kB | 44.77 kB | 44.37 kB | vue.js +342.15 kB | 124.14 kB | 118.14 kB | 44.81 kB | 44.37 kB | vue.js -544.10 kB | 73.54 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js +544.10 kB | 74.13 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js -555.77 kB | 277.01 kB | 270.13 kB | 91.22 kB | 90.80 kB | d3.js +555.77 kB | 278.71 kB | 270.13 kB | 91.40 kB | 90.80 kB | d3.js -1.01 MB | 468.10 kB | 458.89 kB | 126.67 kB | 126.71 kB | bundle.min.js +1.01 MB | 470.11 kB | 458.89 kB | 126.97 kB | 126.71 kB | bundle.min.js -1.25 MB | 663.51 kB | 646.76 kB | 163.76 kB | 163.73 kB | three.js +1.25 MB | 671.02 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js -2.14 MB | 742.01 kB | 724.14 kB | 181.59 kB | 181.07 kB | victory.js +2.14 MB | 756.70 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js -3.20 MB | 1.03 MB | 1.01 MB | 332.45 kB | 331.56 kB | echarts.js +3.20 MB | 1.05 MB | 1.01 MB | 334.11 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 496.45 kB | 488.28 kB | antd.js +6.69 MB | 2.44 MB | 2.31 MB | 498.90 kB | 488.28 kB | antd.js -10.95 MB | 3.56 MB | 3.49 MB | 911.22 kB | 915.50 kB | typescript.js +10.95 MB | 3.59 MB | 3.49 MB | 913.91 kB | 915.50 kB | typescript.js