Glossary / CommonJS

CommonJS

CommonJS (CJS) is Node.js's traditional module system, centered on require() and module.exports. Each module executes inside a wrapper function with local variables such as exports, require, module, __filename, and __dirname; its exported value is the module.exports object.

Loading and exports

require() resolves and loads synchronously, then caches the module's exported value by resolved filename. Subsequent requires of the same resolved module generally return the cached object rather than re-executing it. exports is initially an alias to module.exports, so adding properties works, but reassigning exports alone does not change the exported value.

// counter.cjs
let count = 0;
module.exports = { next: () => ++count };

// app.cjs
const counter = require('./counter.cjs');
console.log(counter.next());

CommonJS exports are object values, unlike ESM's live imported bindings. Mutating the exported object is observable by consumers holding that same object, but replacing module.exports after consumers receive it does not update their local reference. Circular require() calls can observe a partially initialized exports object.

Interoperability and package mode

In Node.js, .cjs explicitly selects CommonJS; .js interpretation depends on the nearest package "type" and current Node rules. CommonJS can use dynamic import() to load ESM asynchronously. ESM can import CommonJS, but named-export detection is interoperability behavior with constraints; the reliable CJS export is the default value representing module.exports. Bundlers may transform either format, but transformed semantics can differ from native execution.

Engineering guidance

Do not mix module systems casually within one package. Mark package type explicitly and test both development and production loaders. Keep require() side effects understood because loading executes code synchronously. For libraries supporting both systems, define and test conditional package entry points rather than assuming transpilation creates perfect interoperability.

Interview questions

  1. What does require() return? The module's module.exports value, commonly cached after first evaluation.
  2. Why does exports = value fail to replace exports? It only reassigns the local alias; assign module.exports = value.
  3. How do CJS cycles behave? A consumer may receive a partially initialized exports object during recursive loading.
  4. Can CJS load ESM? Through dynamic import(), which is asynchronous; native require() support is version- and graph-dependent.
  5. How are CJS exports different from ESM bindings? CJS exposes a value/object; ESM exposes statically linked live bindings.

References