刷新页面?路由拆分?No,动态加载组件。
本文分为如下四部分:javascript
对于前端微服化来讲,有这么一些方案:css
所以,当咱们考虑前端微服务化的时候,咱们但愿:html
在过去的几星期里,我花费了大量的时间在学习 Single-SPA 的代码。可是,我发现它在开发和部署上真的太麻烦了,彻底达不到独立部署地标准。按 Single-SPA 的设计,我须要在入口文件中声名个人应用,而后才能去构建:前端
declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), pathPrefix('/inferno'));
同时,在个人应用里,我还须要去指定个人生命周期。这就意味着,当我开发了一个新的应用时,必须更新两份代码:主工程和应用。这时咱们还很可能在同一个源码里工做。 java
当出现多个团队的时候,在同一份源码里工做,显然变得至关的不可靠——好比说,对方团队使用的是 Tab,而咱们使用的是 2 个空格,隔壁的老王用的是 4 个空格。git
一个单体的前端应用最大的问题是,构建出来的 js、css 文件至关的巨大。而微前端则意味着,这个文件被独立地拆分红多个文件,它们即可以独立去部署应用。github
等等,咱们是否真的须要技术无关?若是咱们不须要技术无关的话,微前端问题就很容易解决了。json
事实上,对于大部分的公司和团队来讲,技术无关只是一个无关痛痒的话术。当一家公司的几个创始人使用了 Java,那么极有可能在将来的选型上继续使用 Java。除非,一些额外的服务来使用 Python 来实现人工智能。所以,在大部分的状况下,仍然是技术栈惟一。bootstrap
对于前端项目来讲,更是如此:一个部门里基本上只会选用一个框架。前端框架
因而,咱们选择了 Angular。
使用路由跳转来进行前端微服务化,是一种很简单、高效的切分方式。然而,路由跳转地过程当中,会有一个白屏的过程。在这个过程当中,跳转前的应用和将要跳转的应用,都失去了对页面的控制权。若是这个应用出了问题,那么用户就会一脸懵逼。
理想的状况下,它应该能够被控制。
互联网本质是去中心化的吗?不,DNS 决定了它不是。TAB,决定了它不是。
微服务从本质上来讲,它应该是去中心化的。可是,它又不能是彻底的去中心化。对于一个微服务来讲,它须要一个服务注册中心:
服务提供方要注册通告服务地址,服务的调用方要能发现目标服务。
对于一个前端应用来讲,这个东西就是路由。
从页面上来讲,只有咱们在网页上添加一个菜单连接,用户才能知道某个页面是可使用的。
而从代码上来讲,那就是咱们须要有一个地方来管理咱们的应用:**发现存在哪些应用,哪一个应用使用哪一个路由。
管理好咱们的路由,实际上就是管理好咱们的应用。
在设计一个微前端框架的时候,为每一个项目取一个名字的问题纠结了我好久——怎么去规范化这个东西。直到,我再一次想到了康威定律:
系统设计(产品结构等同组织形式,每一个设计系统的组织,其产生的设计等同于组织之间的沟通结构。
换句人话说,就是同一个组织下,不可能有两个项目的名称是同样的。
因此,这个问题很简单就解决了。
Single-SPA 设计了一个基本的生命周期(虽然它没有统一管理),它包含了五种状态:
因而,我在设计上基本上沿用了这个生命周期。显然,诸如 load 之类对于个人设计是多余的。
从某种意义上来讲,整个每系统是围绕着应用配置进行的。若是应用的配置能自动化,那么整个系统就自动化。
当咱们只开发一个新的组件,那么咱们只须要更新咱们的组件,并更新配置便可。而这个配置自己也应该是能自动生成的。
基于以上的前提,系统的工做流程以下所示:
总体的工程流程以下所示:
故而,其对应的架构以下图所示:
咱们作的部署策略以下:咱们的应用使用的配置文件叫 apps.json
,由主工程去获取这个配置。每次部署的时候,咱们只须要将 apps.json
指向最新的配置文件便可。配置的文件类以下所示:
一个应用的配置以下所示:
{ "name": "help", "selector": "help-root", "baseScriptUrl": "/assets/help", "styles": [ "styles.bundle.css" ], "prefix": "help", "scripts": [ "inline.bundle.js", "polyfills.bundle.js", "main.bundle.js" ] }
这里的 selector
对应于应用所须要的 DOM 节点,prefix 则是用于 URL 路由上。这些都是自动从 index.html
文件和 package.json
中获取生成的。
因为如今的应用变成了两部分:主工程和应用部分。就会出现一个问题:只有一个工程能捕获路由变化。当由主工程去改变应用的二级路由时,就没法有效地传达到子应用。在这时,只能经过事件的方式去通知子应用,子应用也须要监测是不是当前应用的路由。
if (event.detail.app.name === appName) { let urlPrefix = 'app' if (urlPrefix) { urlPrefix = `/${window.mooa.option.urlPrefix}/` } router.navigate([event.detail.url.replace(urlPrefix + appName, '')]) }
类似的,当咱们须要从应用 A 跳转到应用 B 时,咱们也须要这样的一个机制:
window.addEventListener('mooa.routing.navigate', function(event: CustomEvent) { const opts = event.detail if (opts) { navigateAppByName(opts) } })
剩下的诸如 Loading 动画也是相似的。
So,咱们就有了前端微服务框架 Mooa。它基于 single-spa && single-spa-angular-cli,而且符合以上的设计思想。
GayHub 地址:https://github.com/phodal/mooa
对于主工程而言,只须要如下的几行代码就能够完成上面的功能:
http.get<any[]>('/assets/apps.json') .subscribe(data => { data.map((config) => { that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix)); }); this.mooa.start(); }); this.router.events.subscribe((event: any) => { if (event instanceof NavigationEnd) { that.mooa.reRouter(event); } });
并添加一个对应的子应用路由:
{ path: 'app/:appName/:route', component: HomeComponent }
则如上所述的四个步骤。
对于子工程而言,则只须要一个对应的 Hook 操做:
mooaPlatform.mount('help').then((opts) => { platformBrowserDynamic().bootstrapModule(AppModule).then((module) => { opts['attachUnmount'](module); }); });
并设置好对应的 base_href:
providers: [ {provide: APP_BASE_HREF, useValue: mooaPlatform.appBase()}, ]
嗯,就是这么简单。DEMO 视频以下:
Demo 地址见:http://mooa.phodal.com/
GitHub 示例:https://github.com/phodal/mooa