之前一直接触到 pnpm,但没有应用到业务项目,最近的业务项目安装和编译时间太长了,所以尝试性的换成 pnpm.
pnpm 官网介绍了 pnpm 的优点:
- 中心化存储,避免同一个文件的重复存储,节省磁盘空间和安装时间。
- 创建非扁平的 node_modules,解决幽灵包的问题。
pnpm 优点 1:中心化存储
传统工具(npm/yarn)的局限性:
- 重复安装问题:同一个项目安装一个包的不同版本时,会复制多份;在不同项目中安装相同依赖时,会完整复制整个依赖包到项目目录。
- 安装速度慢:网络传输和磁盘写入时间随依赖数量线性增长(上面的问题导致的这个问题)
同一项目内多版本依赖:
假设在一个项目中,有两个包:
package-a
依赖dayjs@1.11.0
package-b
依赖dayjs@1.10.0
npm 和 yarn 的存储结构如下:
shell
# 项目结构
project/
├── node_modules/
│ ├── package-a/
│ │ └── node_modules/
│ │ └── dayjs@1.11.0/
│ ├── package-b/
│ │ └── node_modules/
│ │ └── dayjs@1.10.0/
│ └── dayjs@1.11.0/ #(比较新的版本提取到根目录)
存储空间如下:

pnpm 在同一项目内多版本依赖:
pnpm 会存储在全局存储中,而不是项目中,所以不会占用项目空间。
同样,dayjs@1.11.0 和 dayjs@1.10.0 的文件也会存储在全局存储中,但是 pnpm 会对每个文件进行哈希计算,如果文件内容没有变化,则不会重复存储,只会存储差异部分。
也就是如果升级包的版本,但只有 1 个文件修改了,那么只会存储这个文件。而在项目中,pnpm 会通过硬链接的方式,将全局存储中的文件链接到项目中,所以相同文件不会占用额外磁盘空间。
bash
# 全局存储
~/.pnpm-store/v10/files/
├── 00/abc123... # dayjs@1.11.0 的 package.json
├── 00/def456... # dayjs@1.11.0 的 index.js
├── 00/ghi789... # dayjs@1.10.0 的 package.json
└── 00/jkl012... # dayjs@1.10.0 的 index.js
# 项目结构
project/
├── node_modules/
│ ├── package-a -> .pnpm/package-a@1.0.0/node_modules/package-a
│ └── package-b -> .pnpm/package-b@1.0.0/node_modules/package-b
└── .pnpm/
├── package-a@1.0.0/
│ └── node_modules/
│ ├── package-a/
│ └── dayjs -> ../../dayjs@1.11.0/node_modules/dayjs
├── package-b@1.0.0/
│ └── node_modules/
│ ├── package-b/
│ └── dayjs -> ../../dayjs@1.10.0/node_modules/dayjs
├── dayjs@1.11.0/
│ └── node_modules/
│ └── dayjs/
│ ├── package.json -> ~/.pnpm-store/v10/files/00/abc123...
│ └── index.js -> ~/.pnpm-store/v10/files/00/def456...
└── dayjs@1.10.0/
└── node_modules/
└── dayjs/
├── package.json -> ~/.pnpm-store/v10/files/00/ghi789...
└── index.js -> ~/.pnpm-store/v10/files/00/jkl012...
存储空间如下:

不同项目使用同一个依赖:
假设有两个项目都依赖了 同版本的 dayjs,那么 pnpm 会存储在全局存储中只存储一份,然后通过硬链接的方式,将全局存储中的文件链接到项目中,所以相同文件不会占用额外磁盘空间。
也就是同一个项目可以共享不同版本的依赖,而不会占用额外磁盘空间。不同项目可以共享同一个依赖,而不会占用额外磁盘空间。
因为 pnpm 本质就是基于内容寻址存储,只要文件相同,就不会重复存储,不管是同一个项目还是不同项目。
pnpm 优点 2:创建非扁平的 node_modules
幽灵包问题:
假设在一个项目中,有个包,其依赖如下:
-
package-a
依赖dayjs@1.11.0
-
npm 和 yarn 中,在项目里可以通过
import dayjs from 'dayjs'
来使用 dayjs。 -
pnpm 中,在项目里不可以 通过
import dayjs from 'dayjs'
来使用 dayjs。
因为在 npm 和 yarn 中,依赖是扁平的,所以 dayjs 被存储到了 node_modules 的根目录。而 pnpm 中,依赖是非扁平的,所以 dayjs 被存储到了 package-a 的 node_modules 的根目录中。
pnpm 的原理:
- 内容寻址存储:依赖文件通过哈希值存储在全局唯一位置,每次安装都只会存储差异部分,也就是如果升级包的版本,但只有 1 个文件修改了,那么只会存储这个文件。
- 硬链接技术:项目中的依赖文件通过硬链接指向全局存储,所以相同文件不会占用额外磁盘空间。
- 跨项目共享依赖:相同版本的依赖文件可以被多个项目共享,避免了重复安装。
已有项目 yarn 换成 pnpm
已有项目 yarn 换成 pnpm,需要以下步骤:
- 安装 pnpm
bash
npm install -g pnpm
- 删除 node_modules 和 重命名 yarn.lock 为 yarn.lock.bak
bash
rm -rf node_modules
mv yarn.lock yarn.lock.bak
- 使用 pnpm 安装依赖 ,并生成 pnpm-lock.yaml
bash
pnpm install
- 部署脚本更新
如果项目有部署脚本,需要更新部署脚本,将 yarn 换成 pnpm。
bash
nvm use 18
pnpm config set registry https://registry.npmmirror.com/
pnpm config set @df:registry http://mir.test.df.cn/npm/
pnpm config set store-dir ~/.pnpm-store
pnpm config set cache-dir ~/.pnpm-cache
pnpm config set prefer-offline true
pnpm install
pnpm run build
可能遇到的问题
之前的 yarn、npm 安装,可能有幽灵包的情况,如果启动项目,遇到模块找不到的情况,可以尝试以下步骤:
bash
mv yarn.lock.bak yarn.lock
yarn list xx包
看下版本是啥,比如
shell
$ yarn list ahooks
yarn list v1.22.19
warning Filtering by arguments is deprecated. Please use the pattern option instead.
├─ @xdf/b-table@1.0.4
│ └─ ahooks@2.10.14
├─ @xdf/channel-selector-v2@3.3.1
│ └─ ahooks@2.10.14
├─ @xdf/cus-pro-table@1.5.15
│ └─ ahooks@2.10.14
└─ form-render@2.5.2
└─ ahooks@3.8.4
✨ Done in 1.01s.
可以看到 ahooks 的版本是 2.10.14,那么可以安装这个版本,顺手把 yarn.lock 还原回去
bash
pnpm install ahooks@2.10.14; mv yarn.lock yarn.lock.bak;
如果项目有 test 功能,运行 test 功能,没有的话,可能需要自己点点页面,看看是否正常。当然最好是借此机会,写写测试用例,覆盖下代码,(* ̄︶ ̄)