npm
介绍
包管理器是开发人员用来寻找、下载、安装、配置、升级和删除依赖的工具。依赖是指别的开发者为了解决特定问题开发的代码,一般会经过多方测试,在兼容性和健壮性也有一定的保证。一个项目或多或少会有一些依赖,而依赖本身有可能又有自己的依赖。 在NPM出现之前,管理依赖是一件比较麻烦的事情。如果没有现代的包管理器,开发人员需要手动下载依赖的对应版本,如果依赖本身又有依赖,还需要按照要求下载;更新依赖更是噩梦,所有过程又要再来一遍。
问题
依赖地狱问题:npm v2在安装依赖时,采用的简单的递归安装。
冗余依赖
重复的依赖包会被下载多次,给下载带宽和存储空间(node_modules过大)带来压力
嵌套层级过深
同时目录层级的加深使得文件的路径过长,在windows系统中可能会触发”Source Path Too Long”的错误弹窗。解决
npm v3采用扁平化的依赖包管理,首先在npm install时同样会遍历package.json中定义的依赖,逐个将各模块放在node_modules的第一层,当发现有重复模块时会跳过。如果碰到依赖版本不兼容的情况,则采用npm v2的处理方式,首先遇见的依赖放入node_modules目录下,后面遇到的放到依赖树中。
解析模块依赖时候,直接到node_modules下面查找模块。扁平化方案并未完全解决冗余依赖问题。
而且还带来了幽灵依赖和依赖分身的问题。
幽灵依赖:所谓幽灵依赖是指在package.json中没有定义的依赖,在项目中依然可以正常被引用。假设我们package.json中只安装了A和C,其中A又依赖B。因为在扁平化处理后B被提升到了/node_modules下,所以在项目中还是能够正常引用B。如果后来某天A不再依赖B了,或者B的版本发生了变化,就会导致依赖缺失或兼容性问题。
依赖分身:其实就是并未完全解决依赖冗余问题,还会有相同包的相同版本存在多个副本的情况。
假设现有如下依赖关系树:
yarn
介绍
yarn相对于npm做了一些优化,例如增加本地缓存,安装依赖时候,yarn首先会去查看缓存目录里是否已经下载了这个包。如果没有,yarn会下载它并放入缓存中,这样既可以避免后面重复下载,也能在离线的情况下继续使用这个包。由于缓存机制,yarn下载依赖的速度比npm快。
问题
yarn也使用扁平化管理依赖,因此同样存在幽灵依赖和依赖分身的问题。pnpm
介绍
pnpm采用内容寻址store + 虚拟store的依赖管理机制,解决了依赖地狱问题,并解决了npm和yarn扁平化带来的幽灵依赖和依赖分身问题。
原理
pnpm是如何解决依赖地狱的问题呢?
pnpm在node_modules下放了一个.pnpm目录(这就是虚拟store),这个目录扁平化存储所有依赖,每个包的每个版本只保存一份,依赖都软链到扁平化的包上。这样就解决了依赖地狱问题。
.pnpm目录扁平化存储所有依赖包,但保留了嵌套结构,只是嵌套的依赖并不会下载真实的包,而是软链到第一级的包上,这就解决了幽灵依赖和依赖分身的问题。
相对于yarn全局缓存,下载时候通过命中缓存时候复制包的操作,pnpm将所有包全局统一存储,所有的文件在磁盘中只存在一处(就是内容寻址store)。当安装包的时候,所有的文件会被硬链接。这样不会消耗额外的磁盘空间,而且多个项目可以共享同一个依赖。这种方式比yarn占用的空间更小,安装速度也更快。
另外,如果我们依赖包的不同版本,只有变化的部分会被加在store中。举个极端的例子,如果包本身有1000个文件,然后它的新版本只变化了其中的一个文件,pnpm update只会把这一个文件加到store中。这种机制进一步降低了存储所需空间。