Skip to main content

包管理工具

Yarn

Yarn 是在 2016 由 Facebook、Google、Exponent 以及 Tilde 团队共同开发推出的。

Yarn 的出现主要是为了解决当时 npm 在速度、安全性以及一致性方面的一些问题:

  • 安装速度:Yarn 支持并行下载,加入缓存机制。
  • 确定性:Yarn 引入了 yarn.lock 锁文件,确保依赖的一致性。
  • 安全性:增加包校验,保证安全性。
  • 离线安装:可基于缓存进行离线安装。
npmyarn说明
npm inityarn init初始化项目
npm install/linkyarn install/link默认的安装依赖操作
npm install <package>yarn add <package>安装某个依赖
npm uninstall <package>yarn remove <package>移除某个依赖
npm install <package> --save-devyarn add <package> --dev安装开发依赖
npm update <package> --saveyarn upgrade <package>更新某个依赖
npm install <package> --globalyarn global add <package>全局安装
npm publish/login/logoutyarn 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-install

在 pnpm 中,直接依赖使用硬链接,而间接依赖使用符号链接。

note

硬链接指多个文件名指向同一个物理文件数据块。这意味着,无论通过哪个硬链接访问文件,看到的内容都是相同的。删除一个硬链接不会影响其他硬链接,只有当所有硬链接都被删除后,文件数据才会真正从硬盘中移除。

符号链接是一个特殊的文件,包含了指向另一个文件或目录的路径。它类似于快捷方式,访问符号链接时,操作系统会将其重定向到实际文件或目录。符号链接本身占用少量空间,但它指向的文件或目录仍然占据实际存储空间。

假设两个项目 ProjectA 和 ProjectB,它们都依赖同一个库 libraryX:

// 安装依赖
cd ProjectA
npm install libraryX

cd ../ProjectB
npm install libraryX

// 结果:
// ProjectA/node_modules/libraryX -> 这是一个完整的libraryX包
// ProjectB/node_modules/libraryX -> 这是另一个完整的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

note

包隔离是指在项目中,每个依赖包都有自己独立的安装环境,这样可以避免不同依赖之间的冲突。当不同的依赖包需要相同的子依赖但不同版本时,如果没有良好的隔离机制,就可能导致依赖版本冲突,进而导致项目运行错误或行为异常。

包提升是指将依赖关系中某些包提升到更高的目录层次,以减少冗余,节省磁盘空间。

假设有一个项目 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 默认采用的就是包隔离策略,自然不存在上面的问题。