浅入深出的微前端MicroApp
前言 本文是由最近做的一个项目有感而发,因为之前做了一些技术栈的统一,为了用ant Design的pro-table,PC统一使用react,但是我们有一些老的项目是vue的,本次新页面较多,老页面的改动较少,除此之外老项目想换菜单,因此我们想借助本次机会用react开发,经过了几番思考,发现本次很适合用微前端来完成本次需求,最终决定用react搭建一个基座(主应用),将原来的vue项目接入到基座,这样我们不仅实现了新页面react开发,而且老项目也能和新项目融合一起。
一、微前端的概念
二、何时使用微前端
一个非常庞大的项目,越来越大,后续难以维护。 在一些大厂,经常会有跨部门和跨团队协作开发项目,这样会导致团队效率降低和沟通成本加大,这时我们可以使用微前端,每个团队或者每个部门单独维护自己的项目,我们只需要一个主项目来把分散的子项目汇集到一起即可。 一个非常老旧的项目,开发效率低,但是一时半会又不能全部重构,这时我们就可以新创建一个新技术新项目的基座,把老项目的页面接入到新项目里面,后面新需求都在新项目里面开发就好,不用再动老项目。 想独立部署每一个单页面应用。 改善初始化加载时间,延迟加载代码。 基于多页的子应用缺乏管理,规范/标准不统一,无法统一控制视觉呈现、共享功能和依赖,造成重复工作。
三、如何创建微前端基座
【 微前端框架选型 】
现有框架
single-spa是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。 qiankun 基于 single-spa 封装的微前端框架。 MicroApp 京东出品,一款基于WebComponent的思想,轻量、高效、功能强大的微前端框架。
MicroApp优势
使用起来成本最低,将所有的页面封装到一个类WebComponent组件中,从而实现在主应用基座中嵌入一行代码即可渲染一个微前端应用。 不需要像 single-spa 和 qiankun 一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。 提供了 js沙箱、样式隔离、元素隔离、预加载、数据通信、静态资源补全等一系列完善的功能。 没有任何依赖,这赋予它小巧的体积和更高的扩展性。 为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何技术框架中都可以正常运行。
MicroAap概念图
以后台管理系统为例
最外层是基座,基座是微前端应用集成的一个重要平台,同时也肩负着管理公共资源、依赖、规范的责任,主要有以下职责:
(1) 子应用集成,给子应用提供渲染容器
(2) 权限管理
(3) 会话管理
(4) 路由、菜单管理
(5) 主题管理
(6) 共享依赖
(7) 多语言管理(最重要的一点)
content里面可以任意放不同技术的子应用,我们只需要开发一个主应用(主应用也可以自由选择语言,目前支持react、vue、vite、angular、next.js、nuxt.js),将一些分散的应用接进来,主应用还可以通过控制权限,让不同的账号看到的菜单不一样,即看到不同系统的页面,通过同一个地址访问到不同的子应用。
以react基座为例
npm i @micro-zoe/micro-app --save"@micro-zoe/micro-app": "^1.0.0-alpha.7"import microApp from '@micro-zoe/micro-app'microApp.start()
分配一个子应用路由 {path: '/yp',name: 'yp',linkHidden: true,linkDisable: true,breadcrumbClose: true,component: '@/pages/yp-app',}子应用的文件 在pages文件下创建一个yp-app(子应用的文件) // name(必传):应用名称 // url(必传):应用地址,会被自动补全为http://localhost:3000/index.html import React from 'react';import config from '@/config';// /** @jsxRuntime classic */// /** @jsx jsxCustomEvent */// import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event';export default (): React.ReactElement => {// 子应用点击了面包屑的回到首页const onDispathChild = (e: any) => {const { isBackHome } = e.detail.data;if (isBackHome) window.location.href = '/';};return (<><micro-app name="yp" url={config?.yp} onDataChange={onDispathChild} /></>);};说明:onDataChange方法是子应用和主应用的信息通讯方法。micro-app 在 window 下面挂载了一个全局的对象,我们只需要去触发它提供的方法,完成主子之间的通信即可,不管是交互逻辑还是数据传递逻辑,就都通了。
主应用成功引入子应用(子应用是VUE项目)
到目前为止如果我们的项目不存在跨域问题,子应用就已成功接入了主应用,项目左侧是主应用,中间模块是子应用,里面包含子应用的整个模块菜单和列表,考虑到菜单统一放到主应用(基座)方便管理,我们需要把子应用的页面的菜单以及一些 不必要的东西删除,然后我们把子项目一些公共样式公共布局等都统一调整下即可,最终我们会得到一个主应用+子应用页面最终页面,恭喜你到这里你就成功接入了第一个子应用,第二个应用。。。。等应用按照同样步骤。
接入完成不代表你子应用里面所有的模块都能用了,此时我们还需要检查导出和导入的接口是获取域名里面的还是单独定义的,如果获取域名里面的前缀,此时你的导入导出不能正常使用,我们需要重新给导入导出单独定义,比如在子应用创建一个单😘😉独的host.js文件,引用根据环境区分到处的域名前缀。
2. 路由跳转
通过主应用的菜单跳转到对应子应用的路由
//config.tslet config = {yp: 'https://xxx.xxx.com:7000/gw',//本地环境子应用的路由前缀};const isEnvPro = process.env.NODE_ENV === 'production';if (isEnvPro) {config = {yp: 'https://xxx.xxx.com/gw',//预发环境环境子应用的路由前缀};}export default config;//以上是config.ts文件的全部-----end//菜单点击事件里面的内容history.push('/yp'); //切换到子应用setTimeout(() => {microApp.router.push({name: 'yp',//和子应用的name要保持一致,为了匹配到对应子应用的路由path: `${config.yp}${item.url}`,}); //跳转子应用的路由,其中config是上面的配置文件,根据不同的环境取对应环境的子应用,item是当前点击的菜单路径信息}, 500);
devServer: {headers: {'Access-Control-Allow-Origin': '*',}},
这个有相对应的文档,我们根据子应用的语言设置不同的跨域信息。
(2) 如果你是接口跨域。
那么你就需要找后端设置允许我们前端跨域的代码了,Java为例:
@Overridepublic void init(FilterConfig filterConfig) {this.origins = Lists.newArrayList("http://localhost:8088","https://xx.51epei.com","https://xx.yunxiu.com","https://dev.51epei.com:8088","https://xx.51epei.com:7000");}
这个是基础配置,我们还可以改成同域名下的所有都允许跨域,最好不要设置成'*',这样很不安全。
4. 代理配置
如果你遇见主应用本地访问不到子应用本地,访问的一直是预发或者线上,这时候你需要首先考虑你的代理是否配对,比如我的一个子项目,如图所示:
proxy: (() => {return {// 本地访问预发'/avoid': {target: 'https://pai.51epei.com',changeOrigin: true,bypass: (req) => {if(req.headers.accept.indexOf('html') !== -1 ) {return '/index'}},}}})()
最初本地代理是路由里面包含'/'就代理到预发上,正常单独访问子应用的链接,可以正常访问本地代理预发的接口,但是放到主应用里面就不可以了,最后给代理改成了整个项目公共部分/avoid,解决了此问题,不一定你的项目是因为这个,但是我觉得可以从代理入手查找问题。
5. 数据通讯
micro-app提供了一套灵活的数据通信机制,方便基座应用和子应用之间的数据传输。
正常情况下,基座应用和子应用之间的通信是绑定的,基座应用只能向指定的子应用发送数据,子应用只能向基座发送数据,这种方式可以有效地避免数据污染,防止多个子应用之间相互影响。
同时我们也提供了全局通信,方便跨应用之间的数据通信。
子应用获取来自基座应用的数据,以及基座应用向子应用发送数据,基座应用获取来自子应用的数据,全局数据通讯,具体的我这里都不细说了,文档里都有详细介绍,详细参考https://zeroing.jd.com/docs.html#/zh-cn/data