Angular 模块联合:如何为每个远程模块配置隔离路由?
Angular Module Federation: How to configure isolated routing for each remote module?
我想为每个远程模块实现一个隔离路由
Webpack Module-Federation Angular 应用程序。
每个遥控器都有自己的路由模块,但取决于 URL
传递给 router.navigate
或 routerLink
,远程也可以覆盖应该专门负责 host/shell 应用程序的基础 URL。
例如
- shell 暴露在
localhost:4200
- remote-a 暴露在
localhost:4201
- remote-b 暴露在
localhost: 4202
- remote-a 由 shell 导入并暴露在
localhost:4200/remote-a
- remote-b 由 shell 导入并暴露在
localhost:4200/remote-b
.
我想要的:
- 每个应用程序路由都应该既可以独立运行,也可以远程运行;
remote-a
在用作遥控器时不应该改变它的基础 URL localhost:4200/remote-a
;
remote-b
在用作遥控器时不应该改变其基础 URL localhost:4200/remote-b
;
我们如何限制每个远程模块路由的行为,使其只能相对于自己的路径执行导航,
不允许它干扰其他遥控器和 shell/host 应用程序?
更新
根据我找到的一些文章
- https://www.angulararchitects.io/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/
- https://www.npmjs.com/package/@angular-architects/module-federation-tools?activeTab=readme#sub-routes
似乎更接近的解决方案如下:
If your Micro Frontend brings its own router, you need to tell your shell that the Micro Frontend will append further segments to the URL. For this, you can go with the startsWith matcher also provided by @angular-architects/module-federation-tools:
import {
startsWith,
WebComponentWrapper,
WebComponentWrapperOptions
}
from '@angular-architects/module-federation-tools';
[...]
export const APP_ROUTES: Routes = [
[...]
{
matcher: startsWith('angular3'),
component: WebComponentWrapper,
data: {
[...]
} as WebComponentWrapperOptions
},
[...]
}
To make this work, the path prefix angular3 used here needs to be used by the Micro Frontend too. As the routing config is just a data structure, you will find ways to add it dynamically.
你能进一步解释一下这个解决方案是如何工作的,它是否能满足我的要求?
解决方案取决于您希望将遥控器与 shell 耦合的紧密程度,以及您希望在何处控制路线和导航。我成功使用的一种方法如下:
- Shell 应用程序不知道它可以加载的遥控器的任何详细信息,只知道 MF 遥控器信息和每个遥控器的
baseHref
路由。
- 远程应用程序对 shell 或可能由 shell
加载的任何其他遥控器一无所知
- 在任何时间点只能激活一个带路由的远程模块
- (可选)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
}
]
在 eager 模式下,您可以通过 RouterModule.forRoot(MY_ROUTES)
在 AppModule
中设置此路由,然后您可以使用 http://localhost:4201
或 http://localhost:4201/orders
导航到这些路由(前提是您这样做了不要设置不同的 BASE_HREF...)。
或者,您可以使用延迟加载将这些路由设置为某些根路径的 sub-routes,例如
//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.[=46= 正确解析]
其他遥控器也一样,确保将实际的遥控器模块提取到专用的 angular 模块中,并使用 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',
}
]
接下来,您定义路由 - shell 应用程序中组件的内部路由和远程模块的路由 - 对于远程模块,您只需指定 RemoteModule 应插入其子路由的根路径,然后您使用 Manfred Steyers @angular-architects/module-federation
库中的 loadRemoteModule
fn 指定 loadChildren
属性。例如:
//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
组件形式 remote-a 我们会使用 /foobar/orders
。所以 shell 完全控制了根 routes/url 结构,远程控制了其子树中的 routes/urls。
如果您需要进行绝对导航,或在 Shell 和 Remotes 之间进行通信,请创建共享 angular 库,使用共享 angular 模块并通过单例服务( providedIn: root
).
现在你无法在加载远程模块时将参数或 DI 令牌传递给它(在 angular 中有一个未解决的问题),所以如果你的 child/remote 模块需要知道,在什么 URL/route 下安装了它,您需要天真地解析浏览器 URL 并假设路径的第一段是您的根路径,或者使用共享服务(在 shell,当你调用loadChildren
时,你可以将一些值赋给服务,在你的远程模块中你可以读取它们)
我想为每个远程模块实现一个隔离路由 Webpack Module-Federation Angular 应用程序。
每个遥控器都有自己的路由模块,但取决于 URL
传递给 router.navigate
或 routerLink
,远程也可以覆盖应该专门负责 host/shell 应用程序的基础 URL。
例如
- shell 暴露在
localhost:4200
- remote-a 暴露在
localhost:4201
- remote-b 暴露在
localhost: 4202
- remote-a 由 shell 导入并暴露在
localhost:4200/remote-a
- remote-b 由 shell 导入并暴露在
localhost:4200/remote-b
.
我想要的:
- 每个应用程序路由都应该既可以独立运行,也可以远程运行;
remote-a
在用作遥控器时不应该改变它的基础 URLlocalhost:4200/remote-a
;remote-b
在用作遥控器时不应该改变其基础 URLlocalhost:4200/remote-b
;
我们如何限制每个远程模块路由的行为,使其只能相对于自己的路径执行导航, 不允许它干扰其他遥控器和 shell/host 应用程序?
更新
根据我找到的一些文章
- https://www.angulararchitects.io/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/
- https://www.npmjs.com/package/@angular-architects/module-federation-tools?activeTab=readme#sub-routes
似乎更接近的解决方案如下:
If your Micro Frontend brings its own router, you need to tell your shell that the Micro Frontend will append further segments to the URL. For this, you can go with the startsWith matcher also provided by @angular-architects/module-federation-tools:
import {
startsWith,
WebComponentWrapper,
WebComponentWrapperOptions
}
from '@angular-architects/module-federation-tools';
[...]
export const APP_ROUTES: Routes = [
[...]
{
matcher: startsWith('angular3'),
component: WebComponentWrapper,
data: {
[...]
} as WebComponentWrapperOptions
},
[...]
}
To make this work, the path prefix angular3 used here needs to be used by the Micro Frontend too. As the routing config is just a data structure, you will find ways to add it dynamically.
你能进一步解释一下这个解决方案是如何工作的,它是否能满足我的要求?
解决方案取决于您希望将遥控器与 shell 耦合的紧密程度,以及您希望在何处控制路线和导航。我成功使用的一种方法如下:
- Shell 应用程序不知道它可以加载的遥控器的任何详细信息,只知道 MF 遥控器信息和每个遥控器的
baseHref
路由。 - 远程应用程序对 shell 或可能由 shell 加载的任何其他遥控器一无所知
- 在任何时间点只能激活一个带路由的远程模块
- (可选)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
}
]
在 eager 模式下,您可以通过 RouterModule.forRoot(MY_ROUTES)
在 AppModule
中设置此路由,然后您可以使用 http://localhost:4201
或 http://localhost:4201/orders
导航到这些路由(前提是您这样做了不要设置不同的 BASE_HREF...)。
或者,您可以使用延迟加载将这些路由设置为某些根路径的 sub-routes,例如
//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.[=46= 正确解析]
其他遥控器也一样,确保将实际的遥控器模块提取到专用的 angular 模块中,并使用 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',
}
]
接下来,您定义路由 - shell 应用程序中组件的内部路由和远程模块的路由 - 对于远程模块,您只需指定 RemoteModule 应插入其子路由的根路径,然后您使用 Manfred Steyers @angular-architects/module-federation
库中的 loadRemoteModule
fn 指定 loadChildren
属性。例如:
//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
组件形式 remote-a 我们会使用 /foobar/orders
。所以 shell 完全控制了根 routes/url 结构,远程控制了其子树中的 routes/urls。
如果您需要进行绝对导航,或在 Shell 和 Remotes 之间进行通信,请创建共享 angular 库,使用共享 angular 模块并通过单例服务( providedIn: root
).
现在你无法在加载远程模块时将参数或 DI 令牌传递给它(在 angular 中有一个未解决的问题),所以如果你的 child/remote 模块需要知道,在什么 URL/route 下安装了它,您需要天真地解析浏览器 URL 并假设路径的第一段是您的根路径,或者使用共享服务(在 shell,当你调用loadChildren
时,你可以将一些值赋给服务,在你的远程模块中你可以读取它们)