⚠️ 保持单个内容小于 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 的运算单元有异曲同工之处。
不同缓存的实现,是由 @satumjs/midware-cachecode 提供支持,也可自行实现缓存机制。这里我有一个算是独创的思路,即利用浏览器的数据库 indexedDB,伪装会话缓存,即软缓存。实现思路是这样的:
当调度系统调用缓存中间件时,会先判断是否禁用了软硬缓存,如果都禁用了所有内容缓存到内存中;未被禁用的话,会判断目前是 html/json 文件内容,又没有设置白名单强制走软硬缓存,则会由内存缓存处理。非 html/json 的文件继续走下面的逻辑,判断是否启用硬缓存,如果是则直接由硬缓存逻辑处理。否的话则判断 sessionStorage 里是否有标志位,这意味着这个页面是第一次进入还是刷新,大家都知道 sessionStorage 是会话结束清除缓存,刷新不会清除。如果是第一次进入,则先删除已有的缓存,再写入新的缓存。这样做是防止老的缓存干扰。
其实这块先前的实现,我使用的是 sessionStorage,方便是很方便不过有很多问题:容量仅有 5M,微应用多了之后很容易用尽,而影响业务逻辑中使用 sessionStorage。而我看到浏览器的数据库 indexedDB 是很理想的缓存载体,理论上讲有无限的容量。但如何把它伪装成会话缓存呢?于是想到使用一个标志位来控制删除缓存的时机,让它尽可能达到会话缓存的效果。
最后,上面谈到大部分的优化,都在 Satum 微前端内核或中间件中有实现。欢迎加群讨论,有兴趣的可以关注和试用该框架,甚至基于内核定制适合自己团队的微前端体系来。
项目官网:
PS. 如果群的二维码失效,请添加微信 valleykiddy 拉小伙伴入群哈~
关于本文
暂无评论内容