在不预缓存的情况下使用 Workbox

到目前为止,本文档提供了大量关于预缓存的文档,其中经常会涉及 generateSWinjectManifest 构建工具。虽然在 Service Worker 中包含预缓存逻辑的原因有很多,但您不必使用预缓存即可使用 Workbox。

或许您的项目只需要运行时缓存,或者您可能希望以更简洁的方式集成 Service Worker API,例如网络推送。在这些情况下,您不想使用 Workbox 的构建工具,本文将对此进行介绍。

使用捆绑器时

捆绑器在 Web 开发环境中很显眼,您的项目很可能会使用捆绑器。如果是这种情况,请务必注意,如果您不预缓存任何内容,则无需使用捆绑器插件(如 workbox-webpack-plugin)。您将把 Service Worker 视为应用中的独立入口点。

在项目源目录的根目录中,您将创建一个 Service Worker,并使用您的应用所需的任何 Workbox 模块。下面是一个未使用预缓存的示例,它改为在单独的 Cache 实例中为导航和图片素材资源请求设置了缓存策略:

// sw.js
import {NetworkFirst, CacheFirst} from 'workbox-strategies';
import {registerRoute, NavigationRoute, Route} from 'workbox-routing';

const navigationRoute = new NavigationRoute(new NetworkFirst({
  cacheName: 'navigations'
}));

const imageAssetRoute = new Route(({request}) => {
  return request.destination === 'image';
}, new CacheFirst({
  cacheName: 'image-assets'
}));

registerRoute(navigationRoute);
registerRoute(imageAssetRoute);

之后,只需在所选捆绑器中将此 Service Worker 指定为入口点。下面几个示例说明了如何在一些热门捆绑器中执行此操作。

Webpack

webpack 接受其 entry 配置中的入口点。使用此方法时,需要注意以下几点:

  1. 为确保 Service Worker 拥有尽可能广泛的范围,您需要将其输出到输出目录的根目录。
  2. 您不希望对 Service Worker 进行版本控制,因为对 Service Worker 的更新将生成新的哈希值,可能导致在您的网站上部署多个 Service Worker。

为了满足上述条件,可以向 output.filename 传递一个函数,该函数检查当前处理的入口点是否为 Service Worker 入口点。否则,版本化文件将写入其正常目标位置。

// webpack.config.js
import process from 'process';

const isProd = process.env.NODE_ENV === 'production';

export default {
  mode: isProd ? 'production' : 'development',
  context: process.cwd(),
  entry: {
    // Service worker entry point:
    sw: './src/sw.js',
    // Application entry point:
    app: './src/index.js'
  },
  output: {
    filename: ({runtime}) => {
      // Check if the current filename is for the service worker:
      if (runtime === 'sw') {
        // Output a service worker in the root of the dist directory
        // Also, ensure the output file name doesn't have a hash in it
        return '[name].js';
      }

      // Otherwise, output files as normal
      return 'js/[name].[contenthash:8].js';
    },
    path: './dist',
    publicPath: '/',
    clean: true
  }
};

汇总

Rollup 的情况与 webpack 类似,不同之处在于多个入口点被指定为以数组形式导出的单独配置对象:

// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';

// Plugins common to both entry points
const plugins = [
  nodeResolve(),
];

export default [
  // Application entry point
  {
    input: './src/index.js',
    output: {
      dir: './dist/js',
      format: 'esm'
    },
    plugins
  },
  // Service worker entry point
  {
    input: './src/sw.js',
    output: {
      file: './dist/sw.js',
      format: 'iife'
    },
    plugins: [
      ...plugins,
      // This @rollup/plugin-replace instance replaces process.env.NODE_ENV
      // statements in the Workbox libraries to match your current environment.
      // This changes whether logging is enabled ('development') or disabled ('production').
      replace({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
      })
    ]
  }
];

Esbuild

esbuild 提供了简单易用的命令行界面:

npx esbuild ./src/sw.js --bundle --minify --outfile=./dist/sw.js

默认情况下,esbuild 将 process.env.NODE_ENV 替换为“开发”或者“生产”(如果已启用缩减)。

不使用 workbox-sw 的捆绑器

您的项目甚至不能使用捆绑器。如果您使用 importScripts 导入,则 workbox-sw 可以从 Service Worker 内的 CDN 为您加载 Workbox 运行时,而无需构建步骤:

// sw.js

// Imports Workbox from the CDN. Note that "6.2.0" of the URL
// is the version of the Workbox runtime.
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-sw.js');

const navigationRoute = new workbox.routing.NavigationRoute(new workbox.strategies.NetworkFirst({
  cacheName: 'navigations'
}));

const imageAssetRoute = new workbox.routing.Route(({request}) => {
  return request.destination === 'image';
}, new workbox.strategies.CacheFirst({
  cacheName: 'image-assets'
}));

workbox.routing.registerRoute(navigationRoute);
workbox.routing.registerRoute(staticAssetRoute);

如果从 CDN 加载 Workbox 运行时的前景似乎不太理想,您可以workbox-sw 与本地网址搭配使用

总结

现在,您已经知道如何在不进行预缓存的情况下使用 Workbox,因此不必再局限于特定的打包器或构建工具。这样,您就可以灵活地使用您感兴趣的 Workbox 运行时缓存代码来编写 Service Worker。