@shuvi/router 2.0.0-dev.34 → 2.0.0-dev.35
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/matchPathname.js +3 -4
- package/esm/matchRoutes.d.ts +6 -0
- package/esm/matchRoutes.js +52 -9
- package/esm/pathParserRanker.d.ts +8 -0
- package/esm/pathParserRanker.js +41 -12
- package/esm/router.js +9 -5
- package/lib/matchPathname.js +2 -3
- package/lib/matchRoutes.d.ts +6 -0
- package/lib/matchRoutes.js +51 -7
- package/lib/pathParserRanker.d.ts +8 -0
- package/lib/pathParserRanker.js +43 -12
- package/lib/router.js +8 -4
- package/package.json +2 -2
package/esm/matchPathname.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tokenizePath } from './pathTokenizer';
|
|
1
|
+
import { compilePath } from './pathParserRanker';
|
|
3
2
|
function safelyDecodeURIComponent(value, paramName, optional) {
|
|
4
3
|
try {
|
|
5
4
|
if (Array.isArray(value)) {
|
|
@@ -28,7 +27,7 @@ export function matchPathname(pattern, pathname) {
|
|
|
28
27
|
pattern = { path: pattern };
|
|
29
28
|
}
|
|
30
29
|
const { path, caseSensitive = false, end = true, strict = false } = pattern;
|
|
31
|
-
const pathParser =
|
|
30
|
+
const pathParser = compilePath(path, {
|
|
32
31
|
strict,
|
|
33
32
|
end,
|
|
34
33
|
sensitive: caseSensitive
|
|
@@ -52,6 +51,6 @@ export function matchPathname(pattern, pathname) {
|
|
|
52
51
|
* @param options
|
|
53
52
|
*/
|
|
54
53
|
export function matchStringify(path, params, options) {
|
|
55
|
-
const pathParser =
|
|
54
|
+
const pathParser = compilePath(path, options);
|
|
56
55
|
return pathParser.stringify(params);
|
|
57
56
|
}
|
package/esm/matchRoutes.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export declare function rankRouteBranches<T extends [string, ...any[]]>(branches
|
|
|
6
6
|
export interface IMatchRoutesOptions {
|
|
7
7
|
strict?: boolean;
|
|
8
8
|
}
|
|
9
|
+
export type RoutesMatcher<T extends IRouteBaseObject> = (location: string | PartialLocation, basename?: string) => IRouteMatch<T>[] | null;
|
|
10
|
+
/**
|
|
11
|
+
* Flattens and ranks the routes once, on the first match, and reuses them for
|
|
12
|
+
* every match after. The routes must not be changed in place once matched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRoutesMatcher<T extends IRouteBaseObject>(routes: T[], options?: IMatchRoutesOptions): RoutesMatcher<T>;
|
|
9
15
|
export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string, options?: IMatchRoutesOptions): IRouteMatch<T>[] | null;
|
|
10
16
|
/**
|
|
11
17
|
* When strict trailing-slash routing is enabled, a pathname that does not
|
package/esm/matchRoutes.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { matchPathname } from './matchPathname';
|
|
2
|
-
import { joinPaths, normalizeBase, resolvePath, stripBase } from './utils';
|
|
3
|
-
import {
|
|
4
|
-
import { tokenizePath } from './pathTokenizer';
|
|
2
|
+
import { joinPaths, normalizeBase, resolvePath, stripBase, isDev, warning } from './utils';
|
|
3
|
+
import { compilePath, getBranchScore, comparePathParserScore } from './pathParserRanker';
|
|
5
4
|
function matchRouteBranch(branch, pathname, strict = false) {
|
|
6
5
|
let routes = branch[1];
|
|
7
6
|
let matchedPathname = '/';
|
|
@@ -36,8 +35,7 @@ export function rankRouteBranches(branches) {
|
|
|
36
35
|
}
|
|
37
36
|
const normalizedPaths = branches.map((branch, index) => {
|
|
38
37
|
const [path, routes] = branch;
|
|
39
|
-
return Object.assign(Object.assign({},
|
|
40
|
-
index });
|
|
38
|
+
return Object.assign(Object.assign({}, compilePath(path)), { score: getBranchScore(path, routes), index });
|
|
41
39
|
});
|
|
42
40
|
normalizedPaths.sort((a, b) => comparePathParserScore(a, b));
|
|
43
41
|
const newBranches = [];
|
|
@@ -71,8 +69,24 @@ function flattenRoutes(routes, branches = [], parentPath = '', parentRoutes = []
|
|
|
71
69
|
});
|
|
72
70
|
return branches;
|
|
73
71
|
}
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
// Everything matching reads from the routes: each array and its items, and the
|
|
73
|
+
// path, caseSensitive and children of each route.
|
|
74
|
+
function snapshotRoutes(routes, snapshot = []) {
|
|
75
|
+
snapshot.push(routes, routes.length);
|
|
76
|
+
routes.forEach(route => {
|
|
77
|
+
snapshot.push(route, route.path, route.caseSensitive, route.children);
|
|
78
|
+
if (route.children) {
|
|
79
|
+
snapshotRoutes(route.children, snapshot);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return snapshot;
|
|
83
|
+
}
|
|
84
|
+
function isSameSnapshot(a, b) {
|
|
85
|
+
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
86
|
+
}
|
|
87
|
+
// The branches are only got once the basename has matched, so a route that
|
|
88
|
+
// fails to compile does not throw for a pathname outside the basename.
|
|
89
|
+
function matchBranches(getBranches, location, basename, strict) {
|
|
76
90
|
if (typeof location === 'string') {
|
|
77
91
|
location = resolvePath(location);
|
|
78
92
|
}
|
|
@@ -87,8 +101,7 @@ export function matchRoutes(routes, location, basename = '', options = {}) {
|
|
|
87
101
|
return null;
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
|
-
|
|
91
|
-
branches = rankRouteBranches(branches);
|
|
104
|
+
const branches = getBranches();
|
|
92
105
|
let matches = null;
|
|
93
106
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
94
107
|
// TODO: Match on search, state too?
|
|
@@ -96,6 +109,36 @@ export function matchRoutes(routes, location, basename = '', options = {}) {
|
|
|
96
109
|
}
|
|
97
110
|
return matches;
|
|
98
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Flattens and ranks the routes once, on the first match, and reuses them for
|
|
114
|
+
* every match after. The routes must not be changed in place once matched.
|
|
115
|
+
*/
|
|
116
|
+
export function createRoutesMatcher(routes, options = {}) {
|
|
117
|
+
const { strict = false } = options;
|
|
118
|
+
let branches;
|
|
119
|
+
// development only, to warn about routes changed in place after the first match
|
|
120
|
+
let snapshot;
|
|
121
|
+
const getBranches = () => {
|
|
122
|
+
if (!branches) {
|
|
123
|
+
branches = rankRouteBranches(flattenRoutes(routes));
|
|
124
|
+
if (isDev) {
|
|
125
|
+
snapshot = snapshotRoutes(routes);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (snapshot && !isSameSnapshot(snapshot, snapshotRoutes(routes))) {
|
|
129
|
+
warning(false, `Routes were changed in place after they had been matched. ` +
|
|
130
|
+
`Matching does not fully apply such changes and may return wrong ` +
|
|
131
|
+
`results, use router.replaceRoutes() to change the routes instead.`);
|
|
132
|
+
snapshot = undefined;
|
|
133
|
+
}
|
|
134
|
+
return branches;
|
|
135
|
+
};
|
|
136
|
+
return (location, basename = '') => matchBranches(getBranches, location, basename, strict);
|
|
137
|
+
}
|
|
138
|
+
export function matchRoutes(routes, location, basename = '', options = {}) {
|
|
139
|
+
const { strict = false } = options;
|
|
140
|
+
return matchBranches(() => rankRouteBranches(flattenRoutes(routes)), location, basename, strict);
|
|
141
|
+
}
|
|
99
142
|
/**
|
|
100
143
|
* When strict trailing-slash routing is enabled, a pathname that does not
|
|
101
144
|
* match any route may be a non-canonical variant (e.g. `/foo/` for a route
|
|
@@ -55,6 +55,14 @@ export type PathParserOptions = Pick<_PathParserOptions, 'end' | 'sensitive' | '
|
|
|
55
55
|
export declare function tokensToParser(segments: Array<Token[]>, extraOptions?: _PathParserOptions, branchInfo?: {
|
|
56
56
|
routes: IRouteRecord[];
|
|
57
57
|
}): PathParser;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the parser of a path, compiled once per path and options.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compilePath(path: string, options?: PathParserOptions): PathParser;
|
|
62
|
+
/**
|
|
63
|
+
* The score of a branch, without changing the cached score of its path.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getBranchScore(path: string, routes?: IRouteRecord[]): Array<number[]>;
|
|
58
66
|
/**
|
|
59
67
|
* Compare function that can be used with `sort` to sort an array of PathParser
|
|
60
68
|
* @param a - first PathParser
|
package/esm/pathParserRanker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { tokenizePath } from './pathTokenizer';
|
|
1
2
|
// default pattern for a param: non greedy everything but /
|
|
2
3
|
const BASE_PARAM_PATTERN = '[^/]+?';
|
|
3
4
|
const BASE_PATH_PARSER_OPTIONS = {
|
|
@@ -36,7 +37,6 @@ function endsAtSeparator(segments) {
|
|
|
36
37
|
* @returns a PathParser
|
|
37
38
|
*/
|
|
38
39
|
export function tokensToParser(segments, extraOptions, branchInfo) {
|
|
39
|
-
var _a;
|
|
40
40
|
const options = Object.assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
|
|
41
41
|
/**
|
|
42
42
|
* end with *, eg: /* /a* /a/*
|
|
@@ -258,17 +258,7 @@ export function tokensToParser(segments, extraOptions, branchInfo) {
|
|
|
258
258
|
}
|
|
259
259
|
return path;
|
|
260
260
|
}
|
|
261
|
-
|
|
262
|
-
* To make sure the score of pageBranch is always higher priority
|
|
263
|
-
* than the layoutRoute.
|
|
264
|
-
* We append a bonus score if the last route is an empty path.
|
|
265
|
-
*/
|
|
266
|
-
if (branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.routes) {
|
|
267
|
-
const lastRoutePath = (_a = branchInfo.routes[branchInfo.routes.length - 1]) === null || _a === void 0 ? void 0 : _a.path;
|
|
268
|
-
if (lastRoutePath === '') {
|
|
269
|
-
score.push([0.1 /* PathScore.BonusEmptyStringPath */]);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
261
|
+
score = withBranchBonus(score, branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.routes);
|
|
272
262
|
return {
|
|
273
263
|
re,
|
|
274
264
|
score,
|
|
@@ -277,6 +267,45 @@ export function tokensToParser(segments, extraOptions, branchInfo) {
|
|
|
277
267
|
stringify
|
|
278
268
|
};
|
|
279
269
|
}
|
|
270
|
+
// Parsers are cached rather than tokens: tokensToParser mutates the tokens of a
|
|
271
|
+
// catch-all path, so sharing them would leak one call's options into another.
|
|
272
|
+
// Past the limit new paths are compiled without being cached.
|
|
273
|
+
const PATH_PARSER_CACHE_LIMIT = 10000;
|
|
274
|
+
const pathParserCache = new Map();
|
|
275
|
+
/**
|
|
276
|
+
* Returns the parser of a path, compiled once per path and options.
|
|
277
|
+
*/
|
|
278
|
+
export function compilePath(path, options = {}) {
|
|
279
|
+
const { end = true, strict = false, sensitive = false } = options;
|
|
280
|
+
const cacheKey = `${+end}${+strict}${+sensitive}${path}`;
|
|
281
|
+
let parser = pathParserCache.get(cacheKey);
|
|
282
|
+
if (!parser) {
|
|
283
|
+
parser = tokensToParser(tokenizePath(path), { end, strict, sensitive });
|
|
284
|
+
if (pathParserCache.size < PATH_PARSER_CACHE_LIMIT) {
|
|
285
|
+
pathParserCache.set(cacheKey, parser);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return parser;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* To make sure the score of pageBranch is always higher priority
|
|
292
|
+
* than the layoutRoute.
|
|
293
|
+
* We append a bonus score if the last route is an empty path.
|
|
294
|
+
* Returns a new array rather than changing the given score.
|
|
295
|
+
*/
|
|
296
|
+
function withBranchBonus(score, routes) {
|
|
297
|
+
var _a;
|
|
298
|
+
const lastRoutePath = (_a = routes === null || routes === void 0 ? void 0 : routes[routes.length - 1]) === null || _a === void 0 ? void 0 : _a.path;
|
|
299
|
+
return lastRoutePath === ''
|
|
300
|
+
? score.concat([[0.1 /* PathScore.BonusEmptyStringPath */]])
|
|
301
|
+
: score;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The score of a branch, without changing the cached score of its path.
|
|
305
|
+
*/
|
|
306
|
+
export function getBranchScore(path, routes) {
|
|
307
|
+
return withBranchBonus(compilePath(path).score, routes);
|
|
308
|
+
}
|
|
280
309
|
/**
|
|
281
310
|
* Compares an array of numbers as used in PathParser.score and returns a
|
|
282
311
|
* number. This function can be used to `sort` an array
|
package/esm/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createDefer } from '@shuvi/utils/defer';
|
|
2
|
-
import {
|
|
2
|
+
import { createRoutesMatcher, getTrailingSlashRedirectPath } from './matchRoutes';
|
|
3
3
|
import { createRoutesFromArray } from './createRoutesFromArray';
|
|
4
4
|
import { createEvents, resolvePath } from './utils';
|
|
5
5
|
import { isError, isFunction } from './utils/error';
|
|
@@ -73,9 +73,7 @@ class Router {
|
|
|
73
73
|
return this._history.resolve(to, from);
|
|
74
74
|
};
|
|
75
75
|
this.match = (to) => {
|
|
76
|
-
|
|
77
|
-
const matches = matchRoutes(routes, to, undefined, { strict });
|
|
78
|
-
return matches || [];
|
|
76
|
+
return this._matchRoutes(to) || [];
|
|
79
77
|
};
|
|
80
78
|
this.replaceRoutes = (routes) => {
|
|
81
79
|
if (this._ready) {
|
|
@@ -92,6 +90,9 @@ class Router {
|
|
|
92
90
|
this._cancleHandler = null;
|
|
93
91
|
}
|
|
94
92
|
this._routes = createRoutesFromArray(routes);
|
|
93
|
+
this._matchRoutes = createRoutesMatcher(this._routes, {
|
|
94
|
+
strict: this._strictTrailingSlash
|
|
95
|
+
});
|
|
95
96
|
this._current = START;
|
|
96
97
|
const setup = () => this._history.setup();
|
|
97
98
|
this._history.transitionTo(this._getCurrent(), {
|
|
@@ -102,6 +103,9 @@ class Router {
|
|
|
102
103
|
this._history = history;
|
|
103
104
|
this._routes = createRoutesFromArray(routes);
|
|
104
105
|
this._strictTrailingSlash = strictTrailingSlash;
|
|
106
|
+
this._matchRoutes = createRoutesMatcher(this._routes, {
|
|
107
|
+
strict: strictTrailingSlash
|
|
108
|
+
});
|
|
105
109
|
this._current = START;
|
|
106
110
|
this._history.doTransition = this._doTransition.bind(this);
|
|
107
111
|
}
|
|
@@ -292,7 +296,7 @@ class Router {
|
|
|
292
296
|
if (this._strictTrailingSlash && !matches.length) {
|
|
293
297
|
const canonical = getTrailingSlashRedirectPath(parsedPath.pathname || '/');
|
|
294
298
|
if (canonical) {
|
|
295
|
-
const rematched =
|
|
299
|
+
const rematched = this._matchRoutes(Object.assign(Object.assign({}, parsedPath), { pathname: canonical }));
|
|
296
300
|
if (rematched && rematched.length) {
|
|
297
301
|
route.redirect = { path: canonical, status: 308 };
|
|
298
302
|
}
|
package/lib/matchPathname.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.matchPathname = matchPathname;
|
|
4
4
|
exports.matchStringify = matchStringify;
|
|
5
5
|
const pathParserRanker_1 = require("./pathParserRanker");
|
|
6
|
-
const pathTokenizer_1 = require("./pathTokenizer");
|
|
7
6
|
function safelyDecodeURIComponent(value, paramName, optional) {
|
|
8
7
|
try {
|
|
9
8
|
if (Array.isArray(value)) {
|
|
@@ -32,7 +31,7 @@ function matchPathname(pattern, pathname) {
|
|
|
32
31
|
pattern = { path: pattern };
|
|
33
32
|
}
|
|
34
33
|
const { path, caseSensitive = false, end = true, strict = false } = pattern;
|
|
35
|
-
const pathParser = (0, pathParserRanker_1.
|
|
34
|
+
const pathParser = (0, pathParserRanker_1.compilePath)(path, {
|
|
36
35
|
strict,
|
|
37
36
|
end,
|
|
38
37
|
sensitive: caseSensitive
|
|
@@ -56,6 +55,6 @@ function matchPathname(pattern, pathname) {
|
|
|
56
55
|
* @param options
|
|
57
56
|
*/
|
|
58
57
|
function matchStringify(path, params, options) {
|
|
59
|
-
const pathParser = (0, pathParserRanker_1.
|
|
58
|
+
const pathParser = (0, pathParserRanker_1.compilePath)(path, options);
|
|
60
59
|
return pathParser.stringify(params);
|
|
61
60
|
}
|
package/lib/matchRoutes.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export declare function rankRouteBranches<T extends [string, ...any[]]>(branches
|
|
|
6
6
|
export interface IMatchRoutesOptions {
|
|
7
7
|
strict?: boolean;
|
|
8
8
|
}
|
|
9
|
+
export type RoutesMatcher<T extends IRouteBaseObject> = (location: string | PartialLocation, basename?: string) => IRouteMatch<T>[] | null;
|
|
10
|
+
/**
|
|
11
|
+
* Flattens and ranks the routes once, on the first match, and reuses them for
|
|
12
|
+
* every match after. The routes must not be changed in place once matched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRoutesMatcher<T extends IRouteBaseObject>(routes: T[], options?: IMatchRoutesOptions): RoutesMatcher<T>;
|
|
9
15
|
export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string, options?: IMatchRoutesOptions): IRouteMatch<T>[] | null;
|
|
10
16
|
/**
|
|
11
17
|
* When strict trailing-slash routing is enabled, a pathname that does not
|
package/lib/matchRoutes.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.rankRouteBranches = rankRouteBranches;
|
|
4
|
+
exports.createRoutesMatcher = createRoutesMatcher;
|
|
4
5
|
exports.matchRoutes = matchRoutes;
|
|
5
6
|
exports.getTrailingSlashRedirectPath = getTrailingSlashRedirectPath;
|
|
6
7
|
const matchPathname_1 = require("./matchPathname");
|
|
7
8
|
const utils_1 = require("./utils");
|
|
8
9
|
const pathParserRanker_1 = require("./pathParserRanker");
|
|
9
|
-
const pathTokenizer_1 = require("./pathTokenizer");
|
|
10
10
|
function matchRouteBranch(branch, pathname, strict = false) {
|
|
11
11
|
let routes = branch[1];
|
|
12
12
|
let matchedPathname = '/';
|
|
@@ -41,8 +41,7 @@ function rankRouteBranches(branches) {
|
|
|
41
41
|
}
|
|
42
42
|
const normalizedPaths = branches.map((branch, index) => {
|
|
43
43
|
const [path, routes] = branch;
|
|
44
|
-
return Object.assign(Object.assign({}, (0, pathParserRanker_1.
|
|
45
|
-
index });
|
|
44
|
+
return Object.assign(Object.assign({}, (0, pathParserRanker_1.compilePath)(path)), { score: (0, pathParserRanker_1.getBranchScore)(path, routes), index });
|
|
46
45
|
});
|
|
47
46
|
normalizedPaths.sort((a, b) => (0, pathParserRanker_1.comparePathParserScore)(a, b));
|
|
48
47
|
const newBranches = [];
|
|
@@ -76,8 +75,24 @@ function flattenRoutes(routes, branches = [], parentPath = '', parentRoutes = []
|
|
|
76
75
|
});
|
|
77
76
|
return branches;
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// Everything matching reads from the routes: each array and its items, and the
|
|
79
|
+
// path, caseSensitive and children of each route.
|
|
80
|
+
function snapshotRoutes(routes, snapshot = []) {
|
|
81
|
+
snapshot.push(routes, routes.length);
|
|
82
|
+
routes.forEach(route => {
|
|
83
|
+
snapshot.push(route, route.path, route.caseSensitive, route.children);
|
|
84
|
+
if (route.children) {
|
|
85
|
+
snapshotRoutes(route.children, snapshot);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
function isSameSnapshot(a, b) {
|
|
91
|
+
return a.length === b.length && a.every((value, i) => value === b[i]);
|
|
92
|
+
}
|
|
93
|
+
// The branches are only got once the basename has matched, so a route that
|
|
94
|
+
// fails to compile does not throw for a pathname outside the basename.
|
|
95
|
+
function matchBranches(getBranches, location, basename, strict) {
|
|
81
96
|
if (typeof location === 'string') {
|
|
82
97
|
location = (0, utils_1.resolvePath)(location);
|
|
83
98
|
}
|
|
@@ -92,8 +107,7 @@ function matchRoutes(routes, location, basename = '', options = {}) {
|
|
|
92
107
|
return null;
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
|
-
|
|
96
|
-
branches = rankRouteBranches(branches);
|
|
110
|
+
const branches = getBranches();
|
|
97
111
|
let matches = null;
|
|
98
112
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
99
113
|
// TODO: Match on search, state too?
|
|
@@ -101,6 +115,36 @@ function matchRoutes(routes, location, basename = '', options = {}) {
|
|
|
101
115
|
}
|
|
102
116
|
return matches;
|
|
103
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Flattens and ranks the routes once, on the first match, and reuses them for
|
|
120
|
+
* every match after. The routes must not be changed in place once matched.
|
|
121
|
+
*/
|
|
122
|
+
function createRoutesMatcher(routes, options = {}) {
|
|
123
|
+
const { strict = false } = options;
|
|
124
|
+
let branches;
|
|
125
|
+
// development only, to warn about routes changed in place after the first match
|
|
126
|
+
let snapshot;
|
|
127
|
+
const getBranches = () => {
|
|
128
|
+
if (!branches) {
|
|
129
|
+
branches = rankRouteBranches(flattenRoutes(routes));
|
|
130
|
+
if (utils_1.isDev) {
|
|
131
|
+
snapshot = snapshotRoutes(routes);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (snapshot && !isSameSnapshot(snapshot, snapshotRoutes(routes))) {
|
|
135
|
+
(0, utils_1.warning)(false, `Routes were changed in place after they had been matched. ` +
|
|
136
|
+
`Matching does not fully apply such changes and may return wrong ` +
|
|
137
|
+
`results, use router.replaceRoutes() to change the routes instead.`);
|
|
138
|
+
snapshot = undefined;
|
|
139
|
+
}
|
|
140
|
+
return branches;
|
|
141
|
+
};
|
|
142
|
+
return (location, basename = '') => matchBranches(getBranches, location, basename, strict);
|
|
143
|
+
}
|
|
144
|
+
function matchRoutes(routes, location, basename = '', options = {}) {
|
|
145
|
+
const { strict = false } = options;
|
|
146
|
+
return matchBranches(() => rankRouteBranches(flattenRoutes(routes)), location, basename, strict);
|
|
147
|
+
}
|
|
104
148
|
/**
|
|
105
149
|
* When strict trailing-slash routing is enabled, a pathname that does not
|
|
106
150
|
* match any route may be a non-canonical variant (e.g. `/foo/` for a route
|
|
@@ -55,6 +55,14 @@ export type PathParserOptions = Pick<_PathParserOptions, 'end' | 'sensitive' | '
|
|
|
55
55
|
export declare function tokensToParser(segments: Array<Token[]>, extraOptions?: _PathParserOptions, branchInfo?: {
|
|
56
56
|
routes: IRouteRecord[];
|
|
57
57
|
}): PathParser;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the parser of a path, compiled once per path and options.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compilePath(path: string, options?: PathParserOptions): PathParser;
|
|
62
|
+
/**
|
|
63
|
+
* The score of a branch, without changing the cached score of its path.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getBranchScore(path: string, routes?: IRouteRecord[]): Array<number[]>;
|
|
58
66
|
/**
|
|
59
67
|
* Compare function that can be used with `sort` to sort an array of PathParser
|
|
60
68
|
* @param a - first PathParser
|
package/lib/pathParserRanker.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.tokensToParser = tokensToParser;
|
|
4
|
+
exports.compilePath = compilePath;
|
|
5
|
+
exports.getBranchScore = getBranchScore;
|
|
4
6
|
exports.comparePathParserScore = comparePathParserScore;
|
|
7
|
+
const pathTokenizer_1 = require("./pathTokenizer");
|
|
5
8
|
// default pattern for a param: non greedy everything but /
|
|
6
9
|
const BASE_PARAM_PATTERN = '[^/]+?';
|
|
7
10
|
const BASE_PATH_PARSER_OPTIONS = {
|
|
@@ -40,7 +43,6 @@ function endsAtSeparator(segments) {
|
|
|
40
43
|
* @returns a PathParser
|
|
41
44
|
*/
|
|
42
45
|
function tokensToParser(segments, extraOptions, branchInfo) {
|
|
43
|
-
var _a;
|
|
44
46
|
const options = Object.assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
|
|
45
47
|
/**
|
|
46
48
|
* end with *, eg: /* /a* /a/*
|
|
@@ -262,17 +264,7 @@ function tokensToParser(segments, extraOptions, branchInfo) {
|
|
|
262
264
|
}
|
|
263
265
|
return path;
|
|
264
266
|
}
|
|
265
|
-
|
|
266
|
-
* To make sure the score of pageBranch is always higher priority
|
|
267
|
-
* than the layoutRoute.
|
|
268
|
-
* We append a bonus score if the last route is an empty path.
|
|
269
|
-
*/
|
|
270
|
-
if (branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.routes) {
|
|
271
|
-
const lastRoutePath = (_a = branchInfo.routes[branchInfo.routes.length - 1]) === null || _a === void 0 ? void 0 : _a.path;
|
|
272
|
-
if (lastRoutePath === '') {
|
|
273
|
-
score.push([0.1 /* PathScore.BonusEmptyStringPath */]);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
267
|
+
score = withBranchBonus(score, branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.routes);
|
|
276
268
|
return {
|
|
277
269
|
re,
|
|
278
270
|
score,
|
|
@@ -281,6 +273,45 @@ function tokensToParser(segments, extraOptions, branchInfo) {
|
|
|
281
273
|
stringify
|
|
282
274
|
};
|
|
283
275
|
}
|
|
276
|
+
// Parsers are cached rather than tokens: tokensToParser mutates the tokens of a
|
|
277
|
+
// catch-all path, so sharing them would leak one call's options into another.
|
|
278
|
+
// Past the limit new paths are compiled without being cached.
|
|
279
|
+
const PATH_PARSER_CACHE_LIMIT = 10000;
|
|
280
|
+
const pathParserCache = new Map();
|
|
281
|
+
/**
|
|
282
|
+
* Returns the parser of a path, compiled once per path and options.
|
|
283
|
+
*/
|
|
284
|
+
function compilePath(path, options = {}) {
|
|
285
|
+
const { end = true, strict = false, sensitive = false } = options;
|
|
286
|
+
const cacheKey = `${+end}${+strict}${+sensitive}${path}`;
|
|
287
|
+
let parser = pathParserCache.get(cacheKey);
|
|
288
|
+
if (!parser) {
|
|
289
|
+
parser = tokensToParser((0, pathTokenizer_1.tokenizePath)(path), { end, strict, sensitive });
|
|
290
|
+
if (pathParserCache.size < PATH_PARSER_CACHE_LIMIT) {
|
|
291
|
+
pathParserCache.set(cacheKey, parser);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return parser;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* To make sure the score of pageBranch is always higher priority
|
|
298
|
+
* than the layoutRoute.
|
|
299
|
+
* We append a bonus score if the last route is an empty path.
|
|
300
|
+
* Returns a new array rather than changing the given score.
|
|
301
|
+
*/
|
|
302
|
+
function withBranchBonus(score, routes) {
|
|
303
|
+
var _a;
|
|
304
|
+
const lastRoutePath = (_a = routes === null || routes === void 0 ? void 0 : routes[routes.length - 1]) === null || _a === void 0 ? void 0 : _a.path;
|
|
305
|
+
return lastRoutePath === ''
|
|
306
|
+
? score.concat([[0.1 /* PathScore.BonusEmptyStringPath */]])
|
|
307
|
+
: score;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The score of a branch, without changing the cached score of its path.
|
|
311
|
+
*/
|
|
312
|
+
function getBranchScore(path, routes) {
|
|
313
|
+
return withBranchBonus(compilePath(path).score, routes);
|
|
314
|
+
}
|
|
284
315
|
/**
|
|
285
316
|
* Compares an array of numbers as used in PathParser.score and returns a
|
|
286
317
|
* number. This function can be used to `sort` an array
|
package/lib/router.js
CHANGED
|
@@ -76,9 +76,7 @@ class Router {
|
|
|
76
76
|
return this._history.resolve(to, from);
|
|
77
77
|
};
|
|
78
78
|
this.match = (to) => {
|
|
79
|
-
|
|
80
|
-
const matches = (0, matchRoutes_1.matchRoutes)(routes, to, undefined, { strict });
|
|
81
|
-
return matches || [];
|
|
79
|
+
return this._matchRoutes(to) || [];
|
|
82
80
|
};
|
|
83
81
|
this.replaceRoutes = (routes) => {
|
|
84
82
|
if (this._ready) {
|
|
@@ -95,6 +93,9 @@ class Router {
|
|
|
95
93
|
this._cancleHandler = null;
|
|
96
94
|
}
|
|
97
95
|
this._routes = (0, createRoutesFromArray_1.createRoutesFromArray)(routes);
|
|
96
|
+
this._matchRoutes = (0, matchRoutes_1.createRoutesMatcher)(this._routes, {
|
|
97
|
+
strict: this._strictTrailingSlash
|
|
98
|
+
});
|
|
98
99
|
this._current = START;
|
|
99
100
|
const setup = () => this._history.setup();
|
|
100
101
|
this._history.transitionTo(this._getCurrent(), {
|
|
@@ -105,6 +106,9 @@ class Router {
|
|
|
105
106
|
this._history = history;
|
|
106
107
|
this._routes = (0, createRoutesFromArray_1.createRoutesFromArray)(routes);
|
|
107
108
|
this._strictTrailingSlash = strictTrailingSlash;
|
|
109
|
+
this._matchRoutes = (0, matchRoutes_1.createRoutesMatcher)(this._routes, {
|
|
110
|
+
strict: strictTrailingSlash
|
|
111
|
+
});
|
|
108
112
|
this._current = START;
|
|
109
113
|
this._history.doTransition = this._doTransition.bind(this);
|
|
110
114
|
}
|
|
@@ -295,7 +299,7 @@ class Router {
|
|
|
295
299
|
if (this._strictTrailingSlash && !matches.length) {
|
|
296
300
|
const canonical = (0, matchRoutes_1.getTrailingSlashRedirectPath)(parsedPath.pathname || '/');
|
|
297
301
|
if (canonical) {
|
|
298
|
-
const rematched =
|
|
302
|
+
const rematched = this._matchRoutes(Object.assign(Object.assign({}, parsedPath), { pathname: canonical }));
|
|
299
303
|
if (rematched && rematched.length) {
|
|
300
304
|
route.redirect = { path: canonical, status: 308 };
|
|
301
305
|
}
|
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.35",
|
|
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.35",
|
|
32
32
|
"query-string": "6.13.8"
|
|
33
33
|
}
|
|
34
34
|
}
|