# 将Angular应用分割成微前端应用:一个分步指南
## 微前端介绍
微前端为管理大型应用程序提供了一种可扩展的解决方案,通过将它们分割成更小、更独立的部件。这种方法允许专门的团队分别开发、测试和部署应用程序的各个部分,提高了效率并减少了复杂性。
### 什么是微前端?
微前端将微服务原则应用于前端开发,将大型应用程序分解成可管理的部分。这些部件,或称为微应用,可以独立开发并通过路由集成。有关微前端的更深入信息,特别是涉及到模块联邦的部分,我们建议阅读我们的[模块联邦文章](https://test.com)。
#### 视角
用户看到一个统一的应用,而开发者则在部署和服务选项中享有灵活性,包括用于集成微服务的模块联邦。
## 微前端技术概览
微前端允许使用不同的框架或库来构建应用程序的不同部分,提供了灵活性但也带来了挑战,如团队流动性。像单页面SPA或特定框架的解决方案,如Angular的Nx,帮助将这些技术集成到一个连贯的应用中。目标是独立构建、测试和部署每个微应用,同时确保用户无缝体验。
## 应对向微前端的转变
### 大型应用程序的挑战
大型应用程序可能会因为测试和构建耗时而遭受重负,以及本地服务速度缓慢。通过将应用程序分割成更小的部分转变为微前端,可以显著缓解这些问题。
实施微前端有许多方法,有各种指导和资源可用。一些专注于从头构建微前端,这对于预期要大规模扩展的新项目来说是理想的。
## 重构现有应用程序
然而,本指南集中在重构现有应用程序,可能使用Angular CLI构建,将其分割成共享库和单个应用。然后这些组件在不破坏统一用户体验的同时,增强开发效率,无缝集成在更大的应用程序中。
## 为微前端实现审核基础应用程序
### 使用示例应用程序进行实验设置
对于我们对微前端的探索,我们将使用一个示例应用程序。该应用程序虽然不大也不复杂,但包含几个服务,演示了它们如何可以与微前端架构中的每个微应用集成。
#### 应用程序结构
应用程序的结构反映了使用Angular CLI创建新项目时通常会得到的样子。主要元素包括:
- **功能模块**:这些是应用程序内的特定功能或页面,如 'add-user'(添加用户)、'dashboard'(仪表板)、'login'(登录)和 'user-list'(用户列表)。
- **模型**:定义应用程序内数据形态的数据结构。
- **共享服务**:在应用程序的不同部分中使用的常见功能,如身份验证(`auth`)和用户管理(`users`)。
目录结构如下:
```bash
- src
+ features
- add-user
- dashboard
- login
- user-list
- models
+ shared
- auth
- users
```
### 重构策略
目标是将服务和模型重构为可以跨微应用共享的库。另一方面,功能模块将被转换为各自的应用程序。这种方法确保了模块化,并便于每个功能的独立开发和部署。
#### 路由考虑
应用程序中当前的路由设置使用懒加载,提高了性能和用户体验。下面是展示路由策略的一个摘录:
```typescript title="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:
```bash
ng generate library models
```
这个命令会产生以下动作:
- 创建一个新的 `projects` 文件夹,包含 `models` 库。
- 安装 `ng-packagr` 作为依赖。
- 更新 `angular.json` 和 `tsconfig.json` 以包含新的库。
#### 自定义库
- **路径映射**:修改 `tsconfig.json` 中的路径映射以避免与npm模块发生潜在的冲突。建议的前缀是 `@@`,因为npm模块名称不能包含双感叹号。
- **包命名**:如果你打算将库发布到注册表,请确保 `package.json` 中的名称适合注册表。否则,你可以选择一个方便的名称。
#### 重构模型
1. **移动模型**:将 `models` 文件夹的内容转移到新库中。生成的库将包含默认的组件、模块和服务,这些可以被移除。将用户模型文件放置在库的 `lib` 文件夹中。
2. **更新公共API**:修改 `public-api.ts` 文件以从 `lib` 文件夹导出适当的内容。
#### 管理文件移动
- **使用Git进行文件传输**:使用 `git mv` 而不是操作系统功能来移动文件。这样可以确保Git更好地跟踪更改。
#### 库文件夹结构
`models` 库的文件夹结构应该组织得清晰易懂,便于访问。
#### 更新导入
**更改导入路径**:将现有的导入路径替换为新库的路径。例如,更改:
```javascript
// from
import { User } from 'src/app/models/user';
// to
import { User } from '@@models';
```
确保路径与 `tsconfig.json` 文件中指定的相匹配。
#### 构建库
**解决错误**:最初,你可能会遇到由于库未构建而导致的错误。通过运行以下命令来解决这个问题:
```bash
ng build models
```
这个命令会编译 `models` 库,使 TypeScript 能够正确引用 `@@models`。
#### 迁移至高级阶段
成功将独立代码迁移到库中后,下一步涉及到处理也可以隔离的依赖代码。继续以我们的示例为例,我们将专注于将 `auth` 服务及其守卫迁移到一个独立的库中。这个过程与为 `models` 库采取的步骤相似。
#### 创建认证库
### 生成库
使用 Angular CLI 创建 `auth` 库,命令如下:
```bash
ng generate library auth
```
### 重构库内容:
从 `lib` 文件夹中移除默认内容,并将 `auth` 文件夹中的所有内容转移到其中。更新 `public-api.ts` 文件以反映这些更改。
`public-api.ts` 文件应该类似于如下结构:
```javascript
/*
* 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`库:
```bash
ng build auth
```
### 更新导入
将应用程序的导入语句更改为使用新的 `auth` 库。
值得庆祝!你的应用程序的部分已经成功迁移到库中。这不仅保持了应用程序的运行,还有可能加快了速度,因为Angular不需要重建整个应用程序。你现在拥有了几个具有独立测试和构建功能的库,适用于更大应用程序中的组件、服务、管道和其他元素的共享。
#### 高级库组织
对于更复杂的应用程序,请考虑使用ng-packagr的次要入口点(Secondary Entrypoints)功能将常用的项(例如组件或服务)进行分组。这需要进行额外的配置调整。
### 迁移第一个应用程序
已经学会了如何打包库,我们现在转向将一个特性模块迁移到它自己的应用程序。
#### 选择迁移的模块
在我们的示例中,选择 `Login` 特性模块进行迁移。确保此模块使用的共享工件已经在它们各自的库中。像 `users` 服务这样的其他服务可以稍后迁移。
#### 创建登录应用程序
### 生成应用程序
使用Angular CLI创建 `login` 应用程序:
```bash
ng generate application login --style=scss --routing
```
\--routing标志对于应用内的导航至关重要。
### 启动应用程序
使用以下命令测试新创建的应用程序:
```bash
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` 并更新引用(如 `root`、`sourceRoot`)以指向 `login` 应用程序。
- 更新后的配置应调整以反映 `login` 应用程序的特定路径和设置。
2. **创建 `ng-package.json`**:此文件对于指导 `ng-packagr` 如何打包应用程序至关重要。从另一个库中复制一个现有的 `ng-package.json` 文件,将其粘贴到 `Login` 应用程序的根文件夹中。将 `dest` 键更新为 `"../../dist/login-lib"` 以指明构建输出目录。
### 建立公共API
1. **创建 `public-api.ts`**:这个文件不同于典型的库设置,应该在 `src` 文件夹下创建。它应该只导出 `Login` 特征模块:
```javascript
// public-api.ts
export { LoginModule } from './app/feature/login/login.module';
```
### 构建库
1. **准备 `package.json`**:从另一个库复制一个 `package.json` 到 `Login` 应用的根目录,并确保更新项目名称。
2. **构建命令**:执行 Angular CLI 构建命令:
```bash
ng build login-lib
```
这个过程会编译 Login 模块为库。
### 导入库
1. **更新 tsconfig.json**:在 tsconfig.json 中为 login-lib 添加一个新的路径映射,指向 "dist/login-lib"。这一步对 TypeScript 正确定位库至关重要。
2. 修改应用程序路由:在 app-routing.module.ts 中,更新懒加载路径以使用新库:
```javascript
loadChildren: () => import('@@login').then((m) => m.LoginModule),
```
### 运行集成应用程序
将这些更改应用之后,你可以像往常一样启动应用程序。现在作为一个单独项目的 `Login` 模块应该能够在主应用程序中无缝地工作。
### 更多的机会和考虑
- **自动化构建**:为了简化流程,考虑使用像 Lerna 这样的工具进行自动化构建和依赖项管理。这在处理多个库和应用程序时尤其有帮助。
- **版本管理**:当前的设置没有使用库的版本控制,这可能会使得引入破坏性更改变得复杂。实施版本控制策略对长期维护来说可能是有益的。