微前端microApp实践
微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
项目改造背景
因项目需要要做一个大数据融合平台,主要功能模块包括用户管理、流程管理、大数据治理算子应用,基础组件等混合构成的融合平台;公司目前已经存在用户管理、流程管理等成品;基本主内容也是和这些产品的功能一致,主框架只是控制着导航和头部信息;因此希望能够抱着复用的方式,更快的进行融合接入。
前端流行的微服务框架比对
参数 | singlespa | qianku | microapp |
---|---|---|---|
开发成本 | 开发成本高 | 开发成本中 | 开发成本高 |
维护成本 | 中 | 中 | 中 |
技术栈 | 不限技术栈 | 不限技术栈 | 不限技术栈 |
实现难易 | 实现难 | 实现中 | 实现简单 |
原理 | 监听 url change 事件 | 监听 url change 事件 | WebComponent |
经过对比,目前觉得micro-app使用简单,将所有功能都封装到一个类WebComponent组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。同时micro-app还提供了js沙箱、样式隔离、元素隔离、预加载、数据通信、静态资源补全等一系列完善的功能。子应用基本不需要过多的进行项目改造,相对使用成本更低
对接说明
- 主框架依赖包引入
import microApp from '@micro-zoe/micro-app'
- 主应用启动(并可以进行全局配置)
microApp.start({
plugins: {
global: [{
loader(code, url, options) { // 必填
console.log('全局插件')
return code
}
}],
}
}
})
//相关参数
microApp.start({
inline: true, // 默认值false
destroy: true, // 默认值false
disableScopecss: true, // 默认值false
disableSandbox: true, // 默认值false
shadowDOM: true, // 默认值false
ssr: true, // 默认值false
})
- 配置组件节点
<micro-app
style="height: 100%;"
name='appnameUserCenter'
:url='url'
:data='microAppData'
@created='handleCreate'
@beforemount='handleBeforeMount'
@mounted='handleMount'
@unmount='handleUnmount'
@error='handleError'
@datachange='handleDataChange'
></micro-app>
生命周期列表
- created
<micro-app>标签初始化后,加载资源前触发。 - beforemount
加载资源完成后,开始渲染之前触发。 - mounted
子应用渲染结束后触发。 - unmount
子应用卸载时触发。 - error
子应用渲染出错时触发,只有会导致渲染终止的错误才会触发此生命周期。
- 路由匹配设置(模糊匹配,建议主应用history,微应用hash模式,)
{
path: '/mainPanel',
name: 'mainPanel',
component: mainPanel,
children:[
{
path: '/mainPanel/dataCenter*',
name: 'dataCenter',
component:()=>dataCenter
},
{
path: '/mainPanel/userCenter*',
name: 'userCenter',
component:()=>userCenter
},
]
}
- 主微框架通讯
microMenu(appName, path, hash) {
if (!getActiveApps().includes(appName)) {
path = '/mainPanel' + path+'/'
// child-vite 和 child-react17子应用为hash路由,这里拼接一下hash值
hash && (path += `/#${hash}`)
// 主应用跳转
this.$router.push(path)
} else {
let childPath = null
// child-vite 和 child-react17子应用是hash路由,hash值就是它的页面地址,这里单独处理
if (hash) {
childPath = hash
} else {
// path的值形式如:/app-vue2/page2,这里/app-vue2是子应用的基础路由,/page2才是页面地址,所以我们需要将/app-vue2部分删除
childPath = path.replace(/^\/app-[^/]+/, '')
!childPath && (childPath = '/') // 防止地址为空
}
// 主应用通过下发data数据控制子应用跳转
microApp.setData(appName, { path: childPath })
}
}
- 微框架及其路由跳转
(function () {
let app = null;
//创建vue实例
const createVue = function () {
app = new Vue({
el: "#app",
router,
store,
render: (h) => h(App),
});
};
// 与基座进行数据交互
const handleMicroData = function (callBack) {
window.microApp.addDataListener((data) => {
callBack(data);
});
};
// 微前端环境下,注册mount和unmount方法
if (window.__MICRO_APP_ENVIRONMENT__) {
let {token} = window.microApp.getData();
token && setToken(token);
console.log("data-center token:", token);
window[`micro-app-${window.__MICRO_APP_NAME__}`] = {
//微前端挂载
mount: () => {
createVue();
handleMicroData((data) => {
//交互操作
store.dispatch("settings/changeSetting", {
key: "microFlag",
value: true,
});
console.log("data-center addDataListener:", data);
router.push(data.path);
});
},
//微前端卸载
unmount: () => {
app.$destroy();
app.$el.innerHTML = "";
app = null;
console.log("微应用child-vue2卸载了");
},
};
} else {
// 非微前端环境直接渲染
createVue();
}
})();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。