Skip to content

Commit

Permalink
JS: revert performance regression from 61a4bb8, up to 120% faster for…
Browse files Browse the repository at this point in the history
… antd.js
  • Loading branch information
tdewolff committed Mar 12, 2024
1 parent 5844b27 commit 75e7703
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 82 deletions.
5 changes: 3 additions & 2 deletions js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ func TestJS(t *testing.T) {
{`a=5;with(b=4){}`, `with(a=5,b=4);`},
{`(function(){})();(function(){})()`, `(function(){})(),function(){}()`},
//{`for(var a=0;;){var b=5;var d=6;for(var c=0;;);}`, `for(a=0;;)for(var a,b=5,d=6,c=0;;);`}, // TODO
//{`var aaaa=[];for(var b=0;;){}for(var b in aaaa){}var aaaa=[];for(var b=0;;){}`, `for(var aaaa=[],b=0;;);for(b in aaaa);for(aaaa=[],b=0;;);`}, // TODO

// collapse functions
//{`var a=function(){return 5}`, `var a=()=>5`},
Expand Down Expand Up @@ -808,8 +809,8 @@ func TestJS(t *testing.T) {
{`export default function Foo(){a}Foo.prototype.bar=b`, `export default function Foo(){a}Foo.prototype.bar=b`}, // #525
{`(e=1,e=2)`, `e=1,e=2`}, // #528
{`"\x00\x31 \0\u0000"`, `"\x001 \0\x00"`}, // #577
{`function transform(){{var aaaa=[];for(var b=0;;){}for(var b in aaaa){}var aaaa=[];for(var b=0;;){}}}`, `function transform(){{for(var aaaa=[],b=0;;);for(b in aaaa);for(aaaa=[],b=0;;);}}`}, // #619
{`for(var a=0;;){var b=5;for(var c=0;;);}`, `for(var b,c,a=0;;)for(b=5,c=0;;);`}, // #634
{`function transform(){{var aaaa=[];for(var b=0;;){}for(var b in aaaa){}var aaaa=[];for(var b=0;;){}}}`, `function transform(){{for(var aaaa=[],b=0;;);for(b in aaaa);for(var aaaa=[],b=0;;);}}`}, // #619
{`for(var a=0;;){var b=5;for(var c=0;;);}`, `for(var b,c,a=0;;)for(b=5,c=0;;);`}, // #634
{"if(a)for(;;)\n;else b", `if(a)for(;;);else b`}, // #636
{`'\u000A\u000D'`, "`\n\r`"}, // #653
{`for(!a;b;c);`, "for(!a;b;c);"}, // #656
Expand Down
138 changes: 58 additions & 80 deletions js/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,112 +288,90 @@ func mergeVarDeclExprStmt(decl *js.VarDecl, exprStmt *js.ExprStmt, forward bool)
return false
}

func (m *jsMinifier) varNameLen(v *js.Var) int {
func (m *jsMinifier) countHoistLength(ibinding js.IBinding) int {
if !m.o.KeepVarNames {
return 2 // assume that var name will be of length one, +1 for the comma
return len(bindingVars(ibinding)) * 2 // assume that var name will be of length one, +1 for the comma
}
return len(v.Data) + 1 // +1 for the comma when added to other declaration

n := 0
for _, v := range bindingVars(ibinding) {
n += len(v.Data) + 1 // +1 for the comma when added to other declaration
}
return n
}

func (m *jsMinifier) hoistVars(body *js.BlockStmt) {
// Hoist all variable declarations in the current module/function scope to the top
// Find the best var declaration (that results in the shortest code), all others are converted to expressions.
// This is possible because an ArrayBindingPattern and ObjectBindingPattern can be converted to an ArrayLiteral or ObjectLiteral respectively, as they are supersets of the BindingPatterns.
// Hoist all variable declarations in the current module/function scope to the variable
// declaration that reduces file size the most. All other declarations are converted to
// expressions and their variable names are copied to the only remaining declaration.
// This is possible because an ArrayBindingPattern and ObjectBindingPattern can be converted to
// an ArrayLiteral or ObjectLiteral respectively, as they are supersets of the BindingPatterns.
if 1 < len(body.Scope.VarDecls) {
// Select which variable declarations will be hoisted (convert to expression) and which not
best := 0
scores := make([]int, len(body.Scope.VarDecls)) // savings if hoisting target
hoists := make([][]bool, len(body.Scope.VarDecls))
for i, target := range body.Scope.VarDecls {
// keep list of target variables to avoid declaring a var more than once
var refsTarget []*js.Var
for _, item := range target.List {
refsTarget = append(refsTarget, bindingVars(item.Binding)...)
hoist := make([]bool, len(body.Scope.VarDecls))
for i, varDecl := range body.Scope.VarDecls {
hoist[i] = true
if varDecl.InForInOf {
continue
}

hoists[i] = make([]bool, len(body.Scope.VarDecls))
for j, varDecl := range body.Scope.VarDecls {
if i == j {
hoists[i][j] = false
continue
}

score := 4 // "var "
hoists[i][j] = true
if !varDecl.InForInOf {
// variable names in for-in or for-of cannot be removed
n := 0 // total number of vars with decls
nArrays := 0 // of which lhs arrays
nObjects := 0 // of which lhs objects
nNames := 0 // length of var names and commas
hasDefinitions := false
for k, item := range varDecl.List {
if item.Default != nil {
// move arrays/objects to the front (saves a space)
if _, ok := item.Binding.(*js.BindingObject); ok {
if k != 0 && nArrays == 0 && nObjects == 0 {
varDecl.List[0], varDecl.List[k] = varDecl.List[k], varDecl.List[0]
}
nObjects++
} else if _, ok := item.Binding.(*js.BindingArray); ok {
if k != 0 && nArrays == 0 && nObjects == 0 {
varDecl.List[0], varDecl.List[k] = varDecl.List[k], varDecl.List[0]
}
nArrays++
}

refs := bindingVars(item.Binding)
CountNamesLoop:
for _, ref := range refs {
for _, v := range refsTarget {
if ref == v {
// already exists in target
continue CountNamesLoop
}
}

// declaration separate from assignment will copy var name + comma
nNames += m.varNameLen(ref)
refsTarget = append(refsTarget, ref)
}
hasDefinitions = true
n++
// variable names in for-in or for-of cannot be removed
n := 0 // total number of vars with decls
score := 3 // "var"
nArrays := 0 // of which lhs arrays
nObjects := 0 // of which lhs objects
hasDefinitions := false
for j, item := range varDecl.List {
if item.Default != nil {
// move arrays/objects to the front (saves a space)
if _, ok := item.Binding.(*js.BindingObject); ok {
if j != 0 && nArrays == 0 && nObjects == 0 {
varDecl.List[0], varDecl.List[j] = varDecl.List[j], varDecl.List[0]
}
nObjects++
} else if _, ok := item.Binding.(*js.BindingArray); ok {
if j != 0 && nArrays == 0 && nObjects == 0 {
varDecl.List[0], varDecl.List[j] = varDecl.List[j], varDecl.List[0]
}
nArrays++
}
if hasDefinitions {
score -= nNames // copy var names and commas to target
} else if varDecl.InFor {
score-- // semicolon can be reused
}
if nObjects != 0 && !varDecl.InFor && nObjects == n {
score -= 2 // required parenthesis around braces
}
if nArrays != 0 || nObjects != 0 {
score-- // space after var disappears
}
if score < 0 {
// don't hoist if it increases the amount of characters
hoists[i][j] = false
score = 0
}
score -= m.countHoistLength(item.Binding) // var names and commas
hasDefinitions = true
n++
}
scores[i] += score
}
if scores[best] < scores[i] || body.Scope.VarDecls[best].InForInOf {
// select var decl with the most savings if hoist target
if nArrays == 0 && nObjects == 0 {
score++ // required space after var
}
if !hasDefinitions && varDecl.InFor {
score-- // semicolon can be reused
}
if nObjects != 0 && !varDecl.InFor && nObjects == n {
// required parenthesis around braces to not confound it with a block statement
score -= 2
}
if score < scores[best] || body.Scope.VarDecls[best].InForInOf {
// select var decl that reduces the least when hoist target
best = i
}
if score < 0 {
// don't hoist if it increases the amount of characters
hoist[i] = false
}
scores[i] = score
}
if scores[best] < 0 || body.Scope.VarDecls[best].InForInOf {
if body.Scope.VarDecls[best].InForInOf {
// no savings possible
return
}

hoist := hoists[best]
decl := body.Scope.VarDecls[best]
if 10000 < len(decl.List) {
return
}
hoist[best] = false

// get original declarations
orig := []*js.Var{}
Expand Down

0 comments on commit 75e7703

Please sign in to comment.