前端工程化 - Monorepo
1 views
Monorepo 是什么
Monorepo是一种项目管理方式,在Monorepo之前,代码仓库管理方式是 MultiRepo,即每个项目都对应着一个单独的代码仓库每个项目进行分散管理 这就会导致许多弊端,例如可能每个项目的基建以及工具库都是差不多的,基础代码的重复复用问题等等…
发展历程
Monolith时期
单一代码仓库:传统的单体式应用程序通常将所有的功能和模块打包在一起,形成一个单一的代码库和部署单元。这种单一的代码库包含了应用程序的所有部分,从前端界面到后端逻辑,甚至包括数据库模式和配置文件等。🚧
存在问题:
- 难以实现部分更新和独立扩展的灵活性
- 高度耦合,代码臃肿
MultiRepo时期
多代码仓库:将不同的功能模块、组件或服务等分别存放在独立的仓库中,可以单独进行版本控制、构建、部署和发布,使得不同的团队或开发者可以独立地开发、测试和维护各自的模块,更容易实现并行开发和团队协作。😆
存在问题:
- 跨仓库开发:多仓维护成本高
- 开发调试:npm包(修改->发布->安装成本高),调试麻烦
- 版本管理:依赖版本同步升级管理麻烦
- 项目基建:脚手架升级,新老项目规范很难保证统一
MonoRepo时期
随着业务复杂度的提升,模块仓库越来越多,MultiRepo这种方式虽然从业务上解耦了,但增加了项目工程管理的难度,随着模块仓库达到一定数量级,会有几个问题:跨仓库代码难共享;分散在单仓库的模块依赖管理复杂;增加了构建耗时。于是将多个项目集成到一个仓库下,共享工程配置,同时又快捷地共享模块代码,成为趋势🎯
Monorepo 的优势:
1. 本地计算缓存
能够存储和重放文件并处理任务的输出。在同一台机器上,您永远不会重复构建或测试相同的内容。

2. 本地任务编排
能够以正确的顺序并行运行任务。所有列出的工具都可以以大致相同的方式执行此操作,但 Lerna 除外,因为它的限制较多。

3. 分布式计算缓存
能够跨不同环境共享缓存工件。这意味着您的整个组织(包括 CI 代理)将永远不会重复构建或测试相同的东西。

4. 分布式任务执行
能够在多台机器上分发命令,同时在很大程度上保留在单台机器上运行该命令的开发人体工程学。

5. 透明的远程执行
在本地开发时能够在多台机器上执行任何命令。
6. 检测受影响的项目/包
确定更改可能产生的影响,以便仅运行受影响的构建/测试项目。

7. 工作空间分析
无需额外配置即可了解工作区的项目图。

8 . 依赖图可视化
可视化项目和/或任务之间的依赖关系。可视化是交互式的,这意味着您可以搜索、过滤、隐藏、聚焦/突出显示和查询图中的节点。
9. 源代码共享
促进离散源代码片段的共享。

10. 一致的工具
无论您使用什么来开发项目,该工具都能帮助您获得一致的体验:不同的 JavaScript 框架、Go、Rust、Java 等。
换句话说,该工具以相同的方式对待不同的技术。
例如,该工具可以分析 package.json 和 JS/TS 文件,找出 JS 项目依赖项,以及如何构建和测试它们。但它会分析 Cargo.toml 文件以对 Rust 执行相同操作,或分析 Gradle 文件以对 Java 执行相同操作。这要求该工具具有可插入性。

11. 代码生成
原生支持生成代码

12.项目约束和可见性
支持定义规则以约束存储库内的依赖关系。例如,开发人员可以将某些项目标记为团队私有,这样其他人就无法依赖它们。开发人员还可以根据所使用的技术(例如 React 或 Nest.js)标记项目,并确保后端项目不会导入前端项目。

总结:
它不仅仅是代码和工具。 ==Monorepo 改变你的组织== 以及您对代码的看法。通过增加一致性、降低创建新项目和执行大规模重构时的摩擦、通过促进代码共享和跨团队协作,它将使您的组织能够更高效地工作。
下面是一个常见

Monorepo 存在的一些问题
幽灵依赖
在 Monorepo 中,幽灵依赖(Phantom Dependency) 是指项目 A 无意中依赖了项目 B 的某些文件或依赖项,而这些文件或依赖项并没有在项目 A 的 package.json(或其他依赖管理文件)中明确声明。这种依赖关系通常是因为项目 B 的依赖项存在于同一 Monorepo 中,所以项目 A 在本地开发时能够正常工作,但这种依赖实际上并不安全,因为它在实际的构建或部署中可能会导致问题。😤
基于 npm/yarn 的 Monorepo 方案,依然存在 “幽灵依赖” 问题,我们可以通过 pnpm 彻底解决这个问题
依赖安装耗时长
MonoRepo 中每个项目都有自己的 package.json 依赖列表,随着 MonoRepo 中依赖总数的增长,每次 install 时,耗时会较长。😭
相同版本依赖提升到 Monorepo 根目录下,减少冗余依赖安装;使用 pnpm 按需安装及依赖缓存。
为什么 pnpm 可以解决这些问题
pnpm 是一种现代的包管理工具,与 npm 和 Yarn 类似,但在处理依赖的方式上有一些独特的特性,能够帮助避免 幽灵依赖 问题。以下是 pnpm 如何避免这个问题的具体原因:
1. 严格的模块隔离
- pnpm 使用了一种独特的方式来管理
node_modules。它会将所有安装的包链接到一个统一的存储位置(称为“内容可寻址存储”),而不是将包直接复制到每个项目的node_modules目录中。 - 在每个项目的
node_modules目录中,pnpm 会创建一个符号链接结构,确保每个包只能访问它明确声明的依赖。这意味着,如果项目 A 没有声明对某个包的依赖,那么它的node_modules目录中就不会包含这个包。因此,项目 A 无法“无意”地使用项目 B 的依赖,从而避免了幽灵依赖问题。
2. 严格的依赖树解析
- pnpm 强制要求每个依赖项都必须明确声明,这使得在解析依赖树时,pnpm 会严格按照
package.json中声明的依赖进行安装。如果某个依赖项未被声明但在代码中被使用,pnpm 会在运行时立即报错。 - 这种严格的解析方式确保了每个项目只能访问其显式声明的依赖项,避免了意外依赖未声明包的情况。
3. 一致性和重复性
- pnpm 通过其符号链接和内容寻址机制,保证了每次安装的依赖结构是一致的。即使在不同的开发环境中,使用同一套依赖声明文件 (
package.json和pnpm-lock.yaml),项目的依赖树也会被精确地重现。 - 这种一致性意味着开发者在本地和 CI/CD 环境中看到的依赖结构完全一致,从而避免了在不同环境下由于幽灵依赖而导致的潜在问题。
4. 独立工作区(Workspaces)支持
- pnpm 的工作区功能可以有效管理 Monorepo 中的多个项目。每个项目在工作区内都能保持其依赖的独立性,确保项目之间的依赖关系是显式且可控的。
- 当一个工作区中的项目引用另一个项目时,pnpm 会在
node_modules中为这个依赖项目创建符号链接,而不是直接访问其node_modules。这使得依赖关系更加清晰和透明,进一步降低了幽灵依赖的风险。
总结
pnpm 通过严格的依赖管理、符号链接的模块隔离机制,以及对依赖树的精准解析,有效地避免了 Monorepo 中的幽灵依赖问题。pnpm 确保每个项目只能访问自己明确声明的依赖,减少了在多项目环境下出现意外依赖的可能性,从而提高了代码的可维护性和可靠性。