官方说明
参考: Specifying dependencies and devDependencies in a package.json file
通过 package.json 文件中的 dependencies 及 devDependencies 字段,可以指定项目依赖的包。执行 npm install 后,npm 将下载 dependencies 和 devDependencies 所列出的包。
dependencies 和 devDependencies 的区别:
dependencies: 指定应用在生产环境中所依赖的包;devDependencies: 指定应用仅在本地开发和测试时所依赖的包。
上面的说明并不直观,下面我们通过实验演示两者的区别。
实战
实战概述: 我们创建两个 npm 包 fff-pkg-a 和 fff-pkg-b,其中 fff-pkg-b 依赖于 fff-pkg-a,分别以 dependencies 和 devDependencies 两种方式指定依赖。之后我们再创建一个测试项目 test,查看不同依赖方式的效果。
创建 fff-pkg-a 包
首先,我们创建文件夹 fff-pkg-a,进入该文件夹并通过 pnpm init 命令进行初始化,创建 index.js 文件。最终该文件夹下有 package.json 和 index.js 两个文件。内容分别为:
- package.json:
{
"name": "fff-pkg-a",
"version": "1.1.1",
"description": "测试包发布",
"main": "index.js",
"type": "module",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC"
}
注意: 要将 package.json 的 type 字段设为 module 类型,否则 index.js 不能使用 export 语法。
- index.js
export function foo() {
console.log("fff-pkg-a@1.1.1");
}
最后通过 npm publish 发布 fff-pkg-a 包,发布 npm 包参考 如何发布一个自己的 npm 包。
创建 fff-pkg-b 包
创建 fff-pkg-b 文件夹,通过 pnpm init 初始化创建 package.json 文件。
通过 pnpm add fff-pkg-a 以 dependencies 的方式安装依赖,并创建 index.js,最终,package.json 和 index.js 的内容分别为:
- package.json
{
"name": "fff-pkg-b",
"version": "1.2.1",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"fff-pkg-a": "1.1.1"
}
}
- index.js
import { foo } from "fff-pkg-a";
export function bar() {
foo();
console.log("fff-pkg-b 【dependencies 方式】 依赖 fff-pkg-a");
}
创建测试项目
创建文件夹 test,初始化项目,引入 fff-pkg-b 的依赖:
$ mkdir test
$ cd test
$ pnpm init
$ pnpm add fff-pkg-b@1.2.1
创建 index.mjs :
import { bar } from "fff-pkg-b";
bar();
执行 node index.mjs:
$ node index.mjs
fff-pkg-a@1.1.1
fff-pkg-b 【dependencies 方式】 依赖 fff-pkg-a
我们正确地调用了 fff-pkg-b 中的 bar 方法,而 bar 方法也正确地调用了 fff-pkg-a 中的 foo 方法。
查看 node_modules 中的文件结构:
$ tree -a node_modules
node_modules
├── .modules.yaml
├── .pnpm
│ ├── fff-pkg-a@1.1.1
│ │ └── node_modules
│ │ └── fff-pkg-a
│ │ ├── index.js
│ │ └── package.json
│ ├── fff-pkg-b@1.2.1
│ │ └── node_modules
│ │ ├── fff-pkg-a -> ../../fff-pkg-a@1.1.1/node_modules/fff-pkg-a
│ │ └── fff-pkg-b
│ │ ├── index.js
│ │ └── package.json
│ ├── lock.yaml
│ └── node_modules
│ └── fff-pkg-a -> ../fff-pkg-a@1.1.1/node_modules/fff-pkg-a
└── fff-pkg-b -> .pnpm/fff-pkg-b@1.2.1/node_modules/fff-pkg-b
因为 test 项目显式依赖 fff-pkg-b,因此在 node_modules 根目录下有 fff-pkg-b 软连接,指向 fff-pkg-b 文件夹。并且 .pnpm 目录下有 fff-pkg-a@1.1.1,该文件夹被 fff-pkg-b@1.2.1 中的一个软连接所引用,表明了 fff-pkg-b 依赖于 fff-pkg-a。
修改依赖方式为 devDependencies
修改 fff-pkg-b 中依赖方式及版本号,package.json 的内容变为:
{
"name": "fff-pkg-b",
"version": "1.2.3",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"fff-pkg-a": "1.1.1"
}
}
并修改 index.js 的打印信息:
import { foo } from "fff-pkg-a";
export function bar() {
foo();
console.log("fff-pkg-b 【devDependencies 方式】 依赖 fff-pkg-a");
}
通过 npm publish 重新发布 fff-pkg-b。
删除 test 项目中的 node_modules,并将 package.json 中对 fff-pkg-a 的依赖变为 1.2.3:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"dependencies": {
"fff-pkg-b": "1.2.3"
}
}
通过 pnpm i 重新安装依赖后,执行 node index.mjs 将报错:
$ node index.mjs
node:internal/modules/esm/resolve:844
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'fff-pkg-a' imported from test/node_modules/.pnpm/fff-pkg-b@1.2.3/node_modules/fff-pkg-b/index.js
at packageResolve (node:internal/modules/esm/resolve:844:9)
at moduleResolve (node:internal/modules/esm/resolve:901:20)
at defaultResolve (node:internal/modules/esm/resolve:1121:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:396:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:365:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:240:38)
at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
at link (node:internal/modules/esm/module_job:84:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
Node.js v20.10.0
报错信息提示 fff-pkg-b 的依赖 fff-pkg-a 不能找到,可见,devDependencies 类型的依赖,不会被递归下载安装,此时查看 node_modules 的文件树:
$ tree -a node_modules
node_modules
├── .modules.yaml
├── .pnpm
│ ├── fff-pkg-b@1.2.3
│ │ └── node_modules
│ │ └── fff-pkg-b
│ │ ├── index.js
│ │ └── package.json
│ └── lock.yaml
└── fff-pkg-b -> .pnpm/fff-pkg-b@1.2.3/node_modules/fff-pkg-b
可见的确没有下载 fff-pkg-a。如果此时我们在 test 中手动引入 fff-pkg-a 包会怎么样呢?
手动引入缺失的依赖
为了区分,我们先修改并发布一个更高版本的 fff-pkg-a 库。修改 fff-pkg-a 的 index.js:
export function foo() {
console.log("fff-pkg-a@1.1.2");
}
并将 fff-pkg-a 中 package.json 的 version 字段修改为 1.1.2,重新发布 fff-pkg-a。
在 test 项目中,通过 pnpm add fff-pkg-a@1.1.2 引入 fff-pkg-a。
此时执行结果为:
$ node index.mjs
fff-pkg-a@1.1.2
fff-pkg-b 【devDependencies 方式】 依赖 fff-pkg-a
查询正常执行,node_modules 中的树结果为:
$ tree -a node_modules
node_modules
├── .modules.yaml
├── .pnpm
│ ├── fff-pkg-a@1.1.2
│ │ └── node_modules
│ │ └── fff-pkg-a
│ │ ├── index.js
│ │ └── package.json
│ ├── fff-pkg-b@1.2.3
│ │ └── node_modules
│ │ └── fff-pkg-b
│ │ ├── index.js
│ │ └── package.json
│ └── lock.yaml
├── fff-pkg-a -> .pnpm/fff-pkg-a@1.1.2/node_modules/fff-pkg-a
└── fff-pkg-b -> .pnpm/fff-pkg-b@1.2.3/node_modules/fff-pkg-b
此时,如果我们引入 fff-pkg-b@1.2.1 版本会怎么样呢?即 test 自身引入的是 fff-pkg-a@1.1.2,fff-pkg-b@1.2.1 以 dependencies 引入的是 fff-pkg-a@1.1.1,此时 test 项目中有多个版本的 fff-pkg-a 库存在,那输出的会是什么结果?
多个 fff-pkg-a 版本并存
修改 test 中 packag-pkg-b 的版本为 1.2.1:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"dependencies": {
"fff-pkg-a": "1.1.2",
"fff-pkg-b": "1.2.1"
}
}
删除 node_modules 后重新通过 pnpm i 安装依赖。
执行结果为:
$ node index.mjs
fff-pkg-a@1.1.1
fff-pkg-b 【dependencies 方式】 依赖 fff-pkg-a
可以看到 node_modules 的确存在多个版本的 fff-pkg-a 并存,并且 fff-pkg-b 使用的是通过其自身 package.json 指定的 fff-pkg-a 版本。
$ tree -a node_modules
node_modules
├── .modules.yaml
├── .pnpm
│ ├── fff-pkg-a@1.1.1
│ │ └── node_modules
│ │ └── fff-pkg-a
│ │ ├── index.js
│ │ └── package.json
│ ├── fff-pkg-a@1.1.2
│ │ └── node_modules
│ │ └── fff-pkg-a
│ │ ├── index.js
│ │ └── package.json
│ ├── fff-pkg-b@1.2.1
│ │ └── node_modules
│ │ ├── fff-pkg-a -> ../../fff-pkg-a@1.1.1/node_modules/fff-pkg-a
│ │ └── fff-pkg-b
│ │ ├── index.js
│ │ └── package.json
│ └── lock.yaml
├── fff-pkg-a -> .pnpm/fff-pkg-a@1.1.2/node_modules/fff-pkg-a
└── fff-pkg-b -> .pnpm/fff-pkg-b@1.2.1/node_modules/fff-pkg-b
总结
上述实验可以总结如下:
- fff-pkg-b 以 dependencies 方式引入 fff-pkg-a@1.1.1,test 调用
bar方法,最终会调用 fff-pkg-a@1.1.1 中的foo函数 - fff-pkg-b 以 devDependencies 方式引入 fff-pkg-a@1.1.1,因为 devDependencies 的依赖不能被递归安装,会因为找不到 fff-pkg-a 库导致 test 执行报错
- fff-pkg-b 以 devDependencies 方式引入 fff-pkg-a@1.1.1,test 显式安装依赖 fff-pkg-a@1.1.2,test 调用
bar方法,最终会调用 fff-pkg-a@1.1.2 中的foo函数 - fff-pkg-b 以 dependencies 方式引入 fff-pkg-a@1.1.1,test 显式安装依赖 fff-pkg-a@1.1.2,test 调用
bar方法,最终会调用 fff-pkg-a@1.1.1 中的foo函数
因此,dependencies 与 devDependencies 最主要的区别是,作为依赖包时,能否被递归安装依赖。