pnpm:快速、节省磁盘空间的包管理器
pnpm:快速、节省磁盘空间的包管理器
在现代 JavaScript 开发中,包管理器是不可或缺的工具。它们负责下载、安装、更新和管理项目所需的依赖项。长期以来,npm 和 Yarn 一直是主流的选择。然而,一个名为 pnpm 的后起之秀正逐渐崭露头角,它以其独特的依赖管理方式,提供了更快的安装速度和更节省磁盘空间的优势。本文将深入探讨 pnpm 的工作原理、优势、使用方法,以及与 npm 和 Yarn 的对比,帮助您全面了解这个强大的包管理器。
1. 传统包管理器的困境:node_modules 的“黑洞”
在深入了解 pnpm 之前,我们需要先了解传统包管理器(如 npm 和 Yarn)在依赖管理方面存在的问题。
1.1. 依赖的重复安装
假设您有多个项目,每个项目都依赖于同一个库,例如 lodash
。使用 npm 或 Yarn 时,每个项目都会在自己的 node_modules
文件夹中安装一份 lodash
的副本。这意味着,如果您有 10 个项目都依赖 lodash
,那么您的硬盘上就会有 10 份 lodash
的拷贝。这不仅浪费了大量的磁盘空间,也增加了安装时间。
1.2. 扁平化依赖的隐患(npm v3 之前)
在 npm v3 之前,npm 采用的是嵌套的依赖结构。这意味着如果 A 依赖 B,B 依赖 C,那么 node_modules
的结构会是这样的:
node_modules/
A/
node_modules/
B/
node_modules/
C/
这种嵌套结构在某些情况下会导致路径过长的问题,尤其是在 Windows 系统上。此外,如果多个依赖项依赖于同一个库的不同版本,可能会导致版本冲突和难以调试的问题。
1.3. 幽灵依赖(Phantom Dependencies)
npm v3 之后,为了解决嵌套依赖的问题,npm 采用了扁平化的依赖结构。它会将所有依赖项尽可能地提升到顶层的 node_modules
文件夹中。这虽然解决了路径过长的问题,但也引入了另一个问题:幽灵依赖。
幽灵依赖指的是,您的项目可以访问到那些并没有在 package.json
中明确声明的依赖项。这是因为这些依赖项可能是您声明的某个依赖项的子依赖,而被提升到了顶层的 node_modules
文件夹中。
幽灵依赖的问题在于,它破坏了依赖关系的明确性。如果您的项目在不知不觉中依赖了某个幽灵依赖,而未来某个时候这个幽灵依赖被移除(例如,因为您升级了某个依赖项,而这个依赖项不再依赖这个幽灵依赖),您的项目可能会崩溃。
2. pnpm 的核心理念:内容寻址存储和硬链接
pnpm 采用了一种完全不同的依赖管理方式,解决了传统包管理器存在的诸多问题。它的核心理念是:
-
内容寻址存储(Content-addressable Store):
- pnpm 不会将每个依赖项的副本都安装到项目的
node_modules
文件夹中。相反,它会在全局维护一个存储库(通常位于用户主目录下的.pnpm-store
文件夹中)。 - 当您安装一个依赖项时,pnpm 会根据该依赖项的内容计算出一个唯一的哈希值。
- 如果这个哈希值对应的依赖项已经存在于全局存储库中,pnpm 就不会再次下载。
- 如果不存在,pnpm 会下载该依赖项,并将其存储到全局存储库中。
- pnpm 不会将每个依赖项的副本都安装到项目的
-
硬链接(Hard Links)和符号链接(Symbolic Links):
- 在项目的
node_modules
文件夹中,pnpm 不会直接复制依赖项的文件。 - 相反,它会创建硬链接或符号链接指向全局存储库中的实际文件。
- 硬链接:指向文件系统中同一个 inode(索引节点)的多个文件路径。它们共享相同的文件内容,但可以有不同的文件名。删除其中一个硬链接不会影响其他硬链接或原始文件。
- 符号链接(软链接):类似于 Windows 中的快捷方式或 macOS 中的别名。它是一个特殊的文件,其中包含指向另一个文件或目录的路径。
- 在项目的
2.1. pnpm 的依赖安装过程
下面通过一个例子来详细说明 pnpm 的依赖安装过程:
- 假设您有一个项目,需要安装
lodash
。 - pnpm 首先会检查
package.json
文件,确定需要安装lodash
的哪个版本。 - 然后,pnpm 会根据
lodash
的内容计算出一个哈希值。 - pnpm 会检查全局存储库(
.pnpm-store
)中是否已经存在这个哈希值对应的lodash
版本。- 如果存在,pnpm 会直接进行下一步。
- 如果不存在,pnpm 会从 npm 仓库下载
lodash
,并将其存储到全局存储库中。
- 接下来,pnpm 会在项目的
node_modules
文件夹中创建一个名为.pnpm
的隐藏文件夹。 - 在
.pnpm
文件夹中,pnpm 会创建一个以lodash
版本号和哈希值命名的文件夹(例如,[email protected]_somehash
)。 - 在这个文件夹中,pnpm 会创建硬链接或符号链接,指向全局存储库中
lodash
的实际文件。 - 最后,pnpm 会在项目的
node_modules
文件夹中创建一个名为lodash
的符号链接,指向.pnpm
文件夹中的lodash
文件夹。
通过这种方式,pnpm 实现了以下效果:
- 节省磁盘空间:由于所有项目都共享全局存储库中的依赖项,因此避免了重复安装,大大节省了磁盘空间。
- 加速安装:由于不需要重复下载已经存在的依赖项,因此安装速度更快。
- 避免幽灵依赖:由于
node_modules
文件夹中只有明确声明的依赖项的符号链接,因此避免了幽灵依赖的问题。 - 严格性:由于所有依赖都在.pnpm文件夹下以规范化方式存储,不存在依赖提升,所以一个包无法访问到未声明的依赖。
2.2 硬链接 vs 符号链接
- 硬链接:
- 优点:更节省空间,因为它们不占用额外的 inode。即使原始文件被删除,硬链接仍然有效。
- 缺点:不能跨文件系统使用。
- 符号链接:
- 优点:可以跨文件系统使用。
- 缺点:占用额外的 inode。如果原始文件被删除,符号链接会失效。
pnpm 默认使用硬链接,但在某些情况下(例如,跨文件系统或不支持硬链接的环境)会回退到符号链接。
3. pnpm 的优势
总结一下,pnpm 相比于 npm 和 Yarn,具有以下显著优势:
- 极大地节省磁盘空间:通过内容寻址存储和硬链接/符号链接,避免了依赖项的重复安装。
- 显著加速安装:无需重复下载已存在的依赖项,大大减少了安装时间。
- 避免幽灵依赖:
node_modules
结构更清晰,只包含明确声明的依赖项。 - 更严格的依赖管理:通过
.pnpm
文件夹和规范化的存储方式,确保依赖关系的明确性和一致性。 - 内置对 monorepo 的支持:pnpm 提供了一套完整的命令和工作区(workspaces)功能,可以轻松管理多包项目。
- 兼容性:pnpm 的命令和用法与 npm 和 Yarn 非常相似,迁移成本低。
4. pnpm 的使用
pnpm 的安装和使用非常简单。
4.1. 安装 pnpm
您可以通过多种方式安装 pnpm:
-
使用 npm 安装:
bash
npm install -g pnpm -
使用 Corepack(Node.js 16.13+):
bash
corepack enable
Corepack 是 Node.js 内置的包管理器管理器,它可以自动为您安装和管理 pnpm、Yarn 和 npm。 -
独立脚本(适用于不安装 Node.js 的情况):
- 在 Linux 或 macOS 上:
bash
curl -fsSL https://get.pnpm.io/install.sh | sh - - 在 Windows 上(PowerShell):
powershell
iwr https://get.pnpm.io/install.ps1 -useb | iex
- 在 Linux 或 macOS 上:
4.2. 常用命令
pnpm 的命令与 npm 和 Yarn 非常相似,以下是一些常用命令的对比:
操作 | npm | Yarn | pnpm |
---|---|---|---|
安装所有依赖 | npm install |
yarn install |
pnpm install |
安装单个依赖 | npm install <package> |
yarn add <package> |
pnpm add <package> |
安装开发依赖 | npm install -D <package> |
yarn add -D <package> |
pnpm add -D <package> |
全局安装 | npm install -g <package> |
yarn global add <package> |
pnpm add -g <package> |
删除依赖 | npm uninstall <package> |
yarn remove <package> |
pnpm remove <package> |
更新依赖 | npm update |
yarn upgrade |
pnpm update |
运行脚本 | npm run <script> |
yarn <script> |
pnpm <script> |
列出已安装的依赖 | npm ls |
yarn list |
pnpm ls |
发布包 | npm publish |
yarn publish |
pnpm publish |
4.3. pnpm 的配置文件
pnpm 的配置文件是 .npmrc
,与 npm 相同。您可以在 .npmrc
文件中配置 pnpm 的行为,例如:
store-dir
:指定全局存储库的路径。link-workspace-packages
:控制是否在工作区中链接本地包。shamefully-hoist
: 默认值为false
。设置为true
时,会创建扁平的node_modules
结构,类似 npm 或 yarn classic.
4.4 工作区 (workspace)
pnpm 内置了对 monorepo 的支持, 通过工作区功能, 你可以在一个代码仓库中管理多个项目。
要设置工作区,您需要在仓库的根目录中创建 pnpm-workspace.yaml
文件,指定包含项目的目录:
yaml
packages:
- 'packages/*' # 所有在 packages 目录下的子目录
- 'components/*' # 也可以指定多个目录
- '!**/test/**' # 排除某些目录
在工作区中, 您可以使用 pnpm -r
或 --recursive
选项在所有项目中执行命令,例如:
bash
pnpm install -r # 在所有项目中安装依赖
pnpm run build -r # 在所有项目中运行 build 脚本
您还可以使用过滤器来选择特定的项目执行命令,例如:
bash
pnpm --filter my-package run build # 只在 my-package 项目中运行 build 脚本
pnpm --filter ./packages/* run test # 在 packages 目录下的所有项目中运行 test 脚本
pnpm也支持--filter ...
这种形式的过滤器, 比如:
pnpm --filter ...@foo/bar run build # 会选择@foo/bar以及@foo/bar依赖的所有包, 和依赖@foo/bar的所有包
5. 与 npm 和 Yarn 的对比
特性 | pnpm | npm | Yarn (Classic) | Yarn (Berry) |
---|---|---|---|---|
依赖管理方式 | 内容寻址存储 + 硬链接/符号链接 | 扁平化 + 嵌套(部分) | 扁平化 + 嵌套(部分) | Plug'n'Play (PnP) |
磁盘空间占用 | 非常节省 | 较大 | 较大 | 较小(但仍有 node_modules ) |
安装速度 | 非常快 | 较慢 | 较快 | 快 |
幽灵依赖 | 无 | 有 | 有 | 无 |
依赖关系严格性 | 非常严格 | 较宽松 | 较宽松 | 严格 |
Monorepo 支持 | 内置 | 需要 Lerna 等第三方工具 | 内置工作区 | 内置工作区 |
配置文件 | .npmrc |
.npmrc |
.yarnrc |
.yarnrc.yml |
兼容性 | 与 npm 和 Yarn 命令高度兼容 | - | 与 npm 命令高度兼容 | 与 npm 命令部分兼容,需要 yarn node 运行脚本 |
从上表可以看出,pnpm 在磁盘空间占用、安装速度、依赖关系严格性和 Monorepo 支持方面具有明显优势。
6. 总结
pnpm 是一款现代化的包管理器,它通过内容寻址存储和硬链接/符号链接的创新方式,解决了传统包管理器在依赖管理方面的诸多问题。它不仅可以显著节省磁盘空间,还能大幅提升安装速度,并避免幽灵依赖的风险。pnpm 的命令和用法与 npm 和 Yarn 高度兼容,迁移成本低,同时内置了对 Monorepo 的支持。如果您正在寻找一款更快速、更高效、更可靠的包管理器,pnpm 绝对值得一试。
希望这篇文章能帮助您全面了解 pnpm,并将其应用到您的项目中。