{{/* Hugo Blox: Dynamic Tailwind @source generation for Hugo modules */}} {{/* Documentation: https://hugoblox.com/docs/ */}} {{/* License: https://github.com/HugoBlox/kit/blob/main/LICENSE.md */}} {{/* This partial generates Tailwind CSS @source directives that tell Tailwind where to scan for CSS classes. It provides a robust bridge between Hugo's virtual filesystem (which includes module mounts) and Tailwind's real filesystem scanner. */}} {{ $paths := slice }} {{/* 1. Always scan the site's source files. */}} {{ $paths = $paths | append `content/**/*.md` }} {{ $paths = $paths | append `layouts/**/*.html` }} {{ $paths = $paths | append `assets/js/**/*.js` }} {{ $paths = $paths | append `assets/js/**/*.jsx` }} {{ $paths = $paths | append `assets/js/**/*.tsx` }} {{ $paths = $paths | append `hugo_stats.json` }} {{/* 2. Crucially, scan Hugo's generated JS output from Preact components. These files are built by Hugo's `js.Build` into a predictable location within the project's `assets` directory, making them a reliable source for Tailwind to scan. This step happens before Tailwind runs, thanks to `templates.Defer` in `site_head.html`. */}} {{ $paths = $paths | append `assets/js/preact-built/**/*.js` }} {{/* 3. Comprehensive class extraction from Preact JSX/TSX source files. Hugo's virtual filesystem mounts block JSX/TSX into assets/js/hbx/blocks/. Tailwind's @source file paths only work on the real filesystem, but these files exist only in Hugo's virtual FS (or in a module cache for remote modules). So we read the file content via resources.Match and extract class candidates inline. Strategy: Extract ALL string literal content — not just class= attributes. This catches classes in any pattern: - Static attrs: class="gap-8 items-center" - JSX expressions: class={"gap-8 items-center"} - Template literals: class={`gap-8 ${cond ? "md:grid" : ""}`} - Utility fns: cn("gap-8", condition && "md:grid-cols-2") Tailwind's scanner automatically ignores non-class tokens (import paths, error messages, etc.), so false positives are harmless. */}} {{ $all_jsx := resources.Match "js/hbx/**/*.jsx" }} {{ $all_tsx := resources.Match "js/hbx/**/*.tsx" }} {{ $extracted_classes := slice }} {{ range $f := $all_jsx | append $all_tsx }} {{ $code := $f.Content }} {{/* A) Extract content of all double-quoted strings */}} {{ $dq_matches := findRE `"[^"]*"` $code }} {{ range $m := $dq_matches }} {{ $str := strings.TrimPrefix `"` (strings.TrimSuffix `"` $m) }} {{ range split $str " " }} {{ $t := strings.TrimSpace . }} {{/* Filter out tokens with unsafe characters (braces, quotes, parens) */}} {{ if and $t (not (findRE `[\{\}\(\)"';]` $t)) }} {{ $extracted_classes = $extracted_classes | append $t }} {{ end }} {{ end }} {{ end }} {{/* B) Extract static parts of template literals (backtick strings) */}} {{/* Removes ${...} interpolations, keeping surrounding text */}} {{ $bt_matches := findRE "`[^`]*`" $code }} {{ range $m := $bt_matches }} {{ $str := strings.TrimPrefix "`" (strings.TrimSuffix "`" $m) }} {{ $static := replaceRE `\$\{[^}]*\}` " " $str }} {{ range split $static " " }} {{ $t := strings.TrimSpace . }} {{/* Filter out tokens with unsafe characters (braces, quotes, parens) */}} {{ if and $t (not (findRE `[\{\}\(\)"';]` $t)) }} {{ $extracted_classes = $extracted_classes | append $t }} {{ end }} {{ end }} {{ end }} {{ end }} {{ $extracted_classes = $extracted_classes | uniq }} {{ range $class := $extracted_classes }} @source inline("{{ $class }}"); {{ end }} {{/* Generate the @source rules */}} {{ range $paths }} @source "{{ . }}"; {{ end }}