From 623722bb07bb00e55b1793a4622f422736881c9b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 14 Mar 2026 11:35:33 +0100 Subject: [PATCH] tpl/css: Allow the user to override single loader entries Fixes #14623 Co-authored-by: Joe Mooring --- internal/js/esbuild/options.go | 33 +++++++++++++------------------ internal/js/esbuild/resolve.go | 19 +++++++++++++----- tpl/css/build_integration_test.go | 26 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/internal/js/esbuild/options.go b/internal/js/esbuild/options.go index e5309fbbe..1e522f0ea 100644 --- a/internal/js/esbuild/options.go +++ b/internal/js/esbuild/options.go @@ -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 } diff --git a/internal/js/esbuild/resolve.go b/internal/js/esbuild/resolve.go index 3bdfda2da..4349bdcc2 100644 --- a/internal/js/esbuild/resolve.go +++ b/internal/js/esbuild/resolve.go @@ -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. diff --git a/tpl/css/build_integration_test.go b/tpl/css/build_integration_test.go index a36ed0774..dd674d63d 100644 --- a/tpl/css/build_integration_test.go +++ b/tpl/css/build_integration_test.go @@ -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 }} + +{{ 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() -- 2.39.5