@esmx/router 3.0.0-rc.12 → 3.0.0-rc.120
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 +158 -0
- package/README.zh-CN.md +158 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +64 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +16 -0
- package/dist/index.d.ts +14 -3
- package/dist/index.mjs +13 -3
- package/dist/location.d.ts +22 -0
- package/dist/location.mjs +64 -0
- package/dist/matcher.d.ts +4 -0
- package/dist/matcher.mjs +49 -0
- package/dist/micro-app.d.ts +20 -0
- package/dist/micro-app.mjs +132 -0
- package/dist/navigation.d.ts +45 -0
- package/dist/navigation.mjs +153 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +95 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +77 -0
- package/dist/route-transition.d.ts +54 -0
- package/dist/route-transition.mjs +362 -0
- package/dist/route.d.ts +77 -0
- package/dist/route.mjs +223 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +142 -0
- package/dist/router.d.ts +113 -102
- package/dist/router.mjs +321 -354
- package/dist/scroll.d.ts +33 -0
- package/dist/scroll.mjs +50 -0
- package/dist/types.d.ts +312 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +32 -0
- package/dist/util.mjs +100 -0
- package/package.json +42 -15
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.ts +67 -3
- package/src/location.ts +124 -0
- package/src/matcher.ts +71 -0
- package/src/micro-app.ts +153 -0
- package/src/navigation.ts +202 -0
- package/src/options.ts +136 -0
- package/src/route-task.ts +102 -0
- package/src/route-transition.ts +480 -0
- package/src/route.ts +335 -0
- package/src/router-link.ts +241 -0
- package/src/router.ts +351 -467
- package/src/scroll.ts +106 -0
- package/src/types.ts +415 -0
- package/src/util.ts +184 -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 -22
- package/dist/history/html.mjs +0 -181
- 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.d.ts +0 -1
- 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/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 -264
- package/dist/utils/path.spec.d.ts +0 -1
- package/dist/utils/path.spec.mjs +0 -30
- 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 -231
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -293
- 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/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 -44
- package/src/utils/path.ts +0 -397
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
package/src/router.ts
CHANGED
|
@@ -1,521 +1,405 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type RouterRawLocation,
|
|
21
|
-
type RouterScrollBehavior,
|
|
22
|
-
StateLayerConfigKey
|
|
1
|
+
import { LAYER_ID } from './increment-id';
|
|
2
|
+
import { MicroApp } from './micro-app';
|
|
3
|
+
import { Navigation } from './navigation';
|
|
4
|
+
import { parsedOptions } from './options';
|
|
5
|
+
import { Route } from './route';
|
|
6
|
+
import { RouteTransition } from './route-transition';
|
|
7
|
+
import { createLinkResolver } from './router-link';
|
|
8
|
+
import type {
|
|
9
|
+
RouteConfirmHook,
|
|
10
|
+
RouteLayerOptions,
|
|
11
|
+
RouteLayerResult,
|
|
12
|
+
RouteLocationInput,
|
|
13
|
+
RouteMatchType,
|
|
14
|
+
RouteNotifyHook,
|
|
15
|
+
RouterLinkProps,
|
|
16
|
+
RouterLinkResolved,
|
|
17
|
+
RouterOptions,
|
|
18
|
+
RouterParsedOptions,
|
|
19
|
+
RouteState
|
|
23
20
|
} from './types';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
matcher: RouterMatcher;
|
|
56
|
-
|
|
57
|
-
/* 路由history类 */
|
|
58
|
-
history: RouterHistory;
|
|
59
|
-
|
|
60
|
-
/* 滚动行为 */
|
|
61
|
-
scrollBehavior: RouterScrollBehavior;
|
|
62
|
-
|
|
63
|
-
/* 当前路由信息 */
|
|
64
|
-
route: Route = {
|
|
65
|
-
href: '',
|
|
66
|
-
origin: '',
|
|
67
|
-
host: '',
|
|
68
|
-
protocol: '',
|
|
69
|
-
hostname: '',
|
|
70
|
-
port: '',
|
|
71
|
-
pathname: '',
|
|
72
|
-
search: '',
|
|
73
|
-
hash: '',
|
|
74
|
-
|
|
75
|
-
params: {},
|
|
76
|
-
query: {},
|
|
77
|
-
queryArray: {},
|
|
78
|
-
state: {},
|
|
79
|
-
meta: {},
|
|
80
|
-
base: '',
|
|
81
|
-
path: '',
|
|
82
|
-
fullPath: '',
|
|
83
|
-
matched: []
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
constructor(options: RouterOptions) {
|
|
87
|
-
this.options = options;
|
|
88
|
-
this.matcher = createRouterMatcher(options.routes || []);
|
|
89
|
-
|
|
90
|
-
this.mode =
|
|
91
|
-
options.mode ||
|
|
92
|
-
(inBrowser ? RouterMode.HISTORY : RouterMode.ABSTRACT);
|
|
93
|
-
|
|
94
|
-
this.base = options.base || '';
|
|
95
|
-
|
|
96
|
-
this.scrollBehavior =
|
|
97
|
-
options.scrollBehavior ||
|
|
98
|
-
((to, from, savedPosition) => {
|
|
99
|
-
if (savedPosition) return savedPosition;
|
|
100
|
-
return {
|
|
101
|
-
left: 0,
|
|
102
|
-
top: 0
|
|
103
|
-
};
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
this.history = createHistory({
|
|
107
|
-
router: this,
|
|
108
|
-
mode: this.mode
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/* 更新路由 */
|
|
113
|
-
updateRoute(route: RouteRecord) {
|
|
114
|
-
const curAppType = this.route?.matched[0]?.appType;
|
|
115
|
-
const appType = route.matched[0]?.appType;
|
|
116
|
-
this.applyRoute(route);
|
|
117
|
-
|
|
118
|
-
const curRegisterConfig =
|
|
119
|
-
curAppType && this.registeredConfigMap[curAppType];
|
|
120
|
-
const registerConfig = appType && this.registeredConfigMap[appType];
|
|
121
|
-
|
|
122
|
-
if (registerConfig) {
|
|
123
|
-
const { mounted, generator } = registerConfig;
|
|
124
|
-
if (!mounted) {
|
|
125
|
-
registerConfig.config = generator(this);
|
|
126
|
-
registerConfig.config?.mount();
|
|
127
|
-
registerConfig.mounted = true;
|
|
128
|
-
|
|
129
|
-
this.layerMap[this.layerId] = {
|
|
130
|
-
router: this,
|
|
131
|
-
config: registerConfig.config,
|
|
132
|
-
destroyed: false
|
|
133
|
-
};
|
|
134
|
-
if (
|
|
135
|
-
!this.layerConfigList.find(
|
|
136
|
-
(item) => item.id === this.layerId
|
|
137
|
-
)
|
|
138
|
-
) {
|
|
139
|
-
this.layerConfigList.push({
|
|
140
|
-
id: this.layerId,
|
|
141
|
-
depth: 0
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
registerConfig.config?.updated();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (curAppType !== appType && curRegisterConfig) {
|
|
149
|
-
curRegisterConfig.config?.destroy();
|
|
150
|
-
curRegisterConfig.mounted = false;
|
|
21
|
+
import { RouterMode, RouteType } from './types';
|
|
22
|
+
import {
|
|
23
|
+
isNotNullish,
|
|
24
|
+
isPlainObject,
|
|
25
|
+
isRouteMatched,
|
|
26
|
+
validateSsrRootElement
|
|
27
|
+
} from './util';
|
|
28
|
+
|
|
29
|
+
const LAYER_SKIP_TYPES = new Set([
|
|
30
|
+
RouteType.pushWindow,
|
|
31
|
+
RouteType.replaceWindow,
|
|
32
|
+
RouteType.replace,
|
|
33
|
+
RouteType.restartApp,
|
|
34
|
+
RouteType.pushLayer
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export class Router {
|
|
38
|
+
public readonly options: RouterOptions;
|
|
39
|
+
public readonly parsedOptions: RouterParsedOptions;
|
|
40
|
+
public readonly isLayer: boolean;
|
|
41
|
+
public readonly navigation: Navigation;
|
|
42
|
+
public readonly microApp: MicroApp = new MicroApp();
|
|
43
|
+
|
|
44
|
+
// Route transition manager
|
|
45
|
+
public readonly transition = new RouteTransition(this);
|
|
46
|
+
public get route() {
|
|
47
|
+
const route = this.transition.route;
|
|
48
|
+
if (route === null) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'No active route found. Please navigate to a route first using router.push() or router.replace().'
|
|
51
|
+
);
|
|
151
52
|
}
|
|
53
|
+
return route;
|
|
152
54
|
}
|
|
153
55
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let url = '';
|
|
157
|
-
const { fullPath } = route;
|
|
158
|
-
if (inBrowser && !regexDomain.test(fullPath)) {
|
|
159
|
-
url = normalizePath(fullPath, location.origin);
|
|
160
|
-
} else {
|
|
161
|
-
url = normalizePath(fullPath);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const {
|
|
165
|
-
hash,
|
|
166
|
-
host,
|
|
167
|
-
hostname,
|
|
168
|
-
href,
|
|
169
|
-
origin,
|
|
170
|
-
pathname,
|
|
171
|
-
port,
|
|
172
|
-
protocol,
|
|
173
|
-
query
|
|
174
|
-
} = new URLParse(url);
|
|
175
|
-
Object.assign(
|
|
176
|
-
this.route,
|
|
177
|
-
{
|
|
178
|
-
href,
|
|
179
|
-
origin,
|
|
180
|
-
host,
|
|
181
|
-
protocol,
|
|
182
|
-
hostname,
|
|
183
|
-
port,
|
|
184
|
-
pathname,
|
|
185
|
-
search: query,
|
|
186
|
-
hash
|
|
187
|
-
},
|
|
188
|
-
route
|
|
189
|
-
);
|
|
56
|
+
public get context() {
|
|
57
|
+
return this.parsedOptions.context;
|
|
190
58
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
resolve(location: RouterRawLocation): RouteRecord {
|
|
194
|
-
return this.history.resolve(location);
|
|
59
|
+
public get data() {
|
|
60
|
+
return this.parsedOptions.data;
|
|
195
61
|
}
|
|
196
62
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
*/
|
|
200
|
-
// addRoutes(routes: RouteConfig[]) {
|
|
201
|
-
// this.matcher.addRoutes(routes);
|
|
202
|
-
// }
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* 新增多个路由匹配规则
|
|
206
|
-
*/
|
|
207
|
-
// addRoute(route: RouteConfig): void {
|
|
208
|
-
// this.matcher.addRoute(route);
|
|
209
|
-
// }
|
|
210
|
-
|
|
211
|
-
/* 初始化 */
|
|
212
|
-
async init(options: RouterInitOptions = {}) {
|
|
213
|
-
const { parent = null, route, replace = true } = options;
|
|
214
|
-
|
|
215
|
-
await this.history.init({
|
|
216
|
-
replace
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
const layerId = getLatestLayerId();
|
|
220
|
-
this.layerId = layerId;
|
|
221
|
-
let layerMap: RouterInstance['layerMap'] = {};
|
|
222
|
-
let layerConfigList: RouterInstance['layerConfigList'] = [];
|
|
223
|
-
if (parent && route) {
|
|
224
|
-
const appType = route.matched[0]?.appType;
|
|
225
|
-
if (!appType) return;
|
|
226
|
-
const registerConfig = parent.registeredConfigMap[appType];
|
|
227
|
-
if (!registerConfig) return;
|
|
228
|
-
|
|
229
|
-
parent.freeze();
|
|
230
|
-
this.registeredConfigMap = parent.registeredConfigMap;
|
|
231
|
-
const { generator } = registerConfig;
|
|
232
|
-
const config = generator(this);
|
|
233
|
-
config.mount();
|
|
234
|
-
config.updated();
|
|
235
|
-
layerMap = parent.layerMap;
|
|
236
|
-
|
|
237
|
-
layerMap[layerId] = {
|
|
238
|
-
router: this,
|
|
239
|
-
config,
|
|
240
|
-
destroyed: false
|
|
241
|
-
};
|
|
242
|
-
layerConfigList = parent.layerConfigList;
|
|
243
|
-
}
|
|
244
|
-
if (!layerConfigList.find((item) => item.id === layerId)) {
|
|
245
|
-
layerConfigList.push({
|
|
246
|
-
id: layerId,
|
|
247
|
-
depth: 0
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
this.parent = parent;
|
|
251
|
-
this.layerMap = layerMap;
|
|
252
|
-
this.layerConfigList = layerConfigList;
|
|
63
|
+
public get appId() {
|
|
64
|
+
return this.parsedOptions.appId;
|
|
253
65
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
* 卸载方法
|
|
257
|
-
*/
|
|
258
|
-
async destroy() {
|
|
259
|
-
// this.history.destroy();
|
|
66
|
+
public get mode(): RouterMode {
|
|
67
|
+
return this.parsedOptions.mode;
|
|
260
68
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
registeredConfigMap: RegisteredConfigMap = {};
|
|
264
|
-
|
|
265
|
-
/* app配置注册 */
|
|
266
|
-
register(
|
|
267
|
-
name: string,
|
|
268
|
-
config: (router: RouterInstance) => RegisteredConfig
|
|
269
|
-
) {
|
|
270
|
-
this.registeredConfigMap[name] = {
|
|
271
|
-
generator: config,
|
|
272
|
-
mounted: false
|
|
273
|
-
};
|
|
69
|
+
public get base(): URL {
|
|
70
|
+
return this.parsedOptions.base;
|
|
274
71
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
readonly guards: {
|
|
278
|
-
beforeEach: NavigationGuard[];
|
|
279
|
-
afterEach: NavigationGuardAfter[];
|
|
280
|
-
} = {
|
|
281
|
-
beforeEach: [],
|
|
282
|
-
afterEach: []
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
/* 注册全局路由前置守卫 */
|
|
286
|
-
beforeEach(guard: NavigationGuard) {
|
|
287
|
-
this.guards.beforeEach.push(guard);
|
|
72
|
+
public get req() {
|
|
73
|
+
return this.parsedOptions.req ?? null;
|
|
288
74
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
unBindBeforeEach(guard: NavigationGuard) {
|
|
292
|
-
const i = this.guards.beforeEach.findIndex((item) => item === guard);
|
|
293
|
-
this.guards.beforeEach.splice(i, 1);
|
|
75
|
+
public get res() {
|
|
76
|
+
return this.parsedOptions.res ?? null;
|
|
294
77
|
}
|
|
295
78
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.
|
|
79
|
+
public constructor(options: RouterOptions) {
|
|
80
|
+
this.options = options;
|
|
81
|
+
this.parsedOptions = parsedOptions(options);
|
|
82
|
+
this.isLayer = this.parsedOptions.layer;
|
|
83
|
+
|
|
84
|
+
this.navigation = new Navigation(
|
|
85
|
+
this.parsedOptions,
|
|
86
|
+
(url: string, state: RouteState) => {
|
|
87
|
+
this.transition.to(RouteType.unknown, {
|
|
88
|
+
url,
|
|
89
|
+
state
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
);
|
|
299
93
|
}
|
|
300
94
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const i = this.guards.afterEach.findIndex((item) => item === guard);
|
|
304
|
-
this.guards.afterEach.splice(i, 1);
|
|
95
|
+
public push(toInput: RouteLocationInput): Promise<Route> {
|
|
96
|
+
return this.transition.to(RouteType.push, toInput);
|
|
305
97
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
async push(location: RouterRawLocation) {
|
|
309
|
-
await this.history.push(location);
|
|
98
|
+
public replace(toInput: RouteLocationInput): Promise<Route> {
|
|
99
|
+
return this.transition.to(RouteType.replace, toInput);
|
|
310
100
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
101
|
+
public pushWindow(toInput: RouteLocationInput): Promise<Route> {
|
|
102
|
+
return this.transition.to(RouteType.pushWindow, toInput);
|
|
103
|
+
}
|
|
104
|
+
public replaceWindow(toInput: RouteLocationInput): Promise<Route> {
|
|
105
|
+
return this.transition.to(RouteType.replaceWindow, toInput);
|
|
106
|
+
}
|
|
107
|
+
public restartApp(toInput?: RouteLocationInput): Promise<Route> {
|
|
108
|
+
return this.transition.to(
|
|
109
|
+
RouteType.restartApp,
|
|
110
|
+
toInput ?? this.route.url.href
|
|
111
|
+
);
|
|
315
112
|
}
|
|
316
113
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 路由弹层配置
|
|
324
|
-
* key为路由弹层id
|
|
325
|
-
*/
|
|
326
|
-
layerConfigList: Array<{
|
|
327
|
-
/**
|
|
328
|
-
* 路由弹层id
|
|
329
|
-
*/
|
|
330
|
-
id: number;
|
|
331
|
-
/**
|
|
332
|
-
* 路由弹层深度
|
|
333
|
-
*/
|
|
334
|
-
depth: number;
|
|
335
|
-
}> = [];
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* 路由弹层id与路由实例的map
|
|
339
|
-
*/
|
|
340
|
-
layerMap: Record<
|
|
341
|
-
number,
|
|
342
|
-
{
|
|
343
|
-
router: RouterInstance;
|
|
344
|
-
config: RegisteredConfig;
|
|
345
|
-
destroyed: boolean;
|
|
114
|
+
public async back(): Promise<Route | null> {
|
|
115
|
+
const result = await this.navigation.go(-1);
|
|
116
|
+
if (result === null) {
|
|
117
|
+
this.parsedOptions.handleBackBoundary(this);
|
|
118
|
+
return null;
|
|
346
119
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
120
|
+
return this.transition.to(RouteType.back, {
|
|
121
|
+
url: result.url,
|
|
122
|
+
state: result.state
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
public async go(index: number): Promise<Route | null> {
|
|
126
|
+
// go(0) refreshes the page in browser, but we return null directly in router
|
|
127
|
+
if (index === 0) return null;
|
|
128
|
+
|
|
129
|
+
const result = await this.navigation.go(index);
|
|
130
|
+
if (result === null) {
|
|
131
|
+
// Call handleBackBoundary when backward navigation has no response
|
|
132
|
+
if (index < 0) {
|
|
133
|
+
this.parsedOptions.handleBackBoundary(this);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return this.transition.to(RouteType.go, {
|
|
138
|
+
url: result.url,
|
|
139
|
+
state: result.state
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
public async forward(): Promise<Route | null> {
|
|
143
|
+
const result = await this.navigation.go(1);
|
|
144
|
+
if (result === null) return null;
|
|
145
|
+
return this.transition.to(RouteType.forward, {
|
|
146
|
+
url: result.url,
|
|
147
|
+
state: result.state
|
|
148
|
+
});
|
|
149
|
+
}
|
|
353
150
|
|
|
354
151
|
/**
|
|
355
|
-
*
|
|
152
|
+
* Parse route location without performing actual navigation
|
|
153
|
+
*
|
|
154
|
+
* This method is used to parse route configuration and return the corresponding route object,
|
|
155
|
+
* but does not trigger actual page navigation. It is mainly used for the following scenarios:
|
|
156
|
+
* - Generate link URLs without jumping
|
|
157
|
+
* - Pre-check route matching
|
|
158
|
+
* - Get route parameters, meta information, etc.
|
|
159
|
+
* - Test the validity of route configuration
|
|
160
|
+
*
|
|
161
|
+
* @param toInput Target route location, can be a string path or route configuration object
|
|
162
|
+
* @returns Parsed route object containing complete route information
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* // Parse string path
|
|
167
|
+
* const route = router.resolve('/user/123');
|
|
168
|
+
* const url = route.url.href; // Get complete URL
|
|
169
|
+
*
|
|
170
|
+
* // Parse named route
|
|
171
|
+
* const userRoute = router.resolve({
|
|
172
|
+
* name: 'user',
|
|
173
|
+
* params: { id: '123' }
|
|
174
|
+
* });
|
|
175
|
+
* console.log(userRoute.params.id); // '123'
|
|
176
|
+
*
|
|
177
|
+
* // Check route validity
|
|
178
|
+
* const testRoute = router.resolve('/some/path');
|
|
179
|
+
* if (testRoute.matched.length > 0) {
|
|
180
|
+
* // Route matched successfully
|
|
181
|
+
* }
|
|
182
|
+
* ```
|
|
356
183
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
184
|
+
public resolve(toInput: RouteLocationInput, toType?: RouteType): Route {
|
|
185
|
+
return new Route({
|
|
186
|
+
options: this.parsedOptions,
|
|
187
|
+
toType,
|
|
188
|
+
toInput,
|
|
189
|
+
from: this.transition.route?.url ?? null
|
|
190
|
+
});
|
|
359
191
|
}
|
|
360
192
|
|
|
361
193
|
/**
|
|
362
|
-
*
|
|
194
|
+
* Check if the route matches the current route
|
|
195
|
+
*
|
|
196
|
+
* @param toRoute Target route object to compare
|
|
197
|
+
* @param matchType Match type
|
|
198
|
+
* - 'route': Route-level matching, compare if route configurations are the same
|
|
199
|
+
* - 'exact': Exact matching, compare if paths are completely the same
|
|
200
|
+
* - 'include': Include matching, check if current path contains target path
|
|
201
|
+
* @returns Whether it matches
|
|
363
202
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
203
|
+
public isRouteMatched(
|
|
204
|
+
toRoute: Route,
|
|
205
|
+
matchType: RouteMatchType = 'include'
|
|
206
|
+
): boolean {
|
|
207
|
+
const currentRoute = this.transition.route;
|
|
208
|
+
if (!currentRoute) return false;
|
|
209
|
+
|
|
210
|
+
return isRouteMatched(currentRoute, toRoute, matchType);
|
|
366
211
|
}
|
|
367
212
|
|
|
368
213
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
214
|
+
* Resolve router link configuration and return complete link data
|
|
215
|
+
*
|
|
216
|
+
* This method analyzes router link properties and returns a comprehensive
|
|
217
|
+
* link resolution result including route information, navigation functions,
|
|
218
|
+
* HTML attributes, and event handlers. It's primarily used for:
|
|
219
|
+
* - Framework-agnostic link component implementation
|
|
220
|
+
* - Generating link attributes and navigation handlers
|
|
221
|
+
* - Computing active states and CSS classes
|
|
222
|
+
* - Creating event handlers for different frameworks
|
|
223
|
+
*
|
|
224
|
+
* @param props Router link configuration properties
|
|
225
|
+
* @returns Complete link resolution result with all necessary data
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* // Basic link resolution
|
|
230
|
+
* const linkData = router.resolveLink({
|
|
231
|
+
* to: '/user/123',
|
|
232
|
+
* type: 'push'
|
|
233
|
+
* });
|
|
234
|
+
*
|
|
235
|
+
* // Access resolved data
|
|
236
|
+
* console.log(linkData.route.path); // '/user/123'
|
|
237
|
+
* console.log(linkData.attributes.href); // Full href URL
|
|
238
|
+
* console.log(linkData.isActive); // Active state
|
|
239
|
+
*
|
|
240
|
+
* // Use navigation function
|
|
241
|
+
* linkData.navigate(); // Programmatic navigation
|
|
242
|
+
*
|
|
243
|
+
* // Get event handlers for React
|
|
244
|
+
* const handlers = linkData.createEventHandlers(name => `on${name.charAt(0).toUpperCase() + name.slice(1)}`);
|
|
245
|
+
* // handlers.onClick for React
|
|
246
|
+
* ```
|
|
371
247
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const route = this.resolve(location);
|
|
375
|
-
if (route.fullPath === this.route.fullPath) return;
|
|
376
|
-
if (!inBrowser) {
|
|
377
|
-
this.push(location);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const router = createRouter({
|
|
381
|
-
...this.options,
|
|
382
|
-
initUrl: route.fullPath
|
|
383
|
-
});
|
|
384
|
-
await router.init({ parent: this, route, replace: false });
|
|
248
|
+
public resolveLink(props: RouterLinkProps): RouterLinkResolved {
|
|
249
|
+
return createLinkResolver(this, props);
|
|
385
250
|
}
|
|
386
251
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
checkLayerState(state: HistoryState) {
|
|
393
|
-
// state 中存放的 layerConfig 配置
|
|
394
|
-
const stateLayerConfigList =
|
|
395
|
-
(state[StateLayerConfigKey] as typeof this.layerConfigList) || [];
|
|
252
|
+
public async createLayer(
|
|
253
|
+
toInput: RouteLocationInput
|
|
254
|
+
): Promise<{ promise: Promise<RouteLayerResult>; router: Router }> {
|
|
255
|
+
const layerOptions: RouteLayerOptions =
|
|
256
|
+
(isPlainObject(toInput) && toInput.layer) || {};
|
|
396
257
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
// state中存放的可用的 layerId 列表
|
|
400
|
-
const stateLayerIdList = stateLayerConfigList
|
|
401
|
-
.map(({ id }) => id)
|
|
402
|
-
.filter((id) => {
|
|
403
|
-
return layerConfigList.includes(id);
|
|
404
|
-
});
|
|
405
|
-
if (stateLayerIdList.length === 0) {
|
|
406
|
-
// 没有可用的 layerId 列表时跳出
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
if (
|
|
410
|
-
stateLayerIdList.length === 1 &&
|
|
411
|
-
stateLayerIdList[0] === this.layerId
|
|
412
|
-
) {
|
|
413
|
-
// 只有一个可用的 layerId 并且就是当前的layerId时跳出
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
258
|
+
const zIndex =
|
|
259
|
+
layerOptions.zIndex ?? this.parsedOptions.zIndex + LAYER_ID.next();
|
|
416
260
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (stateLayerIdList.includes(Number(key))) {
|
|
421
|
-
if (value.destroyed) {
|
|
422
|
-
createList.push(Number(key));
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
destroyList.push(Number(key));
|
|
426
|
-
}
|
|
261
|
+
let promiseResolve: (result: RouteLayerResult) => void;
|
|
262
|
+
let promise = new Promise<RouteLayerResult>((resolve) => {
|
|
263
|
+
promiseResolve = resolve;
|
|
427
264
|
});
|
|
428
|
-
if (createList.length === 0 && destroyList.length === 0) {
|
|
429
|
-
// 没有需要创建的 layerId 列表 并且 没有需要销毁的 layerId 列表时跳出
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const activeId = Math.max(...stateLayerIdList);
|
|
434
265
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
266
|
+
const router = new Router({
|
|
267
|
+
rootStyle: {
|
|
268
|
+
position: 'fixed',
|
|
269
|
+
top: '0',
|
|
270
|
+
left: '0',
|
|
271
|
+
width: '100%',
|
|
272
|
+
height: '100%',
|
|
273
|
+
zIndex: String(zIndex),
|
|
274
|
+
background: 'rgba(0,0,0,.6)',
|
|
275
|
+
display: 'flex',
|
|
276
|
+
alignItems: 'center',
|
|
277
|
+
justifyContent: 'center'
|
|
278
|
+
},
|
|
279
|
+
...this.options,
|
|
280
|
+
context: this.parsedOptions.context,
|
|
281
|
+
mode: RouterMode.memory,
|
|
282
|
+
appId: undefined,
|
|
283
|
+
...layerOptions.routerOptions,
|
|
284
|
+
handleBackBoundary(router) {
|
|
285
|
+
router.destroy();
|
|
286
|
+
promiseResolve({
|
|
287
|
+
type: 'close',
|
|
288
|
+
route: router.route
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
handleLayerClose(router, data) {
|
|
292
|
+
router.destroy();
|
|
293
|
+
if (isNotNullish(data)) {
|
|
294
|
+
promiseResolve({
|
|
295
|
+
type: 'success',
|
|
296
|
+
route: router.route,
|
|
297
|
+
data
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
promiseResolve({
|
|
301
|
+
type: 'close',
|
|
302
|
+
route: router.route
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
layer: true
|
|
307
|
+
});
|
|
308
|
+
const initRoute = await router.replace(toInput);
|
|
309
|
+
|
|
310
|
+
router.afterEach(async (to, from) => {
|
|
311
|
+
if (LAYER_SKIP_TYPES.has(to.type)) return;
|
|
312
|
+
let keepAlive = false;
|
|
313
|
+
if (layerOptions.keepAlive === 'exact') {
|
|
314
|
+
keepAlive = to.path === initRoute.path;
|
|
315
|
+
} else if (layerOptions.keepAlive === 'include') {
|
|
316
|
+
keepAlive = to.path.startsWith(initRoute.path);
|
|
317
|
+
} else if (typeof layerOptions.keepAlive === 'function') {
|
|
318
|
+
keepAlive = await layerOptions.keepAlive(to, from, router);
|
|
439
319
|
} else {
|
|
440
|
-
|
|
320
|
+
if (layerOptions.shouldClose) {
|
|
321
|
+
console.warn(
|
|
322
|
+
'[esmx-router] RouteLayerOptions.shouldClose is deprecated. Use keepAlive instead. ' +
|
|
323
|
+
'Note: shouldClose returns true to close, keepAlive returns true to keep alive.'
|
|
324
|
+
);
|
|
325
|
+
keepAlive = !(await layerOptions.shouldClose(
|
|
326
|
+
to,
|
|
327
|
+
from,
|
|
328
|
+
router
|
|
329
|
+
));
|
|
330
|
+
} else {
|
|
331
|
+
keepAlive = to.path === initRoute.path;
|
|
332
|
+
}
|
|
441
333
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
destroyList.forEach((id) => {
|
|
445
|
-
const layer = this.layerMap[id];
|
|
446
|
-
if (!layer.destroyed) {
|
|
447
|
-
const { router, config } = layer;
|
|
448
|
-
config.destroy();
|
|
334
|
+
if (!keepAlive) {
|
|
449
335
|
router.destroy();
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
createList.forEach((id) => {
|
|
456
|
-
const layer = this.layerMap[id];
|
|
457
|
-
if (layer.destroyed) {
|
|
458
|
-
const { router, config } = layer;
|
|
459
|
-
config.mount();
|
|
460
|
-
router.unfreeze();
|
|
461
|
-
layer.destroyed = false;
|
|
336
|
+
promiseResolve({
|
|
337
|
+
type: 'push',
|
|
338
|
+
route: to
|
|
339
|
+
});
|
|
462
340
|
}
|
|
463
341
|
});
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
342
|
+
if (layerOptions.push) {
|
|
343
|
+
router.navigation.pushHistoryState(
|
|
344
|
+
router.route.state,
|
|
345
|
+
router.route.url
|
|
346
|
+
);
|
|
347
|
+
promise = promise.then(async (result) => {
|
|
348
|
+
await this.navigation.backHistoryState();
|
|
349
|
+
return result;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (layerOptions.autoPush) {
|
|
353
|
+
promise = promise.then(async (result) => {
|
|
354
|
+
if (result.type === 'push') {
|
|
355
|
+
await this.push(result.route.url.href);
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
promise,
|
|
362
|
+
router
|
|
480
363
|
};
|
|
481
|
-
window.history.replaceState(state, '', route.fullPath);
|
|
482
364
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
this.history.pushWindow(location);
|
|
365
|
+
public async pushLayer(
|
|
366
|
+
toInput: RouteLocationInput
|
|
367
|
+
): Promise<RouteLayerResult> {
|
|
368
|
+
const result = await this.transition.to(RouteType.pushLayer, toInput);
|
|
369
|
+
return result.handleResult as RouteLayerResult;
|
|
489
370
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
*/
|
|
494
|
-
replaceWindow(location: RouterRawLocation) {
|
|
495
|
-
this.history.replaceWindow(location);
|
|
371
|
+
public closeLayer(data?: any) {
|
|
372
|
+
if (!this.isLayer) return;
|
|
373
|
+
this.parsedOptions.handleLayerClose(this, data);
|
|
496
374
|
}
|
|
497
375
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
376
|
+
public async renderToString(throwError = false): Promise<string | null> {
|
|
377
|
+
try {
|
|
378
|
+
const result = await this.microApp.app?.renderToString?.();
|
|
379
|
+
const trimmed = result?.trim();
|
|
380
|
+
const hasContent = trimmed && trimmed.length > 0;
|
|
381
|
+
if (hasContent && process.env.NODE_ENV !== 'production') {
|
|
382
|
+
validateSsrRootElement(trimmed);
|
|
383
|
+
}
|
|
384
|
+
const ssrAttr = hasContent ? ' data-ssr' : '';
|
|
385
|
+
return `<div id="${this.appId}"${ssrAttr}>${result ?? ''}</div>`;
|
|
386
|
+
} catch (e) {
|
|
387
|
+
if (throwError) throw e;
|
|
388
|
+
else console.error('[@esmx/router] SSR render failed:', e);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
501
391
|
}
|
|
502
392
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
this.history.forward();
|
|
393
|
+
public beforeEach(guard: RouteConfirmHook): () => void {
|
|
394
|
+
return this.transition.beforeEach(guard);
|
|
506
395
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
back() {
|
|
510
|
-
this.history.back();
|
|
396
|
+
public afterEach(guard: RouteNotifyHook): () => void {
|
|
397
|
+
return this.transition.afterEach(guard);
|
|
511
398
|
}
|
|
512
399
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
400
|
+
public destroy() {
|
|
401
|
+
this.transition.destroy();
|
|
402
|
+
this.navigation.destroy();
|
|
403
|
+
this.microApp.destroy();
|
|
516
404
|
}
|
|
517
405
|
}
|
|
518
|
-
|
|
519
|
-
export function createRouter(options: RouterOptions): RouterInstance {
|
|
520
|
-
return new Router(options);
|
|
521
|
-
}
|