Angular 基于 Component 的思想,可以让其在一个页面上同时运行多个 Angular 应用;可以在一个 DOM 节点下,存在多个 Angular 应用,即类似于下面的形式:
<app-home _nghost-c3="" ng-version="5.2.8">
<app-help _nghost-c0="" ng-version="5.2.2" style="display:block;"><div _ngcontent-c0=""></div></app-help>
<app-app1 _nghost-c0="" ng-version="5.2.3" style="display:none;"><nav _ngcontent-c0="" class="navbar"></div></app-app1>
<app-app2 _nghost-c0="" ng-version="5.2.2" style="display:none;"><nav _ngcontent-c0="" class="navbar"></div></app-app2>
</app-home>
可这一样一来,难免需要做以下的一些额外的工作:
而在这其中最麻烦的就是第三方模块冲突问题。思来想去,在三月中旬,我在 Mooa 中添加了一个 iframe 模式。
在这里,总的设计思想和之前的《如何解构单体前端应用——前端应用的微服务式拆分》中介绍是一致的:
主要过程如下:
其加载形式与之前的 Component 模式并没有太大的区别:
而为了控制不同的 iframe 需要做到这么几件事:
因为大部分的代码可以与之前的 Mooa 复用,于是我便在 Mooa 中实现了相应的功能。
iframe 可以创建一个全新的独立的宿主环境,这意味着我们的 Angular 应用之间可以相互独立运行,我们唯一要做的是:建立一个通讯机制。
它可以不修改子应用代码的情况下,可以直接使用。与此同时,它在一般的 iframe 模式进行了优化。使用普通的 iframe 模式,意味着:我们需要加载大量的重复组件,即使经过 Tree-Shaking 优化,它也将带来大量的重复内容。如果子应用过多,那么它在初始化应用的时候,体验可能就没有那么友好。但是与此相比,在初始化应用的时候,加载所有的依赖在主程序上,也不是一种很友好的体验。
于是,我就在想能不能创建一个更友好地 IFrame 模式,在里面对应用及依赖进行处理。如下,就是最后生成的页面的 iframe 代码:
<app-home _nghost-c2="" ng-version="5.2.8">
<iframe frameborder="" width="100%" height="100%" src="http://localhost:4200/assets/iframe.html" id="help_206547" style="display:block;"></iframe>
<iframe frameborder="" width="100%" height="100%" src="http://localhost:4200/assets/iframe.html" id="app_235458 style="display:none;"></iframe>
</app-home>
对,两个 iframe 的 src 是一样的,但是它表现出来的确实是两个不同的 iframe 应用。那个 iframe.html 里面其实是没有内容的:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>App1</title>
<base href="/">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
</body>
</html>
(PS:详细的代码可以见 https://github.com/phodal/mooa)
只是为了创建 iframe 的需要而存在的,对于一个 Angular 应用来说,是不是一个 iframe 的区别并不大。但是,对于我们而言,区别就大了。我们可以使用自己的方式来控制这个 IFrame,以及我们所要加载的内容。如:
注意
:对于一些共用 UI 组件而言,仍然需要重复加载。这也就是 iframe 模式下的问题。
为了在主工程与子工程通讯,我们需要做到这么一些事件策略:
由于,我们使用 Mooa 来控制 iframe 加载。这就意味着我们可以通过 document.getElementById
来获取到 iframe,随后通过 iframeEl.contentWindow
来发布事件,如下:
let iframeEl: any = document.getElementById(iframeId)
if (iframeEl && iframeEl.contentWindow) {
iframeEl.contentWindow.mooa.option = window.mooa.option
iframeEl.contentWindow.dispatchEvent(
new CustomEvent(MOOA_EVENT.ROUTING_CHANGE, { detail: eventArgs })
)
}
这样,子应用就不需要修改代码,就可以直接接收对应的事件响应。
由于,我们也希望能直接在主工程中处理子程序的事件,并且不修改原有的代码。因此,我们也使用同样的方式来在子应用中监听主应用的事件:
iframeEl.contentWindow.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(event: CustomEvent) {
if (event.detail) {
navigateAppByName(event.detail)
}
})
同样的我们仍以 Mooa 框架作为示例,我们只需要在创建 mooa 实例时,配置使用 iframe 模式即可:
this.mooa = new Mooa({
mode: 'iframe',
debug: false,
parentElement: 'app-home',
urlPrefix: 'app',
switchMode: 'coexist',
preload: true,
includeZone: true
});
...
that.mooa.registerApplicationByLink('help', '/assets/help', mooaRouter.matchRoute('help'));
that.mooa.registerApplicationByLink('app1', '/assets/app1', mooaRouter.matchRoute('app1'));
this.mooa.start();
...
this.router.events.subscribe((event: any) => {
if (event instanceof NavigationEnd) {
that.mooa.reRouter(event);
}
});
子程序则直接使用:https://github.com/phodal/mooa-boilerplate 就可以了。
围观我的Github Idea墙, 也许,你会遇到心仪的项目