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() 语句,或包含代码(其检测是否对以前的服务工作线程注册进行了更新),以此通知用户刷新页面。但对于标准的几行代码而言 这些只是细微变化

那么,navigator.serviceWorker.register 是否有任何细微差别?是否有任何可以遵循的最佳实践?毫无意外(考虑到本文还未完结),对这两个问题的回答都是“有”。

用户的首次访问

我们以用户首次访问 Web 应用为例。目前还没有 Service Worker,而浏览器无法提前知道最终是否会安装一个 Service Worker。

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

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

空闲的后台线程不太可能有重大意义。但是,如果该线程不处于空闲状态,而是决定也将开始从网络下载资源,那该怎么办?比起对 CPU 或内存的任何顾虑,我们更应该担心许多移动设备可用的有限带宽。带宽很宝贵,因此不要同时下载次要资源,这样会破坏关键资源。

所有这些都说明,在后台启动一个新的 Service Worker 下载和缓存资源,违背了为用户首次访问网站提供最快交互体验的目标。

改进样本代码

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

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

但是启动服务工作线程注册的适当时间还取决于您的网络应用在加载后将做什么。例如,2016 年 Google I/O 大会 Web 应用会先播放一个简短的动画,然后再过渡到主屏幕。我们的团队发现,在动画期间启动 Service Worker 注册可能会导致低端移动设备出现卡顿。为了避免给用户带来糟糕的体验,我们延迟了服务工作器注册,直到动画显示完毕,此时浏览器最有可能出现几秒的空闲状态。

同样,如果您的 Web 应用使用在页面加载后执行其他设置的框架,请查找一个框架专用事件,该事件会在该工作完成时发出信号。

后续访问

到目前为止,我们一直在关注首次访问体验,但延迟 Service Worker 注册对重复访问您的网站会有什么影响吗?尽管这让一些人出乎意料,但应该不会有任何影响。

注册服务工作线程后,它将完成 installactivate 生命周期事件。在激活 Service Worker 后,它可以处理 fetch 事件以对网络应用进行任意后续访问。根据 Service Worker 的作用域,它将在请求任意页面*之前*启动,如果您仔细想一下,就会明白这非常有道理。如果在访问页面之前,现有 Service Worker 还没有运行,那么,它将没有机会针对导航请求履行 fetch 事件。

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

尽早注册的原因

是否存在最好尽快注册 Service Worker 的任何场景?我想到一个这样的场景,当 Service Worker 在首次访问期间使用 clients.claim() 控制页面时,Service Worker 积极执行其 fetch 处理程序内部的运行时缓存。在这种情况下,最好是尽快激活 Service Worker,以尝试用稍后可能用到的资源填充其运行时缓存。如果您的网络应用属于此类别,则考虑回退,以确保 Service Worker 的 install 处理程序不会请求与主页面的请求争用带宽的资源。

进行测试

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

以下示例描述了注册时间不同可能产生的差异。系统会在无痕模式下使用网络节流功能访问示例应用时截取两个屏幕截图,以模拟连接速度缓慢的情况。

提前注册的网络流量。

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

延迟注册的网络流量。

在上面的屏幕截图中,延迟 Service Worker 注册直到页面已加载。您可以看到,在从网络获取所有资源之前,预缓存请求不会启动,从而消除了带宽争用。此外,由于我们预缓存的某些项目已存在于浏览器的 HTTP 缓存中 - Size 列中带有 (from disk cache) 的项目,因此,我们可以填充服务工作线程的缓存,而无需再次访问网络。

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

总结

总而言之,确保用户获得理想的首次访问体验应是重中之重。在初始访问期间延迟服务工作线程注册直到页面已加载,可帮助确保这一点。对于重复访问,您仍能获得拥有 Service Worker 的所有优势。

为确保延迟服务工作线程的初始注册直到第一个页面已加载,一个简单的方法是使用以下代码:

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