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

  1. Use only the documented Loader constants.
  2. Validate deserialized loader values against an allowlist of constants.
  3. Do not cast integers to Loader.
  4. 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

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.