js: Return error for missing batch imports

A stale or removed resource used by js.Batch could panic while the esbuild import loader read its content during a rebuild. Return the read error through the loader so esbuild reports a normal build error and a later rebuild can recover when the file returns.

Closes #13737
This commit is contained in:
Alexandre Vaz
2026-05-13 12:15:14 -03:00
committed by GitHub
parent f0cfc28c00
commit 9e64953338
4 changed files with 30 additions and 8 deletions
+5 -6
View File
@@ -45,7 +45,6 @@ import (
"github.com/gohugoio/hugo/resources/resource_factories/create"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
)
var _ js.Batcher = (*batcher)(nil)
@@ -567,17 +566,17 @@ func (b *batcher) doBuild(ctx context.Context) (*Package, error) {
}
return ""
},
ImportOnLoadFunc: func(args api.OnLoadArgs) string {
ImportOnLoadFunc: func(args api.OnLoadArgs) (string, error) {
imp := args.Path
if r, found := state.importResource.Get(imp); found {
content, err := r.(resource.ContentProvider).Content(ctx)
content, err := resources.InternalResourceSourceContent(ctx, r)
if err != nil {
panic(err)
return "", fmt.Errorf("failed to read import %q: %w", resources.InternalResourceSourcePathBestEffort(r), err)
}
return cast.ToString(content)
return content, nil
}
return ""
return "", nil
},
ImportParamsOnLoadFunc: func(args api.OnLoadArgs) json.RawMessage {
if importContext, found := state.importerImportContext.Get(args.Path); found {
@@ -261,6 +261,26 @@ func TestBatchRenameBundledScript(t *testing.T) {
b.Build()
}
func TestBatchMissingImportedAssetIssue13737(t *testing.T) {
files := jsBatchFilesTemplate
b := hugolib.TestRunning(t, files, hugolib.TestOptWithOSFs())
b.AssertFileContent("public/mybatch/mygroup.js", "Hello, Main")
filename := filepath.Join(b.Cfg.WorkingDir, "assets/js/main.js")
content, err := os.ReadFile(filename)
b.Assert(err, qt.IsNil)
b.Assert(os.Remove(filename), qt.IsNil)
_, err = b.BuildE()
b.Assert(err, qt.IsNotNil)
b.Assert(err.Error(), qt.Contains, "failed to read import")
b.Assert(err.Error(), qt.Contains, "main.js")
b.Assert(os.WriteFile(filename, content, 0o666), qt.IsNil)
b.Build()
b.AssertFileContent("public/mybatch/mygroup.js", "Hello, Main")
}
func TestBatchErrorScriptResourceNotSet(t *testing.T) {
files := strings.Replace(jsBatchFilesTemplate, `(resources.Get "js/main.js")`, `(resources.Get "js/doesnotexist.js")`, 1)
b, err := hugolib.TestE(t, files, hugolib.TestOptWithOSFs())
+1 -1
View File
@@ -248,7 +248,7 @@ type InternalOptions struct {
TsConfig string
EntryPoints []string
ImportOnResolveFunc func(string, api.OnResolveArgs) string
ImportOnLoadFunc func(api.OnLoadArgs) string
ImportOnLoadFunc func(api.OnLoadArgs) (string, error)
ImportParamsOnLoadFunc func(args api.OnLoadArgs) json.RawMessage
ErrorMessageResolveFunc func(api.Message) *ErrorMessageResolved
ResolveSourceMapSource func(string) string // Used to resolve paths in error source maps.
+4 -1
View File
@@ -314,7 +314,10 @@ func createBuildPlugins(rs *resources.Spec, assetsResolver *fsResolver, depsMana
})
build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: NsHugoImportResolveFunc},
func(args api.OnLoadArgs) (api.OnLoadResult, error) {
c := opts.ImportOnLoadFunc(args)
c, err := opts.ImportOnLoadFunc(args)
if err != nil {
return api.OnLoadResult{}, err
}
if c == "" {
return api.OnLoadResult{}, fmt.Errorf("ImportOnLoadFunc failed to resolve %q", args.Path)
}