pnpm是什么
pnpm是一个高性能的包管理工具。我们可以理解为performant npm(高性能的npm)的缩写;
可以看到官网给的定义,pnpm是一个包管理工具。
核心优势
- 节省磁盘空间
- 提高安装速度
- 创建一个非扁平的 node_modules 目录
monorepo是什么
Monorepo (monolithic repository,单一代码仓库)指的是: 把多个项目/包放在同一个仓库里而不是每一个包一个独立的仓库
my-project/
├── packages/
│ ├── ui/ # UI 组件库
│ ├── utils/ # 工具库
│ └── app/ # 业务应用
├── package.json
└── tsconfig.json
这些包既能独立发布,又能共享同一个仓库的配置。
monorepo的核心特点
- 单仓库多包: 所有包在同一个repo下的不同目录。
- 统一依赖管理: 依赖通安装在根目录,子包通过workspace互相引用。
- 包间相互引用: 本地直接import, 调试开发更高效。
对比 Polyrepo(每个项目单独一个仓库) ,Monorepo 有这些优势:
✅ 依赖一致性
- 一个地方定义 React 版本,所有包都用同样的版本,不容易冲突。
✅ 开发效率高
- 改了
utils
包,app
可以立刻用,无需发布再安装。
✅ 统一规范和工具链
- 代码风格、测试、CI/CD 流程都能共享配置。
✅ 更容易重构和大规模协作
- 所有包代码都在同一个仓库,跨包修改时更直观。
这里我们组件库
+icon库
+多个业务项目
是非常契合这种架构的。
monorepo+pnpm使用
pnpm
内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持。 一个workspace
必须在根目录有一个,可能有一个.npmrc
.
工作协议workspace
link-workspace-packages
设置为true 时,则 pnpm 将在可用包与声明的范围匹配时链接工作区中的包。比如workspace 中A
项目依赖@B1.0.0
包,并且@B1.0.0
在workspace 中时,则A
会链接到@B1.0.0
包,但是如果依赖@B2.0.0
包,而工作区中没有,则会从远程仓库进行安装,如何远程仓库也没有,则会报错,这种行为会带来一定的不确定性。这时候可以使用pnpm支持的workspace:
协议,如果使用workspace:
协议,pnpm 将仅链接来自工作区的包。
使用
假设你在 workspace 中有一个名为 foo
的包, 通常,你会将其引用为 "foo":"workspace:*"
。
如果你想使用不同的别名,以下语法也将起作用: "bar": "workspace:foo@*"
。
在发布之前,别名被转换为常规名称。 上述示例将变成:"bar": "npm:foo@1.0.0"
。 如果我们在工作空间中有 foo
, bar
, qar
, zoo
,它们都是版本1.5.0
,如下所示 :
json
{
"dependencies": {
"foo": "workspace:*",
"bar": "workspace:~",
"qar": "workspace:^",
"zoo": "workspace:^1.5.0"
}
}
打包发布时将转换为:
json
{
"dependencies": {
"foo": "1.5.0",
"bar": "~1.5.0",
"qar": "^1.5.0",
"zoo": "^1.5.0"
}
}
pnpm原理
要了解pnpm的原理,我们必须先知道硬连接
和和软连接
。
硬连接和软连接的概念
- 硬连接: 是计算机文件系统中的多个文件平等地共享同一个文件存储单元。 如果其中一个文件被修改,其他硬链接文件也会同步被修改。
- 软连接: 是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。如果原文件被删除,软链接就失效了。其本质是保存某一个文件的路径。
pnpm安装
要了解其关系,我们先来看下执行pnpm install
的时候都发生了什么?
- 跟
npm
和yarn
类似,读取package.json
和package-lock.yaml
,从npm registry
拉取最新匹配的版本并生成lock。最终得到一份完整的依赖树。 - 下载依赖到全局内容寻址区(store)或者复用,mac目前是在/pnpm/store目录下。
pnpm store path
进行查看,包的目录基于内容hash,所以相同的包只会存储一次,比如两个项目都依赖ant-design@1.0.0
,只会下载一次和存储一次。 - 连接到项目的
node_modules
, 下载完成之后,pnpm
不会复制文件,而是用硬链接
或者软链接
的形式生成到项目的node_modules
下。(项目node_modules
不是扁平化的,而是采用虚拟存储结构)。 - 更新lockfile。如果依赖版本有变化,
pnpm
会更新pnpm-lock.yaml
文件。 - 校验完整性(通过文件hash值),保证下载没有被篡改。
实操
下面实操一下,初始化一个pnpm-demo仓库 ,安装一个axios
依赖。我们可以看到,download数量为0 ,reused数量23
,那么这个就是在全局store中找到了对应依赖,通过硬连接的方式到当前项目中,所以速度会非常快。而且硬连接,和全局store中对应的包指向同一个存储地址,并不会copy一份开辟新的存储空间,所以也节省了很大的磁盘空间,而且项目越多,相比于节省的磁盘空间也越多。
node_modules
目录如下图所示。
axios
依赖右方箭头代表该文件是一个软连接,然后有一个.pnpm
目录。展开(下图)可以看到没有软链接箭头,代表是硬连接,里面除了axios
以外还有很多包,为axios
包的所有依赖和依赖的依赖...在.pnpm
目录中平铺展示。

再看下图npm
安装一个axios
是什么样的,和pnpm
项目下的.pnpm
目录类似。

为什么要将实际包都放在.pnpm文件下呢?
我们项目中引入三方包,比如import react from 'react' , 1. 判断是否是核心模块fs
,path
这种,不是的话找当前目录下的node_modules
,没找到的话向上层继续寻找,类似于作用域链 的概念。那么这时候我们找axios,就会在当前目录的node_modules
中查找,找到之后导入。这时候,我们如果导入一个axios
的依赖呢?npm
在就会在当前目录下找到并且导入,而pnpm
就会因为没有找到并且报错,这里pnpm
的行为才是符合我们预期的,因为我们的package.json
没有这个依赖。
pnpm是如何通过workspace:
协议实现monorepo的?
首先创建demo2
作为依赖包(比如组件库,Hooks库等)。
然后创建
demo
作为使用依赖包的项目。这里添加依赖包demo2
到dependencies
中,执行pnpm install
然后可以看到
demo
下的node_modules
下生成了一个软链接的demo2
依赖包。
我们再看一下lock文件,
demo
中的依赖包demo2
通过node_modules
软链接到了实际的demo2
目录中。 所以我们本地通过import 'demo2' 这种形式去安装并引入本地依赖包,实际上是通过查找到
noode_modules
中demo2
依赖包的软链接转到实际的demo2项目里面,从而实现了丝滑的monorepo
。
pnpm用法
基础用法
arduino
pnpm add pkg // 安装软件包及其依赖的任何软件包。 默认情况下,任何新软件包都被安装为生产依赖项。
pnpm install // 用于安装项目的所有依赖项。
pnpm update // 基于指定的范围更新包到它们的最新版本。
pnpm remove // 从 node_modules 和 package.json 中删除软件包。
pnpm link // 引用本地项目以外的包,本质就是通过软链接,原理和前面workspace类似,但是没workspace可靠
pnpm unlink // 取消链接一个系统范围的 package
pnpm run // 执行script脚本
进阶一点
csharp
pnpm store add pkg // 直接将包加在全局store
pnpm store path // 查看全局store的路径
pnpm store prune // 清理全局包未被应用的包,不建议频繁使用,可以在磁盘空间不够的时候的一个优化手段。
总结一下
pnpm
通过全局store存储依赖,同构软件链接➕硬连接的方式实现包的多个仓库复用,从而节省磁盘空间 ,提高安装效率。- 通过node_modules 中
.pnpm
存放实际的依赖包(硬链接),将项目直接安装的包通过软链接方式直接安装在node_modules
目录下,从而避免了幽灵依赖 的发生,提高项目的可靠性。 workspace:
协议通过软链接的方式将本地的依赖包安装到了项目中,从而实现了高效的modorepo