@esmx/router 3.0.0-rc.18 → 3.0.0-rc.20
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/LICENSE +1 -1
- package/README.md +70 -0
- package/README.zh-CN.md +70 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +61 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +14 -3
- package/dist/index.test.mjs +8 -0
- package/dist/location.d.ts +15 -0
- package/dist/location.mjs +53 -0
- package/dist/location.test.d.ts +8 -0
- package/dist/location.test.mjs +370 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/matcher.mjs +44 -0
- package/dist/matcher.test.mjs +1492 -0
- package/dist/micro-app.d.ts +18 -0
- package/dist/micro-app.dom.test.d.ts +1 -0
- package/dist/micro-app.dom.test.mjs +532 -0
- package/dist/micro-app.mjs +80 -0
- package/dist/navigation.d.ts +43 -0
- package/dist/navigation.mjs +143 -0
- package/dist/navigation.test.d.ts +1 -0
- package/dist/navigation.test.mjs +681 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +88 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +75 -0
- package/dist/route-task.test.d.ts +1 -0
- package/dist/route-task.test.mjs +673 -0
- package/dist/route-transition.d.ts +53 -0
- package/dist/route-transition.mjs +307 -0
- package/dist/route-transition.test.d.ts +1 -0
- package/dist/route-transition.test.mjs +146 -0
- package/dist/route.d.ts +72 -0
- package/dist/route.mjs +194 -0
- package/dist/route.test.d.ts +1 -0
- package/dist/route.test.mjs +1664 -0
- package/dist/router-back.test.d.ts +1 -0
- package/dist/router-back.test.mjs +361 -0
- package/dist/router-forward.test.d.ts +1 -0
- package/dist/router-forward.test.mjs +376 -0
- package/dist/router-go.test.d.ts +1 -0
- package/dist/router-go.test.mjs +73 -0
- package/dist/router-guards-cleanup.test.d.ts +1 -0
- package/dist/router-guards-cleanup.test.mjs +437 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +126 -0
- package/dist/router-push.test.d.ts +1 -0
- package/dist/router-push.test.mjs +115 -0
- package/dist/router-replace.test.d.ts +1 -0
- package/dist/router-replace.test.mjs +114 -0
- package/dist/router-resolve.test.d.ts +1 -0
- package/dist/router-resolve.test.mjs +393 -0
- package/dist/router-restart-app.dom.test.d.ts +1 -0
- package/dist/router-restart-app.dom.test.mjs +616 -0
- package/dist/router-window-navigation.test.d.ts +1 -0
- package/dist/router-window-navigation.test.mjs +359 -0
- package/dist/router.d.ts +109 -102
- package/dist/router.mjs +260 -361
- package/dist/types.d.ts +246 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.mjs +53 -0
- package/dist/util.test.d.ts +1 -0
- package/dist/util.test.mjs +1020 -0
- package/package.json +10 -13
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.test.ts +9 -0
- package/src/index.ts +54 -3
- package/src/location.test.ts +406 -0
- package/src/location.ts +96 -0
- package/src/matcher.test.ts +1685 -0
- package/src/matcher.ts +59 -0
- package/src/micro-app.dom.test.ts +708 -0
- package/src/micro-app.ts +101 -0
- package/src/navigation.test.ts +858 -0
- package/src/navigation.ts +195 -0
- package/src/options.ts +131 -0
- package/src/route-task.test.ts +901 -0
- package/src/route-task.ts +105 -0
- package/src/route-transition.test.ts +178 -0
- package/src/route-transition.ts +425 -0
- package/src/route.test.ts +2014 -0
- package/src/route.ts +308 -0
- package/src/router-back.test.ts +487 -0
- package/src/router-forward.test.ts +506 -0
- package/src/router-go.test.ts +91 -0
- package/src/router-guards-cleanup.test.ts +595 -0
- package/src/router-link.ts +235 -0
- package/src/router-push.test.ts +140 -0
- package/src/router-replace.test.ts +139 -0
- package/src/router-resolve.test.ts +475 -0
- package/src/router-restart-app.dom.test.ts +783 -0
- package/src/router-window-navigation.test.ts +457 -0
- package/src/router.ts +289 -470
- package/src/types.ts +341 -0
- package/src/util.test.ts +1262 -0
- package/src/util.ts +116 -0
- package/dist/history/abstract.d.ts +0 -29
- package/dist/history/abstract.mjs +0 -107
- package/dist/history/base.d.ts +0 -79
- package/dist/history/base.mjs +0 -275
- package/dist/history/html.d.ts +0 -30
- package/dist/history/html.mjs +0 -183
- package/dist/history/index.d.ts +0 -7
- package/dist/history/index.mjs +0 -16
- package/dist/matcher/create-matcher.d.ts +0 -5
- package/dist/matcher/create-matcher.mjs +0 -218
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +0 -1
- package/dist/matcher/index.mjs +0 -1
- package/dist/task-pipe/index.d.ts +0 -1
- package/dist/task-pipe/index.mjs +0 -1
- package/dist/task-pipe/task.d.ts +0 -30
- package/dist/task-pipe/task.mjs +0 -66
- package/dist/types/index.d.ts +0 -694
- package/dist/types/index.mjs +0 -6
- package/dist/utils/bom.d.ts +0 -5
- package/dist/utils/bom.mjs +0 -10
- package/dist/utils/encoding.d.ts +0 -48
- package/dist/utils/encoding.mjs +0 -44
- package/dist/utils/guards.d.ts +0 -9
- package/dist/utils/guards.mjs +0 -12
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.mjs +0 -27
- package/dist/utils/path.d.ts +0 -60
- package/dist/utils/path.mjs +0 -282
- package/dist/utils/path.spec.mjs +0 -27
- package/dist/utils/scroll.d.ts +0 -25
- package/dist/utils/scroll.mjs +0 -59
- package/dist/utils/utils.d.ts +0 -16
- package/dist/utils/utils.mjs +0 -11
- package/dist/utils/warn.d.ts +0 -2
- package/dist/utils/warn.mjs +0 -12
- package/src/history/abstract.ts +0 -149
- package/src/history/base.ts +0 -408
- package/src/history/html.ts +0 -228
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -292
- package/src/matcher/index.ts +0 -1
- package/src/task-pipe/index.ts +0 -1
- package/src/task-pipe/task.ts +0 -97
- package/src/types/index.ts +0 -858
- package/src/utils/bom.ts +0 -14
- package/src/utils/encoding.ts +0 -153
- package/src/utils/guards.ts +0 -25
- package/src/utils/index.ts +0 -27
- package/src/utils/path.spec.ts +0 -32
- package/src/utils/path.ts +0 -418
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
- /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
- /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Route } from './route';
|
|
2
|
+
import type { Router } from './router';
|
|
3
|
+
import { RouteType } from './types';
|
|
4
|
+
import type { RouteConfirmHook, RouteConfirmHookResult, RouteLocationInput, RouteNotifyHook } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Route transition hooks responsible for handling different stages of the navigation process.
|
|
7
|
+
* Each hook is responsible for a specific aspect of route transition.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ROUTE_TRANSITION_HOOKS: {
|
|
10
|
+
fallback(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
11
|
+
override(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
12
|
+
asyncComponent(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
13
|
+
beforeLeave(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
14
|
+
beforeEnter(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
15
|
+
beforeUpdate(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
16
|
+
beforeEach(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
17
|
+
confirm(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Route type handlers configuration.
|
|
21
|
+
* Maps each route type to its corresponding navigation handler function.
|
|
22
|
+
* These handlers perform the actual navigation operations like updating browser state,
|
|
23
|
+
* managing micro-app updates, and handling different navigation patterns.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ROUTE_TYPE_HANDLERS: {
|
|
26
|
+
push(to: Route, from: Route | null, router: Router): void;
|
|
27
|
+
replace(to: Route, from: Route | null, router: Router): void;
|
|
28
|
+
restartApp(to: Route, from: Route | null, router: Router): void;
|
|
29
|
+
pushWindow(to: Route, from: Route | null, router: Router): unknown;
|
|
30
|
+
replaceWindow(to: Route, from: Route | null, router: Router): unknown;
|
|
31
|
+
pushLayer(to: Route, from: Route | null, router: Router): Promise<import("./types").RouteLayerResult>;
|
|
32
|
+
default(to: Route, from: Route | null, router: Router): void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Route Transition Manager
|
|
36
|
+
* Responsible for managing all route transition logic, including guard execution,
|
|
37
|
+
* task processing, and status updates.
|
|
38
|
+
*/
|
|
39
|
+
export declare class RouteTransition {
|
|
40
|
+
private readonly router;
|
|
41
|
+
route: Route | null;
|
|
42
|
+
private _controller;
|
|
43
|
+
readonly guards: {
|
|
44
|
+
beforeEach: RouteConfirmHook[];
|
|
45
|
+
afterEach: RouteNotifyHook[];
|
|
46
|
+
};
|
|
47
|
+
constructor(router: Router);
|
|
48
|
+
beforeEach(guard: RouteConfirmHook): () => void;
|
|
49
|
+
afterEach(guard: RouteNotifyHook): () => void;
|
|
50
|
+
destroy(): void;
|
|
51
|
+
to(toType: RouteType, toInput: RouteLocationInput): Promise<Route>;
|
|
52
|
+
private _runTask;
|
|
53
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { Route } from "./route.mjs";
|
|
2
|
+
import {
|
|
3
|
+
RouteTaskController,
|
|
4
|
+
createRouteTask
|
|
5
|
+
} from "./route-task.mjs";
|
|
6
|
+
import { RouteType } from "./types.mjs";
|
|
7
|
+
import {
|
|
8
|
+
isRouteMatched,
|
|
9
|
+
isUrlEqual,
|
|
10
|
+
isValidConfirmHookResult,
|
|
11
|
+
removeFromArray
|
|
12
|
+
} from "./util.mjs";
|
|
13
|
+
export const ROUTE_TRANSITION_HOOKS = {
|
|
14
|
+
async fallback(to, from, router) {
|
|
15
|
+
if (to.matched.length === 0) {
|
|
16
|
+
return router.parsedOptions.fallback;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
async override(to, from, router) {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
const result = await ((_b = (_a = to.config) == null ? void 0 : _a.override) == null ? void 0 : _b.call(_a, to, from, router));
|
|
22
|
+
if (isValidConfirmHookResult(result)) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async asyncComponent(to, from, router) {
|
|
27
|
+
await Promise.all(
|
|
28
|
+
to.matched.map(async (matched) => {
|
|
29
|
+
const { asyncComponent, component } = matched;
|
|
30
|
+
if (!component && typeof asyncComponent === "function") {
|
|
31
|
+
try {
|
|
32
|
+
const result = await asyncComponent();
|
|
33
|
+
matched.component = result;
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Async component '${matched.compilePath}' is not a valid component.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
async beforeLeave(to, from, router) {
|
|
44
|
+
if (!(from == null ? void 0 : from.matched.length)) return;
|
|
45
|
+
const leavingRoutes = from.matched.filter(
|
|
46
|
+
(fromRoute) => !to.matched.some((toRoute) => toRoute === fromRoute)
|
|
47
|
+
);
|
|
48
|
+
for (let i = leavingRoutes.length - 1; i >= 0; i--) {
|
|
49
|
+
const route = leavingRoutes[i];
|
|
50
|
+
if (route.beforeLeave) {
|
|
51
|
+
const result = await route.beforeLeave(to, from, router);
|
|
52
|
+
if (isValidConfirmHookResult(result)) {
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async beforeEnter(to, from, router) {
|
|
59
|
+
if (!to.matched.length) return;
|
|
60
|
+
const enteringRoutes = to.matched.filter(
|
|
61
|
+
(toRoute) => !(from == null ? void 0 : from.matched.some((fromRoute) => fromRoute === toRoute))
|
|
62
|
+
);
|
|
63
|
+
for (const route of enteringRoutes) {
|
|
64
|
+
if (route.beforeEnter) {
|
|
65
|
+
const result = await route.beforeEnter(to, from, router);
|
|
66
|
+
if (isValidConfirmHookResult(result)) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
async beforeUpdate(to, from, router) {
|
|
73
|
+
if (!isRouteMatched(to, from, "route")) return;
|
|
74
|
+
if (!from || to.matched.length !== from.matched.length) return;
|
|
75
|
+
const isSameRouteSet = to.matched.every(
|
|
76
|
+
(toRoute, index) => toRoute === from.matched[index]
|
|
77
|
+
);
|
|
78
|
+
if (!isSameRouteSet) return;
|
|
79
|
+
if (!isRouteMatched(to, from, "exact")) {
|
|
80
|
+
for (const route of to.matched) {
|
|
81
|
+
if (route.beforeUpdate) {
|
|
82
|
+
const result = await route.beforeUpdate(to, from, router);
|
|
83
|
+
if (isValidConfirmHookResult(result)) {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async beforeEach(to, from, router) {
|
|
91
|
+
const transition = router.transition;
|
|
92
|
+
for (const guard of transition.guards.beforeEach) {
|
|
93
|
+
const result = await guard(to, from, router);
|
|
94
|
+
if (isValidConfirmHookResult(result)) {
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
async confirm(to, from, router) {
|
|
100
|
+
if (to.confirm) {
|
|
101
|
+
const result = await to.confirm(to, from, router);
|
|
102
|
+
if (isValidConfirmHookResult(result)) {
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
switch (to.type) {
|
|
107
|
+
case RouteType.push:
|
|
108
|
+
return ROUTE_TYPE_HANDLERS.push;
|
|
109
|
+
case RouteType.replace:
|
|
110
|
+
return ROUTE_TYPE_HANDLERS.replace;
|
|
111
|
+
case RouteType.restartApp:
|
|
112
|
+
return ROUTE_TYPE_HANDLERS.restartApp;
|
|
113
|
+
case RouteType.pushWindow:
|
|
114
|
+
return ROUTE_TYPE_HANDLERS.pushWindow;
|
|
115
|
+
case RouteType.replaceWindow:
|
|
116
|
+
return ROUTE_TYPE_HANDLERS.replaceWindow;
|
|
117
|
+
case RouteType.pushLayer:
|
|
118
|
+
return ROUTE_TYPE_HANDLERS.pushLayer;
|
|
119
|
+
default:
|
|
120
|
+
return ROUTE_TYPE_HANDLERS.default;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
export const ROUTE_TYPE_HANDLERS = {
|
|
125
|
+
push(to, from, router) {
|
|
126
|
+
router.transition.route = to;
|
|
127
|
+
router.microApp._update(router);
|
|
128
|
+
if (!isUrlEqual(to.url, from == null ? void 0 : from.url)) {
|
|
129
|
+
const newState = router.navigation.push(to.state, to.url);
|
|
130
|
+
to.applyNavigationState(newState);
|
|
131
|
+
} else {
|
|
132
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
133
|
+
to.applyNavigationState(newState);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
replace(to, from, router) {
|
|
137
|
+
router.transition.route = to;
|
|
138
|
+
router.microApp._update(router);
|
|
139
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
140
|
+
to.applyNavigationState(newState);
|
|
141
|
+
},
|
|
142
|
+
restartApp(to, from, router) {
|
|
143
|
+
router.transition.route = to;
|
|
144
|
+
router.microApp._update(router, true);
|
|
145
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
146
|
+
to.applyNavigationState(newState);
|
|
147
|
+
},
|
|
148
|
+
pushWindow(to, from, router) {
|
|
149
|
+
return router.parsedOptions.fallback(to, from, router);
|
|
150
|
+
},
|
|
151
|
+
replaceWindow(to, from, router) {
|
|
152
|
+
return router.parsedOptions.fallback(to, from, router);
|
|
153
|
+
},
|
|
154
|
+
async pushLayer(to, from, router) {
|
|
155
|
+
const { promise } = await router.createLayer(to);
|
|
156
|
+
return promise;
|
|
157
|
+
},
|
|
158
|
+
default(to, from, router) {
|
|
159
|
+
router.transition.route = to;
|
|
160
|
+
router.microApp._update(router);
|
|
161
|
+
if (!isUrlEqual(to.url, from == null ? void 0 : from.url)) {
|
|
162
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
163
|
+
to.applyNavigationState(newState);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const ROUTE_TRANSITION_PIPELINE = {
|
|
168
|
+
[RouteType.push]: [
|
|
169
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
170
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
171
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
172
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
173
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
174
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
175
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
176
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
177
|
+
],
|
|
178
|
+
[RouteType.replace]: [
|
|
179
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
180
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
181
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
182
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
183
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
184
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
185
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
186
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
187
|
+
],
|
|
188
|
+
[RouteType.pushWindow]: [
|
|
189
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
190
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
191
|
+
// ROUTE_TRANSITION_HOOKS.beforeLeave
|
|
192
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
193
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
194
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
195
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
196
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
197
|
+
],
|
|
198
|
+
[RouteType.replaceWindow]: [
|
|
199
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
200
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
201
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
202
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
203
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
204
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
205
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
206
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
207
|
+
],
|
|
208
|
+
[RouteType.pushLayer]: [
|
|
209
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
210
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
211
|
+
// ROUTE_TRANSITION_HOOKS.beforeLeave
|
|
212
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
213
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
214
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
215
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
216
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
217
|
+
],
|
|
218
|
+
[RouteType.restartApp]: [
|
|
219
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
220
|
+
// ROUTE_TRANSITION_HOOKS.override,
|
|
221
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
222
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
223
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
224
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
225
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
226
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
227
|
+
],
|
|
228
|
+
[RouteType.unknown]: [
|
|
229
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
230
|
+
// ROUTE_TRANSITION_HOOKS.override,
|
|
231
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
232
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
233
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
234
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
235
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
236
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
237
|
+
]
|
|
238
|
+
};
|
|
239
|
+
export class RouteTransition {
|
|
240
|
+
router;
|
|
241
|
+
route = null;
|
|
242
|
+
// Task controller for the current transition.
|
|
243
|
+
_controller = null;
|
|
244
|
+
// Guard arrays, responsible for storing navigation guards.
|
|
245
|
+
guards = {
|
|
246
|
+
beforeEach: [],
|
|
247
|
+
afterEach: []
|
|
248
|
+
};
|
|
249
|
+
constructor(router) {
|
|
250
|
+
this.router = router;
|
|
251
|
+
}
|
|
252
|
+
beforeEach(guard) {
|
|
253
|
+
this.guards.beforeEach.push(guard);
|
|
254
|
+
return () => {
|
|
255
|
+
removeFromArray(this.guards.beforeEach, guard);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
afterEach(guard) {
|
|
259
|
+
this.guards.afterEach.push(guard);
|
|
260
|
+
return () => {
|
|
261
|
+
removeFromArray(this.guards.afterEach, guard);
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
destroy() {
|
|
265
|
+
var _a;
|
|
266
|
+
(_a = this._controller) == null ? void 0 : _a.abort();
|
|
267
|
+
this._controller = null;
|
|
268
|
+
}
|
|
269
|
+
async to(toType, toInput) {
|
|
270
|
+
const from = this.route;
|
|
271
|
+
const to = await this._runTask(
|
|
272
|
+
new Route({
|
|
273
|
+
options: this.router.parsedOptions,
|
|
274
|
+
toType,
|
|
275
|
+
toInput,
|
|
276
|
+
from: (from == null ? void 0 : from.url) ?? null
|
|
277
|
+
}),
|
|
278
|
+
from
|
|
279
|
+
);
|
|
280
|
+
if (typeof to.handle === "function") {
|
|
281
|
+
to.handleResult = await to.handle(to, from, this.router);
|
|
282
|
+
}
|
|
283
|
+
if (to.handle) {
|
|
284
|
+
for (const guard of this.guards.afterEach) {
|
|
285
|
+
guard(to, from, this.router);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return to;
|
|
289
|
+
}
|
|
290
|
+
async _runTask(to, from) {
|
|
291
|
+
var _a;
|
|
292
|
+
(_a = this._controller) == null ? void 0 : _a.abort();
|
|
293
|
+
this._controller = new RouteTaskController();
|
|
294
|
+
const taskFunctions = ROUTE_TRANSITION_PIPELINE[to.type] || ROUTE_TRANSITION_PIPELINE[RouteType.unknown];
|
|
295
|
+
const tasks = taskFunctions.map((taskFn) => ({
|
|
296
|
+
name: taskFn.name,
|
|
297
|
+
task: taskFn
|
|
298
|
+
}));
|
|
299
|
+
return createRouteTask({
|
|
300
|
+
to,
|
|
301
|
+
from,
|
|
302
|
+
tasks,
|
|
303
|
+
controller: this._controller,
|
|
304
|
+
router: this.router
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { Router } from "./router.mjs";
|
|
3
|
+
import { RouteType, RouterMode } from "./types.mjs";
|
|
4
|
+
describe("Route Transition Tests", () => {
|
|
5
|
+
let router;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
router = new Router({
|
|
8
|
+
mode: RouterMode.memory,
|
|
9
|
+
base: new URL("http://localhost:3000/"),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: "/", component: () => "Home" },
|
|
12
|
+
{ path: "/user/:id", component: () => "User" },
|
|
13
|
+
{ path: "/about", component: () => "About" },
|
|
14
|
+
{
|
|
15
|
+
path: "/async",
|
|
16
|
+
asyncComponent: () => Promise.resolve("Async")
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
await router.replace("/");
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
router.destroy();
|
|
24
|
+
});
|
|
25
|
+
describe("Basic transitions", () => {
|
|
26
|
+
test("should successfully transition to new route", async () => {
|
|
27
|
+
const route = await router.push("/user/123");
|
|
28
|
+
expect(route.path).toBe("/user/123");
|
|
29
|
+
expect(route.params.id).toBe("123");
|
|
30
|
+
expect(route.handle).not.toBe(null);
|
|
31
|
+
});
|
|
32
|
+
test("should handle async component loading", async () => {
|
|
33
|
+
const route = await router.push("/async");
|
|
34
|
+
expect(route.path).toBe("/async");
|
|
35
|
+
expect(route.handle).not.toBe(null);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("Error handling", () => {
|
|
39
|
+
test("should handle non-existent routes", async () => {
|
|
40
|
+
const route = await router.push("/non-existent");
|
|
41
|
+
expect(route.matched).toHaveLength(0);
|
|
42
|
+
expect(route.config).toBe(null);
|
|
43
|
+
});
|
|
44
|
+
test("should handle component loading errors", async () => {
|
|
45
|
+
const errorRouter = new Router({
|
|
46
|
+
mode: RouterMode.memory,
|
|
47
|
+
base: new URL("http://localhost:3000/"),
|
|
48
|
+
routes: [
|
|
49
|
+
{ path: "/", component: () => "Home" },
|
|
50
|
+
{
|
|
51
|
+
path: "/error",
|
|
52
|
+
asyncComponent: () => Promise.reject(new Error("Loading failed"))
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
await errorRouter.replace("/");
|
|
57
|
+
await expect(errorRouter.push("/error")).rejects.toThrow();
|
|
58
|
+
errorRouter.destroy();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("Concurrent navigation", () => {
|
|
62
|
+
test("should handle concurrent navigation attempts", async () => {
|
|
63
|
+
const promises = Array.from(
|
|
64
|
+
{ length: 5 },
|
|
65
|
+
(_, i) => router.push(`/user/${i + 1}`).catch((err) => err)
|
|
66
|
+
);
|
|
67
|
+
const results = await Promise.all(promises);
|
|
68
|
+
const successResults = results.filter((r) => !(r instanceof Error));
|
|
69
|
+
const abortedResults = results.filter((r) => r instanceof Error);
|
|
70
|
+
expect(successResults).toHaveLength(1);
|
|
71
|
+
expect(abortedResults).toHaveLength(4);
|
|
72
|
+
const successResult = successResults[0];
|
|
73
|
+
expect(router.route.path).toBe(successResult.path);
|
|
74
|
+
expect(router.route.params.id).toBe(successResult.params.id);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe("Route guards", () => {
|
|
78
|
+
let guardRouter;
|
|
79
|
+
let guardLog;
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
guardLog = [];
|
|
82
|
+
guardRouter = new Router({
|
|
83
|
+
mode: RouterMode.memory,
|
|
84
|
+
base: new URL("http://localhost:3000/"),
|
|
85
|
+
routes: [
|
|
86
|
+
{ path: "/", component: () => "Home" },
|
|
87
|
+
{
|
|
88
|
+
path: "/protected",
|
|
89
|
+
component: () => "Protected",
|
|
90
|
+
beforeEnter: () => {
|
|
91
|
+
guardLog.push("beforeEnter-protected");
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
path: "/allowed",
|
|
97
|
+
component: () => "Allowed",
|
|
98
|
+
beforeEnter: () => {
|
|
99
|
+
guardLog.push("beforeEnter-allowed");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
await guardRouter.replace("/");
|
|
105
|
+
});
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
guardRouter.destroy();
|
|
108
|
+
});
|
|
109
|
+
test("should block navigation when guard returns false", async () => {
|
|
110
|
+
await expect(guardRouter.push("/protected")).rejects.toThrow();
|
|
111
|
+
expect(guardLog).toContain("beforeEnter-protected");
|
|
112
|
+
expect(guardRouter.route.path).toBe("/");
|
|
113
|
+
});
|
|
114
|
+
test("should allow navigation when guard returns true", async () => {
|
|
115
|
+
const route = await guardRouter.push("/allowed");
|
|
116
|
+
expect(guardLog).toContain("beforeEnter-allowed");
|
|
117
|
+
expect(route.path).toBe("/allowed");
|
|
118
|
+
expect(guardRouter.route.path).toBe("/allowed");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("Navigation types", () => {
|
|
122
|
+
test("should correctly set route type for push navigation", async () => {
|
|
123
|
+
const route = await router.push("/user/123");
|
|
124
|
+
expect(route.type).toBe(RouteType.push);
|
|
125
|
+
});
|
|
126
|
+
test("should correctly set route type for replace navigation", async () => {
|
|
127
|
+
const route = await router.replace("/user/123");
|
|
128
|
+
expect(route.type).toBe(RouteType.replace);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("Route parameters and query", () => {
|
|
132
|
+
test("should correctly extract route parameters", async () => {
|
|
133
|
+
const route = await router.push("/user/456");
|
|
134
|
+
expect(route.params.id).toBe("456");
|
|
135
|
+
expect(route.path).toBe("/user/456");
|
|
136
|
+
});
|
|
137
|
+
test("should handle query parameters", async () => {
|
|
138
|
+
const route = await router.push(
|
|
139
|
+
"/user/123?tab=profile&active=true"
|
|
140
|
+
);
|
|
141
|
+
expect(route.params.id).toBe("123");
|
|
142
|
+
expect(route.query.tab).toBe("profile");
|
|
143
|
+
expect(route.query.active).toBe("true");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type RouteConfirmHook, type RouteHandleHook, type RouteHandleResult, type RouteLayerOptions, type RouteLocationInput, type RouteMatchResult, type RouteMeta, type RouteOptions, type RouteParsedConfig, type RouteState, RouteType } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for non-enumerable properties in Route class
|
|
5
|
+
* These properties will be hidden during object traversal and serialization
|
|
6
|
+
*/
|
|
7
|
+
export declare const NON_ENUMERABLE_PROPERTIES: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Append user-provided parameters to URL path
|
|
10
|
+
* @param match Route matching result
|
|
11
|
+
* @param toInput User-provided route location object
|
|
12
|
+
* @param base Base URL
|
|
13
|
+
* @param to Current parsed URL object
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyRouteParams(match: RouteMatchResult, toInput: RouteLocationInput, base: URL, to: URL): void;
|
|
16
|
+
/**
|
|
17
|
+
* Route class provides complete route object functionality
|
|
18
|
+
*/
|
|
19
|
+
export declare class Route {
|
|
20
|
+
private _handled;
|
|
21
|
+
private _handle;
|
|
22
|
+
private _handleResult;
|
|
23
|
+
private readonly _options;
|
|
24
|
+
readonly statusCode: number | null;
|
|
25
|
+
readonly state: RouteState;
|
|
26
|
+
readonly keepScrollPosition: boolean;
|
|
27
|
+
/** Custom confirm handler that overrides default route-transition confirm logic */
|
|
28
|
+
readonly confirm: RouteConfirmHook | null;
|
|
29
|
+
/** Layer configuration for layer routes */
|
|
30
|
+
readonly layer: RouteLayerOptions | null;
|
|
31
|
+
readonly type: RouteType;
|
|
32
|
+
readonly req: IncomingMessage | null;
|
|
33
|
+
readonly res: ServerResponse | null;
|
|
34
|
+
readonly context: Record<string | symbol, any>;
|
|
35
|
+
readonly url: URL;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly fullPath: string;
|
|
38
|
+
readonly hash: string;
|
|
39
|
+
readonly params: Record<string, string>;
|
|
40
|
+
readonly query: Record<string, string | undefined>;
|
|
41
|
+
readonly queryArray: Record<string, string[] | undefined>;
|
|
42
|
+
readonly meta: RouteMeta;
|
|
43
|
+
readonly matched: readonly RouteParsedConfig[];
|
|
44
|
+
readonly config: RouteParsedConfig | null;
|
|
45
|
+
constructor(routeOptions?: Partial<RouteOptions>);
|
|
46
|
+
get isPush(): boolean;
|
|
47
|
+
get handle(): RouteHandleHook | null;
|
|
48
|
+
set handle(val: RouteHandleHook | null);
|
|
49
|
+
get handleResult(): RouteHandleResult | null;
|
|
50
|
+
set handleResult(val: RouteHandleResult | null);
|
|
51
|
+
/**
|
|
52
|
+
* Set handle function with validation logic wrapper
|
|
53
|
+
*/
|
|
54
|
+
setHandle(val: RouteHandleHook | null): void;
|
|
55
|
+
/**
|
|
56
|
+
* Apply navigation-generated state to current route
|
|
57
|
+
* Used by route handlers to add system state like pageId
|
|
58
|
+
* @param navigationState Navigation-generated state to apply
|
|
59
|
+
*/
|
|
60
|
+
applyNavigationState(navigationState: Partial<RouteState>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Sync all properties of current route to target route object
|
|
63
|
+
* Used for route object updates in reactive systems
|
|
64
|
+
* @param targetRoute Target route object
|
|
65
|
+
*/
|
|
66
|
+
syncTo(targetRoute: Route): void;
|
|
67
|
+
/**
|
|
68
|
+
* Clone current route instance
|
|
69
|
+
* Returns a new Route instance with same configuration and state
|
|
70
|
+
*/
|
|
71
|
+
clone(): Route;
|
|
72
|
+
}
|