I have a number of published npm packages that I have upgraded to provide both commonjs and esm builds. Some of the packages might be for both node and the browser. All packages compiled with webpack or rollup. All are written in typescript and transpiled into a dist
directory.
I create a commonjs index.js
file that looks like this:
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./react-abortable-fetch.cjs.production.min.js')
} else {
module.exports = require('./react-abortable-fetch.cjs.development.js')
}
I set the package.json main
field to the above index.js
file.
I also generate a .esm.js
file for each package and I set both browser
and module
fields to the esm.js
file and set the type
file to be module
.
The end result is something like this:
"type": "module",
"main": "dist/index.js",
"browser": "dist/react-abortable-fetch.esm.js",
"module": "dist/react-abortable-fetch.esm.js",
"types": "dist/index.d.ts",
The problem with this approach is that only esm packages can consume it (unless I am wrong).
What is the best way to configure the package.json
file so that packages that have not made the leap yet (and that is quite a few) can still consume the package?
The idea is to leverage Node.js conditional exports to customize the import behavior depending on how you import the module (require
or import
).
In order to have two different interfaces for CommonJS and ESM, you could either:
import
and require
for your library in the same application which could cause unwanted and unpredictable behaviors)require
an ECMAScript module when it has top-level await
)So you have to set CommonJS as a TypeScript transpilation target, then create an ESM wrapper (preferably in a dedicated folder). Finally, map the CommonJS and the ESM entry points to the corresponding files in the package.json
:
"exports": {
"require": "./index.js",
"import": "./esm/index.js"
}
I have published a minimal working project here: https://github.com/Guerric-P/demo-commonjs-esm
In order to use the libraries in TypeScript, you also need one .d.ts
file aside every JavaScript file.