1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | import { match, } from 'path-to-regexp';
|
10 | function decode(val) {
|
11 | try {
|
12 | return decodeURIComponent(val);
|
13 | }
|
14 | catch (err) {
|
15 | return val;
|
16 | }
|
17 | }
|
18 | function 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 | }
|
71 | function resolveRoute(context, params) {
|
72 | if (typeof context.route.action === 'function') {
|
73 | return context.route.action(context, params);
|
74 | }
|
75 | return undefined;
|
76 | }
|
77 | function 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 | }
|
87 | class UniversalRouterSync {
|
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 |
|
99 |
|
100 |
|
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 null;
|
123 | }
|
124 | }
|
125 | if (matches.done) {
|
126 | const error = new Error('Route not found');
|
127 | error.status = 404;
|
128 | throw error;
|
129 | }
|
130 | currentContext = { ...context, ...matches.value };
|
131 | const result = resolve(currentContext, matches.value.params);
|
132 | if (result !== null && result !== undefined) {
|
133 | return result;
|
134 | }
|
135 | return next(resume, parent, result);
|
136 | }
|
137 | context.next = next;
|
138 | try {
|
139 | return next(true, this.root);
|
140 | }
|
141 | catch (error) {
|
142 | if (this.options.errorHandler) {
|
143 | return this.options.errorHandler(error, currentContext);
|
144 | }
|
145 | throw error;
|
146 | }
|
147 | }
|
148 | }
|
149 | export default UniversalRouterSync;
|
150 |
|
\ | No newline at end of file |