微前端框架是怎么导入加载子应用的
微前端似乎是微前最近一个很火的话题,我们也即将使用在生产环境中,端框接下来会更新一系列微前端源码分析、架导手写微前端文章
废话不多说,入加直接参考目前的微前微前端框架注册子应用模块代码
下面代码,我指定的端框entry,就是子应用的访问入口地址

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

我们今天不谈其他的实现技术细节,坑点,架导就谈整体架构,入加这张图就能完全解释清楚
那么registerMicroApps,微前到底做了什么呢?

源码解析下,只看重要部分今天:
lifeCycles是端框我们自己传入的生命周期函数(这里先不解释),跟react这种框架一样,架导微前端针对每个子应用,入加也封装了一些生命周期,微前如果你是端框小白,那我就用最简单的架导话告诉你,生命周期钩子,其实在框架源码就是一个函数编写调用顺序而已(有的分异步和同步)
apps就是我们传入的数组,子应用集合
代码里做了一些防重复注册、WordPress模板数据处理等
看源码,不要全部都看,那样很费时间,而且你也得不到利益最大化,只看最精髓、重要部分
无论上面做了上面子应用去重、数据处理,我只要盯着每个子应用,即app这个对象即可
看到了loadApp这个方法,我们可以大概猜测到,是通过这个方法加载
下面__rest是对数据进行处理

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

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

上面这个函数,应该是整个微前端框架最复杂的地方,它最终会返回一个函数,当成函数传递给single-spa这个库的registerApplication方法使用
它的内部是switch case逻辑,然后返回一个数组
这是一个逻辑判断
case 0: entry = app.entry, appappName = app.name; _b = configuration.singular, singular = _b === void 0 ? false : _b, _c = configuration.sandbox, sandbox = _c === void 0 ? true : _c, importEntryOpts = __rest(configuration, ["singular", "sandbox"]); return [4 /*yield*/ , importEntry(entry, importEntryOpts)];重点来了
会通过importEntry 去加载entry(子应用地址)
上面代码里最重要的,如果我们entry传入字符串,那么就会使用这个函数去加载HTML内容(其实微前端的所有子应用加载,都是云服务器把dom节点加载渲染到基座的index.html文件中的一个div标签内)

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

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

整个函数,最后返回了一个对象,这里很明显,通过fetch请求,获取了对应子应用entry入口的资源文件后,转换成了字符串
这里processTpl其实就是对这个子应用的dom模版(字符串格式)进行一个数据拼装,其实也不是很复杂,由于时间关系,服务器托管可以自己看看过程,重点看结果
这里的思想,是redux的中间件源码思想,将数据进行了一层包装,高可用使用
function processTpl(tpl, baseURI) { var scripts = []; var styles = []; var entry = null; var template = tpl /* remove html comment first */ .replace(HTML_COMMENT_REGEX, ).replace(LINK_TAG_REGEX, function (match) { /* change the css link */ var styleType = !!match.match(STYLE_TYPE_REGEX); if (styleType) { var styleHref = match.match(STYLE_HREF_REGEX); var styleIgnore = match.match(LINK_IGNORE_REGEX); if (styleHref) { var href = styleHref && styleHref[2]; var newHref = href; if (href && !hasProtocol(href)) { newHref = getEntirePath(href, baseURI); } if (styleIgnore) { return genIgnoreAssetReplaceSymbol(newHref); } styles.push(newHref); return genLinkReplaceSymbol(newHref); } } var preloadOrPrefetchType = match.match(LINK_PRELOAD_OR_PREFETCH_REGEX) && match.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]; return genLinkReplaceSymbol(linkHref, true); } return match; }).replace(STYLE_TAG_REGEX, function (match) { if (STYLE_IGNORE_REGEX.test(match)) { return genIgnoreAssetReplaceSymbol(style file); } return match; }).replace(ALL_SCRIPT_REGEX, function (match) { var scriptIgnore = match.match(SCRIPT_IGNORE_REGEX); // in order to keep the exec order of all javascripts // if it is a external script if (SCRIPT_TAG_REGEX.test(match) && match.match(SCRIPT_SRC_REGEX)) { /* collect scripts and replace the ref */ var matchmatchedScriptEntry = match.match(SCRIPT_ENTRY_REGEX); var matchmatchedScriptSrcMatch = match.match(SCRIPT_SRC_REGEX); var matchedScriptSrc = matchedScriptSrcMatch && matchedScriptSrcMatch[2]; if (entry && matchedScriptEntry) { throw new SyntaxError(You should not set multiply entry script!); } else { // append the domain while the script not have an protocol prefix if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) { matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI); } entryentry = entry || matchedScriptEntry && matchedScriptSrc; } if (scriptIgnore) { return genIgnoreAssetReplaceSymbol(matchedScriptSrc || js file); } if (matchedScriptSrc) { var asyncScript = !!match.match(SCRIPT_ASYNC_REGEX); scripts.push(asyncScript ? { async: true, src: matchedScriptSrc } : matchedScriptSrc); return genScriptReplaceSymbol(matchedScriptSrc, asyncScript); } return match; } else { if (scriptIgnore) { return genIgnoreAssetReplaceSymbol(js file); } // if it is an inline script var code = (0, _utils.getInlineCode)(match); // remove script blocks when all of these lines are comments. var isPureCommentBlock = code.split(/[\r\n]+/).every(function (line) { return !line.trim() || line.trim().startsWith(//); }); if (!isPureCommentBlock) { scripts.push(match); } return inlineScriptReplaceSymbol; } }); scriptsscripts = scripts.filter(function (script) { // filter empty script return !!script; }); return { template: template, scripts: scripts, styles: styles, // set the last script as entry if have not set entry: entry || scripts[scripts.length - 1] }; }最终返回了一个对象,此时已经不是一个纯html的字符串了,而是一个对象,而且脚本样式都分离了

这个是框架帮我们处理的,必须要设置一个入口js文件
// set the last script as entry if have not set下面是真正的single-spa源码,注册子应用,用apps这个数组去收集所有的子应用(数组每一项已经拥有了脚本、html、css样式的内容)

此时我们只要根据我们之前编写的activeRule和监听前端路由变化去控制展示子应用即可,原理如下:(今天不做过多讲解这块)
window.addEventListener(hashchange, reroute); window.addEventListener(popstate, reroute); // 拦截所有注册的事件,以便确保这里的事件总是第一个执行 const originalAddEventListener = window.addEventListener; const originalRemoveEventListener = window.removeEventListener; window.addEventListener = function (eventName, handler, args) { if (eventName && HIJACK_EVENTS_NAME.test(eventName) && typeof handler === function) { EVENTS_POOL[eventName].indexOf(handler) === -1 && EVENTS_POOL[eventName].push(handler); } return originalAddEventListener.apply(this, arguments); }; window.removeEventListener = function (eventName, handler) { if (eventName && HIJACK_EVENTS_NAME.test(eventName) && typeof handler === function) { let eventList = EVENTS_POOL[eventName]; eventList.indexOf(handler) > -1 && (EVENTS_POOL[eventName] = eventList.filter(fn => fn !== handler)); } return originalRemoveEventListener.apply(this, arguments); };也是redux的中间件思想,劫持了事件,然后进行派发,优先调用微前端框架的路由事件,然后进行过滤展示子应用:
export function getAppsToLoad() { return APPS.filter(notSkipped).filter(withoutLoadError).filter(isntLoaded).filter(shouldBeActive); }整个微前端的触发流程
相关文章
年富供应链(以年富供应链为例,探索供应链管理的关键要素和创新点)
摘要:随着全球贸易的发展和商业环境的不断变化,供应链管理成为企业成功的关键因素之一。而年富供应链作为一家专业的供应链服务商,在不断创新和优化自身业务模式的基础上,为客户提供高效、可靠的供...2025-11-05
什么是子域名?子域名是域名吗?子域名与二级域名一样吗?它们二者有什么区别呢?近期,不少朋友咨询上述问题,今天小编特地整理了有关子域名和二级域名的介绍,下面就跟大家详细介绍一下。子域名是域名吗?子域名与2025-11-05
近日有人咨询小聚,为什么企业要花重金购买域名?购买来的域名值不值得?企业花重金购买域名买的仅仅是域名吗?还有可能买的是什么?一、企业购买域名买的是什么?①众所周知,域名是互联网时代企业的门牌,域名是用2025-11-05
上文小聚给大家介绍了如何拥有自己的域名,有详细的注册自己域名的方法。现在问题来了,有了自己的域名后怎么样搭建网站呢?下面小聚给大家介绍三种方法,希望可以解决大家的疑问。1、使用模板建站聚名网的便捷模块2025-11-051.6GHz处理器的优势与应用(揭秘1.6GHz处理器的性能和适用领域)
摘要:在现代科技发展日新月异的时代,处理器作为电子设备的核心组件之一,扮演着至关重要的角色。而1.6GHz处理器以其独特的性能和广泛的应用领域,成为许多用户和制造商的首选。本文将深入探讨...2025-11-05
除了常见的com域名、cn域名,不知道大家有没有见过today域名,today这个英文单词相信大家也都不陌生,那么放在域名行业today是什么域名后缀呢?today域名适用在哪些行业呢?一起和小聚了解2025-11-05

最新评论