微前端框架是怎么导入加载子应用的

开发 前端
微前端似乎是最近一个很火的话题,我们也即将使用在生产环境中,接下来会更新一系列微前端源码分析、手写微前端文章。

微前端似乎是最近一个很火的话题,我们也即将使用在生产环境中,接下来会更新一系列微前端源码分析、手写微前端文章

废话不多说,直接参考目前的微前端框架注册子应用模块代码

下面代码,我指定的entry,就是子应用的访问入口地址

微前端到底是怎么回事呢?  我画了一张图

我们今天不谈其他的实现技术细节,坑点,就谈整体架构,这张图就能完全解释清楚

那么registerMicroApps,到底做了什么呢?

源码解析下,只看重要部分今天:

lifeCycles是我们自己传入的生命周期函数(这里先不解释),跟react这种框架一样,微前端针对每个子应用,也封装了一些生命周期,如果你是小白,那我就用最简单的话告诉你,生命周期钩子,其实在框架源码就是一个函数编写调用顺序而已(有的分异步和同步)

apps就是我们传入的数组,子应用集合

代码里做了一些防重复注册、数据处理等

看源码,不要全部都看,那样很费时间,而且你也得不到利益最大化,只看最精髓、重要部分

无论上面做了上面子应用去重、数据处理,我只要盯着每个子应用,即app这个对象即可

看到了loadApp这个方法,我们可以大概猜测到,是通过这个方法加载

下面__rest是对数据进行处理

loadApp这个函数有大概300行,挑最重点地方看

registerApplication是single-spa的方法,我们这里通过loadApp这个方法,对数据进行处理

上面这个函数,应该是整个微前端框架最复杂的地方,它最终会返回一个函数,当成函数传递给single-spa这个库的registerApplication方法使用

它的内部是switch case逻辑,然后返回一个数组

这是一个逻辑判断 

  1. case 0:  
  2.           entry = app.entry, appappName = app.name;  
  3.           _b = configuration.singular, singular = _b === void 0 ? false : _b, _c = configuration.sandbox, sandbox = _c === void 0 ? true : _c, importEntryOpts = __rest(configuration, ["singular", "sandbox"]); 
  4.  return [4  
  5. /*yield*/  
  6.           , importEntry(entry, importEntryOpts)]; 

重点来了

会通过importEntry 去加载entry(子应用地址)

  

上面代码里最重要的,如果我们entry传入字符串,那么就会使用这个函数去加载HTML内容(其实微前端的所有子应用加载,都是把dom节点加载渲染到基座的index.html文件中的一个div标签内)

importHTML这个函数,就是我们今晚最重要的一个点

传入url地址,发起fetch请求(此时由于域名或者端口不一样,会出现跨域,所有子应用的热更新开发模式下,webpack配置要做以下处理,部署也要考虑这个问题)

整个importHTML函数好像很长很长,但是我们就看最重要的地方,一个框架(库),流程线很长+版本迭代原因,需要兼容老的版本,所以很多源码对于我们其实是无用的

整个函数,最后返回了一个对象,这里很明显,通过fetch请求,获取了对应子应用entry入口的资源文件后,转换成了字符串

这里processTpl其实就是对这个子应用的dom模版(字符串格式)进行一个数据拼装,其实也不是很复杂,由于时间关系,可以自己看看过程,重点看结果

这里的思想,是redux的中间件源码思想,将数据进行了一层包装,高可用使用 

  1. function processTpl(tpl, baseURI) {  
  2. var scripts = [];  
  3. var styles = [];  
  4. var entry = null 
  5. var template = tpl  
  6. /*  
  7.   remove html comment first  
  8.   */  
  9.   .replace(HTML_COMMENT_REGEX, '').replace(LINK_TAG_REGEX, function (match) {  
  10. /*  
  11.     change the css link  
  12.     */  
  13. var styleType = !!match.match(STYLE_TYPE_REGEX);  
  14. if (styleType) {  
  15. var styleHref = match.match(STYLE_HREF_REGEX);  
  16. var styleIgnore = match.match(LINK_IGNORE_REGEX);  
  17. if (styleHref) {  
  18. var href = styleHref && styleHref[2];  
  19. var newHref = href 
  20. if (href && !hasProtocol(href)) {  
  21.           newHref = getEntirePath(href, baseURI);  
  22.         }  
  23. if (styleIgnore) {  
  24. return genIgnoreAssetReplaceSymbol(newHref);  
  25.         }  
  26.         styles.push(newHref);  
  27. return genLinkReplaceSymbol(newHref);  
  28.       }  
  29.     }  
  30. var preloadOrPrefetchType = match.match(LINK_PRELOAD_OR_PREFETCH_REGEX) && match.match(LINK_HREF_REGEX);  
  31. if (preloadOrPrefetchType) {  
  32. var _match$matchmatch = match.match(LINK_HREF_REGEX),  
  33.           _match$match2 = (0, _slicedToArray2["default"])(_match$match, 3),  
  34.           linkHref = _match$match2[2];  
  35. return genLinkReplaceSymbol(linkHref, true);  
  36.     }  
  37. return match;  
  38.   }).replace(STYLE_TAG_REGEX, function (match) {  
  39. if (STYLE_IGNORE_REGEX.test(match)) {  
  40. return genIgnoreAssetReplaceSymbol('style file');  
  41.     }  
  42. return match;  
  43.   }).replace(ALL_SCRIPT_REGEX, function (match) {  
  44. var scriptIgnore = match.match(SCRIPT_IGNORE_REGEX); // in order to keep the exec order of all javascripts  
  45. // if it is a external script  
  46. if (SCRIPT_TAG_REGEX.test(match) && match.match(SCRIPT_SRC_REGEX)) {  
  47. /* 
  48.       collect scripts and replace the ref  
  49.       */  
  50. var matchmatchedScriptEntry = match.match(SCRIPT_ENTRY_REGEX);  
  51. var matchmatchedScriptSrcMatch = match.match(SCRIPT_SRC_REGEX);  
  52. var matchedScriptSrc = matchedScriptSrcMatch && matchedScriptSrcMatch[2];  
  53. if (entry && matchedScriptEntry) {  
  54. throw new SyntaxError('You should not set multiply entry script!');  
  55.       } else {  
  56. // append the domain while the script not have an protocol prefix  
  57. if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) {  
  58.           matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI);  
  59.         }  
  60.         entryentry = entry || matchedScriptEntry && matchedScriptSrc;  
  61.       }  
  62. if (scriptIgnore) {  
  63. return genIgnoreAssetReplaceSymbol(matchedScriptSrc || 'js file');  
  64.       }  
  65. if (matchedScriptSrc) {  
  66. var asyncScript = !!match.match(SCRIPT_ASYNC_REGEX);  
  67.         scripts.push(asyncScript ? {  
  68.           async: true,  
  69.           src: matchedScriptSrc  
  70.         } : matchedScriptSrc);  
  71. return genScriptReplaceSymbol(matchedScriptSrc, asyncScript);  
  72.       }  
  73. return match;  
  74.     } else {  
  75. if (scriptIgnore) {  
  76. return genIgnoreAssetReplaceSymbol('js file');  
  77.       } // if it is an inline script  
  78. var code = (0, _utils.getInlineCode)(match); // remove script blocks when all of these lines are comments.  
  79. var isPureCommentBlock = code.split(/[\r\n]+/).every(function (line) {  
  80. return !line.trim() || line.trim().startsWith('//');  
  81.       });  
  82. if (!isPureCommentBlock) { 
  83.          scripts.push(match);  
  84.       }  
  85. return inlineScriptReplaceSymbol;  
  86.     }  
  87.   });  
  88.   scriptsscripts = scripts.filter(function (script) {  
  89. // filter empty script  
  90. return !!script;  
  91.   });  
  92. return {  
  93.     template: template,  
  94.     scripts: scripts,  
  95.     styles: styles,  
  96. // set the last script as entry if have not set  
  97.     entry: entry || scripts[scripts.length - 1]  
  98.   };  

最终返回了一个对象,此时已经不是一个纯html的字符串了,而是一个对象,而且脚本样式都分离了

这个是框架帮我们处理的,必须要设置一个入口js文件 

  1. // set the last script as entry if have not set 

下面是真正的single-spa源码,注册子应用,用apps这个数组去收集所有的子应用(数组每一项已经拥有了脚本、html、css样式的内容)

此时我们只要根据我们之前编写的activeRule和监听前端路由变化去控制展示子应用即可,原理如下:(今天不做过多讲解这块) 

  1. window.addEventListener('hashchange', reroute);  
  2. window.addEventListener('popstate', reroute);  
  3. // 拦截所有注册的事件,以便确保这里的事件总是第一个执行  
  4. const originalAddEventListener = window.addEventListener;  
  5. const originalRemoveEventListener = window.removeEventListener;  
  6. window.addEventListener = function (eventName, handler, args) {  
  7.     if (eventName && HIJACK_EVENTS_NAME.test(eventName) && typeof handler === 'function') {  
  8.         EVENTS_POOL[eventName].indexOf(handler) === -1 && EVENTS_POOL[eventName].push(handler); 
  9.     }  
  10.     return originalAddEventListener.apply(this, arguments);  
  11. };  
  12. window.removeEventListener = function (eventName, handler) {  
  13.     if (eventName && HIJACK_EVENTS_NAME.test(eventName) && typeof handler === 'function') {  
  14.         let eventList = EVENTS_POOL[eventName];  
  15.         eventList.indexOf(handler) > -1 && (EVENTS_POOL[eventName] = eventList.filter(fn => fn !== handler));  
  16.     }  
  17.     return originalRemoveEventListener.apply(this, arguments);  
  18. }; 

也是redux的中间件思想,劫持了事件,然后进行派发,优先调用微前端框架的路由事件,然后进行过滤展示子应用: 

  1. export function getAppsToLoad() {  
  2.     return APPS.filter(notSkipped).filter(withoutLoadError).filter(isntLoaded).filter(shouldBeActive);  

整个微前端的触发流程

 

 

责任编辑:庞桂玉 来源: 前端大全
相关推荐

2021-04-21 19:20:53

前端 容器应用

2022-09-07 21:31:19

微前端架构iframe

2023-11-03 08:04:47

Web微前端框架

2023-11-22 19:10:42

前端父应用文案

2020-10-18 07:37:24

微前端框架前端

2021-12-24 16:59:14

前端Web框架

2022-02-13 23:00:48

前端微前端qiankun

2022-10-20 15:43:39

htmxDjango技术栈

2022-10-17 15:21:18

2018-11-01 14:12:03

前端架构Javascript

2017-01-05 11:26:22

腾讯云微信小程序

2022-02-23 15:33:19

前端框架开发Web

2022-08-30 19:11:12

Docker虚拟化技术

2023-04-28 07:44:44

MyBatis查询SQL

2022-03-14 15:26:59

Hi3516Ark子系统鸿蒙

2024-04-07 08:56:25

JavaScriptweb应用开发工具

2023-06-13 07:54:17

DOM 封装作用域

2021-06-26 07:40:21

前端自动化测试Jest

2023-06-29 07:48:35

异步加载JavaScript

2013-10-24 10:40:23

前端框架
点赞
收藏

51CTO技术栈公众号