JavaScript path · Under the hood · Topic 11 of 12

Modules (ESM vs CommonJS)

Not started

ES modules use import and export, which are read before any code runs, so imports are hoisted, bindings stay live, and top-level await is allowed. CommonJS uses require(), which loads and runs a file synchronously and hands back its module.exports object. Node supports both, chosen by .mjs/.cjs or the "type" field in package.json, and from Node 22.12 require() can also load an ES module that has no top-level await.

Why it matters: Mixing the two is behind most "cannot use import statement outside a module" and default-export puzzles.

1

How it works

The explanation, with examples you can run.

Side by side

ES modulesCommonJS
Syntaximport / exportrequire() / module.exports
When it is resolvedBefore any code runs (static)When the require line runs
LoadingCan be asynchronous; allows top-level awaitSynchronous
ExportsLive bindings: importers see later changesA plain object, copied out at require time if you destructure
Strict modeAlwaysOnly with "use strict"
File locationimport.meta.url, import.meta.dirname__filename, __dirname

Mixing them in Node

  • An ES module can import a CommonJS file. Its module.exports becomes the default export, and Node tries to detect named exports too.
  • A CommonJS file can load an ES module with await import(), and since Node 22.12 also with plain require(), as long as that module (and its imports) use no top-level await.
  • In ES modules, relative imports need the full file name: import "./util.js", not "./util".
// math.mjsexport let calls = 0export function add(a, b) {  calls++  return a + b} // main.mjs (run with: node main.mjs)import { add, calls } from "./math.mjs"add(1, 2)console.log(calls) // 1: the binding is live, not a copy
2

Check yourself

2 questions. Get them all right to finish this topic.

  1. 1.In Node 22, which file is always treated as an ES module, whatever package.json says?
  2. 2.What does a CommonJS file get back from require("./other.js")?

0 of 2 right so far. Get all two to finish this topic.