Module systems are a big milestone in Node.js learning. Let’s carefully go through CommonJS vs ES Modules (ESM).


📦 1. CommonJS (CJS)

  • Default module system in Node.js (since the beginning).

  • Uses require and module.exports.

  • Synchronous loading (good for server-side, not browsers originally).

Example: Export

// utils.js
function add(a, b) {
  return a + b;
}
 
module.exports = { add };

Example: Import

// app.js
const { add } = require("./utils");
console.log(add(2, 3)); // 5

🔑 Key points:

  • Each file is wrapped in a function (function(exports, require, module, __filename, __dirname){}).

  • Modules are cached after first load.

  • Still widely used in existing Node projects.


🌐 2. ES Modules (ESM)

  • Modern, standardized JavaScript module system (used in browsers).

  • Node.js added support starting from v12 (stable in v14+).

  • Uses import and export.

  • Asynchronous by design (works well for the browser).

Example: Export

// utils.mjs
export function add(a, b) {
  return a + b;
}

Example: Import

// app.mjs
import { add } from "./utils.mjs";
console.log(add(2, 3)); // 5

🔑 Key points:

  • No wrapper function — top-level import/export are part of the language.

  • Supports static analysis (tree-shaking, better tooling).

  • Must either:

    • Use .mjs extension, or

    • Set "type": "module" in package.json.


⚖️ Comparison: CommonJS vs ES Modules

FeatureCommonJS (CJS)ES Modules (ESM)
Syntaxrequire, module.exportsimport, export
Default in NodeYes (legacy)Yes (modern, via "type": "module")
File Extension.js.mjs (or .js with "type": "module")
LoadingSynchronousAsynchronous
Exportsmodule.exports (single object)Named & default exports
ScopeWrapped in a functionTop-level
Browser SupportNo (without bundler)Yes (native in modern browsers)

🧩 Interop Between CJS and ESM

Sometimes you need both in one project.

  • Importing CommonJS in ESM:
import pkg from "lodash"; // works, entire export as default
  • Importing ESM in CommonJS:
const { readFile } = await import("fs/promises"); // dynamic import

In short:

  • CommonJS (require/module.exports) → traditional Node.js, synchronous, still common.

  • ESM (import/export) → modern, async, works in both browsers and Node.js, better for new projects.


Do you want me to show you a practical migration example (converting a small CommonJS project to ESM), or should we move on to the next Node.js core concept like Non-blocking I/O?