@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.
Files changed (105) hide show
  1. package/README.md +158 -0
  2. package/README.zh-CN.md +158 -0
  3. package/dist/error.d.ts +23 -0
  4. package/dist/error.mjs +64 -0
  5. package/dist/increment-id.d.ts +7 -0
  6. package/dist/increment-id.mjs +16 -0
  7. package/dist/index.d.ts +14 -3
  8. package/dist/index.mjs +13 -3
  9. package/dist/location.d.ts +22 -0
  10. package/dist/location.mjs +64 -0
  11. package/dist/matcher.d.ts +4 -0
  12. package/dist/matcher.mjs +49 -0
  13. package/dist/micro-app.d.ts +20 -0
  14. package/dist/micro-app.mjs +132 -0
  15. package/dist/navigation.d.ts +45 -0
  16. package/dist/navigation.mjs +153 -0
  17. package/dist/options.d.ts +4 -0
  18. package/dist/options.mjs +95 -0
  19. package/dist/route-task.d.ts +40 -0
  20. package/dist/route-task.mjs +77 -0
  21. package/dist/route-transition.d.ts +54 -0
  22. package/dist/route-transition.mjs +362 -0
  23. package/dist/route.d.ts +77 -0
  24. package/dist/route.mjs +223 -0
  25. package/dist/router-link.d.ts +10 -0
  26. package/dist/router-link.mjs +142 -0
  27. package/dist/router.d.ts +113 -102
  28. package/dist/router.mjs +321 -354
  29. package/dist/scroll.d.ts +33 -0
  30. package/dist/scroll.mjs +50 -0
  31. package/dist/types.d.ts +312 -0
  32. package/dist/types.mjs +18 -0
  33. package/dist/util.d.ts +32 -0
  34. package/dist/util.mjs +100 -0
  35. package/package.json +42 -15
  36. package/src/error.ts +84 -0
  37. package/src/increment-id.ts +12 -0
  38. package/src/index.ts +67 -3
  39. package/src/location.ts +124 -0
  40. package/src/matcher.ts +71 -0
  41. package/src/micro-app.ts +153 -0
  42. package/src/navigation.ts +202 -0
  43. package/src/options.ts +136 -0
  44. package/src/route-task.ts +102 -0
  45. package/src/route-transition.ts +480 -0
  46. package/src/route.ts +335 -0
  47. package/src/router-link.ts +241 -0
  48. package/src/router.ts +351 -467
  49. package/src/scroll.ts +106 -0
  50. package/src/types.ts +415 -0
  51. package/src/util.ts +184 -0
  52. package/dist/history/abstract.d.ts +0 -29
  53. package/dist/history/abstract.mjs +0 -107
  54. package/dist/history/base.d.ts +0 -79
  55. package/dist/history/base.mjs +0 -275
  56. package/dist/history/html.d.ts +0 -22
  57. package/dist/history/html.mjs +0 -181
  58. package/dist/history/index.d.ts +0 -7
  59. package/dist/history/index.mjs +0 -16
  60. package/dist/matcher/create-matcher.d.ts +0 -5
  61. package/dist/matcher/create-matcher.mjs +0 -218
  62. package/dist/matcher/create-matcher.spec.d.ts +0 -1
  63. package/dist/matcher/create-matcher.spec.mjs +0 -0
  64. package/dist/matcher/index.d.ts +0 -1
  65. package/dist/matcher/index.mjs +0 -1
  66. package/dist/task-pipe/index.d.ts +0 -1
  67. package/dist/task-pipe/index.mjs +0 -1
  68. package/dist/task-pipe/task.d.ts +0 -30
  69. package/dist/task-pipe/task.mjs +0 -66
  70. package/dist/utils/bom.d.ts +0 -5
  71. package/dist/utils/bom.mjs +0 -10
  72. package/dist/utils/encoding.d.ts +0 -48
  73. package/dist/utils/encoding.mjs +0 -44
  74. package/dist/utils/guards.d.ts +0 -9
  75. package/dist/utils/guards.mjs +0 -12
  76. package/dist/utils/index.d.ts +0 -7
  77. package/dist/utils/index.mjs +0 -27
  78. package/dist/utils/path.d.ts +0 -60
  79. package/dist/utils/path.mjs +0 -264
  80. package/dist/utils/path.spec.d.ts +0 -1
  81. package/dist/utils/path.spec.mjs +0 -30
  82. package/dist/utils/scroll.d.ts +0 -25
  83. package/dist/utils/scroll.mjs +0 -59
  84. package/dist/utils/utils.d.ts +0 -16
  85. package/dist/utils/utils.mjs +0 -11
  86. package/dist/utils/warn.d.ts +0 -2
  87. package/dist/utils/warn.mjs +0 -12
  88. package/src/history/abstract.ts +0 -149
  89. package/src/history/base.ts +0 -408
  90. package/src/history/html.ts +0 -231
  91. package/src/history/index.ts +0 -20
  92. package/src/matcher/create-matcher.spec.ts +0 -3
  93. package/src/matcher/create-matcher.ts +0 -293
  94. package/src/matcher/index.ts +0 -1
  95. package/src/task-pipe/index.ts +0 -1
  96. package/src/task-pipe/task.ts +0 -97
  97. package/src/utils/bom.ts +0 -14
  98. package/src/utils/encoding.ts +0 -153
  99. package/src/utils/guards.ts +0 -25
  100. package/src/utils/index.ts +0 -27
  101. package/src/utils/path.spec.ts +0 -44
  102. package/src/utils/path.ts +0 -397
  103. package/src/utils/scroll.ts +0 -120
  104. package/src/utils/utils.ts +0 -30
  105. package/src/utils/warn.ts +0 -13
package/src/router.ts CHANGED
@@ -1,521 +1,405 @@
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 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 { 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();
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
- 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
- );
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
- /* 已注册的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
- };
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
- afterEach(guard: NavigationGuardAfter) {
298
- this.guards.afterEach.push(guard);
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
- unBindAfterEach(guard: NavigationGuardAfter) {
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
- async replace(location: RouterRawLocation) {
314
- await this.history.replace(location);
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
- * 当前路由弹层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;
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
- isFrozen = false;
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
- freeze() {
358
- this.isFrozen = true;
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
- unfreeze() {
365
- this.isFrozen = false;
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
- * 打开路由弹层方法,会创建新的路由实例并调用注册的 register 方法
370
- * 服务端会使用 push 作为替代
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
- 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 });
248
+ public resolveLink(props: RouterLinkProps): RouterLinkResolved {
249
+ return createLinkResolver(this, props);
385
250
  }
386
251
 
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) || [];
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
- // 所有的 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
- }
258
+ const zIndex =
259
+ layerOptions.zIndex ?? this.parsedOptions.zIndex + LAYER_ID.next();
416
260
 
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
- }
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
- layerConfigList.forEach((id) => {
436
- const { router } = this.layerMap[id];
437
- if (activeId === id) {
438
- router.unfreeze();
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
- router.freeze();
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
- router.freeze();
451
- layer.destroyed = true;
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
- 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
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
- * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
486
- */
487
- pushWindow(location: RouterRawLocation) {
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
- * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
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
- go(delta = 0) {
500
- this.history.go(delta);
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
- forward() {
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
- /* 根据获取当前所有活跃的路由Record对象 */
514
- getRoutes() {
515
- return this.matcher.getRoutes();
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
- }