When the maintainer of a JavaScript package decides to stop distributing a CommonJS version, what should go into the "main"
field of the package.json file?
Example old package.json fields with the CommonJS file as the "main"
entry:
"type": "module",
"module": "dist/pretty-print-json.js",
"browser": "dist/pretty-print-json.min.js",
"main": "dist/pretty-print-json.umd.cjs",
"types": "dist/pretty-print-json.d.ts",
"files": ["dist"],
"exports": {
".": {
"import": "./dist/pretty-print-json.js",
"require": "./dist/pretty-print-json.umd.cjs"
},
"./": "./dist/"
},
Example new package.json fields:
"type": "module",
"module": "dist/pretty-print-json.js",
"browser": "dist/pretty-print-json.min.js",
"main": “???????????”,
"types": "dist/pretty-print-json.d.ts",
"files": ["dist"],
"exports": "./dist/pretty-print-json.js",
The node documentation states:
The
"main"
field defines the entry point of a package when imported by name via anode_modules
lookup. Its value is a path. When a package has an"exports"
field, this will take precedence over the"main"
field when importing the package by name.
node will use "exports"
over "main"
, but the various bundlers out in the wild might implement different rules. Should the "main"
field simply be removed once a package drops support to require()
a CommonJS module?
Remove the "main"
field and use "exports"
instead.
An author of many prominent npm packages, sindresorhus, wrote an FAQ titled "How can I move my CommonJS project to ESM?" and recommended:
Replace
"main": "index.js"
with"exports": "./index.js"
in your package.json.
A quick survey of some of the popular packages I depend on shows lots of inconsistencies. Some packages even set "type": "module"
and point "main"
to an ESM file. The most important thing is to set "exports"
correctly.