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
requireandexports/module.exports - CJS is the past
- ESM uses
importandexport - 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
nodewithout"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
.cjsor.ctsextensions (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
exportsobject:exports.myVar = 'my var'; - create a default export by assigning it to
module.exports:module.exports = 'my var'; - dynamic (async)
importcan be used:const {myVar} = await import('./my-module.js'); - top-level
awaitare not supported// this will not work
await myAsyncFunction(); - in Node.js, the path to the current script file is obtained with globals
__dirnameand__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.jsonfile - transpile TS to JS with any
"module"compiler option that starts with "ES" (ES6,ES2022, etc) (this will produce ESM outputs) - use the
.mjsor.mtsextensions (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
exportkeyword for exports:export const myVar = 'my var'; - create a default export using
export default:export default 'my var'; - dynamic (async)
importcan be used:const {myVar} = await import('./my-module.js'); - top-level
awaitis supported// this will work
await myAsyncFunction(); - in Node.js, the path to the current script file is obtained with
import.meta.dirnameandimport.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