Bootstrap

第19章 Vue与渐进式Web应用(PWA)深度集成

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 生命周期管理

用户访问 浏览器 Service Worker 用户关闭页面 网络恢复 首次访问 开始注册 安装事件(install) 激活事件(activate) 控制页面(fetch) 进入空闲状态 后台同步(sync) 用户访问 浏览器 Service Worker 用户关闭页面 网络恢复

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')
    }
  }
}

本章重点总结:

  1. 核心能力:掌握Service Worker生命周期与缓存策略
  2. 离线优先:实现可靠的离线体验与数据持久化
  3. 用户粘性:推送通知与安装提示增强互动
  4. 性能优化:智能预加载与自适应资源加载
  5. 渐进增强:兼容性处理与优雅降级策略

企业级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'
      }
    }
  }
}
;