From e431f90bc6fff33742c468f25b77689852930284 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 13 Mar 2026 16:30:06 +0100 Subject: [PATCH] tpl/css: Fix external source maps Fixes #14620 --- internal/js/esbuild/build.go | 7 +++- internal/js/esbuild/sourcemap.go | 36 +++++++++------- .../js/js_integration_test.go | 2 +- tpl/css/build_integration_test.go | 41 ++++++++++++++----- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/internal/js/esbuild/build.go b/internal/js/esbuild/build.go index cf88276c3..a3173a22c 100644 --- a/internal/js/esbuild/build.go +++ b/internal/js/esbuild/build.go @@ -225,7 +225,12 @@ func (c *BuildClient) Build(opts Options) (api.BuildResult, error) { } return ss } - return "" + if strings.HasPrefix(s, opts.OutDir) { + // This is an output file, not a source file. + return "" + } + // s is already the absolute filename set by the Hugo resolve plugin. + return s } return s }); err != nil { diff --git a/internal/js/esbuild/sourcemap.go b/internal/js/esbuild/sourcemap.go index 647f0c081..036cab29c 100644 --- a/internal/js/esbuild/sourcemap.go +++ b/internal/js/esbuild/sourcemap.go @@ -46,7 +46,27 @@ func fixSourceMap(s []byte, resolve func(string) string) ([]byte, error) { return nil, err } - sm.Sources = fixSourceMapSources(sm.Sources, resolve) + hasSourcesContent := len(sm.SourcesContent) == len(sm.Sources) + + var sources []string + var sourcesContent []string + for i, src := range sm.Sources { + if resolved := resolve(src); resolved != "" { + // Absolute filenames works fine on U*ix (tested in Chrome on MacOs), but works very poorly on Windows (again Chrome). + // So, convert it to a URL. + if u, err := paths.UrlFromFilename(resolved); err == nil { + sources = append(sources, u.String()) + if hasSourcesContent { + sourcesContent = append(sourcesContent, sm.SourcesContent[i]) + } + } + } + } + + sm.Sources = sources + if hasSourcesContent { + sm.SourcesContent = sourcesContent + } b, err := json.Marshal(sm) if err != nil { @@ -56,20 +76,6 @@ func fixSourceMap(s []byte, resolve func(string) string) ([]byte, error) { return b, nil } -func fixSourceMapSources(s []string, resolve func(string) string) []string { - var result []string - for _, src := range s { - if s := resolve(src); s != "" { - // Absolute filenames works fine on U*ix (tested in Chrome on MacOs), but works very poorly on Windows (again Chrome). - // So, convert it to a URL. - if u, err := paths.UrlFromFilename(s); err == nil { - result = append(result, u.String()) - } - } - } - return result -} - // Used in tests. func SourcesFromSourceMap(s string) []string { var sm sourceMap diff --git a/resources/resource_transformers/js/js_integration_test.go b/resources/resource_transformers/js/js_integration_test.go index 8452c7c40..ee84ee752 100644 --- a/resources/resource_transformers/js/js_integration_test.go +++ b/resources/resource_transformers/js/js_integration_test.go @@ -228,7 +228,7 @@ TS2: {{ template "print" $ts2 }} } } - checkMap("public/js/main.js.map", 4) + checkMap("public/js/main.js.map", 5) } func TestBuildError(t *testing.T) { diff --git a/tpl/css/build_integration_test.go b/tpl/css/build_integration_test.go index 718357de4..a36ed0774 100644 --- a/tpl/css/build_integration_test.go +++ b/tpl/css/build_integration_test.go @@ -17,8 +17,10 @@ import ( "strings" "testing" + qt "github.com/frankban/quicktest" "github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/internal/js/esbuild" ) func TestCSSBuild(t *testing.T) { @@ -165,30 +167,45 @@ span { background: green; } article { background: yellow; } -- layouts/home.html -- {{ with resources.Get "css/main.css" }} -{{ $opts := (dict "minify" false "sourceMap" "SOURCE_MAP" "sourcesContent" SOURCES_CONTENT "loaders" (dict ".png" "dataurl"))}} +{{ $opts := (dict "minify" MINIFY "sourceMap" "SOURCE_MAP" "sourcesContent" SOURCES_CONTENT "loaders" (dict ".png" "dataurl"))}} {{ with . | css.Build $opts }} {{ end }} {{ end }} ` - r := strings.NewReplacer( - "SOURCE_MAP", "linked", - "SOURCES_CONTENT", "true", + var ( + r *strings.Replacer + files string + b *hugolib.IntegrationTestBuilder ) - files := r.Replace(filesTemplate) + for _, minify := range []string{"true", "false"} { - b := hugolib.Test(t, files, hugolib.TestOptOsFs()) - b.AssertFileContent("public/css/main.css", "/*# sourceMappingURL=main.css.map */") - b.AssertFileContent("public/css/main.css.map", - `"sourcesContent":["p { background: red; }"`, - "AAAA;AAAI,cAAY;AAAK", - ) + r = strings.NewReplacer( + "SOURCE_MAP", "linked", + "SOURCES_CONTENT", "true", + "MINIFY", minify, + ) + + files = r.Replace(filesTemplate) + + b = hugolib.Test(t, files, hugolib.TestOptOsFs()) + b.AssertFileContent("public/css/main.css", "/*# sourceMappingURL=main.css.map */") + b.AssertFileContent("public/css/main.css.map", + `"sourcesContent":["`, + `"mappings":"`, + ) + + sources := esbuild.SourcesFromSourceMap(b.FileContent("public/css/main.css.map")) + // main.css + foo.css + bar.css + baz.css = 4 sources. + b.Assert(len(sources), qt.Equals, 4) + } r = strings.NewReplacer( "SOURCE_MAP", "external", "SOURCES_CONTENT", "true", + "MINIFY", "false", ) files = r.Replace(filesTemplate) @@ -203,6 +220,7 @@ article { background: yellow; } r = strings.NewReplacer( "SOURCE_MAP", "external", "SOURCES_CONTENT", "false", + "MINIFY", "false", ) files = r.Replace(filesTemplate) @@ -216,6 +234,7 @@ article { background: yellow; } r = strings.NewReplacer( "SOURCE_MAP", "inline", "SOURCES_CONTENT", "false", + "MINIFY", "false", ) files = r.Replace(filesTemplate) -- 2.39.5