浅识PNPM

134 阅读5分钟

目前pnpm使用的团队已经不在少数,目前github 已有20k+,已经很稳定了。

1.简介

pnpm大致来讲就是一个包管理工具,与npm、yarn的功能是一样。

但是他与npm、yarn不同的地方是

1)包安装速度更快

2)磁盘空间利用率更高

3)使用过程中出现bug率更低,更加安全。

2.速度快

以react包安装为例

pnpm明显是优于npm以及yarn的,但是yarn是有PnP模式的,直接去掉 node_modules,将依赖包内容写在磁盘,节省了 node 文件 I/O 的开销,这样也能提升安装速度。再来对比一下PnP模式如下图。

可以看到pnpm还是优于yarn PnP模式的。

3.依赖管理

npm以及yarn install的原理是类似的,首先会构建一个依赖树,然后经历下面四个步骤

- 1. 将依赖包的版本区间解析为某个具体的版本号

- 2. 下载对应版本依赖的 tar 包到本地离线镜像

- 3. 将依赖从离线镜像解压到本地缓存

- 4. 将依赖从缓存拷贝到当前目录的 node_modules 目录

然后,对应的包就会到达项目的node_modules当中。

在npm1、npm2中呈现的是嵌套结构,比如:

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

如果bar中有其他依赖的话,就会一直嵌套下去,这样设计的话会有很多问题

比如依赖层级太深,会导致文件路径过长。

大量重复的包被安装,文件的体积超级大。比如foo以及bar都依赖了time组件,那么time就会分别在foo以及bar的node_modules出现,会占用很多存储资源。

模块实例不能共享。比如 time 有一些内部变量,在两个不同包引入的 time 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。

所以从npm3开始包括yarn都是着手通过扁平化依赖的方式来解决这个问题,扁平化依赖是在安装新的包的时候,根据node require规则,不停的往上级node_modules中去寻找,如果找到版本相同的包就不会重新安装了,解决了大量重复包的问题,而且层级依赖不会太深。虽然解决了上面的问题,但是同时也出现了新的问题。

  1. 依赖结构的不确定性
  2. 扁平化算法本身的复杂性很高,耗时较长。
  3. 项目中仍然可以非法访问没有声明过依赖的包

不确定性拿foo和bar两个包来举例子:

npm/yarn install扁平化处理后是这样的

还是这样的呢

是都有可能的,哪种结构取决于package.json的位置,如果foo在前则是前面的结构,否则为后。

为了解决这种 不确定 的问题,就有了lock文件的诞生,package-lock/yarn.lock,主要是为了保证install之后产生的确定的node_modules结构。

下面介绍一下pnpm的依赖管理

pnpm的出现正是要解决上面的问题。

  1. pnpm 内部使用基于内容寻址的文件系统来存储磁盘上所有的文件,这个文件系统出色的地方在于: 不会重复安装同一个包。用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink。
  2. 即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink,仅仅写入那一个新增的文件。

我们执行安装express

pnpm install express

可以看到

并没有发现node_modules,同时外层的node_modules文件夹中也只出现了express,并没有出现扁平化的其他express依赖的包,那去哪里了呢,是在.pnpm这个文件中

▾ node_modules
  ▾ .pnpm
    ▸ accepts@1.3.7array-flatten@1.1.1
    ...
    ▾ express@4.17.1
      ▾ node_modules
        ▸ accepts
        ▸ array-flatten
        ▸ body-parser
        ▸ content-disposition
        ...
        ▸ etag
        ▾ express
          ▸ lib
            History.md
            index.js
            LICENSE
            package.json
            Readme.md

pnpm的目录结构为@version/node_modules/这种目录结构。并且 express 的依赖都在.pnpm/express@4.17.1/node_modules下面,这些依赖也全都是软链接。

包本身依赖放在同一个node_module下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙。

现在我们回过头来看,根目录下的 node_modules 下面不再是眼花缭乱的依赖,而是跟 package.json 声明的依赖基本保持一致。即使 pnpm 内部会有一些包会设置依赖提升,会被提升到根目录 node_modules 当中,但整体上,根目录的node_modules比以前还是清晰和规范了许多。

安全上也是解决了非法访问依赖的问题,只要未在package.json中生命的依赖是没办法进行访问的。

最主要的是从npm迁移未pnpm,没有任何的学习成本以及使用成本,用法基本都是一样的。后续我再研究深入了解一下pnpm原理,可以项目后面用pnpm。大家用起来吧😊