微前端框架 qiankun 技术分析
我们在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 的返回值中,除了几个工具类的方法,最重要的就是 template 和 execScripts 了。
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>。允许我们传入一个沙箱对象,如果子应用按照微前端的规范打包,那么会在全局对象上设置 mount、unmount 这几个生命周期函数属性。execScripts 在执行 eval("code") 的时候,会巧妙的把我们指定的沙箱最为全局对象包装到 "code" 中,子应用能够运行在沙盒环境中。
在执行完 eval("code") 以后,就可以从沙盒对象上获取子应用导出的生命周期函数了。
loadApp
现在我们把视线拉回 loadApp 中,loadApp 在获取到 template、execScripts 这些信息以后,会基于 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/posts/ 。

微前端框架 qiankun 技术分析的更多相关文章
- 微前端框架 single-spa 技术分析
在理解微前端技术原理中我们介绍了微前端的概念和核心技术原理.本篇我们结合目前业内主流的微前端实现 single-spa 来说明在生产实践中是如何实现微前端的. single-spa 的文档略显凌乱,概 ...
- 微前端框架 之 qiankun 从入门到源码分析
封面 简介 从 single-spa 的缺陷讲起 -> qiankun 是如何从框架层面解决 single-spa 存在的问题 -> qiankun 源码解读,带你全方位刨析 qianku ...
- 极致简洁的微前端框架-京东MicroApp开源了
前言 MicroApp是一款基于类WebComponent进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度.提升工作效率.它是目前市面上接入微前端成本最低的 ...
- 微前端(qiankun)主应用共享React组件
前言 最近需要重构一个老项目,定的方案用微前端去改造.主应用是老的项目,微应用是新的项目,由于重构时间比较紧张,子应用还需要使用父应用的一些组件.过程中遇到一些问题,记录一下. 方案 我们知道qian ...
- 基于 iframe 的微前端框架 —— 擎天
vivo 互联网前端团队- Jiang Zuohan 一.背景 VAPD是一款专为团队协作办公场景设计的项目管理工具,实践敏捷开发与持续交付,以「项目」为核心,融合需求.任务.缺陷等应用,使用敏捷迭代 ...
- 微前端框架single-spa初探
前言 最近入职的一家公司采用single-spa这个微前端框架,所以自学了此框架. single-spa这个微前端框架虽然有中文文档,但是有些零散和晦涩. 所以我想在学习之余,写篇博客拉平一下这个学习 ...
- 【微前端】微前端最终章-qiankun指南以及微前端整体探索
序 这才2月中旬,广州就已经渐渐地进入了夏季,--夏天总是让人焦虑的.过年闲暇时间写下了微前端这系列的终章,欢迎拍砖.如果你习惯直接上手代码,不妨跳到实践一节,直接上代码教程玩一玩. qiankun原 ...
- 微前端框架 single-spa
单体应用对比前端微服务化 普通的前端单体应用 微前端架构 1.基本概念 实现一套微前端架构,可以把其分成四部分(参考:https://alili.tech/archive/11052bf4/) 加载器 ...
- 基于微前端qiankun的多页签缓存方案实践
作者:vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun,实现多页签及子应用缓存的方案,同时还类比了多个不同方案之间的区别及优劣势,为使用微前端进行多页签开 ...
随机推荐
- [loj3347]有趣的旅途
考虑求出重心,以0为根建树,求出第 $i$个点的子树大小$sz[i]$($a(0,i)$),则满足$n-sz[i]\le \lfloor\frac{n}{2}\rfloor$的$sz[i]$中的最小值 ...
- C++ and OO Num. Comp. Sci. Eng. - Part 4.
命名空间与文件(Namespaces and Files) 在 C++ 中,命名空间为包含相关声明与定义的逻辑单元. 将一个大程序分割为不同部分并且将其储存在不同的文件中可以实现模块化编程. 未命名的 ...
- mysql-计算排名
mysql计算排名,获取行号rowno 学生成绩表数据 SELECT * FROM table_score ORDER BY score DESC; 获取某个学生成绩排名并计算该学生和上一名学生成绩差 ...
- EXCEL-对筛选出(单独手动隐藏行还是在统计范围内)的表格数据进行统计
=SUBTOTAL(3,A1:A5) #计算筛选出的表格中A1:A5中有几个值. =SUBTOTAL(3,I71:I21447) ,在I71:I21447之间计数,会自动略去没有筛选上的隐藏单元格 ...
- 搭建zabbix服务器常见问题解析处理
1. 找不到url 2. 服务器无法处理当前请求,PHP解析出错 3. 服务器无法处理当前请求,权限不足 1. 找不到url 浏览器报错:The requested URL /zabbix/ was ...
- springcloud - alibaba快速上手 - 更新完毕
1.简单对比一下springcloud与springcloud-alibaba 2.准备知识 官网:https://nacos.io/zh-cn/ 查看cloud和springboot的对应关系 ht ...
- Spring整合Mybatis报 java.lang.ClassNotFoundException:org.springframework.core.metrics.ApplicationStartup,即:spring的版本过高,采用RELEASE稳定版
1.遇到的问题: 今天在弄spring整合mybatis的时候遇到一个小问题,如图所示: 简单来说:就是我的spring的xml文件没找到,我就奇了怪了,我所有的配置都没问题啊! 我pom.xml配置 ...
- Hadoop入门 运行环境搭建
模板虚拟机 目录 模板虚拟机 1 硬件 2 操作系统 3 IP地址和主机名称 vm windows10 Hadoop100服务器 远程访问工具 其他准备 克隆虚拟机 克隆 修改主机名/ip 安装jdk ...
- 日常Java 2021/9/23
练习使用Math.random函数,以及JOptionPane.showMessageDialog(null,"字符串","Results",JOptionPa ...
- Android Https相关完全解析
转载: 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/48129405: 本文出自:[张鸿洋的博客] 一.概述 其实这篇文章理论 ...