UNPKG

universal-router

Version:

Isomorphic router for JavaScript web applications

150 lines 5.58 kB
/** * Universal Router (https://www.kriasoft.com/universal-router/) * * Copyright (c) 2015-present Kriasoft. * * This source code is licensed under the MIT license found in the * LICENSE.txt file in the root directory of this source tree. */ import { match } from './path-to-regexp.js'; function decode(val) { try { return decodeURIComponent(val); } catch { return val; } } function matchRoute(route, baseUrl, options, pathname, parentParams) { let matchResult; let childMatches; let childIndex = 0; return { next(routeToSkip) { if (route === routeToSkip) { return { done: true, value: false }; } if (!matchResult) { const rt = route; const end = !rt.children; if (!rt.match) { rt.match = match(rt.path || '', { end, ...options }); } matchResult = rt.match(pathname); if (matchResult) { const { path } = matchResult; matchResult.path = !end && path.charAt(path.length - 1) === '/' ? path.substr(1) : path; matchResult.params = { ...parentParams, ...matchResult.params }; return { done: false, value: { route, baseUrl, path: matchResult.path, params: matchResult.params, }, }; } } if (matchResult && route.children) { while (childIndex < route.children.length) { if (!childMatches) { const childRoute = route.children[childIndex]; childRoute.parent = route; childMatches = matchRoute(childRoute, baseUrl + matchResult.path, options, pathname.substr(matchResult.path.length), matchResult.params); } const childMatch = childMatches.next(routeToSkip); if (!childMatch.done) { return { done: false, value: childMatch.value }; } childMatches = null; childIndex++; } } return { done: true, value: false }; }, }; } function resolveRoute(context, params) { if (typeof context.route.action === 'function') { return context.route.action(context, params); } return undefined; } function isChildRoute(parentRoute, childRoute) { let route = childRoute; while (route) { route = route.parent; if (route === parentRoute) { return true; } } return false; } class UniversalRouter { constructor(routes, options) { if (!routes || typeof routes !== 'object') { throw new TypeError('Invalid routes'); } this.options = { decode, ...options }; this.baseUrl = this.options.baseUrl || ''; this.root = Array.isArray(routes) ? { path: '', children: routes, parent: null } : routes; this.root.parent = null; } /** * Traverses the list of routes in the order they are defined until it finds * the first route that matches provided URL path string and whose action function * returns anything other than `null` or `undefined`. */ resolve(pathnameOrContext) { const context = { router: this, ...this.options.context, ...(typeof pathnameOrContext === 'string' ? { pathname: pathnameOrContext } : pathnameOrContext), }; const matchResult = matchRoute(this.root, this.baseUrl, this.options, context.pathname.substr(this.baseUrl.length)); const resolve = this.options.resolveRoute || resolveRoute; let matches; let nextMatches; let currentContext = context; function next(resume, parent = !matches.done && matches.value.route, prevResult) { const routeToSkip = prevResult === null && !matches.done && matches.value.route; matches = nextMatches || matchResult.next(routeToSkip); nextMatches = null; if (!resume) { if (matches.done || !isChildRoute(parent, matches.value.route)) { nextMatches = matches; return Promise.resolve(null); } } if (matches.done) { const error = new Error('Route not found'); error.status = 404; return Promise.reject(error); } currentContext = { ...context, ...matches.value }; return Promise.resolve(resolve(currentContext, matches.value.params)).then((result) => { if (result !== null && result !== undefined) { return result; } return next(resume, parent, result); }); } context['next'] = next; return Promise.resolve() .then(() => next(true, this.root)) .catch((error) => { if (this.options.errorHandler) { return this.options.errorHandler(error, currentContext); } throw error; }); } } export default UniversalRouter; //# sourceMappingURL=universal-router.js.map