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`.
This commit is contained in:
sapphi-red 2025-01-27 14:31:12 +00:00
parent f7f2d2f93a
commit ad14403f9e
No known key found for this signature in database
GPG key ID: 67631A259A77AC6C
2 changed files with 23 additions and 25 deletions

View file

@ -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 <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_parser/js_parser_test.go#L4658>
@ -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]

View file

@ -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