探讨 Vue CLI 中的 PWA 插件如何集成 Service Worker,实现离线缓存、推送通知和添加到主屏幕功能。

各位观众老爷,晚上好!我是今晚的讲师,今天咱们不聊风花雪月,就聊聊Vue CLI的PWA插件,看看它怎么把Service Worker这玩意儿玩得转,让你的Vue应用也能离线可用,还能推送通知,甚至能添加到主屏幕,瞬间逼格满满。

开场白:PWA是个啥?Service Worker又是啥?

咱们先来个热身,简单解释一下PWA和Service Worker是啥,如果你已经滚瓜烂熟,可以直接跳到下一节。

  • PWA (Progressive Web App): 这玩意儿不是一个具体的技术,而是一种理念!就是让你的网站像原生App一样丝滑流畅,拥有离线访问、推送通知等特性。简单来说,就是让网页更像App。

  • Service Worker: 这才是真正干活的!它是一个运行在浏览器后台的脚本,可以拦截网络请求,缓存资源,推送通知等等。它是PWA的核心技术,没有它,PWA就是个空壳子。你可以把它想象成一个默默守护你的应用的管家,在你网络不好的时候,还能给你端茶倒水(提供缓存)。

第一节:Vue CLI PWA插件,一键梭哈!

Vue CLI 提供了非常方便的 PWA 插件,让咱们可以一键集成 PWA 功能,省去了手动配置 Service Worker 的烦恼。

  1. 安装插件:

    首先,确保你已经有了一个 Vue CLI 项目。如果没有,先用 vue create my-project 创建一个。 然后,进入你的项目目录,运行以下命令安装 PWA 插件:

    vue add @vue/pwa

    这个命令会帮你完成以下操作:

    • 安装 register-service-worker 依赖。
    • public 目录下生成 manifest.json 文件(用于配置 PWA 应用信息)。
    • src/registerServiceWorker.js 文件中注册 Service Worker。
    • 修改 vue.config.js 文件(如果需要)。
  2. manifest.json:PWA的身份证!

    manifest.json 文件是 PWA 应用的“身份证”,它包含了应用的名称、图标、描述等信息,浏览器会根据这些信息来安装 PWA 应用到用户的设备上。打开 public/manifest.json 文件,你会看到类似下面的内容:

    {
     "name": "my-project",
     "short_name": "my-project",
     "icons": [
       {
         "src": "./img/icons/android-chrome-192x192.png",
         "sizes": "192x192",
         "type": "image/png"
       },
       {
         "src": "./img/icons/android-chrome-512x512.png",
         "sizes": "512x512",
         "type": "image/png"
       }
     ],
     "start_url": ".",
     "display": "standalone",
     "theme_color": "#000000",
     "background_color": "#ffffff"
    }
    • name: 应用的完整名称,会显示在安装提示和启动画面上。
    • short_name: 应用的简称,如果完整名称太长,会显示这个。
    • icons: 应用的图标,不同尺寸的图标,用于不同的场景。
    • start_url: 应用启动时打开的 URL。
    • display: 应用的显示模式,standalone 表示以独立窗口模式运行,就像原生 App 一样。
    • theme_color: 应用的主题颜色,会影响状态栏的颜色。
    • background_color: 应用的背景颜色,在应用启动时会显示。

    重点: 你需要根据你的应用信息修改这些配置,尤其是 nameshort_nameicons。 把 icons 替换成你自己的应用图标,并确保提供不同尺寸的版本。

  3. registerServiceWorker.js:Service Worker的注册员!

    src/registerServiceWorker.js 文件负责注册 Service Worker。打开这个文件,你会看到类似下面的代码:

    import { register } from 'register-service-worker'
    
    if (process.env.NODE_ENV === 'production') {
     register(`${process.env.BASE_URL}service-worker.js`, {
       ready () {
         console.log(
           'App is being served from cache by service worker.n' +
           'For more details, visit https://goo.gl/AFskqB'
         )
       },
       registered () {
         console.log('Service worker has been registered.')
       },
       cached () {
         console.log('Content has been cached for offline use.')
       },
       updatefound () {
         console.log('New content is downloading.')
       },
       updated () {
         console.log('New content is available; please refresh.')
       },
       offline () {
         console.log('No internet connection found. App is running in offline mode.')
       },
       error (error) {
         console.error('Error during service worker registration:', error)
       }
     })
    }

    这段代码会在生产环境下注册 Service Worker。你可以通过 readyregisteredcached 等回调函数来监听 Service Worker 的状态。 这些回调函数可以帮你了解 Service Worker 的生命周期,比如什么时候缓存完成,什么时候有更新等等。

  4. service-worker.js:干活的来了!

    这个文件才是真正实现离线缓存的核心!但是,Vue CLI PWA 插件默认使用 Workbox 来生成 service-worker.js 文件,这个文件是自动生成的,一般情况下你不需要手动修改它。Workbox 是一套 Google 提供的 Service Worker 工具库,可以帮你简化 Service Worker 的开发。

第二节:自定义Service Worker配置,玩出花样!

虽然 Vue CLI PWA 插件已经很方便了,但有时候我们还是需要自定义 Service Worker 的配置,比如缓存策略、预缓存资源等等。

  1. vue.config.js:配置的入口!

    所有的 PWA 相关配置都放在 vue.config.js 文件中。打开这个文件,找到 pwa 选项。如果没有,你可以手动添加一个:

    module.exports = {
     pwa: {
       // PWA 相关配置
     }
    }
  2. workboxPluginMode:选择生成模式!

    workboxPluginMode 选项决定了 Workbox 的生成模式,有两种模式:

    • GenerateSW: 这是默认模式,Workbox 会自动生成 service-worker.js 文件,并根据你的配置进行优化。

    • InjectManifest: 这种模式允许你手动编写 service-worker.js 文件,Workbox 会将你的配置注入到这个文件中。

    如果你需要完全控制 Service Worker 的行为,可以选择 InjectManifest 模式。

    module.exports = {
     pwa: {
       workboxPluginMode: 'InjectManifest',
       workboxOptions: {
         swSrc: 'src/service-worker.js' // 指定你的 Service Worker 文件
       }
     }
    }
  3. workboxOptions:配置的宝库!

    workboxOptions 选项包含了 Workbox 的所有配置项,你可以通过它来控制 Service Worker 的行为。 太多了,咱们挑几个常用的说:

    • swSrc: 指定你的 Service Worker 文件的路径,只有在 workboxPluginModeInjectManifest 时才需要配置。

    • swDest: 指定生成的 Service Worker 文件的路径,默认为 dist/service-worker.js

    • importWorkboxFrom: 指定 Workbox 的来源,可以是 cdnlocal,默认为 cdn。 如果选择 local,Workbox 会被打包到你的项目中。

    • clientsClaim: 是否让 Service Worker 立即控制所有客户端,默认为 true

    • skipWaiting: 是否跳过等待阶段,立即激活新的 Service Worker,默认为 true

    • runtimeCaching: 配置运行时缓存策略,这是最重要的一个配置项!

  4. runtimeCaching:缓存策略!

    runtimeCaching 选项用于配置运行时缓存策略,你可以根据不同的 URL 设置不同的缓存策略。

    module.exports = {
     pwa: {
       workboxOptions: {
         runtimeCaching: [
           {
             urlPattern: /^https://fonts.(googleapis|gstatic).com/.*/,
             handler: 'CacheFirst',
             options: {
               cacheName: 'google-fonts-cache',
               expiration: {
                 maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
                 maxEntries: 30
               }
             }
           },
           {
             urlPattern: /.(?:png|jpg|jpeg|svg|gif)$/,
             handler: 'CacheFirst',
             options: {
               cacheName: 'images-cache',
               expiration: {
                 maxEntries: 60,
                 maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days
               }
             }
           },
           {
             urlPattern: new RegExp('^https://your-api-domain.com/api/'),
             handler: 'NetworkFirst',
             options: {
               cacheName: 'api-cache',
               networkTimeoutSeconds: 5,
               expiration: {
                 maxEntries: 50,
                 maxAgeSeconds: 60 * 60 // 1 hour
               },
               plugins: [
                 {
                   handlerDidError: async ({ request }) => {
                     console.log('Failed to fetch', request.url);
                   }
                 }
               ]
             }
           }
         ]
       }
     }
    }

    上面的代码配置了三个缓存策略:

    • Google Fonts: 使用 CacheFirst 策略,优先从缓存中读取,如果缓存中没有,则从网络请求,并将结果缓存起来。缓存时间为一年。
    • Images: 使用 CacheFirst 策略,缓存图片资源。缓存时间为 30 天。
    • API: 使用 NetworkFirst 策略,优先从网络请求,如果网络请求失败,则从缓存中读取。缓存时间为 1 小时。

    handler 的选择:

    • CacheFirst: 优先从缓存中读取,适用于静态资源,如图片、字体等。
    • NetworkFirst: 优先从网络请求,适用于 API 接口,可以保证数据的实时性。
    • StaleWhileRevalidate: 先从缓存中读取,然后发起网络请求更新缓存,适用于对实时性要求不高的场景。
    • NetworkOnly: 只从网络请求,不使用缓存。
    • CacheOnly: 只从缓存中读取,不发起网络请求。

    options 的配置:

    • cacheName: 缓存的名称。
    • expiration: 缓存的过期时间。
    • plugins: 插件,可以用来扩展缓存策略的功能,比如添加请求头、处理错误等。

第三节:手动编写Service Worker,掌控全局!

如果你选择了 InjectManifest 模式,就需要手动编写 service-worker.js 文件。 这需要你对 Service Worker 的生命周期和 API 有一定的了解。

  1. Service Worker 的生命周期:

    Service Worker 有三个主要的生命周期阶段:

    • Install: 安装阶段,在这个阶段可以缓存静态资源。

    • Activate: 激活阶段,在这个阶段可以清理旧的缓存。

    • Fetch: 拦截网络请求阶段,在这个阶段可以从缓存中读取资源,或者发起网络请求。

  2. src/service-worker.js:你的舞台!

    创建一个 src/service-worker.js 文件,并按照你的需求编写 Service Worker 代码。

    import { precacheAndRoute } from 'workbox-precaching'
    import { registerRoute } from 'workbox-routing'
    import { CacheFirst, NetworkFirst } from 'workbox-strategies'
    import { ExpirationPlugin } from 'workbox-expiration'
    
    // 预缓存资源 (Vue CLI PWA 插件会自动生成这个变量)
    precacheAndRoute(self.__WB_MANIFEST)
    
    // 缓存 Google Fonts
    registerRoute(
     /^https://fonts.(googleapis|gstatic).com/.*/,
     new CacheFirst({
       cacheName: 'google-fonts-cache',
       plugins: [
         new ExpirationPlugin({
           maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days
           maxEntries: 30
         })
       ]
     })
    )
    
    // 缓存图片
    registerRoute(
     /.(?:png|jpg|jpeg|svg|gif)$/,
     new CacheFirst({
       cacheName: 'images-cache',
       plugins: [
         new ExpirationPlugin({
           maxEntries: 60,
           maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days
         })
       ]
     })
    )
    
    // 缓存 API 接口
    registerRoute(
     new RegExp('^https://your-api-domain.com/api/'),
     new NetworkFirst({
       cacheName: 'api-cache',
       networkTimeoutSeconds: 5,
       plugins: [
         new ExpirationPlugin({
           maxEntries: 50,
           maxAgeSeconds: 60 * 60 // 1 hour
         })
       ]
     })
    )
    
    self.addEventListener('message', (event) => {
     if (event.data && event.data.type === 'SKIP_WAITING') {
       self.skipWaiting()
     }
    })
    • precacheAndRoute: 预缓存资源,Vue CLI PWA 插件会自动生成一个包含所有需要预缓存的资源的列表,存储在 self.__WB_MANIFEST 变量中。

    • registerRoute: 注册路由,用于拦截指定 URL 的请求,并使用指定的缓存策略。

    • CacheFirstNetworkFirst:缓存策略,与 vue.config.js 中的 runtimeCaching 选项中的 handler 作用相同。

    • ExpirationPlugin: 缓存过期插件,用于设置缓存的过期时间和最大条目数。

    • self.addEventListener('message', ...): 监听来自页面的消息,用于跳过等待阶段,立即激活新的 Service Worker。

第四节:推送通知,搞事情!

PWA 的一个重要特性就是推送通知,可以让你的应用像原生 App 一样,主动向用户发送消息。

  1. VAPID Keys:身份验证!

    要使用推送通知,首先需要生成 VAPID (Voluntary Application Server Identification) Keys,这是一对公钥和私钥,用于验证你的服务器的身份。

    可以使用 web-push 这个 npm 包来生成 VAPID Keys:

    npm install web-push --save
    const webpush = require('web-push');
    
    const vapidKeys = webpush.generateVAPIDKeys();
    
    console.log(vapidKeys.publicKey);
    console.log(vapidKeys.privateKey);

    将生成的公钥和私钥保存起来,后面会用到。

  2. 配置 VAPID Keys:

    在你的服务器端代码中,配置 VAPID Keys:

    const webpush = require('web-push');
    
    webpush.setVapidDetails(
     'mailto:[email protected]', // 你的邮箱
     'YOUR_PUBLIC_KEY', // 你的公钥
     'YOUR_PRIVATE_KEY'  // 你的私钥
    );
  3. 订阅推送:

    在客户端代码中,你需要先检查浏览器是否支持推送通知,然后获取用户的许可,才能订阅推送。

    if ('serviceWorker' in navigator && 'PushManager' in window) {
     navigator.serviceWorker.ready
       .then(function(registration) {
         return registration.pushManager.getSubscription()
           .then(function(subscription) {
             if (subscription) {
               // 用户已经订阅了推送
               return subscription;
             }
    
             const vapidPublicKey = 'YOUR_PUBLIC_KEY';
    
             return registration.pushManager.subscribe({
               userVisibleOnly: true,
               applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
             });
           });
       })
       .then(function(subscription) {
         // 将订阅信息发送到服务器
         fetch('/api/subscribe', {
           method: 'POST',
           body: JSON.stringify(subscription),
           headers: {
             'content-type': 'application/json'
           }
         });
       })
       .catch(function(error) {
         console.error('Error subscribing to push notifications:', error);
       });
    }
    
    function urlBase64ToUint8Array(base64String) {
     const padding = '='.repeat((4 - base64String.length % 4) % 4);
     const base64 = (base64String + padding)
       .replace(/-/g, '+')
       .replace(/_/g, '/');
    
     const rawData = window.atob(base64);
     const outputArray = new Uint8Array(rawData.length);
    
     for (let i = 0; i < rawData.length; ++i) {
       outputArray[i] = rawData.charCodeAt(i);
     }
     return outputArray;
    }
    • navigator.serviceWorker.ready: 等待 Service Worker 注册完成。
    • registration.pushManager.getSubscription(): 检查用户是否已经订阅了推送。
    • registration.pushManager.subscribe(): 订阅推送,需要提供 VAPID 公钥。
    • urlBase64ToUint8Array(): 将 Base64 编码的 VAPID 公钥转换为 Uint8Array 格式。
    • 将订阅信息发送到服务器,服务器需要保存这些信息,用于后续推送消息。
  4. 发送推送:

    在服务器端,可以使用 web-push 发送推送消息:

    const webpush = require('web-push');
    
    const subscription = {
     endpoint: '...', // 用户的订阅 endpoint
     keys: {
       p256dh: '...', // 用户的公钥
       auth: '...'      // 用户的授权密钥
     }
    };
    
    const payload = JSON.stringify({
     title: 'Hello PWA!',
     body: 'This is a push notification!',
     icon: 'path/to/your/icon.png'
    });
    
    webpush.sendNotification(subscription, payload)
     .catch(error => console.error(error));
    • subscription: 用户的订阅信息,从数据库中获取。
    • payload: 推送消息的内容,包含标题、内容和图标。

第五节:添加到主屏幕,更像App!

PWA 还可以添加到用户的主屏幕,就像原生 App 一样。 当用户访问你的 PWA 应用时,浏览器会提示用户是否要添加到主屏幕。

  1. manifest.json:关键!

    要让 PWA 可以添加到主屏幕,manifest.json 文件必须配置正确。 确保 nameshort_nameiconsstart_urldisplay 等字段都已正确配置。

  2. Service Worker:默默支持!

    Service Worker 必须注册成功,才能让 PWA 可以离线访问和添加到主屏幕。

  3. HTTPS:安全保障!

    PWA 必须部署在 HTTPS 协议下,才能正常工作。

总结:PWA,让你的网站起飞!

通过 Vue CLI PWA 插件,我们可以很方便地将 PWA 功能集成到 Vue 应用中,让你的网站拥有离线缓存、推送通知和添加到主屏幕等特性,提升用户体验,让你的网站更像原生 App。

当然,PWA 的内容还有很多,比如后台同步、Web Share API 等等,需要进一步学习和探索。 希望今天的讲座能帮助你入门 PWA,让你的网站起飞!

感谢各位的观看,下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注