universal-router
Version:
Isomorphic router for JavaScript web applications
226 lines (188 loc) • 6.14 kB
JavaScript
/*! 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