evanw/esbuild · error · Error
The "wasmURL" option only works in the browser
Error message
The "wasmURL" option only works in the browser
What it means
In the node entry, initialize() (lib/npm/node.ts:232) validates options and explicitly rejects wasmURL, throwing because node does not use WebAssembly for esbuild (it spawns the native binary directly). wasmURL is a browser-only option; passing it in node indicates a config meant for esbuild-wasm/esbuild browser being reused under native esbuild.
Solutions
- Remove wasmURL from the options passed to initialize in node (node does not need or accept it).
- Omit the initialize() call entirely in node; the service auto-starts on first use.
- If you truly need WebAssembly in node, use the esbuild-wasm package instead of esbuild.
Example fix
// before
import * as esbuild from 'esbuild' // native node build
await esbuild.initialize({ wasmURL: '/esbuild.wasm' }) // throws
// after
import * as esbuild from 'esbuild'
await esbuild.build({ entryPoints: ['a.ts'] }) // auto-starts, no initialize needed Defensive patterns
Strategy: validation
Validate before calling
// Strip browser-only options before calling initialize in node.
function nodeInitOptions(opts) {
const { wasmURL: _drop1, wasmModule: _drop2, worker: _drop3, ...rest } = opts || {}
return rest
}
// In node you usually skip initialize() entirely; the service auto-starts. Type guard
// Ensure an options object is free of browser-only keys for node.
function hasNoBrowserOnlyOptions(o: any): o is Record<string, never> {
return o.wasmURL === undefined && o.wasmModule === undefined && o.worker === undefined
} Prevention
- Do not share a single config object verbatim between esbuild (node) and esbuild-wasm (browser).
- In node, omit initialize() altogether; the native service starts automatically.
- Use esbuild-wasm if you actually need WebAssembly in node.
When it happens
Trigger: Calling esbuild.initialize({ wasmURL: '...' }) (or a shared config object containing wasmURL) while importing the native node esbuild package.
Common situations: Shared initialization helper used by both browser (esbuild-wasm) and node (esbuild) code paths that unconditionally passes browser options; copy-pasting a browser example into node.
Related errors
- The "wasmModule" option only works in the browser
- Must provide either the "wasmURL" option or the…
- Failed to install package
- Invalid platform
- The "analyzeMetafileSync" API only works in node
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/f3ff1c4fae1949da.
Report an issue: GitHub.
Appendix: source
Thrown at lib/npm/node.ts:234
callName: 'analyzeMetafileSync',
refs: null,
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
options,
callback: (err, res) => { if (err) throw err; result = res! },
}))
return result!
}
export const stop = async () => {
if (stopService) await stopService()
if (workerThreadService) workerThreadService.stop()
}
let initializeWasCalled = false
export let initialize: typeof types.initialize = options => {
options = common.validateInitializeOptions(options || {})
if (options.wasmURL) throw new Error(`The "wasmURL" option only works in the browser`)
if (options.wasmModule) throw new Error(`The "wasmModule" option only works in the browser`)
if (options.worker) throw new Error(`The "worker" option only works in the browser`)
if (initializeWasCalled) throw new Error('Cannot call "initialize" more than once')
ensureServiceIsRunning()
initializeWasCalled = true
return Promise.resolve()
}
interface Service {
build: typeof types.build
context: typeof types.context
transform: typeof types.transform
formatMessages: typeof types.formatMessages
analyzeMetafile: typeof types.analyzeMetafile
}
let defaultWD = process.cwd()
let longLivedService: Service | undefinedView on GitHub (pinned to f6058f8364)