1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-16 18:01:37 +00:00

Refactor assets bundler and split Javascript files

This commit is contained in:
Frédéric Guillot 2018-07-05 22:18:51 -07:00
parent e1c56b2e53
commit 53deb0b8cd
49 changed files with 2837 additions and 2000 deletions

View file

@ -4,6 +4,7 @@ package css // import "github.com/tdewolff/minify/css"
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"strconv"
@ -29,16 +30,19 @@ type cssMinifier struct {
w io.Writer
p *css.Parser
o *Minifier
valuesBuffer []Token
}
////////////////////////////////////////////////////////////////
// DefaultMinifier is the default minifier.
var DefaultMinifier = &Minifier{Decimals: -1}
var DefaultMinifier = &Minifier{Decimals: -1, KeepCSS2: false}
// Minifier is a CSS minifier.
type Minifier struct {
Decimals int
KeepCSS2 bool
}
// Minify minifies CSS data, it reads from r and writes to w.
@ -108,7 +112,19 @@ func (c *cssMinifier) minifyGrammar() error {
if _, err := c.w.Write(data); err != nil {
return err
}
for _, val := range c.p.Values() {
values := c.p.Values()
if css.ToHash(data[1:]) == css.Import && len(values) == 2 && values[1].TokenType == css.URLToken {
url := values[1].Data
if url[4] != '"' && url[4] != '\'' {
url = url[3:]
url[0] = '"'
url[len(url)-1] = '"'
} else {
url = url[4 : len(url)-1]
}
values[1].Data = url
}
for _, val := range values {
if _, err := c.w.Write(val.Data); err != nil {
return err
}
@ -216,138 +232,238 @@ func (c *cssMinifier) minifySelectors(property []byte, values []css.Token) error
return nil
}
func (c *cssMinifier) minifyDeclaration(property []byte, values []css.Token) error {
if len(values) == 0 {
type Token struct {
css.TokenType
Data []byte
Components []css.Token // only filled for functions
}
func (t Token) String() string {
if len(t.Components) == 0 {
return t.TokenType.String() + "(" + string(t.Data) + ")"
} else {
return fmt.Sprint(t.Components)
}
}
func (c *cssMinifier) minifyDeclaration(property []byte, components []css.Token) error {
if len(components) == 0 {
return nil
}
prop := css.ToHash(property)
inProgid := false
for i, value := range values {
if inProgid {
if value.TokenType == css.FunctionToken {
inProgid = false
}
continue
} else if value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Progid {
inProgid = true
continue
}
value.TokenType, value.Data = c.shortenToken(prop, value.TokenType, value.Data)
if prop == css.Font || prop == css.Font_Family || prop == css.Font_Weight {
if value.TokenType == css.IdentToken && (prop == css.Font || prop == css.Font_Weight) {
val := css.ToHash(value.Data)
if val == css.Normal && prop == css.Font_Weight {
// normal could also be specified for font-variant, not just font-weight
value.TokenType = css.NumberToken
value.Data = []byte("400")
} else if val == css.Bold {
value.TokenType = css.NumberToken
value.Data = []byte("700")
}
} else if value.TokenType == css.StringToken && (prop == css.Font || prop == css.Font_Family) && len(value.Data) > 2 {
unquote := true
parse.ToLower(value.Data)
s := value.Data[1 : len(value.Data)-1]
if len(s) > 0 {
for _, split := range bytes.Split(s, spaceBytes) {
val := css.ToHash(split)
// if len is zero, it contains two consecutive spaces
if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default ||
len(split) == 0 || !css.IsIdent(split) {
unquote = false
break
}
}
}
if unquote {
value.Data = s
}
}
} else if prop == css.Outline || prop == css.Border || prop == css.Border_Bottom || prop == css.Border_Left || prop == css.Border_Right || prop == css.Border_Top {
if css.ToHash(value.Data) == css.None {
value.TokenType = css.NumberToken
value.Data = zeroBytes
}
}
values[i].TokenType, values[i].Data = value.TokenType, value.Data
}
prop := css.ToHash(property)
// Strip !important from the component list, this will be added later separately
important := false
if len(values) > 2 && values[len(values)-2].TokenType == css.DelimToken && values[len(values)-2].Data[0] == '!' && css.ToHash(values[len(values)-1].Data) == css.Important {
values = values[:len(values)-2]
if len(components) > 2 && components[len(components)-2].TokenType == css.DelimToken && components[len(components)-2].Data[0] == '!' && css.ToHash(components[len(components)-1].Data) == css.Important {
components = components[:len(components)-2]
important = true
}
if len(values) == 1 {
if prop == css.Background && css.ToHash(values[0].Data) == css.None {
values[0].Data = backgroundNoneBytes
} else if bytes.Equal(property, msfilterBytes) {
alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=")
if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) {
values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...)
// Check if this is a simple list of values separated by whitespace or commas, otherwise we'll not be processing
simple := true
prevSep := true
values := c.valuesBuffer[:0]
for i := 0; i < len(components); i++ {
comp := components[i]
if comp.TokenType == css.LeftParenthesisToken || comp.TokenType == css.LeftBraceToken || comp.TokenType == css.LeftBracketToken || comp.TokenType == css.RightParenthesisToken || comp.TokenType == css.RightBraceToken || comp.TokenType == css.RightBracketToken {
simple = false
break
}
if !prevSep && comp.TokenType != css.WhitespaceToken && comp.TokenType != css.CommaToken {
simple = false
break
}
if comp.TokenType == css.WhitespaceToken || comp.TokenType == css.CommaToken {
prevSep = true
if comp.TokenType == css.CommaToken {
values = append(values, Token{components[i].TokenType, components[i].Data, nil})
}
} else if comp.TokenType == css.FunctionToken {
prevSep = false
j := i + 1
level := 0
for ; j < len(components); j++ {
if components[j].TokenType == css.LeftParenthesisToken {
level++
} else if components[j].TokenType == css.RightParenthesisToken {
if level == 0 {
j++
break
}
level--
}
}
values = append(values, Token{components[i].TokenType, components[i].Data, components[i:j]})
i = j - 1
} else {
prevSep = false
values = append(values, Token{components[i].TokenType, components[i].Data, nil})
}
}
c.valuesBuffer = values
// Do not process complex values (eg. containing blocks or is not alternated between whitespace/commas and flat values
if !simple {
if prop == css.Filter && len(components) == 11 {
if bytes.Equal(components[0].Data, []byte("progid")) &&
components[1].TokenType == css.ColonToken &&
bytes.Equal(components[2].Data, []byte("DXImageTransform")) &&
components[3].Data[0] == '.' &&
bytes.Equal(components[4].Data, []byte("Microsoft")) &&
components[5].Data[0] == '.' &&
bytes.Equal(components[6].Data, []byte("Alpha(")) &&
bytes.Equal(parse.ToLower(components[7].Data), []byte("opacity")) &&
components[8].Data[0] == '=' &&
components[10].Data[0] == ')' {
components = components[6:]
components[0].Data = []byte("alpha(")
}
}
} else {
if prop == css.Margin || prop == css.Padding || prop == css.Border_Width {
if (values[0].TokenType == css.NumberToken || values[0].TokenType == css.DimensionToken || values[0].TokenType == css.PercentageToken) && (len(values)+1)%2 == 0 {
valid := true
for i := 1; i < len(values); i += 2 {
if values[i].TokenType != css.WhitespaceToken || values[i+1].TokenType != css.NumberToken && values[i+1].TokenType != css.DimensionToken && values[i+1].TokenType != css.PercentageToken {
valid = false
for _, component := range components {
if _, err := c.w.Write(component.Data); err != nil {
return err
}
}
if important {
if _, err := c.w.Write([]byte("!important")); err != nil {
return err
}
}
return nil
}
for i := range values {
values[i].TokenType, values[i].Data = c.shortenToken(prop, values[i].TokenType, values[i].Data)
}
if len(values) > 0 {
switch prop {
case css.Font, css.Font_Weight, css.Font_Family:
if prop == css.Font {
// in "font:" shorthand all values before the size have "normal"
// as valid and, at the same time, default value, so just skip them
for i, value := range values {
if !(value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Normal) {
values = values[i:]
break
}
}
if valid {
n := (len(values) + 1) / 2
if n == 2 {
if bytes.Equal(values[0].Data, values[2].Data) {
values = values[:1]
}
} else if n == 3 {
if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) {
values = values[:1]
} else if bytes.Equal(values[0].Data, values[4].Data) {
values = values[:3]
}
} else if n == 4 {
if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[0].Data, values[6].Data) {
values = values[:1]
} else if bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[2].Data, values[6].Data) {
values = values[:3]
} else if bytes.Equal(values[2].Data, values[6].Data) {
values = values[:5]
}
for i, value := range values {
if value.TokenType == css.IdentToken {
val := css.ToHash(value.Data)
if prop == css.Font_Weight && val == css.Normal {
values[i].TokenType = css.NumberToken
values[i].Data = []byte("400")
} else if val == css.Bold {
values[i].TokenType = css.NumberToken
values[i].Data = []byte("700")
}
} else if value.TokenType == css.StringToken && len(value.Data) > 2 {
unquote := true
parse.ToLower(value.Data)
s := value.Data[1 : len(value.Data)-1]
if len(s) > 0 {
for _, split := range bytes.Split(s, spaceBytes) {
val := css.ToHash(split)
// if len is zero, it contains two consecutive spaces
if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default ||
len(split) == 0 || !css.IsIdent(split) {
unquote = false
break
}
}
}
if unquote {
values[i].Data = s
}
}
}
} else if prop == css.Filter && len(values) == 11 {
if bytes.Equal(values[0].Data, []byte("progid")) &&
values[1].TokenType == css.ColonToken &&
bytes.Equal(values[2].Data, []byte("DXImageTransform")) &&
values[3].Data[0] == '.' &&
bytes.Equal(values[4].Data, []byte("Microsoft")) &&
values[5].Data[0] == '.' &&
bytes.Equal(values[6].Data, []byte("Alpha(")) &&
bytes.Equal(parse.ToLower(values[7].Data), []byte("opacity")) &&
values[8].Data[0] == '=' &&
values[10].Data[0] == ')' {
values = values[6:]
values[0].Data = []byte("alpha(")
case css.Margin, css.Padding, css.Border_Width:
n := len(values)
if n == 2 {
if bytes.Equal(values[0].Data, values[1].Data) {
values = values[:1]
}
} else if n == 3 {
if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) {
values = values[:1]
} else if bytes.Equal(values[0].Data, values[2].Data) {
values = values[:2]
}
} else if n == 4 {
if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[3].Data) {
values = values[:1]
} else if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[1].Data, values[3].Data) {
values = values[:2]
} else if bytes.Equal(values[1].Data, values[3].Data) {
values = values[:3]
}
}
case css.Outline, css.Border, css.Border_Bottom, css.Border_Left, css.Border_Right, css.Border_Top:
none := false
iZero := -1
for i, value := range values {
if len(value.Data) == 1 && value.Data[0] == '0' {
iZero = i
} else if css.ToHash(value.Data) == css.None {
values[i].TokenType = css.NumberToken
values[i].Data = zeroBytes
none = true
}
}
if none && iZero != -1 {
values = append(values[:iZero], values[iZero+1:]...)
}
case css.Background:
ident := css.ToHash(values[0].Data)
if len(values) == 1 && (ident == css.None || ident == css.Transparent) {
values[0].Data = backgroundNoneBytes
}
case css.Box_Shadow:
if len(values) == 4 && len(values[0].Data) == 1 && values[0].Data[0] == '0' && len(values[1].Data) == 1 && values[1].Data[0] == '0' && len(values[2].Data) == 1 && values[2].Data[0] == '0' && len(values[3].Data) == 1 && values[3].Data[0] == '0' {
values = values[:2]
}
default:
if bytes.Equal(property, msfilterBytes) {
alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=")
if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) {
values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...)
}
}
}
}
for i := 0; i < len(values); i++ {
if values[i].TokenType == css.FunctionToken {
n, err := c.minifyFunction(values[i:])
prevComma := true
for _, value := range values {
if !prevComma && value.TokenType != css.CommaToken {
if _, err := c.w.Write([]byte(" ")); err != nil {
return err
}
}
if value.TokenType == css.FunctionToken {
err := c.minifyFunction(value.Components)
if err != nil {
return err
}
i += n - 1
} else if _, err := c.w.Write(values[i].Data); err != nil {
return err
} else {
if _, err := c.w.Write(value.Data); err != nil {
return err
}
}
if value.TokenType == css.CommaToken {
prevComma = true
} else {
prevComma = false
}
}
if important {
if _, err := c.w.Write([]byte("!important")); err != nil {
return err
@ -356,104 +472,76 @@ func (c *cssMinifier) minifyDeclaration(property []byte, values []css.Token) err
return nil
}
func (c *cssMinifier) minifyFunction(values []css.Token) (int, error) {
n := 1
simple := true
for i, value := range values[1:] {
if value.TokenType == css.RightParenthesisToken {
n++
break
}
if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) {
simple = false
}
n++
}
values = values[:n]
if simple && (n-1)%2 == 0 {
fun := css.ToHash(values[0].Data[:len(values[0].Data)-1])
nArgs := (n - 1) / 2
if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 {
d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken
if d-1.0 > -minify.Epsilon {
if fun == css.Rgba {
values[0].Data = []byte("rgb(")
fun = css.Rgb
} else {
values[0].Data = []byte("hsl(")
fun = css.Hsl
}
values = values[:len(values)-2]
values[len(values)-1].Data = []byte(")")
nArgs = 3
} else if d < minify.Epsilon {
values[0].Data = []byte("transparent")
values = values[:1]
fun = 0
nArgs = 0
func (c *cssMinifier) minifyFunction(values []css.Token) error {
n := len(values)
if n > 2 {
simple := true
for i, value := range values[1 : n-1] {
if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) {
simple = false
}
}
if fun == css.Rgb && nArgs == 3 {
var err [3]error
rgb := [3]byte{}
for j := 0; j < 3; j++ {
val := values[j*2+1]
if val.TokenType == css.NumberToken {
var d int64
d, err[j] = strconv.ParseInt(string(val.Data), 10, 32)
if d < 0 {
d = 0
} else if d > 255 {
d = 255
if simple && n%2 == 1 {
fun := css.ToHash(values[0].Data[0 : len(values[0].Data)-1])
for i := 1; i < n; i += 2 {
values[i].TokenType, values[i].Data = c.shortenToken(0, values[i].TokenType, values[i].Data)
}
nArgs := (n - 1) / 2
if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 {
d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken
if d-1.0 > -minify.Epsilon {
if fun == css.Rgba {
values[0].Data = []byte("rgb(")
fun = css.Rgb
} else {
values[0].Data = []byte("hsl(")
fun = css.Hsl
}
rgb[j] = byte(d)
} else if val.TokenType == css.PercentageToken {
var d float64
d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32)
if d < 0.0 {
d = 0.0
} else if d > 100.0 {
d = 100.0
}
rgb[j] = byte((d / 100.0 * 255.0) + 0.5)
values = values[:len(values)-2]
values[len(values)-1].Data = []byte(")")
nArgs = 3
} else if d < minify.Epsilon {
values[0].Data = []byte("transparent")
values = values[:1]
fun = 0
nArgs = 0
}
}
if err[0] == nil && err[1] == nil && err[2] == nil {
val := make([]byte, 7)
val[0] = '#'
hex.Encode(val[1:], rgb[:])
parse.ToLower(val)
if s, ok := ShortenColorHex[string(val)]; ok {
if _, err := c.w.Write(s); err != nil {
return 0, err
}
} else {
if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
val[2] = val[3]
val[3] = val[5]
val = val[:4]
}
if _, err := c.w.Write(val); err != nil {
return 0, err
if fun == css.Rgb && nArgs == 3 {
var err [3]error
rgb := [3]byte{}
for j := 0; j < 3; j++ {
val := values[j*2+1]
if val.TokenType == css.NumberToken {
var d int64
d, err[j] = strconv.ParseInt(string(val.Data), 10, 32)
if d < 0 {
d = 0
} else if d > 255 {
d = 255
}
rgb[j] = byte(d)
} else if val.TokenType == css.PercentageToken {
var d float64
d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32)
if d < 0.0 {
d = 0.0
} else if d > 100.0 {
d = 100.0
}
rgb[j] = byte((d / 100.0 * 255.0) + 0.5)
}
}
return n, nil
}
} else if fun == css.Hsl && nArgs == 3 {
if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken {
h, err1 := strconv.ParseFloat(string(values[1].Data), 32)
s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32)
l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32)
if err1 == nil && err2 == nil && err3 == nil {
r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0)
rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)}
if err[0] == nil && err[1] == nil && err[2] == nil {
val := make([]byte, 7)
val[0] = '#'
hex.Encode(val[1:], rgb[:])
parse.ToLower(val)
if s, ok := ShortenColorHex[string(val)]; ok {
if _, err := c.w.Write(s); err != nil {
return 0, err
return err
}
} else {
if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
@ -462,20 +550,50 @@ func (c *cssMinifier) minifyFunction(values []css.Token) (int, error) {
val = val[:4]
}
if _, err := c.w.Write(val); err != nil {
return 0, err
return err
}
}
return n, nil
return nil
}
} else if fun == css.Hsl && nArgs == 3 {
if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken {
h, err1 := strconv.ParseFloat(string(values[1].Data), 32)
s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32)
l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32)
if err1 == nil && err2 == nil && err3 == nil {
r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0)
rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)}
val := make([]byte, 7)
val[0] = '#'
hex.Encode(val[1:], rgb[:])
parse.ToLower(val)
if s, ok := ShortenColorHex[string(val)]; ok {
if _, err := c.w.Write(s); err != nil {
return err
}
} else {
if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
val[2] = val[3]
val[3] = val[5]
val = val[:4]
}
if _, err := c.w.Write(val); err != nil {
return err
}
}
return nil
}
}
}
}
}
for _, value := range values {
if _, err := c.w.Write(value.Data); err != nil {
return 0, err
return err
}
}
return n, nil
return nil
}
func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) (css.TokenType, []byte) {
@ -491,11 +609,15 @@ func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte)
}
dim := data[n:]
parse.ToLower(dim)
data = minify.Number(data[:n], c.o.Decimals)
if tt == css.PercentageToken && (len(data) != 1 || data[0] != '0' || prop == css.Color) {
data = append(data, '%')
} else if tt == css.DimensionToken && (len(data) != 1 || data[0] != '0' || requiredDimension[string(dim)]) {
if !c.o.KeepCSS2 {
data = minify.Number(data[:n], c.o.Decimals)
} else {
data = minify.Decimal(data[:n], c.o.Decimals) // don't use exponents
}
if tt == css.DimensionToken && (len(data) != 1 || data[0] != '0' || !optionalZeroDimension[string(dim)] || prop == css.Flex) {
data = append(data, dim...)
} else if tt == css.PercentageToken {
data = append(data, '%') // TODO: drop percentage for properties that accept <percentage> and <length>
}
} else if tt == css.IdentToken {
//parse.ToLower(data) // TODO: not all identifiers are case-insensitive; all <custom-ident> properties are case-sensitive
@ -541,7 +663,7 @@ func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte)
} else if tt == css.URLToken {
parse.ToLower(data[:3])
if len(data) > 10 {
uri := data[4 : len(data)-1]
uri := parse.TrimWhitespace(data[4 : len(data)-1])
delim := byte('"')
if uri[0] == '\'' || uri[0] == '"' {
delim = uri[0]