# Service Worker 框架无关的缓存与激活机制

Table of Contents

Service Worker 是 Web 标准技术,完全独立于任何前端框架。它可以在 Vue、React、Angular 或原生 JavaScript 应用中工作。本文将展示在不同框架中如何实现相同的 Service Worker 功能。

Service Worker 的框架无关性

Service Worker 是浏览器原生 API,不依赖于任何框架:

// 在任何框架中都可以使用相同的注册代码
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then((registration) => console.log('SW registered'))
.catch((error) => console.log('SW registration failed'))
}

不同框架中的实现

1. Vue 应用中的实现

// main.js (Vue 3)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
})
}
// 在 Vue 组件中使用
export default {
mounted() {
this.checkForUpdates()
},
methods: {
checkForUpdates() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistration().then((registration) => {
if (registration) {
registration.addEventListener('updatefound', () => {
// 处理更新
})
}
})
}
},
},
}

2. React 应用中的实现

// index.js (React)
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
})
}
// React Hook 示例
import { useEffect } from 'react'
function useServiceWorker() {
useEffect(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistration().then((registration) => {
if (registration) {
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 显示更新提示
if (confirm('新版本可用,是否刷新?')) {
window.location.reload()
}
}
})
})
}
})
}
}, [])
}
// 在组件中使用
function App() {
useServiceWorker()
return <div>Your app content</div>
}

3. Angular 应用中的实现

// main.ts (Angular)
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
platformBrowserDynamic().bootstrapModule(AppModule)
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
})
}
// Angular Service
import { Injectable } from '@angular/core'
@Injectable({
providedIn: 'root',
})
export class ServiceWorkerService {
checkForUpdates() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistration().then((registration) => {
if (registration) {
registration.addEventListener('updatefound', () => {
// 处理更新逻辑
})
}
})
}
}
}

4. 原生 JavaScript 应用

// 原生 JavaScript 实现
class ServiceWorkerManager {
constructor() {
this.init()
}
async init() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js')
this.setupUpdateListener(registration)
} catch (error) {
console.error('SW registration failed:', error)
}
}
}
setupUpdateListener(registration) {
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
this.showUpdateNotification()
}
})
})
}
showUpdateNotification() {
if (confirm('新版本可用,是否刷新页面?')) {
window.location.reload()
}
}
}
// 使用
new ServiceWorkerManager()

通用的 Service Worker 文件

无论使用什么框架,Service Worker 文件本身都是相同的:

// public/sw.js - 适用于所有框架
const CACHE_NAME = 'app-cache-v1'
const STATIC_CACHE = 'static-v1'
const DYNAMIC_CACHE = 'dynamic-v1'
// 安装阶段
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/static/js/bundle.js',
'/static/css/main.css',
])
}),
)
})
// 激活阶段
self.addEventListener('activate', (event) => {
event.waitUntil(
Promise.all([
self.clients.claim(),
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (![CACHE_NAME, STATIC_CACHE, DYNAMIC_CACHE].includes(cacheName)) {
return caches.delete(cacheName)
}
}),
)
}),
]),
)
})
// 拦截请求
self.addEventListener('fetch', (event) => {
const { request } = event
// API 请求 - 网络优先
if (request.url.includes('/api/')) {
event.respondWith(
fetch(request)
.then((response) => {
const responseClone = response.clone()
caches.open(DYNAMIC_CACHE).then((cache) => cache.put(request, responseClone))
return response
})
.catch(() => caches.match(request)),
)
}
// 静态资源 - 缓存优先
else if (
request.destination === 'script' ||
request.destination === 'style' ||
request.destination === 'image'
) {
event.respondWith(
caches.match(request).then((response) => response || fetch(request)),
)
}
// 其他请求 - 网络优先
else {
event.respondWith(fetch(request))
}
})

框架特定的工具和插件

Vue CLI PWA 插件

Terminal window
# Vue 项目
vue add pwa
vue.config.js
module.exports = {
pwa: {
workboxPluginMode: 'GenerateSW',
workboxOptions: {
skipWaiting: true,
clientsClaim: true,
},
},
}

Create React App PWA

Terminal window
# React 项目
npx create-react-app my-app --template cra-template-pwa
src/index.js
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
serviceWorkerRegistration.register()

Angular PWA

Terminal window
# Angular 项目
ng add @angular/pwa
app.module.ts
import { ServiceWorkerModule } from '@angular/service-worker'
@NgModule({
imports: [
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production
})
]
})

通用最佳实践

1. 版本管理

// 所有框架通用的版本管理
const APP_VERSION = '1.0.0'
const CACHE_NAME = `app-cache-${APP_VERSION}`
// 在应用代码中检查版本
async function checkAppVersion() {
const response = await fetch('/version.json')
const { version } = await response.json()
if (version !== APP_VERSION) {
// 提示用户刷新
showUpdateNotification()
}
}

2. 离线处理

// 通用的离线页面处理
self.addEventListener('fetch', (event) => {
event.respondWith(
caches
.match(event.request)
.then((response) => response || fetch(event.request))
.catch(() => {
if (event.request.destination === 'document') {
return caches.match('/offline.html')
}
}),
)
})

3. 消息通信

// Service Worker 中
self.addEventListener('message', (event) => {
if (event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
// 应用代码中(适用于所有框架)
function skipWaiting() {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'SKIP_WAITING',
})
}
}

调试技巧

Chrome DevTools

// 在所有框架中都可以使用的调试代码
if (process.env.NODE_ENV === 'development') {
// 开发环境下的调试
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('SW Message:', event.data)
})
}

状态检查

// 通用的状态检查函数
async function getServiceWorkerStatus() {
if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.getRegistration()
if (registration) {
console.log('SW State:', registration.active?.state)
console.log('SW Script URL:', registration.active?.scriptURL)
}
}
}

总结

Service Worker 的框架无关性使其成为构建 PWA 的理想选择:

  1. 标准化: 基于 Web 标准,不依赖特定框架
  2. 兼容性: 在所有现代浏览器中工作
  3. 灵活性: 可以与任何前端框架结合使用
  4. 工具支持: 各框架都有相应的工具和插件

无论你使用 Vue、React、Angular 还是原生 JavaScript,Service Worker 的核心概念和实现方式都是相同的。框架只是提供了不同的集成方式和开发体验。

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


相关文章

评论