From ad14403f9ecdba3b05830ba3902bbef6bf801537 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:31:12 +0000 Subject: [PATCH] feat(minifier): compress `typeof a.b === 'undefined'` to `a.b === void 0` (#8751) The special behavior of `typeof` is only for the identifier, so it should be safe to compress `typeof a.b === 'undefined'` (and other expressions) to `a.b === void 0`. --- .../peephole/substitute_alternate_syntax.rs | 44 +++++++++---------- tasks/minsize/minsize.snap | 4 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 656015724..1204f0253 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -156,10 +156,12 @@ impl<'a> PeepholeOptimizations { /// 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"` + /// - `typeof foo == "undefined"` (if foo is not resolved) -> `typeof foo > "u"` + /// - `typeof foo != "undefined"` (if foo is not resolved) -> `typeof foo < "u"` + /// - `typeof foo == "undefined"` -> `foo === undefined` + /// - `typeof foo != "undefined"` -> `foo !== undefined` + /// - `typeof foo.bar == "undefined"` -> `foo.bar === undefined` (for any expression e.g.`typeof (foo + "")`) + /// - `typeof foo.bar != "undefined"` -> `foo.bar !== undefined` (for any expression e.g.`typeof (foo + "")`) /// /// Enabled by `compress.typeofs` fn try_compress_typeof_undefined( @@ -183,24 +185,19 @@ impl<'a> PeepholeOptimizations { _ => return None, }; if let Expression::Identifier(ident) = &unary_expr.argument { - if !ctx.is_global_reference(ident) { - let Expression::UnaryExpression(unary_expr) = - ctx.ast.move_expression(&mut expr.left) - else { - unreachable!() - }; - 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, - )); + if ctx.is_global_reference(ident) { + let left = ctx.ast.move_expression(&mut expr.left); + let right = ctx.ast.expression_string_literal(expr.right.span(), "u", None); + return Some(ctx.ast.expression_binary(expr.span, left, new_comp_op, right)); } + } + + let Expression::UnaryExpression(unary_expr) = ctx.ast.move_expression(&mut expr.left) + else { + unreachable!() }; - let left = ctx.ast.move_expression(&mut expr.left); - let right = ctx.ast.expression_string_literal(expr.right.span(), "u", None); - Some(ctx.ast.expression_binary(expr.span, left, new_comp_op, right)) + let right = ctx.ast.void_0(expr.right.span()); + Some(ctx.ast.expression_binary(expr.span, unary_expr.unbox().argument, new_eq_op, right)) } /// `a || (b || c);` -> `(a || b) || c;` @@ -1498,6 +1495,10 @@ mod test { test("typeof x !== 'undefined'; var x", "x !== void 0; var x"); // input and output both errors with same TDZ error test("typeof x !== 'undefined'; let x", "x !== void 0; let x"); + + test("typeof x.y === 'undefined'", "x.y === void 0"); + test("typeof x.y !== 'undefined'", "x.y !== void 0"); + test("typeof (x + '') === 'undefined'", "x + '' === void 0"); } /// Port from @@ -1512,9 +1513,6 @@ mod test { test("typeof x == 'undefined'", "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] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 6f4651049..66015129c 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,7 +5,7 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 59.68 kB | 59.82 kB | 19.25 kB | 19.33 kB | moment.js -287.63 kB | 89.54 kB | 90.07 kB | 31.07 kB | 31.95 kB | jquery.js +287.63 kB | 89.52 kB | 90.07 kB | 31.07 kB | 31.95 kB | jquery.js 342.15 kB | 117.69 kB | 118.14 kB | 43.66 kB | 44.37 kB | vue.js @@ -21,7 +21,7 @@ Original | minified | minified | gzip | gzip | Fixture 3.20 MB | 1.01 MB | 1.01 MB | 325.18 kB | 331.56 kB | echarts.js -6.69 MB | 2.30 MB | 2.31 MB | 469.99 kB | 488.28 kB | antd.js +6.69 MB | 2.30 MB | 2.31 MB | 469.97 kB | 488.28 kB | antd.js 10.95 MB | 3.37 MB | 3.49 MB | 866.63 kB | 915.50 kB | typescript.js