CommonJS vs ESM
⭐ Interview Importance: HIGH
⏱️ Revision Time: 4 min
TL;DR
Node.js traditionally used CommonJS (require(), module.exports), which is synchronous. Modern Node.js also supports ECMAScript Modules (ESM) (import, export), which is asynchronous, standard in browsers, and supports Top-Level Await.
Mental Model
How It Works
CommonJS
- Loads modules synchronously at runtime.
- Files are wrapped in an IIFE (Immediately Invoked Function Expression) before execution, providing them with
__dirname,__filename,require, andmodule. - Uses
require()to load andmodule.exportsto export.
ECMAScript Modules (ESM)
- Loads and parses modules asynchronously before executing the code (parsing phase vs execution phase).
- Does not have
__dirname,__filename, orrequirebuilt-in (you must useimport.meta.url). - Supports Top-Level Await (using
awaitoutside of an async function).
Example
// --- CommonJS ---
const fs = require('fs');
module.exports = function doSomething() { /* ... */ };
// --- ESM ---
// (Requires "type": "module" in package.json OR .mjs extension)
import fs from 'fs';
export function doSomething() { /* ... */ };
// Top-level await is valid in ESM!
const data = await fs.promises.readFile('config.json');
Common Interview Questions
Can you use require() inside an ESM file?
By default, no. require is not defined in an ES module. If you need to import a CommonJS module into an ESM file, you can just import it, or you can construct require using module.createRequire(import.meta.url).
How does Node.js know if a file is CJS or ESM?
Node.js treats .cjs files as CommonJS and .mjs files as ESM. For .js files, it checks the nearest package.json for the "type" field. If "type": "module", it treats .js as ESM. If "type": "commonjs" (or missing), it defaults to CommonJS.