← Back to libraryQuestion 146 of 468
🟨JavaScriptIntermediate

Modules: ES Modules vs CommonJS

📌 Definition:

JavaScript modules let you split code into reusable files with explicit imports and exports. The two systems are ES Modules (ESM, the standard) and CommonJS (CJS, Node's original system).

📖 Detailed Explanation:

ESM uses import/export, is statically analyzable (enabling tree-shaking), loads asynchronously, and is the browser and modern standard (files often use .mjs or "type": "module"). CommonJS uses require() and module.exports, loads synchronously, and is Node's historical default (.cjs or default .js). Key differences: ESM imports are live read-only bindings resolved at load time; CJS require returns a copied value at call time and can be called conditionally. Mixing them causes friction — this is exactly the ERR_REQUIRE_ESM error you hit when require() is used on an ESM-only package. Modern tooling (and Node 22+) increasingly bridges the two, but knowing which system a package uses matters for imports.

🔑 Key Points:
  • ESM: import/export, static, async, tree-shakeable (standard)
  • CJS: require/module.exports, synchronous (Node legacy)
  • require() of an ESM-only package throws ERR_REQUIRE_ESM
  • Use dynamic import() to load ESM from CommonJS
🌍 Real-World Example:

A test project's prerender script did const puppeteer = require('puppeteer') and broke on CI because modern Puppeteer is ESM-only — require() of an ES module throws ERR_REQUIRE_ESM. The fix was dynamic import: const puppeteer = (await import('puppeteer')).default.

🎯 Scenario-Based Interview Question:

Your CommonJS script needs to use a package that only ships as an ES Module. require('the-package') throws ERR_REQUIRE_ESM. How do you load it?