From 2869f7d2ab72f3b84d9c0cfda04b81ac2dc62d39 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Sun, 27 Feb 2022 11:35:42 -0800 Subject: [PATCH] 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 | 36 +++++++++++++++++++ 5 files changed, 48 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..4d23d3e2c3d 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: @@ -1417,6 +1421,7 @@ func (p *printer) simplifyUnusedExpr(expr js_ast.Expr) js_ast.Expr { if (symbolFlags&(js_ast.IsIdentityFunction|js_ast.CouldPotentiallyBeMutated)) == js_ast.IsIdentityFunction && len(e.Args) == 1 { return js_ast.SimplifyUnusedExpr(p.simplifyUnusedExpr(e.Args[0]), p.isUnbound) } + } return expr @@ -1792,6 +1797,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 +2556,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