跳转到主文档

模块的入口文件配置

对于可复用模块来说,package.json 是一个很重要的文件,其中包含了很多信息,例如项目的名称、依赖的模块。

本章将要介绍 package.json 文件中 "exports" 字段的使用以及 Modern.js 对于它做了哪些支持。

package.json 中的 exports 是什么?

在一个包的 package.json 文件中,"main" 字段可以用于定义一个包的入口文件。这个字段在 Node.js 所有版本中都支持,并且在其他工具中(例如 webpack)中也默认支持。 虽然它已经被普遍支持,但是它提供的能力其实很有限:它仅仅只能定义一个主要的入口文件。

而 "exports" 字段提供了 "main" 的替代方案,它不仅可以定义主要的入口文件,还可以自定义入口。在自定义入口的同时还起到了封装入口的作用:使用者不可以访问未在 "exports" 中定义的入口路径以外的任何其他路径。

举个例子

如果有一个 foo 包,其中包含了 ./index.js./lib.js./feature.js 三个文件,如果它的 package.json 定义如下:

{
"name": "foo",
"exports": {
".": "./index.js",
"./lib": "./lib.js"
}
}

在支持 "exports" 的环境里,你可以在代码中这样导入模块 foo 的文件:

const foo = require('foo');
const fooLib = require('foo/lib');

但是你无法这样使用:

const fooFeat = require('foo/feature');

因为在 package.json 中未定义 ./feature 入口路径。

关于"exports" 与 "main"之间的优先级

如果同时定义了 "exports" 和 "main","exports" 字段优先于 "main"。因此如果当 "exports" 字段存在的时候,"main" 字段的配置会被覆盖掉。

"exports" 字段不是特定作用于 ES Module 还是 CommonJS,因此不能使用 "main" 字段来作为使用 CommonJS 的回退手段。但它可以作为不支持 "exports "字段的旧版本的 Node.js 的回退手段。 因此对于上面的例子中我们就可以这样修改:

package.json

{
"name": "foo",
"main": "./index.js",
"exports": {
".": "./index.js",
"./lib": "./lib.js"
}
}

条件导出

"exports" 字段提供了条件导出的功能。通过条件导出,可以定义每个环境对应不同的模块包的入口文件,这也包括模块包被使用的时候是通过 require 的方式还是通过 import 的方式。

例如针对上面的例子进行扩展,假设该模块包提供了 ES Module 实现的文件 wrapper.mjs ,那么 package.json 会有如下的变化:

{
"name": "foo",
"main": "./index.cjs",
"exports": {
"import": "./wrapper.mjs",
"require": "./index.cjs"
}
}

此时如果通过 require('foo') 这样的方式使用该模块,则实际上导入的是 foo/index.cjs 文件。

而当通过 import foo from 'foo'; 这样使用的时候,则实际导入的是 foo/wrapper.mjs 文件。

补充信息

关于 .cjs.mjs

  • .mjs 为后缀的文件其代码会被识别为使用了 ES Module 模块系统。
  • .cjs 为后缀的文件其代码会被识别为使用了 CommonJS 模块系统。

除了像上面使用直接映射方式之外,还可以使用嵌套条件的方式来定义 "exports",例如:

{
"main": "./main.js",
"exports": {
"node": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
"default": "./feature.mjs",
}
}

"node" 用于匹配任何 Node.js 环境,可以是 CommonJS 或者是 ES Module 文件。

"default" 用于在不满足上面任何条件情况下的回退手段。

关于更多关于 "exports" 的使用,可以阅读 Node.js Documentation About Packages

在 Modern.js 中提供的支持

对于不同的可复用模块来说,可能会有不同的(语法以及模块化)构建产物需求 Modern.js 针对该需求提供了不同形式的构建产物以满足各种场景。

构建可复用模块 章节中提到,默认情况下,在 JS 构建产物目录(dist/js )下会存在三个目录:

  • dist/js/modern
  • dist/js/node
  • dist/js/treeshaking

这些目录从上到下分别对应了:ES6 + ES ModuleES6 + CommonJSES5 + ES Module 这三种构建产物。

因此如果可复用模块是用于在 Node.js 中使用,那么可以在 package.json 中按照如下方式进行配置:

{
"name": "node-lib",
"main": "./dist/js/node/index.js",
"exports": {
"node": {
"require": "dist/js/node/index.js",
"import": "dist/js/modern/index.js"
},
"default": "dist/js/treeshaking/index.js"
}
}
补充信息

对于其他环境,例如 webpack。可能存在不完全支持 "exports"的情况,因此对于在非 Node.js 环境运行的可复用模块要谨慎使用。

除了默认的三种产物以外,Modern.js 还提供了多个预设配置供不同场景去选择使用。具体如何使用,可以查看 output.packageMode 以及 output.packageFields