写在前面

在平时的项目开发中,我们只需要知道 dependencies 和 devDependencies 的区别就好了,环境依赖放在 devDependencies 里,非环境依赖放在 dependencies 。但是在涉及到开发一个 npm 包的时候,我们就需要深入了解 package.json 里依赖相关字段的区别了。

dependencies

dependencies 翻译过来就是依赖的意思,我们新开发一个包,里面如果完全没有依赖任何第三方包,那么该字段就可以没有。但是实际上,这种情况很少,我们新开发的包一般都会引用到一些第三方包,就是在内部 require 或者 import 一些第三方使用,那么此时,这些被我们内部引用的第三方包就应该放在 dependencies 字段里,这是标准的位置。

那么在别人安装我们的这个包的时候,npm 会如何处理 **dependencies** 字段里的依赖包呢?

首先,dependencies 字段里的第三方包是我们这个包正常运行必须的包,因此,npm 为了保证我们的包能被正常运行,就会去检查并确保这些第三方包必须有。

  1. 当引用我们的包的工程里的 node_modules 里没有我们的包的 dependencies 字段里的包时,npm 会在我们的包被安装时,将我们包里 dependencies 字段里的第三方包平铺安装在 node_modules 里。
    (这也是为什么我们在安装一个包的时候,node_modules 里出现了一堆包的原因之一)
  2. 当引用我们的包的工程里的 node_modules 里有我们的包的 dependencies 字段里的包时,npm 就开始检查了,检查版本,若现有的版本符合能用,就不再重复安装了。比如本地工程里的版本是 1.0.0 ,我们包的 dependencies 里要求的是 ^1.0.0 ,是符合的。
  3. 当引用我们的包的工程里的 node_modules 里有我们的包的 dependencies 字段里的包,但 npm 检查出来版本不匹配,比如本地工程里的版本是 1.0.0 ,但我们包的 dependencies 里要求的版本是 ^1.0.2 , 显然版本不匹配,因为有的第三方包不同版本在 api 上有可能会有很大差异,因此 npm 为了确保我们的包能正常工作,就会在我们包里新建一个 node_modules ,在里面安装依赖。那看起来就是会有 node_modules 的嵌套,如下:

截屏2022-12-12 下午3.22.17.png

PS: 看到有些人会说,这种存在两个版本的情况会有问题,就是当这个存在两个版本的第三方包和工程有数据交互的时候,由于两个包都会被引入使用,就会有冲突或者数据覆盖的情况。是有这个可能,但我感觉,这种情况很少,我们发布的包里依赖的第三方包应该只为我们的包提供特定的功能,完成功能就好了,算是我们的包完整性的一部分。如果真的涉及到数据的,那我觉得不应该放在 dependencies 字段里。可以放在 peerDependencies 里。

devDependencies

devDependencies 翻译过来就是开发环境依赖,我们在开发一个 npm 包的时候,会涉及到编译和打包等等,这些操作依赖一些开发环境的包,按照标准,这些包应该放在 devDependencies 字段里。

那么在别人安装我们的这个包的时候,npm 会如何处理 devDependencies 字段里的依赖包呢?

答案就是,忽视它!npm 不会去管该字段的,因为这个字段里的第三方包仅仅是在其开发的时候用到的,不是我们的 npm 包的一部分。该字段只是在我们的包在本地开发的时候使用的。

peerDependencies

peerDependencies 这个词不太好翻译,直译过来就是同等依赖,对等依赖。我觉得根据其实际的作用,其实翻译为 适配依赖 比较好。

那么这个字段具体是干嘛呢?

是这样的,你想,我们开发一个第三方包的时候,我们会引用到第三方包,那我们开发的这个包是不是也会被别的第三方包引用呢?我们使用 dependencies 字段向 npm 说明了我们依赖的包,那我们是不是也应该向 npm 告知我们当前版本适用于哪些第三方包呢?

由于我们在开发的时候不知道会被哪个第三方包引用,因此这种情况一般是我们根据某个会引用我们的第三方包进行的特定开发,常见的就是插件开发。比如我们开发一个 webpack 插件,webpack 不同版本的插件开发的规范和提供的 api 是不一样的,我们开发一个 webpack 3 的插件,那我们就要告诉 npm 我们这个插件只能适配 webpack 3 ,不适配 webpack 5。用什么告诉呢?

就是使用 peerDependencies 字段。

例如 babel-loader 的 package.json 文件里的 peerDependencies 字段

截屏2022-12-12 下午3.22.50.png

peerDependencies 字段里的第三方包并不是我们的 npm 包里使用的,也不是在开发环境中会用到的包,而是我们适配的包。

那么在别人安装我们的这个包的时候,npm 会如何处理 peerDependencies 字段里的依赖包呢?

为了说明这个字段,npm 还真的是纠结了好久。在 npm v1-v2 版本的时候,npm 对这个字段的处理就是自动帮你安装,但是后来大家认为太迷惑了,功能太像 dependencies 字段了。为了让大家不迷惑,npm 在 v3-v6 的时候,对这个字段的处理是我不帮你自动安装了,给你个警告,提示你说你得自己安装这个依赖。后来,不知道 npm 听到了什么建议,在 v7-v8(目前最新) 版本又恢复到了自动帮你安装的状态。

截屏2022-12-12 下午3.23.14.png

由于 npm v1-v2 版本太老了,就不再研究了。在这里对比了下 npm v3-v6 和 npm v7-v8 版本对 peerDependencies 字段的处理。

npm v3-v6

  1. 当引用我们的包的工程里的 node_modules 里没有我们的包的 peerDependencies 字段里的包时,npm 会在我们的包被安装时,给出警告⚠️,提示用户自己安装。
  2. 当引用我们的包的工程里的 node_modules 里有我们的包的 peerDependencies 字段里的包,但 npm 对比出版本不符合是,给出警告⚠️,提示用户。
  3. 当引用我们的包的工程里的 node_modules 里有我们的包的 peerDependencies 字段里的包,npm 检查后发现版本符合,无警告,输出正常。

npm v7-v8

  1. 当引用我们的包的工程里的 node_modules 里没有我们的包的 peerDependencies 字段里的包时,npm 会在我们的包被安装时,将我们包里 peerDependencies 字段里的第三方包平铺安装在 node_modules 里。
  2. 当引用我们的包的工程里的 node_modules 里有我们的包的 peerDependencies 字段里的包, 但 npm 对比后发现版本不符合,则报错,安装失败。npm 会让用户处理这个冲突错误,若用户不处理,则可以使用其提示的,运行 npm i --forcenpm i --legacy-peer-deps。强行安装冲突中的一个版本。可解决报错,但出了问题用户自己负责。
  3. 当引用我们的包的工程里的 node_modules 里有我们的包的 peerDependencies 字段里的包, npm 对比后发现版本符合,不报错,正常安装。

PS: 总结下来,peerDependenciesdependencies 最大的区别就是,dependencies 字段会出现 node_modules 嵌套的情况,但 peerDependencies 字段是不会出现 node_modules 嵌套的情况的。

写在后面

以上内容里,dependenciesdevDependencies 都好理解。就是 peerDependencies 理解起来费劲点。坦白讲,包管理工具面对包与包之间的依赖的处理是最麻烦的,当依赖的包版本出现冲突时,按理说为了包不被重复安装和在数据交互时出现问题,应该由用户去处理包版本的冲突的。

npm 在 v7-v8 版本里虽然恢复了 peerDependencies 的自动帮你安装的状态,但又新增了一个字段叫 peerDependenciesMeta,这个字段就是可以将 peerDependencies 里的包设置为可选的状态,设置为可选的状态就是不会帮你自动安装,也不会报错。

在一些复杂情况的时候,还得综合考虑这些字段的灵活运用。还有一个字段叫 bundledDependencies 在某些时候也很有用。这些有时间再聊吧。