我们为什么以及如何从 Angular CLI 迁移到 Nx 我们为什么这么做 最终解决方案 我们取得的成果 结论

2025-06-10

我们为何以及如何从 Angular CLI 迁移到 Nx

我们为什么这么做

最终解决方案

我们取得的成就

结论

照片由Unsplash上的Luca Bravo拍摄

注意:由于保密协议,我们不会提及客户的名字。

我们去年完成了从 Angular CLI 到 Nx 的迁移,这是我们做过的最大的重构之一。这篇文章将介绍我们决定这么做的原因以及我们做了哪些工作。

我们的挑战

  • 代码共享:我们在各个应用程序之间共享代码。我们将大部分可复用代码作为应用程序的一部分,并不断在主应用程序中添加更多可复用代码。

  • 重构:正如之前提到的,我们已经开始了性能优化。在现有状态下重构代码库非常具有挑战性。确定需要修改代码的哪一部分,或者在哪里添加新功能,都极具挑战性。

  • 构建时间:我们的构建时间很长;每次发布 PR/MR 后,我们都要等待很长时间。构建时间越长,意味着在单个任务上投入的时间就越多,每个发布周期的交付变更就越少。

  • 添加新功能:在已经太大的应用程序中添加新功能是一项挑战。

  • 代码审查:由于单个应用程序包含所有代码库,因此很难添加代码所有者。

上述痛点让我们清楚地认识到 NxDevTools 是我们最好的选择,我们应该继续使用它。

我们为什么这么做

从 Angular CLI 迁移到 Nx 是一个重大的决定。在迁移到 Nx 之前,我们用 Angular CLI 创建了一个主应用项目,并在同一个工作区中开发了一些较小的独立应用。这就像一小段代码堆放在一个代码库里,因此迁移过程中我们面临诸多挑战,如果我们从未迁移到 Nx,挑战会更大。

当我加入团队时,我们决定解决应用程序中的性能问题,因此我们很快就进行了大量的代码重构。

Nx是什么

Nx 是一款用于管理单一代码库的 DevTools。使用单一代码库的优势在于,您可以在单个工作区内创建和管理多个应用程序,并维护/共享库。Nx
的功能远不止单一代码库。它允许您访问开发工具包 (devkit) 来编写生成器、构建器/执行器(自定义命令)。

Nx 还为您的构建提供了缓存功能,因此您无需在每次运行构建时都编译未更改的代码。如果您想在 CI 管道中获得缓存优势,Nx Cloud 是一款非常棒的产品。

开始前的担忧

在开始迁移之前,必须确定需要将哪些部分代码从 App 中移出并创建为库。

我们决定采取以下措施:

  • 我们不想把所有东西都破坏掉。在第一次迭代中,我们决定只移动一个名为 common/legacy 的大文件夹(该文件夹包含最可复用的代码库),并创建一个新的库。

  • 当我们将大型遗留文件夹移至另一个库时,又遇到了另一个问题。最终,迁移遗留代码的计划是正确的。问题是包大小增加了,而且呈指数级增长。我们无法继续这样做。

我们又回到了绘图板上,决定开会讨论。
我们当时有以下选择:

  • 我以前用过辅助入口点。我的建议是使用辅助入口点。

    • 这听起来是最好的主意,大多数情况下我都会选择这个选项。
    • 问题是我们有大量代码需要移至库中。
    • 如果我们选择这个选项,考虑到代码库很大,我们可能需要花费一年多的时间,因为我们有三个人团队,只有我一个人全职做这件事。
  • 考虑到解决方案一的复杂性,我们决定采用另一个解决方案

    • 我们决定使用通配符路径,tsconfig.base.json如下所示"@domain/common-legacy/*": ["libs/common/legacy/src/lib/*"]
    • 这是一个好主意,因为我们只进口我们需要的东西。
    • 但它也有挑战

关于解决方案

我们决定将整个迁移分为 3 个部分:

  • 移动常见/遗留问题并解决我们遇到的问题。
  • 第一步成功后移动其余代码。
  • 注意循环依赖。

作为初始解决方案的一部分的解决方案

  • 我们不需要创建辅助入口点,工作量更少。我们可以为每个component/module/service/入口点创建一个文件夹,然后将其用作
import { HomeModule } from '@domain-common-legacy/home.module'
Enter fullscreen mode Exit fullscreen mode
  • 我们不会将整个库作为软件包的一部分。我们只获取所需的代码。控制软件包的预算。并且,当我们迁移新代码时,我们需要正确配置路径。

  • 但这带来了一个问题:创建的库无法构建。不过,由于创建可构建的库并非迁移过程第一部分的一部分,我们决定继续推进。

  • 我们决定禁用循环依赖检查。

最终解决方案

一旦我们弄清楚了我们的初始解决方案是如何工作的,我们就决定检查代码库,识别我们拥有的所有功能并将它们拆分成库。

我们发现我们的大多数功能由三部分组成:

  • feature/common:该功能和其他功能中使用的通用组件/指令。
  • 核心:我们延迟加载功能,这样就不会导致应用程序臃肿。核心库由组件/服务/指令/模块组成,它们是延迟加载功能的一部分,不会与外部共享。
  • 状态:每个功能都有一个状态,我们使用 NgRx 来处理全局状态,使用 RxAngular 来处理本地状态,状态库保存功能的 NgRx 代码,有时与其他功能共享。

我们还决定将共享代码放在名为 core 的文件夹中,因此

  • 核心/指令
  • 核心/共享组件
  • 核心/状态
  • 核心/模型

更重要的是,这些库在组织内部的库和多个应用程序中使用。

创建库之后

正如我提到的,创建库只是整个迁移过程的一部分。在这次迁移过程中,我们利用主 bundle 解决了大量的状态管理 / NgRx 代码。

我们决定通过拆分它们并仅加载我们需要的状态作为主代码的一部分来并行处理这个问题。

我们从主捆绑包中的 2.9MB 左右开始,随着常青浏览器的构建,其大小减少到 2.30MB。

处理循环依赖

一旦我们完成了库的创建,我们最终就会拥有 180 多个库,而这些库都是从一个应用程序开始的。

现在该处理循环依赖问题了。一次性解决是不可能的。
所以我们决定从核心库入手,并发现造成循环依赖问题的庞大代码库其实是核心库的一部分,主要是接口/服务和状态。

尽管我们正在修复所犯的一个错误,但我们仍保持循环依赖检查禁用。

我们意识到可以启用对新代码的检查,并且通过在根 eslint 配置中添加以下内容来启用对整个代码库的检查,并针对所有存在循环依赖的库禁用该检查。这样,现在只有不存在循环依赖问题的新库才能被合并。

我们决定启用库的循环依赖检查,并不断修复它。

循环依赖修复要求我们创建更多的库,最终我们得到了 250 多个库。

构建库

正如我们前面提到的,该方法的问题之一是我们无法构建这些库。

我们的队友决定亲自处理此事,并最终编写了一个构建器来构建用这种方法创建的所有新库。

Matt 还编写了一个库生成器,以便我们使用相同的结构创建所有库,这样我们就不会将整个库作为捆绑包的一部分。

我们取得的成就

此次迁移后,我们

代码所有者:我们决定创建一个 CODEOWNERS 文件来划分代码审查的责任以及哪个组拥有代码的特定部分。

  • 自定义 eslint 规则:作为我们流程的一部分,我们对代码审查流程进行了一些检查;迁移到 Nx 使我们能够将所有这些检查转换为自定义 eslint 规则,从而为我们节省更多时间。

  • 易于重构代码:我们每周修复/添加大量代码,有了这些库,我们的生活变得更加轻松,因为现在很容易找出需要触及代码的哪一部分。

结论

迁移到 NX 的选择对我们来说非常有效,我们能够识别功能并将其迁移到库中,从而享受到 PR 规模小的优势。此外,我们还能识别未使用和重复的代码。

添加自定义规则和代码所有者对我们帮助很大。我们能够识别需要审查的代码。

请分享您从 Twitter 迁移到 Nx 的经验以及它如何帮助您。

您可以加入 Nx 社区 Slack:https://go.nrwl.io/join-slack

特别感谢Juri抽出宝贵时间审阅本文。Juri,爱你,也爱你的作品💙

向我的GitHub 赞助商致谢

链接:https://dev.to/this-is-angular/why-and-how-we-migrated-to-nx-from-angular-cli-5a61
PREV
您应该了解的 7 个开源项目 - Python 版 ✔️ pandas:强大的 Python 数据分析工具包 Apache Airflow Ultroi​​d - UserBot 部署文档教程 Zulip 概述
NEXT
Angular-eslint、ESLint 和 Nx 11 的终极迁移指南目录先决条件使用 angular-eslint 设置新的 Nx Angular 工作区使用 ESLint 迁移现有的 Nx 10 Angular 工作区使用 TSLint 迁移现有的 Nx 10 Angular 工作区结论