当前位置: 首页 > Web前端 > HTML

微前端转型初探

时间:2023-04-02 15:46:29 HTML

写这篇文章前一个多月,我还不知道什么是微前端,大概字面意思就是一个比较小的前端项目。本坑因为工作需要开始实践。改造一个运行多年,前端用jsp编写的服务平台项目(以下简称平台)。改造它就是改造它的前端架构。之所以改造它,是因为很多人的反馈比较多。其页面加载渲染困难,页面切换后首屏等待时间长。交互体验的舒适度必然下降,尤其是在老式电脑前。平台业务比较多,所以组长希望前端框架组能把平台的前端部分分离出来。最好用vue,目前满街都是,可以根据每个一级菜单分成几个前端子项。用户访问仍然是集成的。同时,此次改造实施过程无需重做,而是将整个500多页从局部开始,逐步兼容,新旧同时运行,直至整体替换。(ps:没有重做?这……科学吗?)你看,哪里慢了?其实我大概知道哪里慢,但是不知道哪一部分最慢。与其他一贯的同类管理平台布局无异。左侧导航栏,顶部顶部栏,右侧内容栏。整个页面是一个index.jsp。上面说的contentbar是一个iframe,通过切换src来切换页面。更多业务导致更多页面,更多页面导致更多负载。另外资源加载的管理长期没有做好,导致需要加载大量的js、css或者多次加载同一个文件来渲染一个页面。平台大量的配置页面都是通过easyui生成的。使用数据来创建整个页面的dom节点也拖慢了内容完全渲染的时间。我们可以通过谷歌浏览器性能最终追踪到系统的哪些方面、哪些方法有哪些延迟。得出的结论是:1、项目资源管理混乱导致大量资源请求。2.easyui和项目中很多dom操作带来大量的重排和重绘。3、埋点、插件使用不当等。什么是微前端?微前端的概念来源于之前流行的微服务。它的来源主要来自这篇文章。微服务体系使得后台服务架构能够更好的避免体积越来越臃肿带来的性能下降。根据业务合理拆分成单个服务,尽量避免一个子服务的优势影响整个项目的运行,有效隔离。那么,前端有没有同样的需求呢?答案是肯定的。在前端技术日益更新的今天,已经可以将每个页面的每一个小元素打包成一个组件库和功能包,可以在多个项目中引入使用。另外,我们不再需要使用难受的iframe来聚合不同的项目,而是一个一个的导出web组件,只需要导入到页面中就可以使用了。将各个子项目打包成一个web组件,聚合到入口项目中。这可能就是微前端现在的样子。如果大型项目有以下特点,可以在这些项目中使用微前端:1.大型项目统一入口,子项目页面需要切换不刷新,但是每个子项目在业务和开发团队。2.项目过大,打包、运行、部署效率会大幅下降。这时候就希望按照业务拆分打包部署。计划与实施回到本文开头,一开始面对这样的需求我还是有想辞职的冲动,因为觉得这个需求有点不切实际,而真正实施修改也需要一个过程。不过静下心来想一想,搜索,翻看当前项目的前端架构,似乎有一些线索可以说明需求的可行性。因为项目的最终目标是把整个jsp页面改成vue来写。而这个需求是一个逐步替换的过程,所以在改造的过程中,同时需要保证项目对jsp页面的兼容。我们继续使用原来的iframe,从而将jsp整合到整个微前端框架中,改造后的微不再需要iframe。我们的开发团队分为框架组和各个业务组。每个事业群3到8人,大部分有后端出身,主要做后端开发。框架集有前端和后端。以满足庞大的业务发展需要。后端人员大多需要使用jsp、js等前端技术进行开发。为了降低他们的前端开发门槛,框架组会将easyui组件打包提供给业务组。因此,如前所述,后台人员通过框架组提供的数据和组件完成页面的开发。从某种角度来说,数据配置的页面对于后续的改造工作是有帮助的,因为大部分的页面都可以同时改写。我们大致对整个项目进行了分类。1.Portal项目:该项目是整个微前端项目的入口。它包含加载程序,用于加载每个项目模块。也嵌入到子项目中,这样单独运行子项目和门户项目的接口需求是一样的。2、权限项目:该项目包括菜单组件、登录页面、顶栏组件、权限控制等,在任何环境下都必须先加载,为子项目模块挂载提供锚点。3.公共项目:该项目包含公共业务组件。比如打包后的页面,对于不熟悉Vue项目的后端人员可以直接使用,更加友好。4、业务项目:指业务组各模块开发的前端项目。什么样的业务划分成一个项目,是由产品和技术人员决定的。与门户项目相比,业务项目相当于它的子项目。前端框架组必须为业务项目提供一套统一的前端模板,在确认新的子项目后,可以快速添加到整个项目中进行开发部署,这个过程不能影响部署和运行其他项目。除了上述浮出水面的方案外,在改造过程中还会遇到一些细节问题。不过在大方向、框架构成、前端架构等方面,细节问题需要耐心和时间来解决。根据上面的分类,这个坑单独说明,大致说明一下它的实现。这里结合了很多前辈的经验,在文末谢谢大家。Portal:Portal项目是整个项目部署的入口,其核心来自于single-spa,会被集成到整个项目结构中的各个子项目中。集成的方式很粗糙简单,就是外部加载。Portal负责根据不同的环境对应相应的组件和应用。它还会安装和卸载每个应用程序。它负责single-spa中应用程序的生命周期。比如在集成模式下,根据环境和路由加载相应的app,子项目运行时只加载不同业务的公共组件和app。那么门户是如何加载的呢?入口维护了一个json,里面包含了各个子项目的index.html信息,通过匹配index.html中的src和link来加载各种资源。module.exports={common:{webName:'common',globalVarName:'mfe:common',componentsTarget:'/common/release/components/web.html',resourcePatterns:['/components.[0-9a-z]{8}.js/g'],loadType:'before'},permission:{webName:'permission',globalVarName:'mfe:permission',//URL匹配方式matchUrlHash:'',//微前端地址componentsTarget:'/permission/release/components/web.html',webTarget:'/permission/release/web/web.html',//资源匹配模式resourcePatterns:['/common.[0-9a-z]{8}.css/g','/store.[0-9a-z]{8}.js/g','/publicPath.[0-9a-z]{8}.js/g','/singleSpaEntry.[0-9a-z]{8}.js/g','/components.[0-9a-z]{8}.js/g'],//项目启动前是否加载,before对于提前加载,after是hash变化后加载loadType:'before'},app4vue:{webName:'repair-order',globalVarName:'mfe:app4vue',matchUrlHash:'/layout/repair-order',webTarget:'/app4/release/web/web.html',resourcePatterns:['/common.[0-9a-z]{8}.css/g','/store.[0-9a-z]{8}.js/g','/单SpaEntry.[0-9a-z]{8}.js/g'],loadType:'after'}}asyncgatherResource(){constself=this//constspaEntry='portal'constweb=self._webName//如果是微前端聚合模式if(window._IS_SIGLE_PORTAL){if(web!=='mfe-permission'){awaitself.loadComponents(micros.common)awaitself.loadApp(micros.permission)}}else{if(web==='mfe-permission'){awaitself.loadComponents(micros.common)}else{if(web!=='mfe-common'){awaitself.loadComponents(micros.common)}等待自己。loadApp(micros.permission)}}//returnnewPromise(resolve=>resolve('loader:allFinish!'))}permission:permission负责登录页面,布局中的菜单栏,以及所有子itemsinthetopbarapp必须挂载在权限项目中的显示块中。也就是说permssion会提供锚点供子项目挂载。因此,权限负责路由的控制。这里的路由包括整体路由和应用内路由切换。如果app切换的路由控制涉及到singer-spa,则app的切换会触发single-spa:routing-event事件,入口监听unmounted和mountedapp的事件。如果应用内部路由切换,需要触发应用内部路由切换。这个坑尝试监控权限的hash。由于vue新版本,实际上无法监控hash,所以没办法监控hash。这似乎干扰了子项目的代码。如果谁有好的方法,可以在评论区发表你的看法。业务项目业务项目的独立运行只会出现在开发模式中,不会在生产或测试环境中独立运行。集合成环境下输出成三个星期,提供给single-spaexportvarglobal={};exportconstbootstrap=()=>{returnPromise.resolve();}exportfunctionmount(props){Vue.mixin({data:function(){return{props}}})returnPromise.resolve().then(()=>{createDomElement();global.instance=newVue({el:'#app4',router,render:h=>h(App)})})}exportfunctionunmount(){returnPromise.resolve().then(()=>{global.instance.$destroy();global.instance.$el.innerHTML='';deleteglobal.instance})}functioncreateDomElement(){//确保有一个div供我们渲染到letnode=document.getElementById('main-内容');让el=document.getElementById('app4');如果(!el){el=document.createElement('div');el.id='app4';节点.appendChild(el);}returnel;}开发模式独立运行lineconstinit=async()=>{//启动single-spaconstloader=newLoader(process.env)awaitloader.startSingleSpa()Vue.mixin({data(){return{loader}}})Vue.config.productionTip=false;//权限渲染然后自己挂载window.addEventListener('single-spa:main-content-mount',evt=>{if(!window.vim){window.vm=newVue({el:loader.createHookEle('app4'),//自己挂载//store,router,render:h=>h(App)})}})}init()commoncommon类似插件的封装,就不去了进入细节import'./styles/vars.scss'从'./components/button'导入MButtonconstcomponents=[MButton];constinstall=function(Vue){if(install.installed)return;components.map(component=>{Vue.use(component);});};//全局引用可自动安装if(typeofwindow!=='undefined'&&window.Vue){install(window.Vue);}exportdefault{install,MButton}几个主要源代码采用外部链接格式Single-spasingle-spa是如何聚合各个独立的vueapp的?这个坑尝试理解一下。Single-spa将应用程序聚合为三个循环,bootstrap挂载和卸载。这三个循环需要自己配置和重写。其实single-spa还有其他的循环。不需要重写,就是对于single-spa来说,app里面只有这三个东西需要特别注意,app的卸载和加载。其他一切都取决于应用程序本身。mounted、unmonuted和vueapp独立运行mounted和destroyed本质上没有区别,只是single-spa做了一层proxy。代理完成应用的挂载和销毁。single-spa里面还保存了一个数组,负责维护内部注册的app。注册完成后,代理完成应用的挂载。卸载后销毁。总结如果各位客官也遇到了这种问题,不保证一定会用到这种修改方法。之所以在这个坑里实践,很大的原因也是想用自己的方法初步探索微前端实践方法的可行性。这种大跨度的变化,会带来很多不可预测的底层冲突,前后端冲突。第二,这种大跨度的变化几乎等同于重构。第三,微前端方案也有自身的局限性,比如library版本的管理,app样式的隔离没做好等等。还是要根据实际来衡量。对于jsp等过于老旧的系统,可以先尝试jsp转html,提高前端性能,再考虑全面改版。估计是很多地方没做好,代码或者信息有误。欢迎指导。致谢单spa官网微前端实践前端微服务解决方案