查看原文
其他

【第2632期】微前端框架 Satum 的性能优化策略

谷童 前端早读课 2022-06-11

前言

大家可能看到Satum有点陌生,在这篇【第2618期】手把手教你定制一套适合团队的微前端体系有所介绍。今日前端早读课文章由阿里@谷童投稿分享。

@谷童(valleykid),现在在阿里平头哥团队做前端负责人/架构师。github:valleykid,欢迎关注。我们团队目前在芯片生态业务领域实践了两年多时间的微前端,攻克了这个方向的一些难题,沉淀了许多好玩且好用的特性。欢迎大家关注和试用~

正文从这开始~~

什么是 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方法就是对中间件的调用。这样几十行代码,执行后就能看的@尤大 写的 被集成到微应用里了。

从传统优化策略中借鉴

传统优化策略,比较熟悉的莫过于“雅虎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
"shareProps": {
"externals": {},
"globals": {},
"statics": {}
}

// community-app
"shareProps": {
"externals": {
"@react": "React",
"@react-dom": "ReactDOM"
},
"globals": {},
"statics": {}
}

可以看到上面的例子,main-app 是提供共享三方库的微应用,而 community-app 是依赖了三方库的。只需要在配置文件里配置 externals声明依赖哪些三方库即可。

Satum 不但支持共享三方库,还支持运行时的数据共享和静态数据,当某个三方库由其他微应用共享时,通过嵌套关系形成的依赖链,其子级微应用也就能获取到。

如果不依赖三方库,就不用等待,可以和其他微应用并行执行脚本;而依赖三方库的,就只能等到三方库 ready 时再执行。对于三方库的获取,我使用了链表结构的数据类型,可以很快查询到。

沙箱的复用性

因为沙箱实例的创建和销毁是有很大代价的,可以基于共同的 microApp 做到复用。实例化 actor 时,我把 actor 分为六种类型,其中两种是复用沙箱的。比如业务中 main 应用提供了 layout、公共组件等,它自身又有一些业务比如首页渲染、登录注册等。当切换到它自身的路由时 actor 是普通类型,当切换到应用商城时,actor 又变成了 layout 类型。这期间牵涉到新旧 actor 的更替,但沙箱可以复用。

内容真正载入页面时机

这里利用了 MutationObserver 来实时监听 DOM 变化,尽快获取到以便快速载入微应用的 template 和 css 结束白屏。后面规划对微应用设置渲染优先级,可以更精细地控制某个板块渲染。

三级缓存方案实现页面“秒开”

什么是三级缓存呢?主要包含以下分类,它们分别存储不同的内容。这也和 CPU 的运算单元有异曲同工之处。

  • 内存缓存: 存储激活过的微应用 html/json 内容

  • 软缓存: 即会话缓存。存储当前 session 所有激活过的微应用静态资源

  • 硬缓存: 设置开启后,存储微应用静态资源,会比较类似文件进行更新或删除

不同缓存的实现,是由 提供支持,也可自行实现缓存机制。这里我有一个算是独创的思路,即利用浏览器的数据库 indexedDB,伪装会话缓存,即软缓存。实现思路是这样的:

当调度系统调用缓存中间件时,会先判断是否禁用了软硬缓存,如果都禁用了所有内容缓存到内存中;未被禁用的话,会判断目前是 html/json 文件内容,又没有设置白名单强制走软硬缓存,则会由内存缓存处理。非 html/json 的文件继续走下面的逻辑,判断是否启用硬缓存,如果是则直接由硬缓存逻辑处理。否的话则判断 sessionStorage 里是否有标志位,这意味着这个页面是第一次进入还是刷新,大家都知道 sessionStorage 是会话结束清除缓存,刷新不会清除。如果是第一次进入,则先删除已有的缓存,再写入新的缓存。这样做是防止老的缓存干扰。

其实这块先前的实现,我使用的是 sessionStorage,方便是很方便不过有很多问题:容量仅有 5M,微应用多了之后很容易用尽,而影响业务逻辑中使用 sessionStorage。而我看到浏览器的数据库 indexedDB 是很理想的缓存载体,理论上讲有无限的容量。但如何把它伪装成会话缓存呢?于是想到使用一个标志位来控制删除缓存的时机,让它尽可能达到会话缓存的效果。

最后,上面谈到大部分的优化,都在 Satum 微前端内核或中间件中有实现。欢迎加群讨论,有兴趣的可以关注和试用该框架,甚至基于内核定制适合自己团队的微前端体系来。

项目官网:https://satumjs.github.io/website/

PS. 如果群的二维码失效,请添加微信 valleykiddy 拉小伙伴入群哈~

关于本文
作者:@谷童
原文:https://www.yuque.com/docs/share/ac7ac2f6-f467-4c80-a5e2-a9dff3b373c3

微前端相关阅读,欢迎自荐投稿,前端早读课等你来。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存