esbuild "Dynamic require of 'fs' is not supported" in CI
esbuild bundled CommonJS code into an ESM output where require does not exist natively. A runtime require(...) of a Node builtin then throws "Dynamic require is not supported".
What this error means
The bundle builds but crashes at runtime in CI with "Dynamic require of 'fs' is not supported". A dependency calls require() dynamically and the ESM output has no require to satisfy it.
Error: Dynamic require of "fs" is not supported
at file:///work/repo/dist/index.js:1:1234Diagnose it: what is different about the runner?
A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.
- run: |
node --version && npm --version
echo "NODE_ENV=$NODE_ENV CI=$CI"
nproc && free -h && df -h /
ls -la node_modules/.bin | headCommon causes
CJS bundled to ESM without a require shim
Output format is esm but the bundled dependency still calls require() at runtime, which the ESM output cannot provide.
Node builtins were bundled instead of externalized
Builtins like fs were pulled into the bundle, so esbuild emitted a dynamic require it cannot resolve.
How to fix it
Inject a require shim for ESM output
Recreate require with createRequire and inject it via a banner so bundled CJS can call require at runtime.
await esbuild.build({
format: 'esm',
platform: 'node',
banner: { js: "import{createRequire}from'module';const require=createRequire(import.meta.url);" },
});Externalize Node builtins and dependencies
Keep builtins (and node_modules) out of the bundle so the runtime resolves them normally.
esbuild.build({ platform: 'node', external: ['fs', 'path'], packages: 'external' });The three that account for most of them
- Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
- Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise
--max-old-space-sizeor use a larger runner. - devDependencies pruned.
NODE_ENV=productionmakesnpm ciskip devDependencies, so the build tool itself goes missing. Set it after install, not before.
How to prevent it
- Set platform: "node" when bundling server code.
- Externalize Node builtins or inject a createRequire banner for ESM output.
- Test the built artifact in CI, not just the source.