最近需要在老的vue2运营后台中集成新的模块,看着越发臃肿的代码洒家陷入了沉思,这样下去工作的幸福感要去哪里寻找呢?况且没有typescript这可是个大问题,要升级改造老项目风险太大。
一般遇到这种问题,第一想到的是iframe加载的方式,但是这会存在几个问题。
- 浏览器的完全隔离,文件与数据共享很难被共享,一旦有主应用跟子应用有复杂交互的时候很难维护,不过一般不敏感的参数通过url也都能实现。
- 资源加载都要刷新,主应用的返回前进路由无法控制。
不过之前遇到这问题,我们通过在主项目中的静态文件目录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的子项目
安装嵌入一条龙服务看文档即可 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
|
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave: false, devServer: { host: '0.0.0.0', port: 8080, 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
| 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', name: 'test2', component: Test2, }, { path: '/test/:page*', name: 'test', component: Test, } ]
const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes });
if (window.__MICRO_APP_ENVIRONMENT__) { 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
| 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
| <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: 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
| <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
| 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
|
if (window.__MICRO_APP_ENVIRONMENT__) { __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
| 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, }, { path: '/app1/: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
|
<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
|
<template> <h1 class="red">子应用 app1</h1> </template>
<script setup> import { ref } from 'vue';
</script>
|
准备就绪,启动基座与子应用服务,准备起飞=================================================
控制台可以看到通信信息