diff --git a/packages/sw-routing/src/lib/router.js b/packages/sw-routing/src/lib/router.js index e8964dcc9..52c92c28d 100644 --- a/packages/sw-routing/src/lib/router.js +++ b/packages/sw-routing/src/lib/router.js @@ -50,68 +50,19 @@ import logHelper from '../../../../lib/log-helper.js'; */ class Router { /** - * An optional default handler will have its handle method called when a - * request doesn't have a matching route. - * - * @example - * router.setDefaultHandler({ - * handler: new goog.runtimeCaching.NetworkFirst() - * }); - * - * @param {Object} input - * @param {Object} input.handler An Object with a `handle` method. - */ - setDefaultHandler({handler} = {}) { - assert.hasMethod({handler}, 'handle'); - - this.defaultHandler = handler; - } - - /** - * If a Route throws an error while handling a request, this catch handler - * will be called to return an error case. - * - * @example - * router.setCatchHandler({ - * handler: ({event, params}) => { - * return caches.match('/error-page.html'); - * } - * }); - * - * @param {Object} input - * @param {Object} input.handler An Object with a `handle` method. + * Start with an empty array of routes, and set up the fetch handler. */ - setCatchHandler({handler} = {}) { - assert.hasMethod({handler}, 'handle'); - - this.catchHandler = handler; - } - - /** - * Register routes will take an array of Routes to register with the - * router. - * - * @example - * router.registerRoutes({ - * routes: [ - * new RegExpRoute({ ... }), - * new ExpressRoute({ ... }), - * new Route({ ... }), - * ] - * }); - * - * @param {Object} input - * @param {Array.} input.routes An array of routes to register. - */ - registerRoutes({routes} = {}) { - assert.isArrayOfClass({routes}, Route); + constructor() { + // _routes will contain a mapping of HTTP method name ('GET', etc.) to an + // array of all the corresponding Route instances that are registered. + this._routes = new Map(); self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); if (!url.protocol.startsWith('http')) { logHelper.log({ that: this, - message: 'URL does not start with HTTP and so not parsing ' + + message: 'URL does not start with HTTP and so not passing ' + 'through the router.', data: { request: event.request, @@ -122,11 +73,7 @@ class Router { let responsePromise; let matchingRoute; - for (let route of (routes || [])) { - if (route.method !== event.request.method) { - continue; - } - + for (let route of (this._routes.get(event.request.method) || [])) { const matchResult = route.match({url, event}); if (matchResult) { matchingRoute = route; @@ -151,7 +98,6 @@ class Router { params = undefined; } - matchingRoute = route; responsePromise = route.handler.handle({url, event, params}); break; } @@ -169,23 +115,90 @@ class Router { if (responsePromise) { event.respondWith(responsePromise - .then((response) => { - logHelper.debug({ - that: this, - message: 'The router is managing a route with a response.', - data: { - route: matchingRoute, - request: event.request, - response: response, - }, - }); - - return response; - })); + .then((response) => { + logHelper.debug({ + that: this, + message: 'The router is managing a route with a response.', + data: { + route: matchingRoute, + request: event.request, + response: response, + }, + }); + + return response; + })); } }); } + /** + * An optional default handler will have its handle method called when a + * request doesn't have a matching route. + * + * @example + * router.setDefaultHandler({ + * handler: new goog.runtimeCaching.NetworkFirst() + * }); + * + * @param {Object} input + * @param {Object} input.handler An Object with a `handle` method. + */ + setDefaultHandler({handler} = {}) { + assert.hasMethod({handler}, 'handle'); + + this.defaultHandler = handler; + } + + /** + * If a Route throws an error while handling a request, this catch handler + * will be called to return an error case. + * + * @example + * router.setCatchHandler({ + * handler: ({event, params}) => { + * return caches.match('/error-page.html'); + * } + * }); + * + * @param {Object} input + * @param {Object} input.handler An Object with a `handle` method. + */ + setCatchHandler({handler} = {}) { + assert.hasMethod({handler}, 'handle'); + + this.catchHandler = handler; + } + + /** + * Register routes will take an array of Routes to register with the + * router. + * + * @example + * router.registerRoutes({ + * routes: [ + * new RegExpRoute({ ... }), + * new ExpressRoute({ ... }), + * new Route({ ... }), + * ] + * }); + * + * @param {Object} input + * @param {Array.} input.routes An array of routes to register. + */ + registerRoutes({routes} = {}) { + assert.isArrayOfClass({routes}, Route); + + for (let route of routes) { + if (!this._routes.has(route.method)) { + this._routes.set(route.method, []); + } + + // Give precedence to the most recent route by listing it first. + this._routes.get(route.method).unshift(route); + } + } + /** * Registers a single route with the router. *