From 9ea4e31ba3923262072c5b2d14beb1a7d5ea614e Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:48:56 +0000 Subject: [PATCH] feat(minifier): remove `new` from `new Error`/`new Function`/`new RegExp` (#8313) Remove `new` from some cases: - `new Error(...)` -> `Error(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-error-constructor:~:text=Thus%20the%20function%20call%20Error(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Error(%E2%80%A6)%20with%20the%20same%20arguments.) - `new Function(...)` -> `Function(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function-constructor:~:text=Thus%20the%20function%20call%20Function(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Function(%E2%80%A6)%20with%20the%20same%20arguments.) - `new RegExp(nonRegexp)` -> `RegExp(nonRegexp)`: `new` can be removed if there's no argument or the first argument is not regex object, [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp#return_value), [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-constructor:~:text=when%20called%20as%20a%20function%20rather%20than%20as%20a%20constructor%2C%20returns%20either%20a%20new%20RegExp%20object%2C%20or%20the%20argument%20itself%20if%20the%20only%20argument%20is%20a%20RegExp%20object.). I made this to happen if the first argument is not an object. --- .../peephole_substitute_alternate_syntax.rs | 57 ++++++++++++++++++- tasks/minsize/minsize.snap | 20 +++---- 2 files changed, 66 insertions(+), 11 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 a8394cc4ab98a..cf66d2a8eb2c1 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 @@ -1,6 +1,9 @@ use oxc_allocator::Vec; use oxc_ast::{ast::*, NONE}; -use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, ToInt32, ToJsString, ToNumber}; +use oxc_ecmascript::{ + constant_evaluation::{ConstantEvaluation, ValueType}, + ToInt32, ToJsString, ToNumber, +}; use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ @@ -613,6 +616,35 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { ); Some(Self::array_literal(elements, ctx)) } + } else if new_expr.callee.is_global_reference_name("Error", ctx.symbols()) + || new_expr.callee.is_global_reference_name("Function", ctx.symbols()) + { + Some(ctx.ast.expression_call( + new_expr.span, + ctx.ast.move_expression(&mut new_expr.callee), + Option::::None, + ctx.ast.move_vec(&mut new_expr.arguments), + false, + )) + } else if new_expr.callee.is_global_reference_name("RegExp", ctx.symbols()) { + let arguments_len = new_expr.arguments.len(); + if arguments_len == 0 + || (arguments_len >= 1 + && new_expr.arguments[0].as_expression().map_or(false, |first_argument| { + let ty = ValueType::from(first_argument); + !ty.is_undetermined() && !ty.is_object() + })) + { + Some(ctx.ast.expression_call( + new_expr.span, + ctx.ast.move_expression(&mut new_expr.callee), + Option::::None, + ctx.ast.move_vec(&mut new_expr.arguments), + false, + )) + } else { + None + } } else { None } @@ -1058,6 +1090,29 @@ mod test { ); } + #[test] + fn test_fold_new_expressions() { + test("new Error()", "Error()"); + test("new Error('a')", "Error('a')"); + test("new Error('a', { cause: b })", "Error('a', { cause: b })"); + test_same("var Error; new Error()"); + + test("new Function()", "Function()"); + test( + "new Function('a', 'b', 'console.log(a, b)')", + "Function('a', 'b', 'console.log(a, b)')", + ); + test_same("var Function; new Function()"); + + test("new RegExp()", "RegExp()"); + test("new RegExp('a')", "RegExp('a')"); + test("new RegExp(0)", "RegExp(0)"); + test("new RegExp(null)", "RegExp(null)"); + test("new RegExp('a', 'g')", "RegExp('a', 'g')"); + test_same("new RegExp(foo)"); + test_same("new RegExp(/foo/)"); + } + #[test] #[ignore] fn test_split_comma_expressions() { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index f1e5d69314223..8194418b259af 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.68 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js -173.90 kB | 59.86 kB | 59.82 kB | 19.43 kB | 19.33 kB | moment.js +173.90 kB | 59.79 kB | 59.82 kB | 19.42 kB | 19.33 kB | moment.js -287.63 kB | 90.15 kB | 90.07 kB | 32.07 kB | 31.95 kB | jquery.js +287.63 kB | 90.06 kB | 90.07 kB | 32.07 kB | 31.95 kB | jquery.js -342.15 kB | 118.21 kB | 118.14 kB | 44.52 kB | 44.37 kB | vue.js +342.15 kB | 118.16 kB | 118.14 kB | 44.52 kB | 44.37 kB | vue.js 544.10 kB | 71.79 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js -555.77 kB | 273.13 kB | 270.13 kB | 90.95 kB | 90.80 kB | d3.js +555.77 kB | 272.95 kB | 270.13 kB | 90.94 kB | 90.80 kB | d3.js -1.01 MB | 460.31 kB | 458.89 kB | 126.84 kB | 126.71 kB | bundle.min.js +1.01 MB | 460.24 kB | 458.89 kB | 126.84 kB | 126.71 kB | bundle.min.js -1.25 MB | 652.67 kB | 646.76 kB | 163.53 kB | 163.73 kB | three.js +1.25 MB | 652.57 kB | 646.76 kB | 163.52 kB | 163.73 kB | three.js -2.14 MB | 726.14 kB | 724.14 kB | 180.16 kB | 181.07 kB | victory.js +2.14 MB | 726.02 kB | 724.14 kB | 180.14 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 331.88 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 331.84 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 492.77 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 492.75 kB | 488.28 kB | antd.js -10.95 MB | 3.50 MB | 3.49 MB | 909.12 kB | 915.50 kB | typescript.js +10.95 MB | 3.50 MB | 3.49 MB | 909.11 kB | 915.50 kB | typescript.js