包管理工具
Yarn
Yarn 是在 2016 由 Facebook、Google、Exponent 以及 Tilde 团队共同开发推出的。
Yarn 的出现主要是为了解决当时 npm 在速度、安全性以及一致性方面的一些问题:
- 安装速度:Yarn 支持并行下载,加入缓存机制。
- 确定性:Yarn 引入了
yarn.lock锁文件,确保依赖的一致性。 - 安全性:增加包校验,保证安全性。
- 离线安装:可基于缓存进行离线安装。
| npm | yarn | 说明 |
|---|---|---|
npm init | yarn init | 初始化项目 |
npm install/link | yarn install/link | 默认的安装依赖操作 |
npm install <package> | yarn add <package> | 安装某个依赖 |
npm uninstall <package> | yarn remove <package> | 移除某个依赖 |
npm install <package> --save-dev | yarn add <package> --dev | 安装开发依赖 |
npm update <package> --save | yarn upgrade <package> | 更新某个依赖 |
npm install <package> --global | yarn global add <package> | 全局安装 |
npm publish/login/logout | yarn publish/login/logout | 发布/登录/登出 |
npm run <script> | yarn run <script> | 执行 script 命令 |
Yarn 的出现让 npm 团队也感受到了压力,做出了一定的改变。例如从 npm v5 开始引入了名为 package-lock.json 的锁文件,类似于 Yarn 的 yarn.lock 文件。
pnpm
pnpm 的优势主要表现在:
- 节省磁盘空间。
- 解决幽灵依赖。
- 原生支持Monorepo。
节省磁盘空间
使用 pnpm 安装依赖时,pnpm 会将依赖文件存储在一个全局的内容地址存储(例如 ~/.pnpm-store )中,而不是在每个项目中都完整存储一份。接着,pnpm 会为项目中依赖创建硬链接,指向全局存储中的同一个物理文件,因此即使不同的项目有相同的依赖,这些依赖文件在磁盘上只存储了一次,因此节省了大量的磁盘空间。
在 pnpm 中,直接依赖使用硬链接,而间接依赖使用符号链接。
硬链接指多个文件名指向同一个物理文件数据块。这意味着,无论通过哪个硬链接访问文件,看到的内容都是相同的。删除一个硬链接不会影响其他硬链接,只有当所有硬链接都被删除后,文件数据才会真正从硬盘中移除。
符号链接是一个特殊的文件,包含了指向另一个文件或目录的路径。它类似于快捷方式,访问符号链接时,操作系统会将其重定向到实际文件或目录。符号链接本身占用少量空间,但它指向的文件或目录仍然占据实际存储空间。
假设两个项目 ProjectA 和 ProjectB,它们都依赖同一个库 libraryX:
- npm
- pnpm
// 安装依赖
cd ProjectA
npm install libraryX
cd ../ProjectB
npm install libraryX
// 结果:
// ProjectA/node_modules/libraryX -> 这是一个完整的libraryX包
// ProjectB/node_modules/libraryX -> 这是另一个完整的libraryX包
// 使用 pnpm 安装依赖
cd ProjectA
pnpm install libraryX
cd ../ProjectB
pnpm install libraryX
// 结果:
// ~/.pnpm-store/libraryX/ -> 这是libraryX的实际物理文件,存储在全局内容地址存储中
// ProjectA/node_modules/libraryX/ -> 这是指向全局存储的硬链接
// ProjectB/node_modules/libraryX/ -> 这是另一个指向全局存储的硬链接
pnpm 在处理间接依赖时,使用符号链接:
假设 libraryX 本身依赖 libraryY,而 libraryY 也存储在全局内容地址存储中。此时 pnpm 会在 libraryX 中创建一个符号链接,指向全局存储中的 libraryY,而不是将 libraryY 的文件直接复制到 libraryX 中,这进一步减少了文件的重复存储。
// ProjectA/node_modules/libraryX/node_modules/libraryY -> 这是一个符号链接,指向全局存储中的libraryY
如果不同的项目依赖 libraryX 的不同版本,那么会在全局仓库下分别存储每个版本的 libraryX,但仅存储不同版本之间不同的文件。
解决幽灵依赖
幽灵依赖是指当一个包(A)依赖于另一个包(B)时,后者会被放置 node_modules 目录中,这意味着一个包可能会被意外地访问,即使它没有在 package.json 文件中声明这些依赖。幽灵依赖可能产生难以理解的依赖关系,发生潜在的错误。
使用 pnpm 安装依赖,node_modules 目录包只会包含 package.json 中声明的直接依赖,避免了直接访问间接依赖的问题。
支持Monorepo
目前企业中搭建 Monorepo 项目方案:
- Lerna。
- Yarn + Workspace。
- pnpm + Workspace。
pnpm 原生支持Monorepo,无需额外的管理工具。
相关命令
pnpm 可通过 npm 或者 yarn 进行安装:
npm install -g pnpm
pnpm 命令与 npm 大多都类似:
- 创建新项目:
pnpm init。 - 添加依赖:
pnpm add <package>。 - 安装所有依赖:
pnpm install。 - 升级依赖:
pnpm update <package>。 - 删除依赖:
pnpm remove <package>。
包隔离与提升
pnpm 默认策略是包隔离,而 npm 的默认策略是包提升,Feature Comparison。
包隔离是指在项目中,每个依赖包都有自己独立的安装环境,这样可以避免不同依赖之间的冲突。当不同的依赖包需要相同的子依赖但不同版本时,如果没有良好的隔离机制,就可能导致依赖版本冲突,进而导致项目运行错误或行为异常。
包提升是指将依赖关系中某些包提升到更高的目录层次,以减少冗余,节省磁盘空间。
假设有一个项目 MyApp,该项目依赖两个包 PackageA 和 PackageB(这两个包是直接依赖),这两个包又有相同的间接依赖:
PackageA -> lodash@4.17.21
PackageB -> lodash@3.10.1
在没有包隔离的情况下,传统的包管理工具(例如 npm 早期版本)可能会尝试将 lodash 提升到项目的 node_modules 根目录。如果 lodash@4.17.21 被安装在根目录下,那么 PackageB 依赖的 lodash@3.10.1 就会被忽略,可能导致 PackageB 无法正常运行。而 pnpm 默认采用的就是包隔离策略,自然不存在上面的问题。