探讨 Vue CLI 如何集成 `workbox-webpack-plugin` 实现 PWA (Progressive Web App) 的离线功能。

各位程序猿朋友们,早上好/下午好/晚上好!我是你们的老朋友Bug猎手,今天咱们来聊聊Vue CLI和Workbox这俩好基友是如何联手打造PWA应用的,让你的网站即使在信号不好的地方也能溜得飞起!

PWA:让你的网站像App一样靠谱

首先,咱们得简单回顾一下PWA是啥。简单来说,PWA(Progressive Web App)就是一套让你的Web应用拥有类似原生App体验的技术集合。它具有以下特点:

  • 可靠性 (Reliable): 即使在离线或网络状况不佳的情况下也能快速加载。
  • 快速 (Fast): 响应迅速,提供流畅的用户体验。
  • 吸引人 (Engaging): 能够像原生应用一样安装到设备主屏幕,并接收推送通知。

Workbox就是谷歌爸爸提供的一套工具,专门用来简化PWA的开发流程,尤其是在Service Worker的编写和管理方面。

Vue CLI:PWA的官方推荐姿势

Vue CLI 绝对是Vue开发者的福音,它能帮你快速搭建项目框架,集成各种工具,当然也包括PWA。

第一步:创建Vue项目并启用PWA

如果你还没有Vue项目,可以使用Vue CLI创建一个:

vue create my-pwa-app

在项目创建过程中,CLI会问你是否要启用PWA支持。选择手动配置 (Manually select features) ,然后选中 "Progressive Web App (PWA) Support" 这一项。

第二步:Workbox的自动配置

选择了PWA支持后,Vue CLI会自动帮你安装并配置workbox-webpack-plugin。 它会做这些事情:

  1. 安装@vue/cli-plugin-pwa插件。
  2. vue.config.js 中配置 Workbox 插件。
  3. 创建一个 registerServiceWorker.js 文件,用于注册Service Worker。
  4. public 目录下生成 manifest.json 文件,用于描述你的PWA应用。

第三步:vue.config.js 中的Workbox配置

打开 vue.config.js 文件,你会发现里面已经有了PWA相关的配置:

module.exports = {
  // ... 其他配置

  pwa: {
    name: 'My PWA App', // 应用名称
    themeColor: '#4DBA87', // 主题颜色
    msTileColor: '#000000', // Windows磁贴颜色
    appleMobileWebAppCapable: 'yes', // 是否启用WebApp全屏模式
    appleMobileWebAppStatusBarStyle: 'black', // 状态栏样式
    // configure the workbox plugin
    workboxPluginMode: 'GenerateSW', // or 'InjectManifest'
    workboxOptions: {
      // 这些选项将直接传递给 workbox-webpack-plugin
      skipWaiting: true,
      clientsClaim: true,
      // ... 其他 Workbox 配置
    }
  }
}

这里有几个重要的配置项:

  • name: PWA 应用的名称,会显示在添加到主屏幕后的应用图标下方。
  • themeColor: PWA 应用的主题颜色,会影响浏览器工具栏的颜色。
  • workboxPluginMode: 指定 Workbox 插件的运行模式。
    • GenerateSW (默认): Workbox 会自动生成一个完整的 Service Worker 文件(service-worker.js)。这是最简单的方式,但灵活性较低。
    • InjectManifest: 你需要自己编写 Service Worker 文件,Workbox 会将你的配置注入到这个文件中。这种方式更灵活,但需要更多的手动配置。
  • workboxOptions: 一个对象,包含了所有可以传递给 workbox-webpack-plugin 的选项。

第四步:registerServiceWorker.js 文件

这个文件负责注册 Service Worker。通常情况下,你不需要修改它,因为它已经包含了基本的注册逻辑:

/* eslint-disable no-console */

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

第五步:manifest.json 文件

这个文件描述了你的 PWA 应用的元数据,比如名称、图标、启动 URL 等。它位于 public 目录下,内容类似这样:

{
  "name": "My PWA App",
  "short_name": "PWA App",
  "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": "#4DBA87",
  "background_color": "#000000"
}
  • name: 应用的完整名称。
  • short_name: 应用的简短名称,用于主屏幕上。
  • icons: 应用图标的数组,不同尺寸的图标用于不同的设备。务必提供不同尺寸的图标,否则PWA无法正常添加到主屏幕。
  • start_url: 应用启动时加载的 URL。
  • display: 指定应用的显示模式。
    • standalone: 应用会以独立的窗口运行,就像原生 App 一样。
    • fullscreen: 应用会以全屏模式运行。
    • minimal-ui: 应用会以最小化的 UI 运行,只显示浏览器的后退、前进和刷新按钮。
    • browser: 应用会在标准的浏览器窗口中运行。
  • theme_color: 应用的颜色主题。
  • background_color: 应用在启动时显示的背景颜色。

第六步:打包并测试你的PWA

运行以下命令打包你的应用:

npm run build

打包完成后,你会得到一个 dist 目录,包含了你的 PWA 应用的所有文件。

要测试你的 PWA,你需要一个 HTTP 服务器。你可以使用 serve 命令:

npm install -g serve
serve -s dist

然后在浏览器中打开 http://localhost:5000 (或者 serve 命令输出的地址)。

打开 Chrome 开发者工具,切换到 "Application" 选项卡,在 "Manifest" 面板中查看你的 manifest.json 文件是否正确加载。

在 "Service Workers" 面板中查看你的 Service Worker 是否已经注册并激活。

模拟离线状态:在 "Application" -> "Service Workers" 面板中,勾选 "Offline" 复选框。刷新页面,如果你的 PWA 应用能够正常显示,说明离线功能已经生效!

Workbox的两种模式深度解析:GenerateSW vs InjectManifest

咱们前面提到了 workboxPluginMode 这个配置项,它决定了 Workbox 的运行模式。现在咱们来深入了解一下这两种模式的区别:

1. GenerateSW 模式 (自动挡)

  • 优点:
    • 简单易用,不需要手动编写 Service Worker 代码。
    • Workbox 会自动处理所有静态资源的缓存,并生成一个完整的 Service Worker 文件。
  • 缺点:
    • 灵活性较低,无法自定义 Service Worker 的行为。
    • 对于复杂的缓存策略,可能需要编写自定义代码来扩展 Workbox 的功能。
  • 适用场景:
    • 对于简单的 PWA 应用,或者不需要复杂的缓存策略的应用,GenerateSW 模式是一个不错的选择。

如何使用 GenerateSW 模式?

  1. vue.config.js 中设置 workboxPluginMode: 'GenerateSW'

  2. workboxOptions 中配置缓存策略和其他选项。例如,你可以使用 runtimeCaching 选项来配置运行时缓存:

    module.exports = {
      pwa: {
        workboxPluginMode: 'GenerateSW',
        workboxOptions: {
          skipWaiting: true,
          clientsClaim: true,
          runtimeCaching: [
            {
              urlPattern: /^https://fonts.(googleapis|gstatic).com/,
              handler: 'CacheFirst',
              options: {
                cacheName: 'google-fonts-cache',
                expiration: {
                  maxAgeSeconds: 60 * 60 * 24 * 365, // 缓存一年
                  maxEntries: 30
                },
                cacheableResponse: {
                  statuses: [0, 200] // 缓存状态码为 0 和 200 的响应
                }
              }
            },
            {
              urlPattern: /.(png|jpg|jpeg|svg|gif)$/,
              handler: 'CacheFirst',
              options: {
                cacheName: 'images-cache',
                expiration: {
                  maxEntries: 60,
                  maxAgeSeconds: 30 * 24 * 60 * 60 // 缓存 30 天
                }
              }
            }
          ]
        }
      }
    }

    这个例子中,我们配置了两个运行时缓存策略:

    • 第一个策略缓存 Google Fonts 的字体文件,使用 CacheFirst 策略,缓存一年。
    • 第二个策略缓存图片文件,使用 CacheFirst 策略,缓存 30 天。

2. InjectManifest 模式 (手动挡)

  • 优点:
    • 灵活性高,可以完全自定义 Service Worker 的行为。
    • 可以编写复杂的缓存策略,并使用 Workbox 提供的各种 API 来实现更高级的功能。
  • 缺点:
    • 需要手动编写 Service Worker 代码,学习成本较高。
    • 需要自己处理静态资源的缓存,并确保 Service Worker 的正确运行。
  • 适用场景:
    • 对于需要高度自定义的 PWA 应用,或者需要使用 Workbox 的高级功能的应用,InjectManifest 模式是更好的选择。

如何使用 InjectManifest 模式?

  1. vue.config.js 中设置 workboxPluginMode: 'InjectManifest'

  2. 创建一个 Service Worker 文件,例如 src/service-worker.js

  3. vue.config.jsworkboxOptions 中指定 Service Worker 文件的路径:

    module.exports = {
      pwa: {
        workboxPluginMode: 'InjectManifest',
        workboxOptions: {
          swSrc: 'src/service-worker.js' // 指定 Service Worker 文件的路径
        }
      }
    }
  4. src/service-worker.js 中编写你的 Service Worker 代码。例如:

    import { clientsClaim } from 'workbox-core';
    import { precacheAndRoute } from 'workbox-precaching';
    import { registerRoute } from 'workbox-routing';
    import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
    import { ExpirationPlugin } from 'workbox-expiration';
    import { CacheableResponsePlugin } from 'workbox-cacheable-response';
    
    clientsClaim();
    
    // Precache all of the assets generated by your build process.
    // Their URLs are injected into the manifest variable below.
    // This variable must be declared and exported in your service-worker.js file.
    precacheAndRoute(self.__WB_MANIFEST);
    
    // Cache the Google Fonts stylesheets with a cache first strategy.
    registerRoute(
      /^https://fonts.googleapis.com/,
      new CacheFirst({
        cacheName: 'google-fonts-stylesheets',
        plugins: [
          new CacheableResponsePlugin({
            statuses: ['0', 200],
          }),
        ],
      })
    );
    
    // Cache the underlying font files with a cache-first strategy for 1 year.
    registerRoute(
      /^https://fonts.gstatic.com/,
      new CacheFirst({
        cacheName: 'google-fonts-webfonts',
        plugins: [
          new CacheableResponsePlugin({
            statuses: ['0', 200],
          }),
          new ExpirationPlugin({
            maxAgeSeconds: 60 * 60 * 24 * 365,
            maxEntries: 30,
          }),
        ],
      })
    );
    
    // Cache images with a cache-first strategy for 30 days.
    registerRoute(
      /.(?:png|jpg|jpeg|svg|gif)$/,
      new CacheFirst({
        cacheName: 'images',
        plugins: [
          new CacheableResponsePlugin({
            statuses: ['0', 200],
          }),
          new ExpirationPlugin({
            maxEntries: 60,
            maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
          }),
        ],
      })
    );
    
    // An example runtime caching route for dynamic content.
    registerRoute(
      ({ url }) => url.pathname.startsWith('/api/'),
      new NetworkFirst({
        cacheName: 'api-cache',
        plugins: [
          new CacheableResponsePlugin({
            statuses: [0, 200],
          }),
          new ExpirationPlugin({
            maxEntries: 50,
            maxAgeSeconds: 5 * 60, // 5 minutes
          }),
        ],
      })
    );
    
    // This allows the web app to trigger skipWaiting via
    // registration.waiting.postMessage({type: 'SKIP_WAITING'})
    self.addEventListener('message', (event) => {
      if (event.data && event.data.type === 'SKIP_WAITING') {
        self.skipWaiting();
      }
    });
    • clientsClaim(): 让 Service Worker 立即控制所有客户端。
    • precacheAndRoute(self.__WB_MANIFEST): 预缓存所有静态资源,self.__WB_MANIFEST 是 Workbox 注入的变量,包含了所有需要缓存的资源列表。
    • registerRoute(): 注册路由,用于处理不同 URL 的缓存策略。
    • CacheFirst, NetworkFirst, StaleWhileRevalidate: Workbox 提供的缓存策略。
    • ExpirationPlugin: 用于设置缓存的过期时间。
    • CacheableResponsePlugin: 用于设置哪些响应需要缓存。

Workbox常用缓存策略

Workbox 提供了多种缓存策略,可以根据不同的场景选择合适的策略:

策略 描述 适用场景
CacheFirst 首先尝试从缓存中获取资源,如果缓存中不存在,则从网络获取,并将响应缓存起来。 静态资源,例如图片、字体、CSS、JavaScript 文件。
NetworkFirst 首先尝试从网络获取资源,如果网络请求失败,则从缓存中获取。 动态内容,例如 API 请求。
StaleWhileRevalidate 首先从缓存中获取资源,同时在后台从网络获取最新的资源,并在下次请求时使用最新的资源。 对实时性要求不高,但需要快速响应的内容。
NetworkOnly 仅从网络获取资源。 必须从网络获取的资源。
CacheOnly 仅从缓存获取资源。 离线状态下需要访问的资源。

常见问题与解决方案

  1. PWA 应用无法添加到主屏幕?

    • 确保你的 manifest.json 文件正确配置,并且提供了不同尺寸的图标。
    • 确保你的网站使用了 HTTPS 协议。
    • 确保你的网站已经注册了 Service Worker。
  2. Service Worker 没有生效?

    • 检查你的 registerServiceWorker.js 文件是否正确注册了 Service Worker。
    • 检查你的 Service Worker 文件是否有语法错误。
    • 在 Chrome 开发者工具的 "Application" -> "Service Workers" 面板中查看 Service Worker 的状态。
  3. 如何更新 Service Worker?

    • 当你的网站更新时,浏览器会自动下载新的 Service Worker 文件。
    • 新的 Service Worker 会在后台安装,并在下次页面加载时激活。
    • 如果需要立即激活新的 Service Worker,可以在 Service Worker 文件中添加以下代码:

      self.addEventListener('message', (event) => {
        if (event.data && event.data.type === 'SKIP_WAITING') {
          self.skipWaiting();
        }
      });

      然后在你的应用中发送 SKIP_WAITING 消息:

      if (navigator.serviceWorker.controller) {
        navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' });
      }
  4. 如何调试 Service Worker?

    • 使用 Chrome 开发者工具的 "Application" -> "Service Workers" 面板。
    • 在 Service Worker 代码中使用 console.log() 打印日志。
    • 使用 Workbox 提供的调试工具。

总结

Vue CLI 结合 Workbox 可以让你轻松地创建 PWA 应用,让你的网站拥有更好的用户体验。选择 GenerateSW 模式可以快速上手,而 InjectManifest 模式则提供了更高的灵活性。根据你的需求选择合适的模式,并学习 Workbox 提供的各种 API,你就可以打造出功能强大的 PWA 应用。

希望今天的讲座对你有所帮助!如果有什么问题,欢迎在评论区留言,我会尽力解答。祝大家早日摆脱Bug,代码飞起!

发表回复

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