1.前言网易奇遇是一个提供客服和智能营销的SaaS平台。在奇遇业务中,有在线系统、呼叫系统、机器人、工单系统、数据大屏等业务线。它们分布在两个业务端,管理端和客服端。两端的功能框相似,都是由外框(顶部导航、一级菜单)和中间的内容区组成。2.业务现状随着业务量和功能的增加,主系统越来越复杂,成为一个单体应用。所有业务线耦合在一起,在系统建设、业务分离、开发和维护方面都带来了巨大的挑战。新挑战。为了解决上述问题,我们初步采用了“MPA+iframe”的技术方案。首先根据业务维度从庞大的单体应用中拆分出多个子应用,并用React技术栈进行重构,通过iframe实现新旧技术栈的隔离。这些子应用基于URL解耦,每个子应用都可以独立开发、运行和部署。“MPA+iframe”的技术方案是一把双刃剑。它可以更方便地解决已有的问题,但同时也带来了一些新的问题。使用MPA方案可以让子应用使用不同的技术栈,父子应用自然隔离,但是在浏览器页面跳转时无法保持单页应用的流畅体验,父子之间的通信-儿童申请困难。使用iframe可以轻松隔离新旧技术栈,但是也带来了一些问题:问题比较好的解决方案:父子框架的url不同步,浏览器的前进后退按钮异常--define父子框架的路由映射,并使用postMessage和historyAPI解决父子框架UI不同步问题。遮罩层只能覆盖iframe所在的区域,iframe中的弹出框不能相对于外页面居中。没有子框架的全局上下文与父框架完全隔离,导致父子框架和冗余同步数据之间难以通信。Yu--Wu加载慢,体验差--Wu项目初期采用的开发框架是NEJ(NiceEasyJavascript),其依赖管理系统、控制系统等特性对项目早期的开发有很大的贡献贡献,现在已经完成了它的历史使命,项目开始向React技术栈过渡。下图展示了应用框架的现状:可以看到,整个系统使用了NEJ和React两套技术栈。React应用嵌入在React外框内部,这些应用引用各自的外框,通过React业务组件库进行复用。NEJ外框里面的情况比较复杂。有的场景是嵌入NEJ应用,有的场景是通过iframe嵌入React应用。这些React应用中的一些页面也有通过iframe再次嵌入NEJ应用的场景。由于NEJ的旧技术栈缺乏组件支持,遗留代码较多,开发维护成本高。目前前端工程处于统一技术栈的过渡期,需要维护两套外层框架,未来会逐步从NEJ转向React。对于新的应用,直接采用React技术栈。随着新应用的增加,外层框架被引用的次数越来越多,每次更新需要发布多个应用,外层框架使用新技术栈的维护成本越来越高。微前端是目前的热门话题,它是微服务在前端领域的延伸。它将前端整体拆分成多个更小、更易于管理的部分,可以解决工程复杂度高、多种技术栈并存、开发维护困难等问题。微前端的两大特点是独立于微应用技术栈。每个微应用都可以独立开发、运行和部署,可以很好地匹配现有的业务场景。所以我们把目光转向了现有应用的微前端改造。3、微前端Retrofit的好处用微前端对现有应用进行改造,可以带来以下好处:积累实践经验,为将来从巨石应用和微前端改造拆分做准备;去除访问二方应用时使用iframe,优化产品体验;汇聚外框,提升研发效率,降低维护成本;提供前端增量升级能力,进而可以更好的复用历史代码,实现渐进式重构;社区中的微前端解决方案包括:有很多种,包括:Single-spa:只解决应用间的加载方案,不考虑其他外围问题;qiankun:基于single-spa,提供更加开箱即用的API,具备JS沙箱和风格隔离、子应用并行等能力;Icestark:框架应用必须基于React,不利于后续技术栈优化;Magix:适用于单页应用项目,不支持多实例,不满足业务需求;Luigi:是一个基于iframe的微前端框架,依然存在前面提到的iframe带来的产品体验问题;AraFramework:是一个基于Airbnb的Hypernova的微前端框架,通过服务端渲染扩展,在访问Many时侵入原有应用;WidgetJS:轻量级微前端解决方案,文档不够友好;综合考虑业务场景、上手难度、文档友好性、代码入侵性、可维护性等,最终选择的微前端方案是qiankun。下一步是基于qiankun的微前端改造。业务分析及改造效果奇遇微前端改造从技术层面涉及React和NEJ技术栈,从业务层面涉及管理和客服。因为最终目的是将所有前端项目统一到React技术栈中,并且管理端部分应用的外层框架已经用React重构,所以先从管理端入手。首先,从新旧技术栈应用中选择一个应用进行改造,积累相关经验。应用选择的标准是没有复杂的业务逻辑和较少的流量,以降低转型风险。新技术栈应用选择首页应用,老技术栈应用选择数据大屏应用。我们来看看奇遇微前端修改后的首页:这里有两个概念,基础应用(也叫主应用、框架应用等)和子应用(也叫微应用):**基础application负责整体布局,子应用的配置和调度一般包括各个子应用的公共部分,比如外层框架;子应用负责渲染自己的业务逻辑;可以看到,首页的两个组件在上图中用红框标出,外框(顶部导航,一级菜单)和中间的内容区。外框由基础应用控制,通过监听URL进行路由分发和子应用调度。内容区域由一个或多个子应用程序控制。上图中的内容区域由主页子应用程序控制。粗略改造步骤创建管理终端基础项目basic-admin;基础应用仅包括每个子应用的公共部分;创建首页子项目微索引,大屏子项目微大屏,以及对应的应用和集群;在项目的入口文件中,暴露对应的生命周期钩子,供qiankun识别;修改打包配置,使素材以umd形式输出,以webpack为例:constwebpackConfig={//...output:{//...library:`${packageName}-[name]`,//这里的packageName是子应用名,比如微大屏libraryTarget:'umd',jsonpFunction:`webpackJsonp_${packageName}`,}};添加微应用对应的内部路由,修改网关:内部路由用于注册子应用,一般情况下用户无法直接访问;修改后的网关需要将所有匹配baseURL前缀的请求指向base应用;兼容奇遇PC客户端(低版本Chrome浏览器内核):qiankun加载资源时依赖的fetchAPI存在兼容性问题;高度继承导致的样式问题;在基础应用中调用qiankun的API将子app注册到基础应用,如:registerMicroApps([{name:'micro-index',entry:'//'+location.hostname+'/_MicroIndex',container:'#subapp-container',activeRule:'/madmin/home',},{name:'micro-bigscreen',entry:'//'+location.hostname+'/_MicroBigscreen/index',container:'#subapp-container',activeRule:'/madmin/dashboard',}]);4、微前端架构下的业务变化服务网关变化微前端改造后,所有管理相关的子应用的URL前缀都是“/madmin/”,比如首页的URL是“/madmin”/home/”服务网关需要将所有以“/madmin/”开头的路由指向管理基础应用。结合网关的微前端架构图如下:子应用开发模式子应用有独立的仓库。部署完成后,在基础应用中注册应用的发布产品。这些产品可以是子应用的访问地址,也可以是资源配置对象(脚本+样式+html)。需要注意的是,子应用与基础应用联合开发时,子应用会读取基础应用的同步数据,需要在基础应用中配置Mock的同步数据。同样,子应用使用的接口代理也需要在基础应用中进行完整的配置。基础应用的整体流程基础应用启动后,会监听URL的变化。当用户访问系统时,根据当前访问的URL和注册的路由信息??,匹配当前需要加载的子应用信息,进而加载子应用。资源和呈现子应用程序。当用户点击触发跳转时,如果路由变化触发内部URL跳转,页面会直接按照应用内部的路由逻辑进行渲染。如果路由变化触发跨应用跳转,则返回上述路由匹配流程。下图是微前端改造后的应用框架:按照上面的子应用改造流程,一步步完成管理端微前端的改造。接下来就是对客服端的微前端进行改造。客服端和管理端虽然框架结构相似,但是URL是解耦的,一级菜单和顶部导航的业务功能也有很大区别。共享相同的基础应用程序将导致应用程序的高复杂性。最好另外创建一个专用于客服的基础应用,两个基础应用通过业务组件库复用组件。未来的整体应用框架如下:借助微前端,整个系统可以更平滑的升级技术栈,最终实现前端技术栈的统一,让业务开发更高效。五、遇到的问题及解决方案1、子应用连接基础应用后,babel-polyfill报错。babel-polyfill不支持多次引用(基础应用和子应用分别引用一次),直接去掉babel-polyfill会导致子应用无法独立运行,可以使用idempodent-babel-polyfill反而。2、基础应用访问子应用资源时,报404错误资源路径问题。需要在运行时配置公共路径。if(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__){__webpack_public_path__=window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}else{__webpack_public_path__=window.location.protocol+"//"+window.location.host+"/";将沙箱设置为strictStyleIsolation将启用严格的样式隔离。原理是将子应用内容渲染到基础容器的shadowdom中,使得无法直接获取基础应用的dom元素。取消strictStyleIsolation,把jsSandBox设置为true就没有问题了。样式隔离的最佳实践是采用约定隔离:使用CSS命名空间、CSSModule、css-in-js等工程化方式,避免编写全局样式。4、本地联调时,基础应用访问子应用资源时,报跨域错误。开发环境使用browserSync进行浏览器同步。qiankun框架通过浏览器的fetchAPI获取子应用资源。会有跨域问题,所以cors需要设置为true。browserSync({//...cors:true});5、子应用引入qiankun生命周期后,不能独立运行,加条件判断。非qiankun环境,使用之前的运行环境。修改'entry.js'的渲染条件:content'));}6.本地联调时,子应用热加载报错。使用ScriptExtHtmlWebpackPlugin插件修改webpack配置,在每个页面的entryjs中添加entry属性。tplPlugins.push(newScriptExtHtmlWebpackPlugin({custom:{test:/(?{returnwindow.fetch(url,{...init,credentials:'same-origin'//自动发送当前域名下的cookies});}});9、非React环境引入qiankun生命周期方法定义一个与子应用同名的全局变量,lifecyclehook函数必须返回promise。如果不支持promise,需要引入promise-polyfill。入口文件可以这样写:(function(win){//这里的'micro-bigscreen'与注册到基础应用的子应用名称相同:function(){//必须返回Promise,否则子应用无法正常启动returnPromise.resolve();},mount:function(){returnPromise.resolve();},unmount:function(){返回Promise.resolve();}};})(窗口);10、PC客户端子应用变量访问错误:UncaughtTypeError:'get'onproxyPCclientinjectwindow.cefQueryandwindow.cefQueryCancelvariables,andwritableandconfigurableintheirattributedescriptorsarefalse,通过JS直接访问sandboxProxy会报错:UncaughtTypeError:'get'onproxy。因为只有子应用使用沙箱,这个错误只会影响子应用,不会影响基础应用。解决方法是:分别从window.cefQuery和window.cefQueryCancel复制新变量window.cefQuery2和window.cefQueryCancel2,修改其属性描述符writable和configurable为true。然后修改微前端子应用中window.cefQuery和window.cefQueryCancel的引用分别为window.cefQuery2和window.cefQueryCancel2。dockapplication中的相关代码:constpolyfillPcPlatform=()=>{if(window.cefQuery){Object.defineProperty(window,'cefQuery2',{value:window.cefQuery,writable:true,configurable:true});}if(window.cefQueryCancel){Object.defineProperty(window,'cefQueryCancel2',{value:window.cefQueryCancel,writable:true,configurable:true});}};//注册子应用registerMicroApps([//...],{beforeLoad:[app=>{//兼容PC客户端polyfillPcPlatform();}],//...});6.总结本次微前端实践基于qiankun框架,打造了管理基础应用,管理端首页和数据大屏应用都进行了微前端改造。转型涉及两套技术栈,React和NEJ。做好准备;让管理端不同技术栈的两方应用访问不再需要使用iframe,优化了产品体验;融合管理端外框,让新应用的接入不再需要关心顶部导航和一级菜单;提供前端的增量升级能力,可以更好的复用历史代码,实现未来逐步重构;微前端不是一个框架,而是一套架构体系,基础应用的创建和子应用的改造是它的核心基础设施,除了基础设施,还有配置中心和观察工具。配置中心包括参数配置、版本管理、发布策略等。观察工具具有一定的运维功能,包括应用状态的可见性和可控性。具备以上能力,可用于统一管控所有微应用,为SaaS产品提供自由组合的能力,让技术为业务带来更大的价值。更多技术内容,请关注【网易智企科技+】公众号。
