每日一报
package.json 的 exports 字段
Reference
npm 包的 package.json 中包含的 exports 字段只是为了告知 node,依赖方的不同方式加载 npm 包所对应的不同入口模块的位置。
import demo from 'demo';
console.log(demo);const demo = require('demo');
console.log(demo);{
"exports": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
}
}上述例子中,依赖方 main-esm.js 和 main-cjs.js 分别通过 import 和 require 加载 demo 包,node 会根据 demo/package.json 中的 exports 字段来决定 demo 包的入口模块路径。但这仅意味着是 npm 包的入口模块路径,换句话说,node 并不会因为 npm 包的入口模块是由 exports 字段的 import 和 require 决定而将入口模块认定为相对应模块格式进行解析。
npm 包的入口模块路径确认后,node 会做如下决策:
优先看文件扩展名:若以
.mjs结尾,则以esm模块解析,若以.cjs结尾,则以cjs模块解析。对于
.js文件(或无扩展名文件):查找距离这个文件最近的package.json中的type字段(向上冒泡查找)。"type": "module"表示作用域内的.js文件默认为esm模块。"type": "commonjs"(或无"type"字段) 表示作用域内的.js文件默认为cjs模块。
如果无法通过以上方式确定,对于
.js文件,node可能会尝试通过DETECT_MODULE_SYNTAX进行语法检测。DETECT_MODULE_SYNTAX
这是
node的启发式做法,尝试将源码按esm规范解析。若模块源码可以以
esm规范解析成功,那么node会按照下述策略进一步进行解析:- 若源码中包含了
esm特有的语法特性(静态import或export语句、import.meta、TLA),那么则认为是esm模块。 - 在
commonjs中具有特殊的 预定义变量,例如require、exports、module、__filename或__dirname,这些 预定义变量 在esm规范中并没有特定的意义。因此模块中若以const、let或class关键字声明了上述标识符,那么node会认为这个模块不是commonjs模块,会以esm模块解析。
若模块源码无法以
esm规范解析成功,那么node会认为该模块为commonjs模块。- 若源码中包含了
若无法通过
DETECT_MODULE_SYNTAX进行语法检测,那么node会认为该模块为commonjs模块(兼容性考量)。
回到上述例子的 main-cjs.js 中模块中,node 通过包解析算法发现 demo 包的入口模块路径为 ./dist/cjs/index.js。./dist/cjs/index.js 是以 .js 为后缀的文件,那么 node 会向上冒泡查找 package.json 中的 type 字段。若 package.json 中存在 type 字段,那么 node 会根据 type 字段的值来决定解析模块的方式,若 type 字段不存在,那么 node 会尝试通过 DETECT_MODULE_SYNTAX 进行语法检测。最后兜底认为该模块为 commonjs 模块。
因此 require('demo') 可能是 require(commonjs) 或 require(esm) 的产物,取决于上述 node 的包解析算法。同理 import demo from 'demo' 可能是 import demo from commonjs 或 import demo from esm 的产物。
node解析模块的行为并不是由package.json中的exports字段决定的,而是由包作者决定的。