Andrew's Digital Garden

Dual CJS/ESM packages

Even in 2025, this is still a pretty common need, due to a lot of more 'legacy' node/JS applications. For many applications, supporting [[20250627030550-esm-only-packages]] is difficult, due to requirements on particular TSConfigs, Node versions, build tooling, older dependencies, and more. For these apps, they sometimes need to consume the CJS version of a package. You can't use ESM packages in CJS.

Unfortunately this is more difficult than it would ideally be. Lots of permutations that can break things, Node, Typescript, Bundlers, etc. publint is a great package to ensure that your setup is correct for your package.

The main way to do it is in the bundler. Effectively the approach is to build the entire application twice, to two separate folders, e.g. package/cjs/index.js and package/esm/index.js. Then using entry points to load the correct files depending on the environment: [[20250627030601-entry-points-package-json]].

Exactly how you set this config will matter on your bundler. Note that in ESM, there is NO __dirname, __filename, require, require.resolve. Instead, use import.meta.url with some string manipulation.

Note, I'm not best sure how to setup [[20220912113840-tree-shaking]] with this setup. ESM only packages are much better in this regard.

https://blog.isquaredsoftware.com/2023/08/esm-modernization-lessons/ https://blog.logrocket.com/transpile-es-modules-with-webpack-node-js/ https://adamcoster.com/blog/commonjs-and-esm-importexport-compatibility-examples https://antfu.me/posts/publish-esm-and-cjs

[[buildtooling]] [[dependencies]] [[js]] [[npm]]

Dual CJS/ESM packages