工作框窗口

workbox-window 软件包是一组模块,旨在 window 上下文, 比如在您的网页内部它们是对另一个工作模式的补充 在 Service Worker 中运行的软件包。

workbox-window的主要功能/目标包括:

导入和使用 Workbox-window

workbox-window 软件包的主要入口点是 Workbox 类, 您可以从我们的 CDN 或使用任何热门 JavaScript 捆绑工具。

使用我们的 CDN

若要在您的网站上导入 Workbox 类,最简单的方法是从我们的 CDN 中导入:

<script type="module">
  import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';

  if ('serviceWorker' in navigator) {
    const wb = new Workbox('/sw.js');

    wb.register();
  }
</script>

请注意,此示例使用 <script type="module">import 语句来 加载 Workbox 类。虽然您可能认为需要 让它在旧版浏览器中正常运行,这实际上是不必要的。

支持 Service Worker 的所有主流浏览器也都支持原生 JavaScript 模块,因此 可以将此代码提供给所有浏览器(旧版浏览器会直接忽略它)。

使用 JavaScript 打包器加载 Workbox

虽然使用 workbox-window 完全不需要任何工具,但如果您的 已经包括一个打包器, WebpackRollupnpm 依赖项搭配使用时,可以使用它们 加载 workbox-window

第一步是 安装 workbox-window 作为应用的依赖项:

npm install workbox-window

然后,在您应用的某个 JavaScript 文件中,通过 import 引用 workbox-window 软件包名称:

import {Workbox} from 'workbox-window';

if ('serviceWorker' in navigator) {
  const wb = new Workbox('/sw.js');

  wb.register();
}

如果您的捆绑器支持通过动态 import 语句进行代码拆分, 您还可以有条件地加载 workbox-window,这有助于减少 页面主软件包的大小。

虽然“workbox-window”非常小,但没有理由 需要使用网站的核心应用逻辑进行加载 都是一个渐进增强的过程。

if ('serviceWorker' in navigator) {
  const {Workbox} = await import('workbox-window');

  const wb = new Workbox('/sw.js');
  wb.register();
}

高级捆绑概念

与在 Service Worker 中运行的 Workbox 软件包不同,构建文件 由 workbox-windowmainmodule 字段,位于 package.json 已转译为 ES5。这使得它们与现今的 构建工具,其中一些工具不允许开发者转译 其 node_module 依赖项。

如果您的构建系统允许转译依赖项(或者 无需转译任何代码),那么最好导入特定的 而不是软件包本身

以下是导入 Workbox 的各种方法,以及 每个命令将返回的内容:

// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');

// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';

// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';

示例

导入 Workbox 类后,您可以使用它来注册和 与 Service Worker 交互下面列举了一些示例来说明 Workbox

注册 Service Worker,并在 Service Worker 首次处于活动状态时通知用户

许多 Web 应用用户 Service Worker 来预缓存资源,以便其应用能够正常运行 在后续网页加载时离线使用在某些情况下, 让用户知道应用现已可离线使用。

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', event => {
  // `event.isUpdate` will be true if another version of the service
  // worker was controlling the page when this version was registered.
  if (!event.isUpdate) {
    console.log('Service worker activated for the first time!');

    // If your service worker is configured to precache assets, those
    // assets should all be available now.
  }
});

// Register the service worker after event listeners have been added.
wb.register();

如果 Service Worker 已安装但卡在等待激活的状态,则通知用户

当由现有 Service Worker 控制的页面注册新服务时 Service Worker,默认情况下只有在所有客户端上才会激活 Service Worker 完全卸载。

这是开发者常常混淆的地方,尤其是在 重新加载当前页面不会导致新 Service Worker 激活

为了尽量减少混淆,并让您清楚地了解这一情况, Workbox 类提供了一个 waiting 事件,您可以监听该事件:

const wb = new Workbox('/sw.js');

wb.addEventListener('waiting', event => {
  console.log(
    `A new service worker has installed, but it can't activate` +
      `until all tabs running the current version have fully unloaded.`
  );
});

// Register the service worker after event listeners have been added.
wb.register();

通知用户来自 workbox-broadcast-update 软件包的缓存更新

workbox-broadcast-update 软件包是一种通过缓存传送内容(以实现快速传送)的好方法,同时 通知用户该内容有更新(使用 stale-while-revalidate 策略)。

若要从窗口中接收这些更新,您可以监听以下事件的 message 事件: 类型 CACHE_UPDATED

const wb = new Workbox('/sw.js');

wb.addEventListener('message', event => {
  if (event.data.type === 'CACHE_UPDATED') {
    const {updatedURL} = event.data.payload;

    console.log(`A newer version of ${updatedURL} is available!`);
  }
});

// Register the service worker after event listeners have been added.
wb.register();

向 Service Worker 发送要缓存的网址列表

对于某些应用,可能需要知道哪些资源需要 但在构建时预缓存的,但某些应用提供完全不同的页面, 根据用户最先到达的网址确定

对于后一类别的应用,可能合理的做法是只缓存资源 用户所访问的特定网页所需的资源使用 workbox-routing 软件包,您可以 向路由器发送要缓存的网址列表,它会根据 路由器本身定义的规则

此示例会在每次发生 新 Service Worker。请注意,您可以发送所有网址,因为只有 与 Service Worker 中的指定路由匹配的网址将被缓存:

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', event => {
  // Get the current page URL + all resources the page loaded.
  const urlsToCache = [
    location.href,
    ...performance.getEntriesByType('resource').map(r => r.name),
  ];
  // Send that list of URLs to your router in the service worker.
  wb.messageSW({
    type: 'CACHE_URLS',
    payload: {urlsToCache},
  });
});

// Register the service worker after event listeners have been added.
wb.register();

重要的 Service Worker 生命周期时刻

Service Worker 生命周期 非常复杂,完全理解可能比较困难。之所以采用 它必须处理所有可能的极端情况, Service Worker(例如,注册多个 Service Worker、注册 在不同框架中注册不同的 Service Worker, 不同的名称等)。

但大多数实现 Service Worker 的开发者无需担心 所有这些极端情况,因为它们的用法非常简单。开发者最多 每次网页加载时只注册一个 Service Worker,并且它们不会更改 Service Worker 的名称 部署到服务器上的文件

Workbox 类针对 Service Worker 生命周期使用了这个更简单的视图 方法是将所有 Service Worker 注册分为两类: 拥有的已注册 Service Worker 和一个外部 Service Worker:

  • 已注册的 Service Worker:开始作为 Workbox 实例调用 register() 或已处于活动状态的结果 Service Worker(如果调用 register() 没有在注册上触发 updatefound 事件)。
  • 外部 Service Worker:开始安装的 Service Worker 与调用 register()Workbox 实例无关。通常情况下, 当用户在另一个标签页中打开您网站的新版本时触发。当 事件源自外部 Service Worker,那么事件的 isExternal 属性设置为 true

了解了这两种 Service Worker,下面对它们进行了详细说明, 重要的 Service Worker 生命周期时刻,以及开发者的建议 处理方法:

首次安装 Service Worker 时

在首次安装 Service Worker 时,您可能需要处理 不同于您处理日后所有更新的方式。

workbox-window 中,您可以先区分版本 安装和后续更新,方法是检查以下任一项上的 isUpdate 属性: 事件。首次安装时,isUpdate 将为 false

const wb = new Workbox('/sw.js');

wb.addEventListener('installed', event => {
  if (!event.isUpdate) {
    // First-installed code goes here...
  }
});

wb.register();
重要时刻 事件 建议采取的措施
新的 Service Worker 已安装(首次) installed

在首次安装 Service Worker 时,通常会预缓存 网站离线运行所需的所有资源。你可以考虑 告知用户他们的网站现在可以离线工作了。

此外,由于 Service Worker 是第一次安装,因此不会有 拦截的提取事件,您还可以考虑缓存 已加载的资源(但如果这些资源已加载, 资源已预缓存过)。发送 Service Worker,列出了要缓存的网址 上面的示例显示了如何 这个。

Service Worker 已开始控制页面 controlling

一旦安装了新的 Service Worker 并开始控制页面, 所有后续提取事件都将通过该 Service Worker。如果您的 Service Worker 会添加任何特殊逻辑来处理特定提取事件, 这个时候,你知道逻辑会正常运行

请注意,首次安装 Service Worker 时,它会 不会开始控制当前页面,除非该 Service Worker 调用 clients.claim()。默认 就是等到下一网页加载后才开始控制。

workbox-window 的角度来看,这意味着 controlling 事件只有在 Service Worker 调用 clients.claim()。此活动并非 如果网页在注册之前就已受控制,则分派。

Service Worker 已完成激活 activated

如上文所述,Service Worker 首次完成 启用后,它可能会(也可能不会)开始控制相应网页。

因此,您不应通过监听 Service Worker 何时控制着页面。但是,如果 您在活动事件(在 Service Worker 中)中运行逻辑, 当您需要知道该逻辑何时完成时,激活事件将会 这一点。

当找到 Service Worker 的更新版本时

当新的 Service Worker 开始安装,但当前存在一个现有版本时 控制页面时,所有后续事件的 isUpdate 属性都会 为 true

您在这种情况下的反应通常不同于第一次 因为您必须管理用户获取此更新的时间和方式。

重要时刻 事件 建议采取的措施
安装了新的 Service Worker(更新先前的 Service Worker) installed

如果这不是首次安装 Service Worker (event.isUpdate === true),表示较新版本的 Service Worker(即 Service Worker, (即当前控制该网页的一个子账号)。

这通常表示系统已将该网站的较新版本部署到 您的服务器,新资产可能刚刚完成了预缓存。

注意:有些开发者使用 installed 事件来告知 告知他们网站有新版本可用。不过,根据 无论你是调用 <ph type="x-smartling-placeholder"></ph> skipWaiting()(在安装 Service Worker 中), 已安装的 Service Worker 不一定会立即生效。如果您 致电 skipWaiting(),则最好告知用户 在新 Service Worker 激活后,如果 致电 skipWaiting,最好将这一情况告诉他们 等待事件中的待处理更新(有关详情,请参阅下文)。

Service Worker 已安装,但它在等待阶段卡住了 waiting

如果更新版本的 Service Worker 未调用 skipWaiting(),它不会 激活,直到所有页面均由当前活跃的 Service Worker 控制 已卸载。您可能需要通知用户有可用更新 并会在下次访问时应用

警告!开发者经常会提示用户 重新加载以获取更新,但在很多情况下 刷新页面不会激活已安装的 worker。如果 用户刷新页面,Service Worker 仍在等待, 系统会再次触发waiting事件 event.wasWaitingBeforeRegister 属性将为 true。请注意: 我们计划在将来的版本中改善这一体验。请关注问题 #1848 获取更新。

另一种方法是提示用户并询问他们是否希望 更新或继续等待。如果选择获取更新,您可以 使用 postMessage() 告知 Service Worker 运行 skipWaiting()。查看高级配方 <ph type="x-smartling-placeholder"></ph> 例如,向用户提供页面重新加载功能

Service Worker 已开始控制页面 controlling

当更新的 Service Worker 开始控制页面时,这意味着 当前控制的 Service Worker 版本不同于 在网页加载时控制的版本。在某些情况下 这可能没有问题,但也可能意味着 当前页面不再位于缓存中(可能也不在服务器上)。 您可能需要考虑告知用户网页的某些部分 可能无法正常运行。

注意controlling 事件不会触发 。skipWaiting()

Service Worker 已完成激活 activated 当更新的 Service Worker 完成激活时,则意味着: 您在 Service Worker 的 activate 中运行的逻辑具有 已完成。如果某些事情需要推迟到这个逻辑完成 现在是时候运行它了

发现非预期的 Service Worker 版本时

有时,用户会让您的网站在后台标签页中长时间保持打开状态 。他们甚至可能在没有意识到的情况下打开新的标签页并导航到您的网站 他们已经在后台标签页中打开了您的网站。在这种情况下 可以同时运行网站的两个版本,并且 可能会为开发者带来一些有趣的问题。

设想一个场景:标签页 A 运行网站的 v1 版本,标签页 B 运行 运行 v2。标签页 B 加载后,将由您的服务版本控制 但服务器返回的页面(如果使用 网络优先缓存策略 包含您的所有 v2 资源。

不过,这对于标签页 B 通常不是问题,因为您编写 v2 但您知道 v1 代码的工作原理。不过,也可能是 标签页 A 出现的问题,因为 v1 代码无法预测 v2 代码可能引入的更改。

为了帮助处理这些情况,workbox-window 还会分派生命周期配置, 从“外部”事件检测到更新时触发事件Service Worker,其中 “external”仅表示当前 Workbox 以外的版本 实例。

从 Workbox v6 及更高版本开始,这些事件等同于 还添加了为每个事件设置的 isExternal: true 属性 对象。如果您的 Web 应用需要实现特定逻辑来处理 “external”Service Worker,则可以在事件处理脚本中检查该属性。

避免常见错误

Workbox 提供的最实用的功能之一是开发者日志记录。且 对于 workbox-window 来说尤其如此。

我们知道,使用 Service Worker 进行开发往往令人困惑, 可能与您的预期相反,但可能很难知道原因。

例如,当您更改 Service Worker 并重新加载页面时, 您可能在浏览器中看不到这一更改。最可能的原因就是 您的 Service Worker 仍在等待激活。

但是,向 Workbox 类注册 Service Worker 时,您将 获悉开发者控制台中的所有生命周期状态变化, 帮助调试出现异常的原因

正在等待工作器的 Workbox-window 控制台警告

此外,开发者首次使用 Service Worker 时常犯的一个错误是 在 Service Worker 中注册一个 错误范围

为了防止这种情况发生,Workbox 类会在发生以下情况时发出警告: 注册 Service Worker 的页面不在该 Service Worker 的作用域内。它会 在 Service Worker 处于活动状态但尚未处于活动状态时,还会向您发出警告 控制该网页:

针对非控制工作器的 Workbox-window 控制台警告

窗口到 Service Worker 的通信

最高级 Service Worker 的使用涉及到大量的 Service Worker, Service Worker 和窗口。Workbox 类也可通过以下方式帮助完成此任务: 提供了 messageSW() 方法,该方法将postMessage() 并等待响应。

尽管您能以任何格式向 Service Worker 发送数据, 是具有三个属性的对象(后两个属性为 可选):

属性 是否必需? 类型 说明
type string

用于标识此消息的唯一字符串。

按照惯例,类型全部为大写,并以下划线分隔 字词。如果类型表示要执行的操作,则应是命令 采用现在时态(例如 CACHE_URLS),如果类型表示 应使用过去式(例如, URLS_CACHED)。

meta string 在 Workbox 中,这始终是发送 消息。自行发送消息时,您可以忽略此属性,也可以 请随意将其设置为任何值
payload * 正在发送的数据。通常,这是一个对象,但并非必须如此。

通过 messageSW() 方法发送的消息使用 MessageChannel,因此接收器 可以回复这些消息要回复消息,你可以拨打 event.ports[0].postMessage(response)。通过 messageSW() 方法会返回一个会解析为任何 response 的 promise 。

以下是从窗口向 Service Worker 发送消息的示例, 来获得响应第一个代码块是 Service Worker,第二个代码块使用 Workbox 类来发送 消息并等待响应:

sw.js 中的代码

const SW_VERSION = '1.0.0';

addEventListener('message', event => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

main.js 中的代码(在窗口中运行)

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

管理版本不兼容性

上面的示例展示了如何检查 Service Worker 版本。之所以使用以下示例,是因为当您 在窗口与 Service Worker 之间来回传递消息,这一点至关重要, 请注意,您的 Service Worker 运行的版本可能与 网页代码在哪个网站上运行,以及处理此问题的 取决于您在网页上投放的是网络优先的 即缓存优先

广告联盟优先

当首先在网络上投放您的网页时,您的用户始终会收到 从您的服务器提取最新版本的 HTML。不过,当用户首次 再次访问您的网站时(在您部署更新之后),他们获得的 HTML 将是 但其浏览器中运行的 Service Worker 将是 以前安装的版本(可能有很多旧的版本)。

请务必了解这种可能性,因为如果 会将邮件发送到您的 则该版本可能不知道如何响应(或者 格式不兼容)。

因此,最好始终对 Service Worker 进行版本控制,并检查 检查应用的兼容版本,然后再执行任何重要工作。

例如,在上面的代码中,如果该服务返回的 Service Worker 版本 messageSW() 调用版本低于预期版本,建议您耐心等待 直到发现更新(应在调用 register() 时发生)。在 然后您可以通知用户或更新 跳过等待阶段 立即激活新的 Service Worker。

优先缓存

这与以网络优先提供页面的方式不同,在以缓存形式提供页面时, 首先,您知道您的网页最初的版本始终与 您的 Service Worker(因为向其提供数据)。因此, 以便立即使用 messageSW()

但是,如果找到并激活 Service Worker 的更新版本, 当您的页面调用 register() 时(即您有意跳过等待阶段), 则无法再向该地址发送消息。

应对这种可能性的一个策略是使用 以便区分重大更新和非重大更新 如果是重大更新,您会认为 Service Worker。相反,您应该警告用户,他们运行的是旧的 版本,并建议用户重新加载以获取更新。

跳过等待助手

窗口到 Service Worker 消息传递的常见用法是发送 {type: 'SKIP_WAITING'} 消息,用于指示已安装到 跳过等待阶段 并激活。

从 Workbox v6 开始,messageSkipWaiting() 方法可用于发送 将 {type: 'SKIP_WAITING'} 消息发送给与 当前 Service Worker 注册。如果没有 等待 Service Worker。

类型

Workbox

帮助处理 Service Worker 注册、更新和 对 Service Worker 生命周期事件做出响应

属性

  • 构造函数

    void

    使用脚本网址和 Service Worker 创建新的 Workbox 实例 选项。脚本网址和选项与 调用 navigator.serviceWorker.register(script网址, options)

    constructor 函数如下所示:

    (scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}

    • scriptURL

      string |TrustedScriptURL

      Service Worker 脚本 与此实例关联的资源。使用 支持 TrustedScriptURL

    • registerOptions

      对象(可选

  • 活跃

    Promise&lt;ServiceWorker&gt;

  • 控制

    Promise&lt;ServiceWorker&gt;

  • getSW

    void

    通过引用与脚本网址匹配的 Service Worker 进行解析 并在此实例可用时立即对其进行更改

    如果在注册时已有一项有效或等待中的服务 具有匹配脚本网址的 Worker,则会使用它(等待 如果 Service Worker 与活跃 Service Worker 同时存在, 因为等待的 Service Worker 被注册的次数更多, )。 如果在注册时没有匹配的活跃或等待 Service Worker 直到找到更新并启动后,promise 才会进行解析 正在安装,此时会使用安装 Service Worker。

    getSW 函数如下所示:

    () => {...}

    • 返回

      Promise&lt;ServiceWorker&gt;

  • messageSW

    void

    将传递的数据对象发送到由此注册的 Service Worker 实例(通过 workbox-window.Workbox#getSW)解析, 并给出响应(如果有的话)。

    可通过以下方法在 Service Worker 中的消息处理程序中设置响应: 调用 event.ports[0].postMessage(...),这会解析 promise 由 messageSW() 返回。如果未设置响应,promise 绝不会 解决。

    messageSW 函数如下所示:

    (data: object) => {...}

    • 数据

      对象

      要发送到 Service Worker 的对象

    • 返回

      承诺<any>

  • messageSkipWaiting

    void

    向如下 Service Worker 发送一条 {type: 'SKIP_WAITING'} 消息: 当前处于与当前注册关联的 waiting 状态。

    如果没有当前注册或者没有 Service Worker,则其状态为 waiting, 调用此命令将不会产生任何影响。

    messageSkipWaiting 函数如下所示:

    () => {...}

  • register

    void

    为此实例脚本网址和服务注册 Service Worker worker 选项。默认情况下,此方法会将注册延迟到 该窗口已加载完毕

    register 函数如下所示:

    (options?: object) => {...}

    • 选项

      对象(可选

      • 当前位置

        布尔值(可选)

    • 返回

      Promise&lt;ServiceWorkerRegistration&gt;

  • update

    void

    检查已注册 Service Worker 的更新。

    update 函数如下所示:

    () => {...}

    • 返回

      承诺<void>

WorkboxEventMap

WorkboxLifecycleEvent

属性

  • isExternal

    布尔值(可选)

  • isUpdate

    布尔值(可选)

  • originalEvent

    活动(可选

  • sw

    ServiceWorker(可选

  • 目标

    WorkboxEventTarget 可选

  • 类型

    typeOperator

WorkboxLifecycleEventMap

WorkboxLifecycleWaitingEvent

属性

  • isExternal

    布尔值(可选)

  • isUpdate

    布尔值(可选)

  • originalEvent

    活动(可选

  • sw

    ServiceWorker(可选

  • 目标

    WorkboxEventTarget 可选

  • 类型

    typeOperator

  • wasWaitingBeforeRegister

    布尔值(可选)

WorkboxMessageEvent

属性

  • 数据

    任意

  • isExternal

    布尔值(可选)

  • originalEvent

    事件

  • ports

    typeOperator

  • sw

    ServiceWorker(可选

  • 目标

    WorkboxEventTarget 可选

  • 类型

    "消息"

方法

messageSW()

workbox-window.messageSW(
  sw: ServiceWorker,
  data: object,
)

通过 postMessage 向 Service Worker 发送一个数据对象,并使用 回复(如果有)。

可通过以下方法在 Service Worker 中的消息处理程序中设置响应: 调用 event.ports[0].postMessage(...),这会解析 promise 由 messageSW() 返回。如果没有设置响应,promise 将不会 解决。

参数

  • sw

    ServiceWorker

    接收消息的 Service Worker。

  • 数据

    对象

    要发送到 Service Worker 的对象。

返回

  • 承诺<any>