Service Worker 注册

确定 Service Worker 注册时间的最佳实践。

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

一般情况下,延迟 Service Worker 注册直至初始页面完成加载可为用户(特别是网络连接较慢的移动设备用户)提供最佳体验。

通用注册样本

如果您曾阅读有关 Service Worker 的内容,您可能会看到与以下内容实质相似的样板文件:

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

上述内容有时会附带几个 console.log() 语句,或包含代码(其检测是否对以前的服务工作线程注册进行了更新),以此通知用户刷新页面。但对于标准的几行代码而言,这些只是细微变化。

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

用户的首次访问

我们考虑一下用户首次访问网络应用。此时还没有服务工作线程,浏览器无法提前知道最终是否会安装一个服务工作线程。

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

现在,想象一下,在下载页面需要渲染的 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 大会网络应用在过渡到主屏幕前先显示一个简短的动画。我们的团队发现,在显示动画期间启动服务工作线程注册会导致低端移动设备出现卡顿。为了避免给用户带来糟糕的体验,我们延迟了服务工作器注册,直到动画显示完毕,此时浏览器最有可能出现几秒的空闲状态。

同样,如果您的网络应用使用在页面加载后执行额外设置的框架,则查看一个框架特定的事件,其表明该工作何时完成。

后续访问

到目前为止,我们一直在关注首次访问体验,但延迟 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 无痕式窗口中打开您的网页应用,并查看 Chrome 的 DevTools 中的网络流量。作为 Web 开发者,您每天可能会多次重新加载您的 Web 应用的某个本地实例。但是,如果在已有服务工作线程且已完全填充缓存的情况下重新访问您的网站,那么,您不会获得与新用户相同的体验,从而容易忽略潜在的问题。

以下示例描述了注册时间不同可能产生的差异。在隐身模式下使用网络节流访问一个示例应用时截取了两个屏幕截图,以模拟缓慢的网速。

尽早注册时的网络流量。

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

延迟注册时的网络流量。

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

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

总结

总结一下,确保用户具有最佳的首次访问体验应成为重中之重。在初始访问期间延迟服务工作线程注册直到页面已加载,可帮助确保这一点。您仍可获得具有服务工作线程进行重复访问的所有优势。

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

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