From a7cbcf15f0c5ef04b45350390e00e584e4d635b9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 13 Mar 2026 13:14:40 +0100 Subject: [PATCH] tpl/css: Make default loader resolution for CSS @import and url() always behave the same Before this commit, we did dynamic loader resolution for CSS bundling for resources resolved by Hugo while any fallback to ESBuild would fall back to a (potentially) empty loaders config. This revises the logic to always use a static list (see below) if `loaders` is not set. This should be easier do document and less confusing for the end user. ```` ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".avif", ".woff", ".woff2", ".ttf", ".eot", ".otf" ```` Fixes #14619 --- internal/js/esbuild/options.go | 15 +++++++++++---- internal/js/esbuild/resolve.go | 6 ++++++ tpl/css/build_integration_test.go | 7 +++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/internal/js/esbuild/options.go b/internal/js/esbuild/options.go index 32d2a6251..e5309fbbe 100644 --- a/internal/js/esbuild/options.go +++ b/internal/js/esbuild/options.go @@ -299,6 +299,15 @@ 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 @@ -467,16 +476,14 @@ func (o Options) loaderFromFilename(filename string) api.Loader { if found { return l } - // For CSS builds, handling, default to the file loader for unknown extensions. - return api.LoaderFile } else { l, found := extensionToLoaderMapJS[ext] if found { return l } - } - return api.LoaderJS + } + return api.LoaderDefault } func (opts *Options) validate() error { diff --git a/internal/js/esbuild/resolve.go b/internal/js/esbuild/resolve.go index e9cfa7f9b..3bdfda2da 100644 --- a/internal/js/esbuild/resolve.go +++ b/internal/js/esbuild/resolve.go @@ -62,6 +62,12 @@ 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", +} + // This is a common sub-set of ESBuild's default extensions. // We assume that imports of JSON, CSS etc. will be using their full // name with extension. diff --git a/tpl/css/build_integration_test.go b/tpl/css/build_integration_test.go index c294452b7..718357de4 100644 --- a/tpl/css/build_integration_test.go +++ b/tpl/css/build_integration_test.go @@ -270,10 +270,15 @@ func TestCSSBuildLoadersDefault(t *testing.T) { -- hugo.toml -- -- assets/a/pixel.png -- iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- static/b/issue14619.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== -- assets/css/main.css -- body { background-image: url("a/pixel.png"); } +div { + background-image: url("static/b/issue14619.png"); +} -- layouts/home.html -- {{ with resources.Get "css/main.css" }} {{ with . | css.Build (dict "minify" true) }} @@ -285,6 +290,8 @@ body { b := hugolib.Test(t, files, hugolib.TestOptOsFs()) b.AssertFileContent("public/css/main.css", `./pixel-NJRUOINY.png`) b.AssertFileExists("public/css/pixel-NJRUOINY.png", true) + b.AssertFileContent("public/css/main.css", `url("./issue14619-NJRUOINY.png")`) + b.AssertFileExists("public/css/issue14619-NJRUOINY.png", true) } func TestCSSBuildBootstrapFromNPM(t *testing.T) { -- 2.39.5