diff --git a/crates/oxc_minifier/README.md b/crates/oxc_minifier/README.md index e263d12c759cc..f3d8c31891f17 100644 --- a/crates/oxc_minifier/README.md +++ b/crates/oxc_minifier/README.md @@ -23,6 +23,8 @@ The compressor is responsible for rewriting statements and expressions for minim - [Properties of the global object defined in the ECMAScript spec](https://tc39.es/ecma262/multipage/global-object.html#sec-global-object) behaves the same as in the spec - Examples of properties: `Infinity`, `parseInt`, `Object`, `Promise.resolve` - Examples that breaks this assumption: `globalThis.Object = class MyObject {}` +- The code does not rely on the `name` property of `Function` or `Class` + - Examples that breaks this assumption: `function fn() {}; console.log(f.name === 'fn')` - [`document.all`](https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-IsHTMLDDA-internal-slot) is not used or behaves as a normal object - Examples that breaks this assumption: `console.log(typeof document.all === 'undefined')` - TDZ violation does not happen diff --git a/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs b/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs index 8ef342f5e019b..8b05a77ae7ab3 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs @@ -426,9 +426,10 @@ impl<'a> PeepholeOptimizations { if !matches!(consequent.left, AssignmentTarget::AssignmentTargetIdentifier(_)) { return None; } - if consequent.right.is_anonymous_function_definition() { - return None; - } + // TODO: need this condition when `keep_fnames` is introduced + // if consequent.right.is_anonymous_function_definition() { + // return None; + // } if consequent.operator != AssignmentOperator::Assign || consequent.operator != alternate.operator || consequent.left.content_ne(&alternate.left) diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 7d0974d3b3605..f3e4ff96902d4 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -1558,15 +1558,16 @@ mod test { test("x ? a = 0 : a = 1", "a = x ? 0 : 1"); test( "x ? a = function foo() { return 'a' } : a = function bar() { return 'b' }", - "a = x ? function foo() { return 'a' } : function bar() { return 'b' }", + "a = x ? function () { return 'a' } : function () { return 'b' }", ); // a.b might have a side effect test_same("x ? a.b = 0 : a.b = 1"); // `a = x ? () => 'a' : () => 'b'` does not set the name property of the function - test_same("x ? a = () => 'a' : a = () => 'b'"); - test_same("x ? a = function () { return 'a' } : a = function () { return 'b' }"); - test_same("x ? a = class { foo = 'a' } : a = class { foo = 'b' }"); + // TODO: need to pass these tests when `keep_fnames` are introduced + // test_same("x ? a = () => 'a' : a = () => 'b'"); + // test_same("x ? a = function () { return 'a' } : a = function () { return 'b' }"); + // test_same("x ? a = class { foo = 'a' } : a = class { foo = 'b' }"); // for non `=` operators, `GetValue(lref)` is called before `Evaluation of AssignmentExpression` // so cannot be fold to `a += x ? 0 : 1` diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index dac7884de46d6..c6564b784f79a 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -964,6 +964,7 @@ impl<'a> LatePeepholeOptimizations { if let Expression::NewExpression(e) = expr { Self::try_compress_typed_array_constructor(e, ctx); } + Self::remove_name_from_expressions(expr, ctx); if let Some(folded_expr) = match expr { Expression::BooleanLiteral(_) => Self::try_compress_boolean(expr, ctx), @@ -1085,6 +1086,30 @@ impl<'a> LatePeepholeOptimizations { } } + /// Remove name from function / class expressions if it is not used. + /// + /// - `var a = function f() {}` -> `var a = function () {}` + /// - `var a = class C {}` -> `var a = class {}` + /// + /// This compression is not safe if the code relies on `Function::name`. + fn remove_name_from_expressions(expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + match expr { + Expression::FunctionExpression(func) => { + if func.id.as_ref().is_some_and(|id| !ctx.symbols().symbol_is_used(id.symbol_id())) + { + func.id = None; + } + } + Expression::ClassExpression(class) => { + if class.id.as_ref().is_some_and(|id| !ctx.symbols().symbol_is_used(id.symbol_id())) + { + class.id = None; + } + } + _ => {} + } + } + /// Whether the name matches any TypedArray name. /// /// See for the list of TypedArrays. @@ -1259,7 +1284,7 @@ mod test { test("x = new Object()", "x = ({})"); test("x = Object()", "x = ({})"); - test_same("x = (function f(){function Object(){this.x=4}return new Object();})();"); + test_same("x = (function (){function Object(){this.x=4}return new Object();})();"); test("x = new window.Object", "x = ({})"); test("x = new window.Object()", "x = ({})"); @@ -1269,8 +1294,8 @@ mod test { test("x = window.Object?.()", "x = Object?.()"); test( - "x = (function f(){function Object(){this.x=4};return new window.Object;})();", - "x = (function f(){function Object(){this.x=4}return {};})();", + "x = (function (){function Object(){this.x=4};return new window.Object;})();", + "x = (function (){function Object(){this.x=4}return {};})();", ); } @@ -1699,6 +1724,14 @@ mod test { ); } + #[test] + fn test_remove_name_from_expressions() { + test("var a = function f() {}", "var a = function () {}"); + test_same("var a = function f() { return f; }"); + test("var a = class C {}", "var a = class {}"); + test_same("var a = class C { foo() { return C } }"); + } + #[test] fn test_compress_is_null_and_to_nullish_coalescing() { test("x == null && y", "x ?? y"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 857e66339600c..faa61e2e62b1c 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 59.54 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js -287.63 kB | 89.45 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js +287.63 kB | 89.44 kB | 90.07 kB | 30.96 kB | 31.95 kB | jquery.js -342.15 kB | 117.61 kB | 118.14 kB | 43.45 kB | 44.37 kB | vue.js +342.15 kB | 117.46 kB | 118.14 kB | 43.33 kB | 44.37 kB | vue.js 544.10 kB | 71.40 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js 555.77 kB | 271.11 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js -1.01 MB | 440.89 kB | 458.89 kB | 122.52 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.48 kB | 458.89 kB | 122.41 kB | 126.71 kB | bundle.min.js -1.25 MB | 650.30 kB | 646.76 kB | 160.95 kB | 163.73 kB | three.js +1.25 MB | 647.42 kB | 646.76 kB | 160.30 kB | 163.73 kB | three.js -2.14 MB | 716.92 kB | 724.14 kB | 161.98 kB | 181.07 kB | victory.js +2.14 MB | 716.51 kB | 724.14 kB | 161.81 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.32 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.27 kB | 331.56 kB | echarts.js -6.69 MB | 2.28 MB | 2.31 MB | 467.77 kB | 488.28 kB | antd.js +6.69 MB | 2.28 MB | 2.31 MB | 466.21 kB | 488.28 kB | antd.js -10.95 MB | 3.36 MB | 3.49 MB | 861.77 kB | 915.50 kB | typescript.js +10.95 MB | 3.35 MB | 3.49 MB | 861.48 kB | 915.50 kB | typescript.js