阐述 Vue CLI 源码中 `plugin-pwa` 如何集成 `Workbox`,实现 PWA 的 Service Worker 注册和缓存策略。

各位老铁,晚上好!今儿个咱们聊聊 Vue CLI 里的 plugin-pwa,看看它怎么把 Workbox 这玩意儿给安排上,给咱们的 Vue 应用整个 PWA,让它离线也能浪起来。咱可不是光说不练的主儿,代码伺候!

开场白:PWA 是个啥?Workbox 又是个啥?

先来个热身运动,简单说说 PWA 和 Workbox。

  • PWA (Progressive Web App): 这玩意儿就是想让你的 Web 应用像 Native App 一样丝滑。离线访问、推送通知、添加到桌面,这些都是它的拿手好戏。
  • Workbox: 这是 Google 出的工具箱,专门用来简化 Service Worker 的开发。有了它,你就不用手撸复杂的 Service Worker 代码了,配置一下就能搞定缓存策略、离线支持等等。

Vue CLI plugin-pwa:PWA 的好帮手

Vue CLI 的 plugin-pwa 就像个贴心的管家,帮你把 PWA 的基础设施都安排好了。它主要做了以下几件事:

  1. 生成 Service Worker: 自动生成一个 Service Worker 文件(通常是 service-worker.jsregisterServiceWorker.js),负责处理离线缓存、资源更新等逻辑。
  2. 集成 Workbox: 使用 Workbox 来管理 Service Worker 的缓存策略,简化开发流程。
  3. 注册 Service Worker: 在你的 Vue 应用中注册 Service Worker,让它开始工作。
  4. 生成 Manifest 文件: 生成 manifest.json 文件,定义 PWA 的元数据,比如应用名称、图标、启动画面等等。

源码分析:plugin-pwa 是怎么玩转 Workbox 的?

咱们现在就来深入源码,看看 plugin-pwa 到底是怎么把 Workbox 集成进来的。

1. 安装和配置

当你使用 Vue CLI 创建项目并选择安装 plugin-pwa 时,它会做以下事情:

  • 安装依赖: 安装 workbox-webpack-pluginregister-service-worker 这两个关键的 npm 包。

    • workbox-webpack-plugin:在 Webpack 打包过程中生成 Service Worker 文件,并配置缓存策略。
    • register-service-worker:一个简单的库,用于在你的 Vue 应用中注册 Service Worker。
  • 修改 vue.config.js: 在你的 vue.config.js 文件中添加 pwa 配置项,用于自定义 PWA 的行为。

    // vue.config.js
    module.exports = {
     pwa: {
       name: 'My Awesome PWA', // 应用名称
       themeColor: '#4DBA87',  // 主题颜色
       msTileColor: '#000000',
       appleMobileWebAppCapable: 'yes',
       appleMobileWebAppStatusBarStyle: 'black',
       // configure the workbox plugin
       workboxPluginMode: 'GenerateSW', // 或 InjectManifest
       workboxOptions: {
         // swSrc: 'src/service-worker.js', // 如果使用 InjectManifest,需要指定 Service Worker 文件
         // ...其他 Workbox 配置
       }
     }
    }

2. Workbox 插件模式:GenerateSW vs InjectManifest

workboxPluginMode 选项决定了 plugin-pwa 如何使用 Workbox。它有两个可选值:

  • GenerateSW (默认): 这是最简单的模式。Workbox 会自动生成完整的 Service Worker 文件,并根据你的配置自动处理缓存策略。你只需要配置 workboxOptions 即可。

  • InjectManifest: 这个模式更灵活。你需要自己创建一个 Service Worker 文件 (例如 src/service-worker.js),并在其中编写自定义的 Service Worker 逻辑。Workbox 会将你的配置注入到这个文件中。

2.1 GenerateSW 模式

这是最常用的模式,因为它简单易用。

  • 配置 workboxOptions: 你可以通过 workboxOptions 对象来配置 Workbox 的行为。例如,你可以指定缓存的文件类型、缓存策略等等。

    // vue.config.js
    module.exports = {
     pwa: {
       workboxPluginMode: 'GenerateSW',
       workboxOptions: {
         skipWaiting: true, // 立即激活新的 Service Worker
         clientsClaim: true, // 控制 Service Worker 的范围
         runtimeCaching: [ // 配置运行时缓存
           {
             urlPattern: new RegExp('https://fonts.(googleapis|gstatic).com/(.*)'),
             handler: 'CacheFirst',
             options: {
               cacheName: 'google-fonts-cache',
               expiration: {
                 maxEntries: 30,
               },
               cacheableResponse: {
                 statuses: [0, 200]
               }
             }
           },
           {
             urlPattern: /.(png|jpg|jpeg|svg|gif)$/,
             handler: 'CacheFirst',
             options: {
               cacheName: 'images-cache',
               expiration: {
                 maxEntries: 60,
                 maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
               },
               cacheableResponse: {
                 statuses: [0, 200]
               }
             }
           }
         ]
       }
     }
    }
  • 自动生成 Service Worker: 在 Webpack 打包过程中,workbox-webpack-plugin 会根据你的配置自动生成 dist/service-worker.js 文件。这个文件包含了 Workbox 的核心代码和你的缓存策略。

  • Service Worker 内容示例: dist/service-worker.js 的内容大致如下 (简化版):

    importScripts("https://storage.googleapis.com/workbox-cdn/releases/6.6.1/workbox-sw.js");
    
    if (workbox) {
     console.log(`Workbox is loaded`);
    
     workbox.routing.registerRoute(
       new RegExp('https://fonts.(googleapis|gstatic).com/(.*)'),
       new workbox.strategies.CacheFirst({
         cacheName: 'google-fonts-cache',
         plugins: [
           new workbox.expiration.ExpirationPlugin({
             maxEntries: 30,
           }),
           new workbox.cacheableResponse.CacheableResponsePlugin({
             statuses: [0, 200],
           })
         ]
       })
     );
    
     workbox.routing.registerRoute(
       /.(png|jpg|jpeg|svg|gif)$/,
       new workbox.strategies.CacheFirst({
         cacheName: 'images-cache',
         plugins: [
           new workbox.expiration.ExpirationPlugin({
             maxEntries: 60,
             maxAgeSeconds: 2592000,
           }),
           new workbox.cacheableResponse.CacheableResponsePlugin({
             statuses: [0, 200],
           })
         ]
       })
     );
    
     // 预缓存静态资源
     workbox.precaching.precacheAndRoute([
       { url: '/index.html', revision: 'YOUR_REVISION' },
       { url: '/css/app.css', revision: 'YOUR_REVISION' },
       { url: '/js/app.js', revision: 'YOUR_REVISION' },
       // ... 其他静态资源
     ]);
    
     // 其他 Workbox 配置...
    
    } else {
     console.log(`Workbox didn't load`);
    }

2.2 InjectManifest 模式

如果你需要更精细地控制 Service Worker 的行为,可以使用 InjectManifest 模式。

  • 创建 Service Worker 文件: 首先,你需要创建一个 Service Worker 文件,例如 src/service-worker.js。在这个文件中,你可以编写自定义的 Service Worker 逻辑,比如处理推送通知、后台同步等等。

    // src/service-worker.js
    import { precacheAndRoute } from 'workbox-precaching';
    import { registerRoute } from 'workbox-routing';
    import { CacheFirst } from 'workbox-strategies';
    import { CacheableResponsePlugin } from 'workbox-cacheable-response';
    import { ExpirationPlugin } from 'workbox-expiration';
    
    // 预缓存静态资源 (使用 webpack 的 definePlugin 注入)
    precacheAndRoute(self.__WB_MANIFEST);
    
    // 缓存 Google Fonts
    registerRoute(
     new RegExp('https://fonts.(googleapis|gstatic).com/(.*)'),
     new CacheFirst({
       cacheName: 'google-fonts-cache',
       plugins: [
         new CacheableResponsePlugin({
           statuses: [0, 200],
         }),
         new ExpirationPlugin({
           maxEntries: 30,
         }),
       ],
     })
    );
    
    // 自定义推送通知处理
    self.addEventListener('push', (event) => {
     const title = 'My Awesome PWA';
     const options = {
       body: event.data.text(),
       icon: '/img/icons/android-chrome-192x192.png',
     };
     event.waitUntil(self.registration.showNotification(title, options));
    });
    
    // 其他自定义 Service Worker 逻辑...
  • 配置 workboxOptions:vue.config.js 中,你需要指定 swSrc 选项,告诉 Workbox 你的 Service Worker 文件的路径。

    // vue.config.js
    module.exports = {
     pwa: {
       workboxPluginMode: 'InjectManifest',
       workboxOptions: {
         swSrc: 'src/service-worker.js', // 指定 Service Worker 文件
         swDest: 'service-worker.js', // 输出的 Service Worker 文件名 (可选)
       }
     }
    }
  • 注入配置: 在 Webpack 打包过程中,workbox-webpack-plugin 会读取你的 src/service-worker.js 文件,并将你的配置注入到这个文件中。例如,它会将预缓存的资源列表注入到 self.__WB_MANIFEST 变量中。

3. 注册 Service Worker

plugin-pwa 会在你的 Vue 应用中自动注册 Service Worker。它会创建一个 registerServiceWorker.js 文件,并在你的 main.js 文件中引入它。

  • registerServiceWorker.js 内容:

    // registerServiceWorker.js
    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 a 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)
       }
     })
    }
  • main.js 引入:

    // main.js
    import Vue from 'vue'
    import App from './App.vue'
    import './registerServiceWorker' // 引入 registerServiceWorker.js
    
    Vue.config.productionTip = false
    
    new Vue({
     render: h => h(App),
    }).$mount('#app')

缓存策略:Workbox 的核心

Workbox 提供了多种缓存策略,你可以根据你的需求选择合适的策略。

策略 描述 适用场景
CacheFirst 首先从缓存中获取资源,如果缓存中没有,则从网络获取,并将资源添加到缓存中。 静态资源 (例如图片、字体、CSS、JS 文件),这些资源通常不会频繁更新。
NetworkFirst 首先从网络获取资源,如果网络请求失败,则从缓存中获取。 动态资源 (例如 API 请求),这些资源需要保持最新。
StaleWhileRevalidate 首先从缓存中获取资源,同时在后台更新缓存。用户会立即看到缓存中的内容,但下次访问时会看到最新的内容。 对实时性要求不高,但希望尽快显示内容的场景 (例如新闻列表)。
NetworkOnly 始终从网络获取资源,不使用缓存。 某些特殊的 API 请求,不希望被缓存。
CacheOnly 始终从缓存中获取资源,不访问网络。 离线应用或者某些不需要更新的资源。

自定义缓存策略

你可以通过 workboxOptions.runtimeCaching 数组来配置自定义的缓存策略。每个元素都代表一个缓存规则,包含以下属性:

  • urlPattern: 一个正则表达式,用于匹配需要缓存的 URL。
  • handler: 缓存策略的名称 (例如 CacheFirst, NetworkFirst)。
  • options: 缓存策略的选项,例如 cacheName (缓存名称)、expiration (过期时间) 等等。

其他配置

plugin-pwa 还提供了许多其他的配置选项,你可以根据你的需求进行调整。

  • name: PWA 应用的名称,显示在添加到桌面时的图标下方。
  • themeColor: 应用的主题颜色,用于浏览器地址栏和任务栏。
  • msTileColor: Windows 磁贴的颜色。
  • appleMobileWebAppCapable: 是否以全屏模式运行在 iOS 设备上。
  • appleMobileWebAppStatusBarStyle: iOS 状态栏的样式。

总结

Vue CLI 的 plugin-pwa 极大地简化了 PWA 的开发流程。它通过集成 Workbox,让你能够轻松地配置缓存策略、实现离线访问等功能。你可以根据你的需求选择 GenerateSWInjectManifest 模式,并自定义缓存策略,打造一个高性能、可靠的 PWA 应用。

最后的唠叨:注意事项

  • HTTPS: Service Worker 只能在 HTTPS 环境下运行 (或者 localhost)。
  • 缓存更新: Service Worker 的更新机制比较复杂,需要仔细测试。
  • 调试: 使用 Chrome DevTools 的 "Application" 面板可以调试 Service Worker。

好啦,今天的讲座就到这里。希望对大家有所帮助! 祝大家早日写出牛逼的 PWA 应用!

发表回复

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