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

微前端框架如何导入加载子应用

时间:2023-03-15 23:40:47 科技观察

微前端最近好像很火,我们很快就会在生产环境中使用它。接下来我们会更新一系列微前端源码解析和手写微前端文章。废话不多说,直接参考现在的微前端框架注册子应用在模块代码下面的代码中,我指定的入口就是子应用的访问入口地址。微前端是怎么回事?我画了一幅画。其他实现技术细节我们今天不谈。下面说一下整体架构。这张图可以充分说明。那么registerMicroApps到底做了什么?源码分析下,只看今天重要的部分:lifeCycles就是我们传入的生命周期函数(这里不做解释),就像react的框架一样,微前端也为每个子类封装了一些生命周期应用,如果你是新手,那么我就用最简单的话告诉你,生命周期钩子,其实在框架源码中只是一个函数编写和调用顺序(有的分异步和同步)apps是我们传入的数组,在子应用集合代码中做一些防重复注册,数据处理等,看源码。不要全部看。这很耗时,而且您无法获得最大的收益。对于数据处理,我只需要盯着每个子应用,也就是app对象看loadApp方法即可。我们可以大致猜想下面的__rest是用来处理数据的。loadApp函数大约有300行。挑最重要的一点看registerApplication是single-spa方法。这里我们使用loadApp方法来处理数据。上面的功能应该是整个微前端框架中最复杂的部分了。它最终会返回一个函数,并将其作为函数传递给single-spa库的registerApplication方法,使用其内部的switchcase逻辑,然后返回一个数组。这是逻辑判断case0:entry=app.entry,appappName=app.name;_b=configuration.singular,singular=_b===void0?false:_b,_c=configuration.sandbox,sandbox=_c===void0?真:_c,importEntryOpts=__rest(配置,[“单一”,“沙盒”]);返回[4/*产量*/,进口入口(条目,importEntryOpts)];重点是通过importEntry加载入口(子应用地址)。上面代码中最重要的一点,如果我们从entry中传入一个字符串,那么这个函数就会用来加载HTML内容(其实微前端所有子应用的加载就是加载和渲染dom节点放入base的index中的一个div标签,有时由于域名或端口不同,会出现跨域,在所有子应用的热更新开发模式下,webpack配置需要做如下处理,部署也应该考虑这个问题。)整个importHTML函数好像很长,但是我们就看一下最重要的是一个框架(库),流程线很长+版本迭代的原因,需要兼容老版本,那么多源代码对我们其实没用。整个函数最终返回一个对象。这里很明显,通过fetch请求,获取到子应用entry条目对应的资源文件后,转为字符串。在这里,processTpl实际上是对这个子应用的DOM模板(字符串格式)的数据组装。其实也不是很复杂。由于时间关系,大家可以自己看过程,关注结果。这里的思路是redux的中间件源码思路,将数据打包一层,使用functionprocessTpl(tpl,baseURI){varscripts=[];varstyles=[];varentry实现高可用=null;vartemplate=tpl/*removehtmlcommentfirst*/.replace(HTML_COMMENT_REGEX,'').replace(LINK_TAG_REGEX,function(match){/*changethecsslink*/varstyleType=!!match.match(STYLE_TYPE_REGEX);if(styleType){varstyleHref=match.match(STYLE_HREF_REGEX);varstyleIgnore=match.match(LINK_IGNORE_REGEX);if(styleHref){varhref=styleHref&&styleHref[2];varnewHref=href;if(href&&!hasProtocol(href)){newHref=getEntirePath(href,baseURI);}if(styleIgnore){returgenIgnoreAssetReplaceSymbol(newHref);}styles.push(newHref);returngenLinkReplaceSymbol(newHref);}}varpreloadOrPrefetchType=match.match(LINK_PRELOAD_OR_PREFETCH_REGEX)&&匹配.match(LINK_HREF_REGEX);if(preloadOrPrefetchType){var_match$matchmatch=match.match(LINK_HREF_REGEX),_match$match2=(0,_slicedToArray2["default"])(_match$match,3),linkHref=_match$match2[2];returngenLinkReplaceSymbol(linkHref,true);}returnmatch;}).replace(STYLE_TAG_REGEX,function(match){if(STYLE_IGNORE_REGEX.test(match)){returngenIgnoreAssetReplaceSymbol('stylefile');}returnmatch;}).replace(ALL_SCRIPT_REGEX,function(match){varscriptIgnore=match.match(SCRIPT_IGNORE_REGEX);//inordertokeeptheexecorderofalljavascripts//ifitisaexternalscriptif(SCRIPT_TAG_REGEX.test(match)&&match.match(SCRIPT_SRC_REGEX)){/*collectscriptsandreplacetheref*/varmatchmatchedScriptENTRY(SCmatch)_REGEX);varmatchmatchedScriptSrcMatch=match.match(SCRIPT_SRC_REGEX);varmatchedScriptSrc=matchedScriptSrcMatch&&matchedScriptSrcMatch[2];if(entry&&matchedScriptEntry){thrownewSyntaxError('Youshouldnotsetmultiplyentryscript!');}else{//appendthedomainwhilethescriptnothaveanprotocolprefixif(matchedScriptSrc&&!hasProtocol(matchedScriptSrc)){matchedScriptSrc=getEntirePath(matchedScriptSrc,baseURI);}entry=entry||matchedScriptEntry&&matchedScriptSrc;}if(scriptIgnore){returngenIgnoreAssetReplaceSymbol(matchedScriptSrc||'jsfile');}if(matchedScriptSrc){varasyncScript=!!match.match(SCRIPT_ASYNC_REGEX);scripts.push(asyncScript?{async:true,src:matchedScriptSrc}:matchedScriptSrc);returgenScriptReplaceSymbol(matchedScriptSrc,asyncScript);}returnmatch;}else{if(scriptIgnore){returngenIgnoreAssetReplaceSymbol('jsfile');}//ifitisaninlinescriptvarcode=(0,_utils.getInlineCode)(match);//当所有这些行都出现时移除脚本块nts.varisPureCommentBlock=code.split(/[\r\n]+/).every(function(line){return!line.trim()||line.trim().startsWith('//');});if(!isPureCommentBlock){scripts.push(match);}returninlineScriptReplaceSymbol;}});scriptsscripts=scripts.filter(function(script){//filteremptyscriptreturn!!script;});return{template:template,scripts:scripts,styles:styles,//setthelastscriptasentryifhavenotsetentry:entry||scripts[scripts.length-1]};}最后返回一个对象,不再是纯html字符串,而是一个对象,脚本的样式都是分开了。这是由框架为我们处理的。我们必须设置一个入口js文件//setthelastscriptasentryifhavenotset下面是真正的single-spa源码,注册子应用,使用apps数组收集所有子应用(数组中的每一项已经有script,html,cssstylecontent)这时候我们只需要根据我们之前写的activeRule控制显示子应用,监听前端路由变化即可。原理如下:(今天就不多解释了)window.addEventListener('hashchange',reroute);window.addEventListener('popstate',reroute);//拦截所有注册的事件,保证这里的事件总是最先被执行。;window.addEventListener=function(eventName,handler,args){if(eventName&&HIJACK_EVENTS_NAME.test(eventName)&&typeofhandler==='function'){EVENTS_POOL[eventName].indexOf(handler)===-1&&EVENTS_POOL[eventName].push(handler);}returnoriginalAddEventListener.apply(this,arguments);};window.removeEventListener=function(eventName,handler){if(eventName&&HIJACK_EVENTS_NAME.test(eventName)&&typeofhandler==='function'){leteventList=EVENTS_POOL[eventName];eventList.indexOf(handler)>-1&&(EVENTS_POOL[eventName]=eventList.filter(fn=>fn!==handler));}returnoriginalRemoveEventListener.apply(this,arguments);};也是redux的中间件思路,劫持事件,然后派发,先调用微前端框架的路由事件,然后进行过滤显示子应用:exportfunctiongetAppsToLoad(){returnAPPS.filter(notSkipped.filter(withoutLoadError).filter(isntLoaded).filter(shouldBeActive);}整个微前端的触发流程