javascriptnode.jsnpmpackage.jsoncommonjs

As CommonJS dies out, what should go in a library’s package.json "main" field?


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 a node_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?


Solution

  • 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.