那天,我正在奋笔疾书的写代码,看着那四六不通的代码在我手里逐渐光芒四射,不知不觉嘴角扬起了一丝的微笑。 突然,电脑右上角飘来一条钉钉消息: 来我工位一趟!这时大领导的消息,我内心有点忐忑,我战战兢兢的把鼠标移到钉钉上,然后又迅速移开。
(内心独白:)”如果贸然点击,消息由未读成了已读,那就得马上放下手里的鱼,这样会不会显得我工作不饱和啊。不行,先等等再说!“
这时 “钉。。!"的一声,刺耳的提示音像箭一样射进了我的耳朵。于是点开消息,迅速回复道:好的,马上到!
于是,便有了我对6年老项目在微前端改造上摩擦,踩坑的经历!
项目背景
老项目是用vue + iview2写的,由于时间长,iview2早已不维护,加之又是我们公司初创时做的第一个后台系统,功能多,代码糙,有的页面多达4000多行代码,维护成本十分之高,编译项目十分之慢,病入膏肓,所以微前端改造势在必行。
这次改造,代号称之为 - 屎壳郎计划(搬运s山)
关于micro-app
在做微前端框架选型的时候,也做了一番考虑,研究了竞品qiankun,但是qiankun复杂的配置让我有些望而却步。自己也亲身搭建了一套qiankun的demo, 从上手的难易程度来讲,micro-app真的很合适。配置简单、文档细致,虽然目前1.0版本还没有发布,但是已经足以满足了我们这个老项目对于微前端的改造。总之,任何框架都有亮点,也有不足,选择适合自己的就是最好的!
主、子框架选型
主应用: vue + iview2 + vite + microApp hash路由
子应用: vue + ivew2 + webapck hash路由
这里需要解释一下为什么子应用仍然采用了iview2的UI框架?因为页面太多了,如果换了UI框架,样式全部都要改,这个时间太长,耗不起。为了做到子应用和主应用的样式无缝兼容,只好再度采用iview2!至于子应用为什么不用vite 而是用webpack ,是因为对于micro-app来说,适配vite成本是巨大的,需要关闭沙箱功能,这对我们来说显然有点得不偿失。
最近也在研究micro-app的原理,后续也会对为什么不能用vite 做一次分析
主应用
安装依赖
npm i @micro-zoe/micro-app --save
在入口处引入 main.js
import microApp from '@micro-zoe/micro-app'
microApp.start()
分配一个路由给子应用
{
path: '/pages/page1',
meta: {
mgSite: 'iview2'
},
component: ()=>import('../pages/_microApp/index')
}
进入_microApp/index.vue
<template>
<div style="height: 100%; background: white">
<Spin v-if="isShowSkeleton" fix> 加载中... </Spin>
<micro-app
@error="error"
@aftershow="aftershow"
@mounted="mounted"
@created="created"
:name="appName"
url="http://localhost:8080"
baseroute="/pages"
:data="microAppData"
keep-alive
style="height: 100%"
></micro-app>
</div>
</template>
<script>
import { removeDomScope } from '@micro-zoe/micro-app'
export default {
data() {
return {
isShowSkeleton: true,
appName: 'appName',
microAppData: {
pushState: (path) => {
removeDomScope()
this.$router.push(path)
},
},
}
},
created() {
this.initAppName()
},
watch: {
$route() {
this.initAppName()
this.initMicroData()
},
},
methods: {
initMicroData() {
this.isShowSkeleton = true
this.microAppData = {
pushState: (path) => {
removeDomScope()
this.$router.push(path)
},
}
},
initAppName() {
const { hash } = location
if (hash.split('#')[1] && this.appName !== hash.split('#')[1]) {
this.appName = hash.split('#')[1]
}
},
created() {
this.isShowSkeleton = true
},
mounted() {
this.isShowSkeleton = false
},
aftershow() {
console.log('微前端页面' + this.appName)
this.isShowSkeleton = false
},
error() {
this.isShowSkeleton = false
},
},
}
</script>
注意: 是一个web components 组件,无需单独引入。
子应用
在vue.config.js-> devServer 中设置headers 支持跨域请求
headers: {
'Access-Control-Allow-Origin': '*'
}
由于我的主应用和子应用均是hash路由,为了防止冲突,基座设置了基础路由给子应用。子应用可以在这个路由下完成渲染。可以看下官网的解释:micro-zoe.github.io/micro-app/d…
新建一个layout页面作为parent component, 其他页面放在该路由下的children数组中
export default {
path: window.__MICRO_APP_BASE_ROUTE__ || '/pages',
component: () => import('@/views/layout.vue'),
name: 'layout',
children: [
// 其他的路由都写到这里
],
};
layout 页面内容如下:
<template>
<div
style="height: 100%; overflow: hidden"
class="light-wrap"
>
<div
style="height: 100%; overflow: hidden"
class="uu-layout-content"
>
<router-view class="child-view primary-u light" />
</div>
</div>
</template>
<script>
export default {
name: 'Layout',
};
</script>
至此,一个简单的微前端应用便已搭建完成。
遇到的问题(踩坑笔记):
1. 主应用如何控制子应用的权限?
子应用中的页面只是主应用中的一个web component组件,他们共享一个域名,在子应用获取到的缓存数据和主应用是一样的。所以像token、session的获取、权限的控制,子应用都应该和主应用框架保持一致。
2. 传递给micro-app的name如何确保唯一?
在主应用中,为了方便复用,微前端页面封装成了一个组件。根据micro框架要求,传入的name必须唯一。我这边是通过获取页面路由的方式,把路由作为name传给子应用
- 跳转微前端页面会出现 pushstate 为null 的报错信息
在主应用中,如果微前端页面被keep-alive组件 包裹,在进行跳转的时候会出现pushstate为null 的报错信息。解决方式是在路由中添加mgSite
meta: {
mgSite: 'iview2'//必填
}
然后在keep - alive 中添加isMicroApp字段用来判断当前页面是不是微前端。
<keep-alive v-if="!isMicroApp">
<router-view
></router-view>
</keep-alive>
<router-view
v-else
></router-view>
如果是微前端页面的话,就不要用keep-alive 包裹
这应该是micro-app 官方的一个bug。github上已经有人提了issue,预计会在1.0版本解决 issue: github.com/micro-zoe/m…
微前端页面如果需要缓存 可以这样添加。
4. 如何在子应用通过this.$router跳转主应用
micro-app针对路由跳转给了很多方法,但是如果你的微前端应用作为基建向外提供的话,就要考虑其他小伙伴的使用感受了。为了最大限度的减少小伙伴学习微前端的成本,针对子应用的路由的push方法做了一定改造
子应用:
VueRouter.prototype.push = function push(location, onResolve, onReject) {
window.microApp.getData().pushState(location);
}
主应用则需要通过data属性传值的方式来进行跳转,具体代码请看上面的_microApp/index.vue
总结
至此,屎壳郎任务圆满完成。