UNPKG

5.61 kBJavaScriptView Raw
1/**
2 * Universal Router (https://www.kriasoft.com/universal-router/)
3 *
4 * Copyright (c) 2015-present Kriasoft.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.txt file in the root directory of this source tree.
8 */
9import { match, } from 'path-to-regexp';
10function decode(val) {
11 try {
12 return decodeURIComponent(val);
13 }
14 catch (err) {
15 return val;
16 }
17}
18function matchRoute(route, baseUrl, options, pathname, parentParams) {
19 let matchResult;
20 let childMatches;
21 let childIndex = 0;
22 return {
23 next(routeToSkip) {
24 if (route === routeToSkip) {
25 return { done: true, value: false };
26 }
27 if (!matchResult) {
28 const rt = route;
29 const end = !rt.children;
30 if (!rt.match) {
31 rt.match = match(rt.path || '', { end, ...options });
32 }
33 matchResult = rt.match(pathname);
34 if (matchResult) {
35 const { path } = matchResult;
36 matchResult.path = !end && path.charAt(path.length - 1) === '/' ? path.substr(1) : path;
37 matchResult.params = { ...parentParams, ...matchResult.params };
38 return {
39 done: false,
40 value: {
41 route,
42 baseUrl,
43 path: matchResult.path,
44 params: matchResult.params,
45 },
46 };
47 }
48 }
49 if (matchResult && route.children) {
50 while (childIndex < route.children.length) {
51 if (!childMatches) {
52 const childRoute = route.children[childIndex];
53 childRoute.parent = route;
54 childMatches = matchRoute(childRoute, baseUrl + matchResult.path, options, pathname.substr(matchResult.path.length), matchResult.params);
55 }
56 const childMatch = childMatches.next(routeToSkip);
57 if (!childMatch.done) {
58 return {
59 done: false,
60 value: childMatch.value,
61 };
62 }
63 childMatches = null;
64 childIndex++;
65 }
66 }
67 return { done: true, value: false };
68 },
69 };
70}
71function resolveRoute(context, params) {
72 if (typeof context.route.action === 'function') {
73 return context.route.action(context, params);
74 }
75 return undefined;
76}
77function isChildRoute(parentRoute, childRoute) {
78 let route = childRoute;
79 while (route) {
80 route = route.parent;
81 if (route === parentRoute) {
82 return true;
83 }
84 }
85 return false;
86}
87class UniversalRouter {
88 constructor(routes, options) {
89 if (!routes || typeof routes !== 'object') {
90 throw new TypeError('Invalid routes');
91 }
92 this.options = { decode, ...options };
93 this.baseUrl = this.options.baseUrl || '';
94 this.root = Array.isArray(routes) ? { path: '', children: routes, parent: null } : routes;
95 this.root.parent = null;
96 }
97 /**
98 * Traverses the list of routes in the order they are defined until it finds
99 * the first route that matches provided URL path string and whose action function
100 * returns anything other than `null` or `undefined`.
101 */
102 resolve(pathnameOrContext) {
103 const context = {
104 router: this,
105 ...this.options.context,
106 ...(typeof pathnameOrContext === 'string'
107 ? { pathname: pathnameOrContext }
108 : pathnameOrContext),
109 };
110 const matchResult = matchRoute(this.root, this.baseUrl, this.options, context.pathname.substr(this.baseUrl.length));
111 const resolve = this.options.resolveRoute || resolveRoute;
112 let matches;
113 let nextMatches;
114 let currentContext = context;
115 function next(resume, parent = !matches.done && matches.value.route, prevResult) {
116 const routeToSkip = prevResult === null && !matches.done && matches.value.route;
117 matches = nextMatches || matchResult.next(routeToSkip);
118 nextMatches = null;
119 if (!resume) {
120 if (matches.done || !isChildRoute(parent, matches.value.route)) {
121 nextMatches = matches;
122 return Promise.resolve(null);
123 }
124 }
125 if (matches.done) {
126 const error = new Error('Route not found');
127 error.status = 404;
128 return Promise.reject(error);
129 }
130 currentContext = { ...context, ...matches.value };
131 return Promise.resolve(resolve(currentContext, matches.value.params)).then((result) => {
132 if (result !== null && result !== undefined) {
133 return result;
134 }
135 return next(resume, parent, result);
136 });
137 }
138 context.next = next;
139 return Promise.resolve()
140 .then(() => next(true, this.root))
141 .catch((error) => {
142 if (this.options.errorHandler) {
143 return this.options.errorHandler(error, currentContext);
144 }
145 throw error;
146 });
147 }
148}
149export default UniversalRouter;
150//# sourceMappingURL=UniversalRouter.js.map
\No newline at end of file