你听说过微前端框架 Satum 吗?

前端那些趣事

共 4602字,需浏览 10分钟

 · 2022-06-07

?缘起

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✨


浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报