@esmx/router 3.0.0-rc.18 → 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.
- package/LICENSE +1 -1
- package/README.md +70 -0
- package/README.zh-CN.md +70 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +61 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +14 -3
- package/dist/index.test.mjs +8 -0
- package/dist/location.d.ts +15 -0
- package/dist/location.mjs +53 -0
- package/dist/location.test.d.ts +8 -0
- package/dist/location.test.mjs +370 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/matcher.mjs +44 -0
- package/dist/matcher.test.mjs +1492 -0
- package/dist/micro-app.d.ts +18 -0
- package/dist/micro-app.dom.test.d.ts +1 -0
- package/dist/micro-app.dom.test.mjs +532 -0
- package/dist/micro-app.mjs +80 -0
- package/dist/navigation.d.ts +43 -0
- package/dist/navigation.mjs +143 -0
- package/dist/navigation.test.d.ts +1 -0
- package/dist/navigation.test.mjs +681 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +88 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +75 -0
- package/dist/route-task.test.d.ts +1 -0
- package/dist/route-task.test.mjs +673 -0
- package/dist/route-transition.d.ts +53 -0
- package/dist/route-transition.mjs +307 -0
- package/dist/route-transition.test.d.ts +1 -0
- package/dist/route-transition.test.mjs +146 -0
- package/dist/route.d.ts +72 -0
- package/dist/route.mjs +194 -0
- package/dist/route.test.d.ts +1 -0
- package/dist/route.test.mjs +1664 -0
- package/dist/router-back.test.d.ts +1 -0
- package/dist/router-back.test.mjs +361 -0
- package/dist/router-forward.test.d.ts +1 -0
- package/dist/router-forward.test.mjs +376 -0
- package/dist/router-go.test.d.ts +1 -0
- package/dist/router-go.test.mjs +73 -0
- package/dist/router-guards-cleanup.test.d.ts +1 -0
- package/dist/router-guards-cleanup.test.mjs +437 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +126 -0
- package/dist/router-push.test.d.ts +1 -0
- package/dist/router-push.test.mjs +115 -0
- package/dist/router-replace.test.d.ts +1 -0
- package/dist/router-replace.test.mjs +114 -0
- package/dist/router-resolve.test.d.ts +1 -0
- package/dist/router-resolve.test.mjs +393 -0
- package/dist/router-restart-app.dom.test.d.ts +1 -0
- package/dist/router-restart-app.dom.test.mjs +616 -0
- package/dist/router-window-navigation.test.d.ts +1 -0
- package/dist/router-window-navigation.test.mjs +359 -0
- package/dist/router.d.ts +109 -102
- package/dist/router.mjs +260 -361
- package/dist/types.d.ts +246 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.mjs +53 -0
- package/dist/util.test.d.ts +1 -0
- package/dist/util.test.mjs +1020 -0
- package/package.json +10 -13
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.test.ts +9 -0
- package/src/index.ts +54 -3
- package/src/location.test.ts +406 -0
- package/src/location.ts +96 -0
- package/src/matcher.test.ts +1685 -0
- package/src/matcher.ts +59 -0
- package/src/micro-app.dom.test.ts +708 -0
- package/src/micro-app.ts +101 -0
- package/src/navigation.test.ts +858 -0
- package/src/navigation.ts +195 -0
- package/src/options.ts +131 -0
- package/src/route-task.test.ts +901 -0
- package/src/route-task.ts +105 -0
- package/src/route-transition.test.ts +178 -0
- package/src/route-transition.ts +425 -0
- package/src/route.test.ts +2014 -0
- package/src/route.ts +308 -0
- package/src/router-back.test.ts +487 -0
- package/src/router-forward.test.ts +506 -0
- package/src/router-go.test.ts +91 -0
- package/src/router-guards-cleanup.test.ts +595 -0
- package/src/router-link.ts +235 -0
- package/src/router-push.test.ts +140 -0
- package/src/router-replace.test.ts +139 -0
- package/src/router-resolve.test.ts +475 -0
- package/src/router-restart-app.dom.test.ts +783 -0
- package/src/router-window-navigation.test.ts +457 -0
- package/src/router.ts +289 -470
- package/src/types.ts +341 -0
- package/src/util.test.ts +1262 -0
- package/src/util.ts +116 -0
- package/dist/history/abstract.d.ts +0 -29
- package/dist/history/abstract.mjs +0 -107
- package/dist/history/base.d.ts +0 -79
- package/dist/history/base.mjs +0 -275
- package/dist/history/html.d.ts +0 -30
- package/dist/history/html.mjs +0 -183
- package/dist/history/index.d.ts +0 -7
- package/dist/history/index.mjs +0 -16
- package/dist/matcher/create-matcher.d.ts +0 -5
- package/dist/matcher/create-matcher.mjs +0 -218
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +0 -1
- package/dist/matcher/index.mjs +0 -1
- package/dist/task-pipe/index.d.ts +0 -1
- package/dist/task-pipe/index.mjs +0 -1
- package/dist/task-pipe/task.d.ts +0 -30
- package/dist/task-pipe/task.mjs +0 -66
- package/dist/types/index.d.ts +0 -694
- package/dist/types/index.mjs +0 -6
- package/dist/utils/bom.d.ts +0 -5
- package/dist/utils/bom.mjs +0 -10
- package/dist/utils/encoding.d.ts +0 -48
- package/dist/utils/encoding.mjs +0 -44
- package/dist/utils/guards.d.ts +0 -9
- package/dist/utils/guards.mjs +0 -12
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.mjs +0 -27
- package/dist/utils/path.d.ts +0 -60
- package/dist/utils/path.mjs +0 -282
- package/dist/utils/path.spec.mjs +0 -27
- package/dist/utils/scroll.d.ts +0 -25
- package/dist/utils/scroll.mjs +0 -59
- package/dist/utils/utils.d.ts +0 -16
- package/dist/utils/utils.mjs +0 -11
- package/dist/utils/warn.d.ts +0 -2
- package/dist/utils/warn.mjs +0 -12
- package/src/history/abstract.ts +0 -149
- package/src/history/base.ts +0 -408
- package/src/history/html.ts +0 -228
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -292
- package/src/matcher/index.ts +0 -1
- package/src/task-pipe/index.ts +0 -1
- package/src/task-pipe/task.ts +0 -97
- package/src/types/index.ts +0 -858
- package/src/utils/bom.ts +0 -14
- package/src/utils/encoding.ts +0 -153
- package/src/utils/guards.ts +0 -25
- package/src/utils/index.ts +0 -27
- package/src/utils/path.spec.ts +0 -32
- package/src/utils/path.ts +0 -418
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
- /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
- /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
package/src/route.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { parseLocation } from './location';
|
|
3
|
+
import { parsedOptions } from './options';
|
|
4
|
+
import type { Router } from './router';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type RouteConfirmHook,
|
|
8
|
+
type RouteHandleHook,
|
|
9
|
+
type RouteHandleResult,
|
|
10
|
+
type RouteLayerOptions,
|
|
11
|
+
type RouteLocationInput,
|
|
12
|
+
type RouteMatchResult,
|
|
13
|
+
type RouteMeta,
|
|
14
|
+
type RouteOptions,
|
|
15
|
+
type RouteParsedConfig,
|
|
16
|
+
type RouteState,
|
|
17
|
+
RouteType,
|
|
18
|
+
type RouterParsedOptions
|
|
19
|
+
} from './types';
|
|
20
|
+
import { isNonEmptyPlainObject, isPlainObject } from './util';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for non-enumerable properties in Route class
|
|
24
|
+
* These properties will be hidden during object traversal and serialization
|
|
25
|
+
*/
|
|
26
|
+
export const NON_ENUMERABLE_PROPERTIES = [
|
|
27
|
+
// Private fields - internal implementation details
|
|
28
|
+
'_handled',
|
|
29
|
+
'_handle',
|
|
30
|
+
'_handleResult',
|
|
31
|
+
'_options',
|
|
32
|
+
|
|
33
|
+
// SSR-specific properties - meaningless in client environment
|
|
34
|
+
'req',
|
|
35
|
+
'res',
|
|
36
|
+
|
|
37
|
+
// Internal context - used by framework internally
|
|
38
|
+
'context',
|
|
39
|
+
|
|
40
|
+
// Status code - internal status information
|
|
41
|
+
'statusCode',
|
|
42
|
+
|
|
43
|
+
// Route behavior overrides - framework internal logic
|
|
44
|
+
'confirm',
|
|
45
|
+
|
|
46
|
+
// Layer configuration - used for layer routes
|
|
47
|
+
'layer'
|
|
48
|
+
] satisfies string[];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Append user-provided parameters to URL path
|
|
52
|
+
* @param match Route matching result
|
|
53
|
+
* @param toInput User-provided route location object
|
|
54
|
+
* @param base Base URL
|
|
55
|
+
* @param to Current parsed URL object
|
|
56
|
+
*/
|
|
57
|
+
export function applyRouteParams(
|
|
58
|
+
match: RouteMatchResult,
|
|
59
|
+
toInput: RouteLocationInput,
|
|
60
|
+
base: URL,
|
|
61
|
+
to: URL
|
|
62
|
+
): void {
|
|
63
|
+
if (
|
|
64
|
+
!isPlainObject(toInput) ||
|
|
65
|
+
!isNonEmptyPlainObject(toInput.params) ||
|
|
66
|
+
!match.matches.length
|
|
67
|
+
) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get the last matched route configuration
|
|
72
|
+
const lastMatch = match.matches[match.matches.length - 1];
|
|
73
|
+
|
|
74
|
+
// Split current path
|
|
75
|
+
const current = to.pathname.split('/');
|
|
76
|
+
|
|
77
|
+
// Compile new path with user parameters and split
|
|
78
|
+
const next = new URL(
|
|
79
|
+
lastMatch.compile(toInput.params).substring(1),
|
|
80
|
+
base
|
|
81
|
+
).pathname.split('/');
|
|
82
|
+
|
|
83
|
+
// Replace current path segments with new path segments
|
|
84
|
+
next.forEach((item, index) => {
|
|
85
|
+
current[index] = item || current[index];
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Update URL path
|
|
89
|
+
to.pathname = current.join('/');
|
|
90
|
+
|
|
91
|
+
// Merge parameters to match result, user parameters take precedence
|
|
92
|
+
Object.assign(match.params, toInput.params);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Route class provides complete route object functionality
|
|
97
|
+
*/
|
|
98
|
+
export class Route {
|
|
99
|
+
// Private fields for handle validation
|
|
100
|
+
private _handled = false;
|
|
101
|
+
private _handle: RouteHandleHook | null = null;
|
|
102
|
+
private _handleResult: RouteHandleResult | null = null;
|
|
103
|
+
private readonly _options: RouterParsedOptions;
|
|
104
|
+
|
|
105
|
+
// Public properties
|
|
106
|
+
public readonly statusCode: number | null = null;
|
|
107
|
+
public readonly state: RouteState;
|
|
108
|
+
public readonly keepScrollPosition: boolean;
|
|
109
|
+
/** Custom confirm handler that overrides default route-transition confirm logic */
|
|
110
|
+
public readonly confirm: RouteConfirmHook | null;
|
|
111
|
+
/** Layer configuration for layer routes */
|
|
112
|
+
public readonly layer: RouteLayerOptions | null;
|
|
113
|
+
|
|
114
|
+
// Read-only properties
|
|
115
|
+
public readonly type: RouteType;
|
|
116
|
+
public readonly req: IncomingMessage | null;
|
|
117
|
+
public readonly res: ServerResponse | null;
|
|
118
|
+
public readonly context: Record<string | symbol, any>;
|
|
119
|
+
public readonly url: URL;
|
|
120
|
+
public readonly path: string;
|
|
121
|
+
public readonly fullPath: string;
|
|
122
|
+
public readonly hash: string;
|
|
123
|
+
public readonly params: Record<string, string> = {};
|
|
124
|
+
public readonly query: Record<string, string | undefined> = {};
|
|
125
|
+
public readonly queryArray: Record<string, string[] | undefined> = {};
|
|
126
|
+
public readonly meta: RouteMeta;
|
|
127
|
+
public readonly matched: readonly RouteParsedConfig[];
|
|
128
|
+
public readonly config: RouteParsedConfig | null;
|
|
129
|
+
|
|
130
|
+
constructor(routeOptions: Partial<RouteOptions> = {}) {
|
|
131
|
+
const {
|
|
132
|
+
toType = RouteType.push,
|
|
133
|
+
toInput = '/',
|
|
134
|
+
from = null,
|
|
135
|
+
options = parsedOptions()
|
|
136
|
+
} = routeOptions;
|
|
137
|
+
|
|
138
|
+
this._options = options;
|
|
139
|
+
this.type = toType;
|
|
140
|
+
this.req = options.req;
|
|
141
|
+
this.res = options.res;
|
|
142
|
+
this.context = options.context;
|
|
143
|
+
|
|
144
|
+
const base = options.base;
|
|
145
|
+
const to = options.normalizeURL(parseLocation(toInput, base), from);
|
|
146
|
+
const isSameOrigin = to.origin === base.origin;
|
|
147
|
+
const isSameBase = to.pathname.startsWith(base.pathname);
|
|
148
|
+
const match =
|
|
149
|
+
isSameOrigin && isSameBase ? options.matcher(to, base) : null;
|
|
150
|
+
|
|
151
|
+
this.url = to;
|
|
152
|
+
this.path = match
|
|
153
|
+
? to.pathname.substring(base.pathname.length - 1)
|
|
154
|
+
: to.pathname;
|
|
155
|
+
this.fullPath = (match ? this.path : to.pathname) + to.search + to.hash;
|
|
156
|
+
this.matched = match ? match.matches : Object.freeze([]);
|
|
157
|
+
this.keepScrollPosition = isPlainObject(toInput)
|
|
158
|
+
? Boolean(toInput.keepScrollPosition)
|
|
159
|
+
: false;
|
|
160
|
+
this.confirm =
|
|
161
|
+
isPlainObject(toInput) && toInput.confirm ? toInput.confirm : null;
|
|
162
|
+
this.layer =
|
|
163
|
+
toType === RouteType.pushLayer &&
|
|
164
|
+
isPlainObject(toInput) &&
|
|
165
|
+
toInput.layer
|
|
166
|
+
? toInput.layer
|
|
167
|
+
: null;
|
|
168
|
+
this.config =
|
|
169
|
+
this.matched.length > 0
|
|
170
|
+
? this.matched[this.matched.length - 1]
|
|
171
|
+
: null;
|
|
172
|
+
this.meta = this.config?.meta || {};
|
|
173
|
+
|
|
174
|
+
// Initialize state object - create new local object, merge externally passed state
|
|
175
|
+
const state: RouteState = {};
|
|
176
|
+
if (isPlainObject(toInput) && toInput.state) {
|
|
177
|
+
Object.assign(state, toInput.state);
|
|
178
|
+
}
|
|
179
|
+
this.state = state;
|
|
180
|
+
|
|
181
|
+
// Process query parameters
|
|
182
|
+
for (const key of new Set(to.searchParams.keys())) {
|
|
183
|
+
this.query[key] = to.searchParams.get(key)!;
|
|
184
|
+
this.queryArray[key] = to.searchParams.getAll(key);
|
|
185
|
+
}
|
|
186
|
+
this.hash = to.hash;
|
|
187
|
+
|
|
188
|
+
// Apply user-provided route parameters (if match is successful)
|
|
189
|
+
if (match) {
|
|
190
|
+
applyRouteParams(match, toInput, base, to);
|
|
191
|
+
// Assign matched parameters to route object
|
|
192
|
+
Object.assign(this.params, match.params);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Set status code
|
|
196
|
+
// Prioritize user-provided statusCode
|
|
197
|
+
if (isPlainObject(toInput) && typeof toInput.statusCode === 'number') {
|
|
198
|
+
this.statusCode = toInput.statusCode;
|
|
199
|
+
}
|
|
200
|
+
// If statusCode is not provided, keep default null value
|
|
201
|
+
|
|
202
|
+
// Configure property enumerability
|
|
203
|
+
// Set internal implementation details as non-enumerable, keep user-common properties enumerable
|
|
204
|
+
// Set specified properties as non-enumerable according to configuration
|
|
205
|
+
for (const property of NON_ENUMERABLE_PROPERTIES) {
|
|
206
|
+
Object.defineProperty(this, property, { enumerable: false });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get isPush(): boolean {
|
|
211
|
+
return this.type.startsWith('push');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// handle related getter/setter
|
|
215
|
+
get handle(): RouteHandleHook | null {
|
|
216
|
+
return this._handle;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
set handle(val: RouteHandleHook | null) {
|
|
220
|
+
this.setHandle(val);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
get handleResult(): RouteHandleResult | null {
|
|
224
|
+
return this._handleResult;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
set handleResult(val: RouteHandleResult | null) {
|
|
228
|
+
this._handleResult = val;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set handle function with validation logic wrapper
|
|
233
|
+
*/
|
|
234
|
+
setHandle(val: RouteHandleHook | null): void {
|
|
235
|
+
if (typeof val !== 'function') {
|
|
236
|
+
this._handle = null;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const self = this;
|
|
240
|
+
this._handle = function handle(
|
|
241
|
+
this: Route,
|
|
242
|
+
to: Route,
|
|
243
|
+
from: Route | null,
|
|
244
|
+
router: Router
|
|
245
|
+
) {
|
|
246
|
+
if (self._handled) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
'Route handle hook can only be called once per navigation'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
self._handled = true;
|
|
252
|
+
return val.call(this, to, from, router);
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Apply navigation-generated state to current route
|
|
258
|
+
* Used by route handlers to add system state like pageId
|
|
259
|
+
* @param navigationState Navigation-generated state to apply
|
|
260
|
+
*/
|
|
261
|
+
applyNavigationState(navigationState: Partial<RouteState>): void {
|
|
262
|
+
Object.assign(this.state, navigationState);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Sync all properties of current route to target route object
|
|
267
|
+
* Used for route object updates in reactive systems
|
|
268
|
+
* @param targetRoute Target route object
|
|
269
|
+
*/
|
|
270
|
+
syncTo(targetRoute: Route): void {
|
|
271
|
+
// Copy enumerable properties
|
|
272
|
+
Object.assign(targetRoute, this);
|
|
273
|
+
|
|
274
|
+
// Copy non-enumerable properties - type-safe property copying
|
|
275
|
+
for (const property of NON_ENUMERABLE_PROPERTIES) {
|
|
276
|
+
if (!(property in this && property in targetRoute)) continue;
|
|
277
|
+
// Use Reflect.set for type-safe property setting
|
|
278
|
+
const value = Reflect.get(this, property);
|
|
279
|
+
Reflect.set(targetRoute, property, value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Clone current route instance
|
|
285
|
+
* Returns a new Route instance with same configuration and state
|
|
286
|
+
*/
|
|
287
|
+
clone(): Route {
|
|
288
|
+
// Reconstruct route object, passing current state and confirm handler
|
|
289
|
+
const toInput: RouteLocationInput = {
|
|
290
|
+
path: this.fullPath,
|
|
291
|
+
state: { ...this.state },
|
|
292
|
+
...(this.confirm && { confirm: this.confirm }),
|
|
293
|
+
...(this.layer && { layer: this.layer }),
|
|
294
|
+
...(this.statusCode !== null && { statusCode: this.statusCode })
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Get original options from constructor's finalOptions
|
|
298
|
+
const options = this._options;
|
|
299
|
+
|
|
300
|
+
const clonedRoute = new Route({
|
|
301
|
+
options,
|
|
302
|
+
toType: this.type,
|
|
303
|
+
toInput
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return clonedRoute;
|
|
307
|
+
}
|
|
308
|
+
}
|