diff --git a/bindings/js/go/minify.go b/bindings/js/go/minify.go
index c24677823..b43c60df4 100644
--- a/bindings/js/go/minify.go
+++ b/bindings/js/go/minify.go
@@ -109,6 +109,18 @@ func parseOptions(opts *C.MinifyOptions) (minifyOptions, error) {
}, nil
}
+func toSet(tags []string) map[string]struct{} {
+ if len(tags) == 0 {
+ return nil
+ }
+
+ m := make(map[string]struct{}, len(tags))
+ for _, t := range tags {
+ m[strings.ToLower(t)] = struct{}{}
+ }
+ return m
+}
+
func newMinifier(opts minifyOptions) (*minify.M, error) {
m := minify.New()
@@ -125,6 +137,7 @@ func newMinifier(opts minifyOptions) (*minify.M, error) {
KeepQuotes: opts.HTMLKeepQuotes,
KeepSpecialComments: opts.HTMLKeepSpecialComments,
KeepWhitespace: opts.HTMLKeepWhitespace,
+ PreserveWhitespaceTags: toSet(opts.PreserveWhitespaceTags),
}
jsMinifier := &js.Minifier{
Precision: opts.JSPrecision,
diff --git a/html/html.go b/html/html.go
index 938b794a4..5f1c26b54 100644
--- a/html/html.go
+++ b/html/html.go
@@ -5,6 +5,7 @@ import (
"bytes"
"fmt"
"io"
+ "strings"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/parse/v2"
@@ -61,6 +62,7 @@ type Minifier struct {
KeepQuotes bool
KeepWhitespace bool
TemplateDelims [2]string
+ PreserveWhitespaceTags map[string]struct{}
}
// Minify minifies HTML data, it reads from r and writes to w.
@@ -68,6 +70,23 @@ func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) err
return (&Minifier{}).Minify(m, w, r, params)
}
+func (o *Minifier) isWhitespaceTag(t Token) bool {
+ if o.PreserveWhitespaceTags == nil {
+ return false
+ }
+
+ if t.TokenType != html.StartTagToken && t.TokenType != html.EndTagToken {
+ return false
+ }
+
+ tag := strings.ToLower(string(t.Data))
+ tag = strings.TrimPrefix(tag, "<")
+ tag = strings.TrimPrefix(tag, "")
+
+ _, ok := o.PreserveWhitespaceTags[strings.ToLower(string(tag))]
+ return ok
+}
+
// Minify minifies HTML data, it reads from r and writes to w.
func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
var rawTagHash Hash
@@ -80,7 +99,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
}
omitSpace := true // if true the next leading space is omitted
- inPre, inTemplate := false, false
+ inWhitespacePreservingMode, inTemplate := false, false
attrMinifyBuffer := buffer.NewWriter(make([]byte, 0, 64))
attrByteBuffer := make([]byte, 0, 64)
@@ -180,7 +199,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
} else {
w.Write(t.Data)
}
- } else if inPre {
+ } else if inWhitespacePreservingMode {
w.Write(t.Data)
// omitSpace = true after block element
} else {
@@ -260,8 +279,8 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st
omitSpace = true // EndTagToken
}
- if t.Hash == Pre {
- inPre = t.TokenType == html.StartTagToken
+ if t.Hash == Pre || o.isWhitespaceTag(t) {
+ inWhitespacePreservingMode = t.TokenType == html.StartTagToken
} else if t.Hash == Template {
inTemplate = t.TokenType == html.StartTagToken
}
diff --git a/html/html_test.go b/html/html_test.go
index eb5d34eca..a4a0ddb79 100644
--- a/html/html_test.go
+++ b/html/html_test.go
@@ -526,6 +526,44 @@ func TestMinifyErrors(t *testing.T) {
}
}
+func TestHTMLPreserveWhitespaceTags(t *testing.T) {
+ htmlTests := []struct {
+ html string
+ expected string
+ }{
+ {
+ "
\n foo\n bar\n", + "
\n foo\n bar\n", + }, + } + + m := minify.New() + m.AddFunc("text/css", css.Minify) + + htmlMinifier := &Minifier{ + PreserveWhitespaceTags: map[string]struct{}{ + "custom-component": {}, + }, + } + + 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 TestMinifyErrorPropagation(t *testing.T) { errorTests := []struct { html string