|
| 1 | +# [router.js](https://github.com/tildeio/router.js) Architecture |
| 2 | + |
| 3 | +This is a guide to `router.js`'s internals. |
| 4 | + |
| 5 | +`router.js` is a stand-alone microlibrary for client-side routing in JavaScript |
| 6 | +applications. It's notably used by the [Ember.js Router][Ember Router]. |
| 7 | + |
| 8 | + |
| 9 | +## Scope of `router.js` and its Dependencies |
| 10 | + |
| 11 | +Ember.js's router consumes `router.js`, which in turn consumes |
| 12 | +[route-recognizer](https://github.com/tildeio/route-recognizer). |
| 13 | + |
| 14 | +The division of responsibilities of these three libs is as follows: |
| 15 | + |
| 16 | +### `route-recognizer` |
| 17 | + |
| 18 | +`route-recognizer` is an engine for both parsing/generating URLs into/from |
| 19 | +parameters. |
| 20 | + |
| 21 | +It can take a URL like `articles/123/comments` and parse out the parameter |
| 22 | +`{ article_id: "123" }`. |
| 23 | + |
| 24 | +It can take `{ article_id: "123" }` and a route descriptor like |
| 25 | +`articles/:article_id/comments` and generate `articles/123/comments`. |
| 26 | + |
| 27 | +### `router.js` |
| 28 | + |
| 29 | +`router.js` adds the concept of transitions to `route-recognizer`'s |
| 30 | +URL parsing engine. |
| 31 | + |
| 32 | +Transitions can be URL-initiated (via browser navigation) or can be |
| 33 | +directly initiated via route name |
| 34 | +(e.g. `transitionTo('articles', articleObject)`). |
| 35 | + |
| 36 | +`router.js` resolves all of the model objects that needed to be loaded |
| 37 | +in order to enter a route. |
| 38 | + |
| 39 | +e.g. to navigate to `articles/123/comments/2`, a promise for both the |
| 40 | +`article` and `comments` routes need to be fulfilled. |
| 41 | + |
| 42 | +### Ember Router |
| 43 | + |
| 44 | +The [Ember Router][] adds a DSL for declaring your app's routes on top of |
| 45 | +`router.js`. It defines the API for the `Ember.Route` class that handles |
| 46 | +intelligent defaults, rendering templates, and loading data into controllers. |
| 47 | + |
| 48 | + |
| 49 | +## History |
| 50 | + |
| 51 | +`router.js` has gone through a few iterations between 2013 and 2014: |
| 52 | + |
| 53 | +* July of 2013 – `router.js` adds promise-awareness. |
| 54 | +* Jan 2014 – refactored `router.js`'s primitives to handle corner cases. |
| 55 | + |
| 56 | +### Corner Cases |
| 57 | + |
| 58 | +1. Avoid running `model` hooks (responsible for fetching data needed to enter a |
| 59 | + route) for shared parent routes. |
| 60 | + |
| 61 | +2. Avoid running model hooks when redirecting in the middle of another transition. |
| 62 | + e.g. during a transition to `articles/123/comments/2` you redirect to |
| 63 | + `articles/123/comments/3` after resolving Article 123 and you want to |
| 64 | + avoid re-running the hooks to load Article 123 again. |
| 65 | + |
| 66 | +3. Handle two different approaches to transitions: |
| 67 | + |
| 68 | + * URL based (where a URL is parsed into route parameters that are used to |
| 69 | + load all the data needed to enter a route (e.g. `{ article_id: 123 }`). |
| 70 | + |
| 71 | + * direct named transition-based, where a route name and any context objects |
| 72 | + are provided (e.g. `transitionTo('article', articleObject)`), and the |
| 73 | + provided context object(s) might be promises that can't be serialized |
| 74 | + into URL params until they've fulfilled. |
| 75 | + |
| 76 | + |
| 77 | +## Classes |
| 78 | + |
| 79 | +### `HandlerInfo` |
| 80 | + |
| 81 | +A `HandlerInfo` is an object that describes the state of a route handler. |
| 82 | + |
| 83 | +For example, the `foo/bar` URL likely breaks down into a hierarchy of two |
| 84 | +handlers: the `foo` handler and the `bar` handler. A "handler" is just an |
| 85 | +object that defines hooks that `router.js` will call in the course of a |
| 86 | +transition (e.g. `model`, `beforeModel`, `setup`, etc.). |
| 87 | + |
| 88 | +In Ember.js, handlers are instances of `Ember.Route`. |
| 89 | + |
| 90 | +A `HandlerInfo` instance contains that handler's model (e.g. `articleObject`), |
| 91 | +or the URL parameters associated with the current state of that handler |
| 92 | +(e.g. `{ article_id: '123' }`). |
| 93 | + |
| 94 | +Because `router.js` allows you to reuse handlers between different routes and |
| 95 | +route hierarchies, we need `HandlerInfo`s to describe the state of each route |
| 96 | +hierarchy. |
| 97 | + |
| 98 | +`HandlerInfo` is a top-level class with 3 subclasses: |
| 99 | + |
| 100 | +#### `UnresolvedHandlerInfoByParam` |
| 101 | +`UnresolvedHandlerInfoByParam` has the URL params stored on it which it can use |
| 102 | +to resolve itself (by calling the handler's `beforeModel`/`model`/`afterModel` |
| 103 | +hooks). |
| 104 | + |
| 105 | +#### `UnresolvedHandlerInfoByObject` |
| 106 | +`UnresolvedHandlerInfoByObject` has a context object, but no URL params. |
| 107 | +It can use the context to resolve itself and serialize into URL params once |
| 108 | +the context object is fulfilled. |
| 109 | + |
| 110 | +#### `ResolvedHandlerInfo` |
| 111 | +`ResolvedHandlerInfo` has calculated its URL params and resolved context/model |
| 112 | +object. |
| 113 | + |
| 114 | +#### Public API |
| 115 | +`HandlerInfo` has just a `resolve` method which fires all `model` hooks and |
| 116 | +ultimately resolves to a `ResolvedHandlerInfo` object. |
| 117 | + |
| 118 | +The `ResolvedHandlerInfo`'s `resolve` method just returns a promise that |
| 119 | +fulfills with itself. |
| 120 | + |
| 121 | +### `TransitionState` |
| 122 | + |
| 123 | +The `TransitionState` object consists of an array of `HandlerInfo`s |
| 124 | +(though more might be added to it; not sure yet). |
| 125 | + |
| 126 | +#### Public API |
| 127 | +It too has a public API consisting only of a `resolve` method that |
| 128 | +will loop through all of its `HandlerInfo`s, swapping unresolved |
| 129 | +`HandlerInfo`s with `ResolvedHandlerInfo`s as it goes. |
| 130 | + |
| 131 | +Instances of `Router` and `Transition` contain `TransitionState` |
| 132 | +properties, which is useful since, depending on whether or not there is |
| 133 | +a currently active transition, the "starting point" of a transition |
| 134 | +might be the router's current hierarchy of `ResolvedHandlerInfo`s, or it |
| 135 | +might be a transition's hierarchy of `ResolvedHandlerInfo`s mixed with |
| 136 | +unresolved HandlerInfos. |
| 137 | + |
| 138 | +### `TransitionIntent` |
| 139 | + |
| 140 | +A `TransitionIntent` describes an attempt to transition. |
| 141 | + |
| 142 | + via URL |
| 143 | +or by named transition (via its subclasses `URLTransitionIntent` and |
| 144 | +`NamedTransitionIntent`). |
| 145 | + |
| 146 | +#### `URLTransitionIntent` |
| 147 | +A `URLTransitionIntent` has a `url` property. |
| 148 | + |
| 149 | +#### `NamedTransitionIntent` |
| 150 | +A `NamedTransitionIntent` has a target route `name` and `contexts` array |
| 151 | +property. |
| 152 | + |
| 153 | +This class defines only one method `applyToState` which takes an instance of |
| 154 | +`TransitionState` and plays this `TransitionIntent` on top of it to generate |
| 155 | +and return a new instance of `TransitionState` that contains a combination of |
| 156 | +resolved and unresolved `HandlerInfo`s. |
| 157 | + |
| 158 | +`TransitionIntent`s don't care whether the provided state comes from a router |
| 159 | +or a currently active transition; whatever you provide it, both subclasses of |
| 160 | +`TransitionIntent`s are smart enough to spit out a `TransitionState` |
| 161 | +containing `HandlerInfo`s that still need to be resolved in order to complete |
| 162 | +a transition. |
| 163 | + |
| 164 | +Much of the messy logic that used to live in `paramsForHandler`/`getMatchPoint` |
| 165 | +now live way less messily in the `applyToState` methods. |
| 166 | + |
| 167 | +This makes it easy to detect corner cases like no-op transitions – if the |
| 168 | +returned `TransitionState` consists entirely of `ResolvedHandlerInfo`s, there's |
| 169 | +no need to fire off a transition. It simplifies things like redirecting into a |
| 170 | +child route without winding up in some infinite loop on the parent route hook |
| 171 | +that's doing the redirecting. |
| 172 | + |
| 173 | +This simplifies `Transition#retry`; to retry a transition, provide its `intent` |
| 174 | +property to the transitioning function used by `transitionTo`, `handleURL`. |
| 175 | +`handle` function will make the right choice as to the correct `TransitionState` |
| 176 | +to pass to the intent's `applyToState` method. |
| 177 | + |
| 178 | +This approach is used to implement `Router#isActive`. You can determine if a |
| 179 | +destination route is active by constructing a `TransitionIntent`, applying it |
| 180 | +to the router's current state, and returning `true` if all of the |
| 181 | +`HandlerInfo`s are already resolved. |
| 182 | + |
| 183 | +[Ember Router]: http://emberjs.com/guides/routing/ |
0 commit comments