Purpose of .mjs and .cjs File Extensions
Modern JavaScript environments, such as Node.js, support two distinct
module systems: CommonJS (CJS) and ECMAScript Modules (ESM). The
.mjs and .cjs file extensions were introduced
to provide an explicit, unambiguous way for runtimes and build tools to
determine which module system to use when executing a file. By
overriding default configurations, these extensions ensure that files
are parsed correctly without relying solely on project-level
settings.
The Two Module Systems in JavaScript
To understand why these file extensions exist, it is necessary to understand the two module formats:
- CommonJS (CJS): The original module system used by
Node.js. It loads modules synchronously using the
require()function and exports values viamodule.exportsorexports. - ECMAScript Modules (ESM): The official ECMAScript
standard for JavaScript modules. It uses
importandexportstatements, supports static analysis, allows top-levelawait, and loads asynchronously.
Because the syntax, parsing rules, and execution models of these two systems differ fundamentally, runtimes need a reliable way to know which parser to apply before executing the code.
The Purpose of .mjs
The .mjs extension stands for Module
JavaScript. It explicitly indicates that the file should always
be treated as an ECMAScript Module (ESM), regardless of
external configuration.
When a runtime encounters a .mjs file: * It parses the
code using ESM syntax (import and export). *
Strict mode ("use strict") is enabled by default. *
Top-level await is permitted. * CommonJS-specific globals
such as require, module.exports,
__dirname, and __filename are not defined.
Instead, ESM equivalents like import.meta.url must be
used.
The Purpose of .cjs
The .cjs extension stands for CommonJS
JavaScript. It explicitly indicates that the file should always
be treated as a CommonJS module.
When a runtime encounters a .cjs file: * It parses the
code using CommonJS syntax (require() and
module.exports). * Top-level await is not
permitted. * Globals like require, exports,
module, __filename, and __dirname
are available. * Strict mode is not enabled unless explicitly declared
with "use strict".
Interaction with
package.json
By default, Node.js treats standard .js files as
CommonJS. However, developers can add "type": "module" to
their project’s package.json, which instructs the runtime
to treat all standard .js files within that package scope
as ESM.
The specific extensions serve as precise overrides to this setting: *
In a "type": "module" package, standard .js
files run as ESM, but any file named with .cjs will still
execute as CommonJS. * In a default CommonJS package (or one with
"type": "commonjs"), standard .js files run as
CommonJS, but any file named with .mjs will execute as
ESM.
Why Explicit Extensions Matter
- Dual-Package Publishing: Library authors who
publish packages supporting both ESM and CommonJS use
.mjsand.cjsto distribute both module builds simultaneously from a single package. - Deterministic Execution: The explicit extensions eliminate ambiguity across different environments, bundlers (like Webpack, Vite, or Rollup), and Node.js versions.
- Configuration Files: Many modern developer tools run in ESM mode by default, but individual configuration files may still require CommonJS (or vice versa). Using explicit extensions allows these files to run correctly without altering the entire project setup.