CommonJS
CommonJS is a collaborative project and specification framework designed to standardize the module system for JavaScript, facilitating the creation of interoperable, reusable code modules across diverse environments such as web servers, desktop applications, command-line tools, and browsers.[1] It addresses the need for a consistent way to organize and share JavaScript code outside traditional browser contexts, emphasizing privacy, singleton imports, and API exports to build a robust ecosystem.[2] The project originated in January 2009 as "ServerJS," initiated by Mozilla engineer Kevin Dangoor through a blog post calling for standardized server-side JavaScript practices.[3] In March 2009, the group adopted "securable modules" as the core style, releasing the first API version (0.1), and by April, a demonstration occurred at the inaugural JSConf in Washington, DC.[3] The name changed to CommonJS in August 2009 to reflect its expanded scope beyond servers, leading to the development of key specifications like Modules/1.1, which outlines how modules should be written for client-server interoperability.[3][2] At its core, the CommonJS Modules/1.1 specification introduces free variables likerequire(id) for importing modules, exports for defining public APIs, and module for metadata such as identifiers and URIs, ensuring modules are self-contained and handle dependencies synchronously.[2] Module identifiers use a path-like format (e.g., camelCase with / separators, no .js extensions), supporting relative paths and avoiding cycles by providing partial exports during loading.[2] This design promotes encapsulation, where each module is wrapped in a function to keep variables private, a principle widely implemented in environments like Node.js.[4]
CommonJS gained prominence through its adoption as the default module system in Node.js, where it serves as the original packaging mechanism for server-side JavaScript, using require() and module.exports for loading and sharing functionality.[4] While it remains stable and widely used—especially in legacy codebases and with file extensions like .cjs—it has been supplemented by the official ECMAScript Modules (ES Modules) standard since 2015, which offers asynchronous loading and native browser support.[4][5] Despite this evolution, CommonJS continues to influence JavaScript modularity, with multiple implementations and ongoing relevance in non-ESM contexts.[2]
Overview
Definition and Purpose
CommonJS is an open-source project launched in January 2009 by Mozilla engineer Kevin Dangoor, initially under the name ServerJS, aimed at developing a common API for JavaScript libraries and applications, with a particular emphasis on non-browser environments such as server-side runtimes.[6][3] The project sought to establish voluntary standards that would enable consistent behavior across different JavaScript implementations outside the browser, fostering an ecosystem where code could be shared and utilized effectively in diverse contexts.[3] The primary purpose of CommonJS was to mitigate the fragmentation in JavaScript module systems by defining a straightforward, synchronous module loading mechanism centered on therequire() function for importing modules and the exports object for defining a module's public API.[2] This approach ensured interoperability among various module loaders, allowing modules to be written in a way that works across client-side and server-side environments without proprietary extensions.[2] By standardizing these core elements, CommonJS addressed the inconsistencies arising from disparate implementations in early server-side JavaScript engines.
JavaScript's original design, created in 1995 for browser scripting, was inherently browser-centric and lacked built-in support for modules, resulting in reliance on global variables that often led to namespace collisions and poor code organization.[3] This gap necessitated a voluntary standard like CommonJS to enable better dependency management and code reusability in non-browser settings. CommonJS promotes these goals by encapsulating code within module scopes—using require, exports, and module as local variables—thereby avoiding pollution of the global namespace and allowing developers to build modular, maintainable applications.[2] The synchronous loading model, where require() immediately returns the module's exports or throws an error if unavailable, further supports this by providing predictable execution in environments like servers.[2]
Core Principles
CommonJS adheres to the principle of synchronous, blocking module loading, which facilitates straightforward dependency resolution in single-threaded environments like server-side JavaScript runtimes. This approach ensures that modules are executed and their exports become immediately available, simplifying the management of execution order without the complexities of asynchronous coordination.[2] A key tenet is the use of plain JavaScript without requiring additional syntax or language extensions, promoting broad compatibility with existing JavaScript parsers and interpreters across diverse environments. This design choice emphasizes accessibility and ease of adoption, allowing developers to author modules using standard language constructs.[2][1] Modules in CommonJS are conceived as self-contained units, each encapsulated in a private scope that isolates internal variables and functions from the global namespace. Only explicitly defined exports are made accessible to other modules, thereby preventing global pollution and encouraging modular, maintainable code structures. This scoping mechanism supports secure and organized code organization, particularly in shared execution contexts.[2] The project embodies a guiding tenet of minimalism, with specifications designed as voluntary guidelines that concentrate on essential features, such as the core modules/1.1 standard, to avoid unnecessary complexity and bloat. This focused scope enables interoperability among various module systems while allowing extensions for specific needs.[2][1] CommonJS operates as a collaborative, community-driven initiative, coordinated through public mailing lists and open discussions to foster consensus on specifications and implementations. This decentralized governance model has sustained its evolution through contributions from developers and stakeholders invested in JavaScript ecosystem standardization.[1]History
Origins and Early Development
CommonJS originated in January 2009 when Kevin Dangoor, a Mozilla engineer, launched the ServerJS project to establish standards for server-side JavaScript development.[7][8] In March 2009, the group adopted "securable modules" as the core style and released the first API version (0.1). By April, a demonstration occurred at the inaugural JSConf in Washington, DC.[3] The initiative addressed the fragmentation in JavaScript ecosystems outside the browser, where multiple interpreters like Rhino, SpiderMonkey, V8, and JavaScriptCore lacked unified APIs for modules, standard libraries (such as file system access), interfaces (e.g., database connectivity), package management, and repositories.[7][8] This disarray hindered library portability and ecosystem growth, exemplified by disparate efforts like Mozilla's Jetpack project for browser extensions and the Dojo toolkit's emerging Asynchronous Module Definition (AMD) approach for client-side asynchronous loading.[7][9] Dangoor's call to action emphasized the need for interoperability to foster a robust server-side JavaScript environment, drawing inspiration from mature ecosystems like Python's.[7] In August 2009, the project was renamed CommonJS to reflect its expanded scope beyond server-side applications, accommodating broader JavaScript usage scenarios.[8] This transition involved key contributors such as Isaac Schlueter, founder of the npm package manager, and the Node.js team, including creator Ryan Dahl, who joined early discussions to align the specifications with emerging runtimes.[10][11] The renaming underscored a shift toward a more inclusive standard that could unify synchronous module loading for servers while influencing asynchronous variants for browsers, promoting library reusability across environments.[9][8] The CommonJS community coalesced through the formation of a public mailing list at groups.google.com/group/commonjs and a dedicated wiki at wiki.commonjs.org in 2009, serving as hubs for collaborative specification development.[7][8] These platforms facilitated rapid engagement, with over 224 members joining the ServerJS Google Group within the first week, enabling debates on core proposals like the Modules/1.0 specification.[10] Initial adoption came swiftly, notably by the Narwhal JavaScript runtime, which implemented CommonJS modules that year to support portable code execution across engines.[8] This early traction laid the groundwork for broader implementation in projects like Node.js, marking the project's transition from proposal to practical standard.[8]Key Milestones and Evolution
The CommonJS project released its foundational Modules/1.0 specification in 2009, establishing the core synchronous module loading mechanism usingrequire and exports to enable interoperable code sharing across JavaScript environments.[12] This spec emphasized privacy of module scopes and support for relative identifiers, laying the groundwork for standardized module authoring without prescribing storage or path resolution details.[12]
In 2010, the project advanced with Modules/1.1, which refined export behaviors to better handle object properties and enumerability, addressing feedback from early implementations while maintaining backward compatibility with version 1.0.[2] That same year saw the introduction of the Packages/1.0 specification, defining a manifest format (package.json) for bundling modules, dependencies, and metadata into distributable units, facilitating ecosystem growth through tools like npm.[13] Complementing this, the System/1.0 spec emerged to outline runtime environments, including standardized interfaces for command-line arguments, environment variables, and I/O operations, promoting consistency in server-side and desktop applications.[14]
Node.js, first released in May 2009, adopted the CommonJS module system from its inception, solidifying it as the de facto standard for server-side JavaScript development.[15] This alignment propelled widespread adoption, with Node.js leveraging the specs to build a robust package ecosystem. In the 2010s, efforts turned toward asynchronous extensions, such as the Modules/Async proposals (e.g., require.ensure for dynamic loading), aimed at browser compatibility, but these initiatives largely stalled after 2015 amid the rise of ECMAScript modules in ES6.[16]
In 2019, governance of CommonJS transferred to the OpenJS Foundation under the Linux Foundation, ensuring neutral stewardship and long-term sustainability through collaborative oversight.[17] Since then, activity has focused on maintenance rather than innovation; as of 2025, updates have been limited to bug fixes, compatibility enhancements for modern runtimes, and minor clarifications in the specifications, reflecting the project's maturation alongside the dominance of ES modules.[2]
Specifications
Module Definition and Loading
In CommonJS, a module is defined as a single JavaScript file that executes within its own private scope, isolated from the global scope to prevent variable pollution. This scope includes three predefined variables:require (a function for loading dependencies), exports (an object serving as the namespace for module exports), and module (an object representing the module instance itself, with properties like id for the module identifier and uri for a fully-qualified URI if available). Developers populate the exports object with functions, objects, or other values to expose the module's API.[2]
Modules are loaded synchronously using the require(id) function, where id is a string specifying the module identifier in a path-like format using camelCase terms separated by "/", such as "foo/bar" or relative paths starting with "./" or "../", without file extensions like ".js". This function resolves the identifier relative to the calling module for relative paths or from the root namespace for top-level identifiers, executes the module's code once in its private scope, and returns the populated exports object, allowing the calling code to access the module's API immediately. For example, to load a module named "math", one would use var math = require('./math');, which synchronously evaluates the module and returns its exports.[2]
To optimize performance and ensure consistency, CommonJS implements a caching mechanism where each module is loaded and executed only once, functioning as a singleton, with results stored internally. Subsequent calls to require for the same identifier retrieve the cached exports object directly, avoiding re-execution. This applies globally within the runtime environment, meaning all parts of the application share the same instance of a given module. The require function may also have a main property referring to the top-level module and a paths attribute providing an array of prioritized search paths.[2]
Circular dependencies, where modules require each other recursively, are handled by returning a partial exports object during the loading phase to break potential infinite loops. When a module A requires module B, and B in turn requires A while A is still loading, an object with the exports prepared so far is returned for A before B completes, allowing the cycle to resolve without deadlock once all modules finish executing. This ensures that interdependent modules can access each other's available exports at the time of the require call, though fully populated exports may not be immediately available.[2]
Error handling during module loading is strict to maintain reliability. If the identifier cannot be resolved or the module cannot be returned (e.g., due to a nonexistent module), require throws an error. Syntax errors or runtime exceptions encountered while executing the module's code also propagate as uncaught errors, halting the loading process.[2]
Export and Import Mechanisms
In CommonJS, modules export values by assigning properties to theexports object, enabling named exports that can be accessed by importers. For instance, a module might define exports.add = function(a, b) { return a + b; }; to expose a named function. This approach allows multiple named exports within a single module, such as functions, classes, primitives, or objects, all attached as properties to the shared exports object. The exports object is the only means of exporting in the specification; there is no formal default export syntax, and the entire exports object is returned when the module is required.[2]
To import values, the require function is used with a module identifier, returning the exported object for property access. For named exports, importers can access properties like var math = require('./math'); var sum = math.add(2, 3);. Imported objects reference the same instances as in the exporter, avoiding deep cloning and enabling shared state across modules—changes to exported objects affect all importers.[2]
Best practices recommend populating the exports object consistently for clarity in module design, ensuring a collection of named exports. Unlike later proposals such as CommonJS Modules/2.0, which aimed for more flexible export patterns including asynchronous loading but were never finalized, the 1.1 specification relies solely on synchronous exports for all data flow.[2][18]
Implementations
Server-Side Runtimes
Node.js serves as the primary server-side runtime for CommonJS, having adopted the specification shortly after its inception in 2009 with the built-inrequire() function for synchronous module loading.[4] This implementation provides full compliance with CommonJS Modules/1.1, enabling developers to export objects via module.exports and import them through require(), while supporting core modules, local files, and the node_modules directory for dependency resolution.[4] As of Node.js version 25.2.0 in November 2025, CommonJS remains the default module system for .js files unless overridden by package.json configuration, ensuring backward compatibility for legacy applications.[4]
Early server-side runtimes laid the groundwork for CommonJS adoption. Narwhal, released in 2009, was the first runtime to implement Modules/1.0, providing a pure JavaScript standard library and influencing Node.js's module design by demonstrating practical server-side modularity without browser dependencies.[12][19] Similarly, RingoJS, developed starting in 2009 and with releases up to 2022 on the Mozilla Rhino engine, offered robust support for CommonJS specifications including Modules/1.1 and Packages/1.0, which facilitated bundling and distribution of cohesive module sets for server-side JavaScript applications.[20][21]
Node.js extends CommonJS with runtime-specific features for server environments. Files with the .js extension (or .cjs) are treated as CommonJS by default, while .mjs extensions denote ECMAScript modules, allowing coexistence without conflicts.[4] Utilities like __dirname, which resolves the current module's directory path (e.g., /Users/mjr), and the process global object enable precise file system interactions essential for server-side path handling and environment-specific logic.[4]
The synchronous nature of CommonJS loading in Node.js aligns well with I/O-bound server workloads, as modules are cached after initial load to prevent re-execution and support efficient startup times.[4] However, in large-scale applications, dynamic require() calls during runtime can block the event loop, potentially degrading concurrency and responsiveness, particularly on case-insensitive file systems where reloading may occur.[4][22]
As of 2025, Node.js maintains full support for CommonJS to accommodate existing ecosystems but recommends ECMAScript modules for new projects, citing their status as the official JavaScript standard with features like top-level await and better interoperability.[23] This guidance encourages gradual migration while preserving CommonJS for legacy and hybrid setups.[23]
Client-Side and Build Tools
Browserify, released in 2011, was one of the first tools to enable CommonJS modules in browser environments by compiling Node.js-stylerequire() and module.exports into a single, browser-compatible JavaScript bundle.[24][25] It recursively analyzes dependencies at build time, resolving and inlining them to produce a static file that can be served via a simple <script> tag, thus avoiding runtime module loading issues in browsers.[24]
Webpack, introduced in 2012, extended this capability by natively supporting CommonJS alongside other formats like AMD and ES modules.[25][26] Its module system uses loaders to process non-JavaScript assets (e.g., CSS, images) and applies optimizations such as tree-shaking to eliminate unused code from bundles, making it suitable for complex client-side applications.[26]
While RequireJS primarily implements the Asynchronous Module Definition (AMD) for browsers, adaptations like curl.js, launched around 2011, provided compatibility for CommonJS modules through plugins and loaders.[27][28] Curl.js extends AMD loaders to handle CommonJS syntax, allowing synchronous-style require() calls to be emulated asynchronously in the browser without altering module code.[27]
Rollup, developed in 2015, offers configurable support for CommonJS as both input and output formats, particularly useful for bundling libraries in modern workflows.[29][30] It converts CommonJS modules to ES2015 during processing via plugins like @rollup/plugin-commonjs, enabling efficient tree-shaking and output in formats compatible with browser environments.[29]
A key challenge in adapting CommonJS for browsers stems from its synchronous require() mechanism, which blocks execution and is incompatible with the web's asynchronous loading model.[10][31] Tools address this by pre-bundling modules at build time or wrapping calls in asynchronous constructs, but this adds complexity and prevents dynamic loading without additional emulation.[10]
As of 2025, CommonJS remains in use within legacy client-side codebases supported by tools like Webpack and Rollup, but modern build systems such as Vite prioritize native ES modules for faster development servers and reduced bundle sizes, accelerating the shift away from CommonJS adaptations.[32][33][34]
Adoption and Current Status
Usage in Ecosystems
CommonJS remains the dominant module format in the Node.js npm registry as of 2025, with approximately 60.5% of analyzed packages published exclusively in CommonJS and an additional 18.1% supporting dual CommonJS/ESM formats, ensuring compatibility for legacy environments.[35] This prevalence is particularly evident in longstanding libraries such as Express.js, which has relied on CommonJS syntax since its initial release in 2009 and continues to do so in its core implementation for server-side routing and middleware handling. Similarly, utility libraries like Lodash maintain CommonJS as their primary distribution format to support broad interoperability across Node.js versions. In backend frameworks, CommonJS sees full adoption in established tools like Express.js, where its synchronous loading aligns with traditional server architectures, while newer frameworks such as NestJS employ hybrid approaches that combine CommonJS for core modules with optional ESM interoperability enabled via TypeScript configurations. Serverless environments, including AWS Lambda, predominantly utilize CommonJS for Node.js functions due to its seamless integration with runtime defaults and the need to support older Node.js versions like 18.x and 20.x, although ESM support has been available since 2020.[36] Many npm modules now publish in dual CommonJS/ESM formats to accommodate diverse consumer needs, with CommonJS serving as the fallback for projects targeting Node.js versions prior to 14.x, where ESM adoption was limited.[37] This strategy involves configuring thepackage.json "exports" field to specify separate entry points for "require" (CommonJS) and "import" (ESM), often using tools like tsup to generate both .cjs and .mjs builds from a single TypeScript or JavaScript source.[38] Official Node.js documentation provides detailed guides on implementing dual-format publishing, recommending named exports attachment or wrapper modules to mitigate issues like the dual-package hazard, where mismatched formats lead to runtime inconsistencies.[37]
Migration patterns in mixed projects frequently leverage transpilation tools such as Babel's @babel/plugin-transform-commonjs-esm to convert CommonJS require/module.exports syntax to ESM import/export statements, facilitating gradual adoption without breaking existing dependencies.[39] This approach is common in legacy codebases, where automated refactoring preserves functionality while enabling tree-shaking optimizations in modern bundlers.
Npm trends indicate a decline in new CommonJS-only packages since 2018, coinciding with Node.js's ESM stabilization in version 12, as evidenced by the rise in ESM-published packages from 7.8% in 2021 to over 25% by late 2024, though maintenance of existing CommonJS packages remains stable to support the vast installed base.[40] Community resources, including the Node.js documentation, emphasize best practices for dual-format publishing to balance innovation with backward compatibility in ongoing ecosystem development.[37]
Transition to ES Modules
ECMAScript 2015, also known as ES6, introduced native modules to the JavaScript language, featuring explicitimport and export syntax for defining dependencies and exposing functionality.[41] Unlike CommonJS's synchronous require mechanism, ES modules support asynchronous loading, which enables parallel fetching in browsers and improves initial page load times.[5] Additionally, the static nature of ES module declarations—where imports and exports are known at parse time—facilitates advanced optimizations like tree-shaking, allowing build tools to eliminate unused code and produce smaller bundles compared to the dynamic loading in CommonJS.[23]
Node.js began supporting ES modules experimentally in version 8.5.0, released in October 2017, requiring the --experimental-modules flag and the .mjs file extension for module files. Full stable support arrived in version 12.0.0 in April 2019, with the removal of the experimental flag, and further refinements in version 14.0.0 in April 2020, including seamless integration without extensions by setting "type": "module" in package.json. This allowed developers to opt into ES modules alongside existing CommonJS code, marking a pivotal shift toward native standardization in server-side JavaScript.[23]
Interoperability between CommonJS and ES modules presents challenges due to their differing loading models: CommonJS's dynamic require() operates at runtime, while ES modules enforce static import declarations at load time, preventing direct dynamic imports of ES modules from CommonJS without additional configuration.[23] To address this, Node.js introduced createRequire() in version 12.0.0, enabling ES modules to dynamically load CommonJS modules by creating a require-like function from within an ES module context.[42] Conversely, importing CommonJS into ES modules works via default imports, though it may require handling namespace differences.[23]
The transition to ES modules is driven by their superior static analysis capabilities, which enable more precise dependency resolution and optimization during transpilation or bundling, reducing bundle sizes through effective dead code elimination.[5] Native browser support for ES modules eliminates the need for bundlers in many client-side scenarios, streamlining development workflows and improving runtime performance via deferred loading.[23] In Node.js environments, ES modules offer faster cold starts in some cases due to parallel execution and better caching.[31]
As of 2025, CommonJS persists for backward compatibility in legacy Node.js projects and libraries, ensuring stability across the vast existing ecosystem, but new projects overwhelmingly favor ES modules for their alignment with modern JavaScript standards and future-proofing.[43] Node.js maintains dual support, allowing mixed-module applications, though migrations via bundlers like Webpack or Rollup have accelerated post-2020. Enhancements like top-level await, stabilized in Node.js version 14.8.0 in 2020, further bolster ES modules by enabling asynchronous initialization without wrapping code in functions, simplifying data-fetching patterns in modules.[23]