mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): a != null ? a : b -> a ?? b (#8352)
This commit is contained in:
parent
1c4658d617
commit
793cb43138
4 changed files with 60 additions and 20 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
use oxc_allocator::Vec;
|
use oxc_allocator::Vec;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
|
use oxc_syntax::es_target::ESTarget;
|
||||||
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||||
|
|
||||||
mod collapse_variable_declarations;
|
mod collapse_variable_declarations;
|
||||||
|
|
@ -53,7 +54,7 @@ pub struct PeepholeOptimizations {
|
||||||
impl PeepholeOptimizations {
|
impl PeepholeOptimizations {
|
||||||
/// `in_fixed_loop`: Do not compress syntaxes that are hard to analyze inside the fixed loop.
|
/// `in_fixed_loop`: Do not compress syntaxes that are hard to analyze inside the fixed loop.
|
||||||
/// Opposite of `late` in Closure Compiler.
|
/// Opposite of `late` in Closure Compiler.
|
||||||
pub fn new(in_fixed_loop: bool, options: CompressOptions) -> Self {
|
pub fn new(target: ESTarget, in_fixed_loop: bool, options: CompressOptions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x0_statement_fusion: StatementFusion::new(),
|
x0_statement_fusion: StatementFusion::new(),
|
||||||
x1_minimize_exit_points: MinimizeExitPoints::new(),
|
x1_minimize_exit_points: MinimizeExitPoints::new(),
|
||||||
|
|
@ -61,7 +62,7 @@ impl PeepholeOptimizations {
|
||||||
x3_collapse_variable_declarations: CollapseVariableDeclarations::new(),
|
x3_collapse_variable_declarations: CollapseVariableDeclarations::new(),
|
||||||
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
|
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
|
||||||
x5_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
|
x5_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(),
|
||||||
x6_peephole_minimize_conditions: PeepholeMinimizeConditions::new(),
|
x6_peephole_minimize_conditions: PeepholeMinimizeConditions::new(target),
|
||||||
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
|
x7_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
|
||||||
x8_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
|
x8_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
|
||||||
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
|
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use oxc_allocator::Vec;
|
||||||
use oxc_ast::{ast::*, NONE};
|
use oxc_ast::{ast::*, NONE};
|
||||||
use oxc_ecmascript::constant_evaluation::ValueType;
|
use oxc_ecmascript::constant_evaluation::ValueType;
|
||||||
use oxc_span::{cmp::ContentEq, GetSpan, SPAN};
|
use oxc_span::{cmp::ContentEq, GetSpan, SPAN};
|
||||||
|
use oxc_syntax::es_target::ESTarget;
|
||||||
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
|
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::CompressorPass;
|
use crate::CompressorPass;
|
||||||
|
|
@ -14,6 +15,7 @@ use crate::CompressorPass;
|
||||||
///
|
///
|
||||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java>
|
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java>
|
||||||
pub struct PeepholeMinimizeConditions {
|
pub struct PeepholeMinimizeConditions {
|
||||||
|
target: ESTarget,
|
||||||
pub(crate) changed: bool,
|
pub(crate) changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +58,7 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
||||||
Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx),
|
Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx),
|
||||||
Expression::LogicalExpression(e) => Self::try_minimize_logical(e, ctx),
|
Expression::LogicalExpression(e) => Self::try_minimize_logical(e, ctx),
|
||||||
Expression::BinaryExpression(e) => Self::try_minimize_binary(e, ctx),
|
Expression::BinaryExpression(e) => Self::try_minimize_binary(e, ctx),
|
||||||
Expression::ConditionalExpression(e) => Self::try_minimize_conditional(e, ctx),
|
Expression::ConditionalExpression(e) => self.try_minimize_conditional(e, ctx),
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
} {
|
||||||
*expr = folded_expr;
|
*expr = folded_expr;
|
||||||
|
|
@ -66,8 +68,8 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PeepholeMinimizeConditions {
|
impl<'a> PeepholeMinimizeConditions {
|
||||||
pub fn new() -> Self {
|
pub fn new(target: ESTarget) -> Self {
|
||||||
Self { changed: false }
|
Self { target, changed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to minimize NOT nodes such as `!(x==y)`.
|
/// Try to minimize NOT nodes such as `!(x==y)`.
|
||||||
|
|
@ -344,6 +346,7 @@ impl<'a> PeepholeMinimizeConditions {
|
||||||
|
|
||||||
// based on https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/internal/js_ast/js_ast_helpers.go#L2745
|
// based on https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/internal/js_ast/js_ast_helpers.go#L2745
|
||||||
fn try_minimize_conditional(
|
fn try_minimize_conditional(
|
||||||
|
&self,
|
||||||
expr: &mut ConditionalExpression<'a>,
|
expr: &mut ConditionalExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
|
|
@ -633,7 +636,39 @@ impl<'a> PeepholeMinimizeConditions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Try using the "??" or "?." operators
|
// Try using the "??" or "?." operators
|
||||||
|
if let Expression::BinaryExpression(bin_expr) = &mut expr.test {
|
||||||
|
if bin_expr.operator == BinaryOperator::Equality
|
||||||
|
|| bin_expr.operator == BinaryOperator::Inequality
|
||||||
|
{
|
||||||
|
if let Some(check) = {
|
||||||
|
if bin_expr.left.is_null() {
|
||||||
|
Some(&bin_expr.right)
|
||||||
|
} else {
|
||||||
|
Some(&bin_expr.left)
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
// `a != null ? a : b` -> `a ?? b``
|
||||||
|
if check.content_eq(if bin_expr.operator == BinaryOperator::Equality {
|
||||||
|
&expr.alternate
|
||||||
|
} else {
|
||||||
|
&expr.consequent
|
||||||
|
}) && self.target >= ESTarget::ES2020
|
||||||
|
// TODO: this is probably a but too aggressive
|
||||||
|
&& matches!(check, Expression::Identifier(_))
|
||||||
|
{
|
||||||
|
return Some(ctx.ast.expression_logical(
|
||||||
|
SPAN,
|
||||||
|
ctx.ast.move_expression(&mut expr.consequent),
|
||||||
|
LogicalOperator::Coalesce,
|
||||||
|
ctx.ast.move_expression(&mut expr.alternate),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: `a != null ? a.b.c[d](e) : undefined` -> `a?.b.c[d](e)``
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Non esbuild optimizations
|
// Non esbuild optimizations
|
||||||
|
|
||||||
|
|
@ -812,12 +847,13 @@ impl<'a> PeepholeMinimizeConditions {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
|
use oxc_syntax::es_target::ESTarget;
|
||||||
|
|
||||||
use crate::tester;
|
use crate::tester;
|
||||||
|
|
||||||
fn test(source_text: &str, positive: &str) {
|
fn test(source_text: &str, positive: &str) {
|
||||||
let allocator = Allocator::default();
|
let allocator = Allocator::default();
|
||||||
let mut pass = super::PeepholeMinimizeConditions::new();
|
let mut pass = super::PeepholeMinimizeConditions::new(ESTarget::ES2025);
|
||||||
tester::test(&allocator, source_text, positive, &mut pass);
|
tester::test(&allocator, source_text, positive, &mut pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2003,7 +2039,8 @@ mod test {
|
||||||
test("var a; a ? b(c, d) : b(e, d)", "var a; b(a ? c : e, d)");
|
test("var a; a ? b(c, d) : b(e, d)", "var a; b(a ? c : e, d)");
|
||||||
test("var a; a ? b(...c) : b(...e)", "var a; b(...a ? c : e)");
|
test("var a; a ? b(...c) : b(...e)", "var a; b(...a ? c : e)");
|
||||||
test("var a; a ? b(c) : b(e)", "var a; b(a ? c : e)");
|
test("var a; a ? b(c) : b(e)", "var a; b(a ? c : e)");
|
||||||
// test("a != null ? a : b", "a ?? b");
|
test("a != null ? a : b", "a ?? b");
|
||||||
|
test_same("a() != null ? a() : b");
|
||||||
// test("a != null ? a.b.c[d](e) : undefined", "a?.b.c[d](e)");
|
// test("a != null ? a.b.c[d](e) : undefined", "a?.b.c[d](e)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,10 @@ impl<'a> Compressor<'a> {
|
||||||
RemoveSyntax::new(self.options).build(program, &mut ctx);
|
RemoveSyntax::new(self.options).build(program, &mut ctx);
|
||||||
// RemoveUnusedCode::new(self.options).build(program, &mut ctx);
|
// RemoveUnusedCode::new(self.options).build(program, &mut ctx);
|
||||||
Normalize::new().build(program, &mut ctx);
|
Normalize::new().build(program, &mut ctx);
|
||||||
PeepholeOptimizations::new(true, self.options).run_in_loop(program, &mut ctx);
|
PeepholeOptimizations::new(self.options.target, true, self.options)
|
||||||
PeepholeOptimizations::new(false, self.options).build(program, &mut ctx);
|
.run_in_loop(program, &mut ctx);
|
||||||
|
PeepholeOptimizations::new(self.options.target, false, self.options)
|
||||||
|
.build(program, &mut ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dead_code_elimination(self, program: &mut Program<'a>) {
|
pub fn dead_code_elimination(self, program: &mut Program<'a>) {
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture
|
||||||
-------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------
|
||||||
72.14 kB | 23.70 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js
|
72.14 kB | 23.70 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js
|
||||||
|
|
||||||
173.90 kB | 59.79 kB | 59.82 kB | 19.40 kB | 19.33 kB | moment.js
|
173.90 kB | 59.77 kB | 59.82 kB | 19.39 kB | 19.33 kB | moment.js
|
||||||
|
|
||||||
287.63 kB | 90.08 kB | 90.07 kB | 32.02 kB | 31.95 kB | jquery.js
|
287.63 kB | 90.02 kB | 90.07 kB | 32.01 kB | 31.95 kB | jquery.js
|
||||||
|
|
||||||
342.15 kB | 118.09 kB | 118.14 kB | 44.43 kB | 44.37 kB | vue.js
|
342.15 kB | 118.08 kB | 118.14 kB | 44.43 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.72 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js
|
||||||
|
|
||||||
555.77 kB | 273.19 kB | 270.13 kB | 90.91 kB | 90.80 kB | d3.js
|
555.77 kB | 272.89 kB | 270.13 kB | 90.85 kB | 90.80 kB | d3.js
|
||||||
|
|
||||||
1.01 MB | 460.17 kB | 458.89 kB | 126.75 kB | 126.71 kB | bundle.min.js
|
1.01 MB | 460.13 kB | 458.89 kB | 126.74 kB | 126.71 kB | bundle.min.js
|
||||||
|
|
||||||
1.25 MB | 652.81 kB | 646.76 kB | 163.50 kB | 163.73 kB | three.js
|
1.25 MB | 652.81 kB | 646.76 kB | 163.50 kB | 163.73 kB | three.js
|
||||||
|
|
||||||
2.14 MB | 726.23 kB | 724.14 kB | 180.12 kB | 181.07 kB | victory.js
|
2.14 MB | 725.99 kB | 724.14 kB | 180.05 kB | 181.07 kB | victory.js
|
||||||
|
|
||||||
3.20 MB | 1.01 MB | 1.01 MB | 331.78 kB | 331.56 kB | echarts.js
|
3.20 MB | 1.01 MB | 1.01 MB | 331.68 kB | 331.56 kB | echarts.js
|
||||||
|
|
||||||
6.69 MB | 2.32 MB | 2.31 MB | 492.64 kB | 488.28 kB | antd.js
|
6.69 MB | 2.32 MB | 2.31 MB | 492.60 kB | 488.28 kB | antd.js
|
||||||
|
|
||||||
10.95 MB | 3.49 MB | 3.49 MB | 907.46 kB | 915.50 kB | typescript.js
|
10.95 MB | 3.49 MB | 3.49 MB | 907.25 kB | 915.50 kB | typescript.js
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue