js.go 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // Package js minifies ECMAScript5.1 following the specifications at http://www.ecma-international.org/ecma-262/5.1/.
  2. package js // import "github.com/tdewolff/minify/js"
  3. import (
  4. "io"
  5. "github.com/tdewolff/minify"
  6. "github.com/tdewolff/parse"
  7. "github.com/tdewolff/parse/js"
  8. )
  9. var (
  10. spaceBytes = []byte(" ")
  11. newlineBytes = []byte("\n")
  12. )
  13. ////////////////////////////////////////////////////////////////
  14. // DefaultMinifier is the default minifier.
  15. var DefaultMinifier = &Minifier{}
  16. // Minifier is a JS minifier.
  17. type Minifier struct{}
  18. // Minify minifies JS data, it reads from r and writes to w.
  19. func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
  20. return DefaultMinifier.Minify(m, w, r, params)
  21. }
  22. // Minify minifies JS data, it reads from r and writes to w.
  23. func (o *Minifier) Minify(_ *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
  24. prev := js.LineTerminatorToken
  25. prevLast := byte(' ')
  26. lineTerminatorQueued := false
  27. whitespaceQueued := false
  28. l := js.NewLexer(r)
  29. defer l.Restore()
  30. for {
  31. tt, data := l.Next()
  32. if tt == js.ErrorToken {
  33. if l.Err() != io.EOF {
  34. return l.Err()
  35. }
  36. return nil
  37. } else if tt == js.LineTerminatorToken {
  38. lineTerminatorQueued = true
  39. } else if tt == js.WhitespaceToken {
  40. whitespaceQueued = true
  41. } else if tt == js.SingleLineCommentToken || tt == js.MultiLineCommentToken {
  42. if len(data) > 5 && data[1] == '*' && data[2] == '!' {
  43. if _, err := w.Write(data[:3]); err != nil {
  44. return err
  45. }
  46. comment := parse.ReplaceMultipleWhitespace(data[3 : len(data)-2])
  47. if tt != js.MultiLineCommentToken {
  48. // don't trim newlines in multiline comments as that might change ASI
  49. // (we could do a more expensive check post-factum but it's not worth it)
  50. comment = parse.TrimWhitespace(comment)
  51. }
  52. if _, err := w.Write(comment); err != nil {
  53. return err
  54. }
  55. if _, err := w.Write(data[len(data)-2:]); err != nil {
  56. return err
  57. }
  58. } else if tt == js.MultiLineCommentToken {
  59. lineTerminatorQueued = true
  60. } else {
  61. whitespaceQueued = true
  62. }
  63. } else {
  64. first := data[0]
  65. if (prev == js.IdentifierToken || prev == js.NumericToken || prev == js.PunctuatorToken || prev == js.StringToken || prev == js.TemplateToken || prev == js.RegexpToken) &&
  66. (tt == js.IdentifierToken || tt == js.NumericToken || tt == js.StringToken || tt == js.TemplateToken || tt == js.PunctuatorToken || tt == js.RegexpToken) {
  67. if lineTerminatorQueued && (prev != js.PunctuatorToken || prevLast == '}' || prevLast == ']' || prevLast == ')' || prevLast == '+' || prevLast == '-' || prevLast == '"' || prevLast == '\'') &&
  68. (tt != js.PunctuatorToken || first == '{' || first == '[' || first == '(' || first == '+' || first == '-' || first == '!' || first == '~') {
  69. if _, err := w.Write(newlineBytes); err != nil {
  70. return err
  71. }
  72. } else if whitespaceQueued && (prev != js.StringToken && prev != js.PunctuatorToken && tt != js.PunctuatorToken || (prevLast == '+' || prevLast == '-' || prevLast == '/') && first == prevLast) {
  73. if _, err := w.Write(spaceBytes); err != nil {
  74. return err
  75. }
  76. }
  77. }
  78. if _, err := w.Write(data); err != nil {
  79. return err
  80. }
  81. prev = tt
  82. prevLast = data[len(data)-1]
  83. lineTerminatorQueued = false
  84. whitespaceQueued = false
  85. }
  86. }
  87. }