UNPKG

universal-router

Version:

Isomorphic router for JavaScript web applications

226 lines (188 loc) 6.14 kB
/*! Universal Router | MIT License | https://www.kriasoft.com/universal-router/ */ import pathToRegexp from 'path-to-regexp'; /** * Universal Router (https://www.kriasoft.com/universal-router/) * * Copyright © 2015-present Kriasoft, LLC. All rights reserved. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE.txt file in the root directory of this source tree. */ const { hasOwnProperty } = Object.prototype; const cache = new Map(); function decodeParam(val) { try { return decodeURIComponent(val); } catch (err) { return val; } } function matchPath(route, pathname, parentKeys, parentParams) { const end = !route.children; const cacheKey = `${route.path || ''}|${end}`; let regexp = cache.get(cacheKey); if (!regexp) { const keys = []; regexp = { keys, pattern: pathToRegexp(route.path || '', keys, { end }) }; cache.set(cacheKey, regexp); } const m = regexp.pattern.exec(pathname); if (!m) { return null; } const path = m[0]; const params = Object.assign({}, parentParams); for (let i = 1; i < m.length; i += 1) { const key = regexp.keys[i - 1]; const prop = key.name; const value = m[i]; if (value !== undefined || !hasOwnProperty.call(params, prop)) { if (key.repeat) { params[prop] = value ? value.split(key.delimiter).map(decodeParam) : []; } else { params[prop] = value ? decodeParam(value) : value; } } } return { path: !end && path.charAt(path.length - 1) === '/' ? path.substr(1) : path, keys: parentKeys.concat(regexp.keys), params }; } /** * Universal Router (https://www.kriasoft.com/universal-router/) * * Copyright © 2015-present Kriasoft, LLC. All rights reserved. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE.txt file in the root directory of this source tree. */ function matchRoute(route, baseUrl, pathname, parentKeys, parentParams) { let match; let childMatches; let childIndex = 0; return { next(routeToSkip) { if (route === routeToSkip) { return { done: true }; } if (!match) { match = matchPath(route, pathname, parentKeys, parentParams); if (match) { return { done: false, value: { route, baseUrl, path: match.path, keys: match.keys, params: match.params } }; } } if (match && route.children) { while (childIndex < route.children.length) { if (!childMatches) { const childRoute = route.children[childIndex]; childRoute.parent = route; childMatches = matchRoute(childRoute, baseUrl + match.path, pathname.substr(match.path.length), match.keys, match.params); } const childMatch = childMatches.next(routeToSkip); if (!childMatch.done) { return { done: false, value: childMatch.value }; } childMatches = null; childIndex += 1; } } return { done: true }; } }; } /** * Universal Router (https://www.kriasoft.com/universal-router/) * * Copyright © 2015-present Kriasoft, LLC. All rights reserved. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE.txt file in the root directory of this source tree. */ function resolveRoute(context, params) { if (typeof context.route.action === 'function') { return context.route.action(context, params); } return undefined; } /** * Universal Router (https://www.kriasoft.com/universal-router/) * * Copyright © 2015-present Kriasoft, LLC. All rights reserved. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE.txt file in the root directory of this source tree. */ 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 (Object(routes) !== routes) { throw new TypeError('Invalid routes'); } this.baseUrl = options.baseUrl || ''; this.resolveRoute = options.resolveRoute || resolveRoute; this.context = Object.assign({ router: this }, options.context); this.root = Array.isArray(routes) ? { path: '', children: routes, parent: null } : routes; this.root.parent = null; } resolve(pathnameOrContext) { const context = Object.assign({}, this.context, typeof pathnameOrContext === 'string' ? { pathname: pathnameOrContext } : pathnameOrContext); const match = matchRoute(this.root, this.baseUrl, context.pathname.substr(this.baseUrl.length), [], null); const resolve = this.resolveRoute; let matches = null; let nextMatches = null; function next(resume, parent = matches.value.route, prevResult) { const routeToSkip = prevResult === null && matches.value.route; matches = nextMatches || match.next(routeToSkip); nextMatches = null; if (!resume) { if (matches.done || !isChildRoute(parent, matches.value.route)) { nextMatches = matches; return Promise.resolve(null); } } if (matches.done) { return Promise.reject(Object.assign(new Error('Page not found'), { context, status: 404, statusCode: 404 })); } return Promise.resolve(resolve(Object.assign({}, context, matches.value), matches.value.params)).then(result => { if (result !== null && result !== undefined) { return result; } return next(resume, parent, result); }); } context.next = next; return next(true, this.root); } } UniversalRouter.pathToRegexp = pathToRegexp; UniversalRouter.matchPath = matchPath; UniversalRouter.matchRoute = matchRoute; UniversalRouter.resolveRoute = resolveRoute; export default UniversalRouter; //# sourceMappingURL=main.mjs.map