1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
6 |
|
7 | var pathToRegexp = _interopDefault(require('path-to-regexp'));
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | const cache = new Map();
|
19 |
|
20 | function decodeParam(val) {
|
21 | try {
|
22 | return decodeURIComponent(val);
|
23 | } catch (err) {
|
24 | return val;
|
25 | }
|
26 | }
|
27 |
|
28 | function matchPath(routePath, urlPath, end, parentParams) {
|
29 | const key = `${routePath}|${end}`;
|
30 | let regexp = cache.get(key);
|
31 |
|
32 | if (!regexp) {
|
33 | const keys = [];
|
34 | regexp = { pattern: pathToRegexp(routePath, keys, { end }), keys };
|
35 | cache.set(key, regexp);
|
36 | }
|
37 |
|
38 | const m = regexp.pattern.exec(urlPath);
|
39 | if (!m) {
|
40 | return null;
|
41 | }
|
42 |
|
43 | const path = m[0];
|
44 | const params = Object.create(null);
|
45 |
|
46 | if (parentParams) {
|
47 | Object.assign(params, parentParams);
|
48 | }
|
49 |
|
50 | for (let i = 1; i < m.length; i += 1) {
|
51 | params[regexp.keys[i - 1].name] = m[i] && decodeParam(m[i]);
|
52 | }
|
53 |
|
54 | return { path: path === '' ? '/' : path, keys: regexp.keys.slice(), params };
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function matchRoute(route, baseUrl, path, parentParams) {
|
67 | let match;
|
68 | let childMatches;
|
69 | let childIndex = 0;
|
70 |
|
71 | return {
|
72 | next() {
|
73 | if (!match) {
|
74 | match = matchPath(route.path, path, !route.children, parentParams);
|
75 |
|
76 | if (match) {
|
77 | return {
|
78 | done: false,
|
79 | value: {
|
80 | route,
|
81 | baseUrl,
|
82 | path: match.path,
|
83 | keys: match.keys,
|
84 | params: match.params
|
85 | }
|
86 | };
|
87 | }
|
88 | }
|
89 |
|
90 | if (match && route.children) {
|
91 | while (childIndex < route.children.length) {
|
92 | if (!childMatches) {
|
93 | const newPath = path.substr(match.path.length);
|
94 | const childRoute = route.children[childIndex];
|
95 | childRoute.parent = route;
|
96 |
|
97 | childMatches = matchRoute(childRoute, baseUrl + (match.path === '/' ? '' : match.path), newPath.charAt(0) === '/' ? newPath : `/${newPath}`, match.params);
|
98 | }
|
99 |
|
100 | const childMatch = childMatches.next();
|
101 | if (!childMatch.done) {
|
102 | return {
|
103 | done: false,
|
104 | value: childMatch.value
|
105 | };
|
106 | }
|
107 |
|
108 | childMatches = null;
|
109 | childIndex += 1;
|
110 | }
|
111 | }
|
112 |
|
113 | return { done: true };
|
114 | }
|
115 | };
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | function resolveRoute(context, params) {
|
128 | if (typeof context.route.action === 'function') {
|
129 | return context.route.action(context, params);
|
130 | }
|
131 |
|
132 | return null;
|
133 | }
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | function isChildRoute(parentRoute, childRoute) {
|
145 | let route = childRoute;
|
146 | while (route) {
|
147 | route = route.parent;
|
148 | if (route === parentRoute) {
|
149 | return true;
|
150 | }
|
151 | }
|
152 | return false;
|
153 | }
|
154 |
|
155 | class Router {
|
156 | constructor(routes, options = {}) {
|
157 | if (Object(routes) !== routes) {
|
158 | throw new TypeError('Invalid routes');
|
159 | }
|
160 |
|
161 | this.baseUrl = options.baseUrl || '';
|
162 | this.resolveRoute = options.resolveRoute || resolveRoute;
|
163 | this.context = Object.assign({ router: this }, options.context);
|
164 | this.root = Array.isArray(routes) ? { path: '/', children: routes, parent: null } : routes;
|
165 | this.root.parent = null;
|
166 | }
|
167 |
|
168 | resolve(pathOrContext) {
|
169 | const context = Object.assign({}, this.context, typeof pathOrContext === 'string' ? { path: pathOrContext } : pathOrContext);
|
170 | const match = matchRoute(this.root, this.baseUrl, context.path.substr(this.baseUrl.length));
|
171 | const resolve = this.resolveRoute;
|
172 | let matches = null;
|
173 | let nextMatches = null;
|
174 |
|
175 | function next(resume, parent = matches.value.route) {
|
176 | matches = nextMatches || match.next();
|
177 | nextMatches = null;
|
178 |
|
179 | if (!resume) {
|
180 | if (matches.done || !isChildRoute(parent, matches.value.route)) {
|
181 | nextMatches = matches;
|
182 | return Promise.resolve(null);
|
183 | }
|
184 | }
|
185 |
|
186 | if (matches.done) {
|
187 | return Promise.reject(Object.assign(new Error('Page not found'), { context, status: 404, statusCode: 404 }));
|
188 | }
|
189 |
|
190 | return Promise.resolve(resolve(Object.assign({}, context, matches.value), matches.value.params)).then(result => {
|
191 | if (result !== null && result !== undefined) {
|
192 | return result;
|
193 | }
|
194 |
|
195 | return next(resume, parent);
|
196 | });
|
197 | }
|
198 |
|
199 | context.url = context.path;
|
200 | context.next = next;
|
201 |
|
202 | return next(true, this.root);
|
203 | }
|
204 | }
|
205 |
|
206 | Router.pathToRegexp = pathToRegexp;
|
207 | Router.matchPath = matchPath;
|
208 | Router.matchRoute = matchRoute;
|
209 | Router.resolveRoute = resolveRoute;
|
210 |
|
211 | module.exports = Router;
|
212 |
|