From ad1fe928b2066ed3618c3043f8fc970c17446fef Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sat, 28 Dec 2024 18:34:39 +0900 Subject: [PATCH] feat(minifier): fold `typeof foo == undefined` into `foo == undefined` when possible --- .../peephole_substitute_alternate_syntax.rs | 46 +++++++++++++++---- tasks/minsize/minsize.snap | 8 ++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index f28c082a823b11..b798e560f34dba 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -239,8 +239,10 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } - /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` + /// Compress `typeof foo == "undefined"` /// + /// - `typeof foo == "undefined"` (if foo is resolved) -> `foo === undefined` + /// - `typeof foo != "undefined"` (if foo is resolved) -> `foo !== undefined` /// - `typeof foo == "undefined"` -> `typeof foo > "u"` /// - `typeof foo != "undefined"` -> `typeof foo < "u"` /// @@ -249,12 +251,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>, ) -> Option> { - let new_op = match expr.operator { + let (new_eq_op, new_comp_op) = match expr.operator { BinaryOperator::Equality | BinaryOperator::StrictEquality => { - BinaryOperator::GreaterThan + (BinaryOperator::StrictEquality, BinaryOperator::GreaterThan) } BinaryOperator::Inequality | BinaryOperator::StrictInequality => { - BinaryOperator::LessThan + (BinaryOperator::StrictInequality, BinaryOperator::LessThan) } _ => return None, }; @@ -273,10 +275,17 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { }, ); let (_void_exp, id_ref) = pair?; - 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_op, right)) + 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)) + } } /// Compress `foo === null || foo === undefined` into `foo == null`. @@ -1153,6 +1162,27 @@ mod test { test_same("const foo = () => { foo; return 'baz' }"); } + #[test] + fn test_fold_is_typeof_equals_undefined_resolved() { + test("var x; typeof x !== 'undefined'", "var x; x !== undefined"); + test("var x; typeof x != 'undefined'", "var x; x !== undefined"); + test("var x; 'undefined' !== typeof x", "var x; x !== undefined"); + test("var x; 'undefined' != typeof x", "var x; x !== undefined"); + + test("var x; typeof x === 'undefined'", "var x; x === undefined"); + test("var x; typeof x == 'undefined'", "var x; x === undefined"); + test("var x; 'undefined' === typeof x", "var x; x === undefined"); + test("var x; 'undefined' == typeof x", "var x; x === undefined"); + + test("var x; function foo() { typeof x !== 'undefined' }", "var x; function foo() { x !== undefined }"); + test("typeof x !== 'undefined'; function foo() { var x }", "typeof x < 'u'; function foo() { var x }"); + test("typeof x !== 'undefined'; { var x }", "x !== undefined; { var x }"); + test("typeof x !== 'undefined'; { let x }", "typeof x < 'u'; { let x }"); + test("typeof x !== 'undefined'; var x", "x !== undefined; var x"); + // input and output both errors with same TDZ error + test("typeof x !== 'undefined'; let x", "x !== undefined; let x"); + } + /// Port from #[test] fn test_fold_is_typeof_equals_undefined() { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 8b34fd75ae1fb6..f453e189930348 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,7 +5,7 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 60.16 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.59 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js +287.63 kB | 90.59 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js 342.15 kB | 118.61 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 | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js -2.14 MB | 735.28 kB | 724.14 kB | 180.98 kB | 181.07 kB | victory.js +2.14 MB | 735.26 kB | 724.14 kB | 180.97 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 332.23 kB | 331.56 kB | echarts.js -6.69 MB | 2.36 MB | 2.31 MB | 494.93 kB | 488.28 kB | antd.js +6.69 MB | 2.36 MB | 2.31 MB | 494.89 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.92 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js