解决包冲突

前两天和同事交流的时候,偶然间提到了包冲突的问题,

我们设想一个情况,包B调用的是包A的@3.0.0版本,然后包C调用的是包A的@4.0.0版本,我们代码本身又需要用到包A的@5.0.0版本。

这个时候如果用npm包管理工具,就需要执行以下代码

npm i --legacy-peer-deps

那么后面这个 --legacy-peer-deps到底是什么

这里我们就要提到一个东西叫peerDependency,

package.json文件中,存在一个叫做peerDependencies(对等依赖关系)的对象,它包含了项目里需要的所有的包或用户正在下载的版本号相同的所有的包。

如果我们现在没有使用peerDependencies,那么我们上面提到的情况的包如下

└── main
     └── node_modules
         ├── packageA@5.0.0
         ├── packageB
         │    └── node_modules
         │    └── packageA@3.0.0
         └── packageC
             └── node_modules
                 └── packageA@4.0.0

按照这种情况,好像确实没有出现冲突,那么为什么不直接都使用这种情况呢。

我们可以看到,在这种情况下,packageA被安装了3次,造成了2次安装冗余,整个包就会非常的大(有一个项目已经11G了)

但如果我们使用peerDependencies的方式,那么生成的依赖结构如下

└── main
    └── node_modules
        ├── packageA
        ├── packageB
	└── packageC

很显然,packageA只被安装了一次,因此在npm v7开始,就全部是这种方式。

那么此时packageA的版本到底是什么呢?遵循以下原则

  1. 如果用户在根目录的package.json显示依赖了核心库(packageA),那么各个子项目里的peerDepenedencies声明就可以忽略
  2. 如果用户没有显示依赖核心库,那么就按照子项目的peerDepenedencies中声明的版本将依赖安装到项目根目录里

然后方式2就出现了一个问题,如果子项目依赖的包不兼容(packageA@4.0.0,package@A3.0.0),那么就会报错。

那么如果我们使用--legacy-peer-deps,他的本质就是绕过peerDependency的自动安装,转而使用老的npm v3-v6的下载方式。

那么其实并没有真的解决冲突,只是忽略了冲突,用老的方式进行下载。

其实还有一种方式,就是pnpm

pnpm最大的优势其实是节省硬盘空间,pnpm确实可以解决冲突,其目录结构和peerDependencies的方式类似,最大的优势在于基于符号链接的node_modules,使得在安装多个版本依赖时占用较小的磁盘空间。

下面是官网链接

基于符号链接的 node_modules 结构

参考文献

npm install xxxx --legacy-peer-deps命令是什么?_华为云开发者联盟的博客

Q.E.D.