DEV Community

Cover image for Node.js Package Authors: Please support both CJS and ESM
Endel Dreyer
Endel Dreyer

Posted on

Node.js Package Authors: Please support both CJS and ESM

On this post I'll describe you how you can easily add support for both CJS (CommonJS) and ESM (ECMAScript Modules) when authoring your own NPM packages.

Now that TypeScript is the go-to choice for most NPM package authors, it has never been easier to support either both CJS and ESM as "build targets".

The problem

The standard TypeScript compiler (tsc) is still a bit limited when it comes to ESM output. If you use .ts for your source-code files, it simply can't output .mjs files for you. Although there are workarounds for this limitation, it often becomes too much effort, specially if you are under a monorepo with more than one NPM package to create releases from.

The solution

Use esbuild to generate both .js and .mjs files, and tsc only for the declaration files (.d.ts).

You can use the same output directory for all of them.

npm install --save-dev esbuild
Enter fullscreen mode Exit fullscreen mode

Generating the CommonJS (CJS) output:

npx esbuild --outdir=build --platform=node --format=cjs src/*.ts
Enter fullscreen mode Exit fullscreen mode

Generating the ES Modules (ESM) output:

npx esbuild --out-extension:.js=.mjs --outdir=build --platform=node --format=esm src/*.ts
Enter fullscreen mode Exit fullscreen mode

Generating the TypeScript declaration files:

npx tsc
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can do these 3 steps through both esbuild and typescript APIs. See the full example using esbuild and typescript APIs.


Finally, for the package.json, you can combine all the 3 outputs:

  "main": "./build/index.js",
  "module": "./build/index.mjs",
  "typings": "./build/index.d.ts",
Enter fullscreen mode Exit fullscreen mode

Hope this helps!

Discussion (3)

Collapse
yoursunny profile image
Junxiao Shi

This post fails to argue why you want us package publishers to offer both formats and double our workload.
I only publish ESM, which works fine in Node 16, Node 18, and Webpack 5.

Collapse
endel profile image
Endel Dreyer Author

Sorry should've argued on the why, but my point of view is that Node.js/JavaScript has users from all types of backgrounds, and more often than you think a "legacy" (or not too legacy) project that heavily uses CJS desperately needs a module that is only available as ESM.

There are still loads of content and tutorials teaching beginners how to use CJS, and they'll stick with it without questioning the existance of ESM, and there you have another project starting as CJS.

Collapse
yoursunny profile image
Junxiao Shi

You can call ESM module from CJS module via import() expression. No desperation needed.