Skip to content

Commit f2f9bbe

Browse files
perf: faster handling of static paths (#2)
* perf: faster handling of static paths * Fix problems combining `end: false` and `strict: false` * Fix problems with resolving paths for `sensitive` and `end` options * Fix problems caused by merging 'main'
1 parent 6952fea commit f2f9bbe

File tree

5 files changed

+401
-97
lines changed

5 files changed

+401
-97
lines changed

packages/router/src/matcher/index.ts

Lines changed: 13 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
isRouteName,
66
} from '../types'
77
import { createRouterError, ErrorTypes, MatcherError } from '../errors'
8+
import { createMatcherTree, isMatchable } from './matcherTree'
89
import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher'
910
import { RouteRecordNormalized } from './types'
1011

@@ -14,8 +15,6 @@ import type {
1415
_PathParserOptions,
1516
} from './pathParserRanker'
1617

17-
import { comparePathParserScore } from './pathParserRanker'
18-
1918
import { warn } from '../warning'
2019
import { assign, noop } from '../utils'
2120
import type { RouteRecordNameGeneric, _RouteRecordProps } from '../typed-routes'
@@ -58,8 +57,8 @@ export function createRouterMatcher(
5857
routes: Readonly<RouteRecordRaw[]>,
5958
globalOptions: PathParserOptions
6059
): RouterMatcher {
61-
// normalized ordered array of matchers
62-
const matchers: RouteRecordMatcher[] = []
60+
// normalized ordered tree of matchers
61+
const matcherTree = createMatcherTree()
6362
const matcherMap = new Map<
6463
NonNullable<RouteRecordNameGeneric>,
6564
RouteRecordMatcher
@@ -203,28 +202,24 @@ export function createRouterMatcher(
203202
const matcher = matcherMap.get(matcherRef)
204203
if (matcher) {
205204
matcherMap.delete(matcherRef)
206-
matchers.splice(matchers.indexOf(matcher), 1)
205+
matcherTree.remove(matcher)
207206
matcher.children.forEach(removeRoute)
208207
matcher.alias.forEach(removeRoute)
209208
}
210209
} else {
211-
const index = matchers.indexOf(matcherRef)
212-
if (index > -1) {
213-
matchers.splice(index, 1)
214-
if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
215-
matcherRef.children.forEach(removeRoute)
216-
matcherRef.alias.forEach(removeRoute)
217-
}
210+
matcherTree.remove(matcherRef)
211+
if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
212+
matcherRef.children.forEach(removeRoute)
213+
matcherRef.alias.forEach(removeRoute)
218214
}
219215
}
220216

221217
function getRoutes() {
222-
return matchers
218+
return matcherTree.toArray()
223219
}
224220

225221
function insertMatcher(matcher: RouteRecordMatcher) {
226-
const index = findInsertionIndex(matcher, matchers)
227-
matchers.splice(index, 0, matcher)
222+
matcherTree.add(matcher)
228223
// only add the original record to the name map
229224
if (matcher.record.name && !isAliasRecord(matcher))
230225
matcherMap.set(matcher.record.name, matcher)
@@ -297,7 +292,7 @@ export function createRouterMatcher(
297292
)
298293
}
299294

300-
matcher = matchers.find(m => m.re.test(path))
295+
matcher = matcherTree.find(path)
301296
// matcher should have a value after the loop
302297

303298
if (matcher) {
@@ -310,7 +305,7 @@ export function createRouterMatcher(
310305
// match by name or path of current route
311306
matcher = currentLocation.name
312307
? matcherMap.get(currentLocation.name)
313-
: matchers.find(m => m.re.test(currentLocation.path))
308+
: matcherTree.find(currentLocation.path)
314309
if (!matcher)
315310
throw createRouterError<MatcherError>(ErrorTypes.MATCHER_NOT_FOUND, {
316311
location,
@@ -345,7 +340,7 @@ export function createRouterMatcher(
345340
routes.forEach(route => addRoute(route))
346341

347342
function clearRoutes() {
348-
matchers.length = 0
343+
matcherTree.clear()
349344
matcherMap.clear()
350345
}
351346

@@ -528,79 +523,4 @@ function checkMissingParamsInAbsolutePath(
528523
}
529524
}
530525

531-
/**
532-
* Performs a binary search to find the correct insertion index for a new matcher.
533-
*
534-
* Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
535-
* with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
536-
*
537-
* @param matcher - new matcher to be inserted
538-
* @param matchers - existing matchers
539-
*/
540-
function findInsertionIndex(
541-
matcher: RouteRecordMatcher,
542-
matchers: RouteRecordMatcher[]
543-
) {
544-
// First phase: binary search based on score
545-
let lower = 0
546-
let upper = matchers.length
547-
548-
while (lower !== upper) {
549-
const mid = (lower + upper) >> 1
550-
const sortOrder = comparePathParserScore(matcher, matchers[mid])
551-
552-
if (sortOrder < 0) {
553-
upper = mid
554-
} else {
555-
lower = mid + 1
556-
}
557-
}
558-
559-
// Second phase: check for an ancestor with the same score
560-
const insertionAncestor = getInsertionAncestor(matcher)
561-
562-
if (insertionAncestor) {
563-
upper = matchers.lastIndexOf(insertionAncestor, upper - 1)
564-
565-
if (__DEV__ && upper < 0) {
566-
// This should never happen
567-
warn(
568-
`Finding ancestor route "${insertionAncestor.record.path}" failed for "${matcher.record.path}"`
569-
)
570-
}
571-
}
572-
573-
return upper
574-
}
575-
576-
function getInsertionAncestor(matcher: RouteRecordMatcher) {
577-
let ancestor: RouteRecordMatcher | undefined = matcher
578-
579-
while ((ancestor = ancestor.parent)) {
580-
if (
581-
isMatchable(ancestor) &&
582-
comparePathParserScore(matcher, ancestor) === 0
583-
) {
584-
return ancestor
585-
}
586-
}
587-
588-
return
589-
}
590-
591-
/**
592-
* Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without
593-
* a component, or name, or redirect, are just used to group other routes.
594-
* @param matcher
595-
* @param matcher.record record of the matcher
596-
* @returns
597-
*/
598-
function isMatchable({ record }: RouteRecordMatcher): boolean {
599-
return !!(
600-
record.name ||
601-
(record.components && Object.keys(record.components).length) ||
602-
record.redirect
603-
)
604-
}
605-
606526
export type { PathParserOptions, _PathParserOptions }

0 commit comments

Comments
 (0)