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

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

启动时间因设备及其功能而异,但涉及的时间可能相当长,当 CPU 速度缓慢或因环境条件而处于节流状态时,启动时间有时会长达半秒。当导航响应从 Cache 实例传送时,避开网络所能带来的性能提升可能会超过这一启动时间。对于进入网络的导航请求,引入 Service Worker 可能会产生可察觉的延迟。

进入导航预加载模式

导航预加载是一项 Service Worker 功能,它可以解决由 Service Worker 启动时间造成的延迟。如果不启用导航预加载,Service Worker 的启动及其处理的导航请求都将连续发生:

一个黄色和蓝色条形,其中两个部分显示连续的操作。第一个段的黄色显示“SW 启动”以及写着“Navigation request”的蓝色部分。

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

两个条形相互堆叠并左对齐,表示两个并发操作。黄色栏标记为“软件启动”,蓝色栏标记为“导航请求”。

虽然 Navigation 预加载对于使用 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 部署到生产环境时不会看到此日志记录,但这是验证 Navigation 预加载(以及其他操作)是否正常运行的好方法。

自定义预加载的响应

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

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

Service-Worker-Navigation-Preload: true

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

总结

直接使用导航预加载时很难正确实现,但为确保 Service Worker 不会阻止浏览器发出导航请求,这项艰巨工作是值得的。借助 Workbox,您可以大幅减少工作量,从导航预加载功能中受益。如需详细了解 workbox-navigation-preload 模块,请参阅其参考文档