package.json 之 Node.js 相关字段
October 17, 2020
前言
这阵子在工作中参与了业务的抽象,计划打造通用组件并发布成 npm 包便于后续项目的共建,自然而然,学习到了一些关于 package.json 之前没有接触到的知识点。
在此,介绍一些在 package.json 中被 Node.js 所使用的字段,以下所使用的 Nodejs 版本均为 v14.14.0.
- name - 定义了以 package 形式导入时的名称,同时它决定了它在 npm 源上的唯一名称。
- type - 规定 package 下的 .js 文件被 Node.js 以 CommonJS Modules 或 ECMAScript Modules 加载。
- exports - 设置 package 的导出。
- main - 规定加载 package 时的默认入口文件。
name
json
{"name": "package-name"}
json
{"name": "package-name"}
"name"
字段定义你的 package 名称,当你将 package 发布到 npm 源上,牢记 name 需要满足 规范 。
当你下载的 package 位于 node_modules 时,你就可以使用以下方式导入:
js
const pkg = require("package-name");// orimport pkg from "package-name";
js
const pkg = require("package-name");// orimport pkg from "package-name";
type
在 js 的世界中,存在着两种影响力最大的模块规范,它们是 CommonJS Modules 以及 ECMAScript Modules,好在 Nodejs 自从 v12 起就全部支持了,.js
.cjs
文件默认以 CommonJS Modules 执行,.mjs
则默认以 ECMAScript Modules 执行。
而 "type"
字段的出现让我们更好得决定 .js
文件被哪种模块规范执行,它的值有两个,分别是 "module"
和 "commonjs"
.
json
{"type": "commonjs"}
json
{"type": "commonjs"}
当你设置为 "commonjs"
时,那些以该 package.json 作为最近的父级配置文件的 .js
.cjs
文件默认以 CommonJS Modules 执行,如果你想执行 ECMAScript Modules,就必须将后缀名改为 .mjs
.
json
{"type": "module"}
json
{"type": "module"}
同理,当你设置为 "module"
时,那些以该 package.json 作为最近的父级配置文件的 .js
.mjs
文件默认以 ECMAScript Modules 执行,如果你想执行 CommonJS Modules,就必须将后缀名改为 .cjs
.
exports
"exports"
字段允许你通过引用自己的 package name(Self-referencing a package using its name)来定义 package 的入口文件,举个例子:
json
{"name": "pkg","exports": {".": "./main.mjs","./foo": "./foo.js"}}
json
{"name": "pkg","exports": {".": "./main.mjs","./foo": "./foo.js"}}
以上可以被解读为:
json
{"exports": {"pkg": "pkg/main.mjs","pkg/foo": "pkg/foo.js"}}
json
{"exports": {"pkg": "pkg/main.mjs","pkg/foo": "pkg/foo.js"}}
js
import { something } from "pkg"; // from "pkg/main.mjs"
js
import { something } from "pkg"; // from "pkg/main.mjs"
js
const { something } = require("pkg/foo"); // require("pkg/foo.js")
js
const { something } = require("pkg/foo"); // require("pkg/foo.js")
它从 Node.js v12 开始被支持,并作为 "main"
(具体介绍请看下一节) 字段的替代方案。
他最大的一个特性就是 条件导出(Conditional Exports),当该 package 被导入时,能够判断被导入时的模块环境,从而执行不同的文件,简而言之就是,我们如果使用 import
命令,入口会加载 ECMAScript Modules 文件,如果使用 require
命令,入口则加载 CommonJS Modules 文件。
我们来一探究竟,具体目录结构如下:
bash
├── mod│ ├── mod.js│ ├── mod.cjs│ ├── package.json│── app.js└── app.mjs
bash
├── mod│ ├── mod.js│ ├── mod.cjs│ ├── package.json│── app.js└── app.mjs
mod 作为一个本地的 package,它的 package.json 定义如下:
json
{"name": "mod","main": "index.js","type": "module","exports": {"require": "./mod.cjs","import": "./mod.js"}}
json
{"name": "mod","main": "index.js","type": "module","exports": {"require": "./mod.cjs","import": "./mod.js"}}
并且他提供了实现相同功能的两个脚本文件,以应对不同的模块环境:
js
// mod.cjsexports.name = "cjs";
js
// mod.cjsexports.name = "cjs";
js
// mod.jsexport const name = "mjs";
js
// mod.jsexport const name = "mjs";
调用该模块的文件如下:
js
// app.jsconst { name } = require("mod");console.log(name);
js
// app.jsconst { name } = require("mod");console.log(name);
js
// app.mjsimport { name } from "mod";console.log(name);
js
// app.mjsimport { name } from "mod";console.log(name);
这一切还不够,因为 mod 现在作为一个本地 package,它既不是从 npm 上 download 下来,也不处于 node_modules 目录中,无法通过 package name 的形式导入。
办法总是有的,借助于 package link,我们可以实现本地 package 的关联,而不用正式发布到 npm 源上。
先在 ./mod
目录下执行 yarn link
:
bash
success Registered "mod".info You can now run `yarn link "mod"` in the projects where you want to use this package and it will be used instead.
bash
success Registered "mod".info You can now run `yarn link "mod"` in the projects where you want to use this package and it will be used instead.
然后在项目根目录下,执行 yarn link "mod"
:
bash
success Using linked package for "mod".
bash
success Using linked package for "mod".
最后,验证我们的 "exports"
字段是否起作用了:
bash
$ node app.js$ cjs$ node app.mjs$ mjs
bash
$ node app.js$ cjs$ node app.mjs$ mjs
结果证实,在不同的模块环境中,执行了不同的脚本文件。
注意,对于所有在 "exports"
中定义的路径都必须是绝对路径。即 ./
的形式。
main
json
{"main": "./main.js"}
json
{"main": "./main.js"}
"main"
字段定义了导入 package 目录时使用的入口文件,举个例子:
js
const pkg = require("package-name");
js
const pkg = require("package-name");
由于 node_modules 的查找机制,会被解析成以下路径:
js
const pkg = require("./node_modules/package-name");
js
const pkg = require("./node_modules/package-name");
这里只导入了一个目录,他不是一个具体模块,但是借助 main 即入口文件,文件路径最终被解析为:
js
const pkg = require("./node_modules/package-name/main.js");
js
const pkg = require("./node_modules/package-name/main.js");
注意,当一个 package 同时拥有 "exports"
和 "main"
字段时,在被以 package name 方式导入时,exports 的优先级较高。