用Workbox打造超快PWA体验的实战总结

明明 Dev 移动 阅读 1,447
赞 13 收藏
二维码
手机扫码查看
反馈

先说结论:我选 Workbox + GenerateSW,简单粗暴但够用

如果你现在就在搞 PWA,想快速上手一个可靠的缓存方案,别整那些花里胡哨的自定义 service worker 手写逻辑了。直接上 Workbox 的 GenerateSW,几行配置搞定静态资源预缓存、动态路由、版本更新,省下来的时间够你多摸两杯咖啡。

用Workbox打造超快PWA体验的实战总结

当然,Workbox 不只这一种玩法。它还提供了 InjectManifest 模式,允许你完全控制 SW 文件,再把生成的 precache 列表注入进去。听起来很灵活?确实,但也意味着你要自己管理生命周期、缓存策略、回退逻辑……说白了就是:自由度高了,坑也多了。

我两个都用过,项目上线后踩过坑,也半夜被用户反馈“为什么新代码没生效”吵醒过。今天就来聊聊这两个方案到底差在哪,哪个更适合你。

谁更省事?GenerateSW 真的是开箱即用

我做内部管理系统时首选 GenerateSW。原因很简单:我不需要对 SW 做太多定制,只要能把打包后的 JS/CSS/HTML 自动缓存住,支持离线访问就行。

webpack 配置也很简单:

const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  // ... 其他 webpack 配置
  plugins: [
    new GenerateSW({
      swDest: 'sw.js',
      clientsClaim: true,
      skipWaiting: true,
      globPatterns: ['**/*.{js,css,html}'],
      cleanupOutdatedCaches: true,
    }),
  ],
};

就这么几行,构建时会自动扫描 dist 目录下的所有静态文件,生成一个包含 precache manifest 的 sw.js,并且注册了基本的 runtime 缓存(比如 API 请求可以额外配置)。

最爽的是 skipWaitingclientsClaim,新版本安装完直接激活,不用等用户刷新第二次才生效。这个在实际体验上提升很大,不然用户永远卡在旧版界面里出不来。

缺点也不是没有:你想加个自定义消息监听?比如 postMessage 控制缓存清除?不行,你没法往生成的 SW 里写额外逻辑。除非你用 additionalManifestEntries 手动塞点路径,但那也只是补资源,不是写代码。

谁更灵活?InjectManifest 让你从头掌控

后来我们做了个文档类应用,要求更复杂:除了预缓存页面,还需要手动缓存用户浏览过的文章内容,支持全文搜索离线可用,还得能通过按钮触发“清理缓存”。

这时候 GenerateSW 就不够用了。我转投 InjectManifest

它的思路是:你写一个完整的 sw.js 文件,在里面调用 self.__WB_MANIFEST 获取构建时生成的资源列表,然后自己实现 precaching。

先看配置:

const { InjectManifest } = require('workbox-webpack-plugin');

module.exports = {
  // ...其他配置
  plugins: [
    new InjectManifest({
      swSrc: './src/sw.js', // 你自己写的 service worker
      swDest: 'sw.js',
      globPatterns: ['**/*.{js,css,html}'],
    }),
  ],
};

然后是你自己的 sw.js

import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';

// 必须这句,让 workbox 注入资源清单
precacheAndRoute(self.__WB_MANIFEST);

// 动态缓存文章 API
registerRoute(
  ({ url }) => url.origin === 'https://jztheme.com' && url.pathname.startsWith('/api/articles'),
  new StaleWhileRevalidate({ cacheName: 'articles-cache' })
);

// 监听清除缓存的消息
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'CLEAR_CACHES') {
    caches.delete('articles-cache');
    event.waitUntil(clients.matchAll()).then((clients) => {
      clients.forEach(client => client.postMessage({ type: 'CACHE_CLEARED' }));
    });
  }
});

看到区别了吗?你现在可以自由添加事件监听、自定义缓存名、甚至做 indexedDB 存储。这种控制感真的很爽。

但代价是什么?复杂度上去了。你得自己处理缓存更新逻辑、避免重复缓存、注意缓存爆炸问题。有一次我没加 cleanupOutdatedCaches,用户升级后旧版本资源一直占着空间,导致加载变慢——折腾了半天才发现是这里漏了配置。

性能对比:差距其实不大

我一直以为 InjectManifest 因为更“手动”,所以性能更好。实测下来发现根本没差多少。

两者最终生成的 precache 逻辑是一样的,都是基于 workbox-precaching 模块。请求走的缓存策略如果也一致,那运行时表现几乎一样。

真正影响性能的是你的缓存策略设计,而不是你用了哪个模式。比如你把整个网站的所有图片都 pre-cache,不管用哪种方案都会拖慢首次 install。

所以别指望换模式能带来性能飞跃。该懒加载的还得懒加载,该分包的还是得分包。

我的选型逻辑:90% 场景闭眼选 GenerateSW

我现在已经懒得纠结了。新项目只要不是特别复杂的离线需求,一律上 GenerateSW

为什么?因为绝大多数前端项目根本不需要那么细粒度的控制。你要做的只是:

  • 打包后资源能离线访问
  • 更新发布后用户能及时用上新版
  • API 请求有一定容错(比如 stale-while-revalidate)

这些 GenerateSW 全都能满足。而且配置少,出错概率低,团队新人也能快速理解。

只有当你有以下情况时,我才建议切到 InjectManifest

  • 需要监听 postMessage 做交互(比如手动清理、强制更新)
  • 要做复杂的缓存分区管理(比如按用户身份隔离缓存)
  • 要用到 Workbox 没封装的功能(比如 Background Sync)
  • 已有 legacy SW 文件,不想重构成纯声明式

否则真没必要给自己找麻烦。别忘了,service worker 跑在浏览器后台,调试起来比主进程费劲得多。越简单越稳定。

踩坑提醒:这三点一定注意

不管你选哪个模式,这几个坑我都亲自踩过,记住了能省你几小时排查时间。

1. 开发环境别乱开 DevTools 的 “Update on reload”

这个选项在调试时看着方便,但实际上会让每次刷新都强制 install 新 SW,掩盖了真实的更新流程。我有次以为 skipWaiting 生效了,结果生产环境一上线用户全卡住了——因为没人手动刷新两次。

正确做法:开发时关掉这个选项,用 unregister 强制重载测试更新链路。

2. 清理旧缓存要主动

Workbox 默认不会删老版本缓存。你发了 10 个版本,每个版本的 JS 都还在用户的磁盘上躺着。虽然 cleanupOutdatedCaches: true 能帮一点,但它不是万能的。

我在 InjectManifest 里加了个定时任务,冷启动时检查缓存数量,超过阈值就提示用户“建议清理缓存”——这不是最优解,但至少防止缓存无限膨胀。

3. 注意 HTML 的缓存策略

很多人把 index.html 也 pre-cache,结果每次更新都要等 SW install 完才能看到新页面。如果你希望更快生效,可以把 HTML 放到 runtime 缓存里,用 network-first 或 stale-while-revalidate。

但要注意:这样可能造成页面和 JS 版本不匹配。我试过一次,HTML 加载了新的,JS 还是旧的,直接报错。最后还是回到 pre-cache + skipWaiting 的组合,牺牲一点即时性换稳定性。

以上是我的对比总结,有不同看法欢迎评论区交流

Workbox 这两个模式本质上是一个“约定优于配置” vs “完全掌控”的选择。我没有绝对推荐哪一个,但我自己的项目里,GenerateSW 出场率远高于 InjectManifest。

不是因为它更强,而是因为它足够好,且足够简单。前端工程化走到今天,我觉得很多轮子就没必要自己造了。把精力留给业务逻辑和用户体验,才是正道。

当然,如果你正在做一个重度离线应用(比如笔记类、编辑器类),那值得花时间上 InjectManifest 搞精细化运营。但大多数企业级 Web 应用,真没这个必要。

最后提一嘴:Workbox v7 已经出来挺久了,API 稳定,文档虽然啰嗦但还算齐全。npm 包体积也不算大,引入无压力。只要你做 PWA,它依然是目前最靠谱的选择。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论