Dual CommonJS/ES module packages
Some would caution against stepping into this minefield, but at my company, we work in a monorepo that houses both frontend apps and backend microservices. Ideally, we would have configured our TypeScript packages to target only ES Modules, but that was not the case. As a result, we need to transpile certain shared packages to support both CommonJS and browser runtimes.
Configurations
To support multiple build targets in one package, we first need to make that explicit in the TypeScript toolchain. Here, we create tsconfig.json
files specifying the compiler options necessary for each of our build targets:
// tsconfig.esm.json
{
"compilerOptions": {
"outDir": "dist/esm",
"module": "ESNext",
"moduleResolution": "bundler"
// ... other options
}
}
// tsconfig.cjs.json
{
"compilerOptions": {
"outDir": "dist/cjs",
"module": "CommonJS",
"moduleResolution": "node"
// ... other options
}
}
Then in package.json
, we specify the entrypoints for our package when imported:
// package.json
{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
}
},
// ...
}
See the Node.js documentation for more on conditional exports.
Build
When building, we compile our package twice, transpiling our TypeScript to ESM and CommonJS separately:
# Transpiling to ESM
tsc -P tsconfig.esm.json
# Transpiling to CommonJS
tsc -P tsconfig.cjs.json
and then in our final build output we should see:
dist
├── cjs
│ ├── ...
│ ├── ...
│ └── index.js
└── esm
├── ...
├── ...
└── index.js