每日一报
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
字段决定的,而是由包作者决定的。