在 npm 包的 package.json
文件中,有两个字段可以定义包的入口点:main
和 exports
。这两个字段既可以定义 ES 模块的入口点,也可以定义 CommonJS 模块的入口点。
main
字段在所有版本的 Node.js 中都得到支持,但其功能是有限的:它仅定义了包的主要入口点。
有关
main
字段的用法详见 package.json 中的 main 字段的作用是什么?
exports
字段为 main
提供了一种更现代的替代方案,它允许定义多个入口点,支持在不同环境之间进行条件入口解析,并且除了 exports
中定义的入口点之外,阻止其他任何入口点。这种封装方式使模块作者能够清晰地定义其包对外的接口。
对于面向当前新版本的 Node.js 的 npm 包。建议使用 exports
字段定义包的入口。对于面向 Node.js 10 或更低版本的 Node.js 包,main
字段是必需的。如果同时定义了 exports
和 main
,在受支持的 Node.js 版本中,exports
字段优先于 main
字段。
可以在 exports
中使用条件导出,以根据不同的环境定义包的不同入口点,包括包是通过 require
还是通过 import
引用的。
已有的 npm 包如果引入了 exports
字段,将阻止 npm 包的使用者使用任何未定义的入口点,包括 package.json
(例如 require('your-package/package.json')
)。这可能会是该包的破坏性变更。
具体看看案例1,该例子的目录结构如下:
为了模仿实际项目中使用 npm 包的场景,就创建了 node_modules
文件夹,node_modules/common/package.json
的内容如下,使用 exports
导出了 node_modules/common/index.js
导出的所有 API:
{
"name": "common",
"exports": {
".": "./index.js"
}
}
node_modules/common/index.js
文件内容如下:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = {
add: add,
subtract: subtract,
};
app.js
文件的内容如下:
const util = require("common")
const pkg = require('common/package.json')
const total = util.add(1, 2)
console.log('res ', {
pkg,
total
})
然后在命令行终端使用 Node.js 运行 app.js
文件代码,会发现报错了,是由于 package.json 文件中的 exports
字段没有导出 package.json 所致:
下面看看案例2
此案例在 node_modules/common/package.json
文件中导出了 package.json 中的内容:
{
"name": "common",
"exports": {
".": "./index.js",
"./package.json": "./package.json"
}
}
其他地方与案例1相同,然后在命令行终端运行 app.js
文件,发现没有报错了:
为了使 package.json 中 exports
的引入不造成破坏性变更,需确保每个之前支持的入口点都被导出。最好明确指定入口点,以便包的对外开放的 API 得到良好定义。例如,一个之前导出了 main
、lib
、feature
和 package.json
的项目可以使用如下 package.exports
:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/index": "./lib/index.js",
"./lib/index.js": "./lib/index.js",
"./feature": "./feature/index.js",
"./feature/index": "./feature/index.js",
"./feature/index.js": "./feature/index.js",
"./package.json": "./package.json"
}
}
或者,一个 npm 包可以选择使用导出模式,导出带扩展名和不带扩展名子路径的整个文件夹:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./lib/*.js": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./feature/*.js": "./feature/*.js",
"./package.json": "./package.json"
}
}
通过上述方式为任何小版本的 npm 包提供向后兼容性,那么该 npm 包未来的主版本更新就可以适当地将导出内容限制为仅公开特定的功能导出:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature/*.js": "./feature/*.js",
"./feature/internal/*": null
}
}
👆 不会导出
feature/internal
文件下的所有内容
总结
在 npm 包的 package.json
文件中,有两个字段可以定义包的入口点:main
和 exports
。
main
字段定义包的主要入口点,而 export
字段是 main
字段的现代替代方案,可以定义多个入口点,支持在不同环境之间进行条件入口解析,并且除了 exports
中定义的入口点之外,阻止其他任何入口点。
对于面向 Node.js 10 或更低版本的 Node.js 包,main
字段是必需的。如果同时定义了 exports
和 main
,在受支持的 Node.js 版本中,exports
字段优先于 main
字段。