此 Codelab 是 Google Developers 培训团队开发的“开发渐进式 Web 应用”培训课程的一部分。如果您按顺序学习这些 Codelab,将会充分发掘此课程的价值。
如需详细了解本课程,请参阅开发渐进式 Web 应用概览。
简介
本实验将引导您创建一个简单的 service worker,并说明 service worker 的生命周期。
学习内容
- 创建基本的服务工作线程脚本、安装该脚本并进行简单调试
注意事项
- JavaScript 和 HTML 基础知识
- ES2015 Promise 的概念和基本语法
- 如何启用开发者控制台
开始前您需要准备以下内容
- 可访问终端/shell 的计算机
- 连接到互联网
- 支持 Service Worker 的浏览器
- 文本编辑器
从 GitHub 下载或克隆 pwa-training-labs 代码库,并根据需要安装 LTS 版本的 Node.js。
进入 service-worker-lab/app/ 目录并启动本地开发服务器:
cd service-worker-lab/app npm install node server.js
您可以使用 Ctrl-c 随时终止服务器。
打开浏览器,然后前往 localhost:8081/。
注意:请取消注册所有 Service Worker 并清除 localhost 的所有 Service Worker 缓存,以免它们干扰实验。在 Chrome 开发者工具中,您可以通过点击应用标签页的清除存储空间部分中的清除网站数据来实现此目的。
在首选文本编辑器中打开 service-worker-lab/app/ 文件夹。您将在 app/ 文件夹中构建实验。
此文件夹包含:
below/another.html、js/another.js、js/other.js和other.html是我们用于实验 Service Worker 作用域的示例资源styles/文件夹包含本实验的层叠样式表test/文件夹包含用于测试进度的文件index.html是我们示例网站/应用的主要 HTML 网页service-worker.js是用于创建服务工作线程的 JavaScript 文件package.json和package-lock.json用于跟踪此项目中使用的节点软件包server.js是一个简单的 Express 服务器,我们使用它来托管应用
在文本编辑器中打开 service-worker.js。请注意,该文件为空。我们尚未添加任何在服务工作线程中运行的代码。
在文本编辑器中打开 index.html。
在 <script> 标记内,添加以下代码以注册 service worker:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
.then(registration => {
console.log('Service Worker is registered', registration);
})
.catch(err => {
console.error('Registration failed:', err);
});
});
}保存脚本并刷新页面。控制台应返回一条消息,指明已注册 Service Worker。在 Chrome 中,您可以打开开发者工具(在 Windows 和 Linux 上按 Control + Shift + I,在 Mac 上按 ⌘ + alt + I),点击“应用”标签页,然后点击服务工作线程选项,以检查服务工作线程是否已注册。您应该会看到类似以下所示的内容:

可选:在不受支持的浏览器中打开网站,并验证支持检查条件是否有效。
说明
上述代码将 service-worker.js 文件注册为服务工作线程。它会先检查浏览器是否支持 Service Worker。每次注册 Service Worker 时都应执行此操作,因为某些浏览器可能不支持 Service Worker。然后,该代码使用 Navigator 接口中包含的 ServiceWorkerContainer API 的 register 方法注册服务工作线程。
navigator.serviceWorker.register(...) 会返回一个 promise,该 promise 会在成功注册 service worker 后解析为 registration 对象。如果注册失败,promise 将拒绝。
Service Worker 状态的变化会触发 Service Worker 中的事件。
添加事件监听器
在文本编辑器中打开 service-worker.js。
向服务工作线程添加以下事件监听器:
self.addEventListener('install', event => {
console.log('Service worker installing...');
// Add a call to skipWaiting here
});
self.addEventListener('activate', event => {
console.log('Service worker activating...');
});保存文件。
手动取消注册 Service Worker,然后刷新网页以安装并激活更新后的 Service Worker。控制台日志应表明新服务工作线程已注册、安装和激活。
注意 :注册日志可能会与其他日志(安装和激活)的显示顺序不一致。Service Worker 与网页同时运行,因此我们无法保证日志的顺序(注册日志来自网页,而安装和激活日志来自 Service Worker)。不过,安装、激活和其他 Service Worker 事件在 Service Worker 中会按既定顺序发生,并且应始终按预期顺序显示。
说明
服务工作线程会在注册结束时发出 install 事件。在上面的代码中,消息记录在 install 事件监听器内,但在实际应用中,这里非常适合缓存静态资源。
注册 Service Worker 时,浏览器会检测该 Service Worker 是否为新 Service Worker(无论是由于它与之前安装的 Service Worker 不同,还是因为该网站没有已注册的 Service Worker)。如果 Service Worker 是新的(如本例所示),则浏览器会安装它。
当 Service Worker 接管网页时,会发出 activate 事件。上面的代码在此处记录了一条消息,但此事件通常用于更新缓存。
对于给定的作用域,一次只能有一个有效的 service worker(请参阅“探索 service worker 作用域”),因此,新安装的 service worker 在现有 service worker 不再使用之前不会被激活。因此,在新的 Service Worker 接管之前,必须关闭由 Service Worker 控制的所有网页。由于我们取消注册了现有 Service Worker,因此新的 Service Worker 立即被激活。
注意:仅仅刷新网页不足以将控制权转移到新的 Service Worker,因为系统会在卸载当前网页之前请求新网页,并且不会出现旧 Service Worker 未被使用的情况。
注意:您还可以使用某些浏览器的开发者工具手动激活新的服务工作线程,也可以使用 skipWaiting() 以编程方式激活,我们将在 3.4 节中讨论此方法。
更新 service worker
在 service-worker.js 中的任意位置添加以下注释:
// I'm a new service worker保存文件并刷新页面。查看控制台中的日志;请注意,新的 service worker 已安装但未激活。在 Chrome 中,您可以在开发者工具的应用标签页中看到等待中的 Service Worker。

关闭与服务工作线程关联的所有网页。然后,重新打开 localhost:8081/。控制台日志应表明新的 service worker 现已激活。
注意:如果您获得的结果不符合预期,请确保在开发者工具中停用 HTTP 缓存。
说明
浏览器检测到新服务工作器文件与现有服务工作器文件之间存在字节差异(由于添加了注释),因此会安装新的服务工作器。由于一次只能有一个服务工作线程处于活动状态(对于给定的作用域),因此即使安装了新的服务工作线程,它也不会在现有服务工作线程不再使用之前激活。通过关闭旧 Service Worker 控制下的所有网页,我们能够激活新的 Service Worker。
跳过等待阶段
通过跳过等待阶段,新服务工作器可以立即激活,即使存在现有服务工作器也是如此。
在 service-worker.js 中,在 install 事件监听器中添加对 skipWaiting 的调用:
self.skipWaiting();保存文件并刷新页面。请注意,即使之前的服务工作线程处于受控状态,新的服务工作线程也会立即安装并激活。
说明
skipWaiting() 方法允许 Service Worker 在完成安装后立即激活。安装事件监听器是放置 skipWaiting() 调用的常见位置,但也可以在等待阶段期间或之前在任何位置调用该函数。如需详细了解何时以及如何使用 skipWaiting(),请参阅此文档。在实验的剩余部分,我们现在可以测试新的 service worker 代码,而无需手动取消注册 service worker。
了解详情
Service Worker 可充当 Web 应用与网络之间的代理。
我们来添加一个提取监听器,以拦截来自我们网域的请求。
将以下代码添加到 service-worker.js:
self.addEventListener('fetch', event => {
console.log('Fetching:', event.request.url);
});保存脚本并刷新页面,以安装并激活更新后的服务工作线程。
检查控制台,并观察到未记录任何提取事件。刷新页面,然后再次检查控制台。这次,您应该会看到网页及其资源(例如 CSS)的提取事件。
点击指向其他网页、另一个网页和返回的链接。
您会在控制台中看到每个网页及其素材资源的提取事件。所有日志是否都有意义?
注意:如果您访问某个网页,但未停用 HTTP 缓存,则 CSS 和 JavaScript 资源可能会在本地缓存。如果发生这种情况,您将不会看到这些资源的提取事件。
说明
对于浏览器在其作用域内发出的每个 HTTP 请求,服务工作线程都会收到一个提取事件。提取事件对象包含请求。在 service worker 中监听 fetch 事件与在 DOM 中监听点击事件类似。在我们的代码中,当发生提取事件时,我们会将所请求的网址记录到控制台(实际上,我们还可以创建并返回包含任意资源的自定义响应)。
为什么在首次刷新时没有记录任何提取事件?默认情况下,网页的提取事件不会通过 Service Worker,除非网页请求本身通过了 Service Worker。这可确保网站的一致性;如果某个网页在没有 Service Worker 的情况下加载,那么其子资源也会在没有 Service Worker 的情况下加载。
了解详情
解决方案代码
如需获取有效代码的副本,请前往 04-intercepting-network-requests/ 文件夹。
Service Worker 具有作用域。服务工作线程的作用域决定了服务工作线程从哪些路径拦截请求。
查找范围
使用以下代码更新 index.html 中的注册代码:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
.then(registration => {
console.log('SW registered with scope:', registration.scope);
})
.catch(err => {
console.error('Registration failed:', err);
});
});
}刷新浏览器。请注意,控制台会显示 Service Worker 的作用域(在本例中为 http://localhost:8081/)。
说明
register() 返回的 promise 会解析为包含 service worker 作用域的注册对象。
默认范围是 service worker 文件的路径,并扩展到所有较低级别的目录。因此,应用根目录中的服务工作线程会控制应用中所有文件的请求。
移动 Service Worker
将 service-worker.js 移至 below/ 目录,并更新 index.html 中注册代码内的 Service Worker 网址。
在浏览器中取消注册当前 Service Worker 并刷新页面。
控制台会显示,服务工作线程的作用域现在为 http://localhost:8081/below/。在 Chrome 中,您还可以在开发者工具的“应用”标签页中查看 Service Worker 作用域:

返回到主页面,依次点击其他页面、另一个页面和返回。系统会记录哪些提取请求?哪些不是?
说明
Service Worker 的默认作用域是 Service Worker 文件的路径。由于 service worker 文件现在位于 below/ 中,因此其作用域为 below/。控制台现在仅记录 another.html、another.css 和 another.js 的提取事件,因为这些是服务工作线程范围内的唯一资源。
设置任意范围
将服务工作线程移回项目根目录 (app/),并更新 index.html 中注册代码的服务工作线程网址。
使用 MDN 上的参考资料,通过 register() 中的可选参数将服务工作线程的范围设置为 below/ 目录。
取消注册 Service Worker 并刷新页面。点击其他页面、另一个页面和返回。
控制台再次显示,服务工作线程的范围现在为 http://localhost:8081/below/,并且仅针对 another.html、another.css 和 another.js 记录 fetch 事件。
说明
您可以在注册时传入一个额外的参数来设置任意范围,例如:
navigator.serviceWorker.register('/service-worker.js', {
scope: '/kitten/'
});在上述示例中,Service Worker 的作用域设置为 /kitten/。Service Worker 会拦截来自 /kitten/ 和 /kitten/lower/ 中网页的请求,但不会拦截来自 /kitten 或 / 等网页的请求。
注意:您无法设置高于服务工作线程实际位置的任意范围。不过,如果您的服务器 worker 在使用 Service-Worker-Allowed 标头提供服务的客户端上处于活动状态,您可以为该服务 worker 指定高于其位置的最大范围。
了解详情
解决方案代码
如需获取有效代码的副本,请前往 solution/ 文件夹。
现在,您已经启动并运行了一个简单的 Service Worker,并且了解了 Service Worker 生命周期。
了解详情
如需查看 PWA 培训课程中的所有 Codelab,请参阅本课程的欢迎 Codelab。