UNPKG

7.42 kBJavaScriptView Raw
1var defaultExport = /*@__PURE__*/(function (Error) {
2 function defaultExport(route, path) {
3 var message = "Unreachable '" + route + "', segment '" + path + "' is not defined";
4 Error.call(this, message);
5 this.message = message;
6 }
7
8 if ( Error ) defaultExport.__proto__ = Error;
9 defaultExport.prototype = Object.create( Error && Error.prototype );
10 defaultExport.prototype.constructor = defaultExport;
11
12 return defaultExport;
13}(Error));
14
15function buildMatcher(path, parent) {
16 var regex;
17
18 var _isSplat;
19
20 var _priority = -100;
21
22 var keys = [];
23 regex = path.replace(/[-$.]/g, '\\$&').replace(/\(/g, '(?:').replace(/\)/g, ')?').replace(/([:*]\w+)(?:<([^<>]+?)>)?/g, function (_, key, expr) {
24 keys.push(key.substr(1));
25
26 if (key.charAt() === ':') {
27 _priority += 100;
28 return ("((?!#)" + (expr || '[^#/]+?') + ")");
29 }
30
31 _isSplat = true;
32 _priority += 500;
33 return ("((?!#)" + (expr || '[^#]+?') + ")");
34 });
35
36 try {
37 regex = new RegExp(("^" + regex + "$"));
38 } catch (e) {
39 throw new TypeError(("Invalid route expression, given '" + parent + "'"));
40 }
41
42 var _hashed = path.includes('#') ? 0.5 : 1;
43
44 var _depth = path.length * _priority * _hashed;
45
46 return {
47 keys: keys,
48 regex: regex,
49 _depth: _depth,
50 _isSplat: _isSplat
51 };
52}
53var PathMatcher = function PathMatcher(path, parent) {
54 var ref = buildMatcher(path, parent);
55 var keys = ref.keys;
56 var regex = ref.regex;
57 var _depth = ref._depth;
58 var _isSplat = ref._isSplat;
59 return {
60 _isSplat: _isSplat,
61 _depth: _depth,
62 match: function (value) {
63 var matches = value.match(regex);
64
65 if (matches) {
66 return keys.reduce(function (prev, cur, i) {
67 prev[cur] = typeof matches[i + 1] === 'string' ? decodeURIComponent(matches[i + 1]) : null;
68 return prev;
69 }, {});
70 }
71 }
72 };
73};
74
75PathMatcher.push = function push (key, prev, leaf, parent) {
76 var root = prev[key] || (prev[key] = {});
77
78 if (!root.pattern) {
79 root.pattern = new PathMatcher(key, parent);
80 root.route = (leaf || '').replace(/\/$/, '') || '/';
81 }
82
83 prev.keys = prev.keys || [];
84
85 if (!prev.keys.includes(key)) {
86 prev.keys.push(key);
87 PathMatcher.sort(prev);
88 }
89
90 return root;
91};
92
93PathMatcher.sort = function sort (root) {
94 root.keys.sort(function (a, b) {
95 return root[a].pattern._depth - root[b].pattern._depth;
96 });
97};
98
99function merge(path, parent) {
100 return ("" + (parent && parent !== '/' ? parent : '') + (path || ''));
101}
102function walk(path, cb) {
103 var matches = path.match(/<[^<>]*\/[^<>]*>/);
104
105 if (matches) {
106 throw new TypeError(("RegExp cannot contain slashes, given '" + matches + "'"));
107 }
108
109 var parts = path !== '/' ? path.split('/') : [''];
110 var root = [];
111 parts.some(function (x, i) {
112 var parent = root.concat(x).join('/') || null;
113 var segment = parts.slice(i + 1).join('/') || null;
114 var retval = cb(("/" + x), parent, segment ? ((x ? ("/" + x) : '') + "/" + segment) : null);
115 root.push(x);
116 return retval;
117 });
118}
119function reduce(key, root, _seen) {
120 var params = {};
121 var out = [];
122 var splat;
123 walk(key, function (x, leaf, extra) {
124 var found;
125
126 if (!root.keys) {
127 throw new defaultExport(key, x);
128 }
129
130 root.keys.some(function (k) {
131 if (_seen.includes(k)) { return false; }
132 var ref = root[k].pattern;
133 var match = ref.match;
134 var _isSplat = ref._isSplat;
135 var matches = match(_isSplat ? extra || x : x);
136
137 if (matches) {
138 Object.assign(params, matches);
139
140 if (root[k].route) {
141 var routeInfo = Object.assign({}, root[k].info); // properly handle exact-routes!
142
143 var hasMatch = false;
144
145 if (routeInfo.exact) {
146 hasMatch = extra === null;
147 } else {
148 hasMatch = x && leaf === null || x === leaf || _isSplat || !extra;
149 }
150
151 routeInfo.matches = hasMatch;
152 routeInfo.params = Object.assign({}, params);
153 routeInfo.route = root[k].route;
154 routeInfo.path = _isSplat ? extra : leaf || x;
155 out.push(routeInfo);
156 }
157
158 if (extra === null && !root[k].keys) {
159 return true;
160 }
161
162 if (k !== '/') { _seen.push(k); }
163 splat = _isSplat;
164 root = root[k];
165 found = true;
166 return true;
167 }
168
169 return false;
170 });
171
172 if (!(found || root.keys.some(function (k) { return root[k].pattern.match(x); }))) {
173 throw new defaultExport(key, x);
174 }
175
176 return splat || !found;
177 });
178 return out;
179}
180function find(path, routes, retries) {
181 var get = reduce.bind(null, path, routes);
182 var set = [];
183
184 while (retries > 0) {
185 retries -= 1;
186
187 try {
188 return get(set);
189 } catch (e) {
190 if (retries > 0) {
191 return get(set);
192 }
193
194 throw e;
195 }
196 }
197}
198function add(path, routes, parent, routeInfo) {
199 var fullpath = merge(path, parent);
200 var root = routes;
201 var key;
202
203 if (routeInfo && routeInfo.nested !== true) {
204 key = routeInfo.key;
205 delete routeInfo.key;
206 }
207
208 walk(fullpath, function (x, leaf) {
209 root = PathMatcher.push(x, root, leaf, fullpath);
210
211 if (x !== '/') {
212 root.info = root.info || Object.assign({}, routeInfo);
213 }
214 });
215 root.info = root.info || Object.assign({}, routeInfo);
216
217 if (key) {
218 root.info.key = key;
219 }
220
221 return fullpath;
222}
223function rm(path, routes, parent) {
224 var fullpath = merge(path, parent);
225 var root = routes;
226 var leaf = null;
227 var key = null;
228 walk(fullpath, function (x) {
229 if (!root) {
230 leaf = null;
231 return true;
232 }
233
234 key = x;
235 leaf = x === '/' ? routes['/'] : root;
236
237 if (!leaf.keys) {
238 throw new defaultExport(path, x);
239 }
240
241 root = root[x];
242 });
243
244 if (!(leaf && key)) {
245 throw new defaultExport(path, key);
246 }
247
248 delete leaf[key];
249
250 if (key === '/') {
251 delete leaf.info;
252 delete leaf.route;
253 }
254
255 var offset = leaf.keys.indexOf(key);
256
257 if (offset !== -1) {
258 leaf.keys.splice(leaf.keys.indexOf(key), 1);
259 PathMatcher.sort(leaf);
260 }
261}
262
263var Router = function Router() {
264 var routes = {};
265 var stack = [];
266 return {
267 resolve: function (path, cb) {
268 var ref = path.split(/(?=[#?])/);
269 var uri = ref[0];
270 var hash = ref[1];
271 var query = ref[2];
272 var segments = uri.substr(1).split('/');
273 var prefix = [];
274 var seen = [];
275 segments.some(function (key) {
276 var sub = prefix.concat(("/" + key)).join('');
277 if (key.length) { prefix.push(("/" + key)); }
278
279 try {
280 var next = find(sub, routes, 1);
281 cb(null, next.filter(function (x) {
282 if (!seen.includes(x.route)) {
283 seen.push(x.route);
284 return true;
285 }
286
287 return false;
288 }));
289 } catch (e) {
290 cb(e, []);
291 return true;
292 }
293
294 return false;
295 });
296
297 if (hash) {
298 cb(null, find(("" + uri + hash), routes, 1));
299 }
300 },
301 mount: function (path, cb) {
302 if (path !== '/') {
303 stack.push(path);
304 }
305
306 cb();
307 stack.pop();
308 },
309 find: function (path, retries) { return find(path, routes, retries === true ? 2 : retries || 1); },
310 add: function (path, routeInfo) { return add(path, routes, stack.join(''), routeInfo); },
311 rm: function (path) { return rm(path, routes, stack.join('')); }
312 };
313};
314
315export default Router;