为啥用pnpm?

67 阅读3分钟

pnpm相比npm和yarn的优势有哪些?

  • 安装速度更快
  • 占用磁盘空间更小
  • 可以避免幽灵依赖
  • 版本严格依赖

为什么占用空间小,安装更快呢?

早在npm v2早期,我们安装的依赖都是把各个包的依赖安装到自己的 node_modules中,例如有c包依赖b包依赖a包 就会出现层层依赖的关系如下

  c
  node_modules
    b@0.0.1
      node_modules
        a@0.0.1

到了npm v3版本,和 yarn 一样使用了扁平的依赖方式最终 b 和 a 的包都会被打平到一个 node_modules 中,为什么要打平提出来呢,这样如果 c包再依赖了 b@0.0.1 就不会重复安装多个b包了。这样也节省空间了。 但是举个例子:

  1. 如果C包引用了B包的1.0.0的版本,D包引用了B包的1.0.5的版本,那么项目是安装了几个B包的版本呢,并且在使用的时候分别使用的哪个版本运行呢。

假设项目依赖关系如下:

项目 ├── C (依赖 B@1.0.0) 
    └── D (依赖 B@1.0.5)

npm/Yarn (扁平化策略)安装后如下

node_modules 
├── B (版本 1.0.5) 
├── C 
│   └── node_modules 
│   └── B (版本 1.0.0)
└── D
  1. npm/Yarn 会尝试将依赖"提升"到顶层
  2. 由于版本冲突,只能将一个版本放在顶层

这样空间虽然减少了但是又会存在一个幽灵依赖的问题,比如:我项目里只依赖了C包和D包,但是我可以直接 require('B') 因为b包再扁平化的 node_modules 中是可以正常访问的。

针对上面的问题,pnpm做了一些优化 pnpm使用严格且正确的依赖解析

pnpm (严格隔离策略)

安装结构:

node_modules
├── .pnpm
│   ├── b@1.0.0
│   │   └── node_modules
│   │       └── b -> <全局store中的B@1.0.0>
│   └── b@1.0.5
│       └── node_modules
│           └── b -> <全局store中的B@1.0.5>
├── c -> ./.pnpm/c@版本/node_modules/c
└── d -> ./.pnpm/d@版本/node_modules/d

安装逻辑:

  1. pnpm 维护一个全局 store,存储所有包的所有版本
  2. 每个依赖都严格使用声明的版本
  3. 通过硬链接和符号链接引用全局 store 中的包

结果: 全局 store 中会有 两个版本 的 B 包,项目通过链接引用它们

因为使用文件硬连接的方式,把所有的依赖都安装到了全局的store中,各个包都连接到store中,如果有重复的包,就不会被重复下载,只需要连接去过就可以,在我们现代项目多git的仓库中非常实用

    1. 安装速度快
    • 是因为如果store中已经有了 类似 loadsh 这样的包后,下次安装依赖的时候就只是link一下,当然快
    1. 占用空间小
    • 是因为所有git仓库都共用全局的store,都是通过link的方式连接去过,所以相同的 loadsh 包的相同版本只会安装一次
    1. 可以避免幽灵依赖
    • 看上面的目录结构不全是打平的node_modules, 而是每个包有自己依赖的版本,都放到了 .pnpm 下,这样如果B包没有被依赖的话,直接 require 是 访问不到了的