什么才是构建生产级 React Monorepo 的最佳工具?
我认为需要满足三个目标或者说解决三个问题:
- 快速构建
- 代码共享
- 依赖关系
先说一个事实,在今天,大家会比以往任何时候都迫切地需要一个支持 React 应用系统开发的 monorepo 架构。
因为复杂的大型系统越来越多。当团队构建多个不同的应用、数十个“微前端”以及数百个组件时,传统的 multirepo 架构根本无法应对它们。
代码共享等问题必须要很好地解决,因为不同的应用之间需要使用相同的组件或某个功能库,甚至是彼此相互依赖。
工具、技术和版本的标准化成为一个严重的问题,系统中依赖项的管理同样是个大问题。甚至我们构建应用和进行日常开发任务的方式也需要重新整理。
开源社区为我们提供了一些非常棒的工具来解决这一系列的问题。
合理利用这些工具,我们可以在一个工作空间中轻松构建和管理任意数量的应用程序、库和组件,并通过设计实现无限代码共享。
从 Bit 这种下一代构建工具,到超级智能的 NX,我们有很多非常有趣的构建工具可以去探索。
在本文中,我总结了我对 React 项目使用 Monorepo 架构的 5 个最优选择,以及一个你永远不应该使用的、过时的工具。
Bit
Bit 是用于构建 React 应用和组件的下一代 monorepo 工具。
它非常强大,解决了 monorepo 中一系列关键问题:
- 工作区和组件级别的配置
- 通过可组合性实现了更高级别的代码共享
- 代码、技术和开发任务的标准化
- 对组件/功能/体验的所有权
- 通过图的方式进行构建、测试和部署
- 在应用/组件级别时自动定义和管理依赖项
- 大规模项目仍然保持简单的开发体验
只需几个 CLI 命令,我们就可以使用 bit 创建包含 React 应用、组件库、包和超高性能构建和测试管道设置的整个 monorepo,这些管道几乎可以与任意的测试工具一起使用。
Bit 与其他 monorepo 工具的不同之处在于它的模块化和可组合性。
对于 Bit Apps,页面、功能、后端模块和 UI 元素都是“组件”。在 Bit 工作区中,我们可以通过简单而丰富的开发体验创建和组合组件、应用和项目。
是的,在 bit 中,应用程序也是工作区中的一个组件。
组件不会耦合到工作空间;我们可以从工作区动态创建、获取和导出组件,并且只需要在一个小而简单的代码库中处理我们需要的内容。
每个组件就像一个“迷你项目”,可以独立开发、版本控制、发布、测试等。
Bit 可以流畅地处理和自动化像包和依赖项之类的事情,这样来保证非常简单且可扩展的开发体验,即使对于非常庞大的 monorepo 项目也一样。
组件开发环境和模板等功能可帮助我们轻松定义和管理非常多的组件和应用的配置。
Bit 和 TypeScript 以及现代 Web 生态系统中的几乎所有技术都可以一起使用,比如 testing 和 linting;甚至可以和 React Native 一起使用。我们可以很轻松地扩展它并添加新的工具和工作流程。
Bit 提供了很多模板,我们可以直接使用模板来创建项目。
如何使用
首先需要全局安装它。
npx @teambit/bvm install
然后创建一个新的项目。
bit new react my-workspace
进入项目中,创建一个组件。
bit create react-app apps/my-react-app
运行这个组件。
bit run apps/my-react-app
项目就启动成功了。
更详细的教程参考它的文档:bit.dev/docs/gettin…
NX
NX 是一个强大的 React monorepo 构建工具,由 Nrwl 开源,这是一家专注于应用构建的公司。
它可以自动执行在之前开发者必须手动重复执行的任务,包括计算缓存、增量构建、自动化构建等功能,还包括与 Cypress 的插件集成。
和 Bit 类似,NX 同样提供了脚手架工具,帮我们创建好工作区,并且会完成对应的 monorepo 设置。
它的脚手架工具命令如下:
npx create-nx-workspace --preset=core
NX 可以创建存储库中所有项目之间的依赖关系图。我们直观地观察这个依赖关系图,这会有助于我们理解代码的架构。
和 Bit 不同的是,NX 的依赖关系图不会包含工作区中不同组件之间的关系。
作为构建工具,NX 最引以为傲的特性是以高效增量的方式来构建项目。它只会重建必要的内容,而且永远不会出现两次运行相同计算的情况,NX 减少项目的构建时间的能力是非常强大的。
和 Bit 一样,NX 有很多插件,还可以帮助我们开发 React 应用,并且还有对 Jest、Cypress、ESLint 等工具和库的集成支持。
此外,Nx 还支持 Next.js 等框架,并且支持 React Native。
lerna
Lerna 是一个非常老牌的 monorepo 工具。最初是由 BabelJS 的开发团队创建的,现在由 NX 团队维护。
和 Bit 或 NX 相比,它是一个相对简单的工具。lerna 用于管理包含多个包的存储库,我们可以分别对每个包进行版本控制和发布。它的主要目的之一就是为了发包的自动化。
我们可以将项目中所有子包始终保持在同一版本,这种情况下所有子包会共用项目根目录下的 lerna.json 文件中声明的版本。我们还可以选择将它们拆分版本控制,每个子包独享版本,并且在子包中分别发布。
Lerna 也在努力帮助我们减少项目的构建时间。Lerna 不会运行之前执行的任务,而是从缓存中恢复文件。计算缓存可以分布到多台机器上,这样可以进一步减少构建时间。
对于 React 而言,Lerna 可能有点简单。如果你想要开发多个应用和库、组件等等,在它们之间共享代码并将它们组合在一起,Lerna 可能并不适合这种工作流。因为 Lerna 是为了解决 “package=project” 的问题,并且由于它的单一特性,我们使用它时仍然需要进行大量配置。
Turborepo
前不久,Turborepo 已经被 Vercel 收购了。
Turborepo 的目的是为 JS 和 TS 的 monorepo 项目提供一个高性能的构建系统。它的内部实现了智能的高效计算和缓存的增量构建。
Turborepo 在某种程度上是 NX 和 Lerna 的结合体,它专注于 package,但在工作空间中提供了和 NX 类似的功能,例如增量构建、并行执行、远程缓存和任务管道。
这是 GitHub 上一个关于 Nx 和 Turborepo 基准性能测试的项目:
Rush
Rush 是微软开源的一个 monorepo 工具,它同样具有强大的增量构建、智能计算和超高效的构建能力。
在目标、开发体验、生态系统等方面,Rush 和 Nx 有些像。
可以看到如今在构建工具这方面,同质化越来越严重了。
不过 Rush 和 Nx 的理念有些不同,比如 Rush 更强调 npm 依赖的安全性和遵守 Node.js 中的约定,使用 package.json 的脚本来构建和使用 node.js 的包解析算法来解决相互依赖关系。Nx 更强调插件生态和代码生成的可扩展性,可以快速实现 linting、pretty、ci 等功能。
具体的取舍还要看个人和团队的喜好了。
Git Submodules
在所有的方案中,最不推荐的就是 Git Submodules。
不推荐 Git Submodules 的理由有很多,你可以随便去网上搜索,会有很多负面评价。
这里我简单聊一下我认为 Git 子模块的一些主要缺点。
被锁定到外部仓库的特定版本,缺乏有效的合并管理。
Git 仓库本身并不真正知道它现在是一个多模块还是单模块的仓库。
并且 Git Submodules 也不是为了处理组件之间的依赖关系而构建的。所以,围绕代码共享的工作流程会变得非常复杂。
子模块正在努力提供我们工作流所需要的功能。
在 mercurial 中,子存储库被视作“最后手段”来避免。
也就是说,除非其他所有的 monorepo 工具都失败了,否则不要使用它。