什么是 pnpm

pnpm,英文里面的意思叫做 performant npm,意味“高性能的 npm”,官网地址 https://pnpm.io/

Pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的优势在于:

pnpm 的特性

Store

安装的依赖包文件,会通过硬链接(hard links)的形式存储在 pnpm 创建的 store 目录下,默认情况下全局只会有一个 store 目录 .pnpm-store,所有的项目都共用这一目录下的硬链接

node_modules

创建非扁平化的 node_modules 文件夹

Pasted image 20260320150542.png

npm/yarn 如今的安装的 node_modules 都是拍平的形式,而 pnpm 默认是不拍平的,这能解决“幽灵依赖”,如果想要继续使用拍平的方式,也支持配置 .npmrchoist = true

workspace 支持

对于 monorepo 类型的项目,pnpm 提供了 workspace 来支持,而且比 yarn workspace 配置更简单,只需在项目根目录下创建一个 pnpm-workspace.yaml 文件。具体可以参考官网文档

痛点解决

节约磁盘空间,提升安装速度

装过的包都通过 hard links 存储在磁盘的 .pnpm-store 文件夹内,所有项目共用,相同的包直接复用

Pasted image 20260320150810.png

不采用拍平的方式,绝不会下载重复的包

当更新一个包时,如果原版本有 100 个文件,新版本只新增了 1 个文件,pnpm 不会重新下载 101 个文件,而只是下载新增的 1 个文件

NPM doppelgangers

即 npm 包分身的问题

< npm3

node_modules 采用循环链式依赖的方式进行安装,由此产生的问题:依赖链路过长,大量重复安装的依赖

将 npm 切换到 npm3 之前,新建一个项目进行测试:

配置该项目依赖两个包:fs-extrajsonfile

Pasted image 20260320151459.png

执行 npm install 产生的 node_modules

Pasted image 20260320151517.png

可以看到由于 fs-extrajsonfile 都依赖了 graceful-fsuniversalify,所以分别在两个依赖包下分别安装了一遍,这就是链式的循环依赖

实际开发中项目依赖的包会很多,这样链式嵌套的情况会导致链路十分冗长,且重复安装的包特别多

>= npm3 / yarn

为了解决以上问题,npm3+ 和 yarn 采用了拍平 node_modules 的方式进行安装:拍平后依赖链路不再嵌套,不会安装重复的包

下面是上一个项目,使用 npm3+ 安装的 node_modules

Pasted image 20260320151721.png

但因此产生的新问题 ——“幽灵依赖”

随着 monorepo 的趋势,yarn workspace 成为一种流行的实现方式,而这种和 npm3+ 相同的 node_modules 安装方式出现了新的问题:npm 包分身

同样的,新建一个基于 yarn workspace 的 monorepo 项目进行测试:

创建 4 个工作区,分别让 package1package2 依赖 fs-extra@9package3package4 依赖 fs-extra@10

在项目根目录执行 yarn 安装的 node_modules 如下:

Pasted image 20260320151914.png

pnpm

解决幽灵依赖,绝不会重复安装相同的包

每个 workspace 下都有自己的 node_modules,里面只能访问到在 package.json 里显示声明的 fs-extra,而 fs-extra 软链接到全局 node_modules 下对应版本的 fs-extra,没有任何一个包会安装多次

Pasted image 20260320152104.png

Phantom dependencies

Phantom dependencies 幽灵依赖,解释起来很简单,即某个包并没有在 package.json 中声明依赖,却能在项目中导入使用

例如:

这种幽灵依赖的做法在当前的开发环境下已是常见现象,而使用 pnpm,node_modules 不再拍平,将无法再使用幽灵依赖

使用指南

安装 pnpm

npm install pnpm -g

初始化 pnpm 项目

新建一个文件夹作为项目目录(生成 package.json),在目录下执行指令(和 npm init 一致)

pnpm init

安装依赖

# 安装 package.json 所有依赖
pnpm install
# 安装单独包
pnpm add <package_name>

Workspace 配置

在项目下创建一个工作区的文件夹,例如 packages

在根目录创建一个 workspace 配置文件 pnpm-workspace.yaml,写入:

packages:
  # 所有在 packages 子目录下的 package
  - 'packages/**'
  # 不包括在 test 文件夹下的 package
  - '!**/test/**'

Monorepo 常用指令

安装依赖:

# 根项目安装(Monorepo 全局)
pnpm add <package_name> -W
# 某个子项目安装
pnpm add <package_name> --filter <workspace_name>

运行脚本:

pnpm start --filter <workspace_name>

删除全局和每个 workspace 的 node_modules

pnpm -r exec rm -rf node_modules

更多使用方式查看官网

总结

pnpm 作为包管理器里面的“后起之秀”,通过作者别出心裁的设计方案,完美解决了许多了现有的包管理工具 npm、yarn 以及 node_modules 本身设计原因留下的痛点

参考