workbox-window
软件包是一组模块,旨在
window
上下文,
比如在您的网页内部它们是对另一个工作模式的补充
在 Service Worker 中运行的软件包。
workbox-window
的主要功能/目标包括:
- 为了简化 Service Worker 的注册和更新流程,您可以 开发者可以确定 Service Worker 生命周期中最关键的时刻, 来回应这些时刻
- 有助于防止开发者犯下最常见的错误。
- 为了实现更轻松的沟通 Service Worker 中运行的代码与窗口中运行的代码之间。
导入和使用 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
完全不需要任何工具,但如果您的
已经包括一个打包器,
Webpack 或 Rollup
与 npm 依赖项搭配使用时,可以使用它们
加载 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-window
的
main
和
module
字段,位于
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 的更新版本时
当新的 Service Worker 开始安装,但当前存在一个现有版本时
控制页面时,所有后续事件的 isUpdate
属性都会
为 true
。
您在这种情况下的反应通常不同于第一次 因为您必须管理用户获取此更新的时间和方式。
发现非预期的 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 时,您将
获悉开发者控制台中的所有生命周期状态变化,
帮助调试出现异常的原因
此外,开发者首次使用 Service Worker 时常犯的一个错误是 在 Service Worker 中注册一个 错误范围。
为了防止这种情况发生,Workbox
类会在发生以下情况时发出警告:
注册 Service Worker 的页面不在该 Service Worker 的作用域内。它会
在 Service Worker 处于活动状态但尚未处于活动状态时,还会向您发出警告
控制该网页:
窗口到 Service Worker 的通信
最高级 Service Worker 的使用涉及到大量的 Service Worker,
Service Worker 和窗口。Workbox
类也可通过以下方式帮助完成此任务:
提供了 messageSW()
方法,该方法将postMessage()
并等待响应。
尽管您能以任何格式向 Service Worker 发送数据, 是具有三个属性的对象(后两个属性为 可选):
通过 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<ServiceWorker>
-
控制
Promise<ServiceWorker>
-
getSW
void
通过引用与脚本网址匹配的 Service Worker 进行解析 并在此实例可用时立即对其进行更改
如果在注册时已有一项有效或等待中的服务 具有匹配脚本网址的 Worker,则会使用它(等待 如果 Service Worker 与活跃 Service Worker 同时存在, 因为等待的 Service Worker 被注册的次数更多, )。 如果在注册时没有匹配的活跃或等待 Service Worker 直到找到更新并启动后,promise 才会进行解析 正在安装,此时会使用安装 Service Worker。
getSW
函数如下所示:() => {...}
-
返回
Promise<ServiceWorker>
-
-
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<ServiceWorkerRegistration>
-
-
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>