diff options
author | Bjørn Erik Pedersen <[email protected]> | 2023-07-19 17:32:19 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2023-07-19 19:50:37 +0200 |
commit | d8c94c354afb286b4fba9b883e49c1bd2c326bb3 (patch) | |
tree | f5bbfecaa72eee43fd5b004452dafd7753f72ad5 /publisher | |
parent | 6bbec9001445f623cead19de6811ee960cc53a10 (diff) | |
download | hugo-d8c94c354afb286b4fba9b883e49c1bd2c326bb3.tar.gz hugo-d8c94c354afb286b4fba9b883e49c1bd2c326bb3.zip |
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`
Diffstat (limited to 'publisher')
-rw-r--r-- | publisher/htmlElementsCollector.go | 56 | ||||
-rw-r--r-- | publisher/htmlElementsCollector_test.go | 6 |
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}, )) |