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