中后台系统做为每家公司运营体系中的一环,小到内容管理系统,大到资产管理系统、财务系统等,一直都扮演着重要的角色。随着业务迭代升级,参与开发的人员增多,如果没有做好合理的规划,成为“巨石应用”似乎是一个逃不掉的宿命。一、背景
技术驱动型企业在经历过一段时间的“野蛮”发展后,随着开发人员的增多,业务复杂度的攀升,既往应用有时候难以承载复杂多变的业务形态,开发效率也会出现瓶颈。为了解放生产力,从容应对业务挑战,“一体化建设”就显得格外紧要。不同的公司可能有不同的操作模式,不过归根结底就是为了“收敛”和“提效”。比如有些业务线技术栈偏好不一,有 Vue + Element 的路线,有 React + Antd 的路线等等。从团队整体发展的角度考虑,不同的技术生态势必会分散团队有限的精力,增加开发维护成本。技术栈收敛不可避免的就是老业务的迁移,而迁移首先是要保障既有业务的稳定。
升级,摆在面前有两座大山:
- 中后台项目中的“巨石应用”,重构耗时太久,等不起。
用 Iframe?Iframe 虽然成本低,但是缺点也是显而易见的。思来想去,微前端方案再合适不过。
二、为什么是微前端
普遍共识,微前端能够独立开发、独立部署、高可维护、技术栈无关。那最重要的是哪一点?就拿迁移 React 来看,稳妥的方式肯定是渐进式的迁移。我们不关心老业务是用 Vue、Angular 还是 JQuery。首先要解决的问题是需要让他们和谐的相处在一起,React 负责新业务,大家各司其职。所以 “技术栈无关” 是先决条件。再者,我们没办法保证任何一款框架可以永葆青春。就算是同一款框架,也有狠起来砍自己一刀的时候(比如内谁 Angular...)。所以当新的技术更替的时候,依然要可以从容的过渡。所以说, “技术栈无关” 才是微前端的核心价值,其他都是附加。面对 “巨石应用” 微前端是相对合理的架构方式,至少从目前来看是这样的。三、何为微前端
微前端的概念在 2016 年就提出了,它借鉴了微服务的架构思想,是一种应用开发的架构形式。微前端主张将一个庞大的应用拆分成多个互为独立的子应用,子应用又和积木一样组成更为复杂的应用。相比于所有业务代码杂糅的巨石应用来讲,微前端的架构模式,可以很好的降低项目之间的耦合,提高应用的可维护性。四、微前端框架的选择
微前端的解决方案社区已经出现了很多种。比较常见的就是 single-spa,以及阿里基于 single-spa 的 Qiankun(乾坤),京东的 MicroApp,以及最近关注到腾讯的 Wujie (无界)。当然,我们还有成本更低的 Iframe,但是由于明显的缺点暂且不在我们的考虑范围内。为了响应平台技术栈的统一,财务域调研了微前端的 3 个技术方案:single-spa、Qiankun 和 MicroApp(当时还没有注意到 Wujie)。Single-spa 框架的封装度较低,所以改造成本高,如果有太多的项目接入,那这个成本是不得不考虑的问题。Qiankun 是基于 single-spa 的实现库,在其基础上做了进一步的优化,取其精华去其糟粕。极大的降低了开发成本,并且实现了子应用资源的预加载和隔离方案。MicroApp 是借鉴了WebComponent的思想,通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染。确定方案之前,分别尝试了 Qiankun 和 MicroApp 两种接入方案。从实际的接入体验上来说,如果不考虑 Umi 插件对 Qiankun 的加成,MicroApp 的接入成本确实相对要小一些。考虑到中后台统一脚手架是基于 Umi 的,配合 umi-plugin-qiankun 插件,其实接入成本没有明显的差距。经过反复斟酌,最终采用了 Qiankun 的技术方案,原因有三:
- 据官网数据,Qiankun 在蚂蚁内部服务超 2000+ 生产应用,稳定性是经得住考验的。对于公司中后台系统来说,安全稳定才是首要的。
- 了解到其他兄弟团队也大多采用 Qiankun,这不是人云亦云。大家都用一个,有经验可以互相分享,降本增效。反之,每天踩一坑,坑坑不一样,这哪个受得了。
五、Qiankun 在部门的落地
本篇文章并非 Qiankun 的接入教程,不会面面俱到,只是针对接入过程中的一些典型的场景讲讲技术方案实现。需要读者具备基础的 Qiankun 相关知识点,详细的接入文档请参考乾坤官方文档。主应用和子应用使用了 Umi 脚手架的,可以一并参考 Umi 的乾坤插件,事半功倍。5.1 项目概况
为整合其他子应用,我们采用的方案是创建新的主应用项目,使用内部统一的脚手架搭建,截止目前,已相继有 3+ 的子系统接入并正常运转。后期 Vue 技术栈的老业务也会逐步完成新技术栈的迁移。(安全原因,部分信息马赛克处理)
上面的截图就是主应用,左侧菜单栏可以看到,我们在主应用中以微前端的方式接入了 3 个子系统,除此之外,还有部分直接在主应用上开发的新业务(绿色框选)。绿色部分属于主应用主应用的路由,是全新的业务,因为没有历史负担,直接转向 React。还有一部分是从老系统迁移的全新 React 页面。后期蓝色部分会全部完成迁移,到时候就全是 React 的天下了。红色,黄色两个系统是 React 技术栈的其他两个子系统。总体可以看出,主应用整合了多个技术栈的页面于一体,几乎没有违和感。顶部 Tab 可以切换不同的主子应用的路由,实现了多子应用的同时激活,以及页面的 KeepAlive。以上就是主应用的整体情况,接下来拿几个典型的场景做介绍。5.2 主应用菜单聚合方案
上面我们已经简单了解了主应用的菜单组成,看着花哨其实逻辑并不复杂。在得物中后台体系中,系统的菜单均由统一的配置中心负责配置,方便权限的管理和收敛。主应用作为整个系统的核心基座,并没有选择走配置中心把每个子应用的路由重复配置一遍,这样徒增开发和产品运营的工作量。export const MicroAppConfig: MicroAppConfigType = {
[MicroAppNameEnum.App1]: {
name: MicroAppNameEnum.App1,
appName: 'xx系统',
entry: getMicroAppEntry(MicroAppNameEnum.App1),
container: '#MicroAppContainer_App1',
prefixPath: '/app1',
// ...其他配置信息
},
[MicroAppNameEnum.App2]: { ... },
[MicroAppNameEnum.App3]: { ... },
};
主应用在初始化的时候,会一次性从配置中心获取每个子应用的菜单配置,经过一定的规则聚合后作为主应用的路由进行展示。同时,为了避免不同系统菜单的冲突,主应用会在每个子应用的路由前拼接独立的 prefixPath
,做到路由的隔离。
export const MasterAppPathPrefixList = [
'/router-1',
'/router-2',
'/router-3/page-1',
];
如上,主应用还会有一份“路由白名单”配置 MasterAppPathPrefixList
,在白名单里的路由会放弃从子应用加载,直接走主应用。
如此一来,可以细粒度的控制应用路由跳转。在渐进式迁移过程中,从容的把握迁移进度。5.3 多子应用同时激活
在 Qiankun 的典型接入场景中,会使用 registerMicroApps
API 来自动化注册子应用,经过实践发现,这种方式每次只能激活一个子应用,切换不同的应用时会卸载老的应用,安装新的应用,耗时较长,体验极差。这肯定是不能忍受的。为提升使用体验,需实现多个子应用同时激活共存,切换路由实现应用热替换,提升体验。这里的核心点是使用乾坤的 loadMicroApp
API。通过监听路由 pathname 变更来判断当前所属应用,然后手动调用 loadMicroApp
方法加载微应用到对应的 container 中,如果已经加载过的不会重复加载。layout 中会预先根据配置信息放置子应用的容器节点。const handleMenuSelect = useCallback(
(path: string) => {
// 当前路由是微应用路由
if (microApp) {
const newState = { ...loadedApps };
const childRoutePath = path.replace(microApp.prefixPath || '', '');
// 设置当前活动的应用
setActiveAppName(microApp.name);
// 如果微应用未加载过
if (!loadedApps[microApp.name]) {
const app = loadMicroApp(microApp);
newState[microApp.name] = {
app,
childRoute: [],
};
}
newState[microApp.name].childRoute.push(childRoutePath);
setLoadedApps(newState);
} else {
setActiveAppName('');
}
},
[loadedApps, microApp],
);
在页面 Tab 切换的时候,通过 CSS display 来控制对应的子应用显隐,简单粗暴。
5.4 子应用 Keep-Alive
子应用 KeepAlive,可以确保在切换不同 Tab 的时候保证页面的状态不丢失,提升使用体验。实现并不是很复杂,因为我们已经实现了子应用的同时激活,所以当打开多个子应用页面后,切换子应用,其他应用的实例并不会被销毁,所以我们在子应用里面按照常规的方式做页面缓存即可。<keep-alive :include="cachedViews">
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
import { KeepAlive } from 'react-activation';
<KeepAlive name={path} id={path} saveScrollPosition="screen">
{children}
</KeepAlive>
5.5 公共 SDK 的处理
在常规的中后台系统中,几乎都会有一些公共的服务作为 SDK 的方式在各个系统中引入。在微前端场景下,多个子应用激活的情况下,难免会有冲突。那么在微前端的环境中应该如何处理类似的 SDK 呢?以页面统计 SDK 为例:目前采用的方案是只在主应用中接入 SDK,子应用在微前端环境中根据 qiankun 提供的全局变量 window.__POWERED_BY_QIANKUN__
来屏蔽SDK,避免冲突。PV & UV 统计案例
以PV UV统计场景为例,每个子应用均会记录页面的访问信息,主应用也不例外,但是如果同时开启则会造成重复统计。其实只需要在主应用中上报即可。因为在接入微前端后,后期也主要是通过主应用来使用系统入口,所以统计主应用的 PV 和 UV 更合理。
SSO 登录
绝大多数的中后台系统应该都实现了SSO单点登录的能力,以往在每个子系统中分别做了登录的处理,在微前端的场景下,登录可以统一在主应用中做控制,和统计SDK类似,相关的逻辑在子应用中通过window.__POWERED_BY_QIANKUN__
屏蔽即可,这样做及时子系统使用单独域名独立使用也不会受任何影响。
这里仅列举了两个较为通用的场景,对于不同的 SDK,可以根据实际情况决定接入方案,有些可能仍然适合在各个子应用中分别接入。5.6 主子应用样式隔离
在开启沙箱的情况下,也就是设置 sandbox
为 true 的情况下,Qiankun 会自动的隔离微应用之间的样式。但是无法确保主子应用之间的有效隔离。对于主子应用的隔离可以采用如下方案:对于主子应用之间的样式隔离可以采用手动为样式增加 scope 的方式。举个例子,如果中后台应用使用了AntDesign,可以使用 ConfigProvider
为应用配置不同的样式前缀来规避样式冲突。在新版本的 Qiankun 中,可以通过 配置{ sandbox : { experimentalStyleIsolation: true } }
的方式来开启运行时的 scoped css 能力,这种情况下,Qiankun 会改写子应用的样式规则,增加一个特殊的选择器来缩小样式范围,从而解决样式的冲突问题:
.app-main {
font-size: 14px;
}
div[data-qiankun-finance] .app-main {
font-size: 14px;
}
5.7 跨应用的跳转
在有些常见中,可能会需要用到跨应用跳转,比如子应用之间互相跳转,或者子应用需要跳转到主应用页面。一般而言,主子应用均采用 Hash 的路由方式,可以不用考虑这个问题。如果采用 History 的方式,则会有些区别。如果使用子应用本身的路由实例去跳转其他应用的路由,这种是行不通的,因为子应用路由实例跳转都基于路由的 base
,一般有两种解法:以上就是一些在迁移过程中遇到的一些典型场景解决方案,迁移的过程中问题很多,鉴于篇幅原因,就不在这里逐一赘述了。建议大家迁移前多看看乾坤的官方文档,尤其是 常见问题。基本上遇到的大部分问题都能在这里找到答案。六、成果
在技术开发工作中,凡事需要讲 ROI,也就是常说的投入产出比。产出比太低从某种方面来看就是得不偿失,需要及时评估方案的合理性。在使用 Qiankun 的微前端方案中,我们部门用较短的时间将老系统进行整合,并且顺利完成了90%+的老旧技术栈迁移,老应用独立部署亦不受任何影响,新业务采用统一 React 技术栈。团队内部通用物料的使用率得到了很大的提升。七、总结
文章花了很大的篇幅去讲微前端以及微前端的落地实践,但并不是提倡大家一定要把老项目做微前端改造,不是为了改造而改造。一定是从业务本身出发,确定有改造的需求。迁移 Qiankun 微前端的过程中,确实踩了不少坑,一边钻研一边和兄弟团队的大佬们交流经验,到现在 终于能够稳当的跑起来了。虽然 Qiankun 还是有些自己的缺点和不足,比如 Vite 的支持不好。但不可否认的是,这依然是一个优秀的框架,经历了蚂蚁内部不断的打磨,稳定性应该是可以信赖的。“巨石应用”看似头疼,但是搭配微前端以及合理的粒度划分,终究会被瓦解。最后祝大家早日完成 “统一大业”!参考文章:
乾坤官方文档
https://qiankun.umijs.org/zh/guide/getting-started
乾坤插件
https://v3.umijs.org/zh-CN/plugins/plugin-qiankun
常见问题
https://qiankun.umijs.org/zh/faq
- https://zhuanlan.zhihu.com/p/95085796
- https://qiankun.umijs.org/zh/guide
- https://zeroing.jd.com/docs.html#/
*文/胖虎
活动推荐
主题:
得物技术沙龙-中间件架构&稳定性治理实战专场
时间:
10月22日 14:00-18:00
报名方式: