当前位置: 首页 > 科技观察

分析Npm、Yarn、Pnpm的依赖管理逻辑

时间:2023-03-20 21:31:48 科技观察

我们在项目开发过程中会引用各种库,各种库又依赖于其他不同的库。应该如何管理这些依赖关系?今天这篇文章主要讲的就是这个。npm2的依赖管理npm2在安装依赖的时候比较简单直接,直接按照包依赖的树形结构下载并填充本地目录结构。比如项目中A和C都依赖B。不管依赖的B是不是同一个版本,都会直接无脑生成对应的树结构。比如我们现在有如下依赖:A@2.0.0:BaseA@1.0.0BaseB@2.0.0B@3.0.0:BaseA@1.0.0BaseB@2.0.1那么npm后在node_modules中生成的内容我会如下。这种结构很直观,但是有一个问题,如果项目中的依赖太多的话,可能会导致以下问题:生成的依赖嵌套很深。同版本依赖大量冗余的npm3/yarn依赖管理npm3已经优化了npm2的情况,那么如何优化呢?其实我们最直观的想法就是把树压扁,压扁依赖关系,并不能解决嵌套太深,依赖冗余的问题。那么,在上面的例子中,如果我们使用npm3安装,最终生成的node_modules会有这样一个结构:这样是不是看起来好多了,但是这时候会出什么问题呢?让我们试试看。在项目中安装好A和B之后,可以看到我们项目本身的依赖文件中只有a_klx和b_klx,但是执行npmi命令后发现多了几个包a_base_klx和b_base_klx没介绍。其实这是a_klx和b_klx自己引入的一个npm包,只不过出现在我们的node_modules下。那么如果我们直接使用这两个包会怎么样呢?可以看到,我们可以使用这两个我们没有在dependencies中声明的npm包,因为这两个包存在于我们项目的node_modules中接下来,根据npm包的搜索规则,我们可以找到这两个包.所以这种依赖关系导致了以下两个问题:我们项目本身的node_modules结构不直观,依赖不安全。我们可以使用未在依赖文件中声明的npm包。其实第一点的问题不是很大,主要是第二点会导致一些奇怪的问题。还是拿我们前面的例子来说,在项目中直接使用a_base_klx就可以了,因为node_modules中这两个包其实是存在的,但不是永远存在的。万一哪天,把a_klx和b_klx这两个基础包的引用去掉了,a_base_klx和b_base_klx在node_modules中就不存在了,那我们的代码就会出问题了……那就是:我的代码什么都没有同时,我们很容易对这种处理方式产生疑问。如果我同时引用了同一个包的多个不同版本,会给我推荐哪个包,同时我在npmi之后每次推荐的包的版本是不是一样的?会不会出现下一次2.0.0版本变成2.0.1版本的情况,比如下面这种情况:A@1.0.0:BaseA@1.0.0BaseB@2.0.0B@1.0.0:BaseA@1.0.0BaseB@2.0.1D@1.0.0:BaseA@1.0.0BaseB@2.0.1生成的依赖如下:或者如下:其实好像后者更合理,因为有两个使用2.0.1版本的包,还是提出来比较好,我们实践一下:提到的最外层的包是2.0的。0版本,然后在b_klx和d_klx的node_modules下有一个b_base_klx@2.0.1段。将首先获取包依赖项的内容。然而,我做了一些实践,发现并非如此。即使我把b_klx或d_klx移到最前面,建议的包还是a_klx依赖的2.0.0版本。然后查看了npm的源码,发现内部对获取到的依赖列表进行了一些处理,最终会通过localeCompare方法对依赖进行排序,所以会优先推荐字典序在前的npm包的底层依赖。对于我们的示例,它是a_klx所依赖的b_base_klx@。2.0.0将首先被提出。pnpm的依赖管理为了解决上述问题,pnpm采用了不同于npm/yarn的依赖管理方式。如果我们再次使用pnpm安装上述依赖,会发现项目的node_modules文件夹下只有当前package.json中声明的依赖(软链接),真正的模块文件存在于node_modules/.pnpm中,由模块Folder以name@versionnumber的形式确定平面存储(解决重复安装依赖)。同时这样的设计也避免了之前无法访问非法npm包的问题,??因为当前项目的node_modules中只有我们声明过的依赖,这也使得node_modules中的文件看起来非常直观。同时,node_modules/.pnpm中存储的文件实际上是pnpm实际缓存文件的“硬链接”,从而避免了多个项目带来的多个相同文件带来的空间浪费。但是说到硬链接,还有一个问题,那就是所有的项目都依赖同一个文件,所以在一个项目中修改某个npm包文件会影响到其他项目,这对于postinstallfriendly来说是非常难受的。然后继续实践,确实在不同的项目中修改一个npm包会影响到其他项目。同时我一般直接在node_modules中调试一些东西。感觉这种处理方式对这种操作不是很友好。但从pnpm官网来看,其实它默认会使用copy-on-write的方式进行处理,即如果你尝试修改内容,会复制一个文件,而不会影响源文件。那么为什么不起作用好像是因为libuv的bug:https://github.com/pnpm/pnpm/issues/2761,所以当copy-on-write没有生效时,又回落到hardlink方法来处理。参考文档:https://pnpm.io/npmrc#package-import-methodhttps://github.com/pnpm/pnpm/issues/2761