Vue 3 与 Workbox 深度集成

概述

Workbox 是 Google 开发的一套用于构建 Progressive Web App (PWA) 的工具库,它简化了 Service Worker 的开发和维护工作。Workbox 提供了多种缓存策略、预缓存机制、路由处理、背景同步等功能,使得开发者能够轻松构建功能完善、性能优良的 PWA 应用。

Vue 3 凭借其现代化的架构和良好的性能,与 Workbox 结合使用时能够发挥出强大的威力。本集将深入探讨 Workbox 的核心功能以及如何在 Vue 3 应用中深度集成 Workbox,包括预缓存配置、运行时缓存策略、背景同步、推送通知等高级功能。

核心知识点

1. Workbox 简介

1.1 什么是 Workbox

Workbox 是一套用于构建 PWA 的 JavaScript 库,它提供了一系列工具和 API,简化了 Service Worker 的开发和维护。Workbox 由 Google Chrome 团队开发,是构建 PWA 的官方推荐工具之一。

1.2 Workbox 核心功能

  • 预缓存:在 Service Worker 安装时预缓存指定的资源
  • 运行时缓存:根据不同的缓存策略处理运行时的网络请求
  • 路由处理:灵活的路由匹配和处理机制
  • 背景同步:在后台执行数据同步操作
  • 推送通知:处理推送通知事件
  • 定期同步:定期执行任务
  • 缓存管理:自动管理缓存大小和过期时间

1.3 Workbox 优势

  • 简化开发:提供了简单易用的 API,降低了 Service Worker 的开发难度
  • 性能优化:内置了多种缓存策略,优化了应用的性能
  • 可扩展性:支持插件机制,可以扩展 Workbox 的功能
  • 良好的文档和社区支持:拥有完善的文档和活跃的社区
  • 持续更新:由 Google Chrome 团队维护,持续更新和改进

2. Vue 3 中集成 Workbox

2.1 使用 Vite PWA 插件集成 Workbox

在 Vue 3 应用中,我们可以使用 vite-plugin-pwa 插件来集成 Workbox。该插件内部使用了 Workbox,提供了简单的配置选项。

首先,安装插件:

npm install vite-plugin-pwa -D

然后,在 vite.config.js 中配置插件:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
      manifest: {
        name: 'Vue 3 Workbox Deep Integration',
        short_name: 'Vue 3 Workbox',
description: 'Vue 3 with Workbox deep integration tutorial',
        theme_color: '#42b983',
        background_color: '#ffffff',
        display: 'standalone',
        scope: '/',
        start_url: '/',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          },
          {
            src: 'pwa-maskable-192x192.png',
            sizes: '192x192',
            type: 'image/png',
            purpose: 'maskable'
          },
          {
            src: 'pwa-maskable-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
          }
        ]
      },
      workbox: {
        // Workbox 配置
        globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\//,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 * 24 // 24 小时
              },
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          }
        ]
      }
    })
  ]
})

2.2 手动集成 Workbox

除了使用 Vite PWA 插件外,我们还可以手动集成 Workbox。首先,安装 Workbox:

npm install workbox-core workbox-precaching workbox-routing workbox-strategies workbox-expiration workbox-cacheable-response -D

然后,创建一个 Service Worker 文件:

// public/service-worker.js
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'

// 预缓存资源
precacheAndRoute(self.__WB_MANIFEST)

// 注册路由
// 静态资源:Cache First 策略
registerRoute(
  ({ request }) => request.destination === 'style' || request.destination === 'script' || request.destination === 'image',
  new CacheFirst({
    cacheName: 'static-resources',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 1000,
        maxAgeSeconds: 60 * 60 * 24 * 30 // 30 天
      })
    ]
  })
)

// API 请求:Network First 策略
registerRoute(
  ({ url }) => url.origin === 'https://api.example.com',
  new NetworkFirst({
    cacheName: 'api-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200]
      }),
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 60 * 60 * 24 // 24 小时
      })
    ]
  })
)

// 其他请求:Stale While Revalidate 策略
registerRoute(
  ({ request }) => request.method === 'GET',
  new StaleWhileRevalidate({
    cacheName: 'other-resources',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 60 * 60 * 24 // 24 小时
      })
    ]
  })
)

最后,在 vite.config.js 中配置 Workbox:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteSingleFile } from 'vite-plugin-singlefile'
import { generateSW } from 'workbox-build'

// 构建后生成 Service Worker
const workbox = () => {
  return {
    name: 'workbox',
    closeBundle: async () => {
      await generateSW({
        swDest: './dist/service-worker.js',
        globDirectory: './dist',
        globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\//,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 * 24
              },
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          }
        ]
      })
    }
  }
}

export default defineConfig({
  plugins: [
    vue(),
    workbox()
  ]
})

3. Workbox 预缓存配置

3.1 预缓存资源

预缓存是 Workbox 的核心功能之一,它允许我们在 Service Worker 安装时预缓存指定的资源。我们可以使用 precacheAndRoute 函数来配置预缓存:

// public/service-worker.js
import { precacheAndRoute } from 'workbox-precaching'

// 预缓存资源
precacheAndRoute([
  {
    url: '/',
    revision: '1.0.0'
  },
  {
    url: '/index.html',
    revision: '1.0.0'
  },
  {
    url: '/assets/app.js',
    revision: '1.0.0'
  },
  {
    url: '/assets/app.css',
    revision: '1.0.0'
  },
  {
    url: '/assets/icon.png',
    revision: '1.0.0'
  }
])

使用 vite-plugin-pwa 插件时,我们可以在 vite.config.js 中配置预缓存:

// vite.config.js
VitePWA({
  // ...
  workbox: {
    globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
    globIgnores: ['node_modules/**/*', '.git/**/*', 'dist/**/*.map'],
    maximumFileSizeToCacheInBytes: 5 * 1024 * 1024 // 5MB
  }
})

3.2 预缓存策略

Workbox 提供了多种预缓存策略,我们可以根据需要选择合适的策略:

  • precacheAndRoute:预缓存资源并为这些资源注册路由
  • precache:只预缓存资源,不注册路由
  • precacheAndRouteWithNetworkUpdate:预缓存资源并注册路由,同时在网络有更新时更新缓存

3.3 预缓存清单

Workbox 使用预缓存清单来管理预缓存的资源。预缓存清单是一个包含资源 URL 和版本号的数组,Workbox 会根据版本号来判断是否需要更新资源。

在使用 vite-plugin-pwa 插件时,预缓存清单会自动生成,我们不需要手动维护。

4. Workbox 运行时缓存策略

4.1 缓存策略类型

Workbox 提供了多种缓存策略,我们可以根据不同的资源类型选择合适的策略:

  • NetworkFirst:优先从网络获取资源,网络不可用时从缓存获取
  • CacheFirst:优先从缓存获取资源,缓存中不存在时从网络获取
  • StaleWhileRevalidate:先从缓存返回资源(如果存在),然后在后台从网络获取并更新缓存
  • NetworkOnly:只从网络获取资源
  • CacheOnly:只从缓存获取资源

4.2 配置运行时缓存

我们可以使用 registerRoute 函数来配置运行时缓存:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'

// API 请求:Network First 策略
registerRoute(
  ({ url }) => url.origin === 'https://api.example.com',
  new NetworkFirst({
    cacheName: 'api-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200]
      }),
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 60 * 60 * 24 // 24 小时
      })
    ]
  })
)

// 静态资源:Cache First 策略
registerRoute(
  ({ request }) => request.destination === 'style' || request.destination === 'script' || request.destination === 'image',
  new CacheFirst({
    cacheName: 'static-resources',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 1000,
        maxAgeSeconds: 60 * 60 * 24 * 30 // 30 天
      })
    ]
  })
)

// 字体资源:Stale While Revalidate 策略
registerRoute(
  ({ url }) => url.origin === 'https://fonts.googleapis.com' || url.origin === 'https://fonts.gstatic.com',
  new StaleWhileRevalidate({
    cacheName: 'fonts',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 20,
        maxAgeSeconds: 60 * 60 * 24 * 365 // 1 年
      })
    ]
  })
)

4.3 自定义缓存策略

除了使用内置的缓存策略外,我们还可以自定义缓存策略:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { Strategy } from 'workbox-strategies'

class CustomStrategy extends Strategy {
  async _handle(request, handler) {
    try {
      // 先尝试从网络获取
      const networkResponse = await handler.fetch(request)
      return networkResponse
    } catch (error) {
      // 网络请求失败,从缓存获取
      const cachedResponse = await handler.cacheMatch(request)
      if (cachedResponse) {
        return cachedResponse
      }
      // 缓存中也没有,返回默认响应
      return new Response('Network error occurred', {
        status: 503,
        headers: { 'Content-Type': 'text/plain' }
      })
    }
  }
}

// 注册自定义策略
registerRoute(
  ({ request }) => request.url.includes('/custom/'),
  new CustomStrategy({
    cacheName: 'custom-strategy-cache'
  })
)

5. Workbox 路由处理

5.1 路由匹配

Workbox 提供了灵活的路由匹配机制,我们可以根据 URL、请求方法、请求头等条件来匹配路由:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'

// 根据 URL 匹配
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({ cacheName: 'api-cache' })
)

// 根据请求方法匹配
registerRoute(
  ({ request }) => request.method === 'GET',
  new NetworkFirst({ cacheName: 'get-requests' })
)

// 根据请求头等条件匹配
registerRoute(
  ({ request }) => request.headers.get('Accept').includes('application/json'),
  new NetworkFirst({ cacheName: 'json-requests' })
)

// 根据请求目的地匹配
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({ cacheName: 'images' })
)

5.2 自定义路由匹配

我们还可以使用自定义函数来匹配路由:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'

// 自定义匹配函数
const matchFunction = ({ url, request }) => {
  // 匹配所有 /api/ 开头且请求方法为 GET 的请求
  return url.pathname.startsWith('/api/') && request.method === 'GET'
}

registerRoute(
  matchFunction,
  new NetworkFirst({ cacheName: 'custom-match-api-cache' })
)

6. Workbox 缓存管理

6.1 缓存过期

Workbox 提供了 ExpirationPlugin 插件来管理缓存的过期时间和最大条目数:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'

registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 60 * 60 * 24 * 30, // 30 天
        purgeOnQuotaError: true // 在配额不足时清除缓存
      })
    ]
  })
)

6.2 缓存大小管理

Workbox 会自动管理缓存大小,当缓存大小超过浏览器的配额时,会触发 QuotaExceededError 错误。我们可以通过以下方式来管理缓存大小:

  • 设置合理的 maxEntriesmaxAgeSeconds
  • 使用 purgeOnQuotaError: true 选项,在配额不足时清除缓存
  • 定期清理不需要的缓存

6.3 清理旧缓存

我们可以使用 workbox-expiration 插件来清理旧缓存:

// public/service-worker.js
import { cleanupOutdatedCaches } from 'workbox-precaching'

// 清理旧缓存
cleanupOutdatedCaches()

7. Workbox 背景同步

7.1 配置背景同步

Workbox 提供了 BackgroundSyncPlugin 插件来支持背景同步:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { NetworkOnly } from 'workbox-strategies'
import { BackgroundSyncPlugin } from 'workbox-background-sync'

// 注册背景同步路由
registerRoute(
  ({ request }) => request.method === 'POST' && request.url.includes('/api/'),
  new NetworkOnly({
    plugins: [
      new BackgroundSyncPlugin('sync-api-requests', {
        maxRetentionTime: 24 * 60 // 24 小时
      })
    ]
  }),
  'POST'
)

7.2 处理背景同步事件

当网络恢复后,Workbox 会自动重试失败的请求。我们也可以手动监听背景同步事件:

// public/service-worker.js
self.addEventListener('backgroundsync', (event) => {
  if (event.tag === 'sync-api-requests') {
    event.waitUntil(
      // 处理背景同步逻辑
      (async () => {
        // 获取所有待同步的请求
        const queue = new workbox.backgroundSync.Queue('sync-api-requests')
        const entries = await queue.getAll()
        
        // 处理每个请求
        for (const entry of entries) {
          try {
            await fetch(entry.request)
            // 请求成功,从队列中删除
            await queue.deleteEntry(entry.id)
          } catch (error) {
            // 请求失败,保留在队列中
            console.error('Background sync failed:', error)
          }
        }
      })()
    )
  }
})

8. Workbox 推送通知

8.1 处理推送事件

Workbox 可以帮助我们处理推送通知事件:

// public/service-worker.js
self.addEventListener('push', (event) => {
  if (!event.data) return
  
  try {
    const data = event.data.json()
    const options = {
      body: data.body,
      icon: '/assets/icon.png',
      badge: '/assets/badge.png',
      vibrate: [100, 50, 100],
      data: {
        url: data.url || '/'
      }
    }
    
    event.waitUntil(
      self.registration.showNotification(data.title, options)
    )
  } catch (error) {
    console.error('Failed to process push event:', error)
  }
})

// 处理通知点击事件
self.addEventListener('notificationclick', (event) => {
  event.notification.close()
  
  const url = event.notification.data.url
  event.waitUntil(
    clients.openWindow(url)
  )
})

9. Workbox 高级配置

9.1 自定义 Workbox 配置

我们可以在 vite.config.js 中自定义 Workbox 配置:

// vite.config.js
VitePWA({
  // ...
  workbox: {
    // 预缓存配置
    globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
    globIgnores: ['node_modules/**/*', '.git/**/*', 'dist/**/*.map'],
    maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
    
    // 运行时缓存配置
    runtimeCaching: [
      {
        urlPattern: /^https:\/\/api\.example\.com\//,
        handler: 'NetworkFirst',
        options: {
          cacheName: 'api-cache',
          expiration: {
            maxEntries: 100,
            maxAgeSeconds: 60 * 60 * 24
          },
          cacheableResponse: {
            statuses: [0, 200]
          }
        }
      }
    ],
    
    // 其他配置
    skipWaiting: true,
    clientsClaim: true
  }
})

9.2 使用 Workbox 插件

Workbox 支持插件机制,我们可以使用内置插件或自定义插件来扩展 Workbox 的功能:

// public/service-worker.js
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'

// 自定义插件
class CustomPlugin {
  // 插件方法
  requestWillFetch({ request }) {
    console.log('Request will fetch:', request.url)
    return request
  }
  
  cacheWillUpdate({ request, response }) {
    console.log('Cache will update:', request.url, response.status)
    return response
  }
  
  cacheDidUpdate({ request, oldResponse, newResponse }) {
    console.log('Cache did update:', request.url)
  }
}

registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 60 * 60 * 24 * 30
      }),
      new CacheableResponsePlugin({
        statuses: [0, 200]
      }),
      new CustomPlugin() // 使用自定义插件
    ]
  })
)

最佳实践

1. 合理选择缓存策略

  • 静态资源:使用 CacheFirst 策略,配合较长的过期时间
  • API 请求:使用 NetworkFirstStaleWhileRevalidate 策略
  • 频繁更新的资源:使用 NetworkFirst 策略
  • 不常更新的资源:使用 CacheFirst 策略
  • 关键资源:使用 NetworkOnly 策略,确保获取最新资源

2. 优化预缓存

  • 只预缓存必要的资源,避免预缓存过大的资源
  • 为预缓存资源设置合理的版本号
  • 定期清理旧的预缓存资源

3. 管理缓存大小

  • 设置合理的 maxEntriesmaxAgeSeconds
  • 使用 purgeOnQuotaError: true 选项
  • 定期清理不需要的缓存

4. 实现优雅降级

  • 在 Service Worker 不可用时,确保应用仍能正常工作
  • 实现适当的错误处理,避免 Service Worker 错误影响应用

5. 监控 Service Worker 性能

  • 添加日志记录,便于调试和分析问题
  • 使用 Performance API 监控 Service Worker 性能
  • 监控缓存命中率,优化缓存策略

6. 测试 Service Worker

  • 测试不同网络条件下的应用表现
  • 测试 Service Worker 的更新机制
  • 测试缓存策略的效果
  • 测试背景同步和推送通知功能

常见问题和解决方案

1. 缓存策略导致用户看不到最新内容

问题:用户看到的是旧内容,无法获取最新内容

解决方案

  • 对频繁更新的资源使用 NetworkFirstStaleWhileRevalidate 策略
  • 为静态资源添加版本号或哈希值
  • 实现 Service Worker 自动更新机制
  • 使用 workbox-precachingcleanupOutdatedCaches 方法清理旧缓存

2. 预缓存资源过大

问题:预缓存资源过大,导致 Service Worker 安装失败

解决方案

  • 只预缓存必要的资源
  • 对大文件使用运行时缓存,不进行预缓存
  • 调整 maximumFileSizeToCacheInBytes 选项
  • 压缩和优化资源,减少资源大小

3. 背景同步不工作

问题:背景同步无法正常执行

解决方案

  • 确保浏览器支持 Background Sync API
  • 检查 Service Worker 中的背景同步配置
  • 实现适当的错误处理和重试机制
  • 检查网络连接状态

4. 推送通知无法正常工作

问题:推送通知无法发送或接收

解决方案

  • 确保使用 HTTPS 协议
  • 检查推送服务配置是否正确
  • 确保用户已授予通知权限
  • 检查 Service Worker 中的推送事件处理逻辑

5. Service Worker 性能问题

问题:Service Worker 导致应用性能下降

解决方案

  • 减少 Service Worker 脚本体积
  • 优化缓存策略,避免不必要的缓存
  • 优化路由匹配,避免复杂的匹配逻辑
  • 合理使用异步操作,避免阻塞主线程

进阶学习资源

1. 官方文档

2. 教程和博客

3. 视频资源

4. 开源项目和示例

实践练习

练习 1:Vue 3 集成 Workbox

  1. 创建一个 Vue 3 应用
  2. 使用 vite-plugin-pwa 插件集成 Workbox
  3. 配置预缓存和运行时缓存
  4. 测试应用的离线工作能力
  5. 测试应用的自动更新功能

练习 2:Workbox 缓存策略

  1. 配置不同资源类型的缓存策略
  2. 测试不同缓存策略的效果
  3. 实现自定义缓存策略
  4. 优化缓存策略,提高应用性能

练习 3:Workbox 背景同步

  1. 配置 Workbox 背景同步
  2. 测试背景同步功能
  3. 实现背景同步的错误处理和重试机制
  4. 测试不同网络条件下的背景同步表现

练习 4:Workbox 推送通知

  1. 配置 Workbox 推送通知
  2. 测试推送通知的订阅和取消订阅
  3. 测试推送通知的显示和点击处理
  4. 实现推送通知的高级功能,如通知操作和数据传递

练习 5:Workbox 性能优化

  1. 优化预缓存配置
  2. 优化缓存策略
  3. 实现自定义插件监控 Service Worker 性能
  4. 测试不同配置下的应用性能

总结

Workbox 是构建 PWA 的强大工具库,它简化了 Service Worker 的开发和维护工作。在 Vue 3 应用中深度集成 Workbox 可以帮助我们构建功能完善、性能优良的 PWA 应用。

在本集中,我们学习了 Workbox 的核心功能以及如何在 Vue 3 应用中深度集成 Workbox。我们探讨了预缓存配置、运行时缓存策略、路由处理、缓存管理、背景同步、推送通知等高级功能,并介绍了相关的最佳实践和常见问题解决方案。

通过掌握 Workbox 的深度集成,你将能够构建更加可靠、高效、用户体验更好的 Vue 3 PWA 应用。Workbox 为 Vue 3 应用提供了强大的后台功能支持,是现代 Web 应用开发的重要工具之一。

下一集我们将学习 Vue 3 与 Web Share API,敬请期待!

« 上一篇 Vue 3与Service Worker高级应用 - 构建强大后台功能的核心技术 下一篇 » Vue 3与Web Share API - 实现原生设备分享功能的核心技术