
CommonJS vs ESM: Understanding Both JavaScript Module Systems
A clear explanation of the two main module systems in JavaScript — CommonJS and ES Modules (ESM) — how they work, their differences, and how to use them in Node.js, TypeScript, and Bun.
dogunfx
JavaScript has evolved a lot — and so have its module systems. If you’ve ever seen both require() and import, you’ve encountered CommonJS (CJS) and ECMAScript Modules (ESM). Both serve the same purpose — organizing and reusing code across files — but they work differently under the hood.
This post explains both systems, when to use each, and how to transition between them safely in Node.js, TypeScript, and Bun.
1) The Purpose of Modules
Modules help developers split large programs into smaller files. Without modules, everything would exist in a single global scope — messy, risky, and hard to maintain.
// Without modules
function greet(name) {
console.log("Hello " + name);
}
With modules, we can separate functionality:
// greetings.js
export function greet(name) {
console.log(`Hello, ${name}`);
}
// app.js
import { greet } from "./greetings.js";
greet("Dogunfx");
2) CommonJS (CJS)
CommonJS is the original module system used in Node.js. It relies on require() and module.exports.
Example
// math.js
const add = (a, b) => a + b;
module.exports = { add };
// app.js
const { add } = require("./math");
console.log(add(2, 3)); // 5
Characteristics
✅ Synchronous loading — modules are loaded in order, great for server-side.
✅ Dynamic import — can require conditionally or within functions.
❌ Not browser-native — doesn’t work in browsers without bundlers.
❌ Static analysis harder — tooling like tree-shaking isn’t as effective.
Under the hood
- Each file is wrapped in a function by Node.js.
- Variables are scoped locally (not global).
- Exports are assigned to
module.exports.
3) ES Modules (ESM)
ES Modules (standardized in ES6 / 2015) are now the official module format for both browsers and Node.js.
Example
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from "./math.js";
console.log(add(2, 3)); // 5
Characteristics
✅ Asynchronous loading — ideal for browsers and modern runtimes.
✅ Static structure — enables tree-shaking and better optimization.
✅ Native in browsers & Bun.
❌ Stricter syntax — must use explicit file extensions (.js, .mjs, etc.).
❌ No dynamic require() — static imports only at the top level.
Default Export Example
// logger.js
export default function log(msg) {
console.log("[LOG]", msg);
}
// app.js
import log from "./logger.js";
log("Running ESM example");
4) File Extensions and Configuration
Node.js uses file extensions or the "type" field in package.json to decide which system to use.
Option 1 — Per-file Extension
| File | Meaning |
|---|---|
.cjs | Treated as CommonJS |
.mjs | Treated as ES Module |
Option 2 — Project-wide Type
package.json
{
"type": "module"
}
Now all .js files are treated as ESM, unless they have a .cjs extension.
5) Interoperability (Mixing Systems)
Sometimes you’ll need to use both systems, especially when using older NPM packages.
Importing CommonJS in ESM
// app.mjs
import pkg from "lodash";
const { isEmpty } = pkg;
Importing ESM in CommonJS
// app.cjs
(async () => {
const { add } = await import("./math.mjs");
console.log(add(2, 2));
})();
6) TypeScript Modules
TypeScript supports both systems depending on your config.
Example — ES Modules (Recommended)
tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext",
"target": "ES2022"
}
}
math.ts
export function add(a: number, b: number) {
return a + b;
}
app.ts
import { add } from "./math.js";
console.log(add(2, 3));
CommonJS (Legacy Projects)
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020"
}
}
Then:
const { add } = require("./math");
7) Performance and Tooling
| Feature | CommonJS | ESM |
|---|---|---|
| Loading | Synchronous | Asynchronous |
| Native in Browsers | ❌ No | ✅ Yes |
| Static Analysis / Tree Shaking | ❌ Hard | ✅ Easy |
| Top-level await | ❌ No | ✅ Yes |
| Dynamic Import | ✅ Yes (require()) | ✅ Yes (import()) |
| TypeScript Support | ✅ Mature | ✅ Modern default |
8) Real Example — Migrating from CJS to ESM
Before (CJS):
const express = require("express");
const app = express();
module.exports = app;
After (ESM):
import express from "express";
const app = express();
export default app;
In package.json:
{ "type": "module" }
9) Bun, Deno, and Modern Runtimes
| Runtime | Default Module System | Notes |
|---|---|---|
| Bun | ESM | Supports both, with fast native loading |
| Deno | ESM | Uses explicit URL-based imports |
| Node.js | CJS (legacy) / ESM (modern) | Use "type": "module" |
| Browser | ESM | <script type="module"> supported |
Example in Browser:
<script type="module">
import { add } from "./math.js";
console.log(add(1, 2));
</script>
10) Summary Table
| Concept | CommonJS | ES Modules |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Load Type | Synchronous | Asynchronous |
| Default Support | Node.js legacy | Modern JS, browsers, Bun |
| File Extensions | .js / .cjs | .js / .mjs |
| Top-Level Await | ❌ | ✅ |
| Tree Shaking | ❌ | ✅ |
| Dynamic Imports | ✅ | ✅ |
| Best For | Legacy or Node-only apps | Modern, full-stack, or browser apps |
11) References
- Node.js Docs — Modules: CommonJS
- Node.js Docs — Modules: ESM
- MDN — import Statement
- TypeScript Handbook — Modules
- Bun Docs — ESM Support
In short:
- Use CommonJS only if you must support legacy Node.js projects.
- Use ESM for everything else — it’s the modern, universal standard for both JavaScript and TypeScript.
You’ll get cleaner syntax, better tooling, and compatibility with all future runtimes.
Understanding Asynchronous Programming with Promises and async/await in JavaScript and TypeScript
Learn how JavaScript handles asynchronous operations with Promises and async/await — from callbacks to modern async flows, with real examples in both JS and TypeScript.
Getting Started with JavaScript Using Node.js or Bun
A beginner-friendly guide to learning JavaScript syntax using modern runtimes like Node.js and Bun — no browser, no DOM, just pure JavaScript.