@tinkoff/router 0.1.64 → 0.1.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/lib/index.browser.js +3 -4
- package/lib/index.es.js +15 -11
- package/lib/index.js +15 -11
- package/lib/router/abstract.d.ts +2 -1
- package/lib/router/server.d.ts +4 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -66,8 +66,25 @@ export const myGuard: NavigationGuard = async ({ to }) => {
|
|
|
66
66
|
|
|
67
67
|
// if nothing is returned, the transition will be performed as usual
|
|
68
68
|
};
|
|
69
|
+
|
|
70
|
+
router.registerGuard(myGuard);
|
|
69
71
|
```
|
|
70
72
|
|
|
73
|
+
#### Rules
|
|
74
|
+
|
|
75
|
+
- guards are asynchronous and it execution will be awaited inside routing
|
|
76
|
+
- all guards are running in parallel and they are all awaited
|
|
77
|
+
- if several guars return something then the result from a guard that was registered early will be used
|
|
78
|
+
|
|
79
|
+
#### Possible result
|
|
80
|
+
|
|
81
|
+
The behaviour of routing depends on the result of executing guards functions and there result might be next:
|
|
82
|
+
|
|
83
|
+
- if all of the guards returns `undefined` than navigation will continue executing
|
|
84
|
+
- if any of the guards returns `false` than navigation is getting blocked and next action differs on server and client
|
|
85
|
+
- if any of the guards returns `string` it is considered as url to which redirect should be happen
|
|
86
|
+
- if any of the guards returns `NavigateOptions` interface, `url` property from it is considered as url to which redirect should be happen
|
|
87
|
+
|
|
71
88
|
### Transitions hooks
|
|
72
89
|
|
|
73
90
|
Transition hooks allow you to perform your asynchronous actions at different stages of the transition.
|
|
@@ -78,8 +95,27 @@ import { NavigationHook } from '@tinkoff/router';
|
|
|
78
95
|
export const myHook: NavigationHook = async ({ from, to, url, fromUrl }) => {
|
|
79
96
|
console.log(`navigating from ${from} to route ${to}`);
|
|
80
97
|
};
|
|
98
|
+
|
|
99
|
+
router.registerHook('beforeNavigate', myHook);
|
|
81
100
|
```
|
|
82
101
|
|
|
102
|
+
#### Rules
|
|
103
|
+
|
|
104
|
+
- all hooks from the same event are running in parallel
|
|
105
|
+
- most of the hooks are asynchronous and are awaited inside router
|
|
106
|
+
- if some error happens when running hook it will be logged to console but wont affect navigation (except for the `beforeResolve` hook - error for it will be rethrown)
|
|
107
|
+
|
|
108
|
+
#### List of available hooks
|
|
109
|
+
|
|
110
|
+
Async hooks:
|
|
111
|
+
|
|
112
|
+
- [navigate hooks](#navigate-hooks) - asynchronous hooks only for navigate calls
|
|
113
|
+
- [updateCurrentRoute hooks](#updatecurrentroute-hooks) - asynchronous hooks only for updateCurrentRoute calls
|
|
114
|
+
|
|
115
|
+
Sync hooks:
|
|
116
|
+
|
|
117
|
+
- `change` - runs when any of changes to current route\url happens
|
|
118
|
+
|
|
83
119
|
## API
|
|
84
120
|
|
|
85
121
|
### Getting data about the current route or url
|
|
@@ -102,12 +138,20 @@ router.navigate('/test');
|
|
|
102
138
|
router.navigate({ url: './test', query: { a: '1' } });
|
|
103
139
|
```
|
|
104
140
|
|
|
105
|
-
|
|
141
|
+
##### navigate hooks
|
|
106
142
|
|
|
107
143
|
- beforeResolve
|
|
108
144
|
- beforeNavigate
|
|
109
145
|
- afterNavigate
|
|
110
146
|
|
|
147
|
+
##### navigate workflow
|
|
148
|
+
|
|
149
|
+
1. `beforeResolve` hook
|
|
150
|
+
2. [guards](#router-guards)
|
|
151
|
+
3. `beforeNavigate`
|
|
152
|
+
4. `change`
|
|
153
|
+
5. `afterNavigate`
|
|
154
|
+
|
|
111
155
|
#### updateCurrentRoute
|
|
112
156
|
|
|
113
157
|
The transition is based on the current route (therefore this method cannot be called on the server) and allows you to simply update some data for the current page
|
|
@@ -117,11 +161,17 @@ router.updateCurrentRoute({ params: { id: 'abc' } });
|
|
|
117
161
|
router.updateCurrentRoute({ query: { a: '1' } });
|
|
118
162
|
```
|
|
119
163
|
|
|
120
|
-
|
|
164
|
+
##### updateCurrentRoute hooks
|
|
121
165
|
|
|
122
166
|
- beforeUpdateCurrent
|
|
123
167
|
- afterUpdateCurrent
|
|
124
168
|
|
|
169
|
+
##### updateCurrentRoute workflow
|
|
170
|
+
|
|
171
|
+
1. `beforeUpdateCurrent`
|
|
172
|
+
2. `change`
|
|
173
|
+
3. `afterUpdateCurrent`
|
|
174
|
+
|
|
125
175
|
### Working with query
|
|
126
176
|
|
|
127
177
|
#### query option
|
|
@@ -161,6 +211,11 @@ router.updateCurrentRoute({ query: { a: undefined, c: 'c' }, preserveQuery: true
|
|
|
161
211
|
router.getCurrentUrl().query; // { b: 'b', c: 'c' }
|
|
162
212
|
```
|
|
163
213
|
|
|
214
|
+
### Constructor options
|
|
215
|
+
|
|
216
|
+
- `trailingSlash` - do router should force all urls to end with slash. If `true` - force trailing slash for every path, `false` - force no trailing slash, `undefined` - trailing slash is specified by request and both trailing and not trailing slashes are used. By default value if `undefined`
|
|
217
|
+
- `mergeSlashes` - replace several consecutive slashes by single slashes (slashes after protocol are still be with `//` after protocol name). By default is `false` - no merge for slashes.
|
|
218
|
+
|
|
164
219
|
### Integration with React
|
|
165
220
|
|
|
166
221
|
Library has some useful React hooks and components for working with routing
|
|
@@ -229,3 +284,26 @@ export const WrapLink = () => {
|
|
|
229
284
|
return <Link url="/test/">Click me</Link>;
|
|
230
285
|
};
|
|
231
286
|
```
|
|
287
|
+
|
|
288
|
+
## How to
|
|
289
|
+
|
|
290
|
+
### Load route config from external api
|
|
291
|
+
|
|
292
|
+
Use [transition hook](#transitions-hooks) `beforeResolve` and load routes config based on url.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
router.registerHook('beforeResolve', async (navigation) => {
|
|
296
|
+
const route = await routeResolve(navigation);
|
|
297
|
+
|
|
298
|
+
if (route) {
|
|
299
|
+
router.addRoute(routeTransform(route));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### App behind proxy
|
|
305
|
+
|
|
306
|
+
Router doesn't support proxy setup directly. But proxy still can be used with some limitations:
|
|
307
|
+
|
|
308
|
+
- setup proxy server to pass requests to app with rewriting request and response paths. (E.g. for [nginx](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect))
|
|
309
|
+
- it wont work as expected on spa navigation on client, so only option in this case is use the `NoSpaRouter`
|
package/lib/index.browser.js
CHANGED
|
@@ -220,17 +220,16 @@ class AbstractRouter {
|
|
|
220
220
|
await this.runHooks('afterUpdateCurrent', navigation);
|
|
221
221
|
}
|
|
222
222
|
async navigate(navigateOptions) {
|
|
223
|
-
return this.internalNavigate(navigateOptions, {});
|
|
223
|
+
return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
|
|
224
224
|
}
|
|
225
225
|
async internalNavigate(navigateOptions, { history }) {
|
|
226
226
|
var _a;
|
|
227
|
-
const
|
|
228
|
-
const { url, replace, params, navigateState, code } = options;
|
|
227
|
+
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
229
228
|
const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
230
229
|
if (!url && !prevNavigation) {
|
|
231
230
|
throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
|
|
232
231
|
}
|
|
233
|
-
const resolvedUrl = this.resolveUrl(
|
|
232
|
+
const resolvedUrl = this.resolveUrl(navigateOptions);
|
|
234
233
|
const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
|
|
235
234
|
let navigation = {
|
|
236
235
|
type: 'navigate',
|
package/lib/index.es.js
CHANGED
|
@@ -220,17 +220,16 @@ class AbstractRouter {
|
|
|
220
220
|
await this.runHooks('afterUpdateCurrent', navigation);
|
|
221
221
|
}
|
|
222
222
|
async navigate(navigateOptions) {
|
|
223
|
-
return this.internalNavigate(navigateOptions, {});
|
|
223
|
+
return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
|
|
224
224
|
}
|
|
225
225
|
async internalNavigate(navigateOptions, { history }) {
|
|
226
226
|
var _a;
|
|
227
|
-
const
|
|
228
|
-
const { url, replace, params, navigateState, code } = options;
|
|
227
|
+
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
229
228
|
const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
230
229
|
if (!url && !prevNavigation) {
|
|
231
230
|
throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
|
|
232
231
|
}
|
|
233
|
-
const resolvedUrl = this.resolveUrl(
|
|
232
|
+
const resolvedUrl = this.resolveUrl(navigateOptions);
|
|
234
233
|
const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
|
|
235
234
|
let navigation = {
|
|
236
235
|
type: 'navigate',
|
|
@@ -668,9 +667,11 @@ class RouteTree {
|
|
|
668
667
|
|
|
669
668
|
class Router extends AbstractRouter {
|
|
670
669
|
constructor(options) {
|
|
670
|
+
var _a;
|
|
671
671
|
super(options);
|
|
672
672
|
this.blocked = false;
|
|
673
673
|
this.tree = new RouteTree(options.routes);
|
|
674
|
+
this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
|
|
674
675
|
this.history = new ServerHistory();
|
|
675
676
|
}
|
|
676
677
|
async dehydrate() {
|
|
@@ -680,13 +681,16 @@ class Router extends AbstractRouter {
|
|
|
680
681
|
});
|
|
681
682
|
return this.lastNavigation;
|
|
682
683
|
}
|
|
683
|
-
async
|
|
684
|
-
if (this.redirectCode) {
|
|
685
|
-
return this.redirect(navigation, { url: navigation.url.path, code: this.redirectCode });
|
|
686
|
-
}
|
|
684
|
+
async internalNavigate(navigateOptions, internalOptions) {
|
|
687
685
|
// any navigation after initial should be considered as redirects
|
|
688
686
|
if (this.getCurrentRoute()) {
|
|
689
|
-
return this.redirect(
|
|
687
|
+
return this.redirect(this.lastNavigation, navigateOptions);
|
|
688
|
+
}
|
|
689
|
+
return super.internalNavigate(navigateOptions, internalOptions);
|
|
690
|
+
}
|
|
691
|
+
async run(navigation) {
|
|
692
|
+
if (this.redirectCode) {
|
|
693
|
+
return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
|
|
690
694
|
}
|
|
691
695
|
await super.run(navigation);
|
|
692
696
|
}
|
|
@@ -713,13 +717,13 @@ class Router extends AbstractRouter {
|
|
|
713
717
|
normalizePathname(pathname) {
|
|
714
718
|
const normalized = super.normalizePathname(pathname);
|
|
715
719
|
if (normalized !== pathname) {
|
|
716
|
-
this.redirectCode =
|
|
720
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
717
721
|
}
|
|
718
722
|
return normalized;
|
|
719
723
|
}
|
|
720
724
|
resolveUrl(options) {
|
|
721
725
|
if (options.url && isInvalidUrl(options.url)) {
|
|
722
|
-
this.redirectCode =
|
|
726
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
723
727
|
}
|
|
724
728
|
return super.resolveUrl(options);
|
|
725
729
|
}
|
package/lib/index.js
CHANGED
|
@@ -236,17 +236,16 @@ class AbstractRouter {
|
|
|
236
236
|
await this.runHooks('afterUpdateCurrent', navigation);
|
|
237
237
|
}
|
|
238
238
|
async navigate(navigateOptions) {
|
|
239
|
-
return this.internalNavigate(navigateOptions, {});
|
|
239
|
+
return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
|
|
240
240
|
}
|
|
241
241
|
async internalNavigate(navigateOptions, { history }) {
|
|
242
242
|
var _a;
|
|
243
|
-
const
|
|
244
|
-
const { url, replace, params, navigateState, code } = options;
|
|
243
|
+
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
245
244
|
const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
246
245
|
if (!url && !prevNavigation) {
|
|
247
246
|
throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
|
|
248
247
|
}
|
|
249
|
-
const resolvedUrl = this.resolveUrl(
|
|
248
|
+
const resolvedUrl = this.resolveUrl(navigateOptions);
|
|
250
249
|
const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
|
|
251
250
|
let navigation = {
|
|
252
251
|
type: 'navigate',
|
|
@@ -684,9 +683,11 @@ class RouteTree {
|
|
|
684
683
|
|
|
685
684
|
class Router extends AbstractRouter {
|
|
686
685
|
constructor(options) {
|
|
686
|
+
var _a;
|
|
687
687
|
super(options);
|
|
688
688
|
this.blocked = false;
|
|
689
689
|
this.tree = new RouteTree(options.routes);
|
|
690
|
+
this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
|
|
690
691
|
this.history = new ServerHistory();
|
|
691
692
|
}
|
|
692
693
|
async dehydrate() {
|
|
@@ -696,13 +697,16 @@ class Router extends AbstractRouter {
|
|
|
696
697
|
});
|
|
697
698
|
return this.lastNavigation;
|
|
698
699
|
}
|
|
699
|
-
async
|
|
700
|
-
if (this.redirectCode) {
|
|
701
|
-
return this.redirect(navigation, { url: navigation.url.path, code: this.redirectCode });
|
|
702
|
-
}
|
|
700
|
+
async internalNavigate(navigateOptions, internalOptions) {
|
|
703
701
|
// any navigation after initial should be considered as redirects
|
|
704
702
|
if (this.getCurrentRoute()) {
|
|
705
|
-
return this.redirect(
|
|
703
|
+
return this.redirect(this.lastNavigation, navigateOptions);
|
|
704
|
+
}
|
|
705
|
+
return super.internalNavigate(navigateOptions, internalOptions);
|
|
706
|
+
}
|
|
707
|
+
async run(navigation) {
|
|
708
|
+
if (this.redirectCode) {
|
|
709
|
+
return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
|
|
706
710
|
}
|
|
707
711
|
await super.run(navigation);
|
|
708
712
|
}
|
|
@@ -729,13 +733,13 @@ class Router extends AbstractRouter {
|
|
|
729
733
|
normalizePathname(pathname) {
|
|
730
734
|
const normalized = super.normalizePathname(pathname);
|
|
731
735
|
if (normalized !== pathname) {
|
|
732
|
-
this.redirectCode =
|
|
736
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
733
737
|
}
|
|
734
738
|
return normalized;
|
|
735
739
|
}
|
|
736
740
|
resolveUrl(options) {
|
|
737
741
|
if (options.url && url.isInvalidUrl(options.url)) {
|
|
738
|
-
this.redirectCode =
|
|
742
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
739
743
|
}
|
|
740
744
|
return super.resolveUrl(options);
|
|
741
745
|
}
|
package/lib/router/abstract.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface Options {
|
|
|
16
16
|
afterUpdateCurrent?: NavigationHook[];
|
|
17
17
|
guards?: NavigationGuard[];
|
|
18
18
|
onChange?: NavigationSyncHook[];
|
|
19
|
+
defaultRedirectCode?: number;
|
|
19
20
|
}
|
|
20
21
|
interface InternalOptions {
|
|
21
22
|
history?: boolean;
|
|
@@ -44,7 +45,7 @@ export declare abstract class AbstractRouter {
|
|
|
44
45
|
protected internalUpdateCurrentRoute(updateRouteOptions: UpdateCurrentRouteOptions, { history }: InternalOptions): Promise<void>;
|
|
45
46
|
protected runUpdateCurrentRoute(navigation: Navigation): Promise<void>;
|
|
46
47
|
navigate(navigateOptions: NavigateOptions | string): Promise<void>;
|
|
47
|
-
protected internalNavigate(navigateOptions: NavigateOptions
|
|
48
|
+
protected internalNavigate(navigateOptions: NavigateOptions, { history }: InternalOptions): Promise<void>;
|
|
48
49
|
protected runNavigate(navigation: Navigation): Promise<void>;
|
|
49
50
|
protected run(navigation: Navigation): Promise<void>;
|
|
50
51
|
resolve(resolveOptions: NavigateOptions | string, options?: Parameters<AbstractRouter['resolveRoute']>[1]): import("../types").NavigationRoute;
|
package/lib/router/server.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { Options } from './abstract';
|
|
2
2
|
import { AbstractRouter } from './abstract';
|
|
3
|
-
import type { Navigation, NavigateOptions, HookName
|
|
3
|
+
import type { Navigation, NavigateOptions, HookName } from '../types';
|
|
4
4
|
export declare class Router extends AbstractRouter {
|
|
5
|
+
protected defaultRedirectCode: number;
|
|
5
6
|
protected blocked: boolean;
|
|
6
7
|
protected redirectCode?: number;
|
|
7
|
-
constructor(options: Options
|
|
8
|
-
onRedirect: (navigation: Navigation) => Promise<void>;
|
|
9
|
-
onNotFound: NavigationHook;
|
|
10
|
-
onBlock: NavigationHook;
|
|
11
|
-
});
|
|
8
|
+
constructor(options: Options);
|
|
12
9
|
protected onRedirect: (navigation: Navigation) => Promise<void>;
|
|
13
10
|
dehydrate(): Promise<Navigation>;
|
|
11
|
+
protected internalNavigate(navigateOptions: NavigateOptions, internalOptions: any): Promise<void>;
|
|
14
12
|
protected run(navigation: Navigation): Promise<void>;
|
|
15
13
|
protected redirect(navigation: Navigation, target: NavigateOptions): Promise<void>;
|
|
16
14
|
protected notfound(navigation: Navigation): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinkoff/router",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.68",
|
|
4
4
|
"description": "router",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git@github.com:
|
|
17
|
+
"url": "git@github.com:Tinkoff/tramvai.git"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tramvai-build --for-publish",
|