summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-07-19 17:32:19 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-07-19 19:50:37 +0200
commitd8c94c354afb286b4fba9b883e49c1bd2c326bb3 (patch)
treef5bbfecaa72eee43fd5b004452dafd7753f72ad5
parent6bbec9001445f623cead19de6811ee960cc53a10 (diff)
publisher: Improve class collector for dynamic classes
E.g. * AlpinesJS' :class="isTrue 'class1' : 'class2'" * And dynamic classes with colon in them, e.g. `hover:bg-white`
-rw-r--r--publisher/htmlElementsCollector.go56
-rw-r--r--publisher/htmlElementsCollector_test.go6
2 files changed, 51 insertions, 11 deletions
diff --git a/publisher/htmlElementsCollector.go b/publisher/htmlElementsCollector.go
index c9d81818c..6c01fd8d9 100644
--- a/publisher/htmlElementsCollector.go
+++ b/publisher/htmlElementsCollector.go
@@ -32,7 +32,7 @@ const eof = -1
var (
htmlJsonFixer = strings.NewReplacer(", ", "\n")
- jsonAttrRe = regexp.MustCompile(`'?(.*?)'?:.*`)
+ jsonAttrRe = regexp.MustCompile(`'?(.*?)'?:\s.*`)
classAttrRe = regexp.MustCompile(`(?i)^class$|transition`)
skipInnerElementRe = regexp.MustCompile(`(?i)^(pre|textarea|script|style)`)
@@ -404,21 +404,31 @@ func (w *htmlElementsCollectorWriter) parseHTMLElement(elStr string) (el htmlEle
if conf.DisableClasses {
continue
}
+
if classAttrRe.MatchString(a.Key) {
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
} else {
key := strings.ToLower(a.Key)
val := strings.TrimSpace(a.Val)
- if strings.Contains(key, "class") && strings.HasPrefix(val, "{") {
- // This looks like a Vue or AlpineJS class binding.
- val = htmlJsonFixer.Replace(strings.Trim(val, "{}"))
- lines := strings.Split(val, "\n")
- for i, l := range lines {
- lines[i] = strings.TrimSpace(l)
+
+ if strings.Contains(key, ":class") {
+ if strings.HasPrefix(val, "{") {
+ // This looks like a Vue or AlpineJS class binding.
+ val = htmlJsonFixer.Replace(strings.Trim(val, "{}"))
+ lines := strings.Split(val, "\n")
+ for i, l := range lines {
+ lines[i] = strings.TrimSpace(l)
+ }
+ val = strings.Join(lines, "\n")
+
+ val = jsonAttrRe.ReplaceAllString(val, "$1")
+
+ el.Classes = append(el.Classes, strings.Fields(val)...)
}
- val = strings.Join(lines, "\n")
- val = jsonAttrRe.ReplaceAllString(val, "$1")
- el.Classes = append(el.Classes, strings.Fields(val)...)
+ // Also add single quoted strings.
+ // This may introduce some false positives, but it covers some missing cases in the above.
+ // E.g. AlpinesJS' :class="isTrue 'class1' : 'class2'"
+ el.Classes = append(el.Classes, extractSingleQuotedStrings(val)...)
}
}
}
@@ -519,3 +529,29 @@ LOOP:
func isSpace(b byte) bool {
return b == ' ' || b == '\t' || b == '\n'
}
+
+func extractSingleQuotedStrings(s string) []string {
+ var (
+ inQuote bool
+ lo int
+ hi int
+ )
+
+ var words []string
+
+ for i, r := range s {
+ switch {
+ case r == '\'':
+ if !inQuote {
+ inQuote = true
+ lo = i + 1
+ } else {
+ inQuote = false
+ hi = i
+ words = append(words, strings.Fields(s[lo:hi])...)
+ }
+ }
+ }
+
+ return words
+}
diff --git a/publisher/htmlElementsCollector_test.go b/publisher/htmlElementsCollector_test.go
index 3047d5ca9..3cd834acb 100644
--- a/publisher/htmlElementsCollector_test.go
+++ b/publisher/htmlElementsCollector_test.go
@@ -99,6 +99,8 @@ func TestClassCollector(t *testing.T) {
pl-2: b == 3,
'text-gray-600': (a > 1)
}" class="block w-36 cursor-pointer pr-3 no-underline capitalize"></a>`, f("a", "block capitalize cursor-pointer no-underline pl-2 pl-3 pr-3 text-a text-b text-gray-600 w-36", "")},
+ {"AlpineJS bind 6", `<button :class="isActive(32) ? 'border-gray-500 bg-white pt border-t-2' : 'border-transparent hover:bg-gray-100'"></button>`, f("button", "bg-white border-gray-500 border-t-2 border-transparent hover:bg-gray-100 pt", "")},
+ {"AlpineJS bind 7", `<button :class="{ 'border-gray-500 bg-white pt border-t-2': isActive(32), 'border-transparent hover:bg-gray-100': !isActive(32) }"></button>`, f("button", "bg-white border-gray-500 border-t-2 border-transparent hover:bg-gray-100 pt", "")},
{"AlpineJS transition 1", `<div x-transition:enter-start="opacity-0 transform mobile:-translate-x-8 sm:-translate-y-8">`, f("div", "mobile:-translate-x-8 opacity-0 sm:-translate-y-8 transform", "")},
{"Vue bind", `<div v-bind:class="{ active: isActive }"></div>`, f("div", "active", "")},
// Issue #7746
@@ -136,7 +138,9 @@ func TestClassCollector(t *testing.T) {
{minify: true},
} {
- c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
+ name := fmt.Sprintf("%s--minify-%t", test.name, variant.minify)
+
+ c.Run(name, func(c *qt.C) {
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
config.BuildStats{Enable: true},
))