node 模块现状
在我们执行 npm install 时,npm 会做出如下操作:
- 向 registry 查询获取模块的地址
- 根据 package.json 中的配置确定需要安装的模块版本
- 下载对应的压缩包,存放在
~/.npm
目录 - 解压压缩包到当前项目的
node_modules
目录
而在 Node.js 中,我们提供给 require 方法的参数如果不是一个路径,也不是 node 的核心模块, node 将试图去当前目录的 node_modules 文件夹里搜索。如果当前目录的 node_modules 里没有找到, node 会继续试图在父目录的 node_modules 里搜索,这样递归下去直到根目录。
这些过程都需要进行大量的文件 I/O 操作,这无疑是非常低效的。为了解决这些问题,Facebook 提出了 Plug’n’Play(PnP) 方案。
PnP 原理
在 Yarn 中,当我们开启 PnP 后,Yarn 会生成一个 .png.js
文件来描述项目的依赖信息和所需模块的查找路径。同时,项目目录下不再需要一个 node_modules 目录,取而代之的是一个全局的缓存目录,项目所需依赖都可以从这个目录中获取。
使用方法
可以在项目目录下执行 yarn --pnp
,或直接在 package.json 中修改如下字段开启 PnP:1
2
3
4
5{
"installConfig": {
"pnp": true
}
}
如果你使用 CRA 来创建项目,也可以直接在命令中加入 --use-pnp
。
PnP 速度
按照官方的说法, Generating it makes up for more than 70% of the time needed to run yarn install with a hot cache.
在我的一个实际项目中,使用 npm i、yarn 和 PnP 安装依赖完成时间分别为 26.48s、19.71s 和 11.25s,提升极为可观。
扩展
pnpm 使用了 symlink 来记录模块路径,如下所示:
1 | -> - a symlink (or junction on Windows) |
pnpm 在维护了模块层级的同时大幅度提升了安装速度,但是由于它的实现,无法保证和 npm 行为一致。
tink 是 npm 官方提出的一种类似 PnP 的解决方案,和 .pnp.js
文件类似, tink 会在项目中生成一个 .package-map.json
文件用来记录各安装包内文件的 hash 值。目前 tink 依然处于测试阶段,在 npm 8 中我们将能尝试这一特性。