Creating an ECMAScript Module and CommonJS Library with TypeScript

·

2 min read

Creating an ECMAScript Module and CommonJS Library with TypeScript

Introduction

In this tutorial, we'll learn how to create a library that supports both ECMAScript Module (ESM) and CommonJS (CJS) standards using TypeScript. This approach is beneficial for making your library compatible with various environments, including both client-side and server-side JavaScript.

Quick Setup

To support both ESM and CJS in TypeScript, we need to adjust our package.json and TypeScript configuration files.

Update package.json

Add these fields to your package.json:

{
  "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"
      }
    }
  }
}

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;

TypeScript Configuration Files

Create a base TypeScript configuration file tsconfig.base.json:

{
  "$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"]
}

Create tsconfig.esm.json for ESM:

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2020", "DOM"],
    "module": "ES2022",
    "target": "ES6",
    "outDir": "dist/esm"
  },
  "include": ["src/**/*.ts"]
}

Create tsconfig.cjs.json for CJS:

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2020", "DOM"],
    "module": "CommonJS",
    "target": "ES6",
    "outDir": "dist/cjs"
  },
  "include": ["src/**/*.ts"]
}

Building the Library

Run the following commands to build your library for both ESM and CJS:

npm run build:esm
npm run build:cjs
# or for pnpm users
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).

Conclusion

With this setup, your TypeScript library is compatible with both ECMAScript and CommonJS standards. This ensures broader usability across different JavaScript environments. Publish your package, and it will be consumable via both require and import syntaxes. Happy coding!