Skip to content

Commit

Permalink
Drop superfluous __name() calls
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
indutny committed Feb 27, 2022
1 parent a1ff9d1 commit 2869f7d
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 9 deletions.
5 changes: 1 addition & 4 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/bundler/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ TestTypeScriptDecoratorsKeepNames
// entry.ts
var Foo = class {
};
__name(Foo, "Foo");
Foo;
Foo = __decorateClass([
decoratorMustComeAfterName
], Foo);
2 changes: 2 additions & 0 deletions internal/js_ast/js_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 8 additions & 4 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 36 additions & 0 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2869f7d

Please sign in to comment.