关于微前端:微前端框架-qiankun-技术分析

38次阅读

共计 4603 个字符,预计需要花费 12 分钟才能阅读完成。

咱们在 single-spa 技术剖析 根本实现了一个微前端框架须要具备的各种性能,然而又实现的不够彻底,遗留了很多问题须要解决。尽管官网提供了很多样例和最佳实际,然而总显得过于薄弱,总给人一种“问题解决了,然而又没有齐全解决”的感觉。

qiankun 在 single-spa 的根底上做了二次开发,欠缺了很多性能,算是一个比拟齐备的微前端框架了。明天咱们来聊一聊 qiankun 的技术原理。

在本系列的结尾,咱们提到微前端的外围问题其实就是解决如何加载子利用以及如果做好子利用间的隔离问题。所以,咱们从这两点来看 qiankun 的实现。

如何加载子利用

single-spa 通过 js entry 的模式来加载子利用。而 qiankun 采纳了 html entry 的模式。这两种形式的优缺点咱们在了解微前端技术原理中曾经做过剖析,这里不再赘述,咱们看看 qiankun 是如何实现 html entry 的。

qiankun 提供了一个 API registerMicroApps 来注册子利用,其外部调用 single-spa 提供的 registerApplication 办法。在调用 registerApplication 之前,会调用外部的 loadApp 办法来加载子利用的资源,初始化子利用的配置。

通过浏览 loadApp 的代码,咱们发现,qiankun 通过 import-html-entry 这个包来加载子利用。import-html-entry 的作用就是通过解析子利用的入口 html 文件,来获取子利用的 html 模板、css 款式和入口 JS 导出的生命周期函数。

import-html-entry

import-html-entry 是这样工作的,假如咱们有如下 html entry 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>

<!-- mark the entry script with entry attribute -->
<script src="https://unpkg.com/mobx@5.0.3/lib/mobx.umd.js" entry></script>
<script src="https://unpkg.com/react@16.4.2/umd/react.production.min.js"></script>
</body>
</html>

咱们应用 import-html-entry 来解析这个 html 文件:

import importHTML from 'import-html-entry';

importHTML('./subApp/index.html')
    .then(res => {console.log(res.template);

        res.execScripts().then(exports => {
            const mobx = exports;
            const {observable} = mobx;
            observable({name: 'kuitos'})
        })
});

importHTML 的返回值有如下几个属性:

  • template 解决后的 HTML 模板
  • assetPublicPath 动态资源的公共门路
  • getExternalScripts 获取所有内部脚本的函数,返回脚本门路
  • getExternalStyleSheets 获取所有内部款式的函数,返回款式文件的门路
  • execScripts 执行脚本的函数

importHTML 的返回值中,除了几个工具类的办法,最重要的就是 templateexecScripts 了。

importHTML('./subApp/index.html') 的整个执行过程代码比拟长,咱们只讲一下大略的执行原理,感兴趣的同学能够自行查看 importHTML 的源码。

importHTML 首先会通过 fetch 函数申请具体的 html 内容,而后在 processTpl 函数 中通过一系列简单的正则匹配,解析出 html 中的款式文件和 js 文件。

importHTML 函数返回值为 {template, scripts, entry, styles},别离是 html 模板,html 中的 js 文件(蕴含内嵌的代码和通过链接加载的代码),子利用的入口文件,html 中的款式文件(同样是蕴含内嵌的代码和通过链接加载的代码)。

之后通过 getEmbedHTML 函数 将所有应用内部链接加载的款式全副转化成内嵌到 html 中的款式。getEmbedHTML 返回的 html 就是 importHTML 函数最终返回的 template 内容。

当初,咱们看看 execScripts 是怎么实现的。

execScripts 外部会调用 getExternalScripts 加载所有 js 代码的文本内容,而后通过 eval("code") 的模式执行加载的代码。

留神,execScripts 的函数签名是这样的 (sandbox?: object, strictGlobal?: boolean, execScriptsHooks?: ExecScriptsHooks): Promise<unknown>。容许咱们传入一个沙箱对象,如果子利用依照微前端的标准打包,那么会在全局对象上设置 mountunmount 这几个生命周期函数属性。execScripts 在执行 eval("code") 的时候,会奇妙的把咱们指定的沙箱最为全局对象包装到 "code" 中,子利用可能运行在沙盒环境中。

在执行完 eval("code") 当前,就能够从沙盒对象上获取子利用导出的生命周期函数了。

loadApp

当初咱们把眼帘拉回 loadApp 中,loadApp 在获取到 templateexecScripts 这些信息当前,会基于 template 生成 render 函数用于渲染子利用的页面。之后会依据须要生成沙盒,并将沙盒对象传给 execScripts 来获取子利用导出的申明周期函数。

之后,在子利用生命周期函数的根底上,构建新的生命周期函数,再调用 single-spa 的 API 启动子利用。

在这些新的生命周期函数中,会在不同机会负责启动沙盒、渲染子利用、清理沙盒等事务。

隔离

在实现子利用的加载当前,作为一个微前端框架,要解决好子利用的隔离问题,次要要解决 JS 隔离和款式隔离这两方面的问题。

JS 隔离

qiankun 为依据浏览器的能力创立两种沙箱,在老旧浏览器中会创立快照模式 的浏览器中创立 VM 模式的沙箱 ProxySandbox

篇幅限度,咱们只看 ProxySandbox 的实现,在其构造函数中,咱们能够看到具体的逻辑:首先会依据用户指定的全局对象(默认是 window)创立一个 fakeWindow,之后在这个 fakeWindow 上创立一个 proxy 对象,在子利用中,这个 proxy 对象就是全局变量 window

constructor(name: string, globalContext = window) {const { fakeWindow, propertiesWithGetter} = createFakeWindow(globalContext);
  const proxy = new Proxy(fakeWindow, {set: (target: FakeWindow, p: PropertyKey, value: any): boolean => {},
      get: (target: FakeWindow, p: PropertyKey): any => {},
      has(target: FakeWindow, p: string | number | symbol): boolean {},

      getOwnPropertyDescriptor(target: FakeWindow, p: string | number | symbol): PropertyDescriptor | undefined {},

      ownKeys(target: FakeWindow): ArrayLike<string | symbol> {},

      defineProperty(target: Window, p: PropertyKey, attributes: PropertyDescriptor): boolean {},

      deleteProperty: (target: FakeWindow, p: string | number | symbol): boolean => {},

      getPrototypeOf() {return Reflect.getPrototypeOf(globalContext);
      },
    });
  this.proxy = proxy;
}

其实 qiankun 中的沙箱分两个类型:

  • app 环境沙箱
    app 环境沙箱是指利用初始化过之后,利用会在什么样的上下文环境运行。每个利用的环境沙箱只会初始化一次,因为子利用只会触发一次 bootstrap。子利用在切换时,实际上切换的是 app 环境沙箱。
  • render 沙箱
    子利用在 app mount 开始前生成好的的沙箱。每次子利用切换过后,render 沙箱都会重现初始化。

下面说的 ProxySandbox 其实是 render 沙箱。至于 app 环境沙箱,qiankun 目前只针对在利用 bootstrap 时动态创建款式链接、脚本链接等副作用打了补丁,保障子利用切换时这些副作用互不烦扰。

之所以设计两层沙箱,是为了保障每个子利用切换回来之后,还能运行在利用 bootstrap 之后的环境下。

款式隔离

qiankun 提供了多种款式隔离形式,隔离成果最好的是 shadow dom,然而因为其存在诸多限度,qiankun 官网在未来的版本中将会弃用,转而推广 experimentalStyleIsolation 计划。

咱们能够通过上面这段代码看到 experimentalStyleIsolation 计划的基本原理。

const styleNodes = appElement.querySelectorAll('style') || [];
forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {css.process(appElement!, stylesheetElement, appInstanceId);
});

css.process 的外围逻辑,就是给读取到的子利用的款式增加带有子利用信息的前缀。成果如下:

/* 假如利用名是 react16 */
.app-main {font-size: 14px;}

div[data-qiankun-react16] .app-main {font-size: 14px;}

通过下面的隔离办法,根本能够保障子利用间的款式互不影响。

小结

qiankun 在 single-spa 的根底上依据理论的生产实践开发了很多有用的性能,大大降低了微前端的应用老本。

本文仅仅针对如何加载子利用和如何做好子利用间的隔离这两个问题,介绍了 qiankun 的实现。其实,在隔离这个问题上,qiankun 也仅仅是依据理论中会遇到的状况做了必要的隔离措施,并没有像 iframe 那样实现齐全的隔离。咱们能够说 qiankun 实现的隔离有缺点,也能够说是 qiankun 在理论的业务需要和齐全隔离的实现老本之间做的取舍。

常见面试知识点、技术计划剖析、教程,都能够扫码关注公众号“众里千寻”获取,或者来这里 https://everfind.github.io/po…。

正文完
 0