如果你打算发布一个 js 库,那么你需要理解 package.json 里的 main, module, exports, type:module.

  • type:"module": 你的项目里的*.js文件必须使用es module 语法, 如import xxx from 'xxx'. 如果你要使用commonjs语法, 如require('xxx'), 则需要把*.js文件后缀改为.cjs.
    • 相反, 如果没有type:"module", 则'*.js'文件里的代码必须使用commonjs语法. 要使用es module 语法, 则需要把*.js文件后缀改为.mjs.
  • main: js库项目的入口文件. 运行时工具如node, 打包工具如webpack, rollup都把其视为入口文件.
  • module: js库项目的es module 格式入口文件. 打包工具如webpack, rollup都把其视为入口文件. 重要的是, 运行时工具如node不使用此字段.
  • exports: node不使用module字段, 而是使用exports字段. exports字段可以同时指定commonjs和es module 格式的入口文件, 还可以指定入口文件的typescript 类型定义文件. 还可以指定其他导出点.

所以一个标准的js库项目的package.json文件如下:

{
  "type": "module"
  "main": "dist/index.cjs", // 因为`type: module`, 所以以`.cjs`结尾才能指定commonjs格式的入口文件.
  "module": "dist/index.js",
  "exports": {
    ".": {
      "import": "dist/index.mjs",
      "require": "dist/index.js",
      "types": "dist/index.d.ts"
    }
  }
}

可以看出, main是早期使用的commonjs入口文件, 后来出现了es module 格式, 打包工具rollup设定了module字段为es module 格式的入口文件, 用来缩减打包体积. 但是node不使用module字段, 而是设定了exports字段.

exports字段完美覆盖了mainmodule字段. 但是为了兼容旧的软件, 建议在你的js库里保留mainmodule字段.

到底哪个文件被node执行

假如一个js库的package.json文件如下:

{
   "name": "library",
   "main": "dist/index.js",
   "module": "dist/index.es.js"
}

没有type:module, 没有exports. 当执行import library from 'library'时, node会执行哪个文件?

你可能认为因为使用了es语法import, 所以会执行"module": "dist/index.es.js". 但是实际上不是这样的.

因为node不使用module字段, 这里也没有exports字段. 所以node会执行"main": "dist/index.js"文件. node会把main字段的commonjs格式代码转换为es module再执行. node这样做是为了兼容旧的js库, 大量旧的js库只有main字段指定的commonjs文件. 但是node不会把es module 格式的代码转换为commonjs格式.