1 |
|
2 |
|
3 | (function (global, factory) {
|
4 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
5 | typeof define === 'function' && define.amd ? define(factory) :
|
6 | (global.UniversalRouter = factory());
|
7 | }(this, (function () { 'use strict';
|
8 |
|
9 | var index$1 = Array.isArray || function (arr) {
|
10 | return Object.prototype.toString.call(arr) == '[object Array]';
|
11 | };
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | var index = pathToRegexp;
|
17 | var parse_1 = parse;
|
18 | var compile_1 = compile;
|
19 | var tokensToFunction_1 = tokensToFunction;
|
20 | var tokensToRegExp_1 = tokensToRegExp;
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | var PATH_REGEXP = new RegExp([
|
28 |
|
29 |
|
30 | '(\\\\.)',
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
|
38 | ].join('|'), 'g');
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | function parse (str, options) {
|
48 | var tokens = [];
|
49 | var key = 0;
|
50 | var index = 0;
|
51 | var path = '';
|
52 | var defaultDelimiter = options && options.delimiter || '/';
|
53 | var res;
|
54 |
|
55 | while ((res = PATH_REGEXP.exec(str)) != null) {
|
56 | var m = res[0];
|
57 | var escaped = res[1];
|
58 | var offset = res.index;
|
59 | path += str.slice(index, offset);
|
60 | index = offset + m.length;
|
61 |
|
62 |
|
63 | if (escaped) {
|
64 | path += escaped[1];
|
65 | continue
|
66 | }
|
67 |
|
68 | var next = str[index];
|
69 | var prefix = res[2];
|
70 | var name = res[3];
|
71 | var capture = res[4];
|
72 | var group = res[5];
|
73 | var modifier = res[6];
|
74 | var asterisk = res[7];
|
75 |
|
76 |
|
77 | if (path) {
|
78 | tokens.push(path);
|
79 | path = '';
|
80 | }
|
81 |
|
82 | var partial = prefix != null && next != null && next !== prefix;
|
83 | var repeat = modifier === '+' || modifier === '*';
|
84 | var optional = modifier === '?' || modifier === '*';
|
85 | var delimiter = res[2] || defaultDelimiter;
|
86 | var pattern = capture || group;
|
87 |
|
88 | tokens.push({
|
89 | name: name || key++,
|
90 | prefix: prefix || '',
|
91 | delimiter: delimiter,
|
92 | optional: optional,
|
93 | repeat: repeat,
|
94 | partial: partial,
|
95 | asterisk: !!asterisk,
|
96 | pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
|
97 | });
|
98 | }
|
99 |
|
100 |
|
101 | if (index < str.length) {
|
102 | path += str.substr(index);
|
103 | }
|
104 |
|
105 |
|
106 | if (path) {
|
107 | tokens.push(path);
|
108 | }
|
109 |
|
110 | return tokens
|
111 | }
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | function compile (str, options) {
|
121 | return tokensToFunction(parse(str, options))
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | function encodeURIComponentPretty (str) {
|
131 | return encodeURI(str).replace(/[\/?#]/g, function (c) {
|
132 | return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
133 | })
|
134 | }
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | function encodeAsterisk (str) {
|
143 | return encodeURI(str).replace(/[?#]/g, function (c) {
|
144 | return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
145 | })
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | function tokensToFunction (tokens) {
|
152 |
|
153 | var matches = new Array(tokens.length);
|
154 |
|
155 |
|
156 | for (var i = 0; i < tokens.length; i++) {
|
157 | if (typeof tokens[i] === 'object') {
|
158 | matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$');
|
159 | }
|
160 | }
|
161 |
|
162 | return function (obj, opts) {
|
163 | var path = '';
|
164 | var data = obj || {};
|
165 | var options = opts || {};
|
166 | var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent;
|
167 |
|
168 | for (var i = 0; i < tokens.length; i++) {
|
169 | var token = tokens[i];
|
170 |
|
171 | if (typeof token === 'string') {
|
172 | path += token;
|
173 |
|
174 | continue
|
175 | }
|
176 |
|
177 | var value = data[token.name];
|
178 | var segment;
|
179 |
|
180 | if (value == null) {
|
181 | if (token.optional) {
|
182 |
|
183 | if (token.partial) {
|
184 | path += token.prefix;
|
185 | }
|
186 |
|
187 | continue
|
188 | } else {
|
189 | throw new TypeError('Expected "' + token.name + '" to be defined')
|
190 | }
|
191 | }
|
192 |
|
193 | if (index$1(value)) {
|
194 | if (!token.repeat) {
|
195 | throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
|
196 | }
|
197 |
|
198 | if (value.length === 0) {
|
199 | if (token.optional) {
|
200 | continue
|
201 | } else {
|
202 | throw new TypeError('Expected "' + token.name + '" to not be empty')
|
203 | }
|
204 | }
|
205 |
|
206 | for (var j = 0; j < value.length; j++) {
|
207 | segment = encode(value[j]);
|
208 |
|
209 | if (!matches[i].test(segment)) {
|
210 | throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
|
211 | }
|
212 |
|
213 | path += (j === 0 ? token.prefix : token.delimiter) + segment;
|
214 | }
|
215 |
|
216 | continue
|
217 | }
|
218 |
|
219 | segment = token.asterisk ? encodeAsterisk(value) : encode(value);
|
220 |
|
221 | if (!matches[i].test(segment)) {
|
222 | throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
|
223 | }
|
224 |
|
225 | path += token.prefix + segment;
|
226 | }
|
227 |
|
228 | return path
|
229 | }
|
230 | }
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | function escapeString (str) {
|
239 | return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
|
240 | }
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | function escapeGroup (group) {
|
249 | return group.replace(/([=!:$\/()])/g, '\\$1')
|
250 | }
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | function attachKeys (re, keys) {
|
260 | re.keys = keys;
|
261 | return re
|
262 | }
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | function flags (options) {
|
271 | return options.sensitive ? '' : 'i'
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 | function regexpToRegexp (path, keys) {
|
282 |
|
283 | var groups = path.source.match(/\((?!\?)/g);
|
284 |
|
285 | if (groups) {
|
286 | for (var i = 0; i < groups.length; i++) {
|
287 | keys.push({
|
288 | name: i,
|
289 | prefix: null,
|
290 | delimiter: null,
|
291 | optional: false,
|
292 | repeat: false,
|
293 | partial: false,
|
294 | asterisk: false,
|
295 | pattern: null
|
296 | });
|
297 | }
|
298 | }
|
299 |
|
300 | return attachKeys(path, keys)
|
301 | }
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 | function arrayToRegexp (path, keys, options) {
|
312 | var parts = [];
|
313 |
|
314 | for (var i = 0; i < path.length; i++) {
|
315 | parts.push(pathToRegexp(path[i], keys, options).source);
|
316 | }
|
317 |
|
318 | var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
|
319 |
|
320 | return attachKeys(regexp, keys)
|
321 | }
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | function stringToRegexp (path, keys, options) {
|
332 | return tokensToRegExp(parse(path, options), keys, options)
|
333 | }
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 | function tokensToRegExp (tokens, keys, options) {
|
344 | if (!index$1(keys)) {
|
345 | options = (keys || options);
|
346 | keys = [];
|
347 | }
|
348 |
|
349 | options = options || {};
|
350 |
|
351 | var strict = options.strict;
|
352 | var end = options.end !== false;
|
353 | var route = '';
|
354 |
|
355 |
|
356 | for (var i = 0; i < tokens.length; i++) {
|
357 | var token = tokens[i];
|
358 |
|
359 | if (typeof token === 'string') {
|
360 | route += escapeString(token);
|
361 | } else {
|
362 | var prefix = escapeString(token.prefix);
|
363 | var capture = '(?:' + token.pattern + ')';
|
364 |
|
365 | keys.push(token);
|
366 |
|
367 | if (token.repeat) {
|
368 | capture += '(?:' + prefix + capture + ')*';
|
369 | }
|
370 |
|
371 | if (token.optional) {
|
372 | if (!token.partial) {
|
373 | capture = '(?:' + prefix + '(' + capture + '))?';
|
374 | } else {
|
375 | capture = prefix + '(' + capture + ')?';
|
376 | }
|
377 | } else {
|
378 | capture = prefix + '(' + capture + ')';
|
379 | }
|
380 |
|
381 | route += capture;
|
382 | }
|
383 | }
|
384 |
|
385 | var delimiter = escapeString(options.delimiter || '/');
|
386 | var endsWithDelimiter = route.slice(-delimiter.length) === delimiter;
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 | if (!strict) {
|
393 | route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?';
|
394 | }
|
395 |
|
396 | if (end) {
|
397 | route += '$';
|
398 | } else {
|
399 |
|
400 |
|
401 | route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)';
|
402 | }
|
403 |
|
404 | return attachKeys(new RegExp('^' + route, flags(options)), keys)
|
405 | }
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 | function pathToRegexp (path, keys, options) {
|
420 | if (!index$1(keys)) {
|
421 | options = (keys || options);
|
422 | keys = [];
|
423 | }
|
424 |
|
425 | options = options || {};
|
426 |
|
427 | if (path instanceof RegExp) {
|
428 | return regexpToRegexp(path, (keys))
|
429 | }
|
430 |
|
431 | if (index$1(path)) {
|
432 | return arrayToRegexp( (path), (keys), options)
|
433 | }
|
434 |
|
435 | return stringToRegexp( (path), (keys), options)
|
436 | }
|
437 |
|
438 | index.parse = parse_1;
|
439 | index.compile = compile_1;
|
440 | index.tokensToFunction = tokensToFunction_1;
|
441 | index.tokensToRegExp = tokensToRegExp_1;
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 | var cache = new Map();
|
453 |
|
454 | function decodeParam(val) {
|
455 | try {
|
456 | return decodeURIComponent(val);
|
457 | } catch (err) {
|
458 | return val;
|
459 | }
|
460 | }
|
461 |
|
462 | function matchPath(routePath, urlPath, end, parentParams) {
|
463 | var key = routePath + '|' + end;
|
464 | var regexp = cache.get(key);
|
465 |
|
466 | if (!regexp) {
|
467 | var keys = [];
|
468 | regexp = { pattern: index(routePath, keys, { end: end }), keys: keys };
|
469 | cache.set(key, regexp);
|
470 | }
|
471 |
|
472 | var m = regexp.pattern.exec(urlPath);
|
473 | if (!m) {
|
474 | return null;
|
475 | }
|
476 |
|
477 | var path = m[0];
|
478 | var params = Object.create(null);
|
479 |
|
480 | if (parentParams) {
|
481 | Object.assign(params, parentParams);
|
482 | }
|
483 |
|
484 | for (var i = 1; i < m.length; i += 1) {
|
485 | params[regexp.keys[i - 1].name] = m[i] && decodeParam(m[i]);
|
486 | }
|
487 |
|
488 | return { path: path === '' ? '/' : path, keys: regexp.keys.slice(), params: params };
|
489 | }
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 | function matchRoute(route, baseUrl, path, parentParams) {
|
501 | var match = void 0;
|
502 | var childMatches = void 0;
|
503 | var childIndex = 0;
|
504 |
|
505 | return {
|
506 | next: function next() {
|
507 | if (!match) {
|
508 | match = matchPath(route.path, path, !route.children, parentParams);
|
509 |
|
510 | if (match) {
|
511 | return {
|
512 | done: false,
|
513 | value: {
|
514 | route: route,
|
515 | baseUrl: baseUrl,
|
516 | path: match.path,
|
517 | keys: match.keys,
|
518 | params: match.params
|
519 | }
|
520 | };
|
521 | }
|
522 | }
|
523 |
|
524 | if (match && route.children) {
|
525 | while (childIndex < route.children.length) {
|
526 | if (!childMatches) {
|
527 | var newPath = path.substr(match.path.length);
|
528 | var childRoute = route.children[childIndex];
|
529 | childRoute.parent = route;
|
530 |
|
531 | childMatches = matchRoute(childRoute, baseUrl + (match.path === '/' ? '' : match.path), newPath.charAt(0) === '/' ? newPath : '/' + newPath, match.params);
|
532 | }
|
533 |
|
534 | var childMatch = childMatches.next();
|
535 | if (!childMatch.done) {
|
536 | return {
|
537 | done: false,
|
538 | value: childMatch.value
|
539 | };
|
540 | }
|
541 |
|
542 | childMatches = null;
|
543 | childIndex += 1;
|
544 | }
|
545 | }
|
546 |
|
547 | return { done: true };
|
548 | }
|
549 | };
|
550 | }
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 | function resolveRoute(context, params) {
|
562 | if (typeof context.route.action === 'function') {
|
563 | return context.route.action(context, params);
|
564 | }
|
565 |
|
566 | return null;
|
567 | }
|
568 |
|
569 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
570 |
|
571 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 | function isChildRoute(parentRoute, childRoute) {
|
583 | var route = childRoute;
|
584 | while (route) {
|
585 | route = route.parent;
|
586 | if (route === parentRoute) {
|
587 | return true;
|
588 | }
|
589 | }
|
590 | return false;
|
591 | }
|
592 |
|
593 | var Router = function () {
|
594 | function Router(routes) {
|
595 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
596 |
|
597 | _classCallCheck(this, Router);
|
598 |
|
599 | if (Object(routes) !== routes) {
|
600 | throw new TypeError('Invalid routes');
|
601 | }
|
602 |
|
603 | this.baseUrl = options.baseUrl || '';
|
604 | this.resolveRoute = options.resolveRoute || resolveRoute;
|
605 | this.context = Object.assign({ router: this }, options.context);
|
606 | this.root = Array.isArray(routes) ? { path: '/', children: routes, parent: null } : routes;
|
607 | this.root.parent = null;
|
608 | }
|
609 |
|
610 | _createClass(Router, [{
|
611 | key: 'resolve',
|
612 | value: function resolve(pathOrContext) {
|
613 | var context = Object.assign({}, this.context, typeof pathOrContext === 'string' ? { path: pathOrContext } : pathOrContext);
|
614 | var match = matchRoute(this.root, this.baseUrl, context.path.substr(this.baseUrl.length));
|
615 | var resolve = this.resolveRoute;
|
616 | var matches = null;
|
617 | var nextMatches = null;
|
618 |
|
619 | function next(resume) {
|
620 | var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : matches.value.route;
|
621 |
|
622 | matches = nextMatches || match.next();
|
623 | nextMatches = null;
|
624 |
|
625 | if (!resume) {
|
626 | if (matches.done || !isChildRoute(parent, matches.value.route)) {
|
627 | nextMatches = matches;
|
628 | return Promise.resolve(null);
|
629 | }
|
630 | }
|
631 |
|
632 | if (matches.done) {
|
633 | return Promise.reject(Object.assign(new Error('Page not found'), { context: context, status: 404, statusCode: 404 }));
|
634 | }
|
635 |
|
636 | return Promise.resolve(resolve(Object.assign({}, context, matches.value), matches.value.params)).then(function (result) {
|
637 | if (result !== null && result !== undefined) {
|
638 | return result;
|
639 | }
|
640 |
|
641 | return next(resume, parent);
|
642 | });
|
643 | }
|
644 |
|
645 | context.url = context.path;
|
646 | context.next = next;
|
647 |
|
648 | return next(true, this.root);
|
649 | }
|
650 | }]);
|
651 |
|
652 | return Router;
|
653 | }();
|
654 |
|
655 | Router.pathToRegexp = index;
|
656 | Router.matchPath = matchPath;
|
657 | Router.matchRoute = matchRoute;
|
658 | Router.resolveRoute = resolveRoute;
|
659 |
|
660 | return Router;
|
661 |
|
662 | })));
|
663 |
|