DEV Community

Cover image for Dual publish ESM and CJS with tsdown
Sean Boult
Sean Boult

Posted on

Dual publish ESM and CJS with tsdown

There comes a time when you have to dual publish ESM (ECMAScript) and CJS (CommonJS) for your TypeScript projects. This guide should walk you through the steps to get this working properly with tsdown.

NOTE: if you are using tsup there is a codemod provided by tsdown to migrate.

tsdown setup

First let's install tsdown with your favorite package manager, in this case I'll be using pnpm.

pnpm i -D tsdown
Enter fullscreen mode Exit fullscreen mode

Now we can create the tsdown.config.ts file that will have our configuration.

import { defineConfig } from "tsdown";

export default defineConfig([
  {
    // NOTE: this is the default but update if your entrypoint differs
    entry: "src/index.ts",
    format: ["esm", "cjs"],
    sourcemap: true,
  }
]);
Enter fullscreen mode Exit fullscreen mode

package json setup

It's important to get these right otherwise types will fail to resolve in every scenario.

Add the following to your package.json file.

{
  // ...
  "files": [
    "dist"
  ],
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

validation

We can us the are the types wrong CLI to interrogate the output and ensure type resolution is working in all scenarios.

npx -y @arethetypeswrong/cli --pack .
Enter fullscreen mode Exit fullscreen mode

Which should yield something like this if all goes well.

 No problems found 🌟


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚ "tsdown-dual-publish-esm-cjs" β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node10            β”‚ 🟒                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from CJS) β”‚ 🟒 (CJS)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from ESM) β”‚ 🟒 (ESM)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ bundler           β”‚ 🟒                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Demo

Here is a simple demo I've created to show what we've seen here.

tsdown-dual-publish-esm-cjs

If you want to migrate from tsup this is a repo to follow.

pnpm i
pnpm test

What the ouput of test looks like.

> tsdown
β„Ή tsdown v0.14.2 powered by rolldown v1.0.0-beta.34
β„Ή Using tsdown config: /Users/hacksore/code/tsdown-dual-publish-esm-cjs/tsdown.config.ts
β„Ή entry: src/index.ts
β„Ή tsconfig: tsconfig.json
β„Ή Build start
β„Ή Cleaning 4 files
β„Ή [CJS] dist/index.cjs  0.10 kB β”‚ gzip: 0.11 kB
β„Ή [CJS] 1 files, total: 0.10 kB
β„Ή [CJS] dist/index.d.cts  0.09 kB β”‚ gzip: 0.10 kB
β„Ή [CJS] 1 files, total: 0.09 kB
β„Ή [ESM] dist/index.js    0.10 kB β”‚ gzip: 0.11 kB
β„Ή [ESM] dist/index.d.ts  0.09 kB β”‚ gzip: 0.10 kB
β„Ή [ESM] 2 files, total: 0.19 kB
βœ” Build complete in 575ms

tsdown-dual-publish-esm-cjs v0.0.0

Build tools:
- typescript@~5.8.3
- tsdown@^0.14.2

 No problems found 🌟


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚ "tsdown-dual-publish-esm-cjs" β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node10            β”‚ 🟒                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from CJS) β”‚ 🟒 (CJS)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ node16 (from
…

Top comments (0)