Workbox 目前发了一个大版本,从 v3.x 到了 v4.x,变化有挺大的,下面是在 window 环境下的模块。javascript
workbox-window
包是一组模块,用于在 window
上下文中运行,也就是说,在你的网页内部运行。 它们是 servicewoker
中运行的其余 workbox
的补充。html
workbox-window
的主要功能/目标是:java
serviceWorker
生命周期中最关键的时刻,并简化对这些时刻的响应,简化 serviceWoker
注册和更新的过程。serviceWorker
程序中运行的代码与 window
中运行的代码之间的通讯更加轻松。workbox-window
包的主要入口点是 Workbox
类,你能够从咱们的CDN或使用任何流行的 JavaScript 打包工具将其导入代码中。node
在您的网站上导入 Workbox
类的最简单方法是从咱们的 CDN:webpack
<script type="module"> import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-window.prod.mjs'; if ('serviceWorker' in navigator) { const wb = new Workbox('/sw.js'); wb.register(); } </script>
复制代码
注意,此示例使用 <script type ="module">
和 import
语句来加载 Workbox
类。 虽然您可能认为须要转换此代码以使其在旧版浏览器中运行,但实际上并非必需的。git
支持 serviceWorker
的全部主要浏览器也支持 JavaScript 模块,所以将此代码提供给任何浏览器都是完美的(旧版浏览器将忽略它)。es6
虽然使用 Workbox
绝对不须要工具,但若是您的开发基础架构已经包含了与 npm
依赖项一块儿使用的 webpack
或 Rollup
等打包工具,则可使用它们来加载 Workbox
。github
第一步就是安装 Workbox
作为你应用的依赖:web
npm install workbox-window
复制代码
而后,在您的某个应用程序的 JavaScript
文件中,经过引用 workbox-window
包名称导入 Workbox
:npm
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
复制代码
若是您的打包工具支持经过动态 import
语句进行代码拆分,你还能够有条件地加载workbox-window
,这有助于减小页面主包的大小。
尽管 Workbox
很是小(1kb gzip压缩),可是没有理由须要加载站点的核心应用程序逻辑,由于 serviceWorker
本质上是渐进式加强。
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
复制代码
与在 Service worker
中运行的 Workbox
包不一样,workbox-window
在 package.json
中的 main
和 module
字段引用的构建文件被转换为 ES5
。 这使它们与当今的构建工具兼容 - 其中一些不容许开发人员转换其 node_module
依赖项的任何内容。
若是你的构建系统容许您转换依赖项(或者若是您不须要转换任何代码),那么最好导入特定的源文件而不是包自己。
如下是你能够导入 Workbox
的各类方法,以及每一个方法将返回的内容的说明:
// 使用ES5语法导入UMD版本
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// 使用ES5语法导入模块版本
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// 使用ES2015 +语法导入模块源文件
import {Workbox} from 'workbox-window/Workbox.mjs';
复制代码
重要! 若是您直接导入源文件,则还须要配置构建过程以缩小文件,并在将其部署到生产时删除仅开发代码。 有关详细信息,请参阅使用打包(webpack / Rollup)和Workbox的指南。
导入 Workbox 类后,可使用它来注册 serviceWorker 并与之交互。 如下是您能够在应用程序中使用 Workbox 的一些示例:
注册 serviceWorker 并在 serviceWorker 第一次处于 active 状态时通知用户:
许多 Web 应用程序用户 serviceWorker 预缓存资源,以便其应用程序在后续页面加载时离线工做。在某些状况下,通知用户该应用程序如今能够离线使用是有意义的。
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', (event) => {
// 若是另外一个版本的 serviceWorker,`event.isUpdate`将为true
// 当这个版本注册时,worker 正在控制页面。
if (!event.isUpdate) {
console.log('Service worker 第一次激活!');
// 若是您的 serviceWorker 配置为预缓存资源,那么
// 资源如今都应该可用。
}
});
// 添加事件侦听器后注册 serviceWorker 。
wb.register();
复制代码
若是 serviceWorker 已安装但等待激活,则通知用户
当由现有 serviceWorker 控制的页面注册新的 serviceWorker 时,默认状况下,在初始 serviceWorker 控制的全部客户端彻底卸载以前,serviceWorker 将不会激活。
这是开发人员常见的混淆源,特别是在从新加载当前页面不会致使新 serviceWorker 程序激活的状况下。
为了帮助减小混淆并在发生这种状况时明确说明,Workbox 类提供了一个能够监听的等待事件:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', (event) => {
console.log(`已安装新的 serviceWorker,但没法激活` +
`直到运行当前版本的全部选项卡都已彻底卸载。`);
});
// 添加事件侦听器后注册 service worker 。
wb.register();
复制代码
从 workbox-broadcast-update 包通知用户缓存更新
workbox-broadcast-update 包很是棒
可以从缓存中提供内容(快速交付)的方式,同时还可以通知用户该内容的更新(使用stale-while-revalidate 策略)。
要从 window 接收这些更新,您能够侦听 CACHE_UPDATE 类型的消息事件:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', (event) => {
if (event.data.type === 'CACHE_UPDATE') {
const {updatedURL} = event.data.payload;
console.log(`${updatedURL} 的更新版本可用`);
}
});
// 添加事件侦听器后注册 service worker。
wb.register();
复制代码
向 serviceWorker 发送要缓存的URL列表
对于某些应用程序,能够知道在构建时须要预先缓存的全部资源,但某些应用程序根据用户首先登录的 URL 提供彻底不一样的页面。
对于后一类别的应用程序,仅缓存用户所访问的特定页面所需的资源多是有意义的。 使用 workbox-routing 软件包时,您能够向路由器发送一个 URL 列表进行缓存,它将根据路由器自己定义的规则缓存这些 URL。
每当激活新的 serviceWorker 时,此示例都会将页面加载的 URL 列表发送到路由器。 请注意,发送全部 URL 是能够的,由于只会缓存与 serviceWorker 中定义的路由匹配的 URL:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', (event) => {
// 获取当前页面URL +页面加载的全部资源。
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map((r) => r.name),
];
// 将该URL列表发送到 serviceWorker 的路由器。
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// 添加事件侦听器后注册 serviceWorker。
wb.register();
复制代码
注意:上述技术适用于经过默认路由器上的 workbox.routing.registerRoute() 方法定义的任何路由。 若是您要建立本身的路由器实例,则须要手动调用 addCacheListener() 。
serviceWorker 的生命周期很复杂,彻底能够理解。 它之因此如此复杂,部分缘由在于它必须处理 serviceWorker 全部可能使用的全部边缘状况(例如,注册多个 serviceWorker,在不一样的框架中注册不一样的 serviceWorker,注册具备不一样名称的 serviceWorker 等)。
可是大多数实现 serviceWorker 的开发人员不该该担忧全部这些边缘状况,由于它们的使用很是简单。 大多数开发人员每页加载只注册一个 serviceWorker,而且他们不会更改他们部署到服务器的 serviceWorker 文件的名称。
Workbox 类经过将全部 serviceWorker 注册分为两类来包含 serviceWorker 生命周期的这个更简单的视图:实例本身的注册 serviceWorker 和外部 serviceWorker:
咱们的想法是,来自 serviceWorker 的全部生命周期事件都是你的代码应该期待的事件,而来自外部 serviceWorker 的全部生命周期事件都应该被视为具备潜在危险,而且应该相应地警告用户。
考虑到这两类 serviceWorker,下面是全部重要serviceWorker 生命周期时刻的细分,以及开发人员如何处理它们的建议:
你可能但愿在 serviceWorker 第一次安装时不一样于处理全部将来更新的方式。
在 Workbox 中,你能够经过检查如下任何事件的 isUpdate 属性来区分版本首次安装和将来更新。 对于第一次安装,isUpdate 将为 false。
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', (event) => {
if (!event.isUpdate) {
// 在这里编写第一次安装须要的代码
}
});
wb.register();
复制代码
时刻 | 事件 | 建议操做 |
---|---|---|
新的 serviceWorker 已安装(第一次) | installed | serviceWorker 第一次安装时,一般会预先缓存网站离线工做所需的全部资源。 你能够考虑通知用户他们的站点如今能够离线运行。 此外,因为 serviceWorker 第一次安装它时不会截获该页面加载的获取事件,你也能够考虑缓存已加载的资源(尽管若是这些资源已经被预先缓存,则不须要这样作)。 向上面的缓存示例发送 serviceWorker 的URL列表显示了如何执行此操做。 |
serviceWorker 已经控制页面 | controlling | 安装新 serviceWorker 程序并开始控制页面后,全部后续获取事件都将经过该 serviceWorker 程序。 若是你的 serviceWorker 添加了任何特殊逻辑来处理特定的 fetch 事件,那么当你知道逻辑将运行时就是这一点。 请注意,第一次安装 serviceWorker 时,它不会开始控制当前页面,除非该 serviceWorker 在其 activate 事件中调用 clients.claim()。 默认行为是等到下一页加载开始控制。 从 workbox-window 的角度来看,这意味着仅在 serviceWorker 调用 clients.claim() 的状况下才调度 controlling 事件。 若是在注册以前已经控制了页面,则不会调度此事件。 |
serviceWorker 已经完成激活 | activated | 如上所述,serviceWorker 第一次完成激活它可能(或可能不)已经开始控制页面。 所以,你不该该将 activate 事件视为了解 serviceWorker 什么时候控制页面的方式。 可是,若是你在活动事件中(在 serviceWorker )运行逻辑,而且你须要知道该逻辑什么时候完成,则激活的事件将让你知道。 |
当新 serviceWorker 开始安装但现有版本当前正在控制该页面时,如下全部事件的 isUpdate 属性都将为 true。
在这种状况下,你的反应一般与第一次安装不一样,由于你必须管理用户什么时候以及如何得到此更新。
时刻 | 事件 | 建议操做 |
---|---|---|
已安装新 serviceWorker(更新前一个) | installed | 若是这不是第一个 serviceWorker 安装(event.isUpdate === true),则表示已找到并安装了较新版本的 serviceWorker(即,与当前控制页面的版本不一样)。 这一般意味着已将更新版本的站点部署到你的服务器,而且新资源可能刚刚完成预先缓存。 注意:某些开发人员使用已安装的事件来通知用户其新版本的站点可用。 可是,根据我是否在安装 serviceWorker 程序中调用 skipWaiting(),安装的 serviceWorker 可能会当即生效,也可能不会当即生效。 若是你确实调用 skipWaiting(),那么最好在新 serviceWorker 激活后通知用户更新,若是你没有调用 skipWaiting,最好通知他们等待事件中的挂起更新(见下文了解更多信息) 细节)。 |
serviceWorker 已安装,但它仍处于等待阶段 | waiting | 若是 serviceWorker 的更新版本在安装时未调用skipWaiting(),则在当前活动 serviceWorker 控制的全部页面都已卸载以前,它不会激活。 你可能但愿通知用户更新可用,并将在下次访问时应用。 警告! 开发人员一般会提示用户从新加载以获取更新,但在许多状况下刷新页面不会激活已安装的工做程序。 若是用户刷新页面而且serviceWorker 仍在等待,则等待事件将再次触发,而且 event.wasWaitingBeforeRegister 属性将为 true。 请注意,咱们计划在未来的版本中改进此体验。 关注问题#1848以获取更新。 另外一种选择是提示用户并询问他们是否想要得到更新或继续等待。 若是选择获取更新,则可使用 postMessage() 告诉 serviceWorker 运行 skipWaiting()。 有关示例,请参阅高级配方为用户提供页面从新加载。 |
serviceWorker 已开始控制页面 | controlling | 当更新的 serviceWorker 开始控制页面时,这意味着当前控制的 serviceWorker 的版本与加载页面时控制的版本不一样。 在某些状况下可能没问题,但也可能意味着当前页面引用的某些资源再也不位于缓存中(也可能不在服务器上)。 你可能须要考虑通知用户页面的某些部分可能没法正常工做。 注意:若是不在serviceWorker 中调用 skipWaiting(),则不会触发控制事件。 |
serviceWorker 已完成激活 | activated | 当更新的 serviceWorker 完成激活时,这意味着你在 serviceWorker 的激活中运行的任何逻辑都已完成。 若是有什么须要延迟,直到逻辑完成,这是运行它的时间。 |
有时用户会在很长一段时间内在后台标签中打开你的网站。 他们甚至可能会打开一个新标签并导航到你的网站,却没有意识到他们已经在后台标签中打开了您的网站。 在这种状况下,您的网站可能同时运行两个版本,这可能会为开发人员带来一些有趣的问题。
考虑这样一种状况,即您的网站的标签 A 正在运行 v1,标签 B 正在运行 v2。 加载选项卡 B 时,它将由 v1 附带的 serviceWorker 版本控制,但服务器返回的页面(若是使用网络优先缓存策略用于导航请求)将包含全部 v2 资源。
这对于选项卡 B 来讲一般不是问题,由于当你编写 v2 代码时,你知道你的 v1 代码是如何工做的。可是,它多是标签A的问题,由于你的 v1 代码没法预测你的 v2 代码可能会引入哪些更改。
为了帮助处理这些状况,workbox-window 还会在检测到来自“外部” serviceWorker 的更新时调度生命周期事件,其中 external 表示任何不是当前 Workbox 实例注册的版本。
时刻 | 事件 | 建议操做 |
---|---|---|
已安装外部 serviceWorker | externalinstalled | 若是已安装外部 serviceWorker,则可能意味着用户在不一样的选项卡中运行你网站的较新版本。 如何响应可能取决于已安装的服务是进入等待仍是活动阶段。 |
经过等待激活来安装外部 serviceWorker | externalwaiting | 若是外部 serviceWorker 正在等待激活,则可能意味着用户试图在另外一个选项卡中获取你网站的新版本,可是因为此选项卡仍处于打开状态,所以他们已被阻止。 若是发生这种状况,你能够考虑向用户显示通知,要求他们关闭此标签。 在极端状况下,你甚至能够考虑调用 window.reload(),若是这样作不会致使用户丢失任何已保存的状态。 |
serviceWorker 外部 serviceWorker 已激活 | externalactivated | 若是外部 serviceWorker 程序已激活,则当前页面极可能没法继续正常运行。 你可能须要考虑向用户显示他们正在运行旧版本页面的通知,而且可能会出现问题。 |
Workbox 提供的最有用的功能之一是它的开发人员日志记录。 对于 worbox-window 也是这样。
咱们知道与 serviceWorker 一块儿开发每每会让人感到困惑,当事情发生与你指望的相反时,很难知道缘由。
例如,当你对 serviceWorker 进行更改并从新加载页面时,你可能没法在浏览器中看到该更改。 最可能的缘由是,你的 serviceWorker 仍在等待激活。
可是当使用 Workbox 类注册 serviceWorker 时,你将被告知开发人员控制台中的全部生命周期状态更改,这应该有助于调试为何事情不像你指望的那样。
此外,开发人员在首次使用 serviceWorker 时常犯的错误是在错误的范围内注册 serviceWorker。
为了防止这种状况发生,Workbox类将警告您注册服务工做者的页面是否不在该服务工做者的范围内。 若是您的服务工做者处于活动状态但还没有控制该页面,它还会警告你:
大多数高级 serviceWorker 使用涉及 serviceWorker 和 window 之间的消息传递丢失。 Workbox 类经过提供 messageSW() 方法来帮助解决这个问题,该方法将postMessage() 实例的注册 serviceWorker 并等待响应。
虽然你能够以任何格式向 serviceWorker 发送数据,但全部 Workbox 包共享的格式是具备三个属性的对象(后两个是可选的):
属性 | 必须 | 类型 | 描述 |
---|---|---|---|
type | 是 | string | 标识此消息的惟一字符串。 按照惯例,类型都是大写的,下划线分隔单词。 若是类型表示要采起的动做,则它应该是如今时的命令(例如 CACHE_URLS ),若是类型表示报告的信息,则它应该是过去时(例如 URLS_CACHED )。 |
meta | 否 | string | 在 Workbox 中,这始终是发送消息的 Workbox 包的名称。 本身发送邮件时,能够省略此属性或将其设置为你喜欢的任何内容。 |
payload | 否 | * | 正在发送的数据。 一般这是一个对象,但它不必定是。 |
经过 messageSW() 方法发送的消息使用 MessageChannel,所以接收方能够响应它们。 要响应消息,你能够在消息事件侦听器中调用 event.ports[0].postMessage(response)。 messageSW() 方法返回一个 promise,该 promise 将解析为你返回的任何响应。
这是一个从 window 到 serviceWorker 发送消息并得到响应的示例。 第一个代码块是 serviceWorker 中的消息侦听器,第二个块使用 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 中的代码(运行在 window 环境):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
复制代码
上面的示例显示了如何从 window 中实现检查 serviceWorker 版本。 使用此示例是由于当你在 window 和 serviceWorker 之间来回发送消息时,请务必注意你的 serviceWorker 可能没有运行与你的页面代码运行相同的站点版本,以及 处理此问题的解决方案会有所不一样,具体取决于你是以网络优先服务仍是缓存优先服务。
网络优先
首先为你的网页提供服务时,你的用户将始终从你的服务器获取最新版本的 HTML。 可是,当用户第一次从新访问你的站点时(在部署更新以后),他们得到的 HTML 将是最新版本,但在其浏览器中运行的 serviceWorker 将是先前安装的版本(多是许多旧版本)。
理解这种可能性很是重要,由于若是当前版本的页面加载的 JavaScript 向旧版本的 serviceWorker 发送消息,则该版本可能不知道如何响应(或者它可能以不兼容的格式响应)。
所以,在进行任何关键工做以前,始终对 serviceWorker 进行版本控制并检查兼容版本是个好主意。
例如,在上面的代码中,若是该 messageSW() 调用返回的 serviceWorker 版本早于预期版本,则最好等到找到更新(这应该在调用 register() 时发生)。 此时,你能够通知用户或更新,也能够手动跳过等待阶段以当即激活新的 serviceWorker。
缓存优先
与在网络服务页面时相比,首先,当你首先提供页面缓存时,你知道你的页面最初将始终与 serviceWorker 的版本相同(由于这是服务它的缘由)。 所以,当即使用messageSW() 是安全的。
可是,若是找到 serviceWorker 的更新版本并在页面调用 register() 时激活(即你有意跳过等待阶段),则向其发送消息可能再也不安全。
管理这种可能性的一种策略是使用版本控制方案,容许你区分中断更新和非中断更新,而且在更新中断的状况下,你知道向 serviceWorker 发送消息是不安全的。 相反,你须要警告用户他们正在运行旧版本的页面,并建议他们从新加载以获取更新。
博客名称:王乐平博客
CSDN博客地址:blog.csdn.net/lecepin