Service Worker 注册

设置 Service Worker 注册时间的最佳做法。

Service Worker 可以有效加快对用户 Web 应用的重复访问速度,但您应采取措施,确保 Service Worker 的初始安装不会降低用户的首次访问体验。

通常,将 Service Worker 注册延迟到初始页面加载完毕后可为用户(尤其是网络连接速度较慢的移动设备用户)提供最佳体验。

通用注册样板

如果您了解 Service Worker,那么您可能会遇到与以下内容实质类似的样板:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js');
}

这有时可能附带几个 console.log() 语句,或者附带若干代码,用于检测对先前 Service Worker 注册的更新,以此来通知用户刷新页面。但对于标准的几行代码而言 这些只是细微的变化

那么,navigator.serviceWorker.register 有什么细微差别吗?是否有任何可以遵循的最佳做法?毫无疑问(考虑到本文不会就此结束),答案都是“是”!

用户的首次访问

我们考虑一下用户首次访问 Web 应用的情况。目前还没有任何 Service Worker,浏览器无法提前知道最终是否会安装一个 Service Worker。

作为开发者,您的首要任务应该是确保浏览器快速获取显示交互式页面所需的最少量关键资源。拖慢检索这些响应速度的任何因素都是无法实现快速交互体验的。

现在,假设在下载网页需要呈现的 JavaScript 或图片的过程中,浏览器决定启动后台线程或进程(为简单起见,我们假定启动一个线程)。假设您使用的不是较大的台式机,而是性能不足的手机,世界上很多人都将手机视为主要设备。启动这个额外的线程会增加对浏览器在呈现交互式网页时可能花费的 CPU 时间和内存的争用。

空闲的后台线程不太可能产生显著的影响。但是,如果该线程并未处于空闲状态,而是又决定自己也将开始从网络下载资源,该怎么办?任何有关 CPU 或内存争用的担忧都应该显得不那么谨慎,因为许多移动设备可用的带宽有限。带宽非常宝贵,因此,不要同时下载次要资源,以免影响到关键资源。

因此,启动新的 Service Worker 线程在后台下载和缓存资源可能有助于您实现在用户首次访问您的网站时提供最短的交互体验的目标。

改进样板文件

解决方案是通过选择调用 navigator.serviceWorker.register() 的时间来控制 Service Worker 的启动。一个简单的经验法则是将注册延迟到 window 上触发 load event 之后,如下所示:

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
    navigator.serviceWorker.register('/service-worker.js');
    });
}

但是,启动 Service Worker 注册的适当时间还取决于您的 Web 应用在加载后将执行的操作。例如,2016 年 Google I/O 大会 Web 应用会在过渡到主屏幕前显示一小段动画。我们的团队发现,在动画期间启动 Service Worker 注册可能会导致低端移动设备出现卡顿。我们将 Service Worker 注册推迟到动画播放之后,而不是给用户带来糟糕的体验,因为此时浏览器最有可能出现几秒钟的空闲时间。

同样,如果您的 Web 应用使用的框架在页面加载后执行其他设置,请查找用于指示该工作何时完成的特定于框架的事件。

后续访问

到目前为止,我们一直关注首次访问体验,但延迟的 Service Worker 注册对网站的重复访问有什么影响?虽然这可能会让一些人感到惊讶,但应该不会受到任何影响。

注册 Service Worker 后,会经历 installactivate 生命周期事件。激活 Service Worker 后,可处理对 Web 应用的任何后续访问的 fetch 事件。Service Worker 会在请求其作用域内的任何页面之前启动,当您考虑这一点时,会很有意义。如果现有 Service Worker 在访问页面之前尚未运行,那么它就没有机会针对导航请求完成 fetch 事件。

因此,如果有活跃的 Service Worker,那么何时调用 navigator.serviceWorker.register(),或者实际上是否调用它都无关紧要。除非您更改 Service Worker 脚本的网址,否则在后续访问期间,navigator.serviceWorker.register() 实际上是一个空操作。但何时调用无关紧要

尽早报名的原因

是否存在最好尽早注册 Service Worker 的任何场景?您可以想到的一点是,您的 Service Worker 在首次访问期间使用 clients.claim() 控制页面,并且积极地在其 fetch 处理程序内部执行运行时缓存。在这种情况下,尽快让 Service Worker 处于活跃状态的优势在于,可以尝试使用稍后可能会用到的资源填充其运行时缓存。如果您的 Web 应用属于此类别,则值得退一步确保 Service Worker 的 install 处理程序不会请求与主页面的请求争用带宽的资源。

进行测试

模拟首次访问的一个好方法是在 Chrome 无痕式窗口中打开您的 Web 应用,然后在 Chrome 的开发者工具中查看网络流量。作为 Web 开发者,您每天可能会多次重新加载 Web 应用的本地实例。但是,如果在已有 Service Worker 且已完全填充缓存的情况下重新访问您的网站,您不会获得与新用户相同的体验,并且很容易忽略潜在的问题。

以下示例说明了注册时间可能产生的差异。这两张屏幕截图是在无痕模式下使用网络节流访问示例应用时截取的,以模拟连接速度缓慢的情况。

提前注册的网络流量。

上面的屏幕截图反映了修改示例以尽快执行 Service Worker 注册时的网络流量。您可以看到预缓存请求(旁边带有齿轮图标的条目,源自 Service Worker 的 install 处理程序)散布于对显示页面所需的其他资源的请求。

延迟注册的网络流量。

在上面的屏幕截图中,延迟了 Service Worker 注册直到页面加载完成。您可以看到,预缓存请求在从网络中提取所有资源后才会启动,因此不会出现任何带宽争用情况。此外,由于我们预缓存的某些项已在浏览器的 HTTP 缓存中(即“Size”列中带有 (from disk cache) 的项),因此我们无需再次访问网络即可填充 Service Worker 的缓存。

如果您在真实的移动网络上从真实的低端设备上运行此类测试,那就更好了。您可以利用 Chrome 的远程调试功能,通过 USB 将 Android 手机连接到台式机,并确保您运行的测试切实反映了许多用户的真实体验。

总结

总而言之,确保用户拥有最佳的首次访问体验应是头等要务。在初次访问期间将 Service Worker 注册延迟到网页加载完毕后,有助于确保这一点。您仍然可以获得使用 Service Worker 进行重复访问的所有好处。

为了确保将 Service Worker 的初始注册延迟到第一个页面加载完毕之后,一种简单的方法是使用以下代码:

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
    navigator.serviceWorker.register('/service-worker.js');
    });
}