深入剖析 WASM SPA 冷启动延迟:从浏览器编译缓存驱逐到 IndexedDB 持久化修复
本文系统解释了 StaticFlow WASM 前端“长时间空闲后刷新白屏”的根因,并给出可落地的缓存与体验双重修复方案。问题现象与诊断线索文章先描述了典型现象:空闲 30 分钟后刷新会卡白屏,随后请求瞬时并发出现。关键观察是 Network 面板前期几乎无请求,说明瓶颈不在网络而在本地 CPU 计算。由此把排查重点收…
深入剖析 WASM SPA 冷启动延迟:从浏览器编译缓存驱逐到 IndexedDB 持久化修复 项目背景:StaticFlow — 基于 Yew (Rust) + Trunk + wasm-bindgen 构建的 SPA 技术博客,WASM 产物约 1.9 MB(wasm-opt -Oz --converge 优化后)。 代码版本:基于 master 分支最新提交。一、问题现象1.1 Bug 描述 在 StaticFlow 的任何前端页面上,只要用户长时间不操作(大约 30 分钟以上),再点击浏览器刷新按钮,页面会卡在白屏状态很久——可能 3 到 10 秒甚至更长。 打开 Chrome DevTools 的 Network 面板观察,会发现一个非常反直觉的现象:刷新后的前几秒内,Network 面板完全空白——没有任何 HTTP 请求发出突然某一瞬间,所有 API 请求(文章列表、歌曲列表等)同时涌出,页面瞬间渲染完成之后连续刷新都是秒加载但只要再次长时间不操作,问题又会复现1.2 关键线索 这个现象的核心矛盾是:页面卡住了,但没有网络请求。 如果是网络问题(CDN 慢、API 超时),Network 面板一定会显示 pending 状态的请求。但这里什么都没有。这意味着瓶颈不在网络 I/O,而是某种 CPU 密集型的本地计算在阻塞应用启动。 对于一个 WASM SPA 来说,最大的 CPU 密集型本地计算只有一个——WebAssembly 模块编译。二、WASM 在浏览器中的完整生命周期 要理解这个 bug,我们需要先搞清楚一个 WASM SPA 从 HTML 加载到用户看到页面的完整链路。2.1 加载链路:从 HTML 到 WASM 执行 StaticFlow 使用 Trunk 作为构建工具。Trunk 在构建时会将 index.html 中的 <link data-trunk rel="rust" /> 指令替换为一段 <script type="module"> 胶水代码。 源码中的 Trunk 指令(frontend/index.html):<!-- Trunk will compile Rust to WASM --> <!-- Disable built-in wasm-opt; post_build hook runs it with correct feature flags --> <link data-trunk rel="rust" data-wasm-opt="0" /> 💡 Trunk 内置的 wasm-opt 不支持传递 --enable-* flags。StaticFlow 通过 data-wasm-opt="0" 禁用内置 wasm-opt,改用 Trunk.toml 中的 post_build hook 手动调用 wasm-opt -Oz --converge --enable-bulk-memory --enable-nontrapping-float-to-int,详见第五节。 构建后生成的模块脚本(frontend/dist/index.html:135-145):<script type="module"> import init, * as bindings from '/static-flow-frontend-53f92742ae8cdc40.js'; const wasm = await init({ module_or_path: '/static-flow-frontend-53f92742ae8cdc40_bg.wasm' }); window.wasmBindings = bindings; dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}})); </script> 同时 Trunk 还会在 <head> 末尾注入资源预加载提示(dist/index.html:180):<link rel="modulepreload" href="/static-flow-frontend-53f92742ae8cdc40.js" crossorigin="anonymous" integrity="sha384-..." /> <link rel="preload" href="/static-flow-frontend-53f92742ae8cdc40_bg.wasm" crossorigin="anonymous" integrity="sha384-..." as="fetch" type="application/wasm" /> 这段代码触发了以下加载链路:浏览器解析 HT…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行