@shuvi/router 2.0.0-dev.17 → 2.0.0-dev.18
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/esm/matchStaticRoutes.d.ts +18 -0
- package/esm/matchStaticRoutes.js +210 -0
- package/esm/router.d.ts +1 -0
- package/esm/router.js +5 -2
- package/lib/matchStaticRoutes.d.ts +18 -0
- package/lib/matchStaticRoutes.js +213 -0
- package/lib/router.d.ts +1 -0
- package/lib/router.js +5 -2
- package/package.json +2 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IRouteMatch, PartialLocation } from './types';
|
|
2
|
+
import { IRouteBaseObject } from './matchRoutes';
|
|
3
|
+
/**
|
|
4
|
+
* Static Route Matching Function
|
|
5
|
+
*
|
|
6
|
+
* Optimization features:
|
|
7
|
+
* 1. O(1) exact match - direct HashMap lookup
|
|
8
|
+
* 2. Precompiled results - pre-calculate all match results at startup
|
|
9
|
+
* 3. Zero regex - pure string matching
|
|
10
|
+
* 4. Memory friendly - matcher instance reuse
|
|
11
|
+
* 5. Type safe - full TypeScript support
|
|
12
|
+
*
|
|
13
|
+
* @param routes Route configuration array
|
|
14
|
+
* @param location Location to match
|
|
15
|
+
* @param basename Base path
|
|
16
|
+
* @returns Match result or null
|
|
17
|
+
*/
|
|
18
|
+
export declare function matchStaticRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string): IRouteMatch<T>[] | null;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { joinPaths, normalizeBase, resolvePath, stripBase } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Static Route Matcher Class
|
|
4
|
+
*/
|
|
5
|
+
class StaticRouteMatcher {
|
|
6
|
+
constructor(routes) {
|
|
7
|
+
this.exactMatchMap = new Map();
|
|
8
|
+
this.allRoutes = routes;
|
|
9
|
+
this.prefixTree = this.buildPrefixTree(routes);
|
|
10
|
+
this.precompileMatches();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build prefix tree
|
|
14
|
+
*/
|
|
15
|
+
buildPrefixTree(routes) {
|
|
16
|
+
const root = {
|
|
17
|
+
exactRoutes: new Map(),
|
|
18
|
+
children: new Map(),
|
|
19
|
+
routes: []
|
|
20
|
+
};
|
|
21
|
+
// Flatten all routes
|
|
22
|
+
const flatRoutes = this.flattenRoutes(routes);
|
|
23
|
+
for (const { path, routeChain } of flatRoutes) {
|
|
24
|
+
// Only process static routes
|
|
25
|
+
if (this.isStaticPath(path)) {
|
|
26
|
+
this.insertIntoTree(root, path, routeChain);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Flatten route structure
|
|
33
|
+
*/
|
|
34
|
+
flattenRoutes(routes, parentPath = '', parentRoutes = []) {
|
|
35
|
+
const result = [];
|
|
36
|
+
routes.forEach(route => {
|
|
37
|
+
let fullPath;
|
|
38
|
+
if (route.path === '') {
|
|
39
|
+
fullPath = parentPath;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
fullPath = joinPaths([parentPath, route.path]);
|
|
43
|
+
}
|
|
44
|
+
const routeChain = [...parentRoutes, route];
|
|
45
|
+
result.push({ path: fullPath, routeChain });
|
|
46
|
+
// Recursively process child routes
|
|
47
|
+
if (route.children) {
|
|
48
|
+
result.push(...this.flattenRoutes(route.children, fullPath, routeChain));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if path is static
|
|
55
|
+
*/
|
|
56
|
+
isStaticPath(path) {
|
|
57
|
+
return !path.includes(':') && !path.includes('*') && !path.includes('(');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Insert route into prefix tree
|
|
61
|
+
*/
|
|
62
|
+
insertIntoTree(node, path, routes) {
|
|
63
|
+
// Store exact match
|
|
64
|
+
node.exactRoutes.set(path, routes);
|
|
65
|
+
// Build prefix tree for prefix matching
|
|
66
|
+
const segments = path.split('/').filter(Boolean);
|
|
67
|
+
let currentNode = node;
|
|
68
|
+
for (const segment of segments) {
|
|
69
|
+
if (!currentNode.children.has(segment)) {
|
|
70
|
+
currentNode.children.set(segment, {
|
|
71
|
+
exactRoutes: new Map(),
|
|
72
|
+
children: new Map(),
|
|
73
|
+
routes: []
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
currentNode = currentNode.children.get(segment);
|
|
77
|
+
}
|
|
78
|
+
// Store routes at leaf node
|
|
79
|
+
currentNode.routes = routes;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Precompile all possible match results
|
|
83
|
+
*/
|
|
84
|
+
precompileMatches() {
|
|
85
|
+
for (const [path, routeChain] of this.prefixTree.exactRoutes) {
|
|
86
|
+
const matches = this.buildMatches(routeChain, path);
|
|
87
|
+
this.exactMatchMap.set(path, {
|
|
88
|
+
matches,
|
|
89
|
+
pathname: path,
|
|
90
|
+
params: {} // Static routes have no parameters
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build match results
|
|
96
|
+
*/
|
|
97
|
+
buildMatches(routeChain, matchedPathname) {
|
|
98
|
+
const matches = [];
|
|
99
|
+
let currentMatchedPath = '/';
|
|
100
|
+
for (let i = 0; i < routeChain.length; i++) {
|
|
101
|
+
const route = routeChain[i];
|
|
102
|
+
if (route.path === '') {
|
|
103
|
+
// Simulate the exact behavior of matchPathname + joinPaths
|
|
104
|
+
// When remainingPathname is '/' and path is '', matchPathname returns { pathname: '/' }
|
|
105
|
+
// Then joinPaths([currentPath, '/']) adds trailing slash
|
|
106
|
+
currentMatchedPath = joinPaths([currentMatchedPath, '/']);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// For non-empty path, use the path directly
|
|
110
|
+
currentMatchedPath = joinPaths([currentMatchedPath, route.path]);
|
|
111
|
+
}
|
|
112
|
+
matches.push({
|
|
113
|
+
route,
|
|
114
|
+
pathname: currentMatchedPath,
|
|
115
|
+
params: Object.freeze({}) // Static routes have no parameters
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return matches;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Match pathname
|
|
122
|
+
*/
|
|
123
|
+
match(pathname) {
|
|
124
|
+
// 1. Exact match (O(1))
|
|
125
|
+
const exactMatch = this.exactMatchMap.get(pathname);
|
|
126
|
+
if (exactMatch) {
|
|
127
|
+
return exactMatch;
|
|
128
|
+
}
|
|
129
|
+
// 2. Prefix match (for handling trailing slash etc.)
|
|
130
|
+
return this.findPrefixMatch(pathname);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Find prefix match
|
|
134
|
+
*/
|
|
135
|
+
findPrefixMatch(pathname) {
|
|
136
|
+
// Try adding/removing trailing slash
|
|
137
|
+
const alternatives = [
|
|
138
|
+
pathname,
|
|
139
|
+
pathname === '/' ? pathname : pathname.replace(/\/$/, ''),
|
|
140
|
+
pathname.endsWith('/') ? pathname : pathname + '/'
|
|
141
|
+
];
|
|
142
|
+
for (const alt of alternatives) {
|
|
143
|
+
const match = this.exactMatchMap.get(alt);
|
|
144
|
+
if (match) {
|
|
145
|
+
return match;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get all static route paths (for debugging)
|
|
152
|
+
*/
|
|
153
|
+
getAllStaticPaths() {
|
|
154
|
+
return Array.from(this.exactMatchMap.keys()).sort();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get matching statistics
|
|
158
|
+
*/
|
|
159
|
+
getStats() {
|
|
160
|
+
return {
|
|
161
|
+
totalStaticRoutes: this.exactMatchMap.size,
|
|
162
|
+
totalRoutes: this.allRoutes.length,
|
|
163
|
+
staticRatio: (this.exactMatchMap.size / this.allRoutes.length * 100).toFixed(1) + '%'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Global matcher cache
|
|
168
|
+
const staticMatcherCache = new WeakMap();
|
|
169
|
+
/**
|
|
170
|
+
* Static Route Matching Function
|
|
171
|
+
*
|
|
172
|
+
* Optimization features:
|
|
173
|
+
* 1. O(1) exact match - direct HashMap lookup
|
|
174
|
+
* 2. Precompiled results - pre-calculate all match results at startup
|
|
175
|
+
* 3. Zero regex - pure string matching
|
|
176
|
+
* 4. Memory friendly - matcher instance reuse
|
|
177
|
+
* 5. Type safe - full TypeScript support
|
|
178
|
+
*
|
|
179
|
+
* @param routes Route configuration array
|
|
180
|
+
* @param location Location to match
|
|
181
|
+
* @param basename Base path
|
|
182
|
+
* @returns Match result or null
|
|
183
|
+
*/
|
|
184
|
+
export function matchStaticRoutes(routes, location, basename = '') {
|
|
185
|
+
// Normalize input
|
|
186
|
+
if (typeof location === 'string') {
|
|
187
|
+
location = resolvePath(location);
|
|
188
|
+
}
|
|
189
|
+
let pathname = location.pathname || '/';
|
|
190
|
+
// Handle basename
|
|
191
|
+
if (basename) {
|
|
192
|
+
const normalizedBasename = normalizeBase(basename);
|
|
193
|
+
const pathnameWithoutBase = stripBase(pathname, normalizedBasename);
|
|
194
|
+
if (pathnameWithoutBase) {
|
|
195
|
+
pathname = pathnameWithoutBase;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Get or create matcher
|
|
202
|
+
let matcher = staticMatcherCache.get(routes);
|
|
203
|
+
if (!matcher) {
|
|
204
|
+
matcher = new StaticRouteMatcher(routes);
|
|
205
|
+
staticMatcherCache.set(routes, matcher);
|
|
206
|
+
}
|
|
207
|
+
// Execute matching
|
|
208
|
+
const result = matcher.match(pathname);
|
|
209
|
+
return result ? result.matches : null;
|
|
210
|
+
}
|
package/esm/router.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ interface IRouterOptions<RouteRecord extends IPartialRouteRecord> {
|
|
|
4
4
|
history: History;
|
|
5
5
|
routes: RouteRecord[];
|
|
6
6
|
caseSensitive?: boolean;
|
|
7
|
+
staticMode?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export declare const createRouter: <RouteRecord extends IRouteRecord>(options: IRouterOptions<RouteRecord>) => IRouter<RouteRecord>;
|
|
9
10
|
export {};
|
package/esm/router.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createEvents, resolvePath } from './utils';
|
|
|
5
5
|
import { isError, isFunction } from './utils/error';
|
|
6
6
|
import { runQueue } from './utils/async';
|
|
7
7
|
import { getRedirectFromRoutes } from './getRedirectFromRoutes';
|
|
8
|
+
import { matchStaticRoutes } from './matchStaticRoutes';
|
|
8
9
|
const START = {
|
|
9
10
|
matches: [],
|
|
10
11
|
params: {},
|
|
@@ -17,11 +18,12 @@ const START = {
|
|
|
17
18
|
redirected: false
|
|
18
19
|
};
|
|
19
20
|
class Router {
|
|
20
|
-
constructor({ history, routes }) {
|
|
21
|
+
constructor({ history, routes, staticMode }) {
|
|
21
22
|
this._pending = null;
|
|
22
23
|
this._cancleHandler = null;
|
|
23
24
|
this._ready = false;
|
|
24
25
|
this._readyDefer = createDefer();
|
|
26
|
+
this._staticMode = false;
|
|
25
27
|
this._listeners = createEvents();
|
|
26
28
|
this._beforeEachs = createEvents();
|
|
27
29
|
this._beforeResolves = createEvents();
|
|
@@ -74,7 +76,7 @@ class Router {
|
|
|
74
76
|
};
|
|
75
77
|
this.match = (to) => {
|
|
76
78
|
const { _routes: routes } = this;
|
|
77
|
-
const matches = matchRoutes(routes, to);
|
|
79
|
+
const matches = this._staticMode ? matchStaticRoutes(routes, to) : matchRoutes(routes, to);
|
|
78
80
|
return matches || [];
|
|
79
81
|
};
|
|
80
82
|
this.replaceRoutes = (routes) => {
|
|
@@ -102,6 +104,7 @@ class Router {
|
|
|
102
104
|
this._history = history;
|
|
103
105
|
this._routes = createRoutesFromArray(routes);
|
|
104
106
|
this._current = START;
|
|
107
|
+
this._staticMode = !!staticMode;
|
|
105
108
|
this._history.doTransition = this._doTransition.bind(this);
|
|
106
109
|
}
|
|
107
110
|
get ready() {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IRouteMatch, PartialLocation } from './types';
|
|
2
|
+
import { IRouteBaseObject } from './matchRoutes';
|
|
3
|
+
/**
|
|
4
|
+
* Static Route Matching Function
|
|
5
|
+
*
|
|
6
|
+
* Optimization features:
|
|
7
|
+
* 1. O(1) exact match - direct HashMap lookup
|
|
8
|
+
* 2. Precompiled results - pre-calculate all match results at startup
|
|
9
|
+
* 3. Zero regex - pure string matching
|
|
10
|
+
* 4. Memory friendly - matcher instance reuse
|
|
11
|
+
* 5. Type safe - full TypeScript support
|
|
12
|
+
*
|
|
13
|
+
* @param routes Route configuration array
|
|
14
|
+
* @param location Location to match
|
|
15
|
+
* @param basename Base path
|
|
16
|
+
* @returns Match result or null
|
|
17
|
+
*/
|
|
18
|
+
export declare function matchStaticRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string): IRouteMatch<T>[] | null;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchStaticRoutes = matchStaticRoutes;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
/**
|
|
6
|
+
* Static Route Matcher Class
|
|
7
|
+
*/
|
|
8
|
+
class StaticRouteMatcher {
|
|
9
|
+
constructor(routes) {
|
|
10
|
+
this.exactMatchMap = new Map();
|
|
11
|
+
this.allRoutes = routes;
|
|
12
|
+
this.prefixTree = this.buildPrefixTree(routes);
|
|
13
|
+
this.precompileMatches();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build prefix tree
|
|
17
|
+
*/
|
|
18
|
+
buildPrefixTree(routes) {
|
|
19
|
+
const root = {
|
|
20
|
+
exactRoutes: new Map(),
|
|
21
|
+
children: new Map(),
|
|
22
|
+
routes: []
|
|
23
|
+
};
|
|
24
|
+
// Flatten all routes
|
|
25
|
+
const flatRoutes = this.flattenRoutes(routes);
|
|
26
|
+
for (const { path, routeChain } of flatRoutes) {
|
|
27
|
+
// Only process static routes
|
|
28
|
+
if (this.isStaticPath(path)) {
|
|
29
|
+
this.insertIntoTree(root, path, routeChain);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return root;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Flatten route structure
|
|
36
|
+
*/
|
|
37
|
+
flattenRoutes(routes, parentPath = '', parentRoutes = []) {
|
|
38
|
+
const result = [];
|
|
39
|
+
routes.forEach(route => {
|
|
40
|
+
let fullPath;
|
|
41
|
+
if (route.path === '') {
|
|
42
|
+
fullPath = parentPath;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
fullPath = (0, utils_1.joinPaths)([parentPath, route.path]);
|
|
46
|
+
}
|
|
47
|
+
const routeChain = [...parentRoutes, route];
|
|
48
|
+
result.push({ path: fullPath, routeChain });
|
|
49
|
+
// Recursively process child routes
|
|
50
|
+
if (route.children) {
|
|
51
|
+
result.push(...this.flattenRoutes(route.children, fullPath, routeChain));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if path is static
|
|
58
|
+
*/
|
|
59
|
+
isStaticPath(path) {
|
|
60
|
+
return !path.includes(':') && !path.includes('*') && !path.includes('(');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Insert route into prefix tree
|
|
64
|
+
*/
|
|
65
|
+
insertIntoTree(node, path, routes) {
|
|
66
|
+
// Store exact match
|
|
67
|
+
node.exactRoutes.set(path, routes);
|
|
68
|
+
// Build prefix tree for prefix matching
|
|
69
|
+
const segments = path.split('/').filter(Boolean);
|
|
70
|
+
let currentNode = node;
|
|
71
|
+
for (const segment of segments) {
|
|
72
|
+
if (!currentNode.children.has(segment)) {
|
|
73
|
+
currentNode.children.set(segment, {
|
|
74
|
+
exactRoutes: new Map(),
|
|
75
|
+
children: new Map(),
|
|
76
|
+
routes: []
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
currentNode = currentNode.children.get(segment);
|
|
80
|
+
}
|
|
81
|
+
// Store routes at leaf node
|
|
82
|
+
currentNode.routes = routes;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Precompile all possible match results
|
|
86
|
+
*/
|
|
87
|
+
precompileMatches() {
|
|
88
|
+
for (const [path, routeChain] of this.prefixTree.exactRoutes) {
|
|
89
|
+
const matches = this.buildMatches(routeChain, path);
|
|
90
|
+
this.exactMatchMap.set(path, {
|
|
91
|
+
matches,
|
|
92
|
+
pathname: path,
|
|
93
|
+
params: {} // Static routes have no parameters
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build match results
|
|
99
|
+
*/
|
|
100
|
+
buildMatches(routeChain, matchedPathname) {
|
|
101
|
+
const matches = [];
|
|
102
|
+
let currentMatchedPath = '/';
|
|
103
|
+
for (let i = 0; i < routeChain.length; i++) {
|
|
104
|
+
const route = routeChain[i];
|
|
105
|
+
if (route.path === '') {
|
|
106
|
+
// Simulate the exact behavior of matchPathname + joinPaths
|
|
107
|
+
// When remainingPathname is '/' and path is '', matchPathname returns { pathname: '/' }
|
|
108
|
+
// Then joinPaths([currentPath, '/']) adds trailing slash
|
|
109
|
+
currentMatchedPath = (0, utils_1.joinPaths)([currentMatchedPath, '/']);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// For non-empty path, use the path directly
|
|
113
|
+
currentMatchedPath = (0, utils_1.joinPaths)([currentMatchedPath, route.path]);
|
|
114
|
+
}
|
|
115
|
+
matches.push({
|
|
116
|
+
route,
|
|
117
|
+
pathname: currentMatchedPath,
|
|
118
|
+
params: Object.freeze({}) // Static routes have no parameters
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return matches;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Match pathname
|
|
125
|
+
*/
|
|
126
|
+
match(pathname) {
|
|
127
|
+
// 1. Exact match (O(1))
|
|
128
|
+
const exactMatch = this.exactMatchMap.get(pathname);
|
|
129
|
+
if (exactMatch) {
|
|
130
|
+
return exactMatch;
|
|
131
|
+
}
|
|
132
|
+
// 2. Prefix match (for handling trailing slash etc.)
|
|
133
|
+
return this.findPrefixMatch(pathname);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Find prefix match
|
|
137
|
+
*/
|
|
138
|
+
findPrefixMatch(pathname) {
|
|
139
|
+
// Try adding/removing trailing slash
|
|
140
|
+
const alternatives = [
|
|
141
|
+
pathname,
|
|
142
|
+
pathname === '/' ? pathname : pathname.replace(/\/$/, ''),
|
|
143
|
+
pathname.endsWith('/') ? pathname : pathname + '/'
|
|
144
|
+
];
|
|
145
|
+
for (const alt of alternatives) {
|
|
146
|
+
const match = this.exactMatchMap.get(alt);
|
|
147
|
+
if (match) {
|
|
148
|
+
return match;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get all static route paths (for debugging)
|
|
155
|
+
*/
|
|
156
|
+
getAllStaticPaths() {
|
|
157
|
+
return Array.from(this.exactMatchMap.keys()).sort();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get matching statistics
|
|
161
|
+
*/
|
|
162
|
+
getStats() {
|
|
163
|
+
return {
|
|
164
|
+
totalStaticRoutes: this.exactMatchMap.size,
|
|
165
|
+
totalRoutes: this.allRoutes.length,
|
|
166
|
+
staticRatio: (this.exactMatchMap.size / this.allRoutes.length * 100).toFixed(1) + '%'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Global matcher cache
|
|
171
|
+
const staticMatcherCache = new WeakMap();
|
|
172
|
+
/**
|
|
173
|
+
* Static Route Matching Function
|
|
174
|
+
*
|
|
175
|
+
* Optimization features:
|
|
176
|
+
* 1. O(1) exact match - direct HashMap lookup
|
|
177
|
+
* 2. Precompiled results - pre-calculate all match results at startup
|
|
178
|
+
* 3. Zero regex - pure string matching
|
|
179
|
+
* 4. Memory friendly - matcher instance reuse
|
|
180
|
+
* 5. Type safe - full TypeScript support
|
|
181
|
+
*
|
|
182
|
+
* @param routes Route configuration array
|
|
183
|
+
* @param location Location to match
|
|
184
|
+
* @param basename Base path
|
|
185
|
+
* @returns Match result or null
|
|
186
|
+
*/
|
|
187
|
+
function matchStaticRoutes(routes, location, basename = '') {
|
|
188
|
+
// Normalize input
|
|
189
|
+
if (typeof location === 'string') {
|
|
190
|
+
location = (0, utils_1.resolvePath)(location);
|
|
191
|
+
}
|
|
192
|
+
let pathname = location.pathname || '/';
|
|
193
|
+
// Handle basename
|
|
194
|
+
if (basename) {
|
|
195
|
+
const normalizedBasename = (0, utils_1.normalizeBase)(basename);
|
|
196
|
+
const pathnameWithoutBase = (0, utils_1.stripBase)(pathname, normalizedBasename);
|
|
197
|
+
if (pathnameWithoutBase) {
|
|
198
|
+
pathname = pathnameWithoutBase;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Get or create matcher
|
|
205
|
+
let matcher = staticMatcherCache.get(routes);
|
|
206
|
+
if (!matcher) {
|
|
207
|
+
matcher = new StaticRouteMatcher(routes);
|
|
208
|
+
staticMatcherCache.set(routes, matcher);
|
|
209
|
+
}
|
|
210
|
+
// Execute matching
|
|
211
|
+
const result = matcher.match(pathname);
|
|
212
|
+
return result ? result.matches : null;
|
|
213
|
+
}
|
package/lib/router.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ interface IRouterOptions<RouteRecord extends IPartialRouteRecord> {
|
|
|
4
4
|
history: History;
|
|
5
5
|
routes: RouteRecord[];
|
|
6
6
|
caseSensitive?: boolean;
|
|
7
|
+
staticMode?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export declare const createRouter: <RouteRecord extends IRouteRecord>(options: IRouterOptions<RouteRecord>) => IRouter<RouteRecord>;
|
|
9
10
|
export {};
|
package/lib/router.js
CHANGED
|
@@ -8,6 +8,7 @@ const utils_1 = require("./utils");
|
|
|
8
8
|
const error_1 = require("./utils/error");
|
|
9
9
|
const async_1 = require("./utils/async");
|
|
10
10
|
const getRedirectFromRoutes_1 = require("./getRedirectFromRoutes");
|
|
11
|
+
const matchStaticRoutes_1 = require("./matchStaticRoutes");
|
|
11
12
|
const START = {
|
|
12
13
|
matches: [],
|
|
13
14
|
params: {},
|
|
@@ -20,11 +21,12 @@ const START = {
|
|
|
20
21
|
redirected: false
|
|
21
22
|
};
|
|
22
23
|
class Router {
|
|
23
|
-
constructor({ history, routes }) {
|
|
24
|
+
constructor({ history, routes, staticMode }) {
|
|
24
25
|
this._pending = null;
|
|
25
26
|
this._cancleHandler = null;
|
|
26
27
|
this._ready = false;
|
|
27
28
|
this._readyDefer = (0, defer_1.createDefer)();
|
|
29
|
+
this._staticMode = false;
|
|
28
30
|
this._listeners = (0, utils_1.createEvents)();
|
|
29
31
|
this._beforeEachs = (0, utils_1.createEvents)();
|
|
30
32
|
this._beforeResolves = (0, utils_1.createEvents)();
|
|
@@ -77,7 +79,7 @@ class Router {
|
|
|
77
79
|
};
|
|
78
80
|
this.match = (to) => {
|
|
79
81
|
const { _routes: routes } = this;
|
|
80
|
-
const matches = (0, matchRoutes_1.matchRoutes)(routes, to);
|
|
82
|
+
const matches = this._staticMode ? (0, matchStaticRoutes_1.matchStaticRoutes)(routes, to) : (0, matchRoutes_1.matchRoutes)(routes, to);
|
|
81
83
|
return matches || [];
|
|
82
84
|
};
|
|
83
85
|
this.replaceRoutes = (routes) => {
|
|
@@ -105,6 +107,7 @@ class Router {
|
|
|
105
107
|
this._history = history;
|
|
106
108
|
this._routes = (0, createRoutesFromArray_1.createRoutesFromArray)(routes);
|
|
107
109
|
this._current = START;
|
|
110
|
+
this._staticMode = !!staticMode;
|
|
108
111
|
this._history.doTransition = this._doTransition.bind(this);
|
|
109
112
|
}
|
|
110
113
|
get ready() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shuvi/router",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.18",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/shuvijs/shuvi.git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">= 16.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@shuvi/utils": "2.0.0-dev.
|
|
31
|
+
"@shuvi/utils": "2.0.0-dev.18",
|
|
32
32
|
"query-string": "6.13.8"
|
|
33
33
|
}
|
|
34
34
|
}
|