引言
在版本控制系统中,monorepo(“ mono ”表示“单一”,“repo”是“存储库”的缩写)是一种软件开发策略,其中许多项目的代码存储在同一个存储库中。 --维基百科
在当下大型前端项目中基于 monorepo 的解决方案已经深入人心,无论是比如 Google、Facebook,还是社区内部知名的开源项目 Babel、Vue-next 都使用了 monorepo 方案来管理他们的代码。
所谓 Turborepo 是一个用于 JavaScript/TypeScript monorepos 的快速构建系统。
Monorepo
所谓 Monorepo 类似于一个建筑物的概念,它可以让你管理多个项目时将不同的项目保存在一个储存库中。
在 Monorepo 项目中你可以同时管理多个逻辑共存的应用程序,比如桌面应用程序和 Web 应用程序,甚至 Ios 应用也可以保存在 Monorepo 中,只要你愿意的话。
通常在实现一些大型项目架构时,我们会尽可能的拆分一些大型功能独立到一些包之中通过在主应用程序之中通过入口点来加载它。
但是随之而来问题就产生了,如果按照传统方式来拆分这些包每个独立的应用包之间都应该存在一份自己的工作流配置。换而言之,每当我们希望独立出一个新包时需要重复创建配置环境以及配置新环境。
同样在构建应用时,如果某些包内部出现依赖我们不得不的在修改被依赖的包发布之前,依次构建相关依赖包版本并且重新发布。在存在多个项目依赖之间互相依赖时,这无疑是一种灾难。
为了解决上述的问题,Google 提出了 Monorepo 的理念,使用一个项目代码库来管理多个项目包的开发、构建与发布。
基于 Monorepo 的出现,我们可以更多的业务层面的分离而不需要分离时随之而来冗余繁琐的项目依赖问题。我们可以基于一个仓库下共享多个应用程序之间的配置和依赖。
同时我们可以利用业内成熟的工具来辅助管理我们关于多依赖包之间的版本发布。
Monorepo 的优势:
- 抽离多个重复配置文件: 将多个应用程序抽离到一个代码仓库中管理,无疑针对于繁琐且重复的配置文件与环境,我们可以仅仅贡献一份配置文件,然后利用该配置来构建所有的包。
- 轻松的拉取所有最新的代码: 此时由于 Monorepo 的解决方案,解决了多个远程仓库的问题。在拉去代码时,通常我们仅仅需要一个简单的拉取请求即会拉取到所有最新的所有代码内容。
- 更加简单的 NPM 发布: 上边我们谈到过,基于多个依赖包之间版本管理的问题。基于 Monorepo 的解决方案我们可以利用一些比如 Lerna、Yarn Workspaces 等工具更加自动化的处理依赖包之间的构建和发布。
- 更容易的依赖管理: 我们可以提升多个项目中相同的依赖在项目的根依赖中进行管理,这意味这这会大大的缩小项目依赖在硬盘上占据的空间。
- 更好的逻辑复用方案: 基于 Monorepo 的解决方案,我们在独立出不同应用之间逻辑的同时可以基于包之间可以更加清晰的在模块之间复用其他模块。
基于 Monorepo 的传统解决方案
Lerna
Lerna 是一个工具,可以优化使用 git 和 npm 管理多包存储库的工作流程。
Lerna 主流应用在处理版本、构建工作流以及发布包等方面。
你可以将 Lerna 管理的项目理解成为一个大的文件夹,其中每一个文件夹中都会包含一个独立的应用程序文件夹。
在独立的应用程序文件夹外,我们拥有一个大的文件夹来管理每个独立的文件夹,每当我们运行 Lerna 的命令进行构建、发布时,它内部会遍历所有的应用程序从而进行构建对应的包以及自动化的更新相关依赖版本。
Yarn Workspaces
Yarn 1.0 版本中,开发人员发布了一个名为 Workspaces 的功能主要用于基于 Monorepo 方案来管理多个应用程序之间的依赖处理。
通常业界主流基于 Lerna 负责发布和版本控制,而使用 Yarn Workspaces 来管理多个应用程序之间的依赖。
为什么选择 TurboRepo
上述提到传统的 Monorepo 解决方案中,项目构建时如果基于多个应用程序存在依赖构建,耗时是非常可怕的。
TurboRepo 的出现,正是解决 Monorepo 慢的问题。
你可以看到,TurboRepo 内部的核心代码是基于 Go 来实现的,这也就意味着相较于传统 Monorepo 解决方案在处理依赖构建时它拥有更好的性能以及更快的时间。
图片来源自 TurboRepo Documentation。
上图对比了 TurboRepo 与 Lerna 对于构建一个相同项目时的耗时。
比方说 Monorepo 存在三个依赖应用程序包,A、B、C。此时 A 和 C 包都依赖与 B 包。
基于 Lerna 你可以发现一次仅能执行一个任务,当构建时首先运行 lerna run link --parallel
时仅支持单个任务的运行。
而基于 TurboRepo 支持多个任务的并行处理,完美了的解决了 Lerna 构建时类似“单线程”的不足。
同时基于 TurboRepo 官方文档中提出以下优势:
更快的增量构建
TurboRepo 的基本原则是从不重新计算以前完成的工作, Turborepo 会记住你构建的内容并跳过已经计算过的内容,在多次构建开发时,这也就意味更少的构建耗时。
通过内容生成 Hash 甄别文件变动
Turborepo 检查文件内容变动时,会根据内容生成 Hash 来对比,而不是粗略的利用时间戳来确定需要构建的内容。
云缓存
通常针对于构建时产生的缓存文件大部分时都会记录在本地硬盘中,在多人合作或者 Docker 构建中这也就意味着仍然需要首次巨大的耗时构建生成缓存才会提升效率。
但 TurboRepo 开发团队提供了一项名为“云缓存”的功能,它支持将本地 turborepo 链接到远程缓存从而实现多人合作时共享缓存。
任务管道
Turborepo 支持在 package.json 中通过 pipeline 定义任务之间的关系,它会让 Turborepo 在构建内容上智能化的分析模块构建串/并执行顺序,从而大大的缩小构建时间。而不是类似于上文提到 Lerna 中仅机器化的支持单个任务的运行。
基于约定的配置
Turborepo 通过约定降低复杂性,使用 Turborepo 我们仅仅关心简单的 json 配置即可完成项目配置。
浏览器中的配置文件
Turbo 支持通过 有--profile
标志 生成构建配置文件,你可以将它并将其导入 Chrome 或 Edge 以了解哪些任务花费的时间最长。
Feature
Turbo 官方指出支持使用 Lerna 管理包、发布和更改日志生成,同时使用 Turbo 进行任务运行和缓存。
对于版本控制与发布,可能 Turborepo 目前并没有 Lerna 那么耀眼,但是就目前来说基于 Turborepo 的 Monorepo 解决方案在我个人看来已经是非常优秀的方案。
你可以观看这个视频查看 Jared Palmer 对于 Turborepo 的介绍。
同时我个人也处于积极探索 TurboRepo 的过程,逐渐尝试使用 TurboRepo 落地项目实践。
期待 Turborepo 在2022大放异彩,为 Monorepo 提供更多优秀的解决方案。
结尾
我相信 Turborepo 的出现在不久的将来一定会成为 Monorepo 工具链中重要的一环,无论是构建缓存功能还是基于 pipeline 的智能任务调度系统,都非常优秀的解决了传统 Monorepo 存在“慢”的问题。
同时 Turborepo 内部基于 Go 的实现,在笔者看来也标志着 JavaScript 工具即将迎来多元化挑战。
为了更好的性能,大部分人将不再局限于使用 JavaScript 开发 JavaScript 工具,而是更愿意选择其他高门槛语言。