evanw/esbuild · critical
Invalid loader
Error message
Invalid loader
What it means
validateLoader (pkg/api/api_impl.go:299) panics when a Loader value is not one of LoaderNone..LoaderTSX (the 17-constant iota in api.go). The function maps the public Loader to the internal config.Loader used for parsing a file extension or stdin; an unknown value means the parser cannot be selected, so esbuild aborts. Called from validateLoaders (per extension in BuildOptions.Loader), from StdinOptions.Loader, and from TransformOptions.Loader.
Solutions
- Use only the api.Loader* constants; for unknown extensions default to api.LoaderDefault or api.LoaderNone.
- Bounds-check decoded integers against 0..16 (LoaderTSX) before casting.
- Store loader mappings keyed by name ("js","ts","css",...) and translate to constants at load.
- After re-vendoring esbuild, regenerate any bridge/table code (js_table.ts) and rebuild all consumers.
Example fix
// before
opts := api.BuildOptions{Loader: map[string]api.Loader{".custom": api.Loader(40)}}
// after
opts := api.BuildOptions{Loader: map[string]api.Loader{".custom": api.LoaderText}} Defensive patterns
Strategy: validation
Validate before calling
func checkLoader(l api.Loader) error {
switch l {
case api.LoaderNone, api.LoaderBase64, api.LoaderBinary, api.LoaderCopy,
api.LoaderCSS, api.LoaderDataURL, api.LoaderDefault, api.LoaderEmpty,
api.LoaderFile, api.LoaderGlobalCSS, api.LoaderJS, api.LoaderJSON,
api.LoaderJSX, api.LoaderLocalCSS, api.LoaderText, api.LoaderTS, api.LoaderTSX:
return nil
}
return fmt.Errorf("invalid loader %d (want 0..%d)", uint16(l), uint16(api.LoaderTSX))
}
for ext, l := range opts.Loader {
if err := checkLoader(l); err != nil { return fmt.Errorf("loader for %q: %w", ext, err) }
}
if opts.Stdin != nil {
if err := checkLoader(opts.Stdin.Loader); err != nil { return err }
} Type guard
func isValidLoader(l api.Loader) bool {
switch l {
case api.LoaderNone, api.LoaderBase64, api.LoaderBinary, api.LoaderCopy,
api.LoaderCSS, api.LoaderDataURL, api.LoaderDefault, api.LoaderEmpty,
api.LoaderFile, api.LoaderGlobalCSS, api.LoaderJS, api.LoaderJSON,
api.LoaderJSX, api.LoaderLocalCSS, api.LoaderText, api.LoaderTS, api.LoaderTSX:
return true
}
return false
} Prevention
- Loader is the longest esbuild enum and the most fragile across versions; regenerate any bridge after each re-vendor.
- Prefer named loader strings ("js","ts","css",...) in user config and translate to constants at the boundary.
- Default unknown extensions to LoaderDefault or LoaderNone rather than a guessed number.
- Add a test that iterates your Loader map and asserts each value is in range.
When it happens
Trigger: Passing an out-of-range integer for any Loader field, deserializing Loader maps from a numeric wire format, or bridging a different esbuild version where the iota ordering or count differs (this enum is the longest of the set, so it is the most sensitive to renumbering).
Common situations: Re-vendoring esbuild and forgetting to regenerate a JS/Go bridge that maps loader names to numbers, persisted configs that store loader as an int, or a typo like `LoaderJSON5` (no such constant). Also triggered by a `Loader` map entry whose value was set to a stale constant after an upgrade removed it.
Related errors
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/86f2856c303982bc.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/api/api_impl.go:299
return config.LoaderGlobalCSS
case LoaderJS:
return config.LoaderJS
case LoaderJSON:
return config.LoaderJSON
case LoaderJSX:
return config.LoaderJSX
case LoaderLocalCSS:
return config.LoaderLocalCSS
case LoaderNone:
return config.LoaderNone
case LoaderText:
return config.LoaderText
case LoaderTS:
return config.LoaderTS
case LoaderTSX:
return config.LoaderTSX
default:
panic("Invalid loader")
}
}
func extractPathStyle(absPaths AbsPaths, flag AbsPaths) logger.PathStyle {
if (absPaths & flag) != 0 {
return logger.AbsPath
} else {
return logger.RelPath
}
}
var versionRegex = regexp.MustCompile(`^([0-9]+)(?:\.([0-9]+))?(?:\.([0-9]+))?(-[A-Za-z0-9]+(?:\.[A-Za-z0-9]+)*)?$`)
func validateFeatures(log logger.Log, target Target, engines []Engine) (compat.JSFeature, compat.CSSFeature, map[css_ast.D]compat.CSSPrefix, string) {
if target == DefaultTarget && len(engines) == 0 {
return 0, 0, nil, ""
}
View on GitHub (pinned to f6058f8364)