19.1 PWA核心能力解析
19.1.1 核心特性矩阵
// 特性检测与兼容性处理
const pwaFeatures = {
serviceWorker: 'serviceWorker' in navigator,
pushNotification: 'PushManager' in window,
backgroundSync: 'SyncManager' in window,
storage: 'indexedDB' in window || 'localStorage' in window,
installable: window.matchMedia('(display-mode: standalone)').matches
}
// 特性启用示例
if (pwaFeatures.serviceWorker) {
navigator.serviceWorker.register('/sw.js')
}
19.1.2 生命周期管理
19.2 Vue PWA项目配置
19.2.1 Vue CLI PWA插件
// vue.config.js
module.exports = {
pwa: {
name: 'My PWA',
themeColor: '#4DBA87',
msTileColor: '#000000',
appleMobileWebAppCapable: 'yes',
manifestOptions: {
background_color: '#ffffff',
icons: [
{
src: './img/icons/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png'
}
]
},
workboxPluginMode: 'InjectManifest',
workboxOptions: {
// 自定义Service Worker路径
swSrc: './src/sw.js',
// 排除开发环境缓存
exclude: [/\.map$/, /_redirects/]
}
}
}
19.2.2 自定义Service Worker
// src/sw.js
const CACHE_NAME = 'v1'
const PRECACHE_URLS = [
'/',
'/app.js',
'/styles.css',
'/img/logo.svg'
]
// 预缓存关键资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(PRECACHE_URLS))
})
// 缓存优先策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
})
// 后台同步示例
self.addEventListener('sync', event => {
if (event.tag === 'sync-forms') {
event.waitUntil(syncPendingForms())
}
})
19.3 离线功能增强
19.3.1 离线页面处理
// sw.js
const OFFLINE_URL = '/offline.html'
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(OFFLINE_URL))
}
})
// Vue组件中检测离线状态
export default {
data: () => ({
isOnline: navigator.onLine
}),
mounted() {
window.addEventListener('online', this.updateOnlineStatus)
window.addEventListener('offline', this.updateOnlineStatus)
},
methods: {
updateOnlineStatus() {
this.isOnline = navigator.onLine
}
}
}
19.3.2 数据持久化策略
// src/utils/offlineStorage.ts
class OfflineStorage {
private dbPromise: Promise<IDBDatabase>
constructor() {
this.dbPromise = new Promise((resolve, reject) => {
const request = indexedDB.open('pwa-store', 2)
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
if (!db.objectStoreNames.contains('forms')) {
db.createObjectStore('forms', { autoIncrement: true })
}
}
request.onsuccess = () => resolve(request.result)
request.onerror = reject
})
}
async saveFormData(data: object) {
const db = await this.dbPromise
return new Promise((resolve, reject) => {
const transaction = db.transaction('forms', 'readwrite')
const store = transaction.objectStore('forms')
const request = store.add(data)
request.onsuccess = resolve
request.onerror = reject
})
}
}
19.4 高级PWA功能实现
19.4.1 推送通知集成
// src/utils/notification.ts
export async function requestNotificationPermission() {
if (!('Notification' in window)) return 'unsupported'
const permission = await Notification.requestPermission()
if (permission === 'granted') {
await navigator.serviceWorker.ready
return true
}
return false
}
export function showLocalNotification(title: string, options: NotificationOptions) {
if (!('serviceWorker' in navigator)) return
navigator.serviceWorker.ready.then(registration => {
registration.showNotification(title, {
icon: '/img/notification-icon.png',
vibrate: [200, 100, 200],
...options
})
})
}
// 后台推送处理(sw.js)
self.addEventListener('push', event => {
const data = event.data?.json()
const options = {
body: data.body,
icon: '/img/icon-192x192.png',
badge: '/img/badge.png'
}
event.waitUntil(
self.registration.showNotification(data.title, options)
)
})
19.4.2 应用安装引导
<template>
<div v-if="showInstallPrompt" class="install-banner">
<button @click="installPWA">安装应用</button>
<button @click="dismissInstall">稍后再说</button>
</div>
</template>
<script>
let deferredPrompt
export default {
data: () => ({
showInstallPrompt: false
}),
mounted() {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredPrompt = e
this.showInstallPrompt = true
})
},
methods: {
async installPWA() {
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'accepted') {
console.log('用户接受安装')
}
this.showInstallPrompt = false
},
dismissInstall() {
this.showInstallPrompt = false
}
}
}
</script>
19.5 性能与体验优化
19.5.1 资源预加载策略
// vue.config.js
module.exports = {
chainWebpack: config => {
config.plugin('preload')
.tap(args => {
return [{
rel: 'preload',
include: 'all',
fileBlacklist: [/\.map$/, /hot-update\.js$/]
}]
})
}
}
// Service Worker中的预缓存策略
workbox.precaching.precacheAndRoute([
{ url: '/critical.css', revision: '123' },
{ url: '/app.js', revision: '456' }
])
19.5.2 自适应加载模式
// 基于网络状态的组件加载
const NetworkAwareComponent = defineAsyncComponent({
loader: () => {
if (navigator.connection?.effectiveType === '4g') {
return import('./HeavyComponent.vue')
}
return import('./LightComponent.vue')
},
loadingComponent: LoadingSpinner
})
// 基于设备能力的条件渲染
export default {
computed: {
supportsWebGL() {
const canvas = document.createElement('canvas')
return !!window.WebGLRenderingContext &&
!!canvas.getContext('webgl')
}
}
}
本章重点总结:
- 核心能力:掌握Service Worker生命周期与缓存策略
- 离线优先:实现可靠的离线体验与数据持久化
- 用户粘性:推送通知与安装提示增强互动
- 性能优化:智能预加载与自适应资源加载
- 渐进增强:兼容性处理与优雅降级策略
企业级PWA检查清单:
- Service Worker正确注册与更新
- 关键资源预缓存配置
- 离线页面与错误处理
- Manifest文件配置
- 苹果iOS特殊处理(apple-touch-icon)
- 推送通知权限管理
- 应用安装引导流程
- 跨平台兼容性测试
- Lighthouse性能评分 >90
- 定期清理过期缓存
// Lighthouse配置示例
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
isSinglePageApplication: true,
settings: {
chromeFlags: '--no-sandbox --headless'
}
},
assert: {
preset: 'lighthouse:recommended',
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'installable-manifest': 'off'
}
}
}
}