@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.
@@ -1,5 +1,4 @@
1
- import { tokensToParser } from './pathParserRanker';
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 = tokensToParser(tokenizePath(path), {
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 = tokensToParser(tokenizePath(path), options);
54
+ const pathParser = compilePath(path, options);
56
55
  return pathParser.stringify(params);
57
56
  }
@@ -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
@@ -1,7 +1,6 @@
1
1
  import { matchPathname } from './matchPathname';
2
- import { joinPaths, normalizeBase, resolvePath, stripBase } from './utils';
3
- import { tokensToParser, comparePathParserScore } from './pathParserRanker';
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({}, tokensToParser(tokenizePath(path), undefined, { routes })), { path,
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
- export function matchRoutes(routes, location, basename = '', options = {}) {
75
- const { strict = false } = options;
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
- let branches = flattenRoutes(routes);
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
@@ -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 { matchRoutes, getTrailingSlashRedirectPath } from './matchRoutes';
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
- const { _routes: routes, _strictTrailingSlash: strict } = this;
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 = matchRoutes(this._routes, Object.assign(Object.assign({}, parsedPath), { pathname: canonical }), undefined, { strict: true });
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
  }
@@ -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.tokensToParser)((0, pathTokenizer_1.tokenizePath)(path), {
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.tokensToParser)((0, pathTokenizer_1.tokenizePath)(path), options);
58
+ const pathParser = (0, pathParserRanker_1.compilePath)(path, options);
60
59
  return pathParser.stringify(params);
61
60
  }
@@ -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
@@ -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.tokensToParser)((0, pathTokenizer_1.tokenizePath)(path), undefined, { routes })), { path,
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
- function matchRoutes(routes, location, basename = '', options = {}) {
80
- const { strict = false } = options;
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
- let branches = flattenRoutes(routes);
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
@@ -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
- const { _routes: routes, _strictTrailingSlash: strict } = this;
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 = (0, matchRoutes_1.matchRoutes)(this._routes, Object.assign(Object.assign({}, parsedPath), { pathname: canonical }), undefined, { strict: true });
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.34",
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.34",
31
+ "@shuvi/utils": "2.0.0-dev.35",
32
32
  "query-string": "6.13.8"
33
33
  }
34
34
  }