evanw/esbuild · error · Error

Cannot use the "watch" API in this environment

Error message

Cannot use the "watch" API in this environment

What it means

The watch() API tells esbuild's native binary to keep running and rebuild on file changes; it requires the Go process to install filesystem watchers, which only works when hasFS is true. The guard at lib/shared/common.ts:1064 rejects watch() in the browser/wasm builds (hasFS:false). The native binary's watcher cannot observe the browser's virtual filesystem.

Source

Thrown at lib/shared/common.ts:1064

                  // In that situation we didn't get an "on-end" message since
                  // Go thought it wasn't necessary. In that situation, we
                  // trigger another rebuild below so that Go will (almost
                  // surely) send us an "on-end" message next time. I suspect
                  // that this is a very rare case, so the performance impact
                  // of building twice shouldn't really matter. It also only
                  // happens when "rebuild()" is used with "watch()" and/or
                  // "serve()".
                  triggerAnotherBuild()
                }
              })
            }
            triggerAnotherBuild()
          })
          return latestResultPromise
        },

        watch: (options = {}) => new Promise((resolve, reject) => {
          if (!streamIn.hasFS) throw new Error(`Cannot use the "watch" API in this environment`)
          const keys: OptionKeys = {}
          const delay = getFlag(options, keys, 'delay', mustBeInteger)
          checkForInvalidFlags(options, keys, `in watch() call`)
          const request: protocol.WatchRequest = {
            command: 'watch',
            key: buildKey,
          }
          if (delay) request.delay = delay
          sendRequest<protocol.WatchRequest, null>(refs, request, error => {
            if (error) reject(new Error(error))
            else resolve(undefined)
          })
        }),

        serve: (options = {}) => new Promise((resolve, reject) => {
          if (!streamIn.hasFS) throw new Error(`Cannot use the "serve" API in this environment`)
          const keys: OptionKeys = {}
          const port = getFlag(options, keys, 'port', mustBeValidPortNumber)

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Run watch() only under the Node or Deno (native) entry where hasFS is true.
  2. In the browser, implement your own change detection and call rebuild() manually instead of watch().
  3. Switch the import from 'esbuild-wasm' to 'esbuild' when running on a Node host.

Example fix

// before (browser wasm)
const ctx = await esbuild.context({ entryPoints: ['app.ts'] });
await ctx.watch();
// after — poll/rebuild yourself, or run under node
const ctx = await esbuild.context({ entryPoints: ['app.ts'] });
// no ctx.watch(); trigger rebuilds manually:
await ctx.rebuild();
Defensive patterns

Strategy: validation

Validate before calling

const hasFS = typeof process !== 'undefined' && !!process.versions?.node;
if (hasFS) {
  await ctx.watch();
} else {
  // implement manual rebuild loop in the browser
}

Type guard

function canWatch(): boolean {
  return typeof process !== 'undefined' && typeof process.versions?.node === 'string';
}

Try / catch

try {
  await ctx.watch();
} catch (e) {
  if (/Cannot use the "watch" API/.test(e.message)) {
    startManualWatcher();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling ctx.watch() on a context created from esbuild-wasm / @esbuild/wasm in the browser. Calling watch() in Deno wasm mode (lib/deno/wasm.ts:132 hasFS:false). Calling watch() inside a worker on the browser entrypoint.

Common situations: Online playground / in-browser bundler tries to replicate a dev server experience. Devs assume the WASM build is a drop-in replacement for the CLI. SSR edge runtimes that load esbuild but have no fs.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/40281503b4ccb44c.json. Report an issue: GitHub.