Service Worker 可以拦截页面的网络请求。它可能会使用缓存内容、来自网络的内容或 Service Worker 中生成的内容响应浏览器。
workbox-routing
是一个模块,可让您轻松地将这些请求“路由”到提供响应的不同函数。
如何执行路由
当网络请求导致 Service Worker 提取事件时,workbox-routing
将尝试使用提供的路由和处理程序响应该请求。
以下是需要注意的几点重要事项:
请求的方法非常重要。默认情况下,系统会为
GET
请求注册路由。如果您希望拦截其他类型的请求,则需要指定该方法。路由注册的顺序很重要。如果注册了多个可以处理请求的路由,系统将使用最先注册的路由来响应该请求。
您可以通过以下几种方式注册路由:使用回调、正则表达式或路由实例。
路由中的匹配和处理
工作框中的“路线”无非有两个函数:用于确定路线是否与请求匹配的“匹配”函数,以及一个应处理请求并返回响应的“处理”函数。
Workbox 附带一些帮助程序,可为您执行匹配和处理,但如果您发现自己需要不同的行为,编写自定义匹配和处理程序函数是最佳选择。
系统会向匹配回调函数传递 ExtendableEvent
、Request
和 URL
对象,您可以通过返回真实值来进行匹配。举一个简单的例子,您可以匹配特定网址,如下所示:
const matchCb = ({url, request, event}) => {
return url.pathname === '/special/url';
};
通过检查 / 测试 url
或 request
,可以涵盖大多数用例。
处理程序回调函数将获得相同的 ExtendableEvent
、Request
和 URL
对象,以及 params
值,该值是“match”函数返回的值。
const handlerCb = async ({url, request, event, params}) => {
const response = await fetch(request);
const responseBody = await response.text();
return new Response(`${responseBody} <!-- Look Ma. Added Content. -->`, {
headers: response.headers,
});
};
您的处理程序必须返回一个解析为 Response
的 promise。在此示例中,我们使用的是 async
和 await
。在后台,返回的 Response
值将封装在 promise 中。
您可以按如下方式注册这些回调:
import {registerRoute} from 'workbox-routing';
registerRoute(matchCb, handlerCb);
唯一的限制是“match”回调必须同步返回真实值,因此您无法执行任何异步工作。这是因为 Router
必须同步响应提取事件,或允许跳转到其他提取事件。
通常,“handler”回调会使用 workbox-strategies 提供的策略之一,如下所示:
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
registerRoute(matchCb, new StaleWhileRevalidate());
在本页中,我们将重点介绍 workbox-routing
,但您可以详细了解有关 Workbox 策略的这些策略。
如何注册正则表达式路由
常见做法是使用正则表达式,而不是“match”回调。通过 Workbox 可轻松地实现此功能,如下所示:
import {registerRoute} from 'workbox-routing';
registerRoute(new RegExp('/styles/.*\\.css'), handlerCb);
对于来自同一来源的请求,只要请求的网址与正则表达式匹配,此正则表达式就会匹配。
- https://example.com/styles/main.css
- https://example.com/styles/nested/file.css
- https://example.com/nested/styles/directory.css
但是,对于跨域请求,正则表达式必须与网址的开头匹配。原因在于,您不太可能使用正则表达式 new RegExp('/styles/.*\\.css')
来匹配第三方 CSS 文件。
- https://cdn.third-party-site.com/styles/main.css
- https://cdn.third-party-site.com/styles/nested/file.css
- https://cdn.third-party-site.com/nested/styles/directory.css
如果您希望实现此行为,只需确保正则表达式与网址的开头部分匹配即可。如果要匹配 https://cdn.third-party-site.com
的请求,可以使用正则表达式 new RegExp('https://cdn\\.third-party-site\\.com.*/styles/.*\\.css')
。
- https://cdn.third-party-site.com/styles/main.css
- https://cdn.third-party-site.com/styles/nested/file.css
- https://cdn.third-party-site.com/nested/styles/directory.css
如果您想同时匹配本地和第三方,可以在正则表达式的开头使用通配符,但应谨慎执行此操作,以确保不会导致 Web 应用出现意外行为。
如何注册导航路线
如果您的网站是单页应用,您可以使用 NavigationRoute
为所有导航请求返回特定响应。
import {createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';
// This assumes /app-shell.html has been precached.
const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler);
registerRoute(navigationRoute);
每当用户通过浏览器访问您的网站时,对该网页的请求都将是导航请求,并且系统将提供缓存的网页 /app-shell.html
。(注意:您应该通过 workbox-precaching
或您自己的安装步骤缓存该页面。)
默认情况下,这将响应所有导航请求。如果您想限制其响应部分网址,可以使用 allowlist
和 denylist
选项来限制哪些页面将与此路由匹配。
import {createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';
// This assumes /app-shell.html has been precached.
const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler, {
allowlist: [new RegExp('/blog/')],
denylist: [new RegExp('/blog/restricted/')],
});
registerRoute(navigationRoute);
唯一需要注意的是,如果网址同时存在于 allowlist
和 denylist
中,则 denylist
胜出。
设置默认处理程序
如果要为与路由不匹配的请求提供“处理程序”,可以设置默认处理程序。
import {setDefaultHandler} from 'workbox-routing';
setDefaultHandler(({url, event, params}) => {
// ...
});
设置捕获处理程序
如果任何路由抛出错误,您可以通过设置 catch 处理程序进行捕获和降级。
import {setCatchHandler} from 'workbox-routing';
setCatchHandler(({url, event, params}) => {
...
});
为非 GET 请求定义路由
默认情况下,假定所有路由都适用于 GET
请求。
如果您想路由其他请求(例如 POST
请求),则需要在注册路线时定义该方法,如下所示:
import {registerRoute} from 'workbox-routing';
registerRoute(matchCb, handlerCb, 'POST');
registerRoute(new RegExp('/api/.*\\.json'), handlerCb, 'POST');
路由器日志记录
您应该能够使用来自 workbox-routing
的日志来确定请求流程,这些日志会突出显示正在通过 Workbox 处理的网址。
如果您需要更详细的信息,可以将日志级别设置为 debug
,以查看路由器未处理的请求的日志。如需详细了解如何设置日志级别,请参阅我们的调试指南。
高级用法
如果您希望更好地控制何时向 Workbox 路由器发出请求,您可以创建自己的 Router
实例,并在您想要使用路由器响应请求时调用该实例的 handleRequest()
方法。
import {Router} from 'workbox-routing';
const router = new Router();
self.addEventListener('fetch', event => {
const {request} = event;
const responsePromise = router.handleRequest({
event,
request,
});
if (responsePromise) {
// Router found a route to handle the request.
event.respondWith(responsePromise);
} else {
// No route was found to handle the request.
}
});
直接使用 Router
时,您还需要使用 Route
类或任何扩展类来注册路线。
import {Route, RegExpRoute, NavigationRoute, Router} from 'workbox-routing';
const router = new Router();
router.registerRoute(new Route(matchCb, handlerCb));
router.registerRoute(new RegExpRoute(new RegExp(...), handlerCb));
router.registerRoute(new NavigationRoute(handlerCb));
类型
NavigationRoute
借助 NavigationRoute,您可以轻松创建与浏览器 [导航请求]https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests
匹配的 workbox-routing.Route
。
它将仅匹配 https://fetch.spec.whatwg.org/#concept-request-mode|mode
设置为 navigate
的传入请求。
您可以选择使用 denylist
和/或 allowlist
参数,将此路线仅应用于部分导航请求。
属性
-
void
如果同时提供了
denylist
和allowlist
,则denylist
优先,且请求与此路由不匹配。allowlist
和denylist
中的正则表达式会与所请求网址的串联 [pathname
]https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname
和 [search
]https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search
部分进行匹配。注意:系统可能会在导航期间针对每个目标网址评估这些正则表达式。避免使用复杂的正则表达式,否则用户在浏览您的网站时可能会遇到延迟。
constructor
函数如下所示:(handler: RouteHandler, options?: NavigationRouteMatchOptions) => {...}
-
返回 Promise 并产生 Response 的回调函数。
-
-
HTTPMethod
-
void
setCatchHandler
函数如下所示:(handler: RouteHandler) => {...}
-
一个回调函数,用于返回一个解析为响应的 Promise
-
NavigationRouteMatchOptions
属性
-
RegExp[] 可选
-
RegExp[] 可选
RegExpRoute
借助 RegExpRoute,您可以轻松创建基于正则表达式的 workbox-routing.Route
。
对于同源请求,RegExp 只需要与网址的一部分匹配。对于针对第三方服务器的请求,您必须定义一个与网址开头匹配的正则表达式。
属性
-
构造函数
void
如果正则表达式包含 [捕获组]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references
,则捕获的值将传递给workbox-routing~handlerCallback
params
参数。constructor
函数如下所示:(regExp: RegExp, handler: RouteHandler, method?: HTTPMethod) => {...}
-
regExp
RegExp
要与网址匹配的正则表达式。
-
handler
返回 Promise 并产生 Response 的回调函数。
-
method
HTTPMethod 可选
-
-
catchHandler
-
handler
-
method
HTTPMethod
-
setCatchHandler
void
setCatchHandler
函数如下所示:(handler: RouteHandler) => {...}
-
handler
一个回调函数,用于返回一个解析为响应的 Promise
-
Route
Route
由一对回调函数“match”和“handler”组成。“match”回调通过在可能的情况下返回一个非错误值来确定是否应使用路线来“处理”请求。如果存在匹配项,系统会调用“handler”回调,并且应返回解析为 Response
的 Promise。
属性
-
构造函数
void
路线类的构造函数。
constructor
函数如下所示:(match: RouteMatchCallback, handler: RouteHandler, method?: HTTPMethod) => {...}
-
一个回调函数,用于通过返回非虚假值来确定路线是否与指定的
fetch
事件匹配。 -
handler
一个回调函数,用于返回一个解析为响应的 Promise。
-
method
HTTPMethod 可选
-
返回
-
-
catchHandler
-
handler
-
method
HTTPMethod
-
setCatchHandler
void
setCatchHandler
函数如下所示:(handler: RouteHandler) => {...}
-
handler
一个回调函数,用于返回一个解析为响应的 Promise
-
Router
路由器可用于使用一个或多个 workbox-routing.Route
处理 FetchEvent
,如果存在匹配的路由,则使用 Response
进行响应。
如果没有路由与给定请求匹配,则路由器将使用“默认”处理程序(如果已定义)。
如果匹配的路由抛出错误,路由器将使用“catch”处理程序(如果已定义该处理程序妥善处理问题并使用请求进行响应)。
如果请求与多个路由匹配,则使用最早的注册路由来响应该请求。
属性
-
构造函数
void
初始化新的路由器。
constructor
函数如下所示:() => {...}
-
返回
-
-
路由
Map<HTTPMethodRoute[]>
-
addCacheListener
void
针对要从窗口中缓存的网址添加消息事件监听器。这有助于缓存在 Service Worker 开始控制页面之前加载的资源。
从窗口发送的消息数据的格式应如下所示。 其中,
urlsToCache
数组可以由网址字符串或网址字符串 +requestInit
对象的数组组成(与传递给fetch()
的数组相同)。{ type: 'CACHE_URLS', payload: { urlsToCache: [ './script1.js', './script2.js', ['./script3.js', {mode: 'no-cors'}], ], }, }
addCacheListener
函数如下所示:() => {...}
-
addFetchListener
void
添加提取事件监听器,以便在路由与事件的请求匹配时响应事件。
addFetchListener
函数如下所示:() => {...}
-
findMatchingRoute
void
根据已注册路由的列表检查请求和网址(以及可选的事件),如果存在匹配,则返回相应的路由以及匹配生成的任何参数。
findMatchingRoute
函数如下所示:(options: RouteMatchCallbackOptions) => {...}
-
返回
对象
一个包含
route
和params
属性的对象。 如果找到了匹配的路由,则会填充这些路由,否则填充undefined
。
-
-
handleRequest
void
将路由规则应用于 FetchEvent 对象,以从相应路由的处理程序获取响应。
handleRequest
函数如下所示:(options: object) => {...}
-
选项
对象
-
event
ExtendableEvent
触发请求的事件。
-
request
请求
要处理的请求。
-
-
返回
Promise<Response>
如果注册的路由可以处理请求,则返回 promise。如果没有匹配的路由且没有
defaultHandler
,则返回undefined
。
-
-
registerRoute
void
向路由器注册路由。
registerRoute
函数如下所示:(route: Route) => {...}
-
路线
要注册的路线。
-
-
setCatchHandler
void
如果路由在处理请求时抛出错误,则系统将调用此
handler
并有机会提供响应。setCatchHandler
函数如下所示:(handler: RouteHandler) => {...}
-
handler
返回 Promise 并产生 Response 的回调函数。
-
-
setDefaultHandler
void
定义没有与传入请求明确匹配的路由时调用的默认
handler
。每个 HTTP 方法(“GET”、“POST”等)均有自己的默认处理程序。
如果没有默认处理程序,不匹配的请求将向网络发出,就好像没有 Service Worker 存在一样。
setDefaultHandler
函数如下所示:(handler: RouteHandler, method?: HTTPMethod) => {...}
-
handler
返回 Promise 并产生 Response 的回调函数。
-
method
HTTPMethod 可选
-
-
unregisterRoute
void
通过路由器取消注册路由。
unregisterRoute
函数如下所示:(route: Route) => {...}
-
路线
要取消注册的路线。
-
方法
registerRoute()
workbox-routing.registerRoute(
capture: string | RegExp | RouteMatchCallback | Route,
handler?: RouteHandler,
method?: HTTPMethod,
)
使用缓存策略轻松将正则表达式、字符串或函数注册到单例路由器实例。
此方法会为您生成一个路线(如果需要),并调用 workbox-routing.Router#registerRoute
。
参数
-
截图
字符串 | 正则表达式 | RouteMatchCallback | 路线
如果捕获参数是
Route
,则其他所有参数都将被忽略。 -
handler
RouteHandler 可选
-
method
HTTPMethod 可选
返回
-
生成的
Route
。
setCatchHandler()
workbox-routing.setCatchHandler(
handler: RouteHandler,
)
如果路由在处理请求时抛出错误,则系统将调用此 handler
并有机会提供响应。
参数
-
handler
返回 Promise 并产生 Response 的回调函数。
setDefaultHandler()
workbox-routing.setDefaultHandler(
handler: RouteHandler,
)
定义没有与传入请求明确匹配的路由时调用的默认 handler
。
如果没有默认处理程序,不匹配的请求将向网络发出,就好像没有 Service Worker 存在一样。
参数
-
handler
返回 Promise 并产生 Response 的回调函数。