There's three different ways of dictating the entry point of your package in a package.json
. These are mostly due to slow changes over time in Node.
main
was the original, module
was an unofficial standard, and exports
is the new way.
main
is supported in all versions of Node, but doesn't have much flexibility. It's recommended to still use it for backwards compatibility of other tools/plugins.
exports
allows multiple entry points to be defined, including different entry points depending on environments, such as CJS/ESM. exports
takes priority over main
, in supported versions.
Note that module
is still used by Typescript is moduleResolution
is set to node
.
I've found the best usage to be something like the following, taken from clsx
:
{ "main": "dist/clsx.js", // CJS, as most newer things use exports "module": "dist/clsx.mjs", // ESM "types": "clsx.d.ts", "exports": { ".": { // For ESM "import": { // types per exports field is best practice "types": "./clsx.d.mts", "default": "./dist/clsx.mjs" }, // For CJS, as a fallback // I've also seen `require` used as a key for CJS, then usually `default` is made ESM, but that should be unnecessary. "default": { "types": "./clsx.d.ts", "default": "./dist/clsx.js" } }, // Multiple entry points supported "./lite": { "import": { "types": "./clsx.d.mts", "default": "./dist/lite.mjs" }, "default": { "types": "./clsx.d.ts", "default": "./dist/lite.js" } } }
[[20250627100132-js-module-types]]
[[buildtooling]] [[dependencies]] [[node]] [[npm]]