tpl/css: Support @import "hugo:vars" for CSS custom properties in css.Build

Closes #14699

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-04-01 17:20:43 +02:00
parent 303e443ea7
commit 5d09b5e32a
3 changed files with 182 additions and 2 deletions
+3
View File
@@ -207,6 +207,9 @@ type ExternalOptions struct {
// See https://esbuild.github.io/api/#jsx-import-source
JSXImportSource string
// User defined CSS variables. Will be available as CSS global scope CSS variables via @import "hugo:vars".
Vars map[string]any
// There is/was a bug in WebKit with severe performance issue with the tracking
// of TDZ checks in JavaScriptCore.
//
+54 -2
View File
@@ -19,10 +19,12 @@ import (
"os"
"path/filepath"
"slices"
"sort"
"strings"
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
@@ -34,12 +36,13 @@ const (
NsHugoImport = "ns-hugo-imp"
NsHugoImportResolveFunc = "ns-hugo-imp-func"
nsHugoParams = "ns-hugo-params"
nsHugoVars = "ns-hugo-vars"
pathHugoConfigParams = "@params/config"
stdinImporter = "<stdin>"
)
var hugoNamespaces = []string{NsHugoImport, NsHugoImportResolveFunc, nsHugoParams}
var hugoNamespaces = []string{NsHugoImport, NsHugoImportResolveFunc, nsHugoParams, nsHugoVars}
const (
PrefixHugoVirtual = "__hu_v"
@@ -371,5 +374,54 @@ func createBuildPlugins(rs *resources.Spec, assetsResolver *fsResolver, depsMana
},
}
return []api.Plugin{importResolver, paramsPlugin}, nil
varsPlugin := api.Plugin{
Name: "hugo-vars-plugin",
Setup: func(build api.PluginBuild) {
build.OnResolve(api.OnResolveOptions{Filter: `^hugo:vars$`},
func(args api.OnResolveArgs) (api.OnResolveResult, error) {
return api.OnResolveResult{
Path: args.Path,
Namespace: nsHugoVars,
}, nil
})
build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: nsHugoVars},
func(args api.OnLoadArgs) (api.OnLoadResult, error) {
return api.OnLoadResult{
Contents: createCSSVarsStyleSheet(opts.Vars),
Loader: api.LoaderCSS,
}, nil
})
},
}
return []api.Plugin{importResolver, paramsPlugin, varsPlugin}, nil
}
// createCSSVarsStyleSheet creates a CSS custom properties stylesheet from the given vars.
// The result is a :root block with CSS custom properties.
func createCSSVarsStyleSheet(vars map[string]any) *string {
if len(vars) == 0 {
// We need to return a non-nil pointer to an empty string to avoid ESBuild treating this as a missing file.
s := ""
return &s
}
var varsSlice []string
for k, v := range vars {
if !strings.HasPrefix(k, "--") {
k = "--" + k
}
switch v.(type) {
case css.QuotedString:
// E.g. Arial, sans-serif.
varsSlice = append(varsSlice, fmt.Sprintf(" %s: %q;", k, v))
default:
varsSlice = append(varsSlice, fmt.Sprintf(" %s: %v;", k, v))
}
}
sort.Strings(varsSlice)
s := ":root {\n" + strings.Join(varsSlice, "\n") + "\n}\n"
return &s
}