feat(minifier): minimize all variants of typeof x == 'undefined' (#8227)

This commit is contained in:
Boshen 2025-01-03 07:05:55 +00:00
parent 2041477f51
commit 574a2428fd
2 changed files with 51 additions and 48 deletions

View file

@ -1,6 +1,6 @@
use oxc_allocator::Vec; use oxc_allocator::Vec;
use oxc_ast::{ast::*, NONE}; use oxc_ast::{ast::*, NONE};
use oxc_ecmascript::{ToInt32, ToJsString}; use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, ToInt32, ToJsString};
use oxc_semantic::IsGlobalReference; use oxc_semantic::IsGlobalReference;
use oxc_span::{GetSpan, SPAN}; use oxc_span::{GetSpan, SPAN};
use oxc_syntax::{ use oxc_syntax::{
@ -84,7 +84,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
match expr { match expr {
Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx), Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx),
Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx),
Expression::BinaryExpression(e) => self.try_compress_type_of_equal_string(e, ctx), Expression::BinaryExpression(e) => self.try_compress_type_of_equal_string(e),
_ => {} _ => {}
} }
@ -228,6 +228,13 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
expr: &mut BinaryExpression<'a>, expr: &mut BinaryExpression<'a>,
ctx: Ctx<'a, 'b>, ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
let Expression::UnaryExpression(unary_expr) = &expr.left else { return None };
if !unary_expr.operator.is_typeof() {
return None;
}
if !expr.right.is_specific_string_literal("undefined") {
return None;
}
let (new_eq_op, new_comp_op) = match expr.operator { let (new_eq_op, new_comp_op) = match expr.operator {
BinaryOperator::Equality | BinaryOperator::StrictEquality => { BinaryOperator::Equality | BinaryOperator::StrictEquality => {
(BinaryOperator::StrictEquality, BinaryOperator::GreaterThan) (BinaryOperator::StrictEquality, BinaryOperator::GreaterThan)
@ -237,32 +244,25 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
} }
_ => return None, _ => return None,
}; };
let pair = Self::commutative_pair( if let Expression::Identifier(ident) = &unary_expr.argument {
(&expr.left, &expr.right), if !ctx.is_global_reference(ident) {
|a| a.is_specific_string_literal("undefined").then_some(()), let Expression::UnaryExpression(unary_expr) =
|b| { ctx.ast.move_expression(&mut expr.left)
if let Expression::UnaryExpression(op) = b { else {
if op.operator == UnaryOperator::Typeof { unreachable!()
if let Expression::Identifier(id) = &op.argument { };
return Some((*id).clone()); let right = ctx.ast.void_0(expr.right.span());
} return Some(ctx.ast.expression_binary(
} expr.span,
} unary_expr.unbox().argument,
None new_eq_op,
}, right,
); ));
let (_void_exp, id_ref) = pair?; }
let is_resolved = ctx.scopes().find_binding(ctx.current_scope_id(), &id_ref.name).is_some(); };
if is_resolved { let left = ctx.ast.move_expression(&mut expr.left);
let left = Expression::Identifier(ctx.alloc(id_ref)); let right = ctx.ast.expression_string_literal(expr.right.span(), "u", None);
let right = ctx.ast.void_0(SPAN); Some(ctx.ast.expression_binary(expr.span, left, new_comp_op, right))
Some(ctx.ast.expression_binary(expr.span, left, new_eq_op, right))
} else {
let argument = Expression::Identifier(ctx.alloc(id_ref));
let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument);
let right = ctx.ast.expression_string_literal(SPAN, "u", None);
Some(ctx.ast.expression_binary(expr.span, left, new_comp_op, right))
}
} }
/// Compress `foo === null || foo === undefined` into `foo == null`. /// Compress `foo === null || foo === undefined` into `foo == null`.
@ -622,25 +622,25 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
} }
/// `typeof foo === 'number'` -> `typeof foo == 'number'` /// `typeof foo === 'number'` -> `typeof foo == 'number'`
fn try_compress_type_of_equal_string( fn try_compress_type_of_equal_string(&mut self, e: &mut BinaryExpression<'a>) {
&mut self, // Change `'undefined' == typeof _'` -> `typeof _ == 'undefined'`
e: &mut BinaryExpression<'a>, if matches!(&e.right, Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_typeof())
_ctx: Ctx<'a, 'b>, && e.left.is_string_literal()
) { {
std::mem::swap(&mut e.left, &mut e.right);
}
let op = match e.operator { let op = match e.operator {
BinaryOperator::StrictEquality => BinaryOperator::Equality, BinaryOperator::StrictEquality => BinaryOperator::Equality,
BinaryOperator::StrictInequality => BinaryOperator::Inequality, BinaryOperator::StrictInequality => BinaryOperator::Inequality,
_ => return, _ => return,
}; };
if Self::commutative_pair( if !matches!(&e.left, Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_typeof())
(&e.left, &e.right),
|a| a.is_string_literal().then_some(()),
|b| matches!(b, Expression::UnaryExpression(e) if e.operator.is_typeof()).then_some(()),
)
.is_none()
{ {
return; return;
} }
if !e.right.is_string_literal() {
return;
}
e.operator = op; e.operator = op;
self.changed = true; self.changed = true;
} }
@ -1143,6 +1143,9 @@ mod test {
test("typeof x == 'undefined'", "typeof x > 'u'"); test("typeof x == 'undefined'", "typeof x > 'u'");
test("'undefined' === typeof x", "typeof x > 'u'"); test("'undefined' === typeof x", "typeof x > 'u'");
test("'undefined' == typeof x", "typeof x > 'u'"); test("'undefined' == typeof x", "typeof x > 'u'");
test("typeof x.y === 'undefined'", "typeof x.y > 'u'");
test("typeof x.y !== 'undefined'", "typeof x.y < 'u'");
} }
#[test] #[test]
@ -1168,12 +1171,12 @@ mod test {
#[test] #[test]
fn test_try_compress_type_of_equal_string() { fn test_try_compress_type_of_equal_string() {
test("typeof foo === 'number'", "typeof foo == 'number'"); test("typeof foo === 'number'", "typeof foo == 'number'");
test("'number' === typeof foo", "'number' == typeof foo"); test("'number' === typeof foo", "typeof foo == 'number'");
test("typeof foo === `number`", "typeof foo == 'number'"); test("typeof foo === `number`", "typeof foo == 'number'");
test("`number` === typeof foo", "'number' == typeof foo"); test("`number` === typeof foo", "typeof foo == 'number'");
test("typeof foo !== 'number'", "typeof foo != 'number'"); test("typeof foo !== 'number'", "typeof foo != 'number'");
test("'number' !== typeof foo", "'number' != typeof foo"); test("'number' !== typeof foo", "typeof foo != 'number'");
test("typeof foo !== `number`", "typeof foo != 'number'"); test("typeof foo !== `number`", "typeof foo != 'number'");
test("`number` !== typeof foo", "'number' != typeof foo"); test("`number` !== typeof foo", "typeof foo != 'number'");
} }
} }

View file

@ -3,9 +3,9 @@ Original | minified | minified | gzip | gzip | Fixture
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
72.14 kB | 23.69 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js 72.14 kB | 23.69 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js
173.90 kB | 59.87 kB | 59.82 kB | 19.43 kB | 19.33 kB | moment.js 173.90 kB | 59.86 kB | 59.82 kB | 19.43 kB | 19.33 kB | moment.js
287.63 kB | 90.27 kB | 90.07 kB | 32.09 kB | 31.95 kB | jquery.js 287.63 kB | 90.17 kB | 90.07 kB | 32.08 kB | 31.95 kB | jquery.js
342.15 kB | 118.25 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js 342.15 kB | 118.25 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js
@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Fixture
1.25 MB | 652.71 kB | 646.76 kB | 163.55 kB | 163.73 kB | three.js 1.25 MB | 652.71 kB | 646.76 kB | 163.55 kB | 163.73 kB | three.js
2.14 MB | 726.39 kB | 724.14 kB | 180.23 kB | 181.07 kB | victory.js 2.14 MB | 726.38 kB | 724.14 kB | 180.22 kB | 181.07 kB | victory.js
3.20 MB | 1.01 MB | 1.01 MB | 332.13 kB | 331.56 kB | echarts.js 3.20 MB | 1.01 MB | 1.01 MB | 332.14 kB | 331.56 kB | echarts.js
6.69 MB | 2.32 MB | 2.31 MB | 492.88 kB | 488.28 kB | antd.js 6.69 MB | 2.32 MB | 2.31 MB | 492.86 kB | 488.28 kB | antd.js
10.95 MB | 3.50 MB | 3.49 MB | 910.01 kB | 915.50 kB | typescript.js 10.95 MB | 3.50 MB | 3.49 MB | 910.01 kB | 915.50 kB | typescript.js