Skip to content

Commit d7327e6

Browse files
committed
Merge branch 'skirtles-code-static-path-perf' into app-build
2 parents 8e38733 + 5042bff commit d7327e6

File tree

5 files changed

+357
-117
lines changed

5 files changed

+357
-117
lines changed

packages/router/src/matcher/index.ts

Lines changed: 32 additions & 113 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 } from './matcherTree'
89
import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher'
910
import { RouteRecordNormalized } from './types'
1011

@@ -14,11 +15,9 @@ 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'
21-
import type { RouteRecordNameGeneric, _RouteRecordProps } from '../typed-routes'
20+
import type { RouteRecordName, _RouteRecordProps } from '../typed-routes'
2221

2322
/**
2423
* Internal RouterMatcher
@@ -27,13 +26,13 @@ import type { RouteRecordNameGeneric, _RouteRecordProps } from '../typed-routes'
2726
*/
2827
export interface RouterMatcher {
2928
addRoute: (record: RouteRecordRaw, parent?: RouteRecordMatcher) => () => void
30-
removeRoute(matcher: RouteRecordMatcher): void
31-
removeRoute(name: NonNullable<RouteRecordNameGeneric>): void
29+
removeRoute: {
30+
(matcher: RouteRecordMatcher): void
31+
(name: RouteRecordName): void
32+
}
3233
clearRoutes: () => void
3334
getRoutes: () => RouteRecordMatcher[]
34-
getRecordMatcher: (
35-
name: NonNullable<RouteRecordNameGeneric>
36-
) => RouteRecordMatcher | undefined
35+
getRecordMatcher: (name: RouteRecordName) => RouteRecordMatcher | undefined
3736

3837
/**
3938
* Resolves a location. Gives access to the route record that corresponds to the actual path as well as filling the corresponding params objects
@@ -58,18 +57,15 @@ export function createRouterMatcher(
5857
routes: Readonly<RouteRecordRaw[]>,
5958
globalOptions: PathParserOptions
6059
): RouterMatcher {
61-
// normalized ordered array of matchers
62-
const matchers: RouteRecordMatcher[] = []
63-
const matcherMap = new Map<
64-
NonNullable<RouteRecordNameGeneric>,
65-
RouteRecordMatcher
66-
>()
60+
// normalized ordered tree of matchers
61+
const matcherTree = createMatcherTree()
62+
const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
6763
globalOptions = mergeOptions(
6864
{ strict: false, end: true, sensitive: false } as PathParserOptions,
6965
globalOptions
7066
)
7167

72-
function getRecordMatcher(name: NonNullable<RouteRecordNameGeneric>) {
68+
function getRecordMatcher(name: RouteRecordName) {
7369
return matcherMap.get(name)
7470
}
7571

@@ -167,12 +163,6 @@ export function createRouterMatcher(
167163
}
168164
}
169165

170-
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
171-
// not be reached and pass through the catch all route
172-
if (isMatchable(matcher)) {
173-
insertMatcher(matcher)
174-
}
175-
176166
if (mainNormalizedRecord.children) {
177167
const children = mainNormalizedRecord.children
178168
for (let i = 0; i < children.length; i++) {
@@ -192,6 +182,17 @@ export function createRouterMatcher(
192182
// if (parent && isAliasRecord(originalRecord)) {
193183
// parent.children.push(originalRecord)
194184
// }
185+
186+
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
187+
// not be reached and pass through the catch all route
188+
if (
189+
(matcher.record.components &&
190+
Object.keys(matcher.record.components).length) ||
191+
matcher.record.name ||
192+
matcher.record.redirect
193+
) {
194+
insertMatcher(matcher)
195+
}
195196
}
196197

197198
return originalMatcher
@@ -202,35 +203,29 @@ export function createRouterMatcher(
202203
: noop
203204
}
204205

205-
function removeRoute(
206-
matcherRef: NonNullable<RouteRecordNameGeneric> | RouteRecordMatcher
207-
) {
206+
function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
208207
if (isRouteName(matcherRef)) {
209208
const matcher = matcherMap.get(matcherRef)
210209
if (matcher) {
211210
matcherMap.delete(matcherRef)
212-
matchers.splice(matchers.indexOf(matcher), 1)
211+
matcherTree.remove(matcher)
213212
matcher.children.forEach(removeRoute)
214213
matcher.alias.forEach(removeRoute)
215214
}
216215
} else {
217-
const index = matchers.indexOf(matcherRef)
218-
if (index > -1) {
219-
matchers.splice(index, 1)
220-
if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
221-
matcherRef.children.forEach(removeRoute)
222-
matcherRef.alias.forEach(removeRoute)
223-
}
216+
matcherTree.remove(matcherRef)
217+
if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
218+
matcherRef.children.forEach(removeRoute)
219+
matcherRef.alias.forEach(removeRoute)
224220
}
225221
}
226222

227223
function getRoutes() {
228-
return matchers
224+
return matcherTree.toArray()
229225
}
230226

231227
function insertMatcher(matcher: RouteRecordMatcher) {
232-
const index = findInsertionIndex(matcher, matchers)
233-
matchers.splice(index, 0, matcher)
228+
matcherTree.add(matcher)
234229
// only add the original record to the name map
235230
if (matcher.record.name && !isAliasRecord(matcher))
236231
matcherMap.set(matcher.record.name, matcher)
@@ -303,7 +298,7 @@ export function createRouterMatcher(
303298
)
304299
}
305300

306-
matcher = matchers.find(m => m.re.test(path))
301+
matcher = matcherTree.find(path)
307302
// matcher should have a value after the loop
308303

309304
if (matcher) {
@@ -316,7 +311,7 @@ export function createRouterMatcher(
316311
// match by name or path of current route
317312
matcher = currentLocation.name
318313
? matcherMap.get(currentLocation.name)
319-
: matchers.find(m => m.re.test(currentLocation.path))
314+
: matcherTree.find(currentLocation.path)
320315
if (!matcher)
321316
throw createRouterError<MatcherError>(ErrorTypes.MATCHER_NOT_FOUND, {
322317
location,
@@ -351,7 +346,6 @@ export function createRouterMatcher(
351346
routes.forEach(route => addRoute(route))
352347

353348
function clearRoutes() {
354-
matchers.length = 0
355349
matcherMap.clear()
356350
}
357351

@@ -560,79 +554,4 @@ function checkMissingParamsInAbsolutePath(
560554
}
561555
}
562556

563-
/**
564-
* Performs a binary search to find the correct insertion index for a new matcher.
565-
*
566-
* Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
567-
* with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
568-
*
569-
* @param matcher - new matcher to be inserted
570-
* @param matchers - existing matchers
571-
*/
572-
function findInsertionIndex(
573-
matcher: RouteRecordMatcher,
574-
matchers: RouteRecordMatcher[]
575-
) {
576-
// First phase: binary search based on score
577-
let lower = 0
578-
let upper = matchers.length
579-
580-
while (lower !== upper) {
581-
const mid = (lower + upper) >> 1
582-
const sortOrder = comparePathParserScore(matcher, matchers[mid])
583-
584-
if (sortOrder < 0) {
585-
upper = mid
586-
} else {
587-
lower = mid + 1
588-
}
589-
}
590-
591-
// Second phase: check for an ancestor with the same score
592-
const insertionAncestor = getInsertionAncestor(matcher)
593-
594-
if (insertionAncestor) {
595-
upper = matchers.lastIndexOf(insertionAncestor, upper - 1)
596-
597-
if (__DEV__ && upper < 0) {
598-
// This should never happen
599-
warn(
600-
`Finding ancestor route "${insertionAncestor.record.path}" failed for "${matcher.record.path}"`
601-
)
602-
}
603-
}
604-
605-
return upper
606-
}
607-
608-
function getInsertionAncestor(matcher: RouteRecordMatcher) {
609-
let ancestor: RouteRecordMatcher | undefined = matcher
610-
611-
while ((ancestor = ancestor.parent)) {
612-
if (
613-
isMatchable(ancestor) &&
614-
comparePathParserScore(matcher, ancestor) === 0
615-
) {
616-
return ancestor
617-
}
618-
}
619-
620-
return
621-
}
622-
623-
/**
624-
* Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without
625-
* a component, or name, or redirect, are just used to group other routes.
626-
* @param matcher
627-
* @param matcher.record record of the matcher
628-
* @returns
629-
*/
630-
function isMatchable({ record }: RouteRecordMatcher): boolean {
631-
return !!(
632-
record.name ||
633-
(record.components && Object.keys(record.components).length) ||
634-
record.redirect
635-
)
636-
}
637-
638557
export type { PathParserOptions, _PathParserOptions }

0 commit comments

Comments
 (0)