微前端框架是怎么导入加载子应用的 【3000字精读】

写在开头:

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


废话不多说,直接参考目前的微前端框架注册子应用模块代码
  • 下面代码,我指定的entry,就是子应用的访问入口地址
代码语言:javascript
复制
registerMicroApps(
  [
    {
      name: 'rental-web',
      entry: isDev ? '//rental-dev.mysoft.com.cn:8809' : `//${url}:8809`,
      container: '#rental-web',
      render,
      activeRule: '/static',
      props: {
        value,
        setValue
      }
    },
    {
      name: 'fed-rental-web',
      entry: isDev ? '//rental-dev.mysoft.com.cn:8006' : `//${url}:8006`,
      container: '#fed-rental-web',
      render,
      activeRule: '/fed',
      props: {
        value,
        setValue
      }
    },]
  • 微前端到底是怎么回事呢? 我画了一张图
我们今天不谈其他的实现技术细节,坑点,就谈整体架构,这张图就能完全解释清楚

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

源码解析下,只看重要部分今天:
代码语言:javascript
复制
export function registerMicroApps(apps, lifeCycles) {
  var _this = this; // Each app only needs to be registered once

var unregisteredApps = apps.filter(function (app) {
return !microApps.some(function (registeredApp) {
return registeredApp.name === app.name;
});
});
microApps = __spread(microApps, unregisteredApps);
unregisteredApps.forEach(function (app) {
var name = app.name,
activeRule = app.activeRule,
props = app.props,
appConfig = __rest(app, ["name", "activeRule", "props"]);

registerApplication({
  name: name,
  app: function app() {
    return __awaiter(_this, void 0, void 0, function () {
      return __generator(this, function (_a) {
        switch (_a.label) {
          case 0:
            return [4
            /*yield*/
            , frameworkStartedDefer.promise];

          case 1:
            _a.sent();

            return [2
            /*return*/
            , loadApp(__assign({
              name: name,
              props: props
            }, appConfig), frameworkConfiguration, lifeCycles)];
        }
      });
    });
  },
  activeWhen: activeRule,
  customProps: props
});

});
}

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

  • apps就是我们传入的数组,子应用集合
  • 代码里做了一些防重复注册、数据处理等
  • 看源码,不要全部都看,那样很费时间,而且你也得不到利益最大化,只看最精髓、重要部分
  • 无论上面做了上面子应用去重、数据处理,我只要盯着每个子应用,即app这个对象即可
  • 看到了loadApp这个方法,我们可以大概猜测到,是通过这个方法加载
下面__rest是对数据进行处理
代码语言:javascript
复制
export function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
  • loadApp这个函数有大概300行,挑最重点地方看
代码语言:javascript
复制
export function loadApp(app, configuration, lifeCycles) {
if (configuration === void 0) {
configuration = {};
}

var _a;

return __awaiter(this, void 0, void 0, function () {
var entry, appName, _b, singular, _c, sandbox, importEntryOpts, _d, template, execScripts, assetPublicPath, appInstanceId, strictStyleIsolation, appContent, element, container, legacyRender, render, containerGetter, global, mountSandbox, unmountSandbox, sandboxInstance, _e, _f, beforeUnmount, _g, afterUnmount, _h, afterMount, _j, beforeMount, _k, beforeLoad, scriptExports, bootstrap, mount, unmount, globalVariableExports, _l, onGlobalStateChange, setGlobalState, offGlobalStateChange;

var _this = this;

return __generator(this, function (_m) {
  switch (_m.label) {
    case 0:
      entry = app.entry, appName = app.name;
      _b = configuration.singular, singular = _b === void 0 ? false : _b, _c = configuration.sandbox, sandbox = _c === void 0 ? true : _c, importEntryOpts = __rest(configuration, [&#34;singular&#34;, &#34;sandbox&#34;]);
      return [4
      /*yield*/
      , importEntry(entry, importEntryOpts)];

    case 1:
      _d = _m.sent(), template = _d.template, execScripts = _d.execScripts, assetPublicPath = _d.assetPublicPath;
      return [4
      /*yield*/
      , validateSingularMode(singular, app)];

    case 2:
      if (!_m.sent()) return [3
      /*break*/
      , 4];
      return [4
      /*yield*/
      , prevAppUnmountedDeferred &amp;&amp; prevAppUnmountedDeferred.promise];

    case 3:
      _m.sent();

      _m.label = 4;

    case 4:
      appInstanceId = appName + &#34;_&#34; + (appInstanceCounts.hasOwnProperty(appName) ? ((_a = appInstanceCounts[appName]) !== null &amp;&amp; _a !== void 0 ? _a : 0) + 1 : 0);
      strictStyleIsolation = _typeof(sandbox) === &#39;object&#39; &amp;&amp; !!sandbox.strictStyleIsolation;
      appContent = getDefaultTplWrapper(appInstanceId)(template);
      element = createElement(appContent, strictStyleIsolation);
      container = &#39;container&#39; in app ? app.container : undefined;
      legacyRender = &#39;render&#39; in app ? app.render : undefined;
      render = getRender(appContent, container, legacyRender); // 第一次加载设置应用可见区域 dom 结构
      // 确保每次应用加载前容器 dom 结构已经设置完毕

      render({
        element: element,
        loading: true
      });
      containerGetter = getAppWrapperGetter(appInstanceId, !!legacyRender, strictStyleIsolation, function () {
        return element;
      });
      global = window;

      mountSandbox = function mountSandbox() {
        return Promise.resolve();
      };

      unmountSandbox = function unmountSandbox() {
        return Promise.resolve();
      };

      if (sandbox) {
        sandboxInstance = createSandbox(appName, containerGetter, Boolean(singular)); // 用沙箱的代理对象作为接下来使用的全局对象

        global = sandboxInstance.proxy;
        mountSandbox = sandboxInstance.mount;
        unmountSandbox = sandboxInstance.unmount;
      }

      _e = _mergeWith({}, getAddOns(global, assetPublicPath), lifeCycles, function (v1, v2) {
        return _concat(v1 !== null &amp;&amp; v1 !== void 0 ? v1 : [], v2 !== null &amp;&amp; v2 !== void 0 ? v2 : []);
      }), _f = _e.beforeUnmount, beforeUnmount = _f === void 0 ? [] : _f, _g = _e.afterUnmount, afterUnmount = _g === void 0 ? [] : _g, _h = _e.afterMount, afterMount = _h === void 0 ? [] : _h, _j = _e.beforeMount, beforeMount = _j === void 0 ? [] : _j, _k = _e.beforeLoad, beforeLoad = _k === void 0 ? [] : _k;
      return [4
      /*yield*/
      , execHooksChain(toArray(beforeLoad), app)];

    case 5:
      _m.sent(); // cache the execScripts returned promise


      if (!appExportPromiseCaches[appName]) {
        appExportPromiseCaches[appName] = execScripts(global, !singular);
      }

      return [4
      /*yield*/
      , appExportPromiseCaches[appName]];

    case 6:
      scriptExports = _m.sent();

      if (validateExportLifecycle(scriptExports)) {
        // eslint-disable-next-line prefer-destructuring
        bootstrap = scriptExports.bootstrap; // eslint-disable-next-line prefer-destructuring

        mount = scriptExports.mount; // eslint-disable-next-line prefer-destructuring

        unmount = scriptExports.unmount;
      } else {
        if (process.env.NODE_ENV === &#39;development&#39;) {
          console.warn(&#34;[qiankun] lifecycle not found from &#34; + appName + &#34; entry exports, fallback to get from window[&#39;&#34; + appName + &#34;&#39;]&#34;);
        }

        globalVariableExports = global[appName];

        if (validateExportLifecycle(globalVariableExports)) {
          // eslint-disable-next-line prefer-destructuring
          bootstrap = globalVariableExports.bootstrap; // eslint-disable-next-line prefer-destructuring

          mount = globalVariableExports.mount; // eslint-disable-next-line prefer-destructuring

          unmount = globalVariableExports.unmount;
        } else {
          delete appExportPromiseCaches[appName];
          throw new Error(&#34;[qiankun] You need to export lifecycle functions in &#34; + appName + &#34; entry&#34;);
        }
      }

      _l = getMicroAppStateActions(appInstanceId), onGlobalStateChange = _l.onGlobalStateChange, setGlobalState = _l.setGlobalState, offGlobalStateChange = _l.offGlobalStateChange;
      return [2
      /*return*/
      , {
        name: appInstanceId,
        bootstrap: [bootstrap],
        mount: [function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              switch (_a.label) {
                case 0:
                  return [4
                  /*yield*/
                  , validateSingularMode(singular, app)];

                case 1:
                  if (_a.sent() &amp;&amp; prevAppUnmountedDeferred) {
                    return [2
                    /*return*/
                    , prevAppUnmountedDeferred.promise];
                  }

                  return [2
                  /*return*/
                  , undefined];
              }
            });
          });
        }, // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕
        function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              // element would be destroyed after unmounted, we need to recreate it if it not exist
              element = element || createElement(appContent, strictStyleIsolation);
              render({
                element: element,
                loading: true
              });
              return [2
              /*return*/
              ];
            });
          });
        }, // exec the chain after rendering to keep the behavior with beforeLoad
        function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , execHooksChain(toArray(beforeMount), app)];
            });
          });
        }, mountSandbox, function (props) {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , mount(__assign(__assign({}, props), {
                container: containerGetter(),
                setGlobalState: setGlobalState,
                onGlobalStateChange: onGlobalStateChange
              }))];
            });
          });
        }, // 应用 mount 完成后结束 loading
        function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , render({
                element: element,
                loading: false
              })];
            });
          });
        }, function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , execHooksChain(toArray(afterMount), app)];
            });
          });
        }, // initialize the unmount defer after app mounted and resolve the defer after it unmounted
        function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              switch (_a.label) {
                case 0:
                  return [4
                  /*yield*/
                  , validateSingularMode(singular, app)];

                case 1:
                  if (_a.sent()) {
                    prevAppUnmountedDeferred = new Deferred();
                  }

                  return [2
                  /*return*/
                  ];
              }
            });
          });
        }],
        unmount: [function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , execHooksChain(toArray(beforeUnmount), app)];
            });
          });
        }, function (props) {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , unmount(__assign(__assign({}, props), {
                container: containerGetter()
              }))];
            });
          });
        }, unmountSandbox, function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              return [2
              /*return*/
              , execHooksChain(toArray(afterUnmount), app)];
            });
          });
        }, function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              render({
                element: null,
                loading: false
              });
              offGlobalStateChange(appInstanceId); // for gc

              element = null;
              return [2
              /*return*/
              ];
            });
          });
        }, function () {
          return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
              switch (_a.label) {
                case 0:
                  return [4
                  /*yield*/
                  , validateSingularMode(singular, app)];

                case 1:
                  if (_a.sent() &amp;&amp; prevAppUnmountedDeferred) {
                    prevAppUnmountedDeferred.resolve();
                  }

                  return [2
                  /*return*/
                  ];
              }
            });
          });
        }]
      }];
  }
});

});
}

  • registerApplication是single-spa的方法,我们这里通过loadApp这个方法,对数据进行处理
代码语言:javascript
复制
function registerApplication(appNameOrConfig, appOrLoadApp, activeWhen, customProps) {
const registration = sanitizeArguments(appNameOrConfig, appOrLoadApp, activeWhen, customProps);
if (getAppNames().indexOf(registration.name) !== -1) throw Error(formatErrorMessage(21, There is already an app registered with name ${registration.name}, registration.name));
apps.push(assign({
loadErrorTime: null,
status: NOT_LOADED,
parcels: {},
devtools: {
overlays: {
options: {},
selectors: []
}
}
}, registration));

if (isInBrowser) {
ensureJQuerySupport();
reroute();
}
}

  • 上面这个函数,应该是整个微前端框架最复杂的地方,它最终会返回一个函数,当成函数传递给single-spa这个库的registerApplication方法使用
  • 它的内部是switch case逻辑,然后返回一个数组
  • 这是一个逻辑判断
代码语言:javascript
复制
case 0:
entry = app.entry, appName = 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(子应用地址)
代码语言:javascript
复制
function importEntry(entry) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _opts$fetch3 = opts.fetch,
fetch = _optsfetch3 === void 0 ? defaultFetch : _optsfetch3,
_opts$getTemplate = opts.getTemplate,
getTemplate = _optsgetTemplate === void 0 ? defaultGetTemplate : _optsgetTemplate;
var getPublicPath = opts.getPublicPath || opts.getDomain || _utils.defaultGetPublicPath;

if (!entry) {
throw new SyntaxError('entry should not be empty!');
} // html entry

if (typeof entry === 'string') {
return importHTML(entry, {
fetch: fetch,
getPublicPath: getPublicPath,
getTemplate: getTemplate
});
} // config entry

if (Array.isArray(entry.scripts) || Array.isArray(entry.styles)) {
var _entry$scripts = entry.scripts,
scripts = _entryscripts === void 0 ? [] : _entryscripts,
_entry$styles = entry.styles,
styles = _entrystyles === void 0 ? [] : _entrystyles,
_entry$html = entry.html,
html = _entryhtml === void 0 ? &#39;&#39; : _entryhtml;
return getEmbedHTML(getTemplate(html), styles, {
fetch: fetch
}).then(function (embedHTML) {
return {
template: embedHTML,
assetPublicPath: getPublicPath('/'),
getExternalScripts: function getExternalScripts() {
return _getExternalScripts(scripts, fetch);
},
getExternalStyleSheets: function getExternalStyleSheets() {
return _getExternalStyleSheets(styles, fetch);
},
execScripts: function execScripts(proxy, strictGlobal) {
if (!scripts.length) {
return Promise.resolve();
}

      return _execScripts(scripts[scripts.length - 1], scripts, proxy, {
        fetch: fetch,
        strictGlobal: strictGlobal
      });
    }
  };
});

} else {
throw new SyntaxError('entry scripts or styles should be array!');
}
}

  • 上面代码里最重要的,如果我们entry传入字符串,那么就会使用这个函数去加载HTML内容(其实微前端的所有子应用加载,都是把dom节点加载渲染到基座的index.html文件中的一个div标签内)
代码语言:javascript
复制
  if (typeof entry === 'string') {
return importHTML(entry, {
fetch: fetch,
getPublicPath: getPublicPath,
getTemplate: getTemplate
});
} // config entry
  • importHTML这个函数,就是我们今晚最重要的一个点
  • 传入url地址,发起fetch请求(此时由于域名或者端口不一样,会出现跨域,所有子应用的热更新开发模式下,webpack配置要做以下处理,部署也要考虑这个问题)
代码语言:javascript
复制
 devServer: {
contentBase: path.resolve(__dirname, '../'),
port: CONFIG.serverPort,
host: 'rental-dev.mysoft.com.cn',
historyApiFallback: true,
disableHostCheck: true,
headers: {
'Access-Control-Allow-Origin': '',
},
stats: {
timings: true,
assets: false,
entrypoints: false,
modules: false
},
proxy: {
'/api/
': {
target: CONFIG.proxyServer,
changeOrigin: true,
secure: false,
pathRewrite: {
'^/api': ''
},
headers: {
// Cookie: 'RENTALCENTER=5fc10ee067fa5d15a9a7840bd4a75dc98dc7f47a'
}
}
},
before(app) {
app.get('/cookie/set', (req, res) => {
const cookies = req.query
console.log('resolve cookie')
for (const cookie in cookies) {
if (Object.prototype.hasOwnProperty.call(cookies, cookie)) {
res.cookie(cookie, cookies[cookie], {
httpOnly: true
})
}
}
res.redirect(
${req.protocol}://${req.host}:${CONFIG.serverPort}${ CONFIG.baseAlias }
)
})
},
inline: true,
hot: true // 新增
},
整个importHTML函数好像很长很长,但是我们就看最重要的地方,一个框架(库),流程线很长+版本迭代原因,需要兼容老的版本,所以很多源码对于我们其实是无用的
代码语言:javascript
复制
function importHTML(url) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var fetch = defaultFetch;
var getPublicPath = _utils.defaultGetPublicPath;
var getTemplate = defaultGetTemplate; // compatible with the legacy importHTML api

if (typeof opts === 'function') {
fetch = opts;
} else {
fetch = opts.fetch || defaultFetch;
getPublicPath = opts.getPublicPath || opts.getDomain || _utils.defaultGetPublicPath;
getTemplate = opts.getTemplate || defaultGetTemplate;
}

return embedHTMLCache[url] || (embedHTMLCache[url] = fetch(url).then(function (response) {
return response.text();
}).then(function (html) {
var assetPublicPath = getPublicPath(url);

var _processTpl = (0, _processTpl2[&#34;default&#34;])(getTemplate(html), assetPublicPath),
    template = _processTpl.template,
    scripts = _processTpl.scripts,
    entry = _processTpl.entry,
    styles = _processTpl.styles;

return getEmbedHTML(template, styles, {
  fetch: fetch
}).then(function (embedHTML) {
  return {
    template: embedHTML,
    assetPublicPath: assetPublicPath,
    getExternalScripts: function getExternalScripts() {
      return _getExternalScripts(scripts, fetch);
    },
    getExternalStyleSheets: function getExternalStyleSheets() {
      return _getExternalStyleSheets(styles, fetch);
    },
    execScripts: function execScripts(proxy, strictGlobal) {
      if (!scripts.length) {
        return Promise.resolve();
      }

      return _execScripts(entry, scripts, proxy, {
        fetch: fetch,
        strictGlobal: strictGlobal
      });
    }
  };
});

}));
}

  • 整个函数,最后返回了一个对象,这里很明显,通过fetch请求,获取了对应子应用entry入口的资源文件后,转换成了字符串
  • 这里processTpl其实就是对这个子应用的dom模版(字符串格式)进行一个数据拼装,其实也不是很复杂,由于时间关系,可以自己看看过程,重点看结果
  • 这里的思想,是redux的中间件源码思想,将数据进行了一层包装,高可用使用
代码语言:javascript
复制
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$match = match.match(LINK_HREF_REGEX),
_matchmatch2 = (0, _slicedToArray2[&#34;default&#34;])(_matchmatch, 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 matchedScriptEntry = match.match(SCRIPT_ENTRY_REGEX);
var matchedScriptSrcMatch = 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);
}

    entry = entry || matchedScriptEntry &amp;&amp; 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;
}
});
scripts = 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的字符串了,而是一个对象,而且脚本样式都分离了
代码语言:javascript
复制
return {
template: template,
scripts: scripts,
styles: styles,
// set the last script as entry if have not set
entry: entry || scripts[scripts.length - 1]
};
  • 这个是框架帮我们处理的,必须要设置一个入口js文件
代码语言:javascript
复制
 // set the last script as entry if have not set
  • 下面是真正的single-spa源码,注册子应用,用apps这个数组去收集所有的子应用(数组每一项已经拥有了脚本、html、css样式的内容)
此时我们只要根据我们之前编写的activeRule和监听前端路由变化去控制展示子应用即可,原理如下:(今天不做过多讲解这块)
代码语言:javascript
复制
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的中间件思想,劫持了事件,然后进行派发,优先调用微前端框架的路由事件,然后进行过滤展示子应用:
代码语言:javascript
复制
export function getAppsToLoad() {
return APPS.filter(notSkipped).filter(withoutLoadError).filter(isntLoaded).filter(shouldBeActive);
}
整个微前端的触发流程图

相信通过此文,你能真正了解微前端的使用原理,后期我会出一个手写微前端框架的文章

最后

点个赞支持我吧,转发就更好了