Micro-app+vue3微前端玩耍一二

Monday , 2022-10-10 10:53

最近需要在老的vue2运营后台中集成新的模块,看着越发臃肿的代码洒家陷入了沉思,这样下去工作的幸福感要去哪里寻找呢?况且没有typescript这可是个大问题,要升级改造老项目风险太大。

一般遇到这种问题,第一想到的是iframe加载的方式,但是这会存在几个问题。

  1. 浏览器的完全隔离,文件与数据共享很难被共享,一旦有主应用跟子应用有复杂交互的时候很难维护,不过一般不敏感的参数通过url也都能实现。
  2. 资源加载都要刷新,主应用的返回前进路由无法控制。

不过之前遇到这问题,我们通过在主项目中的静态文件目录public中加载子项目的dist文件,通过主应用的路由与iframe的路由做映射去访问,很好的优化了上诉问题,数据共享可以通过本地存储搞定,也可以写全局监听。

这个方案如果对项目的隔离部署有啥要求,并且对包的大小不敏感,可以说是很好处理方式,很传统,但是很稳当,可以让你安心下班!

BUT,日新月异的技术变化,微前端始终是一个绕不过去问题,SO,继续折腾吧。

目前比较主流的微前端框架有几个

阿里的乾坤 https://qiankun.umijs.org/zh/cookbook

腾讯的无界 https://wujie-micro.github.io/doc/

京东的micro-app https://zeroing.jd.com/micro-app/docs.html#/

single_spa https://single-spa.js.org/

最开始看的乾坤,但是其文档写的实在是,怎么说呢,惜字如金!而且对项目的侵入性很强,改的面目全非还不能跑起来,着实是恶心到人了。遂转战micro-app,顿感清新舒畅,接下来大概描述一下其使用过程。

这里用vue3做演示,默认你已经安装好了最新版的脚手架 vue-cli https://cli.vuejs.org/zh/guide/ ,创建一个mian_app的主项目,一个child_app_1的子项目

image.png

安装嵌入一条龙服务看文档即可 https://zeroing.jd.com/micro-app/docs.html#/zh-cn/start

这里把需要单独配置的文件贴出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main_app、child_app_1的配置文件调整,主要是子应用的跨域配置
//vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false, //关闭eslint检查
devServer: {
host: '0.0.0.0',
port: 8080,//子应用配置为3000
headers: {
'Access-Control-Allow-Origin': '*',//子应用必须开启跨域,不然无法访问
}
},
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//main_app的router.js
import { createRouter, createWebHistory } from "vue-router";
import Test from './view/Test.vue';
import Test2 from './view/Test2.vue';

const routes = [
{
path: "",
redirect:'/test'
},
{
path: '/test2', // vue-router@4.x path的写法为:'/my-page/:page*'
name: 'test2',
component: Test2,
},
{
path: '/test/:page*', // vue-router@4.x path的写法为:'/my-page/:page*'
name: 'test',
component: Test,
}
]

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});


//这里是为了处理返路由返回时候的问题

if (window.__MICRO_APP_ENVIRONMENT__) {
// 如果__MICRO_APP_BASE_ROUTE__为 `/基座应用基础路由/子应用基础路由/`,则应去掉`/基座应用基础路由`
// 如果对这句话不理解,可以参考案例:https://github.com/micro-zoe/micro-app-demo
const realBaseRoute = window.__MICRO_APP_BASE_ROUTE__

router.beforeEach(() => {
if (typeof window.history.state?.current === 'string') {
window.history.state.current = window.history.state.current.replace(new RegExp(realBaseRoute, 'g'), '')
}
})

router.afterEach(() => {
if (typeof window.history.state === 'object') {
window.history.state.current = realBaseRoute + (window.history.state.current || '')
}
})
}

export default router;
1
2
3
4
5
6
7
8
9
10
//main_app 入口文件调整
import { createApp } from 'vue';
import microApp from '@micro-zoe/micro-app';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app
.use(router)
.mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//main_app的Test.vue
<template>
<h1 class="red">主应用基座</h1>
<router-link to="/test2">test2,打开控制台看卸载监听</router-link>
<br/><br/>
<button @click="sendChildData">发送数据给子应用并监听</button>
<br/><br/>
<div class="childBox">
<micro-app
name="app1"
url="http://localhost:3000/"
baseroute="/test"
inline
@created='created'
@beforemount='beforemount'
@mounted='mounted'
@unmount='unmount'
@error='error'
:data="sendData"
@datachange='handleDataChange'
></micro-app>
</div>
</template>

<script setup>
import { ref } from "vue";
import micro from '@micro-zoe/micro-app';

const handleDataChange = (e)=>{
console.log('来自子应用的数据:', e.detail.data)
}

const sendChildData = ()=>{
micro.setData('app1', {type: '新的数据'})
}

const sendData = ref({
data:'基座给子应用的数据string'
})

const created = () => {
console.log('micro-app元素被创建');
}

const beforemount = () => {
console.log('即将被渲染');
}
const mounted = () => {
console.log('已经渲染完成');
}

const unmount = () => {
console.log('已经卸载');
}

const error = () => {
console.log('渲染出错');
}

</script>

<style>
.childBox {
border: 1px solid red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main_app的Test2.vue,主要用来测试跳转生命周期监听
<template>
<h1 class="red">其他页面</h1>
</template>

<script setup>
import { ref } from "vue";

</script>

<style>
.childBox {
border: 1px solid red;
}
</style>

分割线,子应用配置====================================================

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//chlld_app_1 入口文件
import './public-path'
import { createApp } from 'vue';
import microApp from '@micro-zoe/micro-app';
import App from './App.vue';
import router from './router';
import './assets/style.css';

microApp.start();

const app = createApp(App);
app
.use(router)
.use(microApp)
.mount('#app');


// 子应用卸载
window.addEventListener('unmount', function () {
console.log('我是子应用,我被卸载了')
})


if (window.__MICRO_APP_ENVIRONMENT__) {
console.error('我在微前端环境中,基座给我的名字是',__MICRO_APP_NAME__)
}


function dataListener (data) {
console.log('来自基座应用的数据', data);
setTimeout(() => {
console.log('数据被更新了',window.microApp.getData());
}, 1000);
}
console.log('window.microApp==>',window.microApp)
window.microApp.addDataListener(dataListener)
1
2
3
4
5
6
//子应用 './public-path' 路径判断文件
// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//router.js文件
import { createRouter, createWebHistory } from "vue-router";
import Test from './view/Test.vue';
import Home from './view/Home.vue';

const routes = [
{
path: "",
redirect:"/home"
},
{
path: "/home",
name: 'home',
component: Home,
},
{
// 👇 非严格匹配,/my-page/* 都指向 MyPage 页面
path: '/app1/:page*', // vue-router@4.x path的写法为:'/my-page/:page*'
name: 'app1',
component: Test,
}
]

const router = createRouter({
history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),
routes
});


export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// view/Home.vue 文件

<template>
<h1 class="red">子应用 Home</h1>
<br/><br/>
<button @click="callBase">向基座发送数据</button>
</template>

<script setup>
import { ref } from 'vue';
const data = window.microApp.getData();
console.log('我在子应用获取到基座数据',data);

const callBase = ()=>{
window.microApp.dispatch({type: '我是子应用发送过来的数据'});
}


</script>
1
2
3
4
5
6
7
8
9
10

// view/Test.vue文件,用于跳转测试
<template>
<h1 class="red">子应用 app1</h1>
</template>

<script setup>
import { ref } from 'vue';

</script>

准备就绪,启动基座与子应用服务,准备起飞=================================================

image.png

控制台可以看到通信信息

image.png