JS: CommonJS Module System (history)

By Xah Lee. Date: .

What is the CommonJS Module System?

CommonJS is a specification for JavaScript modules that was originally developed to standardize module loading in server-side environments, particularly for Node.js. It provides a way to organize code into reusable, encapsulated units (modules) that can depend on and export functionality to other modules. Unlike browser-based JavaScript (which historically lacked native module support), CommonJS treats each file as a separate module, enabling better code organization, dependency management, and avoidance of global namespace pollution.

CommonJS is synchronous by design, meaning modules are loaded and executed immediately when required, which makes it well-suited for server-side applications where startup time is less critical than in browsers. It's the foundation of Node.js's module system (via the require function) and has influenced tools like Webpack and Browserify for bundling client-side code.

Core Concepts

1. Modules as Files: Each JavaScript file (e.g., math.js) is treated as a self-contained module. There's no global scope shared between modules—each runs in its own isolated context.

2. Exports: A module defines what it makes available to other modules via an exports object (or module.exports for the entire module).

3. Imports: Other modules load dependencies using require(), which returns the exported value.

4. Scope: Inside a module, this refers to the module.exports object. The exports variable is a shorthand reference to module.exports, but reassigning exports doesn't affect module.exports—always use module.exports for full exports.

How It Works: Basic Example

Let's say we have two files: math.js (a module to export) and app.js (the main file that uses it).

math.js (exporting functions):

// Define some functions
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

// Export an object with the functions
exports.add = add;
exports.subtract = subtract;

// Or, equivalently, for the entire module:
// module.exports = { add, subtract };

app.js (importing and using the module):

// Load the module synchronously
const math = require('./math.js');  // Path relative to app.js

// Use the exported functions
console.log(math.add(5, 3));     // Output: 8
console.log(math.subtract(5, 3)); // Output: 2

Exporting the Entire Module

You can export a single value (e.g., a class, function, or primitive) by assigning directly to module.exports:

// In greet.js
function greet(name) {
  return `Hello, ${name}!`;
}

module.exports = greet;  // Exports the function itself

Then import it:

const greet = require('./greet.js');
console.log(greet('World'));  // Output: Hello, World!

Common Patterns and Gotchas

Aspect CommonJS Example Notes
Named Export exports.foo = 42; Multiple per module
Full Export module.exports = { foo: 42 }; Replaces entire exports
Import const mod = require('./mod.js'); Synchronous, cached
Runtime Node.js, Browserify, Webpack Server-focused

Pros and Cons

Pros: Cons:

Relation to ES Modules

CommonJS predates ECMAScript (ES) modules (introduced in ES6). ES modules use import/export syntax and are asynchronous/static, making them better for browsers and modern bundlers. Many tools (e.g., Babel) can transpile between them. If you're starting a new project, consider ES modules for future-proofing, but CommonJS remains dominant in legacy Node.js code.

History of CommonJS

CommonJS emerged as a pivotal standardization effort in the JavaScript ecosystem, addressing the lack of a native module system in the language during the late 2000s. At the time, JavaScript was primarily used in browsers via <script> tags, leading to global namespace pollution, dependency management issues, and brittle code organization through patterns like the Immediately Invoked Function Expression (IIFE) or the Module Pattern. The rise of server-side JavaScript, particularly with early runtimes like SpiderMonkey and Narwhal, highlighted the need for a reusable, encapsulated module system outside the browser.

Origins and Early Development (2009)

The project traces its roots to January 29, 2009, when Mozilla engineer Kevin Dangoor published a blog post titled *"What Server-Side JavaScript Needs?"*. In it, he proposed a standardized module system to enable modular, server-side JavaScript development, emphasizing synchronous loading for simplicity in non-browser environments. This initiative launched under the name ServerJS, focusing initially on server-side needs.

By August 2009, the project was renamed CommonJS to reflect its expanded scope beyond servers, encompassing any non-browser JavaScript runtime, such as desktop apps or embedded systems. The CommonJS group operated as an open, community-driven effort—not affiliated with Ecma International's TC39 (which handles ECMAScript standards)—where specifications were only finalized after multiple independent implementations proved viability. Early contributors included Dangoor and a loose collective of developers, with Isaac Z. Schlueter later playing a key role through Node.js integration.

The first major specification, Modules/1.0, defined the core mechanics: each file as a module, require() for synchronous imports, and exports (or module.exports) for sharing functionality. This was quickly iterated to Modules/1.1 and 1.1.1, alongside supporting specs like Packages/1.0 for dependency management.

Adoption and Node.js Integration (2010–2011)

CommonJS gained traction with the launch of Node.js in May 2009 by Ryan Dahl. By 2011, Node.js fully adopted CommonJS as its default module system, using require() and module.exports to enable reusable server-side code. This made CommonJS the de facto standard for backend JavaScript, powering the explosive growth of npm (launched in 2010) where most packages were (and still are) published in CommonJS format. Node's caching mechanism—executing modules once and reusing exports—optimized for server performance, though its synchronous nature suited I/O-bound environments better than browsers.

Expansion and Proposals (2010s)

The CommonJS project evolved beyond core modules, producing over 20 proposals for a broader API ecosystem. These included:

  • Promises/B and Promises/D for asynchronous handling.
  • HTTP Client/B and IO/A for network and file operations.
  • Modules/Async/A and Modules/Transport series for experimental asynchronous loading (though sync remained dominant).

This "batteries-included" approach aimed for interoperability across JS runtimes, influencing tools like Narwhal and later Deno (in concept, though Deno favors ES modules).

Browser Challenges and Bundlers (2013–2015)

CommonJS wasn't natively browser-compatible, prompting workarounds. In 2013, Browserify emerged, allowing developers to write browser code in CommonJS syntax and bundle it into a single file for <script> inclusion. This bridged server and client, enabling npm modules in web apps but adding build-step complexity.

By 2015, Webpack (launched that year) built on this, creating dependency graphs for multi-asset bundling, further popularizing CommonJS in frontend workflows, especially with SPAs like React.

Transition to ES Modules and Legacy (2015–Present)

The landscape shifted with ECMAScript 2015 (ES6), which introduced native ES Modules using import/export syntax—static, asynchronous, and browser-native. This competed with CommonJS, offering better tree-shaking and no bundler requirement in modern browsers.

Node.js lagged in adoption: Experimental ES module support arrived in v8.5 (2018) and stabilized in v12 (2019), but CommonJS remains the default for backward compatibility. Today, interop is seamless—ES modules can import CommonJS via dynamic import()—and tools like TypeScript handle both.

Milestone Date Key Event/Details
Proposal Launch Jan 29, 2009 Kevin Dangoor's blog post initiates ServerJS.
Rename Aug 2009 Becomes CommonJS for broader scope.
Node.js Adoption 2011 Default module system in Node.js.
Browserify 2013 Enables CommonJS in browsers via bundling.
ES6 Modules 2015 Native alternative introduced, shifting paradigms.
Node ES Support 2019 Experimental in v12; CommonJS persists.

CommonJS's legacy endures: It democratized modular JS, birthing the npm ecosystem and influencing every modern bundler. While ES modules dominate new projects, CommonJS powers ~80% of legacy Node code and remains essential for migration paths. For deeper dives, explore the archived CommonJS wiki or Node's module docs.

JavaScript. Module Import Export