@native-router/core 1.1.0 → 1.3.0
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 +101 -57
- package/dist/index.cjs +431 -63
- package/dist/index.mjs +428 -64
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/router.d.ts +93 -5
- package/dist/types/types.d.ts +86 -2
- package/package.json +7 -16
package/dist/types/router.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { History } from 'history';
|
|
2
2
|
import type { Location, Matched, Options, BaseRoute, RouterInstance, ResolveView } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A location resolved through the route guards, together with the view task
|
|
5
|
+
* of its final target. When guards redirected, `location` is the terminal
|
|
6
|
+
* location and `task` resolves the view of the target route.
|
|
7
|
+
*/
|
|
8
|
+
export type ResolvedEntry<V> = {
|
|
9
|
+
location: Location;
|
|
10
|
+
task: Promise<V>;
|
|
11
|
+
};
|
|
3
12
|
/**
|
|
4
13
|
* Create a router instance.
|
|
5
14
|
* @group Methods
|
|
@@ -23,7 +32,7 @@ export declare function setOptions<R extends BaseRoute = BaseRoute, V = any>(rou
|
|
|
23
32
|
currentGuard<T>(promise: Promise<T>): Promise<T>;
|
|
24
33
|
cancelAll(): void;
|
|
25
34
|
resolving?: Location;
|
|
26
|
-
} & Required<Pick<Options<V>, "baseUrl">> & Omit<Options<V>, "baseUrl"> & Omit<Options<V>, "currentView">;
|
|
35
|
+
} & Required<Pick<Options<V>, "baseUrl" | "maxStackDepth">> & Omit<Options<V>, "baseUrl" | "maxStackDepth"> & Omit<Options<V>, "currentView">;
|
|
27
36
|
export declare function getLocation({ history }: Pick<RouterInstance<any>, 'history'>): {
|
|
28
37
|
state: any;
|
|
29
38
|
key: import("history").Key;
|
|
@@ -70,6 +79,51 @@ export declare function resolve<R extends BaseRoute = BaseRoute, V = any>(router
|
|
|
70
79
|
* @returns resolve task(a promise)
|
|
71
80
|
*/
|
|
72
81
|
export declare function resolveTo<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, to: string, state?: any): Promise<V>;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a location through the route guards(`redirect`/`beforeLoad`).
|
|
84
|
+
*
|
|
85
|
+
* Guards run per matched level from the shallowest to the deepest. A guard
|
|
86
|
+
* returning a path string(redirect) restarts the resolution at the new
|
|
87
|
+
* location — from the shallowest level again, so guards of shallower
|
|
88
|
+
* levels re-run on every hop(keep side-effectful guards idempotent) —
|
|
89
|
+
* carrying the original user state; at most
|
|
90
|
+
* {@link MAX_REDIRECTS 10} redirects are followed before a
|
|
91
|
+
* {@link RedirectLoopError} is thrown. An unmatched pathname keeps the
|
|
92
|
+
* {@link resolve resolve} behavior: the task rejects with a
|
|
93
|
+
* {@link NotFoundError} and is routed through `router.errorHandler`.
|
|
94
|
+
*
|
|
95
|
+
* @group Methods
|
|
96
|
+
* @category Router
|
|
97
|
+
* @param router router instance
|
|
98
|
+
* @param location the location to resolve; the object itself is never
|
|
99
|
+
* mutated — a redirect rebinds the resolution to a new location
|
|
100
|
+
* @returns the terminal location and its resolve task
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveEntry<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, location: Location): Promise<ResolvedEntry<V>>;
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a target through the route guards(`redirect`/`beforeLoad`) and
|
|
105
|
+
* cache the result at the router level, keyed by `pathname + search`.
|
|
106
|
+
*
|
|
107
|
+
* Within its TTL(`opts.ttl`, default 30s) repeated and concurrent calls
|
|
108
|
+
* return the very same entry promise, so concurrent callers share one
|
|
109
|
+
* resolution(in-flight dedup) and repeated prefetches reuse the resolved
|
|
110
|
+
* view task instead of re-running guards and `resolveView`. A rejected
|
|
111
|
+
* resolution(guard error, redirect loop) is evicted from the cache, so
|
|
112
|
+
* the next call retries it. Committing a navigation({@link commit} or
|
|
113
|
+
* {@link commitReplace}) consumes the entry and evicts its cache slot —
|
|
114
|
+
* a later preload re-resolves fresh state, while callers still holding
|
|
115
|
+
* the old entry keep their references.
|
|
116
|
+
*
|
|
117
|
+
* @group Methods
|
|
118
|
+
* @category Router
|
|
119
|
+
* @param router router instance
|
|
120
|
+
* @param to path string
|
|
121
|
+
* @param opts options; `ttl` is the cache lifetime in milliseconds
|
|
122
|
+
* @returns the terminal location and its resolve task
|
|
123
|
+
*/
|
|
124
|
+
export declare function preload<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, to: string, opts?: {
|
|
125
|
+
ttl?: number;
|
|
126
|
+
}): Promise<ResolvedEntry<V>>;
|
|
73
127
|
/**
|
|
74
128
|
* Commit the resolve task and push history.
|
|
75
129
|
* @group Methods
|
|
@@ -89,7 +143,11 @@ export declare function commit<R extends BaseRoute = BaseRoute, V = any>(router:
|
|
|
89
143
|
*/
|
|
90
144
|
export declare function commitReplace<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, resolvePromise: Promise<V>, location: Location): Promise<void>;
|
|
91
145
|
/**
|
|
92
|
-
* Navigate to a new path.
|
|
146
|
+
* Navigate to a new path. Route guards(`redirect`/`beforeLoad`) run before
|
|
147
|
+
* the view resolves; the history entry is committed on the terminal
|
|
148
|
+
* location when guards redirected. The guard phase is part of the
|
|
149
|
+
* cancelable navigation: a superseding navigate or a `cancel()` while
|
|
150
|
+
* guards are still running discards this navigation.
|
|
93
151
|
* @group Methods
|
|
94
152
|
* @category Router
|
|
95
153
|
* @param router router instance
|
|
@@ -98,7 +156,8 @@ export declare function commitReplace<R extends BaseRoute = BaseRoute, V = any>(
|
|
|
98
156
|
*/
|
|
99
157
|
export declare function navigate<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, to: string, state?: any): Promise<void>;
|
|
100
158
|
/**
|
|
101
|
-
* Refresh the page.
|
|
159
|
+
* Refresh the page. Route guards run before the view resolves; a redirect
|
|
160
|
+
* replaces the current entry with the terminal location.
|
|
102
161
|
* @group Methods
|
|
103
162
|
* @category Router
|
|
104
163
|
* @param router router instance
|
|
@@ -141,7 +200,18 @@ export declare function createHref<R extends BaseRoute = BaseRoute, V = any>({ b
|
|
|
141
200
|
* @category Router
|
|
142
201
|
* @param router router instance
|
|
143
202
|
*/
|
|
144
|
-
export declare function cancel<R extends BaseRoute = BaseRoute, V = any>(
|
|
203
|
+
export declare function cancel<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>): void;
|
|
204
|
+
/**
|
|
205
|
+
* Restore/warm up the view stack by re-resolving every reachable entry
|
|
206
|
+
* of the in-memory location stack. Call it after a refresh: in-window
|
|
207
|
+
* back/forward then switch views without new resolves.
|
|
208
|
+
*
|
|
209
|
+
* 恢复/预热内存栈中的可达条目(窗口内有 location 的槽位),刷新后调用
|
|
210
|
+
* 可让窗口内前进/后退零请求。
|
|
211
|
+
* @group Methods
|
|
212
|
+
* @category Router
|
|
213
|
+
* @param router router instance
|
|
214
|
+
*/
|
|
145
215
|
export declare function initHistoryStack<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>): Promise<void>;
|
|
146
216
|
/**
|
|
147
217
|
* Listen the history change.
|
|
@@ -153,7 +223,25 @@ export declare function initHistoryStack<R extends BaseRoute = BaseRoute, V = an
|
|
|
153
223
|
*/
|
|
154
224
|
export declare function listen<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, onViewChange: (v: V) => void): () => void;
|
|
155
225
|
/**
|
|
156
|
-
*
|
|
226
|
+
* Merge params of the matched levels. Params of deeper levels override
|
|
227
|
+
* the same keys of shallower ones.
|
|
228
|
+
*
|
|
229
|
+
* When `end` is given, only the levels up to and including `end` are
|
|
230
|
+
* merged — the accumulated params a level at index `end` sees(shallow →
|
|
231
|
+
* current level). Omitting `end` merges every level.
|
|
232
|
+
* @group Methods
|
|
233
|
+
* @category Router
|
|
234
|
+
* @param matched matched route levels, see {@link match}
|
|
235
|
+
* @param end the index of the last level to merge, defaults to the deepest
|
|
236
|
+
* @returns the merged params object
|
|
237
|
+
*/
|
|
238
|
+
export declare function mergeMatchedParams<R extends BaseRoute = BaseRoute>(matched: Matched<R>[], end?: number): Record<string, string>;
|
|
239
|
+
/**
|
|
240
|
+
* Get current route params from router. The params are re-derived by
|
|
241
|
+
* matching the current entry of {@link RouterInstance.locationStack} so
|
|
242
|
+
* they stay correct even when the view stack holds resolved views
|
|
243
|
+
* (e.g. React elements) instead of match results. Merges params of all
|
|
244
|
+
* matched levels; deeper levels override shallower ones.
|
|
157
245
|
* @group Methods
|
|
158
246
|
* @category Router
|
|
159
247
|
* @param router router instance
|
package/dist/types/types.d.ts
CHANGED
|
@@ -4,15 +4,77 @@ export type Location<T = any> = HPath & {
|
|
|
4
4
|
state?: T;
|
|
5
5
|
};
|
|
6
6
|
export type HistoryState = {
|
|
7
|
-
locationStack: Location[];
|
|
8
7
|
index: number;
|
|
9
8
|
state?: any;
|
|
9
|
+
/**
|
|
10
|
+
* Bounded window(tail entries) of the session location stack,
|
|
11
|
+
* serialized into the history entry so {@link create} can restore it
|
|
12
|
+
* after a refresh. Its length never exceeds `maxStackDepth`.
|
|
13
|
+
* 有界窗口:会话 locationStack 的尾部条目,刷新后据此恢复内存栈。
|
|
14
|
+
*/
|
|
15
|
+
locationStack?: Location[];
|
|
16
|
+
/**
|
|
17
|
+
* Absolute stack index of `locationStack[0]`; omitted(or 0) means the
|
|
18
|
+
* window starts at the session root.
|
|
19
|
+
* locationStack[0] 对应的绝对 index,省略时为 0。
|
|
20
|
+
*/
|
|
21
|
+
base?: number;
|
|
10
22
|
};
|
|
11
23
|
export type WrappedLocation = Location<HistoryState>;
|
|
12
24
|
export type Awaitable<T> = T | Promise<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Params contributed by a single path segment: `:name` is required,
|
|
27
|
+
* `:name?` is optional, anything else(static or wildcard) contributes
|
|
28
|
+
* nothing.
|
|
29
|
+
*
|
|
30
|
+
* Only the segment-exact forms of the path-to-regexp 6 syntax are
|
|
31
|
+
* modeled. Prefix/suffix params(`/page-:id`), repetitions(`:id*`,
|
|
32
|
+
* `:id+`) and custom regexes(`:id(\\d+)`) are matched at runtime but
|
|
33
|
+
* not modeled here — they simply contribute no keys.
|
|
34
|
+
* @group Types
|
|
35
|
+
* @category Route
|
|
36
|
+
*/
|
|
37
|
+
export type PathParamsOf<Seg extends string> = Seg extends `:${infer Name}?` ? {
|
|
38
|
+
[K in Name & string]?: string;
|
|
39
|
+
} : Seg extends `:${infer Name}` ? {
|
|
40
|
+
[K in Name & string]: string;
|
|
41
|
+
} : {};
|
|
42
|
+
/**
|
|
43
|
+
* Extract the params shape of a route path pattern. Splits the pattern
|
|
44
|
+
* into `/`-separated segments and intersects the params of each, e.g.
|
|
45
|
+
* `ExtractPathParams<'/users/:id/posts/:postId?'>` is
|
|
46
|
+
* `{id: string} & {postId?: string}`.
|
|
47
|
+
*
|
|
48
|
+
* Within the modeled path-to-regexp 6 syntax scope(see
|
|
49
|
+
* {@link PathParamsOf}); wildcards(`*`) and static segments are
|
|
50
|
+
* ignored. Distributes over unions of patterns.
|
|
51
|
+
* @group Types
|
|
52
|
+
* @category Route
|
|
53
|
+
*/
|
|
54
|
+
export type ExtractPathParams<P extends string> = P extends `${infer Head}/${infer Rest}` ? PathParamsOf<Head> & ExtractPathParams<Rest> : PathParamsOf<P>;
|
|
55
|
+
/**
|
|
56
|
+
* Context passed to a route guard({@link BaseRoute.beforeLoad beforeLoad}).
|
|
57
|
+
* `params` are accumulated from the root level down to the level that
|
|
58
|
+
* owns the guard, so a guard only sees params of itself and its parents.
|
|
59
|
+
*/
|
|
60
|
+
export type GuardContext<R extends BaseRoute = BaseRoute> = {
|
|
61
|
+
router: RouterInstance<R>;
|
|
62
|
+
location: Location;
|
|
63
|
+
params: Record<string, string>;
|
|
64
|
+
};
|
|
13
65
|
export type BaseRoute<T = any> = {
|
|
14
66
|
path?: Path;
|
|
15
67
|
children?: BaseRoute<T>[];
|
|
68
|
+
/**
|
|
69
|
+
* Static redirect target. When set, navigating to this route is
|
|
70
|
+
* redirected to the target path before the view resolves.
|
|
71
|
+
*/
|
|
72
|
+
redirect?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Route guard invoked before the view resolves. Return a path string
|
|
75
|
+
* to redirect, or nothing(`undefined`) to continue.
|
|
76
|
+
*/
|
|
77
|
+
beforeLoad?(ctx: GuardContext<BaseRoute<T>>): Awaitable<string | void>;
|
|
16
78
|
} & Omit<T, 'path' | 'children'>;
|
|
17
79
|
export type Matched<R extends BaseRoute = BaseRoute> = {
|
|
18
80
|
route: R;
|
|
@@ -27,6 +89,22 @@ export type Options<V> = {
|
|
|
27
89
|
currentView?: V;
|
|
28
90
|
errorHandler?(e: Error): V | Promise<V>;
|
|
29
91
|
onLoadingChange?(status?: 'pending' | 'resolved' | 'rejected'): void;
|
|
92
|
+
/**
|
|
93
|
+
* Max number of locations serialized into the history state window,
|
|
94
|
+
* see {@link HistoryState.locationStack}.
|
|
95
|
+
*
|
|
96
|
+
* Defaults to 100, at or above the per-tab history caps of mainstream
|
|
97
|
+
* browsers(Chromium/Gecko ~50, WebKit ~100). The window boundary can
|
|
98
|
+
* therefore only appear after the browser itself has already evicted
|
|
99
|
+
* entries: anything outside the window is unreachable to the user, so
|
|
100
|
+
* a bounded window is observationally equivalent to full serialization.
|
|
101
|
+
*
|
|
102
|
+
* 序列化进 history state 的栈窗口上限,默认 100,不低于主流浏览器
|
|
103
|
+
* 单标签历史上限(Chromium/Gecko 约 50、WebKit 约 100)。窗口边界只会在
|
|
104
|
+
* 浏览器自身裁剪历史之后才可能出现,用户不可达窗口外条目,行为与
|
|
105
|
+
* 全量序列化无可观察差异。
|
|
106
|
+
*/
|
|
107
|
+
maxStackDepth?: number;
|
|
30
108
|
};
|
|
31
109
|
export type RequiredOf<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
|
|
32
110
|
export type RouterInstance<R extends BaseRoute, V = any> = {
|
|
@@ -36,9 +114,15 @@ export type RouterInstance<R extends BaseRoute, V = any> = {
|
|
|
36
114
|
location: WrappedLocation;
|
|
37
115
|
};
|
|
38
116
|
viewStack: V[];
|
|
117
|
+
/**
|
|
118
|
+
* In-memory location stack of the current session. On creation it is
|
|
119
|
+
* restored from the bounded window serialized in the history state,
|
|
120
|
+
* so in-window back/forward survive a refresh.
|
|
121
|
+
* 会话内存栈;create 时从 history state 的有界窗口恢复。
|
|
122
|
+
*/
|
|
39
123
|
locationStack: Location[];
|
|
40
124
|
resolveView: ResolveView<R, V>;
|
|
41
125
|
currentGuard<T>(promise: Promise<T>): Promise<T>;
|
|
42
126
|
cancelAll(): void;
|
|
43
127
|
resolving?: Location;
|
|
44
|
-
} & RequiredOf<Options<V>, 'baseUrl'>;
|
|
128
|
+
} & RequiredOf<Options<V>, 'baseUrl' | 'maxStackDepth'>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-router/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"start": "
|
|
30
|
+
"start": "vitest",
|
|
31
31
|
"build": "rm -rf dist && rollup -c && tsc -p tsconfig.production.json && tsc-alias -p tsconfig.production.json",
|
|
32
32
|
"commit": "lint-staged && git-cz -n",
|
|
33
|
-
"coverage": "
|
|
33
|
+
"coverage": "vitest run --coverage",
|
|
34
34
|
"lint": "eslint --fix src test *.js --ext .js,.jsx,.ts,.tsx",
|
|
35
35
|
"doc:gen": "typedoc",
|
|
36
36
|
"deploy": "npm run doc:gen && gh-pages -d dist",
|
|
37
|
-
"test": "
|
|
37
|
+
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"homepage": "https://github.com/native-router/core",
|
|
50
50
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
51
|
+
"node": ">=18"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"history": "^5.3.0",
|
|
@@ -58,22 +58,17 @@
|
|
|
58
58
|
"@babel/core": "^7.22.9",
|
|
59
59
|
"@babel/preset-env": "^7.22.9",
|
|
60
60
|
"@babel/preset-typescript": "^7.22.5",
|
|
61
|
-
"@babel/register": "^7.22.5",
|
|
62
|
-
"@linaria/babel-preset": "^4.5.4",
|
|
63
|
-
"@linaria/core": "^4.5.4",
|
|
64
61
|
"@rollup/plugin-babel": "^6.0.3",
|
|
65
62
|
"@rollup/plugin-commonjs": "^25.0.4",
|
|
66
63
|
"@rollup/plugin-node-resolve": "^15.1.0",
|
|
67
64
|
"@rollup/plugin-replace": "^5.0.2",
|
|
68
|
-
"@types/mocha": "^10.0.1",
|
|
69
65
|
"@types/node": "^20.4.5",
|
|
70
66
|
"@types/sinon": "^10.0.15",
|
|
71
67
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
72
68
|
"@typescript-eslint/parser": "^6.2.0",
|
|
73
|
-
"
|
|
69
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
74
70
|
"commitizen": "^4.3.0",
|
|
75
71
|
"core-js": "^3.31.1",
|
|
76
|
-
"coveralls": "^3.1.1",
|
|
77
72
|
"cross-env": "^7.0.3",
|
|
78
73
|
"eslint": "^8.50.0",
|
|
79
74
|
"eslint-config-airbnb": "^19.0.4",
|
|
@@ -83,17 +78,12 @@
|
|
|
83
78
|
"eslint-plugin-compat": "^4.1.4",
|
|
84
79
|
"eslint-plugin-import": "^2.27.5",
|
|
85
80
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
86
|
-
"eslint-plugin-mocha": "^10.1.0",
|
|
87
81
|
"eslint-plugin-prettier": "^5.0.0",
|
|
88
82
|
"eslint-plugin-react": "^7.33.0",
|
|
89
83
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
90
84
|
"gh-pages": "^5.0.0",
|
|
91
|
-
"global-jsdom": "^9.0.1",
|
|
92
85
|
"husky": "^8.0.3",
|
|
93
|
-
"jsdom": "^22.1.0",
|
|
94
86
|
"lint-staged": "^13.2.3",
|
|
95
|
-
"mocha": "^10.2.0",
|
|
96
|
-
"nyc": "^15.1.0",
|
|
97
87
|
"prettier": "^3.0.0",
|
|
98
88
|
"rollup": "^3.28.0",
|
|
99
89
|
"should": "^13.2.3",
|
|
@@ -105,6 +95,7 @@
|
|
|
105
95
|
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
106
96
|
"typedoc-plugin-missing-exports": "^2.0.0",
|
|
107
97
|
"typescript": "^5.9.3",
|
|
98
|
+
"vitest": "^4.0.18",
|
|
108
99
|
"semantic-release": "^25.0.3"
|
|
109
100
|
}
|
|
110
101
|
}
|