你听说过微前端框架 Satum 吗?
共 4602字,需浏览 10分钟
·
2022-06-07 14:49
?缘起
hello,大家好,我是 RISC_V 生态业务团队的前端架构师 谷童,本文是我 5.28 参加早早聊分享之后沉淀的一篇文章,文字版讲解 Satum 的性能优化策略,可能会更合一部分同学的口味。声明一下,本文不是对分享的照搬,略有删减。如果喜欢视频版,请查阅早早聊 C43 大会录播。
什么是 Satum?
考虑到部分小伙伴对 Satum 不是很熟悉,我先简单介绍一下这个框架。相比于市面上已经开源的微前端框架,Satum 最重要的特点是面向多实例集成的场景,且诸如沙箱、缓存等功能是可插拔的。展开来说,该框架类似 express/koa
等框架,是微内核化的,支持中间件和插件机制。所以可以基于该内核,定制出适合自己团队的微前端体系。其他特性如集成能力强、共享机制灵活、无缝支持 Vite,如果想进一步了解,请访问官网地址。再看个简单的示例吧,该示例也有线上版本,感兴趣的话可以去编辑下。
import {
MidwareName,
HistoryType,
register,
start,
use,
corsRuleLabel,
} from '@satumjs/core';
import { simpleSandboxMidware } from '@satumjs/simple-midwares';
import midwareSingleSpa from '@satumjs/midware-single-spa';
register({
name: 'vue-todomvc',
entry: 'https://todomvc.com/examples/vue/',
history: HistoryType.HASH,
rules: {
rule: '/',
container: '#mountNode',
},
});
use((system, _, next) => {
// 设置跨域服务,使用中间件节点 MidwareName.urlOption
// 该节点设置类型请参考 https://satumjs.github.io/website/guide/midware/flow-nodes.html#%E6%8C%89%E6%94%AF%E6%8C%81%E3%80%8E%E5%8D%95%E4%B8%AA-%E5%A4%9A%E4%B8%AA%E3%80%8F%E5%88%92%E5%88%86
system.set(MidwareName.urlOption, {
corsRule: `https://vklife.fun/proxy?target=${corsRuleLabel}`,
});
next();
});
// 使用简单的沙箱
use(simpleSandboxMidware);
// 使用 single-spa 中间件处理微应用的加载/卸载
use(midwareSingleSpa);
start({ hostHistoryType: HistoryType.HASH });
不用纠结里面的代码,简单看下即可。use
方法就是对中间件的调用。这样几十行代码,执行后就能看的@尤大 写的 vue-todo 被集成到微应用里了。
从传统优化策略中借鉴
传统优化策略,比较熟悉的莫过于“雅虎N条军规”了。我梳理一下它的内容,发现它包含以下 7 大板块。其中很多“军规”已经融入开发中了,比如服务器端的那几条。
梳理后,发现可以借鉴的有以下几条:
⚠️ 减少 HTTP 请求数 ⚠️ 保持单个内容小于 25K (偏移动端) ✅ 缓存 Ajax 请求 ✅ 预加载 / 延迟加载 ✅ 尽早刷新输出缓冲
其中前两条分别需要在业务中优化,着重看后三条。缓存 Ajax 请求是后面的重头戏,这里先略过。
预加载 / 延迟加载
Satum 已经实现了预加载,当页面第一次进入时,框架会通过 requestIdleCallback 在空闲时间拉取非激活的微应用相关的 html 和静态资源,并放置在缓存中备用。可以在 start 函数执行时,传递配置项 prefetch
是 true,就能根据不同终端,预拉取该端所有微应用的资源。延迟加载还没实现,不过已经有一个理论可行的实施思路。后面实现了之后,再在社区同步。
尽早刷新输出缓冲
这条建议军规中是相对于服务端说的,大概意思就是尽快把 html 内容发送到浏览器去呈现。可以借鉴其中的思想,把 html 模板和静态资源内容缓存到浏览器,渲染时优先使用这些资源,并同时拉取远程的内容,当内容不一致时再局部刷新。这个也是目前的设想,Satum 还没实现。后面实现了之后,会在社区同步。
先有度量再有优化
针对微前端中的微应用优化又能做些什么?谈到性能优化,先决条件肯定是度量出运行数据,分析数据才能对症下药。先梳理下微应用的生命周期,来确定度量的埋点。
Satum 把微应用分为三类:新应用、重新激活的应用、沙箱复用的应用。不同类型的应用,生命周期略有不同。
先看新激活的应用,当调度周期开始后,进入资源拉取,拉取完成后进入脚本在沙箱中执行,执行完成后当挂载点 ready 了就插入挂载点进而渲染到页面上。重新激活的应用直接省去了资源远程拉取,而沙箱复用的应用 因为沙箱的状态还在内存中,只需要分配给它路径 展现特定内容的即可。
这里稍微科普下 Satum 对微应用的处理,一则便于理解所谓沙箱复用的应用,二则便于理解后面沙箱层面上的优化。Satum 是面向多实例集成,但也支持一个微应用同一时间多处渲染。所以微应用的承载有 MIcroApp 类和 Actor 类。而一个 microApp 实例关联多个 actor 实例(简单来说,就是 microApp 和 actor 是一对多的关系),每个 actor 都有完整的沙箱但共用一个 microApp 的配置。当新的调度周期开始时,旧的 actor 被卸载,但其相关的 microApp 可能还是处于激活状态的,沙箱就可以被复用,让新的 actor 使用。
梳理完微应用的生命周期,来确定下度量指标包括:资源拉取耗时、脚本执行耗时、渲染耗时和卸载耗时。通过这些耗时,就能直观分析微应用实际的运行状况,灵活的配置报警策略。
而这些耗时数据的收集,是依赖 Satum 对每个阶段做了性能埋点:
第一个 scheduleSystemStart 是页面初始化时,调用 start 方法时触发。
scheduleCycleStart 是页面初始化、每次 URL 变化时,内置的调度开始时触发。在使用时可以当成参考时间。
后面这几个都是成对出现的,和上面分析的生命周期对应,分别是资源拉取、脚本执行、渲染阶段的起始时间。
通过对耗时数据的收集和分析,就能制定优化和报警策略,进而让微应用健康运行。也能让优化的工作可以用数据量化,直观展示价值。
沙箱层面上的优化
使用微前端时,可以很精细地控制每个微应用的调度。可以调度微应用脚本的执行时机,提升沙箱的复用性,内容真正载入页面的时机。下面可以从这三方面来阐述优化点。
脚本执行时机
Satum 因为有很灵活的共享机制,包括三方库的共享。所以当一个微应用依赖共享的三方库时,其脚本执行时机和其他不依赖此的微应用是不一样的。如何设置三方库的依赖呢?可以看下图:
可以看到上面的例子,main-app 是提供共享三方库的微应用,而 community-app 是依赖了三方库的。只需要在配置文件里配置 externals
声明依赖哪些三方库即可。
Satum 不但支持共享三方库,还支持运行时的数据共享和静态数据,当某个三方库由其他微应用共享时,通过嵌套关系形成的依赖链,其子级微应用也就能获取到。
如果不依赖三方库,就不用等待,可以和其他微应用并行执行脚本;而依赖三方库的,就只能等到三方库 ready 时再执行。对于三方库的获取,我使用了链表结构的数据类型,可以很快查询到。
沙箱的复用性
因为沙箱实例的创建和销毁是有很大代价的,可以基于共同的 microApp 做到复用。实例化 actor 时,我把 actor 分为六种类型,其中两种是复用沙箱的。比如业务中 main 应用提供了 layout、公共组件等,它自身又有一些业务比如首页渲染、登录注册等。当切换到它自身的路由时 actor 是普通类型,当切换到应用商城时,actor 又变成了 layout 类型。这期间牵涉到新旧 actor 的更替,但沙箱可以复用。
内容真正载入页面时机
这里利用了 MutationObserver 来实时监听 DOM 变化,尽快获取到以便快速载入微应用的 template 和 css 结束白屏。后面规划对微应用设置渲染优先级,可以更精细地控制某个板块渲染。
三级缓存方案实现页面“秒开”
什么是三级缓存呢?主要包含以下分类,它们分别存储不同的内容。这也和 CPU 的运算单元有异曲同工之处。
内存缓存: 存储激活过的微应用 html/json 内容 软缓存: 即会话缓存。存储当前 session 所有激活过的微应用静态资源 硬缓存: 设置开启后,存储微应用静态资源,会比较类似文件进行更新或删除
不同缓存的实现,是由 @satumjs/midware-cachecode 提供支持,也可自行实现缓存机制。这里我有一个算是独创的思路,即利用浏览器的数据库 indexedDB,伪装会话缓存,即软缓存。实现思路是这样的:
当调度系统调用缓存中间件时,会先判断是否禁用了软硬缓存,如果都禁用了所有内容缓存到内存中;未被禁用的话,会判断目前是 html/json 文件内容,又没有设置白名单强制走软硬缓存,则会由内存缓存处理。非 html/json 的文件继续走下面的逻辑,判断是否启用硬缓存,如果是则直接由硬缓存逻辑处理。否的话则判断 sessionStorage 里是否有标志位,这意味着这个页面是第一次进入还是刷新,大家都知道 sessionStorage 是会话结束清除缓存,刷新不会清除。如果是第一次进入,则先删除已有的缓存,再写入新的缓存。这样做是防止老的缓存干扰。
其实这块先前的实现,我使用的是 sessionStorage,方便是很方便不过有很多问题:容量仅有 5M,微应用多了之后很容易用尽,而影响业务逻辑中使用 sessionStorage。而我看到浏览器的数据库 indexedDB 是很理想的缓存载体,理论上讲有无限的容量。但如何把它伪装成会话缓存呢?于是想到使用一个标志位来控制删除缓存的时机,让它尽可能达到会话缓存的效果。
最后
以上就是分享的所有内容。现在搞成文字版,方便查阅。上面谈到大部分的优化,都在 Satum 微前端内核或中间件中有实现。欢迎加入微信群和钉钉群,关注和试用该框架,甚至基于内核定制适合自己团队的微前端体系来。
PS. 如果微信群的二维码失效,请添加我的微信 valleykiddy 拉小伙伴入群哈~
请你喝杯🍵 记得三连哦~
1.阅读完记得给🌲 酱点个赞哦,有👍 有动力
2.关注公众号前端那些趣事,陪你聊聊前端的趣事
3.文章收录在Github frontendThings 感谢Star✨