diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index b456e68086012..4a5e199f74902 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -353,130 +353,108 @@ impl<'a> PeepholeOptimizations { } } - // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2745 + // fn try_minimize_conditional( expr: &mut ConditionalExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { - // `(a, b) ? c : d` -> `a, b ? c : d` - if let Expression::SequenceExpression(sequence_expr) = &mut expr.test { - if sequence_expr.expressions.len() > 1 { - let span = expr.span(); - let mut sequence = ctx.ast.move_expression(&mut expr.test); - let Expression::SequenceExpression(sequence_expr) = &mut sequence else { - unreachable!() - }; - let test = sequence_expr.expressions.pop().unwrap(); - sequence_expr.expressions.push(ctx.ast.expression_conditional( - span, - test, - ctx.ast.move_expression(&mut expr.consequent), - ctx.ast.move_expression(&mut expr.alternate), - )); - return Some(sequence); + match &mut expr.test { + // `x != y ? b : c` -> `x == y ? c : b` + Expression::BinaryExpression(test_expr) => { + if matches!( + test_expr.operator, + BinaryOperator::Inequality | BinaryOperator::StrictInequality + ) { + test_expr.operator = test_expr.operator.equality_inverse_operator().unwrap(); + let test = ctx.ast.move_expression(&mut expr.test); + let consequent = ctx.ast.move_expression(&mut expr.consequent); + let alternate = ctx.ast.move_expression(&mut expr.alternate); + return Some( + ctx.ast.expression_conditional(expr.span, test, alternate, consequent), + ); + } } - } - - // `!a ? b : c` -> `a ? c : b` - if let Expression::UnaryExpression(test_expr) = &mut expr.test { - if test_expr.operator.is_not() - // Skip `!!!a` - && !matches!(test_expr.argument, Expression::UnaryExpression(_)) - { - let test = ctx.ast.move_expression(&mut test_expr.argument); - let consequent = ctx.ast.move_expression(&mut expr.consequent); - let alternate = ctx.ast.move_expression(&mut expr.alternate); - return Some( - ctx.ast.expression_conditional(expr.span, test, alternate, consequent), - ); + // "(a, b) ? c : d" => "a, b ? c : d" + Expression::SequenceExpression(sequence_expr) => { + if sequence_expr.expressions.len() > 1 { + let span = expr.span(); + let mut sequence = ctx.ast.move_expression(&mut expr.test); + let Expression::SequenceExpression(sequence_expr) = &mut sequence else { + unreachable!() + }; + let mut cond_expr = ctx.ast.conditional_expression( + span, + sequence_expr.expressions.pop().unwrap(), + ctx.ast.move_expression(&mut expr.consequent), + ctx.ast.move_expression(&mut expr.alternate), + ); + let expr = + Self::try_minimize_conditional(&mut cond_expr, ctx).unwrap_or_else(|| { + Expression::ConditionalExpression(ctx.ast.alloc(cond_expr)) + }); + sequence_expr.expressions.push(expr); + return Some(sequence); + } } - } - - // `x != y ? b : c` -> `x == y ? c : b` - if let Expression::BinaryExpression(test_expr) = &mut expr.test { - if matches!( - test_expr.operator, - BinaryOperator::Inequality | BinaryOperator::StrictInequality - ) { - test_expr.operator = test_expr.operator.equality_inverse_operator().unwrap(); - let test = ctx.ast.move_expression(&mut expr.test); - let consequent = ctx.ast.move_expression(&mut expr.consequent); - let alternate = ctx.ast.move_expression(&mut expr.alternate); - return Some( - ctx.ast.expression_conditional(expr.span, test, alternate, consequent), - ); + // "!a ? b : c" => "a ? c : b" + Expression::UnaryExpression(test_expr) => { + if test_expr.operator.is_not() { + let test = ctx.ast.move_expression(&mut test_expr.argument); + let consequent = ctx.ast.move_expression(&mut expr.alternate); + let alternate = ctx.ast.move_expression(&mut expr.consequent); + return Some( + ctx.ast.expression_conditional(expr.span, test, consequent, alternate), + ); + } } + // "a ? a : b" => "a || b" + Expression::Identifier(id) => { + if let Expression::Identifier(id2) = &expr.consequent { + if id.name == id2.name { + return Some(ctx.ast.expression_logical( + expr.span, + ctx.ast.move_expression(&mut expr.test), + LogicalOperator::Or, + ctx.ast.move_expression(&mut expr.alternate), + )); + } + } + // "a ? b : a" => "a && b" + if let Expression::Identifier(id2) = &expr.alternate { + if id.name == id2.name { + return Some(ctx.ast.expression_logical( + expr.span, + ctx.ast.move_expression(&mut expr.test), + LogicalOperator::And, + ctx.ast.move_expression(&mut expr.consequent), + )); + } + } + } + _ => {} } - // TODO: `/* @__PURE__ */ a() ? b : b` -> `b` - - // `a ? b : b` -> `a, b` - if expr.alternate.content_eq(&expr.consequent) { - let expressions = ctx.ast.vec_from_array([ - ctx.ast.move_expression(&mut expr.test), - ctx.ast.move_expression(&mut expr.consequent), - ]); - return Some(ctx.ast.expression_sequence(expr.span, expressions)); - } - - // `a ? true : false` -> `!!a` - // `a ? false : true` -> `!a` - if let ( - Expression::Identifier(_), - Expression::BooleanLiteral(consequent_lit), - Expression::BooleanLiteral(alternate_lit), - ) = (&expr.test, &expr.consequent, &expr.alternate) + // "a ? true : false" => "!!a" + // "a ? false : true" => "!a" + if let (Expression::BooleanLiteral(left), Expression::BooleanLiteral(right)) = + (&expr.consequent, &expr.alternate) { - match (consequent_lit.value, alternate_lit.value) { + let op = UnaryOperator::LogicalNot; + match (left.value, right.value) { (false, true) => { let ident = ctx.ast.move_expression(&mut expr.test); - return Some(ctx.ast.expression_unary( - expr.span, - UnaryOperator::LogicalNot, - ident, - )); + return Some(ctx.ast.expression_unary(expr.span, op, ident)); } (true, false) => { let ident = ctx.ast.move_expression(&mut expr.test); - return Some(ctx.ast.expression_unary( - expr.span, - UnaryOperator::LogicalNot, - ctx.ast.expression_unary(expr.span, UnaryOperator::LogicalNot, ident), - )); + let unary = ctx.ast.expression_unary(expr.span, op, ident); + return Some(ctx.ast.expression_unary(expr.span, op, unary)); } _ => {} } } - // `a ? a : b` -> `a || b` - if let (Expression::Identifier(test_ident), Expression::Identifier(consequent_ident)) = - (&expr.test, &expr.consequent) - { - if test_ident.name == consequent_ident.name { - return Some(ctx.ast.expression_logical( - expr.span, - ctx.ast.move_expression(&mut expr.test), - LogicalOperator::Or, - ctx.ast.move_expression(&mut expr.alternate), - )); - } - } - - // `a ? b : a` -> `a && b` - if let (Expression::Identifier(test_ident), Expression::Identifier(alternate_ident)) = - (&expr.test, &expr.alternate) - { - if test_ident.name == alternate_ident.name { - return Some(ctx.ast.expression_logical( - expr.span, - ctx.ast.move_expression(&mut expr.test), - LogicalOperator::And, - ctx.ast.move_expression(&mut expr.consequent), - )); - } - } - - // `a ? b ? c : d : d` -> `a && b ? c : d` + // "a ? b ? c : d : d" => "a && b ? c : d" if let Expression::ConditionalExpression(consequent) = &mut expr.consequent { if consequent.alternate.content_eq(&expr.alternate) { return Some(ctx.ast.expression_conditional( @@ -493,7 +471,7 @@ impl<'a> PeepholeOptimizations { } } - // `a ? b : c ? b : d` -> `a || c ? b : d` + // "a ? b : c ? b : d" => "a || c ? b : d" if let Expression::ConditionalExpression(alternate) = &mut expr.alternate { if alternate.consequent.content_eq(&expr.consequent) { return Some(ctx.ast.expression_conditional( @@ -510,7 +488,7 @@ impl<'a> PeepholeOptimizations { } } - // `a ? c : (b, c)` -> `(a || b), c` + // "a ? c : (b, c)" => "(a || b), c" if let Expression::SequenceExpression(alternate) = &mut expr.alternate { if alternate.expressions.len() == 2 && alternate.expressions[1].content_eq(&expr.consequent) @@ -530,7 +508,7 @@ impl<'a> PeepholeOptimizations { } } - // `a ? (b, c) : c` -> `(a && b), c` + // "a ? (b, c) : c" => "(a && b), c" if let Expression::SequenceExpression(consequent) = &mut expr.consequent { if consequent.expressions.len() == 2 && consequent.expressions[1].content_eq(&expr.alternate) @@ -550,7 +528,7 @@ impl<'a> PeepholeOptimizations { } } - // `a ? b || c : c` => "(a && b) || c" + // "a ? b || c : c" => "(a && b) || c" if let Expression::LogicalExpression(logical_expr) = &mut expr.consequent { if logical_expr.operator == LogicalOperator::Or && logical_expr.right.content_eq(&expr.alternate) @@ -569,7 +547,7 @@ impl<'a> PeepholeOptimizations { } } - // `a ? c : b && c` -> `(a || b) && c` + // "a ? c : b && c" => "(a || b) && c" if let Expression::LogicalExpression(logical_expr) = &mut expr.alternate { if logical_expr.operator == LogicalOperator::And && logical_expr.right.content_eq(&expr.consequent) @@ -715,6 +693,21 @@ impl<'a> PeepholeOptimizations { )); } + if expr.alternate.content_eq(&expr.consequent) { + // TODO: + // "/* @__PURE__ */ a() ? b : b" => "b" + // if ctx.ExprCanBeRemovedIfUnused(test) { + // return yes + // } + + // "a ? b : b" => "a, b" + let expressions = ctx.ast.vec_from_array([ + ctx.ast.move_expression(&mut expr.test), + ctx.ast.move_expression(&mut expr.consequent), + ]); + return Some(ctx.ast.expression_sequence(expr.span, expressions)); + } + None } diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index 95afd49c5e602..686bdd16e6f57 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -333,16 +333,16 @@ impl<'a> PeepholeOptimizations { /// `[].concat(a).concat(b)` -> `[].concat(a, b)` /// `"".concat(a).concat(b)` -> `"".concat(a, b)` fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if matches!(ctx.parent(), Ancestor::StaticMemberExpressionObject(_)) { - return; - } - let original_span = if let Expression::CallExpression(root_call_expr) = node { root_call_expr.span } else { return; }; + if matches!(ctx.parent(), Ancestor::StaticMemberExpressionObject(_)) { + return; + } + let mut current_node: &mut Expression = node; let mut collected_arguments = ctx.ast.vec(); let new_root_callee: &mut Expression<'a>; diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index d4a288450060d..6c5b9ebbc3ff6 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -9,19 +9,19 @@ Original | minified | minified | gzip | gzip | Fixture 342.15 kB | 118.19 kB | 118.14 kB | 44.45 kB | 44.37 kB | vue.js -544.10 kB | 71.74 kB | 72.48 kB | 26.14 kB | 26.20 kB | lodash.js +544.10 kB | 71.73 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js 555.77 kB | 272.89 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js -1.01 MB | 460.18 kB | 458.89 kB | 126.78 kB | 126.71 kB | bundle.min.js +1.01 MB | 460.17 kB | 458.89 kB | 126.78 kB | 126.71 kB | bundle.min.js -1.25 MB | 652.90 kB | 646.76 kB | 163.54 kB | 163.73 kB | three.js +1.25 MB | 652.85 kB | 646.76 kB | 163.53 kB | 163.73 kB | three.js -2.14 MB | 724.01 kB | 724.14 kB | 179.93 kB | 181.07 kB | victory.js +2.14 MB | 724.00 kB | 724.14 kB | 179.93 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.00 kB | 331.56 kB | echarts.js -6.69 MB | 2.31 MB | 2.31 MB | 491.97 kB | 488.28 kB | antd.js +6.69 MB | 2.31 MB | 2.31 MB | 491.95 kB | 488.28 kB | antd.js -10.95 MB | 3.48 MB | 3.49 MB | 905.34 kB | 915.50 kB | typescript.js +10.95 MB | 3.48 MB | 3.49 MB | 905.35 kB | 915.50 kB | typescript.js