【问题标题】:Angular Module Federation: How to configure isolated routing for each remote module?Angular Module Federation:如何为每个远程模块配置隔离路由?
【发布时间】:2022-08-04 20:49:18
【问题描述】:

我想实施一个隔离路由对于每个远程模块 一个Webpack 模块-联合 应用。

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

例如。

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

我想要的是:

  • 每个应用程序路由都应该作为独立和远程工作;
  • remote-a 用作远程时不应更改其基本 URL localhost:4200/remote-a
  • remote-b 用作远程时不应更改其基本 URL localhost:4200/remote-b

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

更新

根据我发现的一些文章

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

如果您的微前端带有自己的路由器,您需要告诉您的外壳微前端将在 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
    },
    [...]
}

为了完成这项工作,微前端也需要使用此处使用的路径前缀 angular3。由于路由配置只是一个数据结构,您将找到动态添加它的方法。

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

  • 我认为(目前)还没有真正的机制可以做你想做的事。但是,我想你可以让主机强行控制一些东西。最好不要让遥控器直接控制路由(仅通过对主机的“请求”)。
  • 尝试以抽象的方式思考解决方案(我对 Angular 还没有深入的了解):如果每个遥控器都有一种单独的路由器,而不在外壳和其他遥控器之间共享它,是否可能/更容易?像这里github.com/kylecannon/angular-dream-stack 所示的东西?
  • 这是一种可能的方法(但仍有一些警告)。 Angular 和 MFE 领域的许多人都在思考如何正确处理这个问题,以及如何将它很好地集成到 Angular 中(尽管这并不是 Angular 的具体问题)。我们选择根本不让遥控器控制路由,而是为主机使用的每个遥控器发送路由配置。这似乎效果很好,但显然不是在每种情况下都理想(例如,当遥控器也需要独立工作时)。
  • 也许为每个遥控器提供一个动态的 APP_BASE_HREF 可以解决这个问题。 angular.io/api/common/APP_BASE_HREF

标签: angular webpack-module-federation


【解决方案1】:

解决方案取决于您希望将遥控器和外壳耦合到何种程度,以及您希望在哪里控制路线和导航。我成功使用的一种方法如下:

  • Shell App 不知道它可以加载的遥控器的任何详细信息,只知道 MF 遥控器信息和每个遥控器的 baseHref 路由。
  • Remote Apps 不知道有关 shell 或任何其他可能由 shell 加载的 Remotes 的信息
  • 在任何时间点只能激活一个带路由的远程模块
  • (可选)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
     ...

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

const MY_ROUTES =[
{ 
    path: '', 
    component: RemoteAComponent, 
    pathMatch: 'full'
},
{ 
    path: 'orders', 
    component: OrdersComponent
}
]

在急切模式下,您可以通过RouterModule.forRoot(MY_ROUTES)AppModule 中设置此路由,然后您可以使用http://localhost:4201http://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)

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

这样,您的FeatureModule 在哪个根 URL 下加载并不重要,它的内部路由将相对于根 URL 正确解析。

其他遥控器也一样,请确保将实际的远程模块提取到专用的角度模块中,并设置forChild 路由器。

壳:您需要以某种方式定义可用的遥控器 - 静态配置或动态构造它,例如通过对某个注册表端点进行 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',
    }
]

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

//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])
    }))
])

使用上面的示例配置,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         

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

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

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

【讨论】:

    【解决方案2】:

    太棒了!,您能否提供完整的源代码示例?

    【讨论】:

      猜你喜欢
      • 2022-08-06
      • 2022-12-03
      • 1970-01-01
      • 1970-01-01
      • 2017-09-18
      • 1970-01-01
      • 2020-06-08
      • 1970-01-01
      • 2020-05-24
      相关资源
      最近更新 更多