Back to 20 Concepts
foundations • Intermediate
ESM vs CommonJS Dual Package Hazard & Top-Level Await
Node.js supports both CommonJS (require/module.exports - synchronous) and ECMAScript Modules (import/export - asynchronous with static analysis and Top-Level Await).
Intuitive Mental Model
Direct Delivery vs Scheduled Flight: CommonJS require() is an instant courier handoff (synchronous, halts execution); ESM import is a booked flight evaluated and linked before takeoff.
Node.js ESM / CJS ImplementationNode.js v22 LTS
// 1. ESM (package.json has "type": "module"):
import { readFile } from 'fs/promises';
// Top-Level Await (Native in ESM):
const data = await readFile('./config.json', 'utf-8');
console.log('Loaded config:', data);
// 2. Dynamic Import (Works in both CJS and ESM):
const module = await import('./heavy-plugin.js');Key Architectural Takeaways
- •CommonJS cannot use Top-Level Await; ESM natively supports top-level await in modules.
- •require() is synchronous; import is asynchronous and statically analyzable by bundlers (Rollup, Vite).
- •Dual package hazard: Bundling both CJS and ESM versions of the same library can instantiate duplicate singleton state.
Common Production Mistake
Trying to use require() inside an ES Module without createRequire, causing ReferenceError: require is not defined.
Recommended Solution
Use import statement or createRequire(import.meta.url).