tpl/css: Allow the user to override single loader entries

Fixes #14623

 Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-03-14 11:35:33 +01:00
parent e431f90bc6
commit 623722bb07
3 changed files with 54 additions and 24 deletions
+14 -19
View File
@@ -289,8 +289,19 @@ OUTER:
}
var loaders map[string]api.Loader
if opts.Loaders != nil {
if opts.IsCSS {
loaders = make(map[string]api.Loader)
// Add default CSS file loaders.
// May be overridden by opts.Loaders.
for ext, loader := range extensionToLoaderMapCSS {
loaders[ext] = loader
}
}
if opts.Loaders != nil {
if loaders == nil {
loaders = make(map[string]api.Loader)
}
for k, v := range opts.Loaders {
loader, found := nameLoader[v]
if !found {
@@ -299,15 +310,6 @@ OUTER:
}
loaders[k] = loader
}
} else if opts.IsCSS {
loaders = make(map[string]api.Loader)
// For CSS builds, default to the file loader for common static
// file extensions so that url() references are handled correctly
// even when resolved by ESBuild's native resolver.
// See #14619.
for _, ext := range defaultCSSFileLoaderExts {
loaders[ext] = api.LoaderFile
}
}
mediaType := opts.MediaType
@@ -471,17 +473,10 @@ func (o Options) loaderFromFilename(filename string) api.Loader {
return l
}
}
if o.IsCSS {
l, found := extensionToLoaderMapCSS[ext]
if found {
if !o.IsCSS {
if l, found := extensionToLoaderMapJS[ext]; found {
return l
}
} else {
l, found := extensionToLoaderMapJS[ext]
if found {
return l
}
}
return api.LoaderDefault
}
+14 -5
View File
@@ -60,12 +60,21 @@ var extensionToLoaderMapJS = map[string]api.Loader{
var extensionToLoaderMapCSS = map[string]api.Loader{
".css": api.LoaderCSS,
}
// Common static file extensions that should use the file loader in CSS builds.
var defaultCSSFileLoaderExts = []string{
".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".avif",
".woff", ".woff2", ".ttf", ".eot", ".otf",
// Common static file extensions that should use the file loader in CSS builds.
".png": api.LoaderFile,
".jpg": api.LoaderFile,
".jpeg": api.LoaderFile,
".gif": api.LoaderFile,
".svg": api.LoaderFile,
".webp": api.LoaderFile,
".avif": api.LoaderFile,
".woff": api.LoaderFile,
".woff2": api.LoaderFile,
".ttf": api.LoaderFile,
".eot": api.LoaderFile,
".otf": api.LoaderFile,
}
// This is a common sub-set of ESBuild's default extensions.
+26
View File
@@ -313,6 +313,32 @@ div {
b.AssertFileExists("public/css/issue14619-NJRUOINY.png", true)
}
// Issue #14623
func TestCSSBuildLoadersPartial(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/home.html --
{{ $opts := dict "loaders" (dict ".svg" "dataurl") }}
{{ with resources.Get "css/main.css" | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{ end }}
-- assets/css/main.css --
body { background-image: url('images/pixel.png'); }
div { background-image: url('images/foo.svg'); }
-- assets/images/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/images/foo.svg --
SVG file.
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileExists("public/css/pixel-NJRUOINY.png", true)
b.AssertFileContent("public/css/main.css", `url(data:image/svg`) // svg should be inlined as dataurl.
b.AssertFileContent("public/css/main.css", `pixel-NJRUOINY.png`) // png should be referenced as a hashed file, not inlined.
}
func TestCSSBuildBootstrapFromNPM(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()