Angular Module Federation:如何为每个远程模块配置隔离路由?

1Z1*_*Z10 5 angular webpack-module-federation

我想为Webpack Module-Federation Angular应用程序的每个远程模块实现隔离路由。

每个远程都有自己的路由模块,但根据传递给router.navigate或 的URL routerLink,远程也可以覆盖应该专门负责主机/shell 应用程序的基本 URL。

例如

  • 外壳暴露在localhost:4200
  • 远程-a暴露在localhost:4201
  • 远程-b暴露于localhost: 4202
  • Remote-a由 shell 导入并暴露在localhost:4200/remote-a
  • Remote-b由 shell 导入并暴露在localhost:4200/remote-b.

我想要的是:

  • 每个应用程序路由应该既可以独立工作,也可以远程工作;
  • remote-alocalhost:4200/remote-a用作遥控器时不应更改其基本 URL ;
  • remote-blocalhost:4200/remote-b用作遥控器时不应更改其基本 URL ;

我们如何限制每个远程模块路由的行为,以便它只能相对于自己的路径执行导航,而不允许它干扰其他远程模块和 shell/主机应用程序?

更新

根据我找到的一些文章

似乎更接近的解决方案可能如下:

如果您的微前端自带路由器,您需要告诉您的 shell,微前端将向 URL 追加更多分段。为此,您可以使用@angular-architects/module-federation-tools也提供的startsWith匹配器:

import { 
    startsWith, 
    WebComponentWrapper, 
    WebComponentWrapperOptions 
} 
from '@angular-architects/module-federation-tools';

[...]

export const APP_ROUTES: Routes = [
    [...]
    {
        matcher: startsWith('angular3'),
        component: WebComponentWrapper,
        data: {
            [...]
        } as WebComponentWrapperOptions
    },
    [...]
}
Run Code Online (Sandbox Code Playgroud)

为了实现此功能,微前端也需要使用此处使用的路径前缀 angular3。由于路由配置只是一个数据结构,因此您将找到动态添加它的方法。

您能否进一步解释一下该解决方案的工作原理以及它是否可以满足我的要求?

ynt*_*ual 13

解决方案取决于您希望将遥控器和外壳结合得有多紧密,以及您希望将路线和导航的控制权放在哪里。我成功使用的一种方法如下:

  • Shell 应用程序不知道有关它可以加载的遥控器的任何详细信息,只知道 MF 远程信息和baseHref每个遥控器的路由。
  • 远程应用程序不了解有关外壳程序或外壳程序可能加载的任何其他远程程序的任何信息
  • 在任何时间点只能激活一个具有路由功能的远程模块
  • (可选)mflib - 用于 shell 和遥控器、辅助函数等之间通信的共享库

远程应用程序 将您想要公开的遥控器部分放入专用的 NG 模块中,该模块为其组件FeatureModule导入和设置路由。RouterModule.forChild

src/app/
     |-app.module.ts  # your main module, - not exposed as mf remote 
     |-feature.module.ts   # your feature module, exposed as mf remote
       |-orders.component.ts
       |-remote-a.component.ts
     ...
Run Code Online (Sandbox Code Playgroud)

当您将Remote-A应用程序作为独立应用程序运行时,您可以FeatureModule急切地将您的应用程序导入到主应用程序中AppModule(或者懒惰地,您的调用取决于用例,如果您有更多模块等)。如果您使用标准(热切)导入,那么您可以RouterModule.forRoot在根模块中进行设置,以使用您想要的任何路径导航到您的组件FeatureModule。如果延迟导入,则 中定义的路由RouterModule.forChild将相对于基本路径,在该路径下该模块延迟加载到根路由器。您需要确保在功能模块内导航的任何位置都使用相对导航(例如this.router.navigate(['items'], { relativeTo: this.route }))。例如,假设您的Remote-A项目中有 2 个组件,并且以下目录中有 2 个相应的路由FeatureModule:

const MY_ROUTES =[
{ 
    path: '', 
    component: RemoteAComponent, 
    pathMatch: 'full'
},
{ 
    path: 'orders', 
    component: OrdersComponent
}
]
Run Code Online (Sandbox Code Playgroud)

AppModule在 eager 模式下,您可以在via中设置此路由RouterModule.forRoot(MY_ROUTES),然后您可以使用http://localhost:4201或http://localhost:4201/orders导航到这些路由(前提是您没有设置不同的 BASE_HREF...)。或者,您可以使用延迟加载将这些路由设置为某些根路径的子路由,例如

//app.module.tsm import section:
RouterModule.forRoot([
    {
        path: '',
        component: HomeComponent
    },
    {
        path: 'feature',
        loadChildren: () => import('./feature.module').then(m => m.FeatureModule)
    }
])

//feature.module.ts
RouterModule.forChild(MY_ROUTES)
Run Code Online (Sandbox Code Playgroud)

现在要访问,OrdersComponent您将使用http://localhost:4201/feature/orders我们定义的所有路由都FeatureModule将相对于父路由(的子路由),在本例中/feature/

这样,无论您在什么根 URL 下FeatureModule加载,其内部路由都将相对于根 URL 正确解析。

对于其他遥控器也是如此,请确保将实际的遥控器模块提取到专用的角度模块中,并进行forChild路由器设置。

Shell: 您需要以某种方式定义可用的遥控器 - 静态配置或动态构造它,例如通过对某些注册表端点进行 HTTP 调用、加载 JSON 文件等。

const microFrontends = [
    {
        baseUrl: "remote-a", //where should we append this to router
        moduleName: "FeatureAModule" //name of NG module class in your remote
        remoteEntry: 'http://localhost:4201/remoteEntry.js', //remote webpack url
        remoteName: 'remotea', //name of the remote module, 
        exposedModule: './FeatureAModule', //exposed module inside the webpack remote
    },
    {
        baseUrl: "remote-b",
        moduleName: "FeatureBModule"
        remoteEntry: 'http://localhost:4202/remoteEntry.js',
        remoteName: 'remoteb',
        exposedModule: './FeatureBModule',
    }
]
Run Code Online (Sandbox Code Playgroud)

接下来,定义路由 - 包括 shell 应用程序内组件的内部路由和远程模块的路由 - 对于遥控器,您只需指定 RemoteModule 应在其下插入其子路由的根路径,然后使用Manfred Steyers 的 fn指定loadChildren属性库。例如:loadRemoteModule@angular-architects/module-federation

//app.module.ts
RouterModule.forRoot([
    { 
        path: '', //this is an example internal route to component that exists inside shell app
        component: HomeComponent,
        pathMatch: 'full' 
    },
    //we map each microfrontend entry to a lazy loaded Route object, 
    ...microFrontends.map(mf=> ({
        path: mf.baseUrl, // we insert any routes defined in the remote module we load 
        //as children of `mf.baseUrl` route
        loadChildren: () => loadRemoteModule(mf).then(m => m[mf.moduleName])
    }))
])
Run Code Online (Sandbox Code Playgroud)

使用上面的示例配置,shell 最终将得到以下路由树:

/                  -> shell/HomeComponent.ts
/remote-a          -> lazy loaded module FeatureAModule from remote-a remote
    /            -> remote-a/FeatureModule/remote-a.component.ts
    /orders      -> remote-a/FeatureModule/orders.component.ts         
Run Code Online (Sandbox Code Playgroud)

例如,shell 可以在任何 URL 下安装远程模块,foobar然后导航到orders我们将使用的远程组件形式/foobar/orders。因此,shell 可以完全控制根路由/url 结构,并且远程控制其子树内的路由/url。

如果您需要进行绝对导航,或在 Shell 和远程之间进行通信,请使用共享角度模块创建一个共享角度库,并通过单例服务 ( providedIn: root) 进行通信。

现在,当您加载远程模块时,您无法将参数或 DI 令牌传递给远程模块(角度中存在一个问题),因此,如果您的子/远程模块需要知道它已安装在哪个 URL/路由下,您需要天真地解析浏览器 URL 并假设路径的第一段是您的根路径,或者使用共享服务(在 shell 中,当您调用时,loadChildren 您可以向该服务添加一些值,并在您的远程模块你可以阅读它们)