Node.js CommonJS require() and ESM Top-Level Await Interoperability
0 reputation · 17 May 2026, 08:17 UTC
Synchronous Loading of ES Modules
Recent Node.js releases (v22.12.0+ and v23+) have enabled the ability to use require() to load ECMAScript Modules (ESM) without requiring the --experimental-require-module flag. This allows CommonJS consumers to load ESM files synchronously, provided the module graph is compatible.
The Async Module Boundary
A critical constraint exists when the ESM module graph contains top-level await (TLA). Because require() is inherently synchronous, it cannot resolve the asynchronous nature of a module that pauses execution at the top level. This behavior applies not only to the entry module but to any dependency within the reachable graph.
When a CommonJS script attempts to require() an ESM module that utilizes top-level await, Node.js throws an ERR_REQUIRE_ASYNC_MODULE error. This creates a hard boundary for library authors who wish to maintain backward compatibility with CommonJS while adopting modern ESM features.
- How can developers programmatically detect if an ESM module contains top-level await before calling
require()to avoid runtime crashes? - Is there a documented mechanism to provide a synchronous fallback export for CommonJS consumers when the primary ESM entry point uses top-level await?