针对网络优先 HTML 的导航预加载

当 Service Worker 处理 fetch 事件时,浏览器会等待 Service Worker 提供响应。虽然网络请求的延迟是等待的主要原因,但浏览器可能还必须等待 Service Worker 启动并触发 fetch 事件回调。

启动时间因设备及其功能而异,但涉及的时间可能很长,如果 CPU 速度缓慢,或因环境条件而工作处于受限状态,则时间可能长达半秒。当您的导航响应由 Cache 实例提供时,避开网络所带来的性能提升可能会超过此启动时间。对于发送到网络的导航请求,引入 Service Worker 可能会产生可察觉的延迟。

进入导航预加载

Navigation 预加载是一项 Service Worker 功能,解决了 Service Worker 启动时间导致的延迟。如果未启用导航预加载,Service Worker 的启动和它处理的导航请求将连续发生:

一个黄色和蓝色条形,其中两个部分显示了连续的操作。第一段以黄色显示“SW 启动”,蓝色段为“导航请求”。

这并不理想,但您可以通过启用导航预加载来解决此问题,这可确保 Service Worker 启动和导航请求同时发生:

两个条形堆叠在一起并左对齐,表示两项并发操作。黄色条标记为“SW 启动”,蓝色条标记为“导航请求”。

虽然导航预加载功能可以很好地优化使用 Service Worker 的网站的性能,但并非在所有情况下都应该启用的功能。特别是,使用预缓存的 App Shell 的网站不需要预加载导航,因为缓存会为 App Shell 标记处理导航请求,而不会出现任何导航延迟。在这种情况下,预加载的响应将会白白浪费,这样不太好。

当网站无法预缓存 HTML 时,便可以使用导航预加载功能。例如,某些网站的标记响应是动态的,并且随身份验证状态等因素而变化。针对这些项的导航请求可能会使用网络优先(甚至仅限网络)策略,而这正是导航预加载功能的有利影响。

在 Workbox 中使用导航预加载

直接在并非由 Workbox 提供支持的 Service Worker 中使用导航预加载并不容易。首先,并非所有浏览器都支持此功能。其次,制作正确并非易事。您可以参阅 Jake Archibald 的这篇精彩解说,直接了解如何使用这项功能。

Workbox 简化了导航预加载的使用,因为 workbox-navigation-preload 模块的 enable 方法会执行必要的功能支持检查,并创建 activate 事件监听器来为您启用它。

从这里开始,通过利用 Workbox 使用网络优先策略处理程序处理导航请求,在支持浏览器方面实现了导航预加载的优势:

import * as navigationPreload from 'workbox-navigation-preload';
import {NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
import {registerRoute, NavigationRoute, Route} from 'workbox-routing';
import {precacheAndRoute} from 'workbox-precaching';

// Precache the manifest
precacheAndRoute(self.__WB_MANIFEST);

// Enable navigation preload
navigationPreload.enable();

// Create a new navigation route that uses the Network-first, falling back to
// cache strategy for navigation requests with its own cache. This route will be
// handled by navigation preload. The NetworkOnly strategy will work as well.
const navigationRoute = new NavigationRoute(new NetworkFirst({
  cacheName: 'navigations'
}));

// Register the navigation route
registerRoute(navigationRoute);

// Create a route for image, script, or style requests that use a
// stale-while-revalidate strategy. This route will be unaffected
// by navigation preload.
const staticAssetsRoute = new Route(({request}) => {
  return ['image', 'script', 'style'].includes(request.destination);
}, new StaleWhileRevalidate({
  cacheName: 'static-assets'
}));

// Register the route handling static assets
registerRoute(staticAssetsRoute);

启用导航预加载后,Workbox 会通过预加载响应响应使用 NetworkFirstNetworkOnly 策略的导航请求。

如何判断导航预加载功能是否正常运行?

开发 build 中,Workbox 会记录关于其用途的大量日志。如果您想要检查导航预加载在 Workbox 中是否正常运行,请在导航请求期间在支持的浏览器中打开控制台,您将看到一条日志消息,其中包含以下内容:

Chrome 开发者工具控制台中 Workbox 日志的屏幕截图。消息从上到下朗读:“路由器正在响应 /”“使用预加载的导航请求 /”和“使用 NetworkFirst 响应 /”

默认情况下,此日志记录在正式版 build 中不可见,因此您在将 Service Worker 部署到生产环境时不会看到此日志记录,但它是验证导航预加载是否正常运行的好方法(以及其他内容)。

自定义预加载的响应

使用导航预加载时,在某些情况下,需要在应用后端自定义预加载响应。从网络流式传输部分内容的 Service Worker 可能很方便。

在这类情况下,有必要知道在发送预加载请求时将 Service-Worker-Navigation-Preload 标头设置为默认值 true

Service-Worker-Navigation-Preload: true

然后,在您选择的应用后端中,您可以检查此标头并根据需要修改响应。如果标头的默认值由于任何原因存在问题,您可以在窗口上下文中进行更改。请注意,您在服务器上读取此标头的任何工作都由您决定,不在 Workbox 的处理范围内。

总结

Navigation 预加载在直接使用时很难正确使用,但为了确保 Service Worker 不会阻止浏览器发出导航请求,这项艰苦的工作是值得的。借助 Workbox,您可以大幅减少导航预加载工作的成效。如需详细了解 workbox-navigation-preload 模块,请查看其参考文档