In Node.js, ESM (ECMAScript Modules) and CommonJS (CJS) are two module systems used to organize and manage code. They differ in syntax, usage, and how they handle imports and exports.
1. CommonJS (CJS) Modules
CommonJS was the original module system in Node.js and is widely used in many Node.js applications. It’s synchronous, designed to work on the server-side in environments like Node.js.
Key Features:
- Importing Modules: You use
require()to import other modules. - Exporting Modules: You use
module.exportsorexportsto export modules. - Synchronous Loading: Modules are loaded synchronously, which makes it suitable for server-side environments where blocking code isn’t a major concern.
- File Extension: CommonJS modules usually have the
.jsextension.
Use Cases:
- Most widely used in Node.js.
- Supported in all versions of Node.js.
- Many existing Node.js modules and packages use CommonJS.
2. ESM (ECMAScript Modules)
ECMAScript Modules (ESM) is the standardized module system introduced in JavaScript (ES6, or ES2015). It’s the official module system for JavaScript, and Node.js has gradually adopted it to stay aligned with the evolving JavaScript standard.
Key Features:
- Importing Modules: You use the
importkeyword to import other modules. - Exporting Modules: You use the
exportkeyword to export modules. - Asynchronous Loading: ESM modules are loaded asynchronously, making them better suited for environments like the browser, where non-blocking operations are important.
- File Extension: ESM modules can have
.mjsextension or can use.jswith a"type": "module"field inpackage.json.
Example of ESM Syntax:
// Exporting in ESM
export function greet() {
return 'Hello, world!';
}
// Importing in ESM
import { greet } from './greet.mjs';
console.log(greet()); // Outputs: Hello, world!Use Cases:
- The preferred module system for new JavaScript/Node.js code.
- Asynchronous module loading makes it more suitable for modern web applications.
- Aligns with the JavaScript standard (used in browsers).
3. Differences Between ESM and CommonJS
| Feature | ESM | CommonJS (CJS) |
|---|---|---|
| Import syntax | import | require |
| Export syntax | export, export default | module.exports, exports |
| Loading | Asynchronous | Synchronous |
| File extension | .mjs or .js (with "type": "module") | .js |
| Default exports | Supports export default | No native default export |
| Support in Node.js | Node 12+ (Stable in Node 14+) | Supported in all versions |
4. How to Use ESM in Node.js
By default, Node.js treats files with a .js extension as CommonJS modules. However, to use ESM syntax (import/export), you can:
- Use the
.mjsExtension: Node.js will treat.mjsfiles as ESM modules. - Set
"type": "module"inpackage.json: You can also set"type": "module"in yourpackage.jsonto tell Node.js that all.jsfiles should be treated as ESM modules.
Example:
// package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}5. Interoperability
Node.js provides limited interoperability between ESM and CommonJS modules:
- CJS to ESM: You can import CommonJS modules into ESM using
import. - ESM to CJS: You need to use
import()(dynamic import) to load ESM modules into CommonJS.
6. All examples of CJS vs MJS export and import
- In-short, CJS have a
module.exportsand in MJS there’s onlyexport. - In CJS
exportsis a reference tomodule.exports. But always avoid to mess things. - In CJS,
module.exports = {} or functionno need for{}in require. But ifmodule.exports.foois used, thenfoois added as a property, and can only be required with{}.
// =======================================================================
// ========================== COMMONJS (CJS) ============================
// ========================== module.exports ============================
// =======================================================================
// 1. Single export (function)
module.exports = function add(a, b) {
return a + b;
};
// 2. Single export (object)
module.exports = {
sum(a, b) { return a + b; },
mul(a, b) { return a * b; }
};
// 3. Export multiple items individually
module.exports.add = (a, b) => a + b;
module.exports.sub = (a, b) => a - b;
// 4. Using `exports`
exports.mul = (a, b) => a * b;
exports.div = (a, b) => a / b;
// 5. Export a class
class Person {
constructor(name) { this.name = name; }
}
module.exports.Person = Person;
// 6. Runtime conditional exports
if (process.env.NODE_ENV === 'prod') {
module.exports.config = require('./prod.config');
} else {
module.exports.config = require('./dev.config');
}
// 7. CJS export merging (valid)
module.exports = {};
module.exports.a = 10;
module.exports.b = 20;
// 8. ❌ Wrong pattern — reassigning `exports`
/*
exports = { x: 10 }; // breaks!
*/
// 9. Exporting a function with properties (valid in CJS)
function fn() {}
fn.version = 1.0;
module.exports = fn;
// 10. Exporting a constructor + static functions
function Service() {}
Service.start = () => {};
module.exports = Service;
// 11. Exporting JSON is also valid
module.exports.settings = require('./settings.json');
// =======================================================================
// ========================= ECMASCRIPT MODULES =========================
// ========================== MJS / ESM =====================
// =======================================================================
// ----------------------
// Named exports
// ----------------------
// 12. Named function export
export function greet(name) {
return `Hello ${name}`;
}
// 13. Named variables export
export const A = 10;
export const B = 20;
// 14. Named class export
export class User {
constructor(n) { this.name = n; }
}
// 15. Inline declarations
export const PI = 3.14;
export function area(r) { return PI * r * r; }
// 16. Export list at bottom
function foo() {}
function bar() {}
export { foo, bar };
// 17. Renaming exports
function login() {}
function logout() {}
export { login as userLogin, logout as userLogout };
// ----------------------
// Default exports
// ----------------------
// 18. Default export — function
export default function logger(msg) {
console.log("LOG:", msg);
}
// 19. Default export — arrow function
export default (msg) => {
console.log("LOG:", msg);
};
// 20. Default export — class
export default class Animal {
constructor(type) { this.type = type; }
}
// 21. Default export — object literal
export default {
x: 1,
y: 2,
print() { console.log("hello"); }
};
// 22. Default export — already declared value
const apiVersion = "v1";
export default apiVersion;
// 23. Default export — renaming a value
function compute() {}
export default compute;
// ----------------------
// Re-exports
// ----------------------
// 24. Re-export all
export * from './math.js';
// 25. Re-export specific
export { add, mul } from './operations.js';
// 26. Re-export with rename
export { add as plus } from './calc.js';
// 27. Re-export default (rare but valid)
export { default as MyUtil } from './util.js';
// 28. Export + re-export together
export { A as Alpha };
export * from './helpers.js';
// -------------------------------------------------------
// Dynamic imports (valid in ESM)
// -------------------------------------------------------
// 29. Dynamic import (top-level await)
const config = await import('./config.mjs');
// 30. Dynamic import inside a function
export async function loadLib() {
const mod = await import('./lib.mjs');
return mod;
}
// -------------------------------------------------------
// Hybrid CJS+ESM patterns (when using `"type": "module"`)
// -------------------------------------------------------
// 31. ESM export of CJS module using createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
export const legacy = require('./old-cjs.js');
// =======================================================================
// ************************** END OF MASTER FILE **************************
// =======================================================================