@esmx/router 3.0.0-rc.12

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 (61) hide show
  1. package/dist/history/abstract.d.ts +29 -0
  2. package/dist/history/abstract.mjs +107 -0
  3. package/dist/history/base.d.ts +79 -0
  4. package/dist/history/base.mjs +275 -0
  5. package/dist/history/html.d.ts +22 -0
  6. package/dist/history/html.mjs +181 -0
  7. package/dist/history/index.d.ts +7 -0
  8. package/dist/history/index.mjs +16 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.mjs +3 -0
  11. package/dist/matcher/create-matcher.d.ts +5 -0
  12. package/dist/matcher/create-matcher.mjs +218 -0
  13. package/dist/matcher/create-matcher.spec.d.ts +1 -0
  14. package/dist/matcher/create-matcher.spec.mjs +0 -0
  15. package/dist/matcher/index.d.ts +1 -0
  16. package/dist/matcher/index.mjs +1 -0
  17. package/dist/router.d.ts +111 -0
  18. package/dist/router.mjs +399 -0
  19. package/dist/task-pipe/index.d.ts +1 -0
  20. package/dist/task-pipe/index.mjs +1 -0
  21. package/dist/task-pipe/task.d.ts +30 -0
  22. package/dist/task-pipe/task.mjs +66 -0
  23. package/dist/utils/bom.d.ts +5 -0
  24. package/dist/utils/bom.mjs +10 -0
  25. package/dist/utils/encoding.d.ts +48 -0
  26. package/dist/utils/encoding.mjs +44 -0
  27. package/dist/utils/guards.d.ts +9 -0
  28. package/dist/utils/guards.mjs +12 -0
  29. package/dist/utils/index.d.ts +7 -0
  30. package/dist/utils/index.mjs +27 -0
  31. package/dist/utils/path.d.ts +60 -0
  32. package/dist/utils/path.mjs +264 -0
  33. package/dist/utils/path.spec.d.ts +1 -0
  34. package/dist/utils/path.spec.mjs +30 -0
  35. package/dist/utils/scroll.d.ts +25 -0
  36. package/dist/utils/scroll.mjs +59 -0
  37. package/dist/utils/utils.d.ts +16 -0
  38. package/dist/utils/utils.mjs +11 -0
  39. package/dist/utils/warn.d.ts +2 -0
  40. package/dist/utils/warn.mjs +12 -0
  41. package/package.json +66 -0
  42. package/src/history/abstract.ts +149 -0
  43. package/src/history/base.ts +408 -0
  44. package/src/history/html.ts +231 -0
  45. package/src/history/index.ts +20 -0
  46. package/src/index.ts +3 -0
  47. package/src/matcher/create-matcher.spec.ts +3 -0
  48. package/src/matcher/create-matcher.ts +293 -0
  49. package/src/matcher/index.ts +1 -0
  50. package/src/router.ts +521 -0
  51. package/src/task-pipe/index.ts +1 -0
  52. package/src/task-pipe/task.ts +97 -0
  53. package/src/utils/bom.ts +14 -0
  54. package/src/utils/encoding.ts +153 -0
  55. package/src/utils/guards.ts +25 -0
  56. package/src/utils/index.ts +27 -0
  57. package/src/utils/path.spec.ts +44 -0
  58. package/src/utils/path.ts +397 -0
  59. package/src/utils/scroll.ts +120 -0
  60. package/src/utils/utils.ts +30 -0
  61. package/src/utils/warn.ts +13 -0
package/src/router.ts ADDED
@@ -0,0 +1,521 @@
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
23
+ } 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;
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
+ );
190
+ }
191
+
192
+ /* 解析路由 */
193
+ resolve(location: RouterRawLocation): RouteRecord {
194
+ return this.history.resolve(location);
195
+ }
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;
253
+ }
254
+
255
+ /**
256
+ * 卸载方法
257
+ */
258
+ async destroy() {
259
+ // this.history.destroy();
260
+ }
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
+ };
274
+ }
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);
288
+ }
289
+
290
+ /* 卸载全局路由前置守卫 */
291
+ unBindBeforeEach(guard: NavigationGuard) {
292
+ const i = this.guards.beforeEach.findIndex((item) => item === guard);
293
+ this.guards.beforeEach.splice(i, 1);
294
+ }
295
+
296
+ /* 注册全局路由后置守卫 */
297
+ afterEach(guard: NavigationGuardAfter) {
298
+ this.guards.afterEach.push(guard);
299
+ }
300
+
301
+ /* 卸载全局路由后置守卫 */
302
+ unBindAfterEach(guard: NavigationGuardAfter) {
303
+ const i = this.guards.afterEach.findIndex((item) => item === guard);
304
+ this.guards.afterEach.splice(i, 1);
305
+ }
306
+
307
+ /* 路由跳转方法,会创建新的历史记录 */
308
+ async push(location: RouterRawLocation) {
309
+ await this.history.push(location);
310
+ }
311
+
312
+ /* 路由跳转方法,会替换当前的历史记录 */
313
+ async replace(location: RouterRawLocation) {
314
+ await this.history.replace(location);
315
+ }
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;
346
+ }
347
+ > = {};
348
+
349
+ /**
350
+ * 路由是否冻结
351
+ */
352
+ isFrozen = false;
353
+
354
+ /**
355
+ * 路由冻结方法
356
+ */
357
+ freeze() {
358
+ this.isFrozen = true;
359
+ }
360
+
361
+ /**
362
+ * 路由解冻方法
363
+ */
364
+ unfreeze() {
365
+ this.isFrozen = false;
366
+ }
367
+
368
+ /**
369
+ * 打开路由弹层方法,会创建新的路由实例并调用注册的 register 方法
370
+ * 服务端会使用 push 作为替代
371
+ */
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 });
385
+ }
386
+
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) || [];
396
+
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
+ }
416
+
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
+ }
427
+ });
428
+ if (createList.length === 0 && destroyList.length === 0) {
429
+ // 没有需要创建的 layerId 列表 并且 没有需要销毁的 layerId 列表时跳出
430
+ return false;
431
+ }
432
+
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();
449
+ 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;
462
+ }
463
+ });
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
480
+ };
481
+ window.history.replaceState(state, '', route.fullPath);
482
+ }
483
+
484
+ /**
485
+ * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
486
+ */
487
+ pushWindow(location: RouterRawLocation) {
488
+ this.history.pushWindow(location);
489
+ }
490
+
491
+ /**
492
+ * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
493
+ */
494
+ replaceWindow(location: RouterRawLocation) {
495
+ this.history.replaceWindow(location);
496
+ }
497
+
498
+ /* 前往特定路由历史记录的方法,可以在历史记录前后移动 */
499
+ go(delta = 0) {
500
+ this.history.go(delta);
501
+ }
502
+
503
+ /* 路由历史记录前进方法 */
504
+ forward() {
505
+ this.history.forward();
506
+ }
507
+
508
+ /* 路由历史记录后退方法 */
509
+ back() {
510
+ this.history.back();
511
+ }
512
+
513
+ /* 根据获取当前所有活跃的路由Record对象 */
514
+ getRoutes() {
515
+ return this.matcher.getRoutes();
516
+ }
517
+ }
518
+
519
+ export function createRouter(options: RouterOptions): RouterInstance {
520
+ return new Router(options);
521
+ }
@@ -0,0 +1 @@
1
+ export { Tasks, createTasks } from './task';
@@ -0,0 +1,97 @@
1
+ import type { Awaitable } from '../types';
2
+ import { warn } from '../utils';
3
+
4
+ /**
5
+ * 创建可操作的任务队列
6
+ */
7
+
8
+ type func = (...args: any) => any;
9
+
10
+ /**
11
+ * 任务状态
12
+ */
13
+ export enum TaskStatus {
14
+ INITIAL = 'initial',
15
+ RUNNING = 'running',
16
+ FINISHED = 'finished',
17
+ ERROR = 'error',
18
+ ABORTED = 'aborted'
19
+ }
20
+
21
+ export class Tasks<T extends func = func> {
22
+ protected handlers: T[] = [];
23
+
24
+ public add(handler: T | T[]) {
25
+ const params: T[] = handler instanceof Array ? handler : [handler];
26
+ this.handlers.push(...params);
27
+ }
28
+
29
+ public reset() {
30
+ this.handlers = [];
31
+ }
32
+
33
+ get list() {
34
+ return this.handlers;
35
+ }
36
+
37
+ get length() {
38
+ return this.handlers.length;
39
+ }
40
+
41
+ status: TaskStatus = TaskStatus.INITIAL;
42
+
43
+ public async run({
44
+ cb,
45
+ final
46
+ }: {
47
+ cb?: (res: Awaited<ReturnType<T>>) => Awaitable<void>;
48
+ final?: () => Awaitable<void>;
49
+ } = {}) {
50
+ if (this.status !== 'initial') {
51
+ if (process.env.NODE_ENV !== 'production') {
52
+ warn(`task start failed in status ${this.status}`);
53
+ }
54
+ return;
55
+ }
56
+
57
+ this.status = TaskStatus.RUNNING;
58
+
59
+ for await (const handler of this.list) {
60
+ if ((this.status as TaskStatus) === TaskStatus.ABORTED) {
61
+ return;
62
+ }
63
+ if (typeof handler === 'function') {
64
+ try {
65
+ const res = await handler();
66
+ cb && (await cb(res));
67
+ } catch (error) {
68
+ // if (process.env.NODE_ENV !== 'production') {
69
+ warn('task error:', error);
70
+ // }
71
+ this.status = TaskStatus.ERROR;
72
+ }
73
+ } else {
74
+ // if (process.env.NODE_ENV !== 'production') {
75
+ warn('task is not a function', handler);
76
+ // }
77
+ }
78
+ }
79
+ if ((this.status as TaskStatus) !== TaskStatus.RUNNING) return;
80
+ final && (await final());
81
+ this.status = TaskStatus.FINISHED;
82
+ }
83
+
84
+ public abort() {
85
+ if (
86
+ process.env.NODE_ENV !== 'production' &&
87
+ this.status === TaskStatus.RUNNING
88
+ ) {
89
+ warn('abort task when task is running');
90
+ }
91
+ this.status = TaskStatus.ABORTED;
92
+ }
93
+ }
94
+
95
+ export function createTasks<T extends func = func>() {
96
+ return new Tasks<T>();
97
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 在新窗口打开页面,如果被拦截,则会降级到当前窗口打开
3
+ * @param url 打开的地址
4
+ */
5
+ export function openWindow(url: string, target?: string) {
6
+ try {
7
+ const newWindow = window.open(url, target);
8
+ if (!newWindow) {
9
+ location.href = url;
10
+ }
11
+ } catch (e) {
12
+ location.href = url;
13
+ }
14
+ }