Bifrost微前端框架及其在美团闪购中的实践
总第377篇
2019年 第55篇
项目背景
异地协作时,信息同步不及时引起的代码冲突以及修改公共组件引入的Bug。
不同的商家针对同一个页面存在定制化的需求。
已经实现的一些功能需要集成到企管平台中来。
因此,我们希望构建一个更高维度的解耦方案,使我们能够在开发阶段把互不干涉的模块拆成一个个类似后端微服务架构那样的子系统,各自迭代,在运行时集成为一个能够覆盖上述各种使用场景的完整系统。
方案选型
希望异地开发时不同的模块能够独立开发、独立部署。
对已在线上运行的项目,希望能够低成本地接入企管平台,而不需要对开发、部署流程做大规模的改动。
各个子系统独立运行,互不影响,但允许我们在开发阶段与其他子系统进行联调。
保持单页应用的体验。
由于现有项目都是基于Vue技术栈开发,因此,我们的框架并不需要做到技术栈无关,只要满足Vue的项目即可。
基于以上这些诉求,我们调研了目前市面上常用的微前端方案,最常见的方案有:
基于Nginx的路由分发。
使用Iframe将页面嵌入。
除此之外,还有美团集团内部的微前端实践——美团HR系统(用微前端方式搭建类单页应用)和业界比较知名的微前端框架——SingleSPA。
从用户体验角度出发,Nginx和Iframe首先被否决;HR系统的方案需要对现有的项目进行改造,把不同团队目前开发的项目整合到同一个单页应用中,在项目快速迭代的过程中,成本过高,所以也被否掉。SingleSPA看起来完美,但它没有照顾到实际生产环境中的开发、部署的差异性,并不是Product-Ready。综合多种因素考虑,我们最终决定采用自研的方式来实现微前端化Bifrost。
核心架构
主系统是用来控制子系统的调度中心,职责包括:
维护子系统的注册表。
管理各个子系统的生命周期。
传递路由信息。
加载子项目的入口资源。
为子系统的实例提供挂载点。
子系统只负责业务逻辑的实现。如果进一步细分的话,子系统可以分为业务子系统、实现公共菜单子系统、导航布局子系统,其中布局子系统会先于业务子系统加载。
具体实现
主系统
AppName:子系统名,与系统的路由前缀保持对应,同时也会作为子系统在DOM中挂载节点的ID。
Domain:非必填,如果出现多个路由前缀都对应同一个子系统,可以通过Domain进行映射。
ConfigPath:对应子系统配置文件的URL。
一个简单的主系统实现如下:
import { Platform } from '@sfe/bifrost'
new Platform({
layoutFrame: {
render () {
// render layout
}
},
appRegister: [
{ appName: 'app1', configPath: '/path/to/app1/config.js' }
]
}).start()
业务子系统
import { AppContainer } from '@sfe/bifrost'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({})
new AppContainer({
appName: 'app1',
router,
mounted () {
return new Vue({
router,
components: { App },
template: '<App/>'
}).$mount()
}
}).start()
另外,还需要修改子系统的构建流程,构建完成之后生成一个包含子系统入口资源信息的配置文件。一个典型的配置文件如下:
((callback) => callback({
scripts: [
'/js/chunk-vendors.dee65310.js','/js/home.b822227c.js'
],
styles: [
'/css/chunk-vendors.e7f4dbac.css','/css/home.285dac42.css'
]
}))(configLoadedCb.crm)
此处我们实现了@sfe/bifrost-config-plugin插件,在Webpack构建脚本中引入该插件就可以自动生成项目对应的配置文件。配置文件是一个立即执行函数,主系统可以通过JSONP的方式读取配置文件中的内容。
布局子系统
import { LayoutContainer } from '@sfe/bifrost'
import Vue from 'vue'
import App from './app'
new LayoutContainer({
appName: 'layout',
router,
onInit ({ appSlot, callback }) {
Vue.config.productionTip = false
const app = new Vue({
el: '#app',
router,
store,
render: (h) => (
<App appSlot={appSlot} />
)
})
callback()
}
}).start()
布局子系统作为主系统的一部分,既可以放在主系统中去实现,也可以像其他子系统一样通过异步的方式去加载。在我们的项目中,结合了上面两种方式(布局子系统既可以为作为常规的Vue项目构建,也可以发布成NPM包),每次发布时,会同时发布布局的静态资源和NPM包。主系统通过NPM包的方式引入布局子系统,将它打包到项目中,避免线上运行时,额外加载布局子系统的资源,减小项目体积,加快渲染速度。
工程实践
本地开发时,如何保证与线上实际运行效果的一致性?
如何实现全局状态管理和子系统之间的通信?
如何对公共依赖和公共模块进行管理?
发布部署流程需要怎样调整?
根据闪购业务实践,我们总结了一套适用于Bifrost的解决方案。
本地联调
本地开发时,无法看到当前开发的功能在主系统中实际运行的效果。
子系统之间有时会存在跳转关系,在开发阶段难以验证这种跳转逻辑的正确性。
为了解决这些问题,Bifrost定义了MockPlatform。MockPlatform的思路很简单:既然主系统可以动态加载线上的子系统,那么我们只需要在开发时,模拟主系统的运行方式,去加载其他子系统的线上资源,之后就可以像调用后端API一样同各个子系统进行联调了。这也就解释了为什么布局子系统在输出NPM包的同时还维护了一份静态资源。
// ...others...
new AppContainer({
// ...others...
runDevPlatform: process.env.NODE_ENV === 'development', // 只在开发环境下启动mock platform
devPlatformConfig: {
layoutFrame: {
mode: 'remote',
configPath: 'path/to/layout/config.js'
},
appRegister: [{
appName: 'app2',
mode: 'remote',
configPath: 'path/to/app2/config.js'
}]
},
// ...others...
}).start()
借助MockPlatform,我们项目在开发阶段的感受和开发普通的单页应用没有任何差异,如果某个我们依赖的子系统更新了功能,只需要让对应的RD发布一下,就可以在本地看到它的最新效果。
全局状态
import { AppContainer, syncGlobalStore } from '@sfe/bifrost'
import Vue from 'vue'
import Vuex from 'vuex'
// ...others...
Vue.use(Vuex)
const store = new Vuex.Store({})
new AppContainer({
mounted () {
// 同步全局store状态
syncGlobalStore(store)
// ...others...
return new Vue({
store,
router,
components: { App },
template: '<App/>'
}).$mount()
}
}).start()
如果子系统自身的状态需要共享,Bifrost还会提供installGlobalModule函数。该函数会将当前子系统需要共享的状态挂载到全局Store下,其他子系统可以通过前面提到的方式来同步这些状态。虽然Bifrost提供了子系统通信的能力,但在实际拆分子系统时,应该尽量避免这种情况发生。如果两个子系统之间需要频繁通信,那就应该考虑把他们划分到同一个子系统。
公共依赖
模块复用
发布及部署流程
版本控制
埋点及错误上报
收益
实现了异地合作开发时的完全解耦。采用微前端架构之后,两地团队在开发过程中再也没有遇到代码冲突的问题。
避免了单页应用发展成“巨石”应用。目前,企管平台总共实现了上百个页面,采用微前端的方式进行划分后,每个子系统包含的页面都不超过三十个,子系统的可维护性得到大大提高。
今年企管平台经历了两次大的组件库版本升级。第一次升级时,项目还是单页应用,我们在暂停业务开发的基础上,耗费了大约一周的时间对所有的页面进行回归验证、完成升级。第二次升级时,我们已经完成了项目的微前端改造,可以通过增量的方式,先升级不常用的子系统,验证通过后再升级其他子系统。这样既不用中断正常的业务开发,也保证了依赖库升级时的影响范围和风险可控。
不是“银弹”
功能模块较多,且各个功能模块相对较为独立的中后台系统。
项目存在大量历史遗留问题,希望在保留已有功能的基础上,开发新的功能模块。
其他大部分项目都可以通过调整代码结构,构建单页应用,甚至采用最传统的多页应用等方式来进行优化、调整,从来达到降低耦合的目的。微前端并不是“银弹”。
期许
提供更加完善的前端微服务治理工具。
实现JS和CSS沙盒。
支持更多的技术栈。
结语
作者简介
雨甫,美团闪购前端研发工程师。
欢迎加入美团前端技术交流群,跟作者零距离交流。如想进群,请加美美同学的微信(微信号:MTDPtech03),回复:微前端框架,美美会自动拉你进群。
---------- END ----------
招聘信息
美团闪购前端团队诚招高级前端开发、前端开发专家。欢迎各位大佬的加入,共同打造极致的LBS电商体验。感兴趣同学可投递简历至:tech@meituan.com(邮件标题注明:美团闪购前端团队)