12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271 |
- // Package js minifies ECMAScript 2021 following the language specification at https://tc39.es/ecma262/.
- package js
- import (
- "bytes"
- "io"
- "github.com/tdewolff/minify/v2"
- "github.com/tdewolff/parse/v2"
- "github.com/tdewolff/parse/v2/js"
- )
- type blockType int
- const (
- defaultBlock blockType = iota
- functionBlock
- iterationBlock
- )
- // Minifier is a JS minifier.
- type Minifier struct {
- Precision int // number of significant digits
- KeepVarNames bool
- useAlphabetVarNames bool
- Version int
- }
- func (o *Minifier) minVersion(version int) bool {
- return o.Version == 0 || version <= o.Version
- }
- // Minify minifies JS data, it reads from r and writes to w.
- func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
- return (&Minifier{}).Minify(m, w, r, params)
- }
- // Minify minifies JS data, it reads from r and writes to w.
- func (o *Minifier) Minify(_ *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
- z := parse.NewInput(r)
- ast, err := js.Parse(z, js.Options{
- WhileToFor: true,
- Inline: params != nil && params["inline"] == "1",
- })
- if err != nil {
- return err
- }
- m := &jsMinifier{
- o: o,
- w: w,
- renamer: newRenamer(!o.KeepVarNames, !o.useAlphabetVarNames),
- }
- m.hoistVars(&ast.BlockStmt)
- ast.List = optimizeStmtList(ast.List, functionBlock)
- for _, item := range ast.List {
- m.writeSemicolon()
- m.minifyStmt(item)
- }
- if _, err := w.Write(nil); err != nil {
- return err
- }
- return nil
- }
- type expectExpr int
- const (
- expectAny expectExpr = iota
- expectExprStmt // in statement
- expectExprBody // in arrow function body
- )
- type jsMinifier struct {
- o *Minifier
- w io.Writer
- prev []byte
- needsSemicolon bool // write a semicolon if required
- needsSpace bool // write a space if next token is an identifier
- expectExpr expectExpr // avoid ambiguous syntax such as an expression starting with function
- groupedStmt bool // avoid ambiguous syntax by grouping the expression statement
- inFor bool
- spaceBefore byte
- renamer *renamer
- }
- func (m *jsMinifier) write(b []byte) {
- // 0 < len(b)
- if m.needsSpace && js.IsIdentifierContinue(b) || m.spaceBefore == b[0] {
- m.w.Write(spaceBytes)
- }
- m.w.Write(b)
- m.prev = b
- m.needsSpace = false
- m.expectExpr = expectAny
- m.spaceBefore = 0
- }
- func (m *jsMinifier) writeSpaceAfterIdent() {
- // space after identifier and after regular expression (to prevent confusion with its tag)
- if js.IsIdentifierEnd(m.prev) || 1 < len(m.prev) && m.prev[0] == '/' {
- m.w.Write(spaceBytes)
- }
- }
- func (m *jsMinifier) writeSpaceBeforeIdent() {
- m.needsSpace = true
- }
- func (m *jsMinifier) writeSpaceBefore(c byte) {
- m.spaceBefore = c
- }
- func (m *jsMinifier) requireSemicolon() {
- m.needsSemicolon = true
- }
- func (m *jsMinifier) writeSemicolon() {
- if m.needsSemicolon {
- m.w.Write(semicolonBytes)
- m.needsSemicolon = false
- m.needsSpace = false
- }
- }
- func (m *jsMinifier) minifyStmt(i js.IStmt) {
- switch stmt := i.(type) {
- case *js.ExprStmt:
- m.expectExpr = expectExprStmt
- m.minifyExpr(stmt.Value, js.OpExpr)
- if m.groupedStmt {
- m.write(closeParenBytes)
- m.groupedStmt = false
- }
- m.requireSemicolon()
- case *js.VarDecl:
- m.minifyVarDecl(stmt, false)
- m.requireSemicolon()
- case *js.IfStmt:
- hasIf := !isEmptyStmt(stmt.Body)
- hasElse := !isEmptyStmt(stmt.Else)
- if !hasIf && !hasElse {
- break
- }
- m.write(ifOpenBytes)
- m.minifyExpr(stmt.Cond, js.OpExpr)
- m.write(closeParenBytes)
- if !hasIf && hasElse {
- m.requireSemicolon()
- } else if hasIf {
- if hasElse && endsInIf(stmt.Body) {
- // prevent: if(a){if(b)c}else d; => if(a)if(b)c;else d;
- m.write(openBraceBytes)
- m.minifyStmt(stmt.Body)
- m.write(closeBraceBytes)
- m.needsSemicolon = false
- } else {
- m.minifyStmt(stmt.Body)
- }
- }
- if hasElse {
- m.writeSemicolon()
- m.write(elseBytes)
- m.writeSpaceBeforeIdent()
- m.minifyStmt(stmt.Else)
- }
- case *js.BlockStmt:
- m.renamer.renameScope(stmt.Scope)
- m.minifyBlockStmt(stmt)
- case *js.ReturnStmt:
- m.write(returnBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(stmt.Value, js.OpExpr)
- m.requireSemicolon()
- case *js.LabelledStmt:
- m.write(stmt.Label)
- m.write(colonBytes)
- m.minifyStmtOrBlock(stmt.Value, defaultBlock)
- case *js.BranchStmt:
- m.write(stmt.Type.Bytes())
- if stmt.Label != nil {
- m.write(spaceBytes)
- m.write(stmt.Label)
- }
- m.requireSemicolon()
- case *js.WithStmt:
- m.write(withOpenBytes)
- m.minifyExpr(stmt.Cond, js.OpExpr)
- m.write(closeParenBytes)
- m.minifyStmtOrBlock(stmt.Body, defaultBlock)
- case *js.DoWhileStmt:
- m.write(doBytes)
- m.writeSpaceBeforeIdent()
- m.minifyStmtOrBlock(stmt.Body, iterationBlock)
- m.writeSemicolon()
- m.write(whileOpenBytes)
- m.minifyExpr(stmt.Cond, js.OpExpr)
- m.write(closeParenBytes)
- case *js.WhileStmt:
- m.write(whileOpenBytes)
- m.minifyExpr(stmt.Cond, js.OpExpr)
- m.write(closeParenBytes)
- m.minifyStmtOrBlock(stmt.Body, iterationBlock)
- case *js.ForStmt:
- stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
- m.renamer.renameScope(stmt.Body.Scope)
- m.write(forOpenBytes)
- m.inFor = true
- if decl, ok := stmt.Init.(*js.VarDecl); ok {
- m.minifyVarDecl(decl, true)
- } else {
- m.minifyExpr(stmt.Init, js.OpLHS)
- }
- m.inFor = false
- m.write(semicolonBytes)
- m.minifyExpr(stmt.Cond, js.OpExpr)
- m.write(semicolonBytes)
- m.minifyExpr(stmt.Post, js.OpExpr)
- m.write(closeParenBytes)
- m.minifyBlockAsStmt(stmt.Body)
- case *js.ForInStmt:
- stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
- m.renamer.renameScope(stmt.Body.Scope)
- m.write(forOpenBytes)
- m.inFor = true
- if decl, ok := stmt.Init.(*js.VarDecl); ok {
- m.minifyVarDecl(decl, false)
- } else {
- m.minifyExpr(stmt.Init, js.OpLHS)
- }
- m.inFor = false
- m.writeSpaceAfterIdent()
- m.write(inBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(stmt.Value, js.OpExpr)
- m.write(closeParenBytes)
- m.minifyBlockAsStmt(stmt.Body)
- case *js.ForOfStmt:
- stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
- m.renamer.renameScope(stmt.Body.Scope)
- if stmt.Await {
- m.write(forAwaitOpenBytes)
- } else {
- m.write(forOpenBytes)
- }
- m.inFor = true
- if decl, ok := stmt.Init.(*js.VarDecl); ok {
- m.minifyVarDecl(decl, false)
- } else {
- m.minifyExpr(stmt.Init, js.OpLHS)
- }
- m.inFor = false
- m.writeSpaceAfterIdent()
- m.write(ofBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(stmt.Value, js.OpAssign)
- m.write(closeParenBytes)
- m.minifyBlockAsStmt(stmt.Body)
- case *js.SwitchStmt:
- m.write(switchOpenBytes)
- m.minifyExpr(stmt.Init, js.OpExpr)
- m.write(closeParenOpenBracketBytes)
- m.needsSemicolon = false
- for i, _ := range stmt.List {
- stmt.List[i].List = optimizeStmtList(stmt.List[i].List, defaultBlock)
- }
- m.renamer.renameScope(stmt.Scope)
- for _, clause := range stmt.List {
- m.writeSemicolon()
- m.write(clause.TokenType.Bytes())
- if clause.Cond != nil {
- m.writeSpaceBeforeIdent()
- m.minifyExpr(clause.Cond, js.OpExpr)
- }
- m.write(colonBytes)
- for _, item := range clause.List {
- m.writeSemicolon()
- m.minifyStmt(item)
- }
- }
- m.write(closeBraceBytes)
- m.needsSemicolon = false
- case *js.ThrowStmt:
- m.write(throwBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(stmt.Value, js.OpExpr)
- m.requireSemicolon()
- case *js.TryStmt:
- m.write(tryBytes)
- stmt.Body.List = optimizeStmtList(stmt.Body.List, defaultBlock)
- m.renamer.renameScope(stmt.Body.Scope)
- m.minifyBlockStmt(stmt.Body)
- if stmt.Catch != nil {
- m.write(catchBytes)
- stmt.Catch.List = optimizeStmtList(stmt.Catch.List, defaultBlock)
- if v, ok := stmt.Binding.(*js.Var); ok && v.Uses == 1 && m.o.minVersion(2019) {
- stmt.Catch.Scope.Declared = stmt.Catch.Scope.Declared[1:]
- stmt.Binding = nil
- }
- m.renamer.renameScope(stmt.Catch.Scope)
- if stmt.Binding != nil {
- m.write(openParenBytes)
- m.minifyBinding(stmt.Binding)
- m.write(closeParenBytes)
- }
- m.minifyBlockStmt(stmt.Catch)
- }
- if stmt.Finally != nil {
- m.write(finallyBytes)
- stmt.Finally.List = optimizeStmtList(stmt.Finally.List, defaultBlock)
- m.renamer.renameScope(stmt.Finally.Scope)
- m.minifyBlockStmt(stmt.Finally)
- }
- case *js.FuncDecl:
- m.minifyFuncDecl(stmt, false)
- case *js.ClassDecl:
- m.minifyClassDecl(stmt)
- case *js.DebuggerStmt:
- m.write(debuggerBytes)
- m.requireSemicolon()
- case *js.EmptyStmt:
- case *js.ImportStmt:
- if stmt.Default != nil || stmt.List == nil || 0 < len(stmt.List) {
- m.write(importBytes)
- if stmt.Default != nil {
- m.write(spaceBytes)
- m.write(stmt.Default)
- if stmt.List != nil {
- m.write(commaBytes)
- } else if stmt.Default != nil {
- m.write(spaceBytes)
- }
- }
- if len(stmt.List) == 1 && len(stmt.List[0].Name) == 1 && stmt.List[0].Name[0] == '*' {
- m.writeSpaceBeforeIdent()
- m.minifyAlias(stmt.List[0])
- if stmt.Default != nil || len(stmt.List) != 0 {
- m.write(spaceBytes)
- }
- } else if stmt.List != nil {
- m.write(openBraceBytes)
- for i, item := range stmt.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyAlias(item)
- }
- m.write(closeBraceBytes)
- }
- if stmt.Default != nil || stmt.List != nil {
- m.write(fromBytes)
- }
- m.write(minifyString(stmt.Module, false))
- m.requireSemicolon()
- }
- case *js.ExportStmt:
- m.write(exportBytes)
- if stmt.Decl != nil {
- if stmt.Default {
- m.write(spaceDefaultBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(stmt.Decl, js.OpAssign)
- _, isHoistable := stmt.Decl.(*js.FuncDecl)
- _, isClass := stmt.Decl.(*js.ClassDecl)
- if !isHoistable && !isClass {
- m.requireSemicolon()
- }
- } else {
- m.writeSpaceBeforeIdent()
- m.minifyStmt(stmt.Decl.(js.IStmt)) // can only be variable, function, or class decl
- }
- } else {
- if len(stmt.List) == 1 && (len(stmt.List[0].Name) == 1 && stmt.List[0].Name[0] == '*' || stmt.List[0].Name == nil && len(stmt.List[0].Binding) == 1 && stmt.List[0].Binding[0] == '*') {
- m.writeSpaceBeforeIdent()
- m.minifyAlias(stmt.List[0])
- if stmt.Module != nil && stmt.List[0].Name != nil {
- m.write(spaceBytes)
- }
- } else if 0 < len(stmt.List) {
- m.write(openBraceBytes)
- for i, item := range stmt.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyAlias(item)
- }
- m.write(closeBraceBytes)
- }
- if stmt.Module != nil {
- m.write(fromBytes)
- m.write(minifyString(stmt.Module, false))
- }
- m.requireSemicolon()
- }
- case *js.DirectivePrologueStmt:
- stmt.Value[0] = '"'
- stmt.Value[len(stmt.Value)-1] = '"'
- m.write(stmt.Value)
- m.requireSemicolon()
- case *js.Comment:
- // bang comment
- m.write(stmt.Value)
- if stmt.Value[1] == '/' {
- m.write(newlineBytes)
- }
- }
- }
- func (m *jsMinifier) minifyBlockStmt(stmt *js.BlockStmt) {
- m.write(openBraceBytes)
- m.needsSemicolon = false
- for _, item := range stmt.List {
- m.writeSemicolon()
- m.minifyStmt(item)
- }
- m.write(closeBraceBytes)
- m.needsSemicolon = false
- }
- func (m *jsMinifier) minifyBlockAsStmt(blockStmt *js.BlockStmt) {
- // minify block when statement is expected, i.e. semicolon if empty or remove braces for single statement
- // assume we already renamed the scope
- hasLexicalVars := false
- for _, v := range blockStmt.Scope.Declared[blockStmt.Scope.NumForDecls:] {
- if v.Decl == js.LexicalDecl {
- hasLexicalVars = true
- break
- }
- }
- if 1 < len(blockStmt.List) || hasLexicalVars {
- m.minifyBlockStmt(blockStmt)
- } else if len(blockStmt.List) == 1 {
- m.minifyStmt(blockStmt.List[0])
- } else {
- m.write(semicolonBytes)
- m.needsSemicolon = false
- }
- }
- func (m *jsMinifier) minifyStmtOrBlock(i js.IStmt, blockType blockType) {
- // minify stmt or a block
- if blockStmt, ok := i.(*js.BlockStmt); ok {
- blockStmt.List = optimizeStmtList(blockStmt.List, blockType)
- m.renamer.renameScope(blockStmt.Scope)
- m.minifyBlockAsStmt(blockStmt)
- } else {
- // optimizeStmtList can in some cases expand one stmt to two shorter stmts
- list := optimizeStmtList([]js.IStmt{i}, blockType)
- if len(list) == 1 {
- m.minifyStmt(list[0])
- } else if len(list) == 0 {
- m.write(semicolonBytes)
- m.needsSemicolon = false
- } else {
- m.minifyBlockStmt(&js.BlockStmt{List: list, Scope: js.Scope{}})
- }
- }
- }
- func (m *jsMinifier) minifyAlias(alias js.Alias) {
- if alias.Name != nil {
- if alias.Name[0] == '"' || alias.Name[0] == '\'' {
- m.write(minifyString(alias.Name, false))
- } else {
- m.write(alias.Name)
- }
- if !bytes.Equal(alias.Name, starBytes) {
- m.write(spaceBytes)
- }
- m.write(asSpaceBytes)
- }
- if alias.Binding != nil {
- if alias.Binding[0] == '"' || alias.Binding[0] == '\'' {
- m.write(minifyString(alias.Binding, false))
- } else {
- m.write(alias.Binding)
- }
- }
- }
- func (m *jsMinifier) minifyParams(params js.Params, removeUnused bool) {
- // remove unused parameters from the end
- j := len(params.List)
- if removeUnused && params.Rest == nil {
- for ; 0 < j; j-- {
- if v, ok := params.List[j-1].Binding.(*js.Var); !ok || ok && 1 < v.Uses {
- break
- }
- }
- }
- m.write(openParenBytes)
- for i, item := range params.List[:j] {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyBindingElement(item)
- }
- if params.Rest != nil {
- if len(params.List) != 0 {
- m.write(commaBytes)
- }
- m.write(ellipsisBytes)
- m.minifyBinding(params.Rest)
- }
- m.write(closeParenBytes)
- }
- func (m *jsMinifier) minifyArguments(args js.Args) {
- m.write(openParenBytes)
- for i, item := range args.List {
- if i != 0 {
- m.write(commaBytes)
- }
- if item.Rest {
- m.write(ellipsisBytes)
- }
- m.minifyExpr(item.Value, js.OpAssign)
- }
- m.write(closeParenBytes)
- }
- func (m *jsMinifier) minifyVarDecl(decl *js.VarDecl, onlyDefines bool) {
- if len(decl.List) == 0 {
- return
- } else if decl.TokenType == js.ErrorToken {
- // remove 'var' when hoisting variables
- first := true
- for _, item := range decl.List {
- if item.Default != nil || !onlyDefines {
- if !first {
- m.write(commaBytes)
- }
- m.minifyBindingElement(item)
- first = false
- }
- }
- } else {
- if decl.TokenType == js.VarToken && len(decl.List) <= 10000 {
- // move single var decls forward and order for GZIP optimization
- start := 0
- if _, ok := decl.List[0].Binding.(*js.Var); !ok {
- start++
- }
- for i := 0; i < len(decl.List); i++ {
- item := decl.List[i]
- 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
- }
- }
- }
- }
- m.write(decl.TokenType.Bytes())
- m.writeSpaceBeforeIdent()
- for i, item := range decl.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyBindingElement(item)
- }
- }
- }
- func (m *jsMinifier) minifyFuncDecl(decl *js.FuncDecl, inExpr bool) {
- parentRename := m.renamer.rename
- m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
- m.hoistVars(&decl.Body)
- decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
- if decl.Async {
- m.write(asyncSpaceBytes)
- }
- m.write(functionBytes)
- if decl.Generator {
- m.write(starBytes)
- }
- // TODO: remove function name, really necessary?
- //if decl.Name != nil && decl.Name.Uses == 1 {
- // scope := decl.Body.Scope
- // for i, vorig := range scope.Declared {
- // if decl.Name == vorig {
- // scope.Declared = append(scope.Declared[:i], scope.Declared[i+1:]...)
- // }
- // }
- //}
- if inExpr {
- m.renamer.renameScope(decl.Body.Scope)
- }
- if decl.Name != nil && (!inExpr || 1 < decl.Name.Uses) {
- if !decl.Generator {
- m.write(spaceBytes)
- }
- m.write(decl.Name.Data)
- }
- if !inExpr {
- m.renamer.renameScope(decl.Body.Scope)
- }
- m.minifyParams(decl.Params, true)
- m.minifyBlockStmt(&decl.Body)
- m.renamer.rename = parentRename
- }
- func (m *jsMinifier) minifyMethodDecl(decl *js.MethodDecl) {
- parentRename := m.renamer.rename
- m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
- m.hoistVars(&decl.Body)
- decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
- if decl.Static {
- m.write(staticBytes)
- m.writeSpaceBeforeIdent()
- }
- if decl.Async {
- m.write(asyncBytes)
- if decl.Generator {
- m.write(starBytes)
- } else {
- m.writeSpaceBeforeIdent()
- }
- } else if decl.Generator {
- m.write(starBytes)
- } else if decl.Get {
- m.write(getBytes)
- m.writeSpaceBeforeIdent()
- } else if decl.Set {
- m.write(setBytes)
- m.writeSpaceBeforeIdent()
- }
- m.minifyPropertyName(decl.Name)
- m.renamer.renameScope(decl.Body.Scope)
- m.minifyParams(decl.Params, !decl.Set)
- m.minifyBlockStmt(&decl.Body)
- m.renamer.rename = parentRename
- }
- func (m *jsMinifier) minifyArrowFunc(decl *js.ArrowFunc) {
- parentRename := m.renamer.rename
- m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
- m.hoistVars(&decl.Body)
- decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
- m.renamer.renameScope(decl.Body.Scope)
- if decl.Async {
- m.write(asyncBytes)
- }
- removeParens := false
- if decl.Params.Rest == nil && len(decl.Params.List) == 1 && decl.Params.List[0].Default == nil {
- if decl.Params.List[0].Binding == nil {
- removeParens = true
- } else if _, ok := decl.Params.List[0].Binding.(*js.Var); ok {
- removeParens = true
- }
- }
- if removeParens {
- if decl.Async && decl.Params.List[0].Binding != nil {
- // add space after async in: async a => ...
- m.write(spaceBytes)
- }
- m.minifyBindingElement(decl.Params.List[0])
- } else {
- parentInFor := m.inFor
- m.inFor = false
- m.minifyParams(decl.Params, true)
- m.inFor = parentInFor
- }
- m.write(arrowBytes)
- removeBraces := false
- if 0 < len(decl.Body.List) {
- returnStmt, isReturn := decl.Body.List[len(decl.Body.List)-1].(*js.ReturnStmt)
- if isReturn && returnStmt.Value != nil {
- // merge expression statements to final return statement, remove function body braces
- var list []js.IExpr
- removeBraces = true
- for _, item := range decl.Body.List[:len(decl.Body.List)-1] {
- if expr, isExpr := item.(*js.ExprStmt); isExpr {
- list = append(list, expr.Value)
- } else {
- removeBraces = false
- break
- }
- }
- if removeBraces {
- list = append(list, returnStmt.Value)
- expr := list[0]
- if 0 < len(list) {
- if 1 < len(list) {
- expr = &js.CommaExpr{list}
- }
- expr = &js.GroupExpr{X: expr}
- }
- m.expectExpr = expectExprBody
- m.minifyExpr(expr, js.OpAssign)
- if m.groupedStmt {
- m.write(closeParenBytes)
- m.groupedStmt = false
- }
- }
- } else if isReturn && returnStmt.Value == nil {
- // remove empty return
- decl.Body.List = decl.Body.List[:len(decl.Body.List)-1]
- }
- }
- if !removeBraces {
- m.minifyBlockStmt(&decl.Body)
- }
- m.renamer.rename = parentRename
- }
- func (m *jsMinifier) minifyClassDecl(decl *js.ClassDecl) {
- m.write(classBytes)
- if decl.Name != nil {
- m.write(spaceBytes)
- m.write(decl.Name.Data)
- }
- if decl.Extends != nil {
- m.write(spaceExtendsBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(decl.Extends, js.OpLHS)
- }
- m.write(openBraceBytes)
- m.needsSemicolon = false
- for _, item := range decl.List {
- m.writeSemicolon()
- if item.StaticBlock != nil {
- m.write(staticBytes)
- m.minifyBlockStmt(item.StaticBlock)
- } else if item.Method != nil {
- m.minifyMethodDecl(item.Method)
- } else {
- if item.Static {
- m.write(staticBytes)
- if !item.Name.IsComputed() && item.Name.Literal.TokenType == js.IdentifierToken {
- m.write(spaceBytes)
- }
- }
- m.minifyPropertyName(item.Name)
- if item.Init != nil {
- m.write(equalBytes)
- m.minifyExpr(item.Init, js.OpAssign)
- }
- m.requireSemicolon()
- }
- }
- m.write(closeBraceBytes)
- m.needsSemicolon = false
- }
- func (m *jsMinifier) minifyPropertyName(name js.PropertyName) {
- if name.IsComputed() {
- m.write(openBracketBytes)
- m.minifyExpr(name.Computed, js.OpAssign)
- m.write(closeBracketBytes)
- } else if name.Literal.TokenType == js.StringToken {
- m.write(minifyString(name.Literal.Data, false))
- } else {
- m.write(name.Literal.Data)
- }
- }
- func (m *jsMinifier) minifyProperty(property js.Property) {
- // property.Name is always set in ObjectLiteral
- if property.Spread {
- m.write(ellipsisBytes)
- } else if v, ok := property.Value.(*js.Var); property.Name != nil && (!ok || !property.Name.IsIdent(v.Name())) {
- // add 'old-name:' before BindingName as the latter will be renamed
- m.minifyPropertyName(*property.Name)
- m.write(colonBytes)
- }
- m.minifyExpr(property.Value, js.OpAssign)
- if property.Init != nil {
- m.write(equalBytes)
- m.minifyExpr(property.Init, js.OpAssign)
- }
- }
- func (m *jsMinifier) minifyBindingElement(element js.BindingElement) {
- if element.Binding != nil {
- parentInFor := m.inFor
- m.inFor = false
- m.minifyBinding(element.Binding)
- m.inFor = parentInFor
- if element.Default != nil {
- m.write(equalBytes)
- m.minifyExpr(element.Default, js.OpAssign)
- }
- }
- }
- func (m *jsMinifier) minifyBinding(ibinding js.IBinding) {
- switch binding := ibinding.(type) {
- case *js.Var:
- m.write(binding.Data)
- case *js.BindingArray:
- m.write(openBracketBytes)
- for i, item := range binding.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyBindingElement(item)
- }
- if binding.Rest != nil {
- if 0 < len(binding.List) {
- m.write(commaBytes)
- }
- m.write(ellipsisBytes)
- m.minifyBinding(binding.Rest)
- }
- m.write(closeBracketBytes)
- case *js.BindingObject:
- m.write(openBraceBytes)
- for i, item := range binding.List {
- if i != 0 {
- m.write(commaBytes)
- }
- // item.Key is always set
- if item.Key.IsComputed() {
- m.minifyPropertyName(*item.Key)
- m.write(colonBytes)
- } else if v, ok := item.Value.Binding.(*js.Var); !ok || !item.Key.IsIdent(v.Data) {
- // add 'old-name:' before BindingName as the latter will be renamed
- m.minifyPropertyName(*item.Key)
- m.write(colonBytes)
- }
- m.minifyBindingElement(item.Value)
- }
- if binding.Rest != nil {
- if 0 < len(binding.List) {
- m.write(commaBytes)
- }
- m.write(ellipsisBytes)
- m.write(binding.Rest.Data)
- }
- m.write(closeBraceBytes)
- }
- }
- func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
- if cond, ok := i.(*js.CondExpr); ok {
- i = m.optimizeCondExpr(cond, prec)
- } else if unary, ok := i.(*js.UnaryExpr); ok {
- i = optimizeUnaryExpr(unary, prec)
- }
- switch expr := i.(type) {
- case *js.Var:
- for expr.Link != nil {
- expr = expr.Link
- }
- data := expr.Data
- if bytes.Equal(data, undefinedBytes) { // TODO: only if not defined
- if js.OpUnary < prec {
- m.write(groupedVoidZeroBytes)
- } else {
- m.write(voidZeroBytes)
- }
- } else if bytes.Equal(data, infinityBytes) { // TODO: only if not defined
- if js.OpMul < prec {
- m.write(groupedOneDivZeroBytes)
- } else {
- m.write(oneDivZeroBytes)
- }
- } else {
- m.write(data)
- }
- case *js.LiteralExpr:
- if expr.TokenType == js.DecimalToken || expr.TokenType == js.IntegerToken {
- m.write(decimalNumber(expr.Data, m.o.Precision))
- } else if expr.TokenType == js.BinaryToken {
- m.write(binaryNumber(expr.Data, m.o.Precision))
- } else if expr.TokenType == js.OctalToken {
- m.write(octalNumber(expr.Data, m.o.Precision))
- } else if expr.TokenType == js.HexadecimalToken {
- m.write(hexadecimalNumber(expr.Data, m.o.Precision))
- } else if expr.TokenType == js.TrueToken {
- if js.OpUnary < prec {
- m.write(groupedNotZeroBytes)
- } else {
- m.write(notZeroBytes)
- }
- } else if expr.TokenType == js.FalseToken {
- if js.OpUnary < prec {
- m.write(groupedNotOneBytes)
- } else {
- m.write(notOneBytes)
- }
- } else if expr.TokenType == js.StringToken {
- m.write(minifyString(expr.Data, m.o.minVersion(2015)))
- } else if expr.TokenType == js.RegExpToken {
- // </script>/ => < /script>/
- if 0 < len(m.prev) && m.prev[len(m.prev)-1] == '<' && bytes.HasPrefix(expr.Data, regExpScriptBytes) {
- m.write(spaceBytes)
- }
- m.write(minifyRegExp(expr.Data))
- } else {
- m.write(expr.Data)
- }
- case *js.BinaryExpr:
- mergeBinaryExpr(expr)
- if expr.X == nil {
- m.minifyExpr(expr.Y, prec)
- break
- }
- precLeft := binaryLeftPrecMap[expr.Op]
- // convert (a,b)&&c into a,b&&c but not a=(b,c)&&d into a=(b,c&&d)
- if prec <= js.OpExpr {
- if group, ok := expr.X.(*js.GroupExpr); ok {
- if comma, ok := group.X.(*js.CommaExpr); ok && js.OpAnd <= exprPrec(comma.List[len(comma.List)-1]) {
- expr.X = group.X
- precLeft = js.OpExpr
- }
- }
- }
- if expr.Op == js.InstanceofToken || expr.Op == js.InToken {
- group := expr.Op == js.InToken && m.inFor
- if group {
- m.write(openParenBytes)
- }
- m.minifyExpr(expr.X, precLeft)
- m.writeSpaceAfterIdent()
- m.write(expr.Op.Bytes())
- m.writeSpaceBeforeIdent()
- m.minifyExpr(expr.Y, binaryRightPrecMap[expr.Op])
- if group {
- m.write(closeParenBytes)
- }
- } else {
- // TODO: has effect on GZIP?
- //if expr.Op == js.EqEqToken || expr.Op == js.NotEqToken || expr.Op == js.EqEqEqToken || expr.Op == js.NotEqEqToken {
- // // switch a==const for const==a, such as typeof a=="undefined" for "undefined"==typeof a (GZIP improvement)
- // if _, ok := expr.Y.(*js.LiteralExpr); ok {
- // expr.X, expr.Y = expr.Y, expr.X
- // }
- //}
- if v, not, ok := isUndefinedOrNullVar(expr); ok {
- // change a===null||a===undefined to a==null
- op := js.EqEqToken
- if not {
- op = js.NotEqToken
- }
- expr = &js.BinaryExpr{op, v, &js.LiteralExpr{js.NullToken, nullBytes}}
- }
- m.minifyExpr(expr.X, precLeft)
- if expr.Op == js.GtToken && m.prev[len(m.prev)-1] == '-' {
- // 0 < len(m.prev) always
- m.write(spaceBytes)
- } else if expr.Op == js.EqEqEqToken || expr.Op == js.NotEqEqToken {
- if left, ok := expr.X.(*js.UnaryExpr); ok && left.Op == js.TypeofToken {
- if right, ok := expr.Y.(*js.LiteralExpr); ok && right.TokenType == js.StringToken {
- if expr.Op == js.EqEqEqToken {
- expr.Op = js.EqEqToken
- } else {
- expr.Op = js.NotEqToken
- }
- }
- } else if right, ok := expr.Y.(*js.UnaryExpr); ok && right.Op == js.TypeofToken {
- if left, ok := expr.X.(*js.LiteralExpr); ok && left.TokenType == js.StringToken {
- if expr.Op == js.EqEqEqToken {
- expr.Op = js.EqEqToken
- } else {
- expr.Op = js.NotEqToken
- }
- }
- }
- }
- m.write(expr.Op.Bytes())
- if expr.Op == js.AddToken {
- // +++ => + ++
- m.writeSpaceBefore('+')
- } else if expr.Op == js.SubToken {
- // --- => - --
- m.writeSpaceBefore('-')
- } else if expr.Op == js.DivToken {
- // // => / /
- m.writeSpaceBefore('/')
- }
- m.minifyExpr(expr.Y, binaryRightPrecMap[expr.Op])
- }
- case *js.UnaryExpr:
- if expr.Op == js.PostIncrToken || expr.Op == js.PostDecrToken {
- m.minifyExpr(expr.X, unaryPrecMap[expr.Op])
- m.write(expr.Op.Bytes())
- } else {
- isLtNot := expr.Op == js.NotToken && 0 < len(m.prev) && m.prev[len(m.prev)-1] == '<'
- m.write(expr.Op.Bytes())
- if expr.Op == js.DeleteToken || expr.Op == js.VoidToken || expr.Op == js.TypeofToken || expr.Op == js.AwaitToken {
- m.writeSpaceBeforeIdent()
- } else if expr.Op == js.PosToken {
- // +++ => + ++
- m.writeSpaceBefore('+')
- } else if expr.Op == js.NegToken || isLtNot {
- // --- => - --
- // <!-- => <! --
- m.writeSpaceBefore('-')
- } else if expr.Op == js.NotToken {
- if lit, ok := expr.X.(*js.LiteralExpr); ok && (lit.TokenType == js.StringToken || lit.TokenType == js.RegExpToken) {
- // !"string" => !1
- m.write(oneBytes)
- break
- } else if ok && (lit.TokenType == js.DecimalToken || lit.TokenType == js.IntegerToken) {
- // !123 => !1 (except for !0)
- if lit.Data[len(lit.Data)-1] == 'n' {
- lit.Data = lit.Data[:len(lit.Data)-1]
- }
- if num := minify.Number(lit.Data, m.o.Precision); len(num) == 1 && num[0] == '0' {
- m.write(zeroBytes)
- } else {
- m.write(oneBytes)
- }
- break
- }
- }
- m.minifyExpr(expr.X, unaryPrecMap[expr.Op])
- }
- case *js.DotExpr:
- if group, ok := expr.X.(*js.GroupExpr); ok {
- if lit, ok := group.X.(*js.LiteralExpr); ok && (lit.TokenType == js.DecimalToken || lit.TokenType == js.IntegerToken) {
- if lit.TokenType == js.DecimalToken {
- m.write(minify.Number(lit.Data, m.o.Precision))
- } else {
- m.write(lit.Data)
- m.write(dotBytes)
- }
- m.write(dotBytes)
- m.write(expr.Y.Data)
- break
- }
- }
- if prec < js.OpMember {
- m.minifyExpr(expr.X, js.OpCall)
- } else {
- m.minifyExpr(expr.X, js.OpMember)
- }
- if expr.Optional {
- m.write(questionBytes)
- } else if last := m.prev[len(m.prev)-1]; '0' <= last && last <= '9' {
- // 0 < len(m.prev) always
- isInteger := true
- for _, c := range m.prev[:len(m.prev)-1] {
- if c < '0' || '9' < c {
- isInteger = false
- break
- }
- }
- if isInteger {
- // prevent previous integer
- m.write(dotBytes)
- }
- }
- m.write(dotBytes)
- m.write(expr.Y.Data)
- case *js.GroupExpr:
- if cond, ok := expr.X.(*js.CondExpr); ok {
- expr.X = m.optimizeCondExpr(cond, js.OpExpr)
- }
- precInside := exprPrec(expr.X)
- if prec <= precInside || precInside == js.OpCoalesce && prec == js.OpBitOr {
- m.minifyExpr(expr.X, prec)
- } else {
- parentInFor := m.inFor
- m.inFor = false
- m.write(openParenBytes)
- m.minifyExpr(expr.X, js.OpExpr)
- m.write(closeParenBytes)
- m.inFor = parentInFor
- }
- case *js.ArrayExpr:
- parentInFor := m.inFor
- m.inFor = false
- m.write(openBracketBytes)
- for i, item := range expr.List {
- if i != 0 {
- m.write(commaBytes)
- }
- if item.Spread {
- m.write(ellipsisBytes)
- }
- m.minifyExpr(item.Value, js.OpAssign)
- }
- if 0 < len(expr.List) && expr.List[len(expr.List)-1].Value == nil {
- m.write(commaBytes)
- }
- m.write(closeBracketBytes)
- m.inFor = parentInFor
- case *js.ObjectExpr:
- parentInFor := m.inFor
- m.inFor = false
- groupedStmt := m.expectExpr != expectAny
- if groupedStmt {
- m.write(openParenBracketBytes)
- } else {
- m.write(openBraceBytes)
- }
- for i, item := range expr.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyProperty(item)
- }
- m.write(closeBraceBytes)
- if groupedStmt {
- m.groupedStmt = true
- }
- m.inFor = parentInFor
- case *js.TemplateExpr:
- if expr.Tag != nil {
- if prec < js.OpMember {
- m.minifyExpr(expr.Tag, js.OpCall)
- } else {
- m.minifyExpr(expr.Tag, js.OpMember)
- }
- if expr.Optional {
- m.write(optChainBytes)
- }
- }
- parentInFor := m.inFor
- m.inFor = false
- for _, item := range expr.List {
- m.write(replaceEscapes(item.Value, '`', 1, 2))
- m.minifyExpr(item.Expr, js.OpExpr)
- }
- m.write(replaceEscapes(expr.Tail, '`', 1, 1))
- m.inFor = parentInFor
- case *js.NewExpr:
- if expr.Args == nil && js.OpLHS < prec && prec != js.OpNew {
- m.write(openNewBytes)
- m.writeSpaceBeforeIdent()
- m.minifyExpr(expr.X, js.OpNew)
- m.write(closeParenBytes)
- } else {
- m.write(newBytes)
- m.writeSpaceBeforeIdent()
- if expr.Args != nil {
- m.minifyExpr(expr.X, js.OpMember)
- m.minifyArguments(*expr.Args)
- } else {
- m.minifyExpr(expr.X, js.OpNew)
- }
- }
- case *js.NewTargetExpr:
- m.write(newTargetBytes)
- m.writeSpaceBeforeIdent()
- case *js.ImportMetaExpr:
- if m.expectExpr == expectExprStmt {
- m.write(openParenBytes)
- m.groupedStmt = true
- }
- m.write(importMetaBytes)
- m.writeSpaceBeforeIdent()
- case *js.YieldExpr:
- m.write(yieldBytes)
- m.writeSpaceBeforeIdent()
- if expr.X != nil {
- if expr.Generator {
- m.write(starBytes)
- m.minifyExpr(expr.X, js.OpAssign)
- } else if v, ok := expr.X.(*js.Var); !ok || !bytes.Equal(v.Name(), undefinedBytes) { // TODO: only if not defined
- m.minifyExpr(expr.X, js.OpAssign)
- }
- }
- case *js.CallExpr:
- m.minifyExpr(expr.X, js.OpCall)
- parentInFor := m.inFor
- m.inFor = false
- if expr.Optional {
- m.write(optChainBytes)
- }
- m.minifyArguments(expr.Args)
- m.inFor = parentInFor
- case *js.IndexExpr:
- if m.expectExpr == expectExprStmt {
- if v, ok := expr.X.(*js.Var); ok && bytes.Equal(v.Name(), letBytes) {
- m.write(notBytes)
- }
- }
- if prec < js.OpMember {
- m.minifyExpr(expr.X, js.OpCall)
- } else {
- m.minifyExpr(expr.X, js.OpMember)
- }
- if expr.Optional {
- m.write(optChainBytes)
- }
- if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken && 2 < len(lit.Data) {
- if isIdent := js.AsIdentifierName(lit.Data[1 : len(lit.Data)-1]); isIdent {
- m.write(dotBytes)
- m.write(lit.Data[1 : len(lit.Data)-1])
- break
- } else if isNum := js.AsDecimalLiteral(lit.Data[1 : len(lit.Data)-1]); isNum {
- m.write(openBracketBytes)
- m.write(minify.Number(lit.Data[1:len(lit.Data)-1], 0))
- m.write(closeBracketBytes)
- break
- }
- }
- parentInFor := m.inFor
- m.inFor = false
- m.write(openBracketBytes)
- m.minifyExpr(expr.Y, js.OpExpr)
- m.write(closeBracketBytes)
- m.inFor = parentInFor
- case *js.CondExpr:
- m.minifyExpr(expr.Cond, js.OpCoalesce)
- m.write(questionBytes)
- m.minifyExpr(expr.X, js.OpAssign)
- m.write(colonBytes)
- m.minifyExpr(expr.Y, js.OpAssign)
- case *js.VarDecl:
- m.minifyVarDecl(expr, true) // happens in for statement or when vars were hoisted
- case *js.FuncDecl:
- grouped := m.expectExpr == expectExprStmt && prec != js.OpExpr
- if grouped {
- m.write(openParenBytes)
- } else if m.expectExpr == expectExprStmt {
- m.write(notBytes)
- }
- parentInFor, parentGroupedStmt := m.inFor, m.groupedStmt
- m.inFor, m.groupedStmt = false, false
- m.minifyFuncDecl(expr, true)
- m.inFor, m.groupedStmt = parentInFor, parentGroupedStmt
- if grouped {
- m.write(closeParenBytes)
- }
- case *js.ArrowFunc:
- parentGroupedStmt := m.groupedStmt
- m.groupedStmt = false
- m.minifyArrowFunc(expr)
- m.groupedStmt = parentGroupedStmt
- case *js.MethodDecl:
- parentGroupedStmt := m.groupedStmt
- m.groupedStmt = false
- m.minifyMethodDecl(expr) // only happens in object literal
- m.groupedStmt = parentGroupedStmt
- case *js.ClassDecl:
- if m.expectExpr == expectExprStmt {
- m.write(notBytes)
- }
- parentInFor, parentGroupedStmt := m.inFor, m.groupedStmt
- m.inFor, m.groupedStmt = false, false
- m.minifyClassDecl(expr)
- m.inFor, m.groupedStmt = parentInFor, parentGroupedStmt
- case *js.CommaExpr:
- for i, item := range expr.List {
- if i != 0 {
- m.write(commaBytes)
- }
- m.minifyExpr(item, js.OpAssign)
- }
- }
- }
|