逐步淘汰 Legacy Angular:我们如何迁移到 Next.js、GraphQL 和 Monorepo(无需一次性大改)

发布: (2026年2月13日 GMT+8 08:26)
8 分钟阅读
原文: Dev.to

Source: Dev.to

Sunsetting Legacy Angular:我们如何迁移到 Next.js、GraphQL 和 Monorepo(无大幅重写)的封面图片

概览

如果你曾在一个长期维护的前端项目工作过,你一定了解其中的情况。应用不断壮大,功能堆积,截止日期接踵而至,突然之间你就坐拥一座技术债务的山。

这正是我们当时的处境。

我们拥有一个大型的 Angular 14 应用,600 多个组件,采用单体结构,复杂度日益提升,导致开发速度变慢。完整重写听起来很诱人,但也充满风险、成本高昂,并且会对业务造成干扰。

于是,我们没有选择一次性大改,而是设计了一套迁移策略,让我们能够 逐步替换遗留代码,使用的技术包括:

  • Next.js
  • GraphQL 联邦
  • Monorepo 架构
  • 作为框架桥梁的 Web 组件

本文将介绍该架构、迁移方法以及我们迄今为止学到的经验。

The Legacy Landscape: What We Started With

我们的 Angular 应用多年来一直是业务的支柱。它负责:

  • 客户和用户注册工作流
  • 服务提供商搜索
  • 支付处理
  • 基于角色的用户管理
  • 复杂的多步骤表单
  • AWS Cognito 身份验证

它运行良好,提供了价值。但它已经开始显露出老化的迹象。

传统系统的关键挑战

  • 单体架构 – 一个根模块拥有 637 个声明的组件,导致代码库难以理解。

  • 手动依赖注入 – 自定义 HTTP 服务在 60 多处 手动实例化,绕过了 Angular 的 DI。

  • 紧耦合 – 组件直接绑定到特定的 API 结构。

  • 可重用性受限 – UI 组件仅限于 Angular,无法在其他地方复用。

  • 构建缓慢 – 随着应用规模增长,构建时间不断增加。

  • 技术债务

    - Angular 14
    - Bootstrap 4
    - jQuery dependencies

该应用在多个环境(dev、test、uat、prod)中有机地增长。完整重写可能需要 12–18 个月,并带来严重的业务风险。因此我们需要一种更安全的方案。

如何将各部分组合在一起

  • Angular 继续运行旧版 UI。
  • 新功能使用 React 构建。
  • React 应用以 Web 组件的形式发布。
  • GraphQL 位于前端和后端之间。
  • Next.js 负责身份验证。

这使我们能够一次替换一个功能,而不会影响业务。

我们的迁移策略:绞杀树模式

我们采用了 Strangler Fig 模式,在旧系统持续运行的同时,逐步替换其部分功能。

我们的做法基于三大核心支柱:

  1. Monorepo 基础
  2. 基于 GraphQL 的 API
  3. 作为桥梁的 Web 组件

使用 Turborepo 的 Monorepo

我们使用 pnpmTurborepo 构建了一个 monorepo。

monorepo/
├── apps/
│   ├── auth-service/
│   ├── graphs/
│   ├── services/
│   └── notification-service/
├── packages/
│   ├── design-system/
│   ├── authentication/
│   ├── logger/
│   ├── database/
│   └── web-components/

好处

  • 跨应用共享代码
  • 端到端 TypeScript
  • 更快的构建(约提升 70%)
  • 原子化跨栈 PR
  • 协调的版本管理

GraphQL 作为 API 层

我们没有使用单体 REST API,而是创建了基于领域的 GraphQL 服务。

type Business {
  id: ID!
  name: String!
  subscriptionPlans: [Plan!]!
  defaultPlanId: Int
}

type Query {
  searchBusinesses(country: String!, searchTerm: String!): [Business!]!
}

优势

  • 明确的领域划分
  • 独立部署
  • 强类型
  • 高效的客户端驱动查询
  • 支持 Federation 的架构

Web 组件:在 Angular 中使用 React

我们使用 Web 组件将 React 功能嵌入到 Angular 应用中。

import { r2wc } from '@r2wc/react-to-web-component';
import { UserRegistrationWithApollo } from './UserRegistration';

const UserRegistrationWC = r2wc(UserRegistrationWithApollo, {
  props: {
    businessGraphApiUrl: 'string',
    providerGraphApiUrl: 'string',
  },
});

customElements.define('user-registration', UserRegistrationWC);

为什么有效

  • 与框架无关的 UI
  • 渐进式迁移
  • 现代 React 模式
  • 跨应用可复用

Apollo Client:多 Graph 通信

export const createApolloClients = (
  businessUri: string,
  providerUri: string
) => {
  const businessClient = new ApolloClient({
    link: authLink.concat(httpLink(businessUri)),
    cache: new InMemoryCache(),
  });

  const providerClient = new ApolloClient({
    link: authLink.concat(httpLink(providerUri)),
    cache: new InMemoryCache(),
  });

  return { businessClient, providerClient };
};

Next.js 用于认证

export async function POST(request: Request) {
  const { refreshToken } = await request.json();
  const newTokens = await refreshCognitoToken(refreshToken);

  return Response.json({
    accessToken: newTokens.accessToken,
    idToken: newTokens.idToken,
  });
}

Why Next.js

  • 用于身份验证的 API 路由
  • 支持 Docker 的构建
  • 在 Angular 和 React 之间共享
  • 为迁移做好未来兼容

数据库层:Prisma + SQL Server

@Injectable()
export class DataService {
  constructor(private prisma: PrismaClient) {}

  async getBusiness(id: number) {
    return this.prisma.business.findUnique({
      where: { id },
      include: {
        subscriptionPlans: true,
        locations: true,
      },
    });
  }
}

迁移工作流

步骤 1:在 React 中构建

export const Feature = () => {
  const { data, loading } = useQuery(GET_DATA_QUERY);

  if (loading) return null;

  return (
    <div>
      <h2>{data.title}</h2>
      {/* Feature implementation */}
    </div>
  );
};

步骤 2:包装为 Web 组件

const FeatureWC = r2wc(FeatureWithApollo, {
  props: {
    apiUrl: 'string',
    userId: 'string',
  },
});

customElements.define('app-feature', FeatureWC);

步骤 3:在 Angular 中使用

import '@company/wc-feature';

步骤 4:功能标志

(标志的实现已在 Angular 配置中处理;为简洁起见,HTML 占位符已省略。)

步骤 5:删除旧代码

  • 删除标志
  • 删除 Angular 组件
  • 清理服务
  • 更新测试

6 个月后的关键指标

  • 迁移了 15 个主要功能
  • 构建速度提升 70 %
  • 重复代码减少 40 %
  • 80 % 的新功能使用 React 构建
  • 零迁移相关事故

最终思考

让旧版应用退役并不一定意味着冒险的重写。
通过结合:

  • A monorepo
  • GraphQL
  • Web components
  • Next.js
  • Turborepo

…我们能够逐步现代化,同时保持业务平稳运行。

strangler‑fig 模式真的有效。在彻底重写和永远与遗留系统共存之间,有一条中间道路。

技术栈概览

传统

  • Angular 14
  • Bootstrap 4 + jQuery
  • REST API
  • 单体架构

现代

  • React 18+,Next.js 15
  • NestJS + GraphQL
  • Prisma + SQL Server
  • Turborepo + pnpm
  • 通过 @r2wc 实现的 Web 组件
  • Tailwind + Radix UI
  • TypeScript 全面使用
  • GitHub Actions + Docker
0 浏览
Back to Blog

相关文章

阅读更多 »