1 | # Abstract Nested Router
|
2 |
|
3 | > It _tries_ to capture all matching routes from its **root**.
|
4 | >
|
5 | > [![Build Status](https://api.travis-ci.org/pateketrueke/abstract-nested-router.svg?branch=master)](https://travis-ci.org/pateketrueke/abstract-nested-router)
|
6 | > [![NPM version](https://badge.fury.io/js/abstract-nested-router.svg)](http://badge.fury.io/js/abstract-nested-router)
|
7 | > [![Coverage Status](https://codecov.io/github/pateketrueke/abstract-nested-router/coverage.svg?branch=master)](https://codecov.io/github/pateketrueke/abstract-nested-router)
|
8 | > [![Known Vulnerabilities](https://snyk.io/test/npm/abstract-nested-router/badge.svg)](https://snyk.io/test/npm/abstract-nested-router)
|
9 |
|
10 | ```js
|
11 | import Router from 'abstract-nested-router';
|
12 |
|
13 | const r = new Router();
|
14 |
|
15 | // single routes
|
16 | r.add('/', { is: 'home' });
|
17 | r.add('/*_', { is: 'catch' });
|
18 |
|
19 | // nested routes
|
20 | r.mount('/:a', () => {
|
21 | r.add('/*_', { is: 'undef' });
|
22 | r.add('/:b/:c', { is: 'nested' });
|
23 | });
|
24 |
|
25 | r.find('/');
|
26 | [ { is: 'home', params: {}, route: '/', path: '/' } ]
|
27 |
|
28 | r.find('/test');
|
29 | [ { is: 'home', params: {}, route: '/', path: '/' },
|
30 | { is: 'undef',
|
31 | params: { a: 'test' },
|
32 | route: '/:a',
|
33 | path: '/test' } ]
|
34 |
|
35 | r.find('/x/y');
|
36 | [ { is: 'home', params: {}, route: '/', path: '/' },
|
37 | { is: 'undef', params: { a: 'x' }, route: '/:a', path: '/x' },
|
38 | { is: 'nested',
|
39 | params: { a: 'x', b: 'y' },
|
40 | route: '/:a/:b',
|
41 | path: '/x/y' } ]
|
42 |
|
43 | r.find('/x/y/z');
|
44 | [ { is: 'home', params: {}, route: '/', path: '/' },
|
45 | { is: 'undef', params: { a: 'x' }, route: '/:a', path: '/x' },
|
46 | { is: 'nested',
|
47 | params: { a: 'x', b: 'y' },
|
48 | route: '/:a/:b',
|
49 | path: '/x/y' },
|
50 | { is: 'nested',
|
51 | params: { a: 'x', b: 'y', c: 'z' },
|
52 | route: '/:a/:b/:c',
|
53 | path: '/x/y/z' } ]
|
54 |
|
55 | r.find('/x/y/z/0');
|
56 | // Error: Unreachable '/x/y/z/0', segment '/0' is not defined
|
57 |
|
58 | r.find('/x/y/z/0', true);
|
59 | [ { is: 'home', params: {}, route: '/', path: '/' },
|
60 | { is: 'catch',
|
61 | params: { _: 'x/y/z/0' },
|
62 | route: '/*_',
|
63 | path: '/x/y/z/0' } ]
|
64 | ```
|
65 |
|
66 | In the latter example `catch` is resolved just after the previous failure of `/x/y/z/0` because we're trying at least twice.
|
67 |
|
68 | ## API
|
69 |
|
70 | Available methods:
|
71 |
|
72 | - `mount(path, cb)` — Allow to register routes under the same route
|
73 | - `find(path[, retries])` — Look up routes by path, in case of failure try passing `retries` as true
|
74 | - `add(path[, routeInfo])` — Register a single route by path, additional info will be returned on match
|
75 | - `rm(path)` — Remove a single route by full-path, it will fail if given route is not registered!
|
76 |
|
77 | ### Params
|
78 |
|
79 | By default all segments are optional, e.g. `/a/:b/:c` matches with `/a`, `/a/x` and `/a/x/y` so you can say `:b` and `:c` are optional parameters.
|
80 |
|
81 | More advanced cases would require fragments to be optional, e.g. `/:foo(-bar)` matches with `/x` and `/x-bar` because `-bar` is an optional fragment.
|
82 |
|
83 | In the latter case `params.foo` will always be `x` regardless if `-bar` is appended, if you want to match `bar` then use `/:foo(-:suffix)` instead.
|
84 |
|
85 | > _Splat_ parameters will consume the rest of the segments/fragments if they're present, e.g. `/x*y` captures anything that begins with `x` and stores it on `params.y` so it matches `/xy`, `/xabc`, `/x/y`, `/x/a/b/c` and so on.
|
86 |
|
87 | Every parameter can hold simple regex-like patterns, e.g. `/:id<\d+>`
|
88 |
|
89 | Supported patterns:
|
90 |
|
91 | - `/:x` and `/*y` are optional segments and they cannot be empty
|
92 | - `<...>` to hold regex-like patterns, `-$.` are escaped, `/` is forbidden
|
93 | - `(...)` are used to mark fragments as optional, it translates to `(?:...)?`
|
94 |
|
95 | > Please avoid `/` inside `(...)` or `<...>` as they will fail loudly!
|
96 |
|
97 | ### Nesting
|
98 |
|
99 | Consider the following examples:
|
100 |
|
101 | ```js
|
102 | // 1. regular
|
103 | r.add('/a');
|
104 | r.add('/a/:b');
|
105 | r.add('/a/:b/:c');
|
106 |
|
107 | // 2. nested
|
108 | r.mount('/a', () => {
|
109 | r.mount('/:b', () => {
|
110 | r.add('/:c');
|
111 | });
|
112 | });
|
113 |
|
114 | // 3. concise
|
115 | r.add('/a/:b/:c');
|
116 | ```
|
117 |
|
118 | In the former way (1) we're declaring each route-level by hand, however they can be expressed at once as that latter one (3) which is more concise.
|
119 |
|
120 | The middle form (2) is a shortcut to produce concise routes.
|
121 |
|
122 | So which one is the best? It depends on the context:
|
123 |
|
124 | - Use concise routes to share the same `routeInfo` on all segments, it will be applied only if it's not yet defined on the route.
|
125 | - Use nested routes to use shared paths, it's convenient for creating stacks of context while mounting routes, etc.
|
126 | - Use regular routes to gain full control over its definition, this way each route can have its own separated context.
|
127 |
|
128 | > Routes are sorted and matched by priority and type, routes with splat params will be tried last. As more static and with less parameters the route will be matched sooner!
|