ESM vs CJS
There are currently two main module formats in the JavaScript ecosystem: ECMAScript Modules (ESM) and CommonJS (CJS). CJS was first, but ESM is now the JavaScript standard and is the future. The main difference between the two is how they import files.
tl;dr
- CJS uses
require
andexports
/module.exports
- CJS is the past
- ESM uses
import
andexport
- ESM is the future
CJS
JavaScript used to not have any module system, so CJS was created. CJS is the default module format in Node.js but has never been supported in browsers, without expensive building processes. Thus, there has been a split between code that natively supports Node.js vs natively supporting browsers. (As explained in the CJS section, this is no longer the case.)
How to Run JavaScript in CJS Mode
- run
node
without"type": "module"
in an ancestorpackage.json
- or without any ancestor
package.json
- or with
"type": "commonjs"
- or without any ancestor
- transpile TS to JS with
"module": "CommonJS"
(this produces CJS outputs) - use the
.cjs
or.cts
extensions (to automatically trigger CJS mode)
CJS syntax
- import other modules with
require
:// import export by name
const {myVar} = require('./my-module.js');
// import the default export
const myVar = require('./my-module.js'); - create exports by assigning them to the global
exports
object:exports.myVar = 'my var';
- create a default export by assigning it to
module.exports
:module.exports = 'my var';
- dynamic (async)
import
can be used:const {myVar} = await import('./my-module.js');
- top-level
await
are not supported// this will not work
await myAsyncFunction(); - in Node.js, the path to the current script file is obtained with globals
__dirname
and__filename
ESM
The standard module system incorporated into the JavaScript language itself is ESM. ESM is already supported in browsers and Node.js support has been improving with every release. Using ESM allows you to write code that can run, natively, in both Node.js and browsers. ESM syntax is also (imo) much cleaner than CJS syntax.
How to Run JavaScript in ESM Mode
- include
type="module"
in<script>
tags in HTML:<script type="module" src="./my-modules.js"></script>
- add
"type"="module"
in yourpackage.json
file - transpile TS to JS with any
"module"
compiler option that starts with "ES" (ES6
,ES2022
, etc) (this will produce ESM outputs) - use the
.mjs
or.mts
extensions (to automatically trigger ESM mode)
ESM syntax
- import other modules with
import
:// import export by name
import {myVar} from './my-module.js';
// import the default export
import myVar from './my-module.js';
// import all named exports
import * as myVar from './my-module.js'; - use the
export
keyword for exports:export const myVar = 'my var';
- create a default export using
export default
:export default 'my var';
- dynamic (async)
import
can be used:const {myVar} = await import('./my-module.js');
- top-level
await
is supported// this will work
await myAsyncFunction(); - in Node.js, the path to the current script file is obtained with
import.meta.dirname
andimport.meta.filename
ESM-only features
- ESM can statically import CJS files
- CJS cannot statically import ESM (
import {} from '';
) - CJS can dynamically import ESM (
import('')
)
- CJS cannot statically import ESM (
- ESM supports top-level await
- this might not seem like much but has been a game-changer for my open source packages
- native browser support