mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): minimize all variants of typeof x == 'undefined' (#8227)
This commit is contained in:
parent
2041477f51
commit
574a2428fd
2 changed files with 51 additions and 48 deletions
|
|
@ -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,33 +244,26 @@ 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,
|
||||||
|
new_eq_op,
|
||||||
|
right,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
let left = ctx.ast.move_expression(&mut expr.left);
|
||||||
None
|
let right = ctx.ast.expression_string_literal(expr.right.span(), "u", None);
|
||||||
},
|
|
||||||
);
|
|
||||||
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 = Expression::Identifier(ctx.alloc(id_ref));
|
|
||||||
let right = ctx.ast.void_0(SPAN);
|
|
||||||
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))
|
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'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue