Hello Monorepo(上)https://developer.aliyun.com/article/1411557
分布式任务执行
分布式任务执行是指将一个命令分布到多台机器上执行的能力,同时在很大程度上保持在单台机器上运行时的开发人员体验。
工具 | 是否支持 |
Bazel | ✅ Bazel 的实现是最复杂的一种,可以扩展到包含数十亿行代码的存储库,设置起来也很困难。 |
Gradle Build Tool | ⚠️ Gradle Enterprise可以分布式执行测试任务。 |
Lage | ❌ |
Lerna | ✅ Lerna v6 可以连接到 Nx Cloud 以实现分布式任务执行。 |
Nx | ✅ Nx 的实现并不像 Bazel 那样复杂,但可以通过小的配置更改来打开它。 |
Pants | ✅ Pant 的实现与 Bazel 类似,并使用相同的远程执行 API。 |
Rush | ⚠️ Rush 通过选择性地与 Microsoft 的 BuildXL 加速器集成来提供此功能。 |
Turborepo | ❌ |
透明远程执行
透明的远程执行功能允许开发人员在本地开发环境中执行命令,并将这些命令自动透明地分发到多台远程机器上进行执行。开发人员可以像在单台机器上运行命令一样,在本地环境中编写和执行命令,而无需手动切换到远程机器或修改代码。
工具 | 是否支持 |
Bazel | ✅ 这是 Bazel 和其他工具最大的区别 |
Gradle Build Tool | ❌ |
Lage | ❌ |
Lerna | ❌ |
Nx | ❌ |
Pants | ✅ Pant 的实现与 Bazel 类似,并使用相同的远程执行 API。 |
Rush | ❌ |
Turborepo | ❌ |
检测受影响的项目/包
在进行构建或测试操作时,通过检测变更的内容,确定可能受到变更影响的项目或包,从而只针对受影响的项目进行构建或测试。
工具 | 是否支持 |
Bazel | ⚠️ Bazel没有内置支持,但是第三方工具(如target-determinator)使用Bazel的查询语言提供了这个功能。 |
Gradle Build Tool | ✅ Gradle Build Tool 本身提供增量构建和最新检测。 |
Lage | ✅ |
Lerna | ✅ |
Nx | ✅ 支持,它的实现不仅查看更改了哪些文件,还查看更改的性质。 |
Pants | ✅ |
Rush | ✅ 用于项目选择的命令行参数可以检测哪些项目受到 Git diff 的影响。 Rush 还为自动化场景提供了 PackageChangeAnalyzer API。 |
Turborepo | ✅ |
工作区分析
工作区是指包含多个项目或代码库的根目录。工作区分析通过解析工作区中的项目、文件和依赖关系,生成一个项目图,展示了项目之间的相互关系和依赖。这个项目图可以帮助开发人员更好地理解整个工作区的结构和组织,并能够快速定位和处理相关的项目。
工具 | 是否支持 |
Bazel | ⚠️ Bazel 允许开发人员编写 BUILD 文件。 一些公司构建了分析工作区源并生成 BUILD 文件的工具。 |
Gradle Build Tool | ✅ 可以使用build.gradle(Groovy脚本)或build.gradle.kts(Kotlin脚本)来编写构建任务。 |
Lage | ✅ |
Lerna | ✅ |
Nx | ✅ 默认情况下,Nx 分析 package.json、JavaScript 和 TypeScript 文件。 它是可插拔的,可以扩展以支持其他平台(例如 Go、Java、Rust)。 |
Pants | ✅ 这是Pants的最大差异化点。它使用静态分析来自动推断出支持的所有语言和框架的文件级依赖关系。 |
Rush | ✅ Rush项目和单仓库项目具有相同的package.json 文件和构建脚本。通过创建"rig packages",可以在整个单体存储库(monorepo)之间共享工具和配置。 |
Turborepo | ✅ Turborepo 分析 package.json 文件。 |
依赖图可视化
依赖关系图可视化工具可以将项目和/或任务之间的依赖关系以图形化的方式呈现出来。这个可视化过程是交互式的,也就是说可以对图中的节点进行搜索、过滤、隐藏、聚焦或突出显示,以及进行查询操作。
工具 | 是否支持 |
Bazel | ✅ Bazel 的实现支持自定义查询语言来过滤掉不感兴趣的节点。 |
Gradle Build Tool | ⚠️ Gradle Build Scan 提供丰富的依赖关系信息,并且第三方工具可用于项目/任务图。 |
Lage | ⚠️ Lage 没有附带可视化工具,可以编写自己的可视化工具。 |
Lerna | ⚠️ Lerna 没有附带可视化工具,可以编写自己的可视化工具。 |
Nx | ✅ Nx 配备了交互式可视化工具,可用于过滤和探索大型工作区。 |
Pants | ⚠️ Pants并没有自带可视化工具,但它可以生成一个包含代码库详细图结构的JSON文件,该文件可以被处理成可用于可视化工具的输入数据。 |
Rush | ⚠️ Rush 没有附带可视化工具,可以编写自己的可视化工具。 |
Turborepo | ✅ Turborepo 使用 Graphviz 生成执行计划的静态图像和 HTML 文件。 该实现不是交互式的。 |
代码共享
代码共享是指通过一些方式来方便地分享代码库中独立的代码片段。
工具 | 是否支持 |
Bazel | ✅ 可以将任何文件夹标记为一个项目,并通过Bazel的构建规则来实现该项目的共享。 |
Gradle Build Tool | ✅ 可以发布可共享的构件并从多个仓库中引用依赖项。 |
Lage | ✅ 支持,只有npm包可以共享。 |
Lerna | ✅ 支持,只有npm包可以共享。 |
Nx | ✅ 支持,任何文件夹都可以标记为一个项目,并且可以进行共享。Nx插件帮助配置WebPack、Rollup、TypeScript和其他工具,以实现共享而不影响开发人员的工作环境。 |
Pants | ✅ 支持打包、发布和使用代码构件,使用底层语言和框架的标准惯用法。 |
Rush | ✅ 支持,不鼓励从未声明为npm依赖的文件夹导入代码。这确保了项目可以在单体仓库之间轻松移动。对于创建包是过于繁琐的情况,"packlets"提供了一个轻量级的替代方案。 |
Turborepo | ✅ 支持,只有npm包可以共享。 |
一致的工具
这样的工具可以帮助开发人员在不同的技术栈之间切换时更加轻松和无缝。不管是使用JavaScript框架,还是使用Go、Rust、Java等其他技术,开发人员都可以获得相同的工具支持和开发体验。这种一致性的工具能够简化开发流程,减少学习成本,并提高团队的协作效率。
例如,该工具可以分析package.json
和JS/TS文件,以确定JavaScript项目的依赖关系以及构建和测试方法。但是,对于Rust,它会分析Cargo.toml
文件,对于Java,它会分析Gradle
文件。因此,该工具需要具备插件化的能力。
工具 | 是否支持 |
Bazel | ✅ Bazel的构建规则就像是不同技术和框架的插件。 |
Gradle Build Tool | ✅ 通过插件生态系统可以扩展其功能,例如使用CMake进行本地构建或使用Webpack进行打包。 |
Lage | ❌ 只能运行 npm 脚本。 |
Lerna | ❌ 只能运行 npm 脚本。 |
Nx | ✅ Nx是可插拔的。它默认能够调用npm脚本,但也可以扩展以调用其他工具(例如Gradle)。 |
Pants | ✅ Pants具有强大的插件API,可以提供统一的用户体验(UX),适用于不同的语言和框架。它内置了多个插件,包括Python、Java、Scala、Go、Shell和Docker等,同时还有更多的插件正在开发中。还可以使用相同的API编写自定义的构建规则。 |
Rush | ❌ Rush只针对TypeScript/JavaScript项目进行构建,并推荐一种解耦的方法,即使用本地工具链或BuildXL单独构建原生组件。理想情况下,Node.js是单体库开发者所需的唯一先决条件。 |
Turborepo | ❌ Turborepo只能运行npm脚本,但不必安装Node.js。 |
代码生成
原生支持生成代码。
工具 | 是否支持 |
Bazel | ⚠️ 可以使用外部代码生成器。 |
Gradle Build Tool | ⚠️ 可以使用外部代码生成器。 |
Lage | ⚠️ 可以使用外部代码生成器。 |
Lerna | ⚠️ 可以使用外部代码生成器。 |
Nx | ✅ Nx具有强大的代码生成能力。它使用虚拟文件系统,并提供编辑器集成。Nx插件提供了流行框架的生成器,还可以使用其他生成器。 |
Pants | ✅ Pants内置了适用于流行的代码生成框架的插件,包括Protobuf/gRPC、Thrift、Scrooge、Avro和SOAP。它还提供了插件API支持,以便轻松地添加新的代码生成器。Pants支持从单个codegen源生成多种语言的代码。它能够通过对codegen源的静态分析来推断依赖关系,并在这些源发生变化时正确地使生成的代码失效。 |
Rush | ⚠️ Rush的维护者建议将项目模板作为单体库中的普通项目进行维护,以确保它们能够在编译时没有错误。通过一个社区插件,可以使用项目脚手架命令来生成项目模板。 |
Turborepo | ⚠️ 可以使用外部代码生成器。 |
项目约束和可见性
Rush支持定义规则来约束仓库内的依赖关系。例如,开发人员可以将某些项目标记为私有项目,只有他们团队内的人才能依赖这些项目。开发人员还可以根据使用的技术(例如React或Nest.js)标记项目,并确保后端项目不导入前端项目。
工具 | 是否支持 |
Bazel | ✅ Bazel支持可见性规则,这有助于将私有内容与公共内容、可共享的内容等进行分隔。 |
Gradle Build Tool | ⚠️ 本身不原生支持这样的规则,但其丰富的插件功能允许开发人员开发类似的规则。 |
Lage | ⚠️ 可以使用一组自定义规则和额外配置的代码检查工具(linter)来确保满足某些约束条件。 |
Lerna | ⚠️ 可以使用一组自定义规则和额外配置的代码检查工具(linter)来确保满足某些约束条件。 |
Nx | ✅ 开发人员可以根据自己的需求对项目进行任意方式的注释,并建立不变量,而Nx将确保这些注释的有效性。它允许开发人员注释哪些是私有的、哪些是公开的、哪些是实验性的、哪些是稳定的等等。Nx还允许为每个包定义公共API,这样其他开发人员就无法深层导入到这些包中。 |
Pants | ⚠️ 虽然原生支持尚未实现,但可以编写自定义插件来强制执行此类规则。 |
Rush | ✅ Rush可以根据项目类型选择性地要求在引入新的NPM依赖(内部或外部)时进行审批。它还支持针对NPM发布的版本策略。 |
Turborepo | ⚠️ 可以使用一组自定义规则和额外配置的代码检查工具(linter)来确保满足某些约束条件。 |
小结
综上,Monorepo 是一种代码管理策略,它将一个项目的所有代码存储在一个单独的版本控制库中。Monorepo解决了多个问题:解决了代码共享与重复的困扰,提供更好的可追踪性和一致性。通过使用Monorepo,开发团队可以更好地组织、共享和管理代码,提高开发效率和协作质量。选择合适的Monorepo工具并灵活运用,能够帮助开发者更好地应对复杂的软件开发需求,推动项目的成功完成。