TypeScript: ECMAScript and CommonJS Libraries
In this tutorial, I will show you how to create [ECMAScript Module (ESM)][1] and [CommonJS][2] library with [TypeScript Programming Languages][3].
TLDR
You need to update your package.json and TypeScript configuration file to
support both ECMAScript and CommonJS.
Start from your package.json, add the following new fields (or update the
existing fields):
{
"source": "src/index.ts",
"main": "dist/cjs/index.js",
"types": "dist/cjs/index.d.ts",
"scripts": {
"build:esm": "tsc -p tsconfig.esm.json",
"build:cjs": "tsc -p tsconfig.cjs.json"
},
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
}
}
}
}
For the TypeScript configuration file, you need to create two TypeScript configuration files to target ECMAScript and CommonJS.
First, create new file tsconfig.base.json with the following content:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"moduleResolution": "node",
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"allowJs": true,
"allowSyntheticDefaultImports": true
},
"exclude": ["node_modules"]
}
Then create new file tsconfig.esm.json with the following content:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2020", "DOM"],
"module": "ES2022",
"target": "ES6",
"outDir": "dist/esm"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
Then create new file tsconfig.cjs.json with the following content:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2020", "DOM"],
"module": "CommonJS",
"target": "ES6",
"outDir": "dist/cjs"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
Now you can run the following command to build your ECMAScript and CommonJS library:
# π for npm user
npm run build:esm
npm run build:cjs
# π for pnpm user
pnpm build:esm
pnpm build:cjs
Done.
If you want to learn more, then feel free to continue below.
Brief overview
ECMAScript and CommonJS are JavaScript standard. ECMASCript is designed for the JavaScript that run in the client-side (web browsers) while CommonJS is designed for the JavaScript that run in the server-side. On recent development of Node.js and Deno, the implementation brings ECMAScript to the server-side.
If you are writing JavaScript library, supporting both standards (ECMAScript and CommonJS) is very tedious task. Itβs better to write your library in TypeScript then compile it to multiple JavaScript standards.
So how to create ECMAScript and CommonJS library with typescript?
Create ECMAScript and CommonJs library with Typescript
We will start from scratch, create new project with the following command:
# π for npm user
npm init -y
# π for pnpm user
pnpm init
Update your package.json to include the following fields:
{
"source": "src/index.ts",
"main": "dist/cjs/index.js",
"types": "dist/cjs/index.d.ts",
"scripts": {
"build:esm": "tsc -p tsconfig.esm.json",
"build:cjs": "tsc -p tsconfig.cjs.json"
},
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
}
}
}
}
Install the TypeScript compiler as development dependencies:
# π for npm user
npm install --save-dev --save-exact typescript
# π for pnpm user
pnpm add --save-dev --save-exact typescript
Create new file src/index.ts, for the demo purpose we will use the following
content:
function hello(): string {
return "Hello from CommonJS and ECMAScript";
}
export default hello;
Then we need to create new file tsconfig.base.json as our base TypeScript
configuration file that used to target both ECMAScript and CommonJS library:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"moduleResolution": "node",
"skipLibCheck": true,
"strict": true,
"allowJs": true,
},
"exclude": ["node_modules"]
}
We will use the following compiler options as the base configurations:
declarationis set totrue. This will generate.d.tsfiles for every TypeScript or JavaScript file inside your project.declarationMapis set totrue. This will generates a source map for.d.tsfiles which map back to the original.tssource file. This will allow editors such as VS Code to go to the original.tsfile when using features like Go to Definition.esModuleInteropis set totrue. This will allow you to import and build a javascript module that conform multiple javascript standards such as CommonJS and ECMAScript.forceConsistentCasingInFileNamesis set totrue. This will tell TypeScript compiler to report an error if a program tries to include a file by a casing different from the casing on disk.isolatedModulesis set totrue. This will tell TypeScript compiler to report an error if you write certain code that canβt be correctly interpreted by a single-file transpilation process.moduleResolutionis set to"node". This will tell TypeScript compiler to use Node.js module resolution strategy.skipLibCheckis set totrue. Skip type checking of declaration files.strictis set totrue. This will enables a wide range of type checking behavior that results in stronger guarantees of program correctness.allowJSis set totrue. This will tell TypeScript compiler to use.jsand.jsxtoo instead of just.tsand.tsxfiles.
Next step is to create compiler options to target ECMAScript and compiler options to target CommonJS.
Create new file tsconfig.esm.json with the following content:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2020", "DOM"],
"module": "ES2022",
"target": "ES6",
"outDir": "dist/esm"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
Then create new file tsconfig.cjs.json with the following content:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2020", "DOM"],
"module": "CommonJS",
"target": "ES6",
"outDir": "dist/cjs"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
In these two files, we use the following compiler options:
libis set to["ES2020", "DOM"]itβs mean that we will have access to modern ECMAScript features such asArrayorMap. You can update this based on your project.moduleis set to"ES2022"for ECMAScript library output and"CommonJS"for CommonJS output.targetis set to"ES6".ES6is an ECMAScript standard that widely supported by ECMAScript engines both on client-side (browsers) or server-side (Node and Deno).outDiris the output directly. It is set todist/esmfor ECMAScript library anddist/cjsfor the CommonJS library.
Now we can compile our TypeScript project using the following command:
# π for npm user
npm run build:esm
npm run build:cjs
# π for pnpm user
pnpm build:esm
pnpm build:cjs
This will output the following files:
// β¦ cat dist/esm/index.js
function hello() {
return "Hello from CommonJS and ECMAScript";
}
export default hello;
// β¦ cat dist/cjs/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function hello() {
return "Hello from CommonJS and ECMAScript";
}
exports.default = hello;
Now you can publish it:
# π for npm user
npm publish
# π for pnpm user
pnpm publish
Your npm package will be available as CommonJS (via require) and ECMAScript
module (via import).
Congrats, now you have create ECMAScript and CommonJS library with one TypeScript codebase!