Skip to content

Commit

Permalink
JS: reorder var declaration without default to improve GZIP compression
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Apr 3, 2022
1 parent e7fcbe9 commit 0b7413d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 74 deletions.
23 changes: 23 additions & 0 deletions js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,29 @@ func (m *jsMinifier) minifyVarDecl(decl *js.VarDecl, onlyDefines bool) {
} else {
m.write(decl.TokenType.Bytes())
m.writeSpaceBeforeIdent()

// move single var decls forward and order for GZIP optimization
start := 0
if _, ok := decl.List[0].Binding.(*js.Var); !ok {
start++
}
for i, item := range decl.List {
if v, ok := item.Binding.(*js.Var); ok && item.Default == nil && len(v.Data) == 1 {
for j := start; j < len(decl.List); j++ {
if v2, ok := decl.List[j].Binding.(*js.Var); ok && decl.List[j].Default == nil && len(v2.Data) == 1 {
if m.renamer.identOrder[v2.Data[0]] < m.renamer.identOrder[v.Data[0]] {
continue
} else if m.renamer.identOrder[v2.Data[0]] == m.renamer.identOrder[v.Data[0]] {
break
}
}
decl.List = append(decl.List[:i], decl.List[i+1:]...)
decl.List = append(decl.List[:j], append([]js.BindingElement{item}, decl.List[j:]...)...)
break
}
}
}

for i, item := range decl.List {
if i != 0 {
m.write(commaBytes)
Expand Down
37 changes: 19 additions & 18 deletions js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestJS(t *testing.T) {
{`/a/ instanceof b`, `/a/ instanceof b`},
{`[a] instanceof b`, `[a]instanceof b`},
{`let a = 5;a`, `let a=5;a`},
{`let a = 5,b;a,b`, `let a=5,b;a,b`},
{`let a = 5,b;a,b`, `let b,a=5;a,b`},
{`let a,b = 5;a,b`, `let a,b=5;a,b`},
{`function a(){}`, `function a(){}`},
{`function a(b){b}`, `function a(b){b}`},
Expand Down Expand Up @@ -271,7 +271,6 @@ func TestJS(t *testing.T) {
{`var a;for(var a=1;a;a++);`, `for(var a=1;a;a++);`},
{`var [,,a,,]=b`, `var[,,a]=b`},
{`var [,,a,,...c]=b`, `var[,,a,,...c]=b`},
//{`var {...a}=c;for(var {...b}=d;b;b++);`, `for(var{...a}=c,{...b}=d;b;b++);`}, // TODO
{`const a=3;for(const b=0;b;b++);a`, `const a=3;for(const b=0;b;b++);a`},
{`var a;for(let b=0;b;b++);a`, `var a;for(let b=0;b;b++);a`},
{`var [a,]=[b,]`, `var[a]=[b]`},
Expand All @@ -296,10 +295,10 @@ func TestJS(t *testing.T) {
{`var a;var b=6;a=7;b`, `var b=6,a=7;b`}, // swap declaration order to maintain definition order
{`var a=5;var b=6;a=7,b`, `var a=5,b=6;a=7,b`},
{`var a;var b=6;a,b,z=7`, `var a,b=6;a,b,z=7`},
{`for(var a=6,b=7;;);var c=8;a,b,c`, `for(var a=6,b=7,c;;);c=8,a,b,c`},
{`for(var c;b;){let a=8;a};var a;a`, `for(var c,a;b;){let a=8;a}a`},
{`for(var a=6,b=7;;);var c=8;a,b,c`, `for(var c,a=6,b=7;;);c=8,a,b,c`},
{`for(var c;b;){let a=8;a};var a;a`, `for(var a,c;b;){let a=8;a}a`},
{`for(;b;){let a=8;a};var a;var b;a`, `for(var a,b;b;){let a=8;a}a`},
{`var a=1,b=2;while(c);var d=3,e=4;a,b,d,e`, `for(var a=1,b=2,d,e;c;);d=3,e=4,a,b,d,e`},
{`var a=1,b=2;while(c);var d=3,e=4;a,b,d,e`, `for(var d,e,a=1,b=2;c;);d=3,e=4,a,b,d,e`},
{`var z;var [a,b=5,,...c]=[d,e,...f];z`, `var[a,b=5,,...c]=[d,e,...f],z;z`},
{`var z;var {a,b=5,[5+8]:c,...d}={d,e,...f};z`, `var{a,b=5,[5+8]:c,...d}={d,e,...f},z;z`},
{`var z;z;var [a,b=5,,...c]=[d,e,...f];a`, `z;var[a,b=5,,...c]=[d,e,...f],z;a`},
Expand Down Expand Up @@ -327,7 +326,7 @@ func TestJS(t *testing.T) {
{`var{a}=x;f();var b,d=e;`, `var{a}=x,b,d;f(),d=e`},
{`var{a}=x;f();var b=c,d=e;`, `var{a}=x,b,d;f(),b=c,d=e`},
{`var{a}=x;f();var[b]=c,d=e;`, `var{a}=x;f();var[b]=c,d=e`},
//{`var{a}=x;f();var{b}=y`, `var{a}=x,b;f(),{b}=y`}, // TODO
// {`var{a}=x;f();var{b}=y`, `var{a}=x,b;f(),{b}=y`}, // we can't know that {b} doesn't require parentheses
{`var a=0;a=1`, `var a=0;a=1`},
{`var a,b;a=b`, `var b,a=b`},
{`var a,b=c;a=b`, `var b=c,a=b`},
Expand All @@ -336,8 +335,10 @@ func TestJS(t *testing.T) {
{`var a,b;a=1,b=2,c=3`, `var a=1,b=2;c=3`},
{`var a=[];var b={};var c=d,e=f`, `var a=[],b={},c=d,e=f`},
{`var a=[];var b={};var c=d,e=f`, `var a=[],b={},c=d,e=f`},
{`var a=[];var b;var c,e=f`, `var a=[],b,c,e=f`},
{`var a=[];f();var b;f();var c;f();var e=f`, `var a=[],b,c,e;f(),f(),f(),e=f`},
{`var a=[];var b;var c,e=f`, `var b,c,a=[],e=f`},
{`var a=[];f();var b;f();var c;f();var e=f`, `var b,c,e,a=[];f(),f(),f(),e=f`},
{`var {...a}=c;for(var {...b}=d;b;b++);`, `for(var{...a}=c,{...b}=d;b;b++);`},

// TODO: test for variables renaming (first rename, then merge vars)

// function and method declarations
Expand Down Expand Up @@ -626,8 +627,8 @@ func TestJS(t *testing.T) {
{`var a=5;for(;a;)c()`, `for(var a=5;a;)c()`},
{`let a=5;for(;a;)c()`, `let a=5;for(;a;)c()`},
{`var a=b in c;for(;a;)c()`, `for(var a=(b in c);a;)c()`},
{`var a=5;for(var a=6,b;b;)c()`, `var a=5,b;for(a=6;b;)c()`},
{`var a=5;for(var a,b;b;)c()`, `for(var a=5,b;b;)c()`},
{`var a=5;for(var a=6,b;b;)c()`, `var b,a=5;for(a=6;b;)c()`},
{`var a=5;for(var a,b;b;)c()`, `for(var b,a=5;b;)c()`},
//{`var a=5;for(var b=6,c=7;;);`, `for(var a=5,b=6,c=7;;);`}, // TODO
{`var a=5;while(a)c()`, `for(var a=5;a;)c()`},
{`var a=5;while(a){c()}`, `for(var a=5;a;)c()`},
Expand Down Expand Up @@ -698,8 +699,8 @@ func TestJS(t *testing.T) {
{`0xeb00000000`, `0xeb00000000`}, // go-fuzz
{`export{a,}`, `export{a,}`}, // go-fuzz
{`var D;var{U,W,W}=y`, `var{U,W,W}=y,D`}, // go-fuzz
{`var A;var b=(function(){var e;})=c,d`, `var A,b=function(){var e}=c,d`}, // go-fuzz
{"var a=/\\s?auto?\\s?/i\nvar b;a,b", "var a=/\\s?auto?\\s?/i,b;a,b"}, // #14
{`var A;var b=(function(){var e;})=c,d`, `var d,A,b=function(){var e}=c`}, // go-fuzz
{"var a=/\\s?auto?\\s?/i\nvar b;a,b", "var b,a=/\\s?auto?\\s?/i;a,b"}, // #14
{"false`string`", "(!1)`string`"}, // #181
{"x / /\\d+/.exec(s)[0]", "x/ /\\d+/.exec(s)[0]"}, // #183
{`()=>{return{a}}`, `()=>({a})`}, // #333
Expand All @@ -720,7 +721,7 @@ func TestJS(t *testing.T) {
}

m := minify.New()
o := Minifier{KeepVarNames: true}
o := Minifier{KeepVarNames: true, useAlphabetVarNames: true}
for _, tt := range jsTests {
t.Run(tt.js, func(t *testing.T) {
r := bytes.NewBufferString(tt.js)
Expand Down Expand Up @@ -755,8 +756,8 @@ func TestJSVarRenaming(t *testing.T) {
{`!function(){var x=function(){return y};const y=5;x,y}`, `!function(){var b=function(){return a};const a=5;b,a}`},
{`!function(){if(1){const x=5;x;5}var y=function(){return x};y}`, `!function(){if(1){const a=5;a,5}var a=function(){return x};a}`},
{`!function(){var x=function(){return y};x;if(1){const y=5;y;5}}`, `!function(){var a=function(){return y};if(a,1){const a=5;a,5}}`},
{`!function(){var x=function(){return y};x;if(z)var y=5}`, `!function(){var a=function(){return b},b;a,z&&(b=5)}`},
{`!function(){var x=function(){return y};x;if(z){var y=5;5}}`, `!function(){var a=function(){return b},b;a,z&&(b=5,5)}`},
{`!function(){var x=function(){return y};x;if(z)var y=5}`, `!function(){var b,a=function(){return b};a,z&&(b=5)}`},
{`!function(){var x=function(){return y};x;if(z){var y=5;5}}`, `!function(){var b,a=function(){return b};a,z&&(b=5,5)}`},
{`!function(){var x,y,z=(x,y)=>x+y;x,y,z}`, `!function(){var a,b,c=(a,b)=>a+b;a,b,c}`},
{`!function(){var await;print({await});}`, `!function(){var a;print({await:a})}`},
{`function a(){var name; return {name}}`, `function a(){var a;return{name:a}}`},
Expand All @@ -766,9 +767,9 @@ func TestJSVarRenaming(t *testing.T) {
{`function a(b){function c(d){b[d]}}`, `function a(a){function b(b){a[b]}}`},
{`function r(o){function l(t){if(!z[t]){if(!o[t]);}}}`, `function r(a){function b(b){z[b]||!a[b]}}`},
{`!function(a){a;for(var b=0;;);};var c;var d;`, `!function(a){a;for(var b=0;;);};var c,d`},
{`!function(){var b;b;{(T=x),T}{var T}}`, `!function(){var b,a;b,a=x,a}`},
{`var T;T;!function(){var b;b;{(T=x),T}{var T}}`, `var T;T,!function(){var b,a;b,a=x,a}`},
{`!function(){let a=b,b=c,c=d,d=e,e=f,f=g,g=h,h=a,j;for(let i=0;;)j=4}`, `!function(){let a=b,b=c,c=d,d=e,e=f,f=g,g=h,h=a,i;for(let a=0;;)i=4}`},
{`!function(){var b;b;{(T=x),T}{var T}}`, `!function(){var a,b;b,a=x,a}`},
{`var T;T;!function(){var b;b;{(T=x),T}{var T}}`, `var T;T,!function(){var a,b;b,a=x,a}`},
{`!function(){let a=b,b=c,c=d,d=e,e=f,f=g,g=h,h=a,j;for(let i=0;;)j=4}`, `!function(){let i,a=b,b=c,c=d,d=e,e=f,f=g,g=h,h=a;for(let a=0;;)i=4}`},
{`function a(){var name;with(z){name}} function b(){var name;name}`, `function a(){var name;with(z)name}function b(){var a;a}`},
{`!function(){var name;{name;!function(){name;var other;other}}}`, `!function(){var a;a,!function(){a;var b;b}}`},
{`name=function(){var a001,a002,a003,a004,a005,a006,a007,a008,a009,a010,a011,a012,a013,a014,a015,a016,a017,a018,a019,a020,a021,a022,a023,a024,a025,a026,a027,a028,a029,a030,a031,a032,a033,a034,a035,a036,a037,a038,a039,a040,a041,a042,a043,a044,a045,a046,a047,a048,a049,a050,a051,a052,a053,a054,a055,a056,a057,a058,a059,a060,a061,a062,a063,a064,a065,a066,a067,a068,a069,a070,a071,a072,a073,a074,a075,a076,a077,a078,a079,a080,a081,a082,a083,a084,a085,a086,a087,a088,a089,a090,a091,a092,a093,a094,a095,a096,a097,a098,a099,a100,a101,a102,a103,a104,a105,a106,a107,a108,a109,a110,a111,a112,a113,a114,a115,a116,a117,a118,a119;a001,a002,a003,a004,a005,a006,a007,a008,a009,a010,a011,a012,a013,a014,a015,a016,a017,a018,a019,a020,a021,a022,a023,a024,a025,a026,a027,a028,a029,a030,a031,a032,a033,a034,a035,a036,a037,a038,a039,a040,a041,a042,a043,a044,a045,a046,a047,a048,a049,a050,a051,a052,a053,a054,a055,a056,a057,a058,a059,a060,a061,a062,a063,a064,a065,a066,a067,a068,a069,a070,a071,a072,a073,a074,a075,a076,a077,a078,a079,a080,a081,a082,a083,a084,a085,a086,a087,a088,a089,a090,a091,a092,a093,a094,a095,a096,a097,a098,a099,a100,a101,a102,a103,a104,a105,a106,a107,a108,a109,a110,a111,a112,a113,a114,a115,a116,a117,a118,a119}`,
Expand Down
2 changes: 1 addition & 1 deletion js/stmtlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (m *jsMinifier) optimizeStmtList(list []js.IStmt, blockType blockType) []js
}
} else if left, ok := list[i-1].(*js.VarDecl); ok {
if right, ok := list[i].(*js.VarDecl); ok && left.TokenType == right.TokenType {
// merge const and let declarations
// merge const and let declarations, or non-hoisted var declarations
right.List = append(left.List, right.List...)
j--
} else if left.TokenType == js.VarToken {
Expand Down
99 changes: 44 additions & 55 deletions js/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type renamer struct {
identStart []byte
identContinue []byte
identOrder map[byte]int
reserved map[string]struct{}
rename bool
}
Expand All @@ -26,9 +27,14 @@ func newRenamer(rename, useCharFreq bool) *renamer {
identStart = []byte("etnsoiarclduhmfpgvbjy_wOxCEkASMFTzDNLRPHIBV$WUKqYGXQZJ")
identContinue = []byte("etnsoiarcldu14023hm8f6pg57v9bjy_wOxCEkASMFTzDNLRPHIBV$WUKqYGXQZJ")
}
identOrder := map[byte]int{}
for i, c := range identStart {
identOrder[c] = i
}
return &renamer{
identStart: identStart,
identContinue: identContinue,
identOrder: identOrder,
reserved: reserved,
rename: rename,
}
Expand Down Expand Up @@ -173,62 +179,41 @@ func bindingVars(ibinding js.IBinding) (vs []*js.Var) {
func addDefinition(decl *js.VarDecl, binding js.IBinding, value js.IExpr, forward bool) bool {
// see if not already defined in variable declaration list
// if forward is set, binding=value comes before decl, otherwise the reverse holds true
if vbind, ok := binding.(*js.Var); ok {
for i := 0; i < len(decl.List); i++ {
idx := i
if forward {
// reverse lookup order in destinations
idx = len(decl.List) - i - 1
}
vars := bindingVars(binding)
if len(vars) == 0 {
return false
}

item := decl.List[idx]
// find variables in destination
for _, vbind := range vars {
for _, item := range decl.List {
if v, ok := item.Binding.(*js.Var); ok && v == vbind {
if item.Default != nil {
return false
}
item.Default = value
decl.List = append(decl.List[:idx], decl.List[idx+1:]...)
if forward {
decl.List = append([]js.BindingElement{item}, decl.List...)
} else {
decl.List = append(decl.List, item)
}
return true
break
}
}
return false
}
return false

//vars := bindingRefs(binding)
//if len(vars) == 0 {
// return false
//}
//locs := make([]int, len(vars))
//for i, vdef := range vars {
// locs[i] = -1
// for loc, item := range decl.List {
// if v, ok := item.Binding.(*js.Var); ok && v == vdef {
// locs[i] = loc
// break
// }
// }
// if locs[i] == -1 {
// return false // cannot (probably) happen if we hoist variables
// }
//}
//sort.Ints(locs)
//if locs[0] != 0 {
// decl.List[0], decl.List[locs[0]] = decl.List[locs[0]], decl.List[0]
//}
//decl.List[0].Binding = binding
//decl.List[0].Default = value
//for i := len(locs) - 1; 1 <= i; i-- {
// if locs[i] != locs[i-1] { // ignore duplicates, otherwise remove items from hoisted var declaration
// decl.List = append(decl.List[:locs[i]], decl.List[locs[i]+1:]...)
// }
//}
//return true
// remove variables in destination
for _, vbind := range vars {
for i, item := range decl.List {
if v, ok := item.Binding.(*js.Var); ok && v == vbind {
decl.List = append(decl.List[:i], decl.List[i+1:]...)
break
}
}
}

// add declaration to destination
item := js.BindingElement{Binding: binding, Default: value}
if forward {
decl.List = append([]js.BindingElement{item}, decl.List...)
} else {
decl.List = append(decl.List, item)
}
return true
}

func mergeVarDecls(dst, src *js.VarDecl, forward bool) bool {
Expand Down Expand Up @@ -301,7 +286,7 @@ func (m *jsMinifier) countHoistLength(ibinding js.IBinding) int {

n := 0
for _, v := range bindingVars(ibinding) {
n += len(v.Data) + 1 // +1 for the comma
n += len(v.Data) + 1 // +1 for the comma when added to other declaration
}
return n
}
Expand Down Expand Up @@ -420,12 +405,14 @@ func (m *jsMinifier) hoistVars(body *js.BlockStmt) {
if _, ok := item.Binding.(*js.Var); !ok {
if i != 0 {
interferes := false
InterferenceLoop:
for _, ref := range refs {
for _, v := range prevRefs {
if ref == v {
interferes = true
break InterferenceLoop
if item.Default != nil {
InterferenceLoop:
for _, ref := range refs {
for _, v := range prevRefs {
if ref == v {
interferes = true
break InterferenceLoop
}
}
}
}
Expand All @@ -437,7 +424,9 @@ func (m *jsMinifier) hoistVars(body *js.BlockStmt) {
break BeginArrayObject
}
}
prevRefs = append(prevRefs, refs...)
if item.Default != nil {
prevRefs = append(prevRefs, refs...)
}
}
}
}

0 comments on commit 0b7413d

Please sign in to comment.