]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
publisher: Improve class collector for dynamic classes
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 19 Jul 2023 15:32:19 +0000 (17:32 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 19 Jul 2023 17:50:37 +0000 (19:50 +0200)
E.g.

* AlpinesJS' :class="isTrue 'class1' : 'class2'"
* And dynamic classes with colon in them, e.g. `hover:bg-white`

publisher/htmlElementsCollector.go
publisher/htmlElementsCollector_test.go

index c9d81818ce40f7659abdee073545ee0ab30c489f..6c01fd8d9cbc1e1fb93d7ff66d84b2c3bcf0f9ee 100644 (file)
@@ -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
+}
index 3047d5ca99ec12485300661d0f08a52bf4030e4a..3cd834acbacfd299ec74fa20126f6d4b1a0231b5 100644 (file)
@@ -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},
                                ))