当前位置: 首页 > 科技观察

微前端解决方案乾坤只是一个更完整的Single-Spa

时间:2023-03-15 16:15:21 科技观察

一个前端应用程序可以独立运行,也可以作为一个模块集成到另一个应用程序中。这种架构称为微前端。可以解决前端领域的一些具体问题:在中后台系统中,有一些其他技术栈开发的历史模块,但希望能集成到入口中。sass类的前端应用业务比较复杂,可能有很多模块。希望可以拆分成多个应用独立维护,也可以集成在一起。跨技术栈的应用集成和将大项目拆分成独立的小项目是微前端解决的典型问题。微前端的实现方案有很多种,比较流行的有single-spa和对其进行封装的qiankun。今天我们就来了解一下这两种微前端的实现方案:single-spa微前端的基本需求是在url变化时加载卸载相应的子应用,single-spa实现了这个功能。它的作用是注册微应用,监听URL变化,然后激活对应的微应用:注册微应用是这样的:import{registerApplication}from'single-spa';registerApplication({name:'app',app:()=>{loadScripts('./chunk-a.js');loadScripts('./chunk-b.js');returnloadScripts('./entry.js')}activeWhen:'/appName'})singleSpa.start()指定url为时如何加载子应用。它需要子应用的入口文件导出bootstrap、mount、unmount的生命周期函数,即加载完成后、挂载前、卸载前执行的逻辑。比如react的子应用:importReactfrom'react';从'react-dom'导入ReactDOM;importAppfrom'./index.tsx'exportconstbootstrap=()=>{}exportconstmount=()=>{ReactDOM.render(,document.getElementById('root'));}exportconstunmount=()=>{}这部分逻辑也可以简化,single-spa提供了react,vue,angular等集成包直接使用:importReactfrom'react';importReactDOMfrom'react-dom';从'./index.tsx'导入应用程序;从'single-spa-react'导入singleSpaReact;constreactLifecycles=singleSpaReact({React,ReactDOM,rootComponent:App});exportconstbootstrap=reactLifecycles。引导程序;导出常量装载=reactLifecycles.mount;导出常量卸载=reactLifecycles.unmount;这是微前端的基本需求,可以在url中改变,加载卸载相应的子应用。但是,singlespa做的比较简单,并不完美。比如加载一个微应用,需要指定加载哪些js和css。如果子应用程序的封装逻辑发生变化,则必须相应地进行更改。一个页面可能有多个子应用,会不会有样式冲突,JS冲突?如何处理多个子应用程序之间的通信?当你需要使用sigle-spa时,你得自己解决。所以single-spa还不够完善,所以qiankun出来了:qiankunqiankun并不是一个新的微前端框架,它只是解决了一些single-spa没有解决的问题,是一个基于single-的更完整的微前端解决方案温泉。它解决了什么问题?我们一一来看:使用single-spa加载子应用的资源时,在注册时指定如何加载子应用:import{registerApplication}from'single-spa';registerApplication({name:'app',app:()=>{loadScripts('./chunk-a.js');loadScripts('./chunk-b.js');returnloadScripts('./entry.js')}activeWhen:'/appName'})一般我们会结合SystemJS使用来简化加载逻辑,但是我们还是需要知道子应用需要加载哪些资源。如果子应用的打包逻辑发生变化,这里的加载方式也会随之变化。这个加载过程可以自动化吗?比如我根据url加载子应用的html,然后解析出里面的JS和CSS,自动加载。乾坤是按照这样的思路解决的:会加载入口html,解析出脚本和样式部分,分别加载,剩下的会转化,放到dom中。比如这样一段html:qiankun会把head部分转成qiankun-head,把script部分提取出来自己加载,剩下的放在html:这样开发者就不用再指定怎么加载sub了-applications,实现解析html自动加载功能。该功能的实现放在import-html-entry包中。single-spa的实现称为ConfigEntry或JSEntry,即你要自己指定如何加载子应用,而qiankun称为HtmlEntry,会自动解析html实现加载。所以注册qiankun应用更简单,指定html的地址即可:import{registerMicroApps,start}from'qiankun';registerMicroApps([{name:'vueapp',entry:'//localhost:7100',container:'#container-vue',activeRule:'/micro-vue'},{name:'reactapp',entry:'//localhost:7101',容器:'#container-react',activeRule:'/微反应'},]);开始();并且qiankun还支持预加载,空闲时会加载解析后的脚本和样式:qiankun除了实现基于html的自动加载外,还实现了JSSandboxwithCSS:JS和CSS沙箱应用必须隔离,不能互相影响,即就是,必须做到JS和CSS的隔离。single-spa不做这个,而qiankun实现了这个功能。JS的隔离就是隔离全局变量window,其他的不会有什么冲突。它们最初是在不同功能的范围内执行的。qiankun提供了三种实现窗口隔离的方式:快照,加载子应用前记录窗口的属性,卸载后恢复之前的快照。diff,加载子应用后,记录window属性的增删改查,卸载后恢复。Proxy,创建一个代理对象,每个子应用访问这个代理对象。这些实现思路都比较容易理解。前两种思路有个问题,就是多个子应用不能同时存在,否则会冲突。一般常用第三种代理思路。qiankun中有这样一个策略选择逻辑:当支持Proxy并且传入的配置没有设置为loose时,就会使用Proxy。CSS的隔离是使用shadowdom,这是浏览器支持的特性。shadowroot下dom的样式不会影响其他dom。当然还有一个策略,就是scopedcss的思想,给css选择器加前缀,给dom加ID。不过这还是很实用的,需要手动开启:源码中可以看到这两种方式:总之,JS和CSS隔离的方案有很多,可以通过配置来选择。此外,qiankun还内置了应用间状态管理的解决方案:应用间状态管理子应用、子应用与主应用之间自然会有一些状态管理需求,qiankun也实现了这个功能。它是这样使用的:在主应用中初始化全局状态,为子应用定义方法getGlobalState获取全局状态和全局状态变化时的处理函数onGlobalStateChange:import{initGlobalState}from'qiankun'constinitialState={user:{name:'guang'}}constactions=initGlobalState(initialState)actions.onGlobalStateChange((newState,prev)=>{for(constkeyinnewState){initialState[key]=newState[key]}})actions.getGlobalState=(键)=>{返回键?initialState[key]:initialState}exportdefaultactions在子应用中,可以通过参数获取全局状态的get和set方法:exportasyncfunctionmount(props){constglobalState=props.getGlobalState();props.setGlobalState({user:{name:'dong'}})}总结一下,qiankun其实是一个比较完善的signle-spa,解决了子应用通过html入口手动加载各种资源的麻烦,隔离通过沙箱实现了JS和CSS的交互,同时实现了全局的状态管理机制。子应用里大概念这样写:从'react'导入React;从'react-dom'导入ReactDOM;从'./App'导入App;if(window.__POWERED_BY_QIANKUN__){__webpack_public_path__=window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}functionrender(props){const{container}=props;ReactDOM.render(,container?container.querySelector('#root'):document.querySelector('#root'));}if(!window.__POWERED_BY_QIANKUN__){render({});}导出异步functionbootstrap(){console.log('[react16]reactappbootstraped');}exportasyncfunctionmount(props){props.onGlobalStateChange((value,prev)=>console.log(`[onGlobalStateChange-${props.name}]:`,value,prev),true);props.setGlobalState({ignore:props.name,user:{name:props.name,},});render(props);}exportasyncfunctionunmount(props){const{container}=props;ReactDOM.unmountComponentAtNode(container?container.querySelector('#root'):document.querySelector('#root'));}qiankun会在运行子应用程序之前在窗口沙箱中设置POWERED_BY_QIANKUN变量。如果有这个变量,不要直接渲染,而是在挂载生命周期中渲染。否则,它会直接渲染并指定静态资源的加载地址,通过全局的webpack_public_path变量。其余与single-spa大致相同。综上所述,前端应用程序可以独立运行,也可以集成到另一个应用程序中。这种架构被称为微前端架构。在解决跨技术栈的应用集成和拆分大型项目的场景中非常有用。主流的微前端方案有single-spa和基于single-spa的qiankun:single-spa实现了切换路由时子应用的加载和卸载。但是它并不完美,并没有解决资源加载、沙盒、全局状态管理等问题,而qiankun做得更好:基于html自动解析js和css,并自动加载,不需要开发者手动指定如何加载加载。JS隔离是基于snapshot和Proxy的思想实现的,CSS隔离是基于ShadowDom和scopedcss的思想实现的。提供全局状态管理机制。所以,qiankun是基于single-spa的,使用方法也差不多,但是各方面的功能更加完善。