From 131a4fe4c57641f527c0d39756793d288a9e4bb0 Mon Sep 17 00:00:00 2001 From: Wesley Schwengle Date: Thu, 16 Apr 2026 20:48:20 -0400 Subject: [PATCH 1/2] Rename inPre to inWhitespacePreservingMode Signed-off-by: Wesley Schwengle --- html/html.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html/html.go b/html/html.go index 938b794a4..f5b98fd1d 100644 --- a/html/html.go +++ b/html/html.go @@ -80,7 +80,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 +180,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 { @@ -261,7 +261,7 @@ func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]st } if t.Hash == Pre { - inPre = t.TokenType == html.StartTagToken + inWhitespacePreservingMode = t.TokenType == html.StartTagToken } else if t.Hash == Template { inTemplate = t.TokenType == html.StartTagToken } From fd91f64bedbfd8e7bc2c524e40bb1350fefe5415 Mon Sep 17 00:00:00 2001 From: Wesley Schwengle Date: Thu, 16 Apr 2026 21:21:11 -0400 Subject: [PATCH 2/2] Support custom elements for whitespace preservation This change introduces `PreserveWhitespaceTags` as a configuration option to support custom elements that may or may not require whitespace to function. I'm building webcomponents and some use templates that becomes `
`
tags. The problem is that the minifier eats my whitespace and thus the
component loose the formatting they require. This is especially annoying
when I format code:

```perl
sub foo {
  my @array = qw(here be dragons);
  return \@array;
}

sub bar {
  my @array = qw(go perl);
  return join(" ", @array);
}
```

And my custom elements gets:

```perl
sub foo {
my @array = qw(here be dragons);
return \@array;
}
sub bar {
my @array = qw(go perl);
return join(" ", @array);
}
```

So I added support to treat custom elements the same as `
`-tags.

Signed-off-by: Wesley Schwengle 
---
 bindings/js/go/minify.go | 13 +++++++++++++
 html/html.go             | 21 ++++++++++++++++++++-
 html/html_test.go        | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 71 insertions(+), 1 deletion(-)

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 f5b98fd1d..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, "\n    foo\n    bar\n",
+			"\n    foo\n    bar\n",
+		},
+		{
+			"\n    foo\n    bar\n",
+			"foo\nbar",
+		},
+		{
+			"
\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