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

Thrown by the context's `watch()` method (lib/shared/common.ts:1068) when `streamIn.hasFS` is false. Watch mode monitors source files for changes and triggers rebuilds, which fundamentally requires filesystem access. The browser/WASM build sets `hasFS: false` (lib/npm/browser.ts:142), so watch is unavailable there.

Solutions

  1. Use the native node esbuild package where `hasFS` is true so `watch()` works.
  2. Implement your own change detection at a higher level (e.g. re-run `rebuild()` on external events) instead of relying on esbuild's `watch()`.
  3. Guard the `watch()` call with an environment check and skip it in browser contexts.

Example fix

// before
const ctx = await esbuild.context(opts);
await ctx.watch();
// after (browser/wasm — watch not supported)
const ctx = await esbuild.context(opts);
await ctx.rebuild();
// implement your own change triggering around ctx.rebuild()
Defensive patterns

Strategy: validation

Validate before calling

const canWatch = typeof process !== 'undefined' && !!process.versions && !!process.versions.node;
if (canWatch) await ctx.watch();

Type guard

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

Prevention

When it happens

Trigger: Creating a build context via the browser/WASM esbuild API and then calling `context.watch()` on the returned context object.

Common situations: Reusing a dev-server/watch script that worked in Node inside a browser playground; building a web-based live-reload tool around esbuild-wasm.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/40281503b4ccb44c. Report an issue: GitHub.

Appendix: source

Thrown at lib/shared/common.ts:1068

                  // 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 f6058f8364)