evanw/esbuild · critical
Invalid loader
Error message
Invalid loader
What it means
A panic raised by validateLoader when the Loader value is not one of the documented loader constants (LoaderBase64, LoaderBinary, LoaderCopy, LoaderCSS, LoaderDataURL, LoaderDefault, LoaderEmpty, LoaderFile, LoaderGlobalCSS, LoaderJS, LoaderJSON, LoaderJSX, LoaderLocalCSS, LoaderNone, LoaderText, LoaderTS, LoaderTSX). The JS API maps loader strings to these constants and cannot reach the panic; only Go-API misuse (out-of-range Loader) can.
Source
Thrown at pkg/api/api_impl.go:286
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 6ff1d8b0d8)
Solutions
- Use only the documented Loader constants.
- Validate deserialized loader values against an allowlist of constants.
- Do not cast integers to Loader.
- Pin a single esbuild version.
Example fix
// before
opts.Loader = map[string]api.Loader{ ".x": api.Loader(99) }
// after
opts.Loader = map[string]api.Loader{ ".x": api.LoaderText } Defensive patterns
Strategy: type-guard
Validate before calling
func validLoader(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
} Type guard
type Loader = 'base64' | 'binary' | 'copy' | 'css' | 'data-url' | 'default' | 'empty' | 'file' | 'global-css' | 'js' | 'json' | 'jsx' | 'local-css' | 'text' | 'ts' | 'tsx'
const LOADERS = new Set<Loader>(['base64','binary','copy','css','data-url','default','empty','file','global-css','js','json','jsx','local-css','text','ts','tsx'])
function isLoader(v: unknown): v is Loader {
return typeof v === 'string' && (LOADERS as Set<string>).has(v)
} Prevention
- Use only documented Loader constants.
- Validate per-extension loader maps built from user config.
- Do not cast integers to Loader.
When it happens
Trigger: Go API: BuildOptions.Loader['.x'] = Loader(99). The JS API's Loader union is closed and validated before reaching validateLoader. Also fires when a loader value is computed via reflection without bounds checking.
Common situations: Reflective config decoding; fork that adds/removes a loader without updating the switch; unsafe casts; mapping a file extension to a loader via a numeric lookup table.
Related errors
AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03).
Data as JSON: /data/errors/86f2856c303982bc.json.
Report an issue: GitHub.