Skip to content

Commit 1d49580

Browse files
posvapierresaidatilkansmolinari
authored
Dynamic routing (#122)
* Dynamic Routing * precision from * add getRoutes * add alternative for getRoutes * fix: typo in 0000-router-dynamic-routing.md (#1) * fix typo (#2) * Update 0000-router-dynamic-routing.md (#3) Just some grammatical corrections. Hope you don't mind. Scott * remove template drawbacks * update * rename Symbol to symbol, type typo * rename Co-authored-by: pierresaid <[email protected]> Co-authored-by: atilkan <[email protected]> Co-authored-by: Scott <[email protected]>
2 parents ed64b46 + d86b9d4 commit 1d49580

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

Diff for: active-rfcs/0029-router-dynamic-routing.md

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
- Start Date: 2020-01-27
2+
- Target Major Version: Vue 3 Router 4
3+
- Reference Issues: [issues](https://github.com/vuejs/vue-router/issues?q=is%3Aopen+is%3Aissue+label%3A%22group%5Bdynamic+routing%5D%22)
4+
- Implementation PR:
5+
6+
# Summary
7+
8+
Introducing an API that allows adding and removing route records while the router is working
9+
10+
- `router.addRoute(route: RouteRecord)` Add a new route
11+
- `router.removeRoute(name: string | symbol)` Remove an existing route
12+
- `router.hasRoute(name: string | symbol): boolean` Check if a route exists
13+
- `router.getRoutes(): RouteRecord[]` Get the current list of routes
14+
15+
# Basic example
16+
17+
Given a router instance in a running app:
18+
19+
```ts
20+
router.addRoute({
21+
path: '/new-route',
22+
name: 'NewRoute',
23+
component: NewRoute
24+
})
25+
26+
// add to the children of an existing route
27+
router.addRoute('ParentRoute', {
28+
path: 'new-route',
29+
name: 'NewRoute',
30+
component: NewRoute
31+
})
32+
33+
router.removeRoute('NewRoute')
34+
35+
// normalized version of the records added
36+
const routeRecords = router.getRoutes()
37+
```
38+
39+
# Motivation
40+
41+
Dynamic routing is a feature that enables applications to build its own routing system. A usecase of this is the vue-cli ui, that allows adding graphical plugins which can have their own interfaces.
42+
43+
The current version of Vue Router only supports adding a new absolute route. The idea of this RFC is to add the missing functions to allow dynamic routing. Thinking about the different ways this API can be shaped and what is best for the future of Vue Router. Currently, it's impossible to achieve the example described above without hacks or creating a whole new Router instance.
44+
45+
In theory this could also improve Developer experience by changing routes in place with Hot Module Replacement.
46+
47+
Note: Dynamic routing only makes sense after implementing path ranking (automatic route order that allows route records to be added in any order but still be matched correctly). Most of the cost of adding this feature is on the path scoring side.
48+
49+
# Detailed design
50+
51+
## `addRoute`
52+
53+
- Allow adding an absolute route
54+
- Allow adding a child route
55+
- How should conflicts be handled?
56+
- What should be returned by the function?
57+
58+
The code samples are written in TS to provide more information about the shape of the object expected:
59+
60+
`RouteRecord` is the same kind of object used in the `routes` option when instantiating the Router. To give you an idea, it looks like (https://router.vuejs.org/api/#routes). Important to note, this object is consistent with the `routes` options, since the `RouteRecord` type might have minor changes in the future version of Vue Router 4.
61+
62+
```ts
63+
const routeRecord: RouteRecord = {
64+
path: '/new-route',
65+
name: 'NewRoute',
66+
component: NewRoute
67+
}
68+
```
69+
70+
There are different options for what to return from `addRoute`.
71+
72+
Returning a function that allows removing the route is useful for unamed routes.
73+
74+
```ts
75+
const removeRoute = router.addRoute(routeRecord)
76+
77+
removeRoute() // removes the route record
78+
// or
79+
router.removeRoute('NewRoute') // because names are unique
80+
```
81+
82+
If we are on `/new-route`, adding the record will **not** trigger a _replace_ navigation. The user is responsible for forcing a navigation by calling `router.push` or `router.replace` with the current location `router.replace(router.currentRoute.value.fullPath)`. Using the string version of the location ensures that a new location is resolved instead of the old one. If the route is added inside of a navigation guard, make sure to use the value from the `to` parameter:
83+
84+
```js
85+
router.beforeEach((to, from, next) => {
86+
// ...
87+
router.addRoute(newRoute)
88+
next(to.fullPath)
89+
// ...
90+
})
91+
```
92+
93+
Because the Dynamic Routing API is an advanced API that most users won't directly use, having this split allows more flexible and optimized behavior. The recommended approach to add routes will still be the configuration based one.
94+
95+
_For Alternatives, please check [alternatives](#alternatives)_
96+
97+
### Conflicts
98+
99+
When adding a route that has the same name as an existing route, it should replace the existing route. This is the most convenient version, because it allows replacing new routes without having to explicitely remove the old ones **when they are using the same name**.
100+
101+
Alternatives:
102+
103+
- Fail and throw an error that asks the user to replace it first: less convenient
104+
- Not warn the user but still replace it: could be prone to difficult to debug errors
105+
106+
### Nested routes
107+
108+
Depending on what is returned by `addRoute`, a nested route can be added by referencing the name of an existing route. This forces parent routes to be named.
109+
110+
```ts
111+
router.addRoute('ParentRoute', routeRecord)
112+
```
113+
114+
### Signature
115+
116+
```ts
117+
interface addRoute {
118+
(parentName: string | symbol, route: RouteRecord): () => void
119+
(route: RouteRecord): () => void
120+
}
121+
```
122+
123+
## `removeRoute`
124+
125+
Removing a route removes all its children as well. As with `addRoute`, it's necessary to trigger a new navigation in order to update the current displayed view by _RouterView_.
126+
127+
```ts
128+
interface removeRoute {
129+
(name: string | symbol): void
130+
}
131+
```
132+
133+
## `hasRoute`
134+
135+
Checks if a route exists:
136+
137+
```ts
138+
interface hasRoute {
139+
(name: string | symbol): boolean
140+
}
141+
```
142+
143+
## `getRoutes`
144+
145+
Allows reading the list of normalized route records:
146+
147+
```ts
148+
interface getRoutes {
149+
(): RouteRecordNormalized[]
150+
}
151+
```
152+
153+
What is present in RouteRecordNormalized is yet to be decided, but contains at least all existing properties from a RouteRecord, some of them normalized (like `components` instead of `component` and an `undefined` name).
154+
155+
# Drawbacks
156+
157+
- This API increases vue-router size. To make it treeshakable will require allowing the matcher (responsible for parsing `path` and doing the path ranking) to be extended with simpler versions, which are quite complex to write but we could instead export different versions of the matcher and allow the user specifying the matcher in a leaner version of the router.
158+
159+
# Alternatives
160+
161+
## `addRoutes`
162+
163+
- A promise that resolves or rejects based on the possible navigation that adding the record might trigger (e.g. being on `/new-route` before adding the route)
164+
165+
```ts
166+
window.location.pathname // "/new-route"
167+
// 1. The promise of a function that allows removing the route record
168+
const removeRoute = await router.addRoute(routeRecord)
169+
// 2. The same kind of promise returned by `router.push`.
170+
const route = await router.addRoute(routeRecord)
171+
172+
removeRoute() // removes the route record
173+
// or
174+
router.removeRoute('NewRoute') // names are unique
175+
```
176+
177+
- A reference to the record added (a normalized copy of the original record)
178+
179+
```ts
180+
// js object (same reference hold by the router)
181+
const normalizedRouteRecord = router.addRoute(routeRecord)
182+
router.removeRoute(normalizedRouteRecord)
183+
```
184+
185+
## `getRoutes`
186+
187+
There could also be a reactive property with the routes, but this would allow using them in templates, which in most scenarios is an application level feature which could and should handle things the other way around, **having a reactive source of route records that are syncronized with the router**. Doing this at the application level avoids adding the cost for every user.
188+
189+
# Adoption strategy
190+
191+
- Deprecate `addRoutes` and add `addRoute` to Vue Router 3. Other methods are new.

0 commit comments

Comments
 (0)