diff --git a/go.mod b/go.mod index 7130d890ea..b71ce3f774 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 github.com/tdewolff/argp v0.0.0-20231030173501-fa6c54897951 - github.com/tdewolff/parse/v2 v2.7.1 + github.com/tdewolff/parse/v2 v2.7.2 github.com/tdewolff/test v1.0.10 ) diff --git a/go.sum b/go.sum index 3a6e34b0b3..fc6c87b808 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8 github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/tdewolff/argp v0.0.0-20231030173501-fa6c54897951 h1:QiakK8TMHgZfKZ2enwnyYzT3fOtzIwZ6mDkVC3scOrQ= github.com/tdewolff/argp v0.0.0-20231030173501-fa6c54897951/go.mod h1:fF+gnKbmf3iMG+ErLiF+orMU/InyZIEnKVVigUjfriw= -github.com/tdewolff/parse/v2 v2.7.1 h1:gdImkv0sIupYr/cXAu5s+CxfVpxMdYZX2Qr+5Q+RdF8= -github.com/tdewolff/parse/v2 v2.7.1/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= +github.com/tdewolff/parse/v2 v2.7.2 h1:9NdxF0nk/+lPI0YADDonSlpiY15hGcVUhXRj9hnK8sM= +github.com/tdewolff/parse/v2 v2.7.2/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew= github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= diff --git a/html/buffer.go b/html/buffer.go index f58367b442..f2a6f8c529 100644 --- a/html/buffer.go +++ b/html/buffer.go @@ -8,12 +8,13 @@ import ( // Token is a single token unit with an attribute value (if given) and hash of the data. type Token struct { html.TokenType - Hash Hash - Data []byte - Text []byte - AttrVal []byte - Traits traits - Offset int + Hash Hash + Data []byte + Text []byte + AttrVal []byte + Traits traits + Offset int + HasTemplate bool } // TokenBuffer is a buffer that allows for token look-ahead. @@ -40,10 +41,11 @@ func (z *TokenBuffer) read(t *Token) { t.Offset = z.r.Offset() t.TokenType, t.Data = z.l.Next() t.Text = z.l.Text() + t.HasTemplate = z.l.HasTemplate() if t.TokenType == html.AttributeToken { t.Offset += 1 + len(t.Text) + 1 t.AttrVal = z.l.AttrVal() - if len(t.AttrVal) > 1 && (t.AttrVal[0] == '"' || t.AttrVal[0] == '\'') { + if 1 < len(t.AttrVal) && (t.AttrVal[0] == '"' || t.AttrVal[0] == '\'') { t.Offset++ t.AttrVal = t.AttrVal[1 : len(t.AttrVal)-1] // quotes will be readded in attribute loop if necessary } diff --git a/html/html.go b/html/html.go index c55b673d27..014ffe8761 100644 --- a/html/html.go +++ b/html/html.go @@ -41,6 +41,13 @@ var ( //////////////////////////////////////////////////////////////// +var GoTemplateDelims = [2]string{"{{", "}}"} +var HandlebarsTemplateDelims = [2]string{"{{", "}}"} +var MustacheTemplateDelims = [2]string{"{{", "}}"} +var EJSTemplateDelims = [2]string{"<%", "%>"} +var ASPTemplateDelims = [2]string{"<%", "%>"} +var PHPTemplateDelims = [2]string{""} + // Minifier is an HTML minifier. type Minifier struct { KeepComments bool @@ -50,6 +57,7 @@ type Minifier struct { KeepEndTags bool KeepQuotes bool KeepWhitespace bool + TemplateDelims [2]string } // Minify minifies HTML data, it reads from r and writes to w. @@ -71,7 +79,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st z := parse.NewInput(r) defer z.Restore() - l := html.NewLexer(z) + l := html.NewTemplateLexer(z, o.TemplateDelims) tb := NewTokenBuffer(z, l) for { t := *tb.Shift() @@ -127,7 +135,9 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st } case html.TextToken: // CSS and JS minifiers for inline code - if rawTagHash != 0 { + if t.HasTemplate { + w.Write(t.Data) + } else if rawTagHash != 0 { if rawTagHash == Style || rawTagHash == Script || rawTagHash == Iframe { var mimetype []byte var params map[string]string @@ -372,6 +382,9 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st break } else if attr.Text == nil { continue // removed attribute + } else if attr.HasTemplate { + w.Write(attr.Data) + continue // don't minify attributes that contain templates } val := attr.AttrVal diff --git a/html/html_test.go b/html/html_test.go index 077e6ae3a6..f06d121528 100644 --- a/html/html_test.go +++ b/html/html_test.go @@ -26,6 +26,7 @@ func TestHTML(t *testing.T) { expected string }{ {`html`, `html`}, + //{"title ", `title`}, {``, ``}, {``, ``}, {``, ``}, @@ -67,6 +68,7 @@ func TestHTML(t *testing.T) { {``, ``}, {``, ``}, {``, ``}, + {``, ``}, // increase coverage {``, ``}, @@ -352,6 +354,30 @@ func TestHTMLURL(t *testing.T) { } } +func TestHTMLTemplates(t *testing.T) { + htmlTests := []struct { + html string + expected string + }{ + {`a

{{ printf " ! " }}

b`, `a

{{ printf " ! " }}

b`}, + {`a {{ printf " ! " }} b`, `a {{ printf " ! " }} b`}, + {`
`, ``}, + {``, ``}, + {``, ``}, + } + + m := minify.New() + htmlMinifier := &Minifier{KeepEndTags: true, TemplateDelims: GoTemplateDelims} + for _, tt := range htmlTests { + t.Run(tt.html, func(t *testing.T) { + r := bytes.NewBufferString(tt.html) + w := &bytes.Buffer{} + err := htmlMinifier.Minify(m, w, r, nil) + test.Minify(t, tt.html, err, w.String(), tt.expected) + }) + } +} + func TestSpecialTagClosing(t *testing.T) { m := minify.New() m.AddFunc("text/html", Minify)