前端 Service Worker最佳实践(上):高效的预缓存与运行时缓存方案

本文介绍一种高效、可维护的 Service Worker 缓存策略,涵盖预缓存过滤、关键资源筛选、运行时缓存空间管理,以及如何优雅地关闭 Service Worker。适用于大多数中大型 Web App,尤其是资源量大、更新频繁的项目。


一、背景与痛点

Service Worker 作为 PWA 的核心,能极大提升 Web App 的离线体验和加载速度。但如果缓存策略设计不当,容易出现:

  • 初次加载慢:预缓存资源过多,影响首屏体验
  • 缓存污染:旧资源长期残留,浪费存储空间
  • 更新不及时:新版本资源未能及时生效

二、实施方案

1. 预缓存优化

预缓存指在 Service Worker 安装阶段,将指定资源提前下载并缓存。这样用户下次访问时可直接从本地获取,提升加载速度。

1.1 现状与问题

在实际开发中,很多团队会直接使用 Workboxworkbox.precaching 模块来实现预缓存。
Workbox 的机制

  • 构建工具(如 webpack workbox 插件)会自动生成一个包含所有静态资源的清单(__WB_MANIFEST)。
  • 直接调用 workbox.precaching.precacheAndRoute(__WB_MANIFEST),会将清单中的所有资源(HTML/JS/CSS/图片/字体等)都加入预缓存。

这样做的风险:

  • 如果资源量大,首次访问时 Service Worker 安装阶段会下载所有资源,导致首屏慢、带宽压力大。
  • 某些资源(如图片、字体、视频)并非每次都用到,预缓存它们会造成资源浪费。

1.2 预缓存过滤策略

  • 过滤掉图片、视频、字体等非关键资源
  • 只对高频访问页面的 Chunk 进行预缓存
js 复制代码
const precacheManifest = self.__WB_MANIFEST;

const importantJsChunks = [
  'home',
  'user-profile',
  'dashboard',
  // ... 仅列出高频页面
];

const filteredPrecache = precacheManifest.filter(item => {
  // 过滤图片、字体、视频等
  if (item.url.match(/.(png|jpg|jpeg|svg|gif|webp|mp4|woff2?|ttf|otf)$/i)) {
    return false;
  }
  // 只缓存高频 JS Chunk
  if (item.url.endsWith('.js')) {
    return importantJsChunks.some(name => item.url.includes(name));
  }
  return true; // 其他如 HTML/CSS
});

workbox.precaching.precacheAndRoute(filteredPrecache);

2、运行时缓存空间优化

2.1 问题分析

  • 资源(如图片、字体)通常内容稳定,适合长期缓存
  • 业务 JS/CSS 频繁变更,需按版本区分缓存空间,避免缓存污染

2.2 缓存空间划分

  • 图片/字体/第三方库:固定缓存空间,长期复用
  • 业务 JS/CSS:按版本号动态命名缓存空间,发版自动切换
js 复制代码
const appVersion = Date.now(); // 或用实际版本号

const runtimeCacheConfig = [
  {
    type: 'CacheFirst',
    route: /.*.(png|jpg|jpeg|svg|gif|webp)$/,
    name: 'app-cache:images',
    max: 300,
    time: 90 * 24 * 60 * 60, // 90天
  },
  {
    type: 'CacheFirst',
    route: /.*.(woff2?|ttf|otf)$/,
    name: 'app-cache:fonts',
    max: 20,
    time: 365 * 24 * 60 * 60, // 1年
  },
  {
    type: 'CacheFirst',
    route: //cdn/.*.js$/,
    name: 'app-cache:cdn-libs',
    max: 50,
    time: 90 * 24 * 60 * 60,
  },
  {
    type: 'StaleWhileRevalidate',
    route: /^(?!.*/cdn/).*.(js|css)$/,
    name: `app-cache:static-v${appVersion}`,
    max: 30,
  },
];

runtimeCacheConfig.forEach(cfg => {
  const strategy = {
    'CacheFirst': new workbox.strategies.CacheFirst({ cacheName: cfg.name }),
    'StaleWhileRevalidate': new workbox.strategies.StaleWhileRevalidate({ cacheName: cfg.name }),
    // ...
  }[cfg.type];
  workbox.routing.registerRoute(({url}) => cfg.route.test(url.pathname), strategy);
});

3. Service Worker 快速下线机制

Service Worker 一旦注册,除非用户手动清理或代码主动注销,否则会一直生效。若线上出现严重 bug,需有"紧急开关"机制。

3.1 实现思路

  • 通过远程配置文件(如 JS 文件)控制 Service Worker 是否注册
  • 配置文件需不可缓存,可独立上线

开关文件示例 (如 https://your-cdn.com/sw-switch.js):

js 复制代码
// sw-switch.js
window.SW_TURN_OFF = true; // true 表示关闭 SW,false 表示允许注册

页面引入与判断:

html 复制代码
<script src="https://your-cdn.com/sw-switch.js"></script>
<script>
  if (window.SW_TURN_OFF === true) {
    // 注销已注册的 Service Worker
    navigator.serviceWorker.getRegistrations().then(regs => {
      regs.forEach(reg => reg.unregister());
    });
  } else {
    // 正常注册 Service Worker
    navigator.serviceWorker.register('/sw.js');
  }
</script>

如何保证配置文件不可缓存?

为了让 Service Worker 的开关配置能实时生效,可以考虑以下措施:

  1. 服务端设置 HTTP 头 :为 sw-switch.js 设置 Cache-Control: no-storeCache-Control: no-cache, must-revalidate,确保浏览器每次都去服务器拉取最新文件。

  2. 前端加时间戳:在页面引入 sw-switch.js 时拼接时间戳参数,例如:

    html 复制代码
    <script src="https://your-cdn.com/sw-switch.js?t=1688888888888"></script>

    或用 JS 动态插入,确保每次请求的 URL 不同,绕过缓存。

  3. CDN 配置:如文件托管在 CDN,建议将该文件的缓存策略设置为"不缓存"或"每次回源"。

团队的CDN平台支持直接配置文件的缓存策略,因此采用了第三种方法。

五、总结与最佳实践

  • 预缓存只选关键资源,减少首屏压力
  • 运行时缓存空间分离,长期资源与业务资源分开管理
  • 支持远程开关,可随时下线 Service Worker
相关推荐
weixin_382395235 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__5 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅10 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni10 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端