CommonJS vs ES Modules in Node.js
Node.js supports two distinct module systems for organizing and
sharing code: CommonJS (CJS) and ECMAScript Modules (ESM). While
CommonJS has been the traditional default in Node.js using
require() and module.exports, ES Modules
represent the modern JavaScript standard utilizing import
and export syntax. This article explores the key
differences between these two systems, including syntax, loading
behavior, configuration, and environment variables.
Syntax: Import/Export vs Require/Exports
The most visible difference between CommonJS and ES Modules is the syntax used to import and export code.
CommonJS
CommonJS uses require() to import modules and
module.exports or exports to export them.
// importing
const fs = require('fs');
// exporting
module.exports = function helper() {
return "helper";
};ES Modules
ES Modules use standard ES6 import and
export statements.
// importing
import fs from 'fs';
// exporting
export default function helper() {
return "helper";
}Loading Mechanism: Synchronous vs Asynchronous
The underlying execution and loading behaviors of the two systems are fundamentally different.
- CommonJS (Synchronous): CommonJS modules are loaded
synchronously. When
require()is called, Node.js blocks execution, loads the file from the disk (or cache), evaluates it, and returns the exported values. This makes it ideal for server-side environments where file systems are fast, but unsuitable for browsers. - ES Modules (Asynchronous): ES Modules are parsed and loaded asynchronously. The runtime parses the import statements first to build a dependency graph before executing any code. This allows for static analysis, optimized loading, and features like tree-shaking (removing unused code).
Configuration and File Extensions
Node.js determines which module system to use based on file
extensions or configuration in package.json.
- Default Behavior: By default, Node.js treats all
.jsfiles as CommonJS. - Opting into ES Modules: To use ES Modules, you must
either:
- Change the file extension to
.mjs. - Add
"type": "module"to your project’spackage.jsonfile.
- Change the file extension to
- Opting into CommonJS: If you have
"type": "module"enabled but need to write a CommonJS file, you can use the.cjsextension.
Global Variables and Context
Several global variables that are standard in CommonJS are not available in ES Modules.
__dirname and
__filename
In CommonJS, you can easily get the current directory and file path
using __dirname and __filename. These globals
do not exist in ES Modules. In ESM, you must replicate this behavior
using import.meta.url and the url utility
module:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);Strict Mode
- CommonJS: Does not run in strict mode by default.
You must explicitly add
"use strict";at the top of your files. - ES Modules: Runs in strict mode by default.
Variables cannot be declared without
var,let, orconst, and other strict mode rules are enforced automatically.
Interoperability
While you can import CommonJS modules into ES Modules, you cannot easily do the reverse.
- Importing CommonJS into ESM: You can import
CommonJS modules using the
importsyntax in an ES Module. Node.js wraps the CommonJS module so it can be resolved. - Importing ESM into CommonJS: You cannot use
require()to import an ES Module becauserequire()is synchronous and ES Modules are asynchronous. To load an ES Module in a CommonJS file, you must use dynamicimport():
// Inside a CommonJS file
import('./module.mjs').then((module) => {
// Use the module here
});