Files

91 lines
3.7 KiB
HTML

{{/* 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 }}