Skip to content

Commit 48db602

Browse files
committed
Fix matcher ordering in edge cases
1 parent b894ef7 commit 48db602

File tree

1 file changed

+43
-27
lines changed

1 file changed

+43
-27
lines changed

packages/router/src/matcher/index.ts

+43-27
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ export function createRouterMatcher(
158158
removeRoute(record.name)
159159
}
160160

161+
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
162+
// not be reached and pass through the catch all route
163+
if (isMatchable(matcher)) {
164+
insertMatcher(matcher)
165+
}
166+
161167
if (mainNormalizedRecord.children) {
162168
const children = mainNormalizedRecord.children
163169
for (let i = 0; i < children.length; i++) {
@@ -177,17 +183,6 @@ export function createRouterMatcher(
177183
// if (parent && isAliasRecord(originalRecord)) {
178184
// parent.children.push(originalRecord)
179185
// }
180-
181-
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
182-
// not be reached and pass through the catch all route
183-
if (
184-
(matcher.record.components &&
185-
Object.keys(matcher.record.components).length) ||
186-
matcher.record.name ||
187-
matcher.record.redirect
188-
) {
189-
insertMatcher(matcher)
190-
}
191186
}
192187

193188
return originalMatcher
@@ -514,8 +509,8 @@ function checkMissingParamsInAbsolutePath(
514509
/**
515510
* Performs a binary search to find the correct insertion index for a new matcher.
516511
*
517-
* Matchers are primarily sorted by their score. If scores are tied then the matcher's depth is used instead.
518-
* The depth check ensures that a child with an empty path comes before its parent.
512+
* Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
513+
* with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
519514
*
520515
* @param matcher - new matcher to be inserted
521516
* @param matchers - existing matchers
@@ -524,38 +519,59 @@ function findInsertionIndex(
524519
matcher: RouteRecordMatcher,
525520
matchers: RouteRecordMatcher[]
526521
) {
527-
const depth = getDepth(matcher)
528-
522+
// First phase: binary search based on score
529523
let lower = 0
530524
let upper = matchers.length
531525

532526
while (lower !== upper) {
533527
const mid = (lower + upper) >> 1
534-
const sortOrder =
535-
comparePathParserScore(matcher, matchers[mid]) ||
536-
getDepth(matchers[mid]) - depth
528+
const sortOrder = comparePathParserScore(matcher, matchers[mid])
537529

538-
if (sortOrder === 0) {
539-
return mid
540-
} else if (sortOrder < 0) {
530+
if (sortOrder < 0) {
541531
upper = mid
542532
} else {
543533
lower = mid + 1
544534
}
545535
}
546536

537+
// Second phase: check for an ancestor with the same score
538+
const insertionAncestor = getInsertionAncestor(matcher)
539+
540+
if (insertionAncestor) {
541+
upper = matchers.lastIndexOf(insertionAncestor, upper - 1)
542+
543+
if (__DEV__ && upper === -1) {
544+
// This should never happen
545+
warn(
546+
`Finding ancestor route ${insertionAncestor.record.path} failed for ${matcher.record.path}`
547+
)
548+
}
549+
}
550+
547551
return upper
548552
}
549553

550-
function getDepth(record: RouteRecordMatcher) {
551-
let depth = 0
554+
function getInsertionAncestor(matcher: RouteRecordMatcher) {
555+
let ancestor: RouteRecordMatcher | undefined = matcher
552556

553-
while (record.parent) {
554-
++depth
555-
record = record.parent
557+
while ((ancestor = ancestor.parent)) {
558+
if (
559+
isMatchable(ancestor) &&
560+
comparePathParserScore(matcher, ancestor) === 0
561+
) {
562+
return ancestor
563+
}
556564
}
557565

558-
return depth
566+
return
567+
}
568+
569+
function isMatchable({ record }: RouteRecordMatcher): boolean {
570+
return !!(
571+
record.name ||
572+
(record.components && Object.keys(record.components).length) ||
573+
record.redirect
574+
)
559575
}
560576

561577
export type { PathParserOptions, _PathParserOptions }

0 commit comments

Comments
 (0)