@esmx/router 3.0.0-rc.17 → 3.0.0-rc.19

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.
Files changed (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -22
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/utils/bom.d.ts +0 -5
  120. package/dist/utils/bom.mjs +0 -10
  121. package/dist/utils/encoding.d.ts +0 -48
  122. package/dist/utils/encoding.mjs +0 -44
  123. package/dist/utils/guards.d.ts +0 -9
  124. package/dist/utils/guards.mjs +0 -12
  125. package/dist/utils/index.d.ts +0 -7
  126. package/dist/utils/index.mjs +0 -27
  127. package/dist/utils/path.d.ts +0 -60
  128. package/dist/utils/path.mjs +0 -281
  129. package/dist/utils/path.spec.mjs +0 -27
  130. package/dist/utils/scroll.d.ts +0 -25
  131. package/dist/utils/scroll.mjs +0 -59
  132. package/dist/utils/utils.d.ts +0 -16
  133. package/dist/utils/utils.mjs +0 -11
  134. package/dist/utils/warn.d.ts +0 -2
  135. package/dist/utils/warn.mjs +0 -12
  136. package/src/history/abstract.ts +0 -149
  137. package/src/history/base.ts +0 -408
  138. package/src/history/html.ts +0 -228
  139. package/src/history/index.ts +0 -20
  140. package/src/matcher/create-matcher.spec.ts +0 -3
  141. package/src/matcher/create-matcher.ts +0 -293
  142. package/src/matcher/index.ts +0 -1
  143. package/src/task-pipe/index.ts +0 -1
  144. package/src/task-pipe/task.ts +0 -97
  145. package/src/utils/bom.ts +0 -14
  146. package/src/utils/encoding.ts +0 -153
  147. package/src/utils/guards.ts +0 -25
  148. package/src/utils/index.ts +0 -27
  149. package/src/utils/path.spec.ts +0 -32
  150. package/src/utils/path.ts +0 -417
  151. package/src/utils/scroll.ts +0 -120
  152. package/src/utils/utils.ts +0 -30
  153. package/src/utils/warn.ts +0 -13
  154. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  155. /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
package/src/router.ts CHANGED
@@ -1,521 +1,340 @@
1
- import URLParse from 'url-parse';
2
-
3
- import { createHistory } from './history';
4
- import { createRouterMatcher } from './matcher';
5
- import {
6
- type HistoryState,
7
- type NavigationGuard,
8
- type NavigationGuardAfter,
9
- type RegisteredConfig,
10
- type RegisteredConfigMap,
11
- type Route,
12
- type RouteRecord,
13
- type RouterBase,
14
- type RouterHistory,
15
- type RouterInitOptions,
16
- type RouterInstance,
17
- type RouterMatcher,
18
- RouterMode,
19
- type RouterOptions,
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 { RouteType, RouterMode } from './types';
9
+ import type {
10
+ RouteConfirmHook,
11
+ RouteLayerOptions,
12
+ RouteLayerResult,
13
+ RouteLocationInput,
14
+ RouteMatchType,
15
+ RouteNotifyHook,
16
+ RouteState,
17
+ RouterLinkProps,
18
+ RouterLinkResolved,
19
+ RouterOptions,
20
+ RouterParsedOptions
23
21
  } from './types';
24
- import { inBrowser, normalizePath, regexDomain } from './utils';
25
-
26
- const baseValue = Number(Date.now());
27
- let layerIdOffset = 0;
28
-
29
- function getLatestLayerId() {
30
- return baseValue + ++layerIdOffset;
31
- }
32
-
33
- /**
34
- * 路由类
35
- */
36
- export class Router implements RouterInstance {
37
- /**
38
- * 当前路由对象的上级路由对象
39
- */
40
- parent: RouterInstance | null = null;
41
-
42
- /* 路由配置 */
43
- options: RouterOptions;
44
-
45
- /**
46
- * 路由固定前置路径
47
- * 需要注意的是如果使用函数返回 base,需要尽量保证相同的路径返回相同base
48
- */
49
- base: RouterBase;
50
-
51
- /* 路由模式 */
52
- mode: RouterMode;
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();
22
+ import { isNotNullish, isPlainObject, isRouteMatched } from './util';
23
+
24
+ export class Router {
25
+ public readonly options: RouterOptions;
26
+ public readonly parsedOptions: RouterParsedOptions;
27
+ public readonly isLayer: boolean;
28
+ public readonly navigation: Navigation;
29
+ public readonly microApp: MicroApp = new MicroApp();
30
+
31
+ // Route transition manager
32
+ public readonly transition = new RouteTransition(this);
33
+ public get route() {
34
+ const route = this.transition.route;
35
+ if (route === null) {
36
+ throw new Error(
37
+ 'No active route found. Please navigate to a route first using router.push() or router.replace().'
38
+ );
146
39
  }
147
-
148
- if (curAppType !== appType && curRegisterConfig) {
149
- curRegisterConfig.config?.destroy();
150
- curRegisterConfig.mounted = false;
151
- }
152
- }
153
-
154
- /* 应用路由 */
155
- protected applyRoute(route: RouteRecord) {
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
- );
40
+ return route;
190
41
  }
191
42
 
192
- /* 解析路由 */
193
- resolve(location: RouterRawLocation): RouteRecord {
194
- return this.history.resolve(location);
43
+ public get root() {
44
+ return this.parsedOptions.root;
195
45
  }
196
-
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;
46
+ public get req() {
47
+ return this.parsedOptions.req ?? null;
253
48
  }
254
-
255
- /**
256
- * 卸载方法
257
- */
258
- async destroy() {
259
- // this.history.destroy();
49
+ public get res() {
50
+ return this.parsedOptions.res ?? null;
260
51
  }
261
52
 
262
- /* 已注册的app配置 */
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
- };
53
+ public constructor(options: RouterOptions) {
54
+ this.options = options;
55
+ this.parsedOptions = parsedOptions(options);
56
+ this.isLayer = this.parsedOptions.layer;
57
+
58
+ this.navigation = new Navigation(
59
+ this.parsedOptions,
60
+ (url: string, state: RouteState) => {
61
+ this.transition.to(RouteType.unknown, {
62
+ url,
63
+ state
64
+ });
65
+ }
66
+ );
274
67
  }
275
68
 
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);
69
+ public push(toInput: RouteLocationInput): Promise<Route> {
70
+ return this.transition.to(RouteType.push, toInput);
288
71
  }
289
-
290
- /* 卸载全局路由前置守卫 */
291
- unBindBeforeEach(guard: NavigationGuard) {
292
- const i = this.guards.beforeEach.findIndex((item) => item === guard);
293
- this.guards.beforeEach.splice(i, 1);
72
+ public replace(toInput: RouteLocationInput): Promise<Route> {
73
+ return this.transition.to(RouteType.replace, toInput);
294
74
  }
295
-
296
- /* 注册全局路由后置守卫 */
297
- afterEach(guard: NavigationGuardAfter) {
298
- this.guards.afterEach.push(guard);
75
+ public pushWindow(toInput: RouteLocationInput): Promise<Route> {
76
+ return this.transition.to(RouteType.pushWindow, toInput);
299
77
  }
300
-
301
- /* 卸载全局路由后置守卫 */
302
- unBindAfterEach(guard: NavigationGuardAfter) {
303
- const i = this.guards.afterEach.findIndex((item) => item === guard);
304
- this.guards.afterEach.splice(i, 1);
78
+ public replaceWindow(toInput: RouteLocationInput): Promise<Route> {
79
+ return this.transition.to(RouteType.replaceWindow, toInput);
305
80
  }
306
-
307
- /* 路由跳转方法,会创建新的历史记录 */
308
- async push(location: RouterRawLocation) {
309
- await this.history.push(location);
81
+ public restartApp(toInput?: RouteLocationInput): Promise<Route> {
82
+ return this.transition.to(
83
+ RouteType.restartApp,
84
+ toInput ?? this.route.url.href
85
+ );
310
86
  }
311
87
 
312
- /* 路由跳转方法,会替换当前的历史记录 */
313
- async replace(location: RouterRawLocation) {
314
- await this.history.replace(location);
88
+ public async back(): Promise<Route | null> {
89
+ const result = await this.navigation.go(-1);
90
+ if (result === null) {
91
+ this.parsedOptions.handleBackBoundary(this);
92
+ return null;
93
+ }
94
+ return this.transition.to(RouteType.back, {
95
+ url: result.url,
96
+ state: result.state
97
+ });
315
98
  }
316
-
317
- /**
318
- * 当前路由弹层id,用于区分不同的路由弹层
319
- */
320
- layerId = 0;
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;
99
+ public async go(index: number): Promise<Route | null> {
100
+ // go(0) refreshes the page in browser, but we return null directly in router
101
+ if (index === 0) return null;
102
+
103
+ const result = await this.navigation.go(index);
104
+ if (result === null) {
105
+ // Call handleBackBoundary when backward navigation has no response
106
+ if (index < 0) {
107
+ this.parsedOptions.handleBackBoundary(this);
108
+ }
109
+ return null;
346
110
  }
347
- > = {};
348
-
349
- /**
350
- * 路由是否冻结
351
- */
352
- isFrozen = false;
111
+ return this.transition.to(RouteType.go, {
112
+ url: result.url,
113
+ state: result.state
114
+ });
115
+ }
116
+ public async forward(): Promise<Route | null> {
117
+ const result = await this.navigation.go(1);
118
+ if (result === null) return null;
119
+ return this.transition.to(RouteType.forward, {
120
+ url: result.url,
121
+ state: result.state
122
+ });
123
+ }
353
124
 
354
125
  /**
355
- * 路由冻结方法
126
+ * Parse route location without performing actual navigation
127
+ *
128
+ * This method is used to parse route configuration and return the corresponding route object,
129
+ * but does not trigger actual page navigation. It is mainly used for the following scenarios:
130
+ * - Generate link URLs without jumping
131
+ * - Pre-check route matching
132
+ * - Get route parameters, meta information, etc.
133
+ * - Test the validity of route configuration
134
+ *
135
+ * @param toInput Target route location, can be a string path or route configuration object
136
+ * @returns Parsed route object containing complete route information
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * // Parse string path
141
+ * const route = router.resolve('/user/123');
142
+ * const url = route.url.href; // Get complete URL
143
+ *
144
+ * // Parse named route
145
+ * const userRoute = router.resolve({
146
+ * name: 'user',
147
+ * params: { id: '123' }
148
+ * });
149
+ * console.log(userRoute.params.id); // '123'
150
+ *
151
+ * // Check route validity
152
+ * const testRoute = router.resolve('/some/path');
153
+ * if (testRoute.matched.length > 0) {
154
+ * // Route matched successfully
155
+ * }
156
+ * ```
356
157
  */
357
- freeze() {
358
- this.isFrozen = true;
158
+ public resolve(toInput: RouteLocationInput, toType?: RouteType): Route {
159
+ return new Route({
160
+ options: this.parsedOptions,
161
+ toType,
162
+ toInput,
163
+ from: this.transition.route?.url ?? null
164
+ });
359
165
  }
360
166
 
361
167
  /**
362
- * 路由解冻方法
168
+ * Check if the route matches the current route
169
+ *
170
+ * @param targetRoute Target route object to compare
171
+ * @param matchType Match type
172
+ * - 'route': Route-level matching, compare if route configurations are the same
173
+ * - 'exact': Exact matching, compare if paths are completely the same
174
+ * - 'include': Include matching, check if current path contains target path
175
+ * @returns Whether it matches
363
176
  */
364
- unfreeze() {
365
- this.isFrozen = false;
177
+ public isRouteMatched(
178
+ targetRoute: Route,
179
+ matchType: RouteMatchType = 'include'
180
+ ): boolean {
181
+ const currentRoute = this.transition.route;
182
+ if (!currentRoute) return false;
183
+
184
+ return isRouteMatched(currentRoute, targetRoute, matchType);
366
185
  }
367
186
 
368
187
  /**
369
- * 打开路由弹层方法,会创建新的路由实例并调用注册的 register 方法
370
- * 服务端会使用 push 作为替代
188
+ * Resolve router link configuration and return complete link data
189
+ *
190
+ * This method analyzes router link properties and returns a comprehensive
191
+ * link resolution result including route information, navigation functions,
192
+ * HTML attributes, and event handlers. It's primarily used for:
193
+ * - Framework-agnostic link component implementation
194
+ * - Generating link attributes and navigation handlers
195
+ * - Computing active states and CSS classes
196
+ * - Creating event handlers for different frameworks
197
+ *
198
+ * @param props Router link configuration properties
199
+ * @returns Complete link resolution result with all necessary data
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * // Basic link resolution
204
+ * const linkData = router.resolveLink({
205
+ * to: '/user/123',
206
+ * type: 'push'
207
+ * });
208
+ *
209
+ * // Access resolved data
210
+ * console.log(linkData.route.path); // '/user/123'
211
+ * console.log(linkData.attributes.href); // Full href URL
212
+ * console.log(linkData.isActive); // Active state
213
+ *
214
+ * // Use navigation function
215
+ * linkData.navigate(); // Programmatic navigation
216
+ *
217
+ * // Get event handlers for React
218
+ * const handlers = linkData.getEventHandlers(name => `on${name.charAt(0).toUpperCase() + name.slice(1)}`);
219
+ * // handlers.onClick for React
220
+ * ```
371
221
  */
372
- async pushLayer(location: RouterRawLocation) {
373
- if (this.isFrozen) return;
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 });
222
+ public resolveLink(props: RouterLinkProps): RouterLinkResolved {
223
+ return createLinkResolver(this, props);
385
224
  }
386
225
 
387
- /**
388
- * 更新路由弹层方法
389
- * @param state 参数为history.state
390
- * @description 没有传入 state 时使用当前配置更新 history.state,传入了 state 时使用传入的 state 更新当前配置
391
- */
392
- checkLayerState(state: HistoryState) {
393
- // state 中存放的 layerConfig 配置
394
- const stateLayerConfigList =
395
- (state[StateLayerConfigKey] as typeof this.layerConfigList) || [];
226
+ public async createLayer(
227
+ toInput: RouteLocationInput
228
+ ): Promise<{ promise: Promise<RouteLayerResult>; router: Router }> {
229
+ const layerOptions: RouteLayerOptions =
230
+ (isPlainObject(toInput) ? toInput.layer : null) || {};
396
231
 
397
- // 所有的 layerId 列表
398
- const layerConfigList = Object.keys(this.layerMap).map(Number);
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
- }
232
+ const zIndex =
233
+ layerOptions.zIndex ?? this.parsedOptions.zIndex + LAYER_ID.next();
416
234
 
417
- const createList: number[] = [];
418
- const destroyList: number[] = [];
419
- Object.entries(this.layerMap).forEach(([key, value]) => {
420
- if (stateLayerIdList.includes(Number(key))) {
421
- if (value.destroyed) {
422
- createList.push(Number(key));
423
- }
424
- } else {
425
- destroyList.push(Number(key));
426
- }
235
+ let promiseResolve: (result: RouteLayerResult) => void;
236
+ const promise = new Promise<RouteLayerResult>((resolve) => {
237
+ promiseResolve = resolve;
427
238
  });
428
- if (createList.length === 0 && destroyList.length === 0) {
429
- // 没有需要创建的 layerId 列表 并且 没有需要销毁的 layerId 列表时跳出
430
- return false;
431
- }
432
239
 
433
- const activeId = Math.max(...stateLayerIdList);
434
-
435
- layerConfigList.forEach((id) => {
436
- const { router } = this.layerMap[id];
437
- if (activeId === id) {
438
- router.unfreeze();
439
- } else {
440
- router.freeze();
441
- }
442
- });
443
-
444
- destroyList.forEach((id) => {
445
- const layer = this.layerMap[id];
446
- if (!layer.destroyed) {
447
- const { router, config } = layer;
448
- config.destroy();
240
+ const router = new Router({
241
+ ...this.options,
242
+ mode: RouterMode.memory,
243
+ rootStyle: {
244
+ position: 'fixed',
245
+ top: '0',
246
+ left: '0',
247
+ width: '100%',
248
+ height: '100%',
249
+ zIndex: String(zIndex),
250
+ background: 'rgba(0,0,0,.6)',
251
+ display: 'flex',
252
+ alignItems: 'center',
253
+ justifyContent: 'center'
254
+ },
255
+ root: undefined,
256
+ ...layerOptions.routerOptions,
257
+ handleBackBoundary(router) {
449
258
  router.destroy();
450
- router.freeze();
451
- layer.destroyed = true;
452
- }
259
+ promiseResolve({
260
+ type: 'close',
261
+ route: router.route
262
+ });
263
+ },
264
+ handleLayerClose(router, data) {
265
+ router.destroy();
266
+ if (isNotNullish(data)) {
267
+ promiseResolve({
268
+ type: 'success',
269
+ route: router.route,
270
+ data
271
+ });
272
+ } else {
273
+ promiseResolve({
274
+ type: 'close',
275
+ route: router.route
276
+ });
277
+ }
278
+ },
279
+ layer: true
453
280
  });
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;
281
+ await router.replace(toInput);
282
+
283
+ router.afterEach((to, from) => {
284
+ if (layerOptions.shouldClose) {
285
+ const result = layerOptions.shouldClose(to, from, router);
286
+ if (result === false) {
287
+ router.destroy();
288
+ promiseResolve({
289
+ type: 'push',
290
+ route: to
291
+ });
292
+ }
462
293
  }
463
294
  });
464
- this.layerConfigList = stateLayerConfigList;
465
-
466
- return true;
467
- }
468
-
469
- updateLayerState(route: RouteRecord) {
470
- const layerConfig = this.layerConfigList.find((item) => {
471
- return item.id === this.layerId;
472
- });
473
- if (layerConfig) layerConfig.depth++;
474
- const stateConfigList = this.layerConfigList.filter(({ id }) => {
475
- return !this.layerMap[id]?.destroyed;
476
- });
477
- const state = {
478
- ...history.state,
479
- [StateLayerConfigKey]: stateConfigList
295
+ if (layerOptions.push) {
296
+ router.navigation.pushHistoryState(
297
+ router.route.state,
298
+ router.route.url
299
+ );
300
+ }
301
+ return {
302
+ promise,
303
+ router
480
304
  };
481
- window.history.replaceState(state, '', route.fullPath);
482
305
  }
483
-
484
- /**
485
- * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
486
- */
487
- pushWindow(location: RouterRawLocation) {
488
- this.history.pushWindow(location);
306
+ public async pushLayer(
307
+ toInput: RouteLocationInput
308
+ ): Promise<RouteLayerResult> {
309
+ const result = await this.transition.to(RouteType.pushLayer, toInput);
310
+ return result.handleResult as RouteLayerResult;
489
311
  }
490
-
491
- /**
492
- * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
493
- */
494
- replaceWindow(location: RouterRawLocation) {
495
- this.history.replaceWindow(location);
312
+ public closeLayer(data?: any) {
313
+ if (!this.isLayer) return;
314
+ this.parsedOptions.handleLayerClose(this, data);
496
315
  }
497
316
 
498
- /* 前往特定路由历史记录的方法,可以在历史记录前后移动 */
499
- go(delta = 0) {
500
- this.history.go(delta);
317
+ public async renderToString(throwError = false): Promise<string | null> {
318
+ try {
319
+ const result = await this.microApp.app?.renderToString?.();
320
+ return result ?? null;
321
+ } catch (e) {
322
+ if (throwError) throw e;
323
+ else console.error(e);
324
+ return null;
325
+ }
501
326
  }
502
327
 
503
- /* 路由历史记录前进方法 */
504
- forward() {
505
- this.history.forward();
328
+ public beforeEach(guard: RouteConfirmHook): () => void {
329
+ return this.transition.beforeEach(guard);
506
330
  }
507
-
508
- /* 路由历史记录后退方法 */
509
- back() {
510
- this.history.back();
331
+ public afterEach(guard: RouteNotifyHook): () => void {
332
+ return this.transition.afterEach(guard);
511
333
  }
512
334
 
513
- /* 根据获取当前所有活跃的路由Record对象 */
514
- getRoutes() {
515
- return this.matcher.getRoutes();
335
+ public destroy() {
336
+ this.transition.destroy();
337
+ this.navigation.destroy();
338
+ this.microApp.destroy();
516
339
  }
517
340
  }
518
-
519
- export function createRouter(options: RouterOptions): RouterInstance {
520
- return new Router(options);
521
- }