From 951fba569683b0d8e72b316e37247afe7af26172 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 8 Feb 2024 00:52:26 +0100 Subject: [PATCH 1/5] feat: add matcher sort cache support --- packages/router/src/index.ts | 2 +- packages/router/src/matcher/index.ts | 78 +++++++++++++++---- packages/router/src/matcher/pathMatcher.ts | 6 ++ .../router/src/matcher/pathParserRanker.ts | 15 +++- 4 files changed, 84 insertions(+), 17 deletions(-) diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index 816dd2c51..2fd23587f 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -1,7 +1,7 @@ export { createWebHistory } from './history/html5' export { createMemoryHistory } from './history/memory' export { createWebHashHistory } from './history/hash' -export { createRouterMatcher } from './matcher' +export { createRouterMatcher, createSortCache } from './matcher' export type { RouterMatcher } from './matcher' export { parseQuery, stringifyQuery } from './query' diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index 39a3c24b1..9e7d7d121 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -8,7 +8,7 @@ import { } from '../types' import { createRouterError, ErrorTypes, MatcherError } from '../errors' import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher' -import { RouteRecordNormalized } from './types' +import { RouteRecord, RouteRecordNormalized } from './types' import type { PathParams, @@ -61,8 +61,15 @@ export function createRouterMatcher( // normalized ordered array of matchers const matchers: RouteRecordMatcher[] = [] const matcherMap = new Map() + let restoreSort = globalOptions.sortCache != null + globalOptions = mergeOptions( - { strict: false, end: true, sensitive: false } as PathParserOptions, + { + strict: false, + end: true, + sensitive: false, + sortCache: undefined, + } as PathParserOptions, globalOptions ) @@ -223,17 +230,21 @@ export function createRouterMatcher( } function insertMatcher(matcher: RouteRecordMatcher) { - let i = 0 - while ( - i < matchers.length && - comparePathParserScore(matcher, matchers[i]) >= 0 && - // Adding children with empty path should still appear before the parent - // https://github.com/vuejs/router/issues/1124 - (matcher.record.path !== matchers[i].record.path || - !isRecordChildOf(matcher, matchers[i])) - ) - i++ - matchers.splice(i, 0, matcher) + if (restoreSort) { + matchers.push(matcher) + } else { + let i = 0 + while ( + i < matchers.length && + comparePathParserScore(matcher, matchers[i]) >= 0 && + // Adding children with empty path should still appear before the parent + // https://github.com/vuejs/router/issues/1124 + (matcher.record.path !== matchers[i].record.path || + !isRecordChildOf(matcher, matchers[i])) + ) + i++ + matchers.splice(i, 0, matcher) + } // only add the original record to the name map if (matcher.record.name && !isAliasRecord(matcher)) matcherMap.set(matcher.record.name, matcher) @@ -348,7 +359,46 @@ export function createRouterMatcher( // add initial routes routes.forEach(route => addRoute(route)) - return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher } + // restore matcher order from cache + if (globalOptions.sortCache != null) { + try { + const sorted = matchers.sort( + (a, b) => + globalOptions!.sortCache![a.cacheKey!] - + globalOptions!.sortCache![b.cacheKey!] + ) + + for (const entry of sorted) { + delete entry.cacheKey + } + + matchers.splice(0, matchers.length, ...sorted) + } catch { + console.log('Restore failure.') + } + } + + restoreSort = false + + return { + addRoute, + resolve, + removeRoute, + getRoutes, + getRecordMatcher, + } +} + +/** + * Creates matcher sort cache + * + * @internal + */ +export function createSortCache(records: RouteRecord[]) { + return records.reduce((acc, cur, index) => { + acc[String(cur?.name) + '___' + cur.path] = index + return acc + }, {} as Record) } function paramsFromLocation( diff --git a/packages/router/src/matcher/pathMatcher.ts b/packages/router/src/matcher/pathMatcher.ts index aae2b7826..72ea801f9 100644 --- a/packages/router/src/matcher/pathMatcher.ts +++ b/packages/router/src/matcher/pathMatcher.ts @@ -14,6 +14,8 @@ export interface RouteRecordMatcher extends PathParser { children: RouteRecordMatcher[] // aliases that must be removed when removing this record alias: RouteRecordMatcher[] + // defined when sort cache is present + cacheKey?: string } export function createRouteRecordMatcher( @@ -43,6 +45,10 @@ export function createRouteRecordMatcher( alias: [], }) + if (options?.sortCache) { + matcher.cacheKey = String(record?.name) + '___' + record.path + } + if (parent) { // both are aliases or both are not aliases // we don't want to mix them because the order is used when diff --git a/packages/router/src/matcher/pathParserRanker.ts b/packages/router/src/matcher/pathParserRanker.ts index 670013794..42946bc23 100644 --- a/packages/router/src/matcher/pathParserRanker.ts +++ b/packages/router/src/matcher/pathParserRanker.ts @@ -79,17 +79,28 @@ export interface _PathParserOptions { * @defaultValue `true` */ end?: boolean + + /** + * A precomputed object that maps matchers to its sorted index + * + * @internal + * @defaultValue `undefined` + */ + sortCache?: Record } export type PathParserOptions = Pick< _PathParserOptions, - 'end' | 'sensitive' | 'strict' + 'end' | 'sensitive' | 'strict' | 'sortCache' > // default pattern for a param: non-greedy everything but / const BASE_PARAM_PATTERN = '[^/]+?' -const BASE_PATH_PARSER_OPTIONS: Required<_PathParserOptions> = { +const BASE_PATH_PARSER_OPTIONS: Omit< + Required<_PathParserOptions>, + 'sortCache' +> = { sensitive: false, strict: false, start: true, From cbc24eb07928d74343da19f84b0da002c1a5300d Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 8 Feb 2024 01:16:43 +0100 Subject: [PATCH 2/5] chore: cleanup --- packages/router/src/matcher/index.ts | 21 +++++++++++++++------ packages/router/src/matcher/pathMatcher.ts | 6 +++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index 9e7d7d121..0ab6d0383 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -7,7 +7,11 @@ import { _RouteRecordProps, } from '../types' import { createRouterError, ErrorTypes, MatcherError } from '../errors' -import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher' +import { + createRouteRecordCacheKey, + createRouteRecordMatcher, + RouteRecordMatcher, +} from './pathMatcher' import { RouteRecord, RouteRecordNormalized } from './types' import type { @@ -394,11 +398,16 @@ export function createRouterMatcher( * * @internal */ -export function createSortCache(records: RouteRecord[]) { - return records.reduce((acc, cur, index) => { - acc[String(cur?.name) + '___' + cur.path] = index - return acc - }, {} as Record) +export function createSortCache( + records: RouteRecord[] +): Record { + const sortCache: Record = {} + + for (let i = 0; i < records.length; i++) { + sortCache[createRouteRecordCacheKey(records[i])] = i + } + + return sortCache } function paramsFromLocation( diff --git a/packages/router/src/matcher/pathMatcher.ts b/packages/router/src/matcher/pathMatcher.ts index 72ea801f9..1573e6e9c 100644 --- a/packages/router/src/matcher/pathMatcher.ts +++ b/packages/router/src/matcher/pathMatcher.ts @@ -18,6 +18,10 @@ export interface RouteRecordMatcher extends PathParser { cacheKey?: string } +export function createRouteRecordCacheKey(record: Readonly) { + return String(record?.name) + '___' + record.path +} + export function createRouteRecordMatcher( record: Readonly, parent: RouteRecordMatcher | undefined, @@ -46,7 +50,7 @@ export function createRouteRecordMatcher( }) if (options?.sortCache) { - matcher.cacheKey = String(record?.name) + '___' + record.path + matcher.cacheKey = createRouteRecordCacheKey(record) } if (parent) { From a34bf6b0336d097be8b648205a75f774a199b462 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 8 Feb 2024 09:34:32 +0100 Subject: [PATCH 3/5] refactor: change cache function name --- packages/router/src/index.ts | 2 +- packages/router/src/matcher/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index 2fd23587f..7fbf605b4 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -1,7 +1,7 @@ export { createWebHistory } from './history/html5' export { createMemoryHistory } from './history/memory' export { createWebHashHistory } from './history/hash' -export { createRouterMatcher, createSortCache } from './matcher' +export { createRouterMatcher, createRouterMatcherSortCache } from './matcher' export type { RouterMatcher } from './matcher' export { parseQuery, stringifyQuery } from './query' diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index 0ab6d0383..9c6742101 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -394,11 +394,11 @@ export function createRouterMatcher( } /** - * Creates matcher sort cache + * Creates a router matcher sort cache * * @internal */ -export function createSortCache( +export function createRouterMatcherSortCache( records: RouteRecord[] ): Record { const sortCache: Record = {} From 36c9835ad0526564aec11a71b4bf2c9e1fff4103 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 8 Feb 2024 09:48:06 +0100 Subject: [PATCH 4/5] chore: fix formatting --- packages/router/src/matcher/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index 9c6742101..4992ee36b 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -384,13 +384,7 @@ export function createRouterMatcher( restoreSort = false - return { - addRoute, - resolve, - removeRoute, - getRoutes, - getRecordMatcher, - } + return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher } } /** From 2b3c39838361366dc377208255eaa85c517069c2 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 8 Feb 2024 11:37:14 +0100 Subject: [PATCH 5/5] refactor: remove `splice` as sort mutates array --- packages/router/src/matcher/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/router/src/matcher/index.ts b/packages/router/src/matcher/index.ts index 4992ee36b..0ffd376f2 100644 --- a/packages/router/src/matcher/index.ts +++ b/packages/router/src/matcher/index.ts @@ -366,17 +366,15 @@ export function createRouterMatcher( // restore matcher order from cache if (globalOptions.sortCache != null) { try { - const sorted = matchers.sort( + matchers.sort( (a, b) => globalOptions!.sortCache![a.cacheKey!] - globalOptions!.sortCache![b.cacheKey!] ) - for (const entry of sorted) { + for (const entry of matchers) { delete entry.cacheKey } - - matchers.splice(0, matchers.length, ...sorted) } catch { console.log('Restore failure.') }