工作框路由

Service Worker 可以拦截页面的网络请求。它可能会使用缓存内容、来自网络的内容或 Service Worker 中生成的内容响应浏览器。

workbox-routing 是一个模块,可让您轻松地将这些请求“路由”到提供响应的不同函数。

如何执行路由

当网络请求导致 Service Worker 提取事件时,workbox-routing 将尝试使用提供的路由和处理程序响应该请求。

Workbox 路由图

以下是需要注意的几点重要事项:

  • 请求的方法非常重要。默认情况下,系统会为 GET 请求注册路由。如果您希望拦截其他类型的请求,则需要指定该方法。

  • 路由注册的顺序很重要。如果注册了多个可以处理请求的路由,系统将使用最先注册的路由来响应该请求。

您可以通过以下几种方式注册路由:使用回调、正则表达式或路由实例。

路由中的匹配和处理

工作框中的“路线”无非有两个函数:用于确定路线是否与请求匹配的“匹配”函数,以及一个应处理请求并返回响应的“处理”函数。

Workbox 附带一些帮助程序,可为您执行匹配和处理,但如果您发现自己需要不同的行为,编写自定义匹配和处理程序函数是最佳选择。

系统会向匹配回调函数传递 ExtendableEventRequestURL 对象,您可以通过返回真实值来进行匹配。举一个简单的例子,您可以匹配特定网址,如下所示:

const matchCb = ({url, request, event}) => {
  return url.pathname === '/special/url';
};

通过检查 / 测试 urlrequest,可以涵盖大多数用例。

处理程序回调函数将获得相同的 ExtendableEventRequestURL 对象,以及 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。在此示例中,我们使用的是 asyncawait。在后台,返回的 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 或您自己的安装步骤缓存该页面。)

默认情况下,这将响应所有导航请求。如果您想限制其响应部分网址,可以使用 allowlistdenylist 选项来限制哪些页面将与此路由匹配。

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);

唯一需要注意的是,如果网址同时存在于 allowlistdenylist 中,则 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

    如果同时提供了 denylistallowlist,则 denylist 优先,且请求与此路由不匹配。

    allowlistdenylist 中的正则表达式会与所请求网址的串联 [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)=> {...}

  • catchHandler
  • method

    HTTPMethod

  • setCatchHandler

    void

    setCatchHandler 函数如下所示:

    (handler: RouteHandler)=> {...}

    • handler

      一个回调函数,用于返回一个解析为响应的 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
  • method

    HTTPMethod

  • setCatchHandler

    void

    setCatchHandler 函数如下所示:

    (handler: RouteHandler)=> {...}

    • handler

      一个回调函数,用于返回一个解析为响应的 Promise

Route

Route 由一对回调函数“match”和“handler”组成。“match”回调通过在可能的情况下返回一个非错误值来确定是否应使用路线来“处理”请求。如果存在匹配项,系统会调用“handler”回调,并且应返回解析为 Response 的 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)=> {...}

    • 返回

      对象

      一个包含 routeparams 属性的对象。 如果找到了匹配的路由,则会填充这些路由,否则填充 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 可选

返回

setCatchHandler()

workbox-routing.setCatchHandler(
  handler: RouteHandler,
)

如果路由在处理请求时抛出错误,则系统将调用此 handler 并有机会提供响应。

参数

  • handler

    返回 Promise 并产生 Response 的回调函数。

setDefaultHandler()

workbox-routing.setDefaultHandler(
  handler: RouteHandler,
)

定义没有与传入请求明确匹配的路由时调用的默认 handler

如果没有默认处理程序,不匹配的请求将向网络发出,就好像没有 Service Worker 存在一样。

参数

  • handler

    返回 Promise 并产生 Response 的回调函数。