2022-10-02
*post*
================================================================================

ECMAScript and CommonJS Libraries

================================================================================

0 CONTENTS

*post-contents*

*post-intro*

1 INTRO

In this tutorial, I will show you how to create ECMAScript Module (ESM) and CommonJS library with TypeScript Programming Languages.

*post-tldr*

2 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.

*post-brief-overview*

3 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?

*post-create-library*

4 CREATE LIBRARY

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:

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:

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!

================================================================================

TAGS

*post-tags*