@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
@@ -0,0 +1,195 @@
1
+ import { PAGE_ID } from './increment-id';
2
+ import { RouterMode } from './types';
3
+ import type { Route, RouteState, RouterParsedOptions } from './types';
4
+
5
+ type NavigationSubscribe = (url: string, state: RouteState) => void;
6
+ type NavigationGoResult = null | {
7
+ type: 'success';
8
+ url: string;
9
+ state: RouteState;
10
+ };
11
+
12
+ const PAGE_ID_KEY = '__pageId__';
13
+
14
+ export class Navigation {
15
+ public readonly options: RouterParsedOptions;
16
+ private readonly _history: History | MemoryHistory;
17
+ private readonly _unSubscribePopState: () => void;
18
+ private _promiseResolve:
19
+ | ((url?: string | null, state?: RouteState) => void)
20
+ | null = null;
21
+
22
+ public constructor(
23
+ options: RouterParsedOptions,
24
+ onUpdated?: NavigationSubscribe
25
+ ) {
26
+ const history: History =
27
+ options.mode === RouterMode.history
28
+ ? window.history
29
+ : new MemoryHistory();
30
+ const onPopStateChange: NavigationSubscribe = (url, state) => {
31
+ const dispatchEvent = this._promiseResolve || onUpdated;
32
+ dispatchEvent?.(url, state);
33
+ };
34
+ const subscribePopState =
35
+ history instanceof MemoryHistory
36
+ ? history.onPopState(onPopStateChange)
37
+ : subscribeHtmlHistory(onPopStateChange);
38
+ this.options = options;
39
+ this._history = history;
40
+ this._unSubscribePopState = subscribePopState;
41
+ }
42
+ public get length(): number {
43
+ return this._history.length;
44
+ }
45
+
46
+ private _push(
47
+ history: History,
48
+ data: any,
49
+ url?: string | URL | null
50
+ ): RouteState {
51
+ const state = Object.freeze({
52
+ ...(data || {}),
53
+ [PAGE_ID_KEY]: PAGE_ID.next()
54
+ });
55
+ history.pushState(state, '', url);
56
+ return state;
57
+ }
58
+
59
+ private _replace(
60
+ history: History,
61
+ data: any,
62
+ url?: string | URL | null
63
+ ): RouteState {
64
+ const oldId = history.state?.[PAGE_ID_KEY];
65
+ const state = Object.freeze({
66
+ ...(data || {}),
67
+ [PAGE_ID_KEY]: typeof oldId === 'number' ? oldId : PAGE_ID.next()
68
+ });
69
+ history.replaceState(state, '', url);
70
+ return state;
71
+ }
72
+
73
+ public push(data: any, url?: string | URL | null): RouteState {
74
+ return this._push(this._history, data, url);
75
+ }
76
+
77
+ public replace(data: any, url?: string | URL | null): RouteState {
78
+ return this._replace(this._history, data, url);
79
+ }
80
+
81
+ public pushHistoryState(data: any, url?: string | URL | null) {
82
+ this._push(history, data, url);
83
+ }
84
+
85
+ public replaceHistoryState(data: any, url?: string | URL | null) {
86
+ this._replace(history, data, url);
87
+ }
88
+
89
+ public go(index: number): Promise<NavigationGoResult> {
90
+ if (this._promiseResolve) {
91
+ return Promise.resolve(null);
92
+ }
93
+ return new Promise<NavigationGoResult>((resolve) => {
94
+ this._promiseResolve = (url, state) => {
95
+ this._promiseResolve = null;
96
+ if (typeof url !== 'string') return resolve(null);
97
+ resolve({ type: 'success', url, state: state || {} });
98
+ };
99
+ setTimeout(this._promiseResolve, 80);
100
+ this._history.go(index);
101
+ });
102
+ }
103
+ public forward(): Promise<NavigationGoResult> {
104
+ return this.go(1);
105
+ }
106
+ public back(): Promise<NavigationGoResult> {
107
+ return this.go(-1);
108
+ }
109
+
110
+ public destroy() {
111
+ this._promiseResolve?.();
112
+ this._unSubscribePopState();
113
+ }
114
+ }
115
+
116
+ export class MemoryHistory implements History {
117
+ private _entries: Array<{ state: any; url: string }> = [];
118
+ private _index = -1;
119
+ private get _curEntry() {
120
+ const idx = this._index;
121
+ if (idx < 0 || idx >= this.length) return null;
122
+ return this._entries[idx];
123
+ }
124
+ private readonly _popStateCbs = new Set<NavigationSubscribe>();
125
+ public scrollRestoration: ScrollRestoration = 'auto';
126
+ // Return null when no current entry to align with browser history.state behavior
127
+ // Browser history.state can be null when no state was provided
128
+ public get state() {
129
+ return this._curEntry?.state ?? null;
130
+ }
131
+ public get url() {
132
+ return this._curEntry?.url ?? '';
133
+ }
134
+
135
+ constructor() {
136
+ this.pushState(null, '', '/');
137
+ }
138
+ public get length() {
139
+ return this._entries.length;
140
+ }
141
+
142
+ public pushState(
143
+ data: any,
144
+ unused: string,
145
+ url?: string | URL | null
146
+ ): void {
147
+ // Remove all entries after the current position
148
+ this._entries.splice(this._index + 1);
149
+ this._entries.push({ state: data, url: url?.toString() ?? this.url });
150
+ this._index = this._entries.length - 1;
151
+ }
152
+
153
+ public replaceState(
154
+ data: any,
155
+ unused: string,
156
+ url?: string | URL | null
157
+ ): void {
158
+ const curEntry = this._curEntry;
159
+ if (!curEntry) return;
160
+ curEntry.state = { ...data };
161
+ if (url) curEntry.url = url.toString();
162
+ }
163
+
164
+ public back(): void {
165
+ this.go(-1);
166
+ }
167
+ public forward(): void {
168
+ this.go(1);
169
+ }
170
+ public go(delta?: number): void {
171
+ if (!delta) return;
172
+ const newIdx = this._index + delta;
173
+ if (newIdx < 0 || newIdx >= this.length) return;
174
+ this._index = newIdx;
175
+ const entry = this._curEntry!;
176
+ // Simulate the async popstate event of html history as closely as possible
177
+ setTimeout(() => {
178
+ this._popStateCbs.forEach((cb) => cb(entry.url, entry.state));
179
+ });
180
+ }
181
+
182
+ public onPopState(cb: NavigationSubscribe) {
183
+ if (typeof cb !== 'function') return () => {};
184
+ this._popStateCbs.add(cb);
185
+ return () => this._popStateCbs.delete(cb);
186
+ }
187
+ }
188
+
189
+ function subscribeHtmlHistory(cb: NavigationSubscribe) {
190
+ // Use history.state || {} to handle null state from browser history
191
+ // Browser history.state can be null, but we normalize it to empty object
192
+ const wrapper = () => cb(location.href, history.state || {});
193
+ window.addEventListener('popstate', wrapper);
194
+ return () => window.removeEventListener('popstate', wrapper);
195
+ }
package/src/options.ts ADDED
@@ -0,0 +1,131 @@
1
+ import { createMatcher } from './matcher';
2
+ import type { Router } from './router';
3
+ import { RouterMode } from './types';
4
+ import type { Route, RouterOptions, RouterParsedOptions } from './types';
5
+ import { isBrowser } from './util';
6
+
7
+ /**
8
+ * Gets the base URL object for the router.
9
+ * @param options - Router options.
10
+ * @returns The processed URL object.
11
+ */
12
+ function getBaseUrl(options: RouterOptions): URL {
13
+ // Determine the URL source
14
+ let sourceUrl: string | URL;
15
+
16
+ if (options.base) {
17
+ sourceUrl = options.base;
18
+ } else if (isBrowser) {
19
+ sourceUrl = location.origin;
20
+ } else if (options.req) {
21
+ // Server-side: try to get it from the req object
22
+ const { req } = options;
23
+ const protocol =
24
+ req.headers['x-forwarded-proto'] ||
25
+ req.headers['x-forwarded-protocol'] ||
26
+ (req.socket && 'encrypted' in req.socket && req.socket.encrypted
27
+ ? 'https'
28
+ : 'http');
29
+ const host =
30
+ req.headers['x-forwarded-host'] ||
31
+ req.headers.host ||
32
+ req.headers['x-real-ip'] ||
33
+ 'localhost';
34
+ const port = req.headers['x-forwarded-port'];
35
+ const path = req.url || '';
36
+
37
+ sourceUrl = `${protocol}://${host}${port ? `:${port}` : ''}${path}`;
38
+ } else {
39
+ sourceUrl = 'https://www.esmnext.com/';
40
+ }
41
+
42
+ // Parse the URL, falling back to a default on failure.
43
+ // Use a try-catch block with the standard URL constructor for robustness.
44
+ let base: URL;
45
+ try {
46
+ base = new URL('.', sourceUrl);
47
+ } catch (e) {
48
+ console.warn(
49
+ `Failed to parse base URL '${sourceUrl}', using default: https://www.esmnext.com/`
50
+ );
51
+ base = new URL('https://www.esmnext.com/');
52
+ }
53
+
54
+ // Clean up and return
55
+ base.search = base.hash = '';
56
+ return base;
57
+ }
58
+
59
+ export function parsedOptions(
60
+ options: RouterOptions = {}
61
+ ): RouterParsedOptions {
62
+ const base = getBaseUrl(options);
63
+ const routes = Array.from(options.routes ?? []);
64
+ return Object.freeze<RouterParsedOptions>({
65
+ rootStyle: options.rootStyle || false,
66
+ root: options.root || '',
67
+ context: options.context || {},
68
+ req: options.req || null,
69
+ res: options.res || null,
70
+ layer: options.layer || false,
71
+ zIndex: options.zIndex || 10000,
72
+ base,
73
+ mode: isBrowser
74
+ ? (options.mode ?? RouterMode.history)
75
+ : RouterMode.memory,
76
+ routes,
77
+ apps:
78
+ typeof options.apps === 'function'
79
+ ? options.apps
80
+ : Object.assign({}, options.apps),
81
+ matcher: createMatcher(routes),
82
+ normalizeURL: options.normalizeURL ?? ((url) => url),
83
+ fallback: options.fallback ?? fallback,
84
+ handleBackBoundary: options.handleBackBoundary ?? (() => {}),
85
+ handleLayerClose: options.handleLayerClose ?? (() => {})
86
+ });
87
+ }
88
+
89
+ export function fallback(to: Route, from: Route | null, router: Router) {
90
+ const href = to.url.href;
91
+
92
+ // Server-side environment: handle application-level redirects and status codes
93
+ if (!isBrowser && router?.res) {
94
+ // Determine status code: prioritize route-specified code, default to 302 temporary redirect
95
+ let statusCode = 302;
96
+
97
+ // Validate redirect status code (3xx series)
98
+ const validRedirectCodes = [300, 301, 302, 303, 304, 307, 308];
99
+ if (to.statusCode && validRedirectCodes.includes(to.statusCode)) {
100
+ statusCode = to.statusCode;
101
+ } else if (to.statusCode) {
102
+ console.warn(
103
+ `Invalid redirect status code ${to.statusCode}, using default 302`
104
+ );
105
+ }
106
+
107
+ // Set redirect response
108
+ router.res.statusCode = statusCode;
109
+ router.res.setHeader('Location', href);
110
+ router.res.end();
111
+ return;
112
+ }
113
+
114
+ // Client-side environment: handle browser navigation
115
+ if (isBrowser) {
116
+ if (to.isPush) {
117
+ try {
118
+ const newWindow = window.open(href);
119
+ if (!newWindow) {
120
+ location.href = href;
121
+ } else {
122
+ newWindow.opener = null; // Sever the relationship between the new window and the current one
123
+ }
124
+ return newWindow;
125
+ } catch {}
126
+ }
127
+ location.href = href;
128
+ }
129
+
130
+ // Do nothing in a server environment without a res context
131
+ }