From 02db1222d4923d5e3b9b4942e9bf4015de2acdf3 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Sun, 27 Feb 2022 11:35:42 -0800 Subject: [PATCH 1/8] Drop superfluous `__name()` calls When the output is generated with `--keep-names` flag - `__name(fn, "name")` statements are inserted for every function/class with a name. However, in many situations the renamer doesn't change the name of the function/class, and thus the call to `__name` has no effect other than imposing an extra runtime cost on the application. This change adds `IsKeepName` flag to `ECall` struct and uses it in js_printer to detect such situations and omit the `__name()` from being printed. Sadly, it would be hard to drop it completely without `--minify` flag, but at least we can drop the runtime cost associated with it by just replacing the `__name()` with its first argument in such situations. --- .../bundler/snapshots/snapshots_default.txt | 5 +-- internal/bundler/snapshots/snapshots_ts.txt | 2 +- internal/js_ast/js_ast.go | 2 ++ internal/js_parser/js_parser.go | 12 ++++--- internal/js_printer/js_printer.go | 35 +++++++++++++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 5ed6ae7bbb9..13423045bc4 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1401,14 +1401,12 @@ TestKeepNamesTreeShaking // entry.js function fnStmtKeep() { } -__name(fnStmtKeep, "fnStmtKeep"); x = fnStmtKeep; var fnExprKeep = /* @__PURE__ */ __name(function() { }, "keep"); x = fnExprKeep; var clsStmtKeep = class { }; -__name(clsStmtKeep, "clsStmtKeep"); new clsStmtKeep(); var clsExprKeep = /* @__PURE__ */ __name(class { }, "keep"); @@ -2810,12 +2808,11 @@ export function outer() { let inner = function() { return Math.random(); }; - __name(inner, "inner"); const x = inner(); console.log(x); } } -__name(outer, "outer"), outer(); +outer(); ================================================================================ TestSwitchScopeNoBundle diff --git a/internal/bundler/snapshots/snapshots_ts.txt b/internal/bundler/snapshots/snapshots_ts.txt index 2d75c842191..5b8ac6dc268 100644 --- a/internal/bundler/snapshots/snapshots_ts.txt +++ b/internal/bundler/snapshots/snapshots_ts.txt @@ -1102,7 +1102,7 @@ TestTypeScriptDecoratorsKeepNames // entry.ts var Foo = class { }; -__name(Foo, "Foo"); +Foo; Foo = __decorateClass([ decoratorMustComeAfterName ], Foo); diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index b5bb33b8523..398788b652c 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -513,6 +513,8 @@ type ECall struct { // call itself is removed due to this annotation, the arguments must remain // if they have side effects. CanBeUnwrappedIfUnused bool + + IsKeepName bool } func (a *ECall) HasSameFlagsAs(b *ECall) bool { diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 8ca7ae8138b..9ec2396dc29 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -8944,17 +8944,21 @@ func (p *parser) keepExprSymbolName(value js_ast.Expr, name string) js_ast.Expr // Make sure tree shaking removes this if the function is never used value.Data.(*js_ast.ECall).CanBeUnwrappedIfUnused = true + value.Data.(*js_ast.ECall).IsKeepName = true return value } func (p *parser) keepStmtSymbolName(loc logger.Loc, ref js_ast.Ref, name string) js_ast.Stmt { p.symbols[ref.InnerIndex].Flags |= js_ast.DidKeepName + call := p.callRuntime(loc, "__name", []js_ast.Expr{ + {Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}, + {Loc: loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(name)}}, + }) + call.Data.(*js_ast.ECall).IsKeepName = true + return js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{ - Value: p.callRuntime(loc, "__name", []js_ast.Expr{ - {Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}, - {Loc: loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(name)}}, - }), + Value: call, // Make sure tree shaking removes this if the function is never used DoesNotAffectTreeShaking: true, diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 1e2b80e841f..3416b17b8c7 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -1395,6 +1395,10 @@ func (p *printer) simplifyUnusedExpr(expr js_ast.Expr) js_ast.Expr { } case *js_ast.ECall: + if p.isCallExprSuperfluous(e) { + return js_ast.Expr{Loc: expr.Loc} + } + var symbolFlags js_ast.SymbolFlags switch target := e.Target.Data.(type) { case *js_ast.EIdentifier: @@ -1792,6 +1796,11 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } } + if p.isCallExprSuperfluous(e) { + p.printExpr(e.Args[0], level, flags) + return + } + wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 var targetFlags printExprFlags if e.OptionalChain == js_ast.OptionalChainNone { @@ -2546,6 +2555,32 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla } } +func (p *printer) isCallExprSuperfluous(value *js_ast.ECall) bool { + if !value.IsKeepName { + return false + } + + fn := value.Args[0] + + var fnNameOrNil *js_ast.Ref + switch e := fn.Data.(type) { + case *js_ast.EIdentifier: + fnNameOrNil = &e.Ref + case *js_ast.EFunction: + if e.Fn.Name != nil { + fnNameOrNil = &e.Fn.Name.Ref + } + } + + keptName := helpers.UTF16ToString(value.Args[1].Data.(*js_ast.EString).Value) + + if fnNameOrNil != nil && keptName == p.renamer.NameForSymbol(*fnNameOrNil) { + return true + } + + return false +} + func (p *printer) isUnboundEvalIdentifier(value js_ast.Expr) bool { if id, ok := value.Data.(*js_ast.EIdentifier); ok { // Using the original name here is ok since unbound symbols are not renamed From e4dac602375856077a3e7773642033dfc50a4c4a Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 18:17:51 +0000 Subject: [PATCH 2/8] remove unnecessary DoesNotAffectTreeShaking flag --- internal/js_ast/js_ast.go | 5 ----- internal/js_parser/js_parser.go | 16 ++-------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index 398788b652c..0f53659ea88 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -1135,11 +1135,6 @@ type SLazyExport struct { type SExpr struct { Value Expr - - // This is set to true for automatically-generated expressions that should - // not affect tree shaking. For example, calling a function from the runtime - // that doesn't have externally-visible side effects. - DoesNotAffectTreeShaking bool } type EnumValue struct { diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 9ec2396dc29..9bd74613ae8 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -7724,7 +7724,6 @@ func (p *parser) mangleStmts(stmts []js_ast.Stmt, kind stmtsKind) []js_ast.Stmt prevStmt := result[len(result)-1] if prevS, ok := prevStmt.Data.(*js_ast.SExpr); ok && !js_ast.IsSuperCall(prevStmt) && !js_ast.IsSuperCall(stmt) { prevS.Value = js_ast.JoinWithComma(prevS.Value, s.Value) - prevS.DoesNotAffectTreeShaking = prevS.DoesNotAffectTreeShaking && s.DoesNotAffectTreeShaking continue } } @@ -8957,12 +8956,7 @@ func (p *parser) keepStmtSymbolName(loc logger.Loc, ref js_ast.Ref, name string) }) call.Data.(*js_ast.ECall).IsKeepName = true - return js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{ - Value: call, - - // Make sure tree shaking removes this if the function is never used - DoesNotAffectTreeShaking: true, - }} + return js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{Value: call}} } func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_ast.Stmt { @@ -14428,12 +14422,6 @@ func (p *parser) stmtsCanBeRemovedIfUnused(stmts []js_ast.Stmt) bool { } case *js_ast.SExpr: - if s.DoesNotAffectTreeShaking { - // Expressions marked with this are automatically generated and have - // no side effects by construction. - break - } - if !p.exprCanBeRemovedIfUnused(s.Value) { return false } @@ -14607,7 +14595,7 @@ func (p *parser) exprCanBeRemovedIfUnused(expr js_ast.Expr) bool { return true case *js_ast.ECall: - canCallBeRemoved := e.CanBeUnwrappedIfUnused + canCallBeRemoved := e.CanBeUnwrappedIfUnused || e.IsKeepName // Consider calls to our runtime "__publicField" function to be free of // side effects for the purpose of expression removal. This allows class From f6db468a0ece882e3067f01f12d3c21eb2bffee9 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 18:19:09 +0000 Subject: [PATCH 3/8] avoid some unnecessary memory allocations --- internal/js_printer/js_printer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 3416b17b8c7..3b447841622 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -2572,9 +2572,9 @@ func (p *printer) isCallExprSuperfluous(value *js_ast.ECall) bool { } } - keptName := helpers.UTF16ToString(value.Args[1].Data.(*js_ast.EString).Value) + keptName := value.Args[1].Data.(*js_ast.EString).Value - if fnNameOrNil != nil && keptName == p.renamer.NameForSymbol(*fnNameOrNil) { + if fnNameOrNil != nil && helpers.UTF16EqualsString(keptName, p.renamer.NameForSymbol(*fnNameOrNil)) { return true } From b41af7deed1634bfd1479d306584cf5a023572e0 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 18:23:41 +0000 Subject: [PATCH 4/8] add missing class expression case --- internal/js_printer/js_printer.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 3b447841622..fbe084baa33 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -2560,25 +2560,27 @@ func (p *printer) isCallExprSuperfluous(value *js_ast.ECall) bool { return false } - fn := value.Args[0] - - var fnNameOrNil *js_ast.Ref - switch e := fn.Data.(type) { + var ref *js_ast.Ref + switch e := value.Args[0].Data.(type) { case *js_ast.EIdentifier: - fnNameOrNil = &e.Ref + // "__name(foo, 'foo')" + ref = &e.Ref + case *js_ast.EFunction: + // "__name(function foo() {}, 'foo')" if e.Fn.Name != nil { - fnNameOrNil = &e.Fn.Name.Ref + ref = &e.Fn.Name.Ref } - } - - keptName := value.Args[1].Data.(*js_ast.EString).Value - if fnNameOrNil != nil && helpers.UTF16EqualsString(keptName, p.renamer.NameForSymbol(*fnNameOrNil)) { - return true + case *js_ast.EClass: + // "__name(class foo {}, 'foo')" + if e.Class.Name != nil { + ref = &e.Class.Name.Ref + } } - return false + keptName := value.Args[1].Data.(*js_ast.EString).Value + return ref != nil && helpers.UTF16EqualsString(keptName, p.renamer.NameForSymbol(*ref)) } func (p *printer) isUnboundEvalIdentifier(value js_ast.Expr) bool { From 0662a336b2a4ef99f00e04b727d7a11f7a35fa9b Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 23:29:41 +0000 Subject: [PATCH 5/8] test coverage for keep name helper call removal --- internal/bundler/bundler_default_test.go | 55 ++++++++++++++++ .../bundler/snapshots/snapshots_default.txt | 65 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go index eed4787959a..8a759f970bd 100644 --- a/internal/bundler/bundler_default_test.go +++ b/internal/bundler/bundler_default_test.go @@ -4358,6 +4358,61 @@ func TestDefineThis(t *testing.T) { }) } +func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + import { + functionStmt as functionStmt1, + functionExpr as functionExpr1, + classStmt as classStmt1, + classExpr as classExpr1, + } from './copy1' + + import { + functionStmt as functionStmt2, + functionExpr as functionExpr2, + classStmt as classStmt2, + classExpr as classExpr2, + } from './copy2' + + console.log([ + functionStmt1, functionStmt2, + functionExpr1, functionExpr2, + classStmt1, classStmt2, + classExpr1, classExpr2, + ]) + `, + + "/copy1.js": ` + export function functionStmt() { return 'copy1' } + export class classStmt { foo = 'copy1' } + export let functionExpr = function fn() { return 'copy1' } + export let classExpr = class cls { foo = 'copy1' } + + class classStmtSideEffect { static [copy1]() {} } + let classExprSideEffect = class clsSideEffect { static [copy1]() {} } + `, + + "/copy2.js": ` + export function functionStmt() { return 'copy2' } + export class classStmt { foo = 'copy2' } + export let functionExpr = function fn() { return 'copy2' } + export let classExpr = class cls { foo = 'copy2' } + + class classStmtSideEffect { static [copy2]() {} } + let classExprSideEffect = class clsSideEffect { static [copy2]() {} } + `, + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/out.js", + KeepNames: true, + }, + }) +} + func TestKeepNamesTreeShaking(t *testing.T) { default_suite.expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 13423045bc4..70043ba3e73 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1412,6 +1412,71 @@ var clsExprKeep = /* @__PURE__ */ __name(class { }, "keep"); new clsExprKeep(); +================================================================================ +TestKeepNamesWithNecessaryHelperFunctionCalls +---------- /out.js ---------- +// copy1.js +function functionStmt() { + return "copy1"; +} +functionStmt; +var classStmt = class { + foo = "copy1"; +}; +classStmt; +var functionExpr = function fn() { + return "copy1"; +}; +var classExpr = class cls { + foo = "copy1"; +}; +var classStmtSideEffect = class { + static [copy1]() { + } +}; +classStmtSideEffect; +var classExprSideEffect = class clsSideEffect { + static [copy1]() { + } +}; + +// copy2.js +function functionStmt2() { + return "copy2"; +} +__name(functionStmt2, "functionStmt"); +var classStmt2 = class { + foo = "copy2"; +}; +__name(classStmt2, "classStmt"); +var functionExpr2 = /* @__PURE__ */ __name(function fn2() { + return "copy2"; +}, "fn"); +var classExpr2 = /* @__PURE__ */ __name(class cls2 { + foo = "copy2"; +}, "cls"); +var classStmtSideEffect2 = class { + static [copy2]() { + } +}; +__name(classStmtSideEffect2, "classStmtSideEffect"); +var classExprSideEffect2 = /* @__PURE__ */ __name(class clsSideEffect2 { + static [copy2]() { + } +}, "clsSideEffect"); + +// entry.js +console.log([ + functionStmt, + functionStmt2, + functionExpr, + functionExpr2, + classStmt, + classStmt2, + classExpr, + classExpr2 +]); + ================================================================================ TestLegalCommentsAvoidSlashTagEndOfFile ---------- /out/entry.js ---------- From 3fb3d27877ccecc4d3a1363423832492aed9da2a Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 23:38:30 +0000 Subject: [PATCH 6/8] remove the now-unnecessary identifier references --- .../bundler/snapshots/snapshots_default.txt | 3 -- internal/bundler/snapshots/snapshots_ts.txt | 1 - internal/js_printer/js_printer.go | 42 +++++++++---------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 70043ba3e73..9aef8543255 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1419,11 +1419,9 @@ TestKeepNamesWithNecessaryHelperFunctionCalls function functionStmt() { return "copy1"; } -functionStmt; var classStmt = class { foo = "copy1"; }; -classStmt; var functionExpr = function fn() { return "copy1"; }; @@ -1434,7 +1432,6 @@ var classStmtSideEffect = class { static [copy1]() { } }; -classStmtSideEffect; var classExprSideEffect = class clsSideEffect { static [copy1]() { } diff --git a/internal/bundler/snapshots/snapshots_ts.txt b/internal/bundler/snapshots/snapshots_ts.txt index 5b8ac6dc268..d8cd84fd9d4 100644 --- a/internal/bundler/snapshots/snapshots_ts.txt +++ b/internal/bundler/snapshots/snapshots_ts.txt @@ -1102,7 +1102,6 @@ TestTypeScriptDecoratorsKeepNames // entry.ts var Foo = class { }; -Foo; Foo = __decorateClass([ decoratorMustComeAfterName ], Foo); diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index fbe084baa33..613d087dd4c 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -3387,18 +3387,16 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { update := s.UpdateOrNil // Omit calls to empty functions from the output completely - if p.options.MinifySyntax { - if expr, ok := init.Data.(*js_ast.SExpr); ok { - if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil { - init.Data = nil - } else if value.Data != expr.Value.Data { - init.Data = &js_ast.SExpr{Value: value} - } - } - if update.Data != nil { - update = p.simplifyUnusedExpr(update) + if expr, ok := init.Data.(*js_ast.SExpr); ok { + if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil { + init.Data = nil + } else if value.Data != expr.Value.Data { + init.Data = &js_ast.SExpr{Value: value} } } + if update.Data != nil { + update = p.simplifyUnusedExpr(update) + } p.printIndent() p.printSpaceBeforeIdentifier() @@ -3611,20 +3609,18 @@ func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { value := s.Value // Omit calls to empty functions from the output completely - if p.options.MinifySyntax { - value = p.simplifyUnusedExpr(value) - if value.Data == nil { - // If this statement is not in a block, then we still need to emit something - if (flags & canOmitStatement) == 0 { - // "if (x) empty();" => "if (x) ;" - p.printIndent() - p.print(";") - p.printNewline() - } else { - // "if (x) { empty(); }" => "if (x) {}" - } - break + value = p.simplifyUnusedExpr(value) + if value.Data == nil { + // If this statement is not in a block, then we still need to emit something + if (flags & canOmitStatement) == 0 { + // "if (x) empty();" => "if (x) ;" + p.printIndent() + p.print(";") + p.printNewline() + } else { + // "if (x) { empty(); }" => "if (x) {}" } + break } p.printIndent() From 8e83d624fd4f48085d3474242e3919d6d5051d0d Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 13 Mar 2022 23:47:31 +0000 Subject: [PATCH 7/8] changelog entry --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa5e906b5b..08550ccc7eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,48 @@ Some JavaScript environments such as Cloudflare Workers or Deno Deploy don't allow `new Function` because they disallow dynamic JavaScript evaluation. Previously esbuild's WebAssembly-based library used this to construct the WebAssembly worker function. With this release, the code is now inlined without using `new Function` so it will be able to run even when this restriction is in place. +* Drop superfluous `__name()` calls ([#2062](https://github.com/evanw/esbuild/pull/2062)) + + When the `--keep-names` option is specified, esbuild inserts calls to a `__name` helper function to ensure that the `.name` property on function and class objects remains consistent even if the function or class name is renamed to avoid a name collision or because name minification is enabled. With this release, esbuild will now try to omit these calls to the `__name` helper function when the name of the function or class object was not renamed during the linking process after all: + + ```js + // Original code + import { foo as foo1 } from 'data:text/javascript,export function foo() { return "foo1" }' + import { foo as foo2 } from 'data:text/javascript,export function foo() { return "foo2" }' + console.log(foo1.name, foo2.name) + + // Old output (with --bundle --keep-names) + (() => { + var __defProp = Object.defineProperty; + var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); + function foo() { + return "foo1"; + } + __name(foo, "foo"); + function foo2() { + return "foo2"; + } + __name(foo2, "foo"); + console.log(foo.name, foo2.name); + })(); + + // New output (with --bundle --keep-names) + (() => { + var __defProp = Object.defineProperty; + var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); + function foo() { + return "foo1"; + } + function foo2() { + return "foo2"; + } + __name(foo2, "foo"); + console.log(foo.name, foo2.name); + })(); + ``` + + Notice how one of the calls to `__name` is now no longer printed. This change was contributed by [@indutny](https://github.com/indutny). + ## 0.14.25 * Reduce minification of CSS transforms to avoid Safari bugs ([#2057](https://github.com/evanw/esbuild/issues/2057)) From e7894fa3fc8530af05d8daf20d2e34b3b9ba3fc7 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 14 Mar 2022 00:37:34 +0000 Subject: [PATCH 8/8] handle names coming from assign target bindings --- internal/bundler/bundler_default_test.go | 10 ++++++++++ .../bundler/snapshots/snapshots_default.txt | 18 +++++++++++++++++- internal/js_printer/js_printer.go | 17 +++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go index 830aa6a654a..c264f78074d 100644 --- a/internal/bundler/bundler_default_test.go +++ b/internal/bundler/bundler_default_test.go @@ -4367,6 +4367,8 @@ func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { functionExpr as functionExpr1, classStmt as classStmt1, classExpr as classExpr1, + functionAnonExpr as functionAnonExpr1, + classAnonExpr as classAnonExpr1, } from './copy1' import { @@ -4374,6 +4376,8 @@ func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { functionExpr as functionExpr2, classStmt as classStmt2, classExpr as classExpr2, + functionAnonExpr as functionAnonExpr2, + classAnonExpr as classAnonExpr2, } from './copy2' console.log([ @@ -4381,6 +4385,8 @@ func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { functionExpr1, functionExpr2, classStmt1, classStmt2, classExpr1, classExpr2, + functionAnonExpr1, functionAnonExpr2, + classAnonExpr1, classAnonExpr2, ]) `, @@ -4389,6 +4395,8 @@ func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { export class classStmt { foo = 'copy1' } export let functionExpr = function fn() { return 'copy1' } export let classExpr = class cls { foo = 'copy1' } + export let functionAnonExpr = function() { return 'copy1' } + export let classAnonExpr = class { foo = 'copy1' } class classStmtSideEffect { static [copy1]() {} } let classExprSideEffect = class clsSideEffect { static [copy1]() {} } @@ -4399,6 +4407,8 @@ func TestKeepNamesWithNecessaryHelperFunctionCalls(t *testing.T) { export class classStmt { foo = 'copy2' } export let functionExpr = function fn() { return 'copy2' } export let classExpr = class cls { foo = 'copy2' } + export let functionAnonExpr = function() { return 'copy2' } + export let classAnonExpr = class { foo = 'copy2' } class classStmtSideEffect { static [copy2]() {} } let classExprSideEffect = class clsSideEffect { static [copy2]() {} } diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 9aef8543255..42da6186108 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1428,6 +1428,12 @@ var functionExpr = function fn() { var classExpr = class cls { foo = "copy1"; }; +var functionAnonExpr = function() { + return "copy1"; +}; +var classAnonExpr = class { + foo = "copy1"; +}; var classStmtSideEffect = class { static [copy1]() { } @@ -1452,6 +1458,12 @@ var functionExpr2 = /* @__PURE__ */ __name(function fn2() { var classExpr2 = /* @__PURE__ */ __name(class cls2 { foo = "copy2"; }, "cls"); +var functionAnonExpr2 = /* @__PURE__ */ __name(function() { + return "copy2"; +}, "functionAnonExpr"); +var classAnonExpr2 = /* @__PURE__ */ __name(class { + foo = "copy2"; +}, "classAnonExpr"); var classStmtSideEffect2 = class { static [copy2]() { } @@ -1471,7 +1483,11 @@ console.log([ classStmt, classStmt2, classExpr, - classExpr2 + classExpr2, + functionAnonExpr, + functionAnonExpr2, + classAnonExpr, + classAnonExpr2 ]); ================================================================================ diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 2903e53f050..0f9fee0a5d7 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -441,6 +441,8 @@ type printer struct { callTarget js_ast.E extractedLegalComments map[string]bool js []byte + keepNamesAssignTarget js_ast.E + keepNamesRef js_ast.Ref options Options builder sourcemap.ChunkBuilder stmtStart int @@ -2596,6 +2598,16 @@ func (p *printer) isCallExprSuperfluous(value *js_ast.ECall) bool { } } + // If this is an anonymous function or class expression but the assign target + // has a name binding, use the name from the name binding instead: + // + // let foo = __name(function() {}, "foo"); + // let bar = __name(class {}, "bar"); + // + if ref == nil && p.keepNamesAssignTarget == value { + ref = &p.keepNamesRef + } + keptName := value.Args[1].Data.(*js_ast.EString).Value return ref != nil && helpers.UTF16EqualsString(keptName, p.renamer.NameForSymbol(*ref)) } @@ -2814,6 +2826,11 @@ func (p *printer) printDecls(keyword string, decls []js_ast.Decl, flags printExp p.printBinding(decl.Binding) if decl.ValueOrNil.Data != nil { + if id, ok := decl.Binding.Data.(*js_ast.BIdentifier); ok { + p.keepNamesAssignTarget = decl.ValueOrNil.Data + p.keepNamesRef = id.Ref + } + p.printSpace() p.print("=") p.printSpace()