该词语是:(误解)误解的正确拼音如下:误:wù解:jiě释义:理解得不对,错误的理解。
写在最前:
本文面向对无界和微前端有一定了解的人群,不再对微前端的概念和无界的基础使用方式做解释说明
上搜wujie,那么大眼一看,好像全都是介绍的,并没有几个落地方案的分享。
正好我上个月把部门内三个业务系统用wujie整合了一下,记录成文章和大家分享一下。
(为什么不用qiankun?qiankun之前做了好多次了,这次想尝个鲜~)
笔者部门内有三个管理系统,技术栈分别是:
A: Vue2 + Webpack4 + ant-design-vue@1.7.8:该项目是部门内“司龄”最长的,从部门成立之初起,所有的业务都堆在里边。
B: Vue3 + Webpack5 + ant-desgin-vue@3.2.20:由于业务目标不清晰以及前端开发各自为战,部分需求被拆出来了一个单独的项目进行开发,但实际上然并卵。
C: Vue3 + Vite2 + ant-design-vue@3.2.20:为了响应领导“统一前端UI规范”和“低代码降本增效”的号召,这个项目应运而生,使用JSON Scheme渲染列表页 + 手写Form表单的形式开发需求。
没错,就是3个纯业务向的管理系统。
对接我们部门的大部分业务人员,日常都至少需要操作3个系统,甚至有些人还会用到别的部门的系统,甚至有的人习惯打开多个浏览器tab页来回切换对比同个页面的数据。
。
。
poor guy。
。
。
浏览器密密麻麻的全是tab页。
。
。
某天,发生了如下对话:
领导:业务部门老大说,系统间来回切换太麻烦了,有没有办法解决这个问题?我:有,微前端。
领导:之前XXX不是用qiankun做过吗,问题很多,不了了之了。
我:我看过他的代码,没有什么大问题,都是一些细节方面的小bug,而且还有别的微前端方案可以选择。
领导:行,你安排一下,尽快上线我:好的。
( 打工人被安排任务就是这么朴实,无华,且枯燥。
。
。
)
(此处省略万字长文对比分析qiankun、micro app、single-app...)
直接摆出站在个人角度以及团队技术、业务背景下选择无界的原因:
喜欢吃螃蟹:之前有过多次qiankun的落地经验,直接上qiankun,一点都不酷。
(第一次了解到无界是22年的10月份左右,彼时的无界还在beta版,想尝尝鲜。
况且就算使用无界出了岔子,也有信心能cover住)子应用改造,侵入程度低:就像文档中宣传的那样,我用公司的项目跑demo,除去登录态的因素外,基本可以说是0改动接入,当时脑海中只有2个字----牛X!(当然,仅仅这样接入,离上生产的标准还相距甚远;而且最后我还是选择了类似qiankun根据宿主应用动态选择layout的布局方案,改造成本也可以说是不算低了,这个暂且按下不表)方便独立开发、部署:与第2点相似但又不同:现有的项目有独立的域名、部署方案、且在生产环境已经稳定运行,在保留这些基础的前提下,无界的iframe方案算是最理想的出路(另外也有一点私心,如果生产环境的无界挂了,业务人员可以直接使用老的域名访问独立的子应用进行业务操作,毕竟出了生产事故是要通报批评的)
综上所述,确实没经过太多深思熟虑,想用就用,干就完了
下面,就是在我接入文章开头提到的3个系统后,总结出来的大致接入步骤:
准备主应用,在接入第一个系统之前,不出意外的要先准备宿主应用。
子系统登录态管理根据宿主环境,选择layout方案安装wujieEventBus(基于无界去中心化的通信系统做的二次封装)子应用afterMount生命周期子系统网络请求管理UI组件定位修复公共状态提升
一个比较常规、纯净的管理系统,没有过多的封装,因为宿主应用本身,也不需要什么内容。
技术栈为Vue3 + Vite2 + ant-design-vue@3.2.20(没错,和系统C的技术栈一致,主打的就是一个偷懒),放张目录结构大家就明白了,没什么特殊的,有些细节后边会提到。
简单来说,对于一个子应用,无论你是基于JWT还是Cookie的用户鉴权方案,在他单独运行时发生登陆态失效的情况,是要被redirect到自己的Login页面去;而当集成到了无界中运行的时候,登录态失效则应该被redirect到主应用的Login页面。
一般情况下,有两个地方需要做处理:
http响应拦截,以axios为例:
if (response.status === 401) { if (window.__POWERED_BY_WUJIE__) { wujieEventBus.$emit(&34;, APP_NAME_IN_WUJIE); } else { message.error(&34;); router.replace(&34;); } }
window.__POWERED_BY_WUJIE__是无界注入到子应用window当中的一个全局变量。
而wujieEventBus是我对无界自带的去中心化通信方式eventBus的封装,具体内容放在第四点展开讲,这里只需要知道,是通知主应用“我”登录失效了,并且附上“我”在主应用中的身份标识(对应组件方式使用无界的<WujieVue />所需的name属性)
路由守卫:可根据你的需要更改路由钩子,这里以beForeEach为例:
router.beforeEach((to, from, next) => { if(validToken()) { // some your logic ... next(); }else { wujieEventBus.$emit(&34;, APP_NAME_IN_WUJIE); } }
当然,通过路由守卫拦截下登录态失效的情况可能很少很少,但操作和上面是一样的:通知主应用“我”登录失效了,并且附上“我”在主应用中的身份标识
如果你的主应用布局是打算这样:
子应用甚至不用切换layout方案,在下方content区域中保留子应用所有的模块;上方的Menu区作为一个应用级的切换菜单。
但如果你的主应用是打算像这样常规布局:
想实现应用级的切换,大体上有三种思路:
主应用不设任何layout模块:即Header、Menu、Content全都是子应用的模块。
那么就需要所有子应用都是这种布局,且每个子应用的Menu菜单都必须是所有应用菜单的集合,当切换到非自身的路由时,与宿主通信进行应用切换。
与1相同,Header、Menu、Content全都是子应用的模块,但Menu仍是自己的菜单。
你问我怎么切换应用?加个position: fixed的悬浮球呗(或类似的可折叠菜单)。
通过hover悬浮球,展开/折叠菜单,点击进行应用切换。
说实话,这方案我自己都不相信有人会用。
而第三个,也就是我选择的方案:主应用设有Header和Menu,剔除所有子应用的Header和Menu,只保留子应用的Content模块接入进来。
熟悉吗?就是接qiankun那套。
大概长这样:
<template v-if=&34;> <Menu /> <Layout> <Header /> <Layout> <keep-alive> <router-view /> </keep-alive> </Layout> </Layout> </template> <template v-else> <keep-alive> <router-view /> </keep-alive> </template> // const isInWujieContainer = window.__POWERED_BY_WUJIE__
为什么选择方案3,在我看来:Menu维护在主应用中,相比于对每个子应用的Menu进行侵入式改造,开发成本和维护成本都更小。
Header维护在主应用中,可以方便的管理路由栈(面包屑、tab页签,这里多提一下,我的子应用接入方式是保活+sync路由同步)
既然Menu维护在了主应用中,那么问题来了:点击了Menu中的某个菜单,怎么通知子应用跳转到对应的路由?
我们都知道,当无界开启了url sync同步的时候,主应用、子应用的url变化规则是:子应用url发生变化时,子应用的iframe会与主应用进行通信,主应用同步更新url;当页面刷新时,子应用iframe会从主应用的url中读取路由信息,保证子应用路由状态不丢失。
但是并没有一种规则是主应用主动发起改变url、并且子应用能同步更新路由的方案。
我的做法其实也很简单,点击主应用Menu中的菜单时,通过wujieEventBus进行广播,对应的子应用收到消息时,切换路由:
// 主应用中点击Menu菜单 export const openChildRoute = ( _router: RouterObj, app: AppCollection, ) => { // 通知子应用路由已改变,registerMountedQueue可以理解为给子应用注册一个mounted后需要立即执行的事件,防止出现跳转到一个还未初始化的子应用时,$emit miss的问题。
EventBus.$registerMountedQueue( app, &34;, { path: _router.path, app } ); // 更新主应用自己的url和tab页签 router.push(fullPath); store.commit(&34;, { fullPath, name: _router?.name || &34;, title: _router?.name, }); setActiveKey(fullPath); };
// 子应用收到消息 wujieEventBus.$on(&34;, function ({ path, query, app }) { if (app !== APP_NAME_IN_WUJIE) return; router.push({ path, query }); });
并且CHANGE_ROUTE这个事件可以是双向的:可以由主应用主动发起,通知子应用改变路由;也可以由子应用主动发起,通知主应用改变url和tab页签的显示状态。
之所以这样设计,是因为我们的系统中存在一种特殊的路由页面,他不存在于Menu菜单中,是必须通过点击页面中的指定按钮才能进入。
所以对于这类页面,必须是由子应用主动发起的。
无界提供了一套去中心化的通信方案,去中心化的优点显而易见:
不关心发送方和接收方是谁,可以是不同应用之间通信,可以是一个应用内不同路由通信,可以是一个路由内不同组件通信可以很方便的一对多通信
但同时也有一个致命的缺点:通信成功的前提是建立在通信双方都online的情况下。
假设这样一个场景:用户从站外的某个带参链接进入系统,参数的目的是告诉系统要重定向到指定子应用的指定路由,甚至具体要打开某个弹框。
正常情况下,主应用判断url参数做跳转的逻辑不管放在哪里,都存在目标子应用未加载完成的可能性。
(如果你说每个子应用component的afterMount事件里都写一遍,fine,你赢了)
这个时候,只需要对无界的eventBus稍作改动,即可满足需求:
import WujieVue from &34;; import { AppCollection } from &34;; import store from &39;; const { bus } = WujieVue; type EventList = &34; | &34; | &34;; // 一些事件类型涉及到公司业务,这里省去了 type EventBusInstance = { $emit: (e: EventList, params: Record<string, any>) => void; $on: (e: EventList, fn: (...args: any[]) => void) => void; $registerMountedQueue: ( app: AppCollection, e: EventList, params: Record<string, any> ) => void; // 将事件注册到子应用mount成功的的事件队列中 $cleanMountedQueue: (app: AppCollection) => void; // 清空子应用mount事件队列 }; type Queue = { [app in AppCollection]?: any[]; }; let instance: EventBusInstance | undefined = undefined; export default () => { const queue: Queue = {}; if (!instance) { instance = { $emit: (event, params) => bus.$emit(event, params), $on: (event, fn) => bus.$on(event, fn), $registerMountedQueue: (app, event, params) => { const isMounted = store.state.globalState.appMounted[app]; // store中存储了子应用是否mount完成的状态 const fn = () => bus.$emit(event, params); // 子应用已挂载完成可以直接通信 if (isMounted) return fn(); if (queue[app] && queue[app]!.length) { queue[app]!.push(fn); } else { queue[app] = [fn]; } }, $cleanMountedQueue: (app) => { while (queue[app] && queue[app]!.length) { const fn = queue[app]!.shift(); fn(); } }, }; } return instance; };
为每个子应用都维护一个事件队列,主应用通过$registerMountedQueue注册事件时,若对应子应用已经mount完成,则直接emit进行通信;若子应用没有mount完成,则将注册的事件推入队列中。
子应用afterMount钩子中调用$cleanMountedQueue,清空属于自己的事件队列。
目前根据业务需要,只做了这一点封装,后续有可能会继续补充。
当然前边提到的这个场景,肯定还有许多不同的解决方案,根据自己的项目因地制宜才是最重要的。
上边第4点已经提到过,子应用afterMount钩子中要做两件事情:
store中保存自己mount完成的状态。
调用$cleanMountedQueue清空自己的事件队列。
网络请求管理,主要解决的是跨域问题,分两种:
调用后端服务跨域 如果你的用户鉴权是基于cookie的,那最方便的就是使用无界推荐的方法:将主应用的fetch自定义改写后传给子应用。
如果你的用户鉴权是基于JWT或者你使用了其他的http请求库,赶快买上两杯咖啡贿赂一下运维大佬,给子应用对应的服务配置下Response Header,支持主应用域名的跨域资源共享。
但是要切记,生产环境不要使用Access-Control-Allow-Origin: *。
请求子应用静态资源跨域
刚才为啥要让买两杯咖啡,因为一杯是改后端服务支持跨域,还有一杯是改前端静态资源服务器(比如Nginx)支持跨域。
至此,你(wo)的无界微前端方案已经落地大半了,不出意外的话,除了个别地方的样式比较古怪,业务流程已经没啥大问题了,下面的工作就是各个页面点一点,修一修奇怪的样式问题。
无界官方针对element-plus冒泡系列组件弹出位置不正确的解决方案是给子应用的body添加position: relative,但我这边使用ant-design-vue@1.7.8的项目并不是弹出位置不正确,而是弹出方向不对,只能暂时通过调整组件位置+修改placement的方式见一个改一个。
我这边还有一些使用左弹出的drawer组件也会有问题,起始位置并不是屏幕最左边,而是content区域的最左边。
不知是否是无界的bug,drawer有个fixed定位的包裹容器,按理来说,创建这个包裹容器的时候会使用webcomponent代理的appendChild方法,可以突破iframe的区域限制,但通过审查元素发现,这个position: fixed; left: 0的元素,开始位置还是iframe的左侧。
。
。
导致drawerposition: absolute的主体开始位置也只能是iframe的左侧。
但又不是所有的左弹出drawer都有这个问题,很神奇。
。
。
没办法,只好把这些有问题的暂且改为右弹出。
。
。
有解决方案的朋友也可以交流一下。
。
。
其实从这里开始,就属于优化的范畴了,目前只做了这一趴,后续有其他优化会持续补充。
做公共状态提升的原因,简单来讲就是:除了登录用户的信息以外,我们不同系统中也有着很多相同的枚举数据,这些数据本身也是从同样的接口中读的,存在vuex/pinia中。
所以当一个系统独立运行时,他数据获取的逻辑不变;当作为子应用接入了微前端体系中时,只需要从主应用中等待数据同步,不需要自己再调接口去取。
// 主应用 export default () => { const duties = [ // some http request callbacks ]; duties.forEach(async (d) => { const { action, type, commition } = d; const data = await action(); store.commit(commition, data); bus.$registerMountedQueue( &39;, // 业务系统name标识 &34;, { type, data: toRaw(data), } ); }); };
// 子应用 const state = { // a vuex state } const mutations = { // a vuex mutation } const actions = { // a vuex action } if(window.__POWERED_BY_WUJIE__){ wujieEventBus.$on(&34;, ({ type, data }) => { const [updateFn, stateKey, ...restPath] = type; let config = state[stateKey]; if (restPath && restPath.length) { set(config, restPath, data); // lodash set } else { config = data; } mutations[updateFn](state, config); }); }else { // old logic, init all states by actions }
这篇文章从开篇到写下结语,中间经历了一整个星期。
后半部分整体写的比较仓促,可能有些地方和提笔之初的设想有所出入;并且许多的细节之处涉及到公司业务也没有做过多的说明。
有不明白的地方、或者有想交流的同学也可以留言,我会尽可能的做答复。
另外做个说明,其实最开始的时候文章标题叫【无界(wujie-micro)微前端落地方案分享】,后来才改成现在这个名字,原因有二:
这并不是一套完整的落地方案,只是我对我落地整个过程中,值得记录、分享的一些点的总结原先的名字有种让人一看就不想点进来的感觉
行吧,第一版先到这里,欢迎真诚交流,但如果你来抬杠?阿,对对对~ 你说的都对~
复制链接攻略资讯文章为拓城游所有,未经允许不得转载。
相关资讯MORE +
监狱高压电影在线观看完整免费高清原声满天星原名(菲律宾盐色满天星恐怖片震撼来袭!#因为一个片段看了整部剧)
网友2024-07-07 20:25
古墓丽影满天星版英文名字(古墓丽影:暗影 – 终极版Shadow of the Tomb Raider: Ultimate Edition)
网友2024-06-18 15:23
姐妹牙医是什么电视剧(53岁的美牙医,跟女儿同框像姐妹,看看她的“冻龄”套路)
网友2024-04-13 18:04
善良的小姨子讲的什么(演艺生涯及酸甜苦辣)
网友2024-02-29 18:37
姐妹牙医又名叫什么(53岁的美牙医,跟女儿同框像姐妹,看看她的“冻龄”套路)
网友2024-06-24 13:09
好游安利换一换