将Angular应用分割成微前端应用:一个分步指南

微前端介绍

微前端为管理大型应用程序提供了一种可扩展的解决方案,通过将它们分割成更小、更独立的部件。这种方法允许专门的团队分别开发、测试和部署应用程序的各个部分,提高了效率并减少了复杂性。

什么是微前端?

微前端将微服务原则应用于前端开发,将大型应用程序分解成可管理的部分。这些部件,或称为微应用,可以独立开发并通过路由集成。有关微前端的更深入信息,特别是涉及到模块联邦的部分,我们建议阅读我们的模块联邦文章

视角

用户看到一个统一的应用,而开发者则在部署和服务选项中享有灵活性,包括用于集成微服务的模块联邦。

微前端技术概览

微前端允许使用不同的框架或库来构建应用程序的不同部分,提供了灵活性但也带来了挑战,如团队流动性。像单页面SPA或特定框架的解决方案,如Angular的Nx,帮助将这些技术集成到一个连贯的应用中。目标是独立构建、测试和部署每个微应用,同时确保用户无缝体验。

应对向微前端的转变

大型应用程序的挑战

大型应用程序可能会因为测试和构建耗时而遭受重负,以及本地服务速度缓慢。通过将应用程序分割成更小的部分转变为微前端,可以显著缓解这些问题。

实施微前端有许多方法,有各种指导和资源可用。一些专注于从头构建微前端,这对于预期要大规模扩展的新项目来说是理想的。

重构现有应用程序

然而,本指南集中在重构现有应用程序,可能使用Angular CLI构建,将其分割成共享库和单个应用。然后这些组件在不破坏统一用户体验的同时,增强开发效率,无缝集成在更大的应用程序中。

为微前端实现审核基础应用程序

使用示例应用程序进行实验设置

对于我们对微前端的探索,我们将使用一个示例应用程序。该应用程序虽然不大也不复杂,但包含几个服务,演示了它们如何可以与微前端架构中的每个微应用集成。

应用程序结构

应用程序的结构反映了使用Angular CLI创建新项目时通常会得到的样子。主要元素包括:

  • 功能模块:这些是应用程序内的特定功能或页面,如 'add-user'(添加用户)、'dashboard'(仪表板)、'login'(登录)和 'user-list'(用户列表)。
  • 模型:定义应用程序内数据形态的数据结构。
  • 共享服务:在应用程序的不同部分中使用的常见功能,如身份验证(auth)和用户管理(users)。

目录结构如下:

- src
  + features
    - add-user
    - dashboard
    - login
    - user-list
  - models
  + shared
    - auth
    - users

重构策略

目标是将服务和模型重构为可以跨微应用共享的库。另一方面,功能模块将被转换为各自的应用程序。这种方法确保了模块化,并便于每个功能的独立开发和部署。

路由考虑

应用程序中当前的路由设置使用懒加载,提高了性能和用户体验。下面是展示路由策略的一个摘录:

app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { isLogged } from './shared/auth/is-logged.guard';
import { isNotLogged } from './shared/auth/is-not-logged.guard';

const routes: Routes = [
  {
    path: '',
    canMatch: [isLogged],
    loadChildren: () =>
      import('./features/dashboard/dashboard.module').then((m) => m.DashboardModule),
  },
  {
    path: '',
    canMatch: [isNotLogged],
    loadChildren: () => import('./features/login/login.module').then((m) => m.LoginModule),
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

This routing module demonstrates the use of guards (isLogged and isNotLogged) to manage access to different routes based on user authentication status, and dynamic imports for lazy loading of feature modules.

Starting with Simple Steps in Micro-Frontend Migration

Approach to Migrating a Complex Application

Migrating a complex application to a micro-frontend architecture can be challenging. To facilitate this process, clear steps and possibly additional tooling (for better dependency management) are essential.

Initial Focus: Dependency-Free Files
  1. Identify Independent Files: Start by pinpointing files that do not have dependencies but are used across services and feature modules. For example, in our sample application, this applies to the models folder, which contains models used throughout the application but doesn't depend on any other files.

  2. Create a New Library for these Files: Use Angular CLI to generate a library that will house these independent files. The command for this is:

ng generate library models

这个命令会产生以下动作:

  • 创建一个新的 projects 文件夹,包含 models 库。
  • 安装 ng-packagr 作为依赖。
  • 更新 angular.jsontsconfig.json 以包含新的库。

自定义库

  • 路径映射:修改 tsconfig.json 中的路径映射以避免与npm模块发生潜在的冲突。建议的前缀是 @@,因为npm模块名称不能包含双感叹号。
  • 包命名:如果你打算将库发布到注册表,请确保 package.json 中的名称适合注册表。否则,你可以选择一个方便的名称。

重构模型

  1. 移动模型:将 models 文件夹的内容转移到新库中。生成的库将包含默认的组件、模块和服务,这些可以被移除。将用户模型文件放置在库的 lib 文件夹中。
  2. 更新公共API:修改 public-api.ts 文件以从 lib 文件夹导出适当的内容。

管理文件移动

  • 使用Git进行文件传输:使用 git mv 而不是操作系统功能来移动文件。这样可以确保Git更好地跟踪更改。

库文件夹结构

models 库的文件夹结构应该组织得清晰易懂,便于访问。

更新导入

更改导入路径:将现有的导入路径替换为新库的路径。例如,更改:

// from
import { User } from 'src/app/models/user';
// to
import { User } from '@@models';

确保路径与 tsconfig.json 文件中指定的相匹配。

构建库

解决错误:最初,你可能会遇到由于库未构建而导致的错误。通过运行以下命令来解决这个问题:

ng build models

这个命令会编译 models 库,使 TypeScript 能够正确引用 @@models

迁移至高级阶段

成功将独立代码迁移到库中后,下一步涉及到处理也可以隔离的依赖代码。继续以我们的示例为例,我们将专注于将 auth 服务及其守卫迁移到一个独立的库中。这个过程与为 models 库采取的步骤相似。

创建认证库

生成库

使用 Angular CLI 创建 auth 库,命令如下:

ng generate library auth

重构库内容:

lib 文件夹中移除默认内容,并将 auth 文件夹中的所有内容转移到其中。更新 public-api.ts 文件以反映这些更改。

public-api.ts 文件应该类似于如下结构:

/*
 * Public API Surface of auth
 */
export * from './lib/auth.service';
export * from './lib/is-logged.guard';
export * from './lib/is-not-logged.guard';

构建库

使用Angular CLI编译auth库:

ng build auth

更新导入

将应用程序的导入语句更改为使用新的 auth 库。

值得庆祝!你的应用程序的部分已经成功迁移到库中。这不仅保持了应用程序的运行,还有可能加快了速度,因为Angular不需要重建整个应用程序。你现在拥有了几个具有独立测试和构建功能的库,适用于更大应用程序中的组件、服务、管道和其他元素的共享。

高级库组织

对于更复杂的应用程序,请考虑使用ng-packagr的次要入口点(Secondary Entrypoints)功能将常用的项(例如组件或服务)进行分组。这需要进行额外的配置调整。

迁移第一个应用程序

已经学会了如何打包库,我们现在转向将一个特性模块迁移到它自己的应用程序。

选择迁移的模块

在我们的示例中,选择 Login 特性模块进行迁移。确保此模块使用的共享工件已经在它们各自的库中。像 users 服务这样的其他服务可以稍后迁移。

创建登录应用程序

生成应用程序

使用Angular CLI创建 login 应用程序:

ng generate application login --style=scss --routing

--routing标志对于应用内的导航至关重要。

启动应用程序

使用以下命令测试新创建的应用程序:

ng serve login

如果主应用程序正在运行,你可能需要使用不同的端口。

重构特性模块

  1. 转移特性模块:不要像处理库那样替换文件,而是在新应用程序的 src 中创建一个 feature 文件夹,并将 login 文件夹移入其中。

  2. 更新路由:修改 login 应用程序的 app-routing.module.ts,以便在根路由上提供 Login 模块。

  3. 调整 AppComponent:确保 login 应用的 app.component.html 包含一个 <router-outlet></router-outlet> 标签,以便路由能够正确工作。

解决样式问题

最初,新应用程序可能看起来没有样式。从主应用程序中复制 styles.scss 文件到新应用程序中以解决这个问题。未来的文章将深入探讨跨应用程序共享样式。

结果

如果操作正确,login 应用程序现在应该可以独立运行,有样式且功能完备,这标志着你朝着完全实现微前端架构迈出的重要一步。

将登录应用集成到主应用程序中

在将 Login 应用程序设置为独立实体后,下一步关键步骤是将其与主应用程序集成。这涉及构建 Login 应用为库,并使用主模块中现有的懒加载特性进行导入。

更新 angular.json

  1. 修改项目配置:在 angular.json 中,复制一个库配置,将其放在 login 配置下以更好地组织。将键重命名为 login-lib 并更新引用(如 rootsourceRoot)以指向 login 应用程序。

    • 更新后的配置应调整以反映 login 应用程序的特定路径和设置。
  2. 创建 ng-package.json:此文件对于指导 ng-packagr 如何打包应用程序至关重要。从另一个库中复制一个现有的 ng-package.json 文件,将其粘贴到 Login 应用程序的根文件夹中。将 dest 键更新为 "../../dist/login-lib" 以指明构建输出目录。

建立公共API

  1. 创建 public-api.ts:这个文件不同于典型的库设置,应该在 src 文件夹下创建。它应该只导出 Login 特征模块:
// public-api.ts
export { LoginModule } from './app/feature/login/login.module';

构建库

  1. 准备 package.json:从另一个库复制一个 package.jsonLogin 应用的根目录,并确保更新项目名称。

  2. 构建命令:执行 Angular CLI 构建命令:

ng build login-lib

这个过程会编译 Login 模块为库。

导入库

  1. 更新 tsconfig.json:在 tsconfig.json 中为 login-lib 添加一个新的路径映射,指向 "dist/login-lib"。这一步对 TypeScript 正确定位库至关重要。

  2. 修改应用程序路由:在 app-routing.module.ts 中,更新懒加载路径以使用新库:

loadChildren: () => import('@@login').then((m) => m.LoginModule),

运行集成应用程序

将这些更改应用之后,你可以像往常一样启动应用程序。现在作为一个单独项目的 Login 模块应该能够在主应用程序中无缝地工作。

更多的机会和考虑

  • 自动化构建:为了简化流程,考虑使用像 Lerna 这样的工具进行自动化构建和依赖项管理。这在处理多个库和应用程序时尤其有帮助。

  • 版本管理:当前的设置没有使用库的版本控制,这可能会使得引入破坏性更改变得复杂。实施版本控制策略对长期维护来说可能是有益的。