在我之前的文章提到过,微前端的实质是分治的解决前端利用以及利用间的关系,那么更进一步,落地一个微前端框架,就会波及到三点外围因素:

  • 子利用的加载;
  • 利用见运行时隔离;
  • 路由劫持;

对于 qiankun 来说,路由劫持是在 single-spa 下来做的,而 qiankun 给咱们提供的能力,次要便是子利用的加载和沙箱隔离。

我将分为三个 topic 去讲,这边文章次要基于 qiankun 源码像大家讲一下微前端子利用的加载。

qiankun 是 single-spa 的一层封装,而 qiankun 中,真正去加载解析子利用的逻辑是在 import-html-entry 这个包中实现的。

html 解析

首先,当咱们配置子利用的 entry 后,qiankun 会去通过 fetch 获取到子利用的 html 字符串(这就是为什么须要子利用资源容许跨域)
拿到 html 字符串后,会调用 processTpl 办法通过一大堆正则去匹配获取 html 中对应的 js(内联、外联)、css(内联、外联)、正文、入口脚本 entry 等等。processTpl 办法会返回咱们加载子利用所须要的四个组成部分:

  • template:html 模板;
  • script:js 脚本(内联、外联);
  • styles:css 样式表(内联、外联);
  • entry:子利用入口 js 脚本文件,如果没有默认以解析后的最初一个 js 脚本代替;
export default function processTpl(tpl, baseURI) {  // 省略具体代码,这里是对各种css、js等资源各种写法的预处理,用于标准前面对资源的对立解决  return {    template, // html 模板    scripts, // js 脚本(内联、外联)    styles, // css 样式表(内联、外联)    entry: entry || scripts[scripts.length - 1], // 子利用入口 js 脚本文件,如果没有默认以解析后的最初一个 js 脚本代替;  };}

css 解决

接下来在拿到子利用的依赖的各种资源关系后,会去通过 fetch 获取 css,并将 css 全副以内联模式嵌入 html 模板中,源码地位。到此对 css 的解决大抵就实现了。

function getEmbedHTML(template, styles, opts = {}) {  const { fetch = defaultFetch } = opts;  let embedHTML = template;  // 获取css资源  // getExternalStyleSheets 同时解决了内联和外联css资源  // 其中内联资源会获取css code,外联会先fetch 到css code而后解决  return getExternalStyleSheets(styles, fetch).then((styleSheets) => {    embedHTML = styles.reduce((html, styleSrc, i) => {      // 内联解决全副的css资源      html = html.replace(        genLinkReplaceSymbol(styleSrc),        `<style>/* ${styleSrc} */${styleSheets[i]}</style>`      );      return html;    }, embedHTML);    return embedHTML;  });}

这里我以实在我的项目做了比照:

未嵌入父利用的子利用:

嵌入父利用的子利用:

js 解决

接下来是对 js 的解决,这里 qiankun 和 icestack 的解决模式就不同了。

首先简略说下 icestark,icestark 是在解析完 html 后拿到子利用的 js 依赖,通过动态创建 script 标签的模式去加载 js,因而在 icestark 是忽视 js 跨域的(icestark 的 entry 模式和 url 模式均是如此,区别在于 entry 模式多了一步 fetch 拉 html 字符串并解析 js、css 依赖,而 url 模式只须要制订子利用的脚本和款式依赖即可)。

而 qiankun 则采纳了另一种方法,首先同理睬先通过 fetch 获取外联的 js 字符串。源码地位

export function getExternalScripts(  scripts,  fetch = defaultFetch,  errorCallback = () => {}) {  const fetchScript = (scriptUrl) => {    // 通过fetch 获取js资源,如果有缓存从缓存拿    // 略  };  return Promise.all(    scripts.map((script) => {      if (typeof script === "string") {        if (isInlineCode(script)) {          // 获取内联的js code          return getInlineCode(script);        } else {          // fetch 获取外联的js code          return fetchScript(script);        }      } else {        // 下面说过了,processTpl 办法会解决各种js css资源,其中对于须要异步执行的js资源会打上async标识        // 打上async标识的js资源,会应用requestIdleCallback提早执行        const { src, async } = script;        if (async) {          return {            src,            async: true,            content: new Promise((resolve, reject) =>              requestIdleCallback(() => fetchScript(src).then(resolve, reject))            ),          };        }        return fetchScript(src);      }    })  );}

接下来会创立一个匿名自执行函数包裹住获取到的 js 字符串,最初通过 eval 去创立一个执行上下文执行 js 代码。源码地位。

function getExecutableScript(scriptSrc, scriptText, proxy, strictGlobal) {  const sourceUrl = isInlineCode(scriptSrc)    ? ""    : `//# sourceURL=${scriptSrc}\n`;  window.proxy = proxy;  return strictGlobal    ? `;(function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy);`    : `;(function(window, self){;${scriptText}\n${sourceUrl}}).bind(window.proxy)(window.proxy, window.proxy);`;}

默认不会通过 with 劫持 window 对象的作用域,咱们通过 webpack 打包后的 bundle 是会带着 with 劫持 window 对象的,为什么须要 with 劫持 window 的作用域,之后的的 sandbox 剖析我会具体介绍,这里我先简略讲下,qiankun 的 sandbox 实现原理是通过 Proxy 代理劫持 window 去做的,那么就会呈现一个问题,window.xxx 这样模式的属性会被劫持掉,然而间接申明的全局对象不会被劫持。

那么,就须要用到 with 去劫持 window 的作用域了。

因为 qiankun 是创立本人的执行上下文执行子利用的 js,因而在加载后的子利用中是看不到 js 资源援用的,仅有一个资源被执行替换的标识。

在 qiankun 中,通过调用 import-html-entry 导出的 execScript 函数,能够获取到子利用导出的生命周期勾子。

// get the lifecycle hooks from module exports// 下面说过的eval包裹js代码返回可执行的bundle就是execScripts次要做的事const scriptExports: any = await execScripts(global, !singular);const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(  scriptExports,  appName,  global);

总结

到此,子利用加载的外围逻辑就说完了,具体的实现以及一些其余粗疏逻辑的解决大家能够去看源码。下一片文章我将从 quankun 的源码像大家介绍微前端沙箱的实现。

举荐浏览

  • 《qiankun 官网文档》
  • 《解密微前端:"巨石利用"的诞生》