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