我们在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/posts/

微前端框架 qiankun 技术分析的更多相关文章

  1. 微前端框架 single-spa 技术分析

    在理解微前端技术原理中我们介绍了微前端的概念和核心技术原理.本篇我们结合目前业内主流的微前端实现 single-spa 来说明在生产实践中是如何实现微前端的. single-spa 的文档略显凌乱,概 ...

  2. 微前端框架 之 qiankun 从入门到源码分析

    封面 简介 从 single-spa 的缺陷讲起 -> qiankun 是如何从框架层面解决 single-spa 存在的问题 -> qiankun 源码解读,带你全方位刨析 qianku ...

  3. 极致简洁的微前端框架-京东MicroApp开源了

    前言 MicroApp是一款基于类WebComponent进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度.提升工作效率.它是目前市面上接入微前端成本最低的 ...

  4. 微前端(qiankun)主应用共享React组件

    前言 最近需要重构一个老项目,定的方案用微前端去改造.主应用是老的项目,微应用是新的项目,由于重构时间比较紧张,子应用还需要使用父应用的一些组件.过程中遇到一些问题,记录一下. 方案 我们知道qian ...

  5. 基于 iframe 的微前端框架 —— 擎天

    vivo 互联网前端团队- Jiang Zuohan 一.背景 VAPD是一款专为团队协作办公场景设计的项目管理工具,实践敏捷开发与持续交付,以「项目」为核心,融合需求.任务.缺陷等应用,使用敏捷迭代 ...

  6. 微前端框架single-spa初探

    前言 最近入职的一家公司采用single-spa这个微前端框架,所以自学了此框架. single-spa这个微前端框架虽然有中文文档,但是有些零散和晦涩. 所以我想在学习之余,写篇博客拉平一下这个学习 ...

  7. 【微前端】微前端最终章-qiankun指南以及微前端整体探索

    序 这才2月中旬,广州就已经渐渐地进入了夏季,--夏天总是让人焦虑的.过年闲暇时间写下了微前端这系列的终章,欢迎拍砖.如果你习惯直接上手代码,不妨跳到实践一节,直接上代码教程玩一玩. qiankun原 ...

  8. 微前端框架 single-spa

    单体应用对比前端微服务化 普通的前端单体应用 微前端架构 1.基本概念 实现一套微前端架构,可以把其分成四部分(参考:https://alili.tech/archive/11052bf4/) 加载器 ...

  9. 基于微前端qiankun的多页签缓存方案实践

    作者:vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun,实现多页签及子应用缓存的方案,同时还类比了多个不同方案之间的区别及优劣势,为使用微前端进行多页签开 ...

随机推荐

  1. 如何解决 ASP.NET Core 中的依赖问题

    依赖性注入是一种技术,它允许我们注入一个特定类的依赖对象,而不是直接创建这些实例. 使用依赖注入的好处显而易见,它通过放松模块间的耦合,来增强系统的可维护性和可测试性. 依赖注入允许我们修改具体实现, ...

  2. char数据可以放入int[]中会自动转换

    int[] ary ={'b','c','a','d','e','f'};System.out.println(ary[0]);//98String str = new String(ary, 2, ...

  3. 重测序(RADseq)做群体遗传分析套路

    实验材料 构建的群体,或自然群体,如各地方品种. RAD文库构建 提取DNA后,构建文库,简要步骤如下: ① 限制性内切酶TaqI酶切: ② 连接P1接头: ③ DNA随机打断片断化: ④ 目的片段回 ...

  4. Oracle——检查数据库是否正常运行,如果没有,并重启数据库

    1.su oracle  切换到linux的oracle账号 需要使用 su -oracle,而不是su oracle;原因是: 先执行exit退出,再重新切换 2.打开数据库监听 lsnrctl l ...

  5. 查看nginx(Web网页服务器)状态是否正常

    Linux每个应用运行都会产生一个进程,那么我们就可以通过查看Nginx进程是否存在来判断它是否启动. 1.有时想知道nigix是否在正常运行,需要用linux命令查看nginx运行情况.执行命令:p ...

  6. Ubuntu 和 windows1下文件夹共享的指令

    第一个是通过拖拉的方式将文件放到当前的目录下面,即 mv +路径 + . 第二个是将文件放到了硬盘里面/mnt/hgfs/linusshare/里面

  7. 修复UE4编辑器,ClearLog操作导致的崩溃

    UE4 4.24.3版本,编辑器Output Log窗口中,右键--Clear Log操作很大概率会导致编辑器奔溃:解决办法: 相关文件: Engine\Source\Developer\Output ...

  8. javaWeb - 3 — JSP (技术已淘汰)— 更新完毕

    1.jsp 在servlet中说过java中前端到后台有两条路线嘛 后台 <------ ajax.json <--------- 前端 后台 <------ jsp( EL.JST ...

  9. 我在项目中是这样配置Vue的

    启用压缩,让页面加载更快 在我们开发的时候,为了方便调试,我们需要使用源码进行调试,但在生产环境,我们追求的更多的是加载更快,体验更好,这时候我们会将代码中的空格注释去掉,对代码进行混淆压缩,只为了让 ...

  10. Stream.toMap

    Collectors类的tomap方法将流收集到映射实例中. list 转 map collection.stream().collect(Collectors.toMap(User::getId, ...