{"id":"40281503b4ccb44c","repo":"evanw/esbuild","slug":"cannot-use-the-watch-api-in-this-environment","errorCode":null,"errorMessage":"Cannot use the \"watch\" API in this environment","messagePattern":"Cannot use the \"watch\" API in this environment","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/shared/common.ts","lineNumber":1064,"sourceCode":"                  // In that situation we didn't get an \"on-end\" message since\n                  // Go thought it wasn't necessary. In that situation, we\n                  // trigger another rebuild below so that Go will (almost\n                  // surely) send us an \"on-end\" message next time. I suspect\n                  // that this is a very rare case, so the performance impact\n                  // of building twice shouldn't really matter. It also only\n                  // happens when \"rebuild()\" is used with \"watch()\" and/or\n                  // \"serve()\".\n                  triggerAnotherBuild()\n                }\n              })\n            }\n            triggerAnotherBuild()\n          })\n          return latestResultPromise\n        },\n\n        watch: (options = {}) => new Promise((resolve, reject) => {\n          if (!streamIn.hasFS) throw new Error(`Cannot use the \"watch\" API in this environment`)\n          const keys: OptionKeys = {}\n          const delay = getFlag(options, keys, 'delay', mustBeInteger)\n          checkForInvalidFlags(options, keys, `in watch() call`)\n          const request: protocol.WatchRequest = {\n            command: 'watch',\n            key: buildKey,\n          }\n          if (delay) request.delay = delay\n          sendRequest<protocol.WatchRequest, null>(refs, request, error => {\n            if (error) reject(new Error(error))\n            else resolve(undefined)\n          })\n        }),\n\n        serve: (options = {}) => new Promise((resolve, reject) => {\n          if (!streamIn.hasFS) throw new Error(`Cannot use the \"serve\" API in this environment`)\n          const keys: OptionKeys = {}\n          const port = getFlag(options, keys, 'port', mustBeValidPortNumber)","sourceCodeStart":1046,"sourceCodeEnd":1082,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/lib/shared/common.ts#L1046-L1082","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run watch() only under the Node or Deno (native) entry where hasFS is true.","In the browser, implement your own change detection and call rebuild() manually instead of watch().","Switch the import from 'esbuild-wasm' to 'esbuild' when running on a Node host."],"exampleFix":"// before (browser wasm)\nconst ctx = await esbuild.context({ entryPoints: ['app.ts'] });\nawait ctx.watch();\n// after — poll/rebuild yourself, or run under node\nconst ctx = await esbuild.context({ entryPoints: ['app.ts'] });\n// no ctx.watch(); trigger rebuilds manually:\nawait ctx.rebuild();","handlingStrategy":"validation","validationCode":"const hasFS = typeof process !== 'undefined' && !!process.versions?.node;\nif (hasFS) {\n  await ctx.watch();\n} else {\n  // implement manual rebuild loop in the browser\n}","typeGuard":"function canWatch(): boolean {\n  return typeof process !== 'undefined' && typeof process.versions?.node === 'string';\n}","tryCatchPattern":"try {\n  await ctx.watch();\n} catch (e) {\n  if (/Cannot use the \"watch\" API/.test(e.message)) {\n    startManualWatcher();\n  } else throw e;\n}","preventionTips":["Gate watch() behind an environment check (process.versions.node) before calling.","Don't ship the browser/wasm entry where a Node dev-server script is expected.","Use rebuild() + your own change detection in sandboxed runtimes."],"tags":["environment","watch","filesystem","wasm"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}