likes
comments
collection
share

前端应该知道的pnpm+monorepo知识点

作者站长头像
站长
· 阅读数 20

什么是pnpm?

pnpm是一款现代化的包管理工具,在github上已经有了接近25k的star,应该说pnpm是包管理工具的未来。

前端应该知道的pnpm+monorepo知识点

pnpm官网介绍说pnpm是一个快速的,节省磁盘空间的包管理工具,那么pnpm到底强在哪里呢?

pnpm强在哪里

1. 节省磁盘空间

使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:

  1. 如果在不同的项目中使用了同样的包,在使用npm/yarn时,这个包会被安装多次,也就是说磁盘中会多次写入这块相同的内容,占用空间。而pnpm会将所有的文件存储在磁盘上的某个位置,在包被安装时,包里的文件会通过hardlink(硬链接)到这个位置,也就是说,在不同的项目复用同一个包时,pnpm只会安装一次,并且多次复用。实现了跨项目对同一版本包的共享。
  2. 对于包的不同版本,pnpm会将不同版本间有差异的文件添加至磁盘仓库中,使用pnpm update时也只会更新差异文件,极大程度上复用了之前版本的代码,不会因为部分文件的差异改变整个包的内容。

因此,pnpm在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!

2. 更快的安装速度

pnpm 分三个阶段执行安装:

  1. 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。
  2. 目录结构计算。 node_modules 目录结构是根据依赖计算出来的。
  3. 链接依赖项。 所有以前安装过的依赖项都会直接从仓库中获取并链接到 node_modules

得益于优秀的包管理机制(见下文)和链接依赖,pnpm的安装速度远超npm/yarn。

前端应该知道的pnpm+monorepo知识点

3. 非扁平的 node_modules 目录

包是从全局 store 硬连接到虚拟 store 的,这里的虚拟 store 就是 node_modules/.pnpm。

我们打开 node_modules 看一下:

前端应该知道的pnpm+monorepo知识点

例如在项目中使用 pnpm 安装了一个叫做 express 的依赖,那么最后会在 node_modules 中形成这样两个目录结构:

node_modules/express/...
node_modules/.pnpm/express@4.17.1/node_modules/xxx

其中第一个路径是 nodejs 正常寻找路径会去找的一个目录,如果去查看这个目录下的内容,会发现里面连个 node_modules 文件都没有:

▾ express
    ▸ lib
      History.md
      index.js
      LICENSE
      package.json
      Readme.md

实际上这个文件只是个软链接,它会形成一个到第二个目录的一个软链接(类似于软件的快捷方式),这样 node 在找路径的时候,最终会找到 .pnpm 这个目录下的内容。

其中这个 .pnpm 是个虚拟磁盘目录,然后 express 这个依赖的一些依赖会被平铺到 .pnpm/express@4.17.1/node_modules/ 这个目录下面,这样保证了依赖能够 require 到,同时也不会形成很深的依赖层级。

也就是说,所有的依赖都是从全局 store 硬连接到了 node_modules/.pnpm 下,然后之间通过软链接来相互依赖。

pnpm较npm,yarn解决了什么问题

依赖嵌套

在npm1、npm2中,node_modules的目录结构是这样的

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

如果一个包依赖于另外一个包,会进行依赖嵌套,如果依赖包过多,会造成大量重复的包被安装。 更大的问题是在windows系统中的文件路径最长是 260 多个字符,这样嵌套是会超过 windows 路径的长度限制的。

于是在npm3和yarn都使用了扁平化依赖来解决这个问题。 可以看到我们安装了一个包后,node_modules多了很多依赖,这就是扁平化依赖的结果。

前端应该知道的pnpm+monorepo知识点

现在的依赖层级类似于这种

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

yarn将所有的依赖都拍平到node_modules里,这样在安装新的包时,只需要在node_modules中寻找即可,如果找到相同版本的包就直接复用,解决了包大量重复安装的问题。但是扁平化依赖带来了什么问题呢?最主要的问题是:幽灵依赖

幽灵依赖

什么是幽灵依赖? 举个例子:在项目中,我们可能会使用一个包A,A可能依赖B、C这两个包,在使用npm3/yarn安装A时,由于其采用扁平化依赖管理,A、B、C这三个包都会被安装在node_modules下,而事实上,我们项目的package.json是这样的

{
  "name": "tese",
  "version": "1.0.0",
  "main": "lib/index.js",
  "dependencies": {
    "A": "^1.0.0",
  }
}

也就是说package.json中只存在一个包,而NodeJS 的 require() 函数能够在依赖目录找到A、B、C三个包,因为 require() 在查找文件夹时 根本不会受 package.json 文件影响。

现在,你大概知道可能会发生什么事情了:在项目开发中,我们可能会直接使用B/C这些幽灵依赖,如果项目迭代中A不再被使用,使用幽灵依赖会因为无法找到依赖而报错。

而pnpm使用软硬链接的方式,解决了这个问题。(见上文)

什么是monorepo?

Monorepo 其实就是将多个项目 (pacakage 软件包)放到同一个仓库 (Repo) 中进行管理。这种代码组织形式可以更好地管理多 Package 项目。主要的优点有:

  • 可见性 (Visibility): 每个开发者都可以方便地查看多个包的代码,方便修改跨 Package 的 Bug。比如开发 Admin 的时候发现UI 有问题,随手就可以修改。

  • 更简单的包管理方式(Simpler dependency management): 由于共享依赖简单,因此所有模块都托管在同一个存储库中,因此都不需要私有包管理器。

  • 唯一依赖源(Single source of truth): 每个依赖只有一个版本,可以防止版本冲突,没有依赖地狱。

  • 原子提交: 方便大规模重构,开发者可以一次提交多个包(package)。

在一个monorepo项目中,代码结构大致是这样的

├── packages
|   ├── pkg1
|   |   ├── package.json
|   ├── pkg2
|   |   ├── package.json
├── package.json

在 packages 存放多个子项目,并且每个子项目都有自己的package.json

Monorepo相较于Multirepo的优缺点

场景MultiRepoMonoRepo
代码可见性✅ 代码隔离,研发者只需关注自己负责的仓库 ❌ 包管理按照各自owner划分,当出现问题时,需要到依赖包中进行判断并解决。✅ 一个仓库中多个相关项目,很容易看到整个代码库的变化趋势,更好的团队协作。 ❌ 增加了非owner改动代码的风险
依赖管理❌ 多个仓库都有自己的 node_modules,存在依赖重复安装情况,占用磁盘内存大。✅ 多项目代码都在一个仓库中,相同版本依赖提升到顶层只安装一次,节省磁盘内存,
代码权限✅ 各项目单独仓库,不会出现代码被误改的情况,单个项目出现问题不会影响其他项目。❌ 多个项目代码都在一个仓库中,没有项目粒度的权限管控,一个项目出问题,可能影响所有项目。
开发迭代✅ 仓库体积小,模块划分清晰,可维护性强。 ❌ 多仓库来回切换(编辑器及命令行),项目多的话效率很低。多仓库见存在依赖时,需要手动 npm link,操作繁琐。 ❌ 依赖管理不便,多个依赖可能在多个仓库中存在不同版本,重复安装,npm link 时不同项目的依赖会存在冲突。✅ 多个项目都在一个仓库中,可看到相关项目全貌,编码非常方便。 ✅ 代码复用高,方便进行代码重构。 ❌ 多项目在一个仓库中,代码体积多大几个 G,git clone时间较长。 ✅ 依赖调试方便,依赖包迭代场景下,借助工具自动 npm link,直接使用最新版本依赖,简化了操作流程。
工程配置❌ 各项目构建、打包、代码校验都各自维护,不一致时会导致代码差异或构建差异。✅ 多项目在一个仓库,工程配置一致,代码质量标准及风格也很容易一致。
构建部署❌ 多个项目间存在依赖,部署时需要手动到不同的仓库根据先后顺序去修改版本及进行部署,操作繁琐效率低。✅ 构建性 Monorepo 工具可以配置依赖项目的构建优先级,可以实现一次命令完成所有的部署。

使用pnpm构建monorepo的优点

monorepo由于自身特性,仓库里的模块需要处理与内部模块之间的关系,基于此,pnpm由于其优秀的依赖管理机制以及对workspace的支持可以说是轻量化构建monorepo的最好方式。

实战

我们使用pnpm Workspace来创建

  1. 首先,你需要pnpm这个工具,我们直接使用npm来安装
npm i pnpm -g
  1. 然后,在根目录重新初始化一个package.json。
pnpm init
  1. 初始化工作空间 在monorepo项目中,每个软件包会存放在工作空间,方便管理。

首先需要创建一个 pnpm-workspace.yaml,这个文件用于声明所有软件包全部存放在 packages 目录之中。其实目前 monorepo 风格的项目也普遍使用 packages 作为默认软件包目录位置。

packages:
  # all packages in direct subdirs of packages/
  - 'packages/*'

前端应该知道的pnpm+monorepo知识点

monorepo存在的缺点

上文我们谈到了那么多monorepo的优点,但是不可避免的,monorepo缺点也十分明显:

  1. 性能问题。在仓库规模足够大时,git操作会花费更长时间,安装成本也较高
  2. 权限问题。多人开发时权限管理较为困难,同时代码合并冲突问题会更复杂。 3.打包构建:随着项目数量的增加,构建和打包的时间可能会较长,影响部署速度,需要专门优化。

参考资料

转载自:https://juejin.cn/post/7267585231791341605
评论
请登录