MFE微前端高级版:Angular + Module Federation + webpack + 路由(Route way)完整示例
在查看这篇文章之前,可以先简单预览一下这篇基础版的博客:
MFE微前端基础版:Angular + Module Federation + webpack + 路由(Route way)完整示例 -CSDN博客
这篇Angular + Module Federation 高级路由配置详解包括嵌套路由、路由守卫、懒加载策略和动态路由等高级功能。
1. 主应用 (Shell) 高级路由配置
shell/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes, UrlSegment } from '@angular/router';
import { AuthGuard } from './core/guards/auth.guard';
import { MfePreloadStrategy } from './core/strategies/mfe-preload.strategy';// 动态路由匹配函数 - 用于识别微前端路由
export function mfe1Matcher(url: UrlSegment[]) {return url.length > 0 && url[0].path.startsWith('mfe1-') ? { consumed: [url[0]] } : null;
}export function mfe2Matcher(url: UrlSegment[]) {return url.length > 0 && url[0].path.startsWith('mfe2-') ? { consumed: [url[0]] } : null;
}const routes: Routes = [{path: '',pathMatch: 'full',redirectTo: 'dashboard'},{path: 'dashboard',loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),canActivate: [AuthGuard]},// 标准微前端路由配置{path: 'mfe1',loadChildren: () => import('mfe1/Module').then(m => m.Mfe1Module),data: { preload: true, mfe: 'mfe1' }},{path: 'mfe2',loadChildren: () => import('mfe2/Module').then(m => m.Mfe2Module),canLoad: [AuthGuard],data: { mfe: 'mfe2' }},// 动态微前端路由配置 - 使用自定义URL匹配器{matcher: mfe1Matcher,loadChildren: () => import('mfe1/DynamicModule').then(m => m.DynamicModule),data: { dynamic: true }},{matcher: mfe2Matcher,loadChildren: () => import('mfe2/DynamicModule').then(m => m.DynamicModule),data: { dynamic: true }},// 通配符路由 - 捕获所有未匹配的路由{path: '**',loadChildren: () => import('./not-found/not-found.module').then(m => m.NotFoundModule)}
];@NgModule({imports: [RouterModule.forRoot(routes, {preloadingStrategy: MfePreloadStrategy, // 自定义预加载策略paramsInheritanceStrategy: 'always', // 始终继承路由参数enableTracing: false // 生产环境应设为false})],exports: [RouterModule],providers: [MfePreloadStrategy]
})
export class AppRoutingModule { }
2. 自定义预加载策略
shell/src/app/core/strategies/mfe-preload.strategy.ts
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { MfeLoaderService } from '../services/mfe-loader.service';@Injectable({ providedIn: 'root' })
export class MfePreloadStrategy implements PreloadingStrategy {constructor(private mfeLoader: MfeLoaderService) {}preload(route: Route, load: () => Observable<any>): Observable<any> {// 根据路由数据决定是否预加载if (route.data?.['preload'] && route.data?.['mfe']) {// 预加载微前端应用this.mfeLoader.preloadMfe(route.data['mfe']);return load();}return of(null);}
}
3. 微前端加载服务
shell/src/app/core/services/mfe-loader.service.ts
import { Injectable } from '@angular/core';
import { LoadRemoteModuleOptions } from '@angular-architects/module-federation';@Injectable({ providedIn: 'root' })
export class MfeLoaderService {private loadedMfes = new Set<string>();preloadMfe(mfeName: string): void {if (this.loadedMfes.has(mfeName)) return;const options: LoadRemoteModuleOptions = {type: 'module',exposedModule: './Module'};switch (mfeName) {case 'mfe1':options.remoteEntry = 'http://localhost:4201/remoteEntry.js';options.remoteName = 'mfe1';break;case 'mfe2':options.remoteEntry = 'http://localhost:4202/remoteEntry.js';options.remoteName = 'mfe2';break;}import('@angular-architects/module-federation').then(m => {m.loadRemoteModule(options).then(() => {this.loadedMfes.add(mfeName);console.log(`${mfeName} preloaded`);});});}
}
4. 微前端应用 (MFE1) 高级路由配置
mfe1/src/app/mfe1/mfe1.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Mfe1HomeComponent } from './components/home/home.component';
import { Mfe1DetailComponent } from './components/detail/detail.component';
import { Mfe1Guard } from './guards/mfe1.guard';const routes: Routes = [{path: '',component: Mfe1HomeComponent,children: [{path: 'detail/:id',component: Mfe1DetailComponent,data: {breadcrumb: 'Detail'}},{path: 'admin',loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),canLoad: [Mfe1Guard]},{path: 'lazy',loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)}]},// 动态模块暴露的路由{path: 'dynamic',loadChildren: () => import('./dynamic/dynamic.module').then(m => m.DynamicModule)}
];@NgModule({imports: [RouterModule.forChild(routes)],exports: [RouterModule]
})
export class Mfe1RoutingModule { }@NgModule({imports: [Mfe1RoutingModule,// 其他模块...],declarations: [Mfe1HomeComponent, Mfe1DetailComponent]
})
export class Mfe1Module { }
5. 动态路由注册 (运行时路由)
shell/src/app/core/services/dynamic-routes.service.ts
import { Injectable } from '@angular/core';
import { Router, Routes } from '@angular/router';@Injectable({ providedIn: 'root' })
export class DynamicRoutesService {private dynamicRoutes: Routes = [];constructor(private router: Router) {}registerRoutes(newRoutes: Routes): void {// 合并新路由this.dynamicRoutes = [...this.dynamicRoutes, ...newRoutes];// 重置路由配置this.router.resetConfig([...this.router.config, ...this.dynamicRoutes]);}unregisterRoutes(routesToRemove: Routes): void {this.dynamicRoutes = this.dynamicRoutes.filter(route => !routesToRemove.includes(route));this.router.resetConfig([...this.router.config.filter(route => !routesToRemove.includes(route)),...this.dynamicRoutes]);}
}
6. 路由同步服务 (跨微前端路由状态管理)
shell/src/app/core/services/route-sync.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';@Injectable({ providedIn: 'root' })
export class RouteSyncService {private currentRoute = '';constructor(private router: Router) {this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {this.currentRoute = event.url;// 可以在这里广播路由变化给所有微前端this.broadcastRouteChange(event.url);});}private broadcastRouteChange(url: string): void {// 使用自定义事件或状态管理库广播路由变化window.dispatchEvent(new CustomEvent('microfrontend:route-change', {detail: { url }}));}syncRoute(mfeName: string): void {// 微前端可以调用此方法来同步路由状态this.router.navigateByUrl(this.currentRoute);}
}
7. 微前端路由守卫示例
mfe1/src/app/guards/mfe1.guard.ts
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';@Injectable({ providedIn: 'root' })
export class Mfe1Guard implements CanLoad {constructor(private authService: AuthService,private router: Router) {}canLoad(route: Route, segments: UrlSegment[]): boolean {const requiredRole = route.data?.['requiredRole'];if (this.authService.hasRole(requiredRole)) {return true;}// 重定向到主应用的未授权页面this.router.navigate(['/unauthorized'], {queryParams: { returnUrl: segments.join('/') }});return false;}
}
8. 路由数据解析器
shell/src/app/core/resolvers/user.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from '../services/user.service';@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<any> {constructor(private userService: UserService) {}resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {const userId = route.paramMap.get('id');return this.userService.getUser(userId);}
}
附录:Angular 路由的匹配规则和默认行为
1. 空路径路由的默认匹配
-
示例配置:
const routes: Routes = [{ path: '', component: HomeComponent }, // 空路径{ path: 'login', component: LoginComponent } ];
- 当访问根路径(如
http://localhost:4200
)时,Angular 会优先匹配path: ''
的路由规则。 - 如果没有显式配置
redirectTo
,但定义了空路径对应的组件(如HomeComponent
),则会直接渲染该组件。
2. 路由匹配的优先级规则
-
规则:Angular 按顺序匹配路由配置,第一个匹配的规则生效。
-
示例:
const routes: Routes = [{ path: 'dashboard', component: DashboardComponent },{ path: ':id', component: UserComponent } // 动态参数路由 ];
-
现象:
-
访问
/dashboard
会匹配第一个路由,渲染DashboardComponent
。 -
访问
/123
会匹配第二个路由,渲染UserComponent
。 -
即使没有
redirectTo
,也能直接定位到组件。
-
3. 子路由的默认渲染
-
示例配置:
const routes: Routes = [{path: 'admin',component: AdminComponent,children: [{ path: '', component: AdminDashboardComponent }, // 子路由的空路径{ path: 'users', component: UserListComponent }]} ];
-
现象:
-
访问
/admin
时,会渲染AdminComponent
的模板,并在<router-outlet>
中显示AdminDashboardComponent
(因为子路由的空路径匹配)。 -
虽然未显式配置
redirectTo
,但子路由的空路径规则会直接加载组件。
-
4. 路由通配符 (**
) 的兜底行为
-
示例配置:
const routes: Routes = [{ path: 'home', component: HomeComponent },{ path: '**', component: NotFoundComponent } // 通配符路由 ];
-
现象:
-
访问未定义的路径(如
/foo
)时,会直接匹配**
规则,渲染NotFoundComponent
。 -
无需
redirectTo
,通配符路由会直接显示组件。
-
5. 模块的默认路由
-
懒加载模块的默认行为:
const routes: Routes = [{ path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) } ];
-
如果
LazyModule
内部的路由配置中有空路径规则:const routes: Routes = [{ path: '', component: LazyHomeComponent } // 默认组件 ];
-
访问
/lazy
时,会直接渲染LazyHomeComponent
,而无需重定向。
-
6. 路由参数的隐式匹配
-
动态参数路由:
const routes: Routes = [{ path: 'product/:id', component: ProductDetailComponent } ];
-
现象:
-
访问
/product/123
会直接渲染ProductDetailComponent
,无需重定向。
-
总结:
场景 | 原因 |
---|---|
空路径 (path: '' ) | 直接匹配空路径对应的组件。 |
子路由的空路径 | 父组件渲染后,子路由的空路径组件会自动显示。 |
动态参数路由 (:id ) | 路径匹配后直接渲染组件。 |
通配符路由 (** ) | 兜底路由直接显示组件。 |
懒加载模块的默认路由 | 模块内部的路由配置可能已经定义了空路径组件。 |