diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index d7c731898d9ca..00e0cd971e592 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -1107,8 +1107,39 @@ impl<'a> PeepholeOptimizations { return Some(changed); } } - Expression::AssignmentExpression(_assign_expr) => { - // TODO: requires implementing `MayHaveSideEffects` to `AssignmentTarget` + Expression::AssignmentExpression(assign_expr) => { + if assign_expr.left.may_have_side_effects(ctx) { + // Do not reorder past a side effect in an assignment target, as that may + // change the replacement value. For example, "fn()" may change "a" here: + // ```js + // let a = 1; + // foo[fn()] = a; + // ``` + return Some(false); + } + if assign_expr.operator != AssignmentOperator::Assign && replacement_has_side_effect + { + // If this is a read-modify-write assignment and the replacement has side + // effects, don't reorder it past the assignment target. The assignment + // target is being read so it may be changed by the side effect. For + // example, "fn()" may change "foo" here: + // ```js + // let a = fn(); + // foo += a; + // ``` + return Some(false); + } + // If we get here then it should be safe to attempt to substitute the + // replacement past the left operand into the right operand. + if let Some(changed) = Self::substitute_single_use_symbol_in_expression( + &mut assign_expr.right, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) { + return Some(changed); + } } Expression::LogicalExpression(logical_expr) => { if let Some(changed) = Self::substitute_single_use_symbol_in_expression( @@ -1130,8 +1161,16 @@ impl<'a> PeepholeOptimizations { return Some(changed); } } - Expression::PrivateInExpression(_private_in_expr) => { - // TODO: implement + Expression::PrivateInExpression(private_in_expr) => { + if let Some(changed) = Self::substitute_single_use_symbol_in_expression( + &mut private_in_expr.right, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) { + return Some(changed); + } } Expression::ConditionalExpression(cond_expr) => { if let Some(changed) = Self::substitute_single_use_symbol_in_expression( @@ -1203,8 +1242,16 @@ impl<'a> PeepholeOptimizations { return Some(changed); } } - Expression::PrivateFieldExpression(_private_field_expr) => { - // TODO: implement + Expression::PrivateFieldExpression(private_field_expr) => { + if let Some(changed) = Self::substitute_single_use_symbol_in_expression( + &mut private_field_expr.object, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) { + return Some(changed); + } } Expression::CallExpression(call_expr) => { // Don't substitute something into a call target that could change "this" @@ -1260,8 +1307,55 @@ impl<'a> PeepholeOptimizations { } } } - Expression::NewExpression(_new_expr) => { - // TODO: implement + Expression::NewExpression(new_expr) => { + // Don't substitute something into a call target that could change "this" + if !((replacement.is_member_expression() + || matches!(replacement, Expression::ChainExpression(_))) + && new_expr.callee.is_identifier_reference()) + { + if let Some(changed) = Self::substitute_single_use_symbol_in_expression( + &mut new_expr.callee, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) { + return Some(changed); + } + + for arg in &mut new_expr.arguments { + match arg { + Argument::SpreadElement(spread_elem) => { + if let Some(changed) = + Self::substitute_single_use_symbol_in_expression( + &mut spread_elem.argument, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) + { + return Some(changed); + } + // spread element may have sideeffects + return Some(false); + } + match_expression!(Argument) => { + if let Some(changed) = + Self::substitute_single_use_symbol_in_expression( + arg.to_expression_mut(), + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) + { + return Some(changed); + } + } + } + } + } } Expression::ArrayExpression(array_expr) => { for elem in &mut array_expr.elements { @@ -1391,8 +1485,18 @@ impl<'a> PeepholeOptimizations { chain_expr.expression = expr.into_chain_element().expect("should be chain element"); return changed; } - Expression::SequenceExpression(_sequence_expr) => { - // TODO: implement + Expression::SequenceExpression(sequence_expr) => { + for expr in &mut sequence_expr.expressions { + if let Some(changed) = Self::substitute_single_use_symbol_in_expression( + expr, + search_for, + replacement, + replacement_has_side_effect, + ctx, + ) { + return Some(changed); + } + } } _ => {} } diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index e2977c711adc6..5ce72554743d3 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -1793,54 +1793,54 @@ fn test_inline_single_use_variable() { "var foo; function wrapper(arg0, arg1) { let x = foo; x ||= 2}", "var foo; function wrapper(arg0, arg1) { let x = foo; x ||= 2;}", ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 = x}", - // "var foo; function wrapper(arg0, arg1) { arg0 = foo;}", - // ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 += x}", - // "var foo; function wrapper(arg0, arg1) { arg0 += foo;}", - // ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 ||= x}", - // "var foo; function wrapper(arg0, arg1) { arg0 ||= foo;}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); arg0 = x}", - // "function wrapper(arg0, arg1) { arg0 = fn();}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); arg0 += x}", - // "function wrapper(arg0, arg1) { let x = fn(); arg0 += x;}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); arg0 ||= x}", - // "function wrapper(arg0, arg1) { let x = fn(); arg0 ||= x;}", - // ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z = x}", - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z = x;}", - // ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z += x}", - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z += x;}", - // ); - // test( - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z ||= x}", - // "var foo; function wrapper(arg0, arg1) { let x = foo; y.z ||= x;}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); y.z = x}", - // "function wrapper(arg0, arg1) { let x = fn(); y.z = x;}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); y.z += x}", - // "function wrapper(arg0, arg1) { let x = fn(); y.z += x;}", - // ); - // test( - // "function wrapper(arg0, arg1) { let x = fn(); y.z ||= x}", - // "function wrapper(arg0, arg1) { let x = fn(); y.z ||= x;}", - // ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 = x}", + "var foo; function wrapper(arg0, arg1) { arg0 = foo;}", + ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 += x}", + "var foo; function wrapper(arg0, arg1) { arg0 += foo;}", + ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; arg0 ||= x}", + "var foo; function wrapper(arg0, arg1) { arg0 ||= foo;}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); arg0 = x}", + "function wrapper(arg0, arg1) { arg0 = fn();}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); arg0 += x}", + "function wrapper(arg0, arg1) { let x = fn(); arg0 += x;}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); arg0 ||= x}", + "function wrapper(arg0, arg1) { let x = fn(); arg0 ||= x;}", + ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z = x}", + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z = x;}", + ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z += x}", + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z += x;}", + ); + test( + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z ||= x}", + "var foo; function wrapper(arg0, arg1) { let x = foo; y.z ||= x;}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); y.z = x}", + "function wrapper(arg0, arg1) { let x = fn(); y.z = x;}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); y.z += x}", + "function wrapper(arg0, arg1) { let x = fn(); y.z += x;}", + ); + test( + "function wrapper(arg0, arg1) { let x = fn(); y.z ||= x}", + "function wrapper(arg0, arg1) { let x = fn(); y.z ||= x;}", + ); test( "function wrapper(arg0, arg1) { let x = arg0; return x ? y : z;}", "function wrapper(arg0, arg1) { return arg0 ? y : z;}", diff --git a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs index ef251292ac4da..54285890e3ae8 100644 --- a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs +++ b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs @@ -4,6 +4,122 @@ use super::{test, test_same}; fn test_inline_single_use_variable() { test_same("function wrapper(arg0, arg1) {using x = foo; return x}"); test_same("async function wrapper(arg0, arg1) { await using x = foo; return x}"); + + test( + " + class Foo { + #foo; + static { + let v = this; + let r = #foo in v; + console.log(r); + } + } + ", + " + class Foo { + #foo; + static { + let r = #foo in this; + console.log(r); + } + } + ", + ); + test( + " + class Foo { + #foo; + static { + let x = foo; + this.#foo = x; + } + } + ", + " + class Foo { + #foo; + static { + this.#foo = foo; + } + } + ", + ); + test( + " + class Foo { + #foo; + static { + let x = this; + let y = x.#foo; + console.log(y); + } + } + ", + " + class Foo { + #foo; + static { + let y = this.#foo; + console.log(y); + } + } + ", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0; return new arg1(...x);}", + "function wrapper(arg0, arg1) { return new arg1(...arg0);}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0; return new arg1(x, ...arg1);}", + "function wrapper(arg0, arg1) { return new arg1(arg0, ...arg1);}", + ); + test_same("function wrapper(arg0, arg1) { let x = arg0; return new arg1(...arg1, x);}"); + test( + "function wrapper(arg0, arg1) { let x = arg0; new arg1(x);}", + "function wrapper(arg0, arg1) { new arg1(arg0);}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0; new x()}", + "function wrapper(arg0, arg1) { new arg0();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0; new (0, x)()}", + "function wrapper(arg0, arg1) { new arg0();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0.foo; new x.bar()}", + "function wrapper(arg0, arg1) { new arg0.foo.bar();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0.foo; new x[bar]()}", + "function wrapper(arg0, arg1) { new arg0.foo[bar]();}", + ); + test_same("function wrapper(arg0, arg1) { let x = arg0.foo; new x()}"); + test_same("function wrapper(arg0, arg1) { let x = arg0[foo]; new x()}"); + test_same("function wrapper(arg0, arg1) { let x = arg0?.foo; new x()}"); + test_same("function wrapper(arg0, arg1) { let x = arg0?.[foo]; new x()}"); + test( + "function wrapper(arg0, arg1) { let x = arg0.foo; new (0, x)()}", + "function wrapper(arg0, arg1) { let x = arg0.foo; new x();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0[foo]; new (0, x)()}", + "function wrapper(arg0, arg1) { let x = arg0[foo]; new x();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0?.foo; new (0, x)()}", + "function wrapper(arg0, arg1) { let x = arg0?.foo; new x();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0?.[foo]; new (0, x)()}", + "function wrapper(arg0, arg1) { let x = arg0?.[foo]; new x();}", + ); + test( + "function wrapper(arg0, arg1) { let x = arg0; return (x(), 1);}", + "function wrapper(arg0, arg1) { return (arg0(), 1);}", + ); + test_same("function wrapper(arg0, arg1) { let x = arg0; return (foo(), x(), 1);}"); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 3a62a846b922a..4c768e28d6154 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,7 +1,7 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Iterations | File ------------------------------------------------------------------------------------- -72.14 kB | 23.22 kB | 23.70 kB | 8.39 kB | 8.54 kB | 2 | react.development.js +72.14 kB | 23.22 kB | 23.70 kB | 8.38 kB | 8.54 kB | 2 | react.development.js 173.90 kB | 59.47 kB | 59.82 kB | 19.18 kB | 19.33 kB | 2 | moment.js @@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Iterations | Fi 544.10 kB | 71.18 kB | 72.48 kB | 25.85 kB | 26.20 kB | 2 | lodash.js -555.77 kB | 270.81 kB | 270.13 kB | 88.21 kB | 90.80 kB | 2 | d3.js +555.77 kB | 270.79 kB | 270.13 kB | 88.19 kB | 90.80 kB | 2 | d3.js -1.01 MB | 439.64 kB | 458.89 kB | 122.16 kB | 126.71 kB | 2 | bundle.min.js +1.01 MB | 439.61 kB | 458.89 kB | 122.16 kB | 126.71 kB | 2 | bundle.min.js -1.25 MB | 645.80 kB | 646.76 kB | 159.59 kB | 163.73 kB | 2 | three.js +1.25 MB | 645.76 kB | 646.76 kB | 159.56 kB | 163.73 kB | 2 | three.js -2.14 MB | 714.13 kB | 724.14 kB | 161.16 kB | 181.07 kB | 2 | victory.js +2.14 MB | 713.75 kB | 724.14 kB | 161.08 kB | 181.07 kB | 2 | victory.js -3.20 MB | 1.00 MB | 1.01 MB | 323.35 kB | 331.56 kB | 3 | echarts.js +3.20 MB | 1.00 MB | 1.01 MB | 323.20 kB | 331.56 kB | 3 | echarts.js -6.69 MB | 2.23 MB | 2.31 MB | 460.30 kB | 488.28 kB | 4 | antd.js +6.69 MB | 2.22 MB | 2.31 MB | 459.78 kB | 488.28 kB | 4 | antd.js -10.95 MB | 3.34 MB | 3.49 MB | 855.59 kB | 915.50 kB | 4 | typescript.js +10.95 MB | 3.34 MB | 3.49 MB | 855.49 kB | 915.50 kB | 4 | typescript.js