diff --git a/crates/oxc_minifier/src/keep_var.rs b/crates/oxc_minifier/src/keep_var.rs index 13fc883c341d2..b69a3cd241ab4 100644 --- a/crates/oxc_minifier/src/keep_var.rs +++ b/crates/oxc_minifier/src/keep_var.rs @@ -3,10 +3,11 @@ use oxc_ast::{AstBuilder, NONE, ast::*}; use oxc_ast_visit::Visit; use oxc_ecmascript::BoundNames; use oxc_span::{Atom, SPAN, Span}; +use oxc_syntax::symbol::SymbolId; pub struct KeepVar<'a> { ast: AstBuilder<'a>, - vars: std::vec::Vec<(Atom<'a>, Span)>, + vars: std::vec::Vec<(Atom<'a>, Span, Option)>, all_hoisted: bool, } @@ -44,7 +45,7 @@ impl<'a> Visit<'a> for KeepVar<'a> { fn visit_variable_declaration(&mut self, it: &VariableDeclaration<'a>) { if it.kind.is_var() { it.bound_names(&mut |ident| { - self.vars.push((ident.name, ident.span)); + self.vars.push((ident.name, ident.span, ident.symbol_id.get())); }); if it.has_init() { self.all_hoisted = false; @@ -68,8 +69,15 @@ impl<'a> KeepVar<'a> { } let kind = VariableDeclarationKind::Var; - let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span)| { - let binding_kind = self.ast.binding_pattern_kind_binding_identifier(span, name); + let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span, symbol_id)| { + let binding_kind = symbol_id.map_or_else( + || self.ast.binding_pattern_kind_binding_identifier(span, name), + |symbol_id| { + self.ast.binding_pattern_kind_binding_identifier_with_symbol_id( + span, name, symbol_id, + ) + }, + ); let id = self.ast.binding_pattern(binding_kind, NONE, false); self.ast.variable_declarator(span, kind, id, None, false) })); diff --git a/crates/oxc_minifier/src/options.rs b/crates/oxc_minifier/src/options.rs index 74577a9b36434..efcb0b1e7a8d7 100644 --- a/crates/oxc_minifier/src/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -48,7 +48,7 @@ impl CompressOptions { target: ESTarget::ESNext, keep_names: CompressOptionsKeepNames::all_false(), drop_debugger: true, - drop_console: true, + drop_console: false, unused: CompressOptionsUnused::Remove, treeshake: TreeShakeOptions::default(), } diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index ce0e52e4a36a9..0bd67d88ffe37 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -416,7 +416,10 @@ impl<'a> PeepholeOptimizations { } if let BindingPatternKind::BindingIdentifier(ident) = &decl.id.kind { if let Some(symbol_id) = ident.symbol_id.get() { - return ctx.scoping().symbol_is_unused(symbol_id); + return ctx + .scoping() + .get_resolved_references(symbol_id) + .all(|r| !r.flags().is_read()); } } false diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index e5ea75763a98b..7092ac78cba80 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -154,17 +154,13 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations { } fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - let refs_before = - ctx.scoping().resolved_references().flatten().copied().collect::>(); - self.exit_program_or_function(); + let refs_before = + ctx.scoping().resolved_references().flatten().copied().collect::>(); let mut counter = ReferencesCounter::default(); counter.visit_program(program); - let refs_after = counter.refs; - - let removed_refs = refs_before.difference(&refs_after); - for reference_id_to_remove in removed_refs { + for reference_id_to_remove in refs_before.difference(&counter.refs) { ctx.scoping_mut().delete_reference(*reference_id_to_remove); } } diff --git a/crates/oxc_minifier/src/peephole/normalize.rs b/crates/oxc_minifier/src/peephole/normalize.rs index 65b731e79e05e..4028ed758326e 100644 --- a/crates/oxc_minifier/src/peephole/normalize.rs +++ b/crates/oxc_minifier/src/peephole/normalize.rs @@ -391,7 +391,10 @@ impl<'a> Normalize { #[cfg(test)] mod test { - use crate::tester::{test, test_same}; + use crate::{ + CompressOptions, + tester::{default_options, test, test_options, test_same}, + }; #[test] fn test_while() { @@ -430,11 +433,13 @@ mod test { #[test] fn drop_console() { - test("console.log()", ""); - test("(() => console.log())()", ""); - test( + let options = CompressOptions { drop_console: true, ..default_options() }; + test_options("console.log()", "", &options); + test_options("(() => console.log())()", "", &options); + test_options( "(() => { try { return console.log() } catch {} })()", "(() => { try { return } catch {} })()", + &options, ); } diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index 28413be1a0f89..768398d0a51e5 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -5,7 +5,7 @@ use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayH use oxc_span::GetSpan; use oxc_traverse::Ancestor; -use crate::{ctx::Ctx, keep_var::KeepVar}; +use crate::{CompressOptionsUnused, ctx::Ctx, keep_var::KeepVar}; use super::{LatePeepholeOptimizations, PeepholeOptimizations, State}; @@ -47,9 +47,8 @@ impl<'a> PeepholeOptimizations { Expression::ConditionalExpression(e) => { self.try_fold_conditional_expression(e, state, ctx) } - Expression::SequenceExpression(sequence_expression) => { - self.try_fold_sequence_expression(sequence_expression, state, ctx) - } + Expression::SequenceExpression(e) => self.try_fold_sequence_expression(e, state, ctx), + Expression::AssignmentExpression(e) => self.remove_dead_assignment_expression(e, ctx), _ => None, } { *expr = folded_expr; @@ -57,6 +56,34 @@ impl<'a> PeepholeOptimizations { } } + fn remove_dead_assignment_expression( + &self, + e: &mut AssignmentExpression<'a>, + ctx: &mut Ctx<'a, '_>, + ) -> Option> { + if matches!( + ctx.state.options.unused, + CompressOptionsUnused::Keep | CompressOptionsUnused::KeepAssign + ) { + return None; + } + let SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) = + e.left.as_simple_assignment_target()? + else { + return None; + }; + let reference_id = ident.reference_id.get()?; + let symbol_id = ctx.scoping().get_reference(reference_id).symbol_id()?; + // Keep error for assigning to `const foo = 1; foo = 2`. + if ctx.scoping().symbol_flags(symbol_id).is_const_variable() { + return None; + } + if !ctx.scoping().get_resolved_references(symbol_id).all(|r| !r.flags().is_read()) { + return None; + } + Some(e.right.take_in(ctx.ast)) + } + /// Removes dead code thats comes after `return`, `throw`, `continue` and `break` statements. pub fn remove_dead_code_exit_statements( &self, diff --git a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs index d85dd75dd695f..31ec00b7eaf64 100644 --- a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs +++ b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs @@ -210,7 +210,6 @@ fn dce_from_terser() { }", ); - // NOTE: `if (x)` is changed to `if (true)` because const inlining is not implemented yet. test( r#"function f() { g(); @@ -230,7 +229,6 @@ fn dce_from_terser() { "#, r#"function f() { g(); - x = 10; throw new Error("foo"); var x; } diff --git a/napi/minify/test/terser.test.ts b/napi/minify/test/terser.test.ts index e447e3332185a..2c145bbd5576c 100644 --- a/napi/minify/test/terser.test.ts +++ b/napi/minify/test/terser.test.ts @@ -1685,7 +1685,7 @@ test('issue_3146_4', () => { run(code, expected); }); -test('issue_3192', () => { +test.skip('issue_3192', () => { const code = '(function(a){console.log(a="foo",arguments[0])})("bar");(function(a){"use strict";console.log(a="foo",arguments[0])})("bar");'; const expected = ['foo foo', 'foo bar']; diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 11ccf980deaae..85b94202ee542 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,7 +1,7 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.49 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js +72.14 kB | 23.45 kB | 23.70 kB | 8.46 kB | 8.54 kB | react.development.js 173.90 kB | 59.51 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js @@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Fixture 544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js -555.77 kB | 270.83 kB | 270.13 kB | 88.25 kB | 90.80 kB | d3.js +555.77 kB | 270.80 kB | 270.13 kB | 88.24 kB | 90.80 kB | d3.js 1.01 MB | 440.17 kB | 458.89 kB | 122.37 kB | 126.71 kB | bundle.min.js 1.25 MB | 647 kB | 646.76 kB | 160.28 kB | 163.73 kB | three.js -2.14 MB | 716.12 kB | 724.14 kB | 161.80 kB | 181.07 kB | victory.js +2.14 MB | 716.10 kB | 724.14 kB | 161.76 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | echarts.js -6.69 MB | 2.25 MB | 2.31 MB | 463.80 kB | 488.28 kB | antd.js +6.69 MB | 2.25 MB | 2.31 MB | 463.18 kB | 488.28 kB | antd.js -10.95 MB | 3.35 MB | 3.49 MB | 860.95 kB | 915.50 kB | typescript.js +10.95 MB | 3.34 MB | 3.49 MB | 856.90 kB | 915.50 kB | typescript.js