@@ -158,6 +158,12 @@ export function createRouterMatcher(
158
158
removeRoute ( record . name )
159
159
}
160
160
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
+
161
167
if ( mainNormalizedRecord . children ) {
162
168
const children = mainNormalizedRecord . children
163
169
for ( let i = 0 ; i < children . length ; i ++ ) {
@@ -177,17 +183,6 @@ export function createRouterMatcher(
177
183
// if (parent && isAliasRecord(originalRecord)) {
178
184
// parent.children.push(originalRecord)
179
185
// }
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
- }
191
186
}
192
187
193
188
return originalMatcher
@@ -223,17 +218,8 @@ export function createRouterMatcher(
223
218
}
224
219
225
220
function insertMatcher ( matcher : RouteRecordMatcher ) {
226
- let i = 0
227
- while (
228
- i < matchers . length &&
229
- comparePathParserScore ( matcher , matchers [ i ] ) >= 0 &&
230
- // Adding children with empty path should still appear before the parent
231
- // https://github.com/vuejs/router/issues/1124
232
- ( matcher . record . path !== matchers [ i ] . record . path ||
233
- ! isRecordChildOf ( matcher , matchers [ i ] ) )
234
- )
235
- i ++
236
- matchers . splice ( i , 0 , matcher )
221
+ const index = findInsertionIndex ( matcher , matchers )
222
+ matchers . splice ( index , 0 , matcher )
237
223
// only add the original record to the name map
238
224
if ( matcher . record . name && ! isAliasRecord ( matcher ) )
239
225
matcherMap . set ( matcher . record . name , matcher )
@@ -525,12 +511,78 @@ function checkMissingParamsInAbsolutePath(
525
511
}
526
512
}
527
513
528
- function isRecordChildOf (
529
- record : RouteRecordMatcher ,
530
- parent : RouteRecordMatcher
531
- ) : boolean {
532
- return parent . children . some (
533
- child => child === record || isRecordChildOf ( record , child )
514
+ /**
515
+ * Performs a binary search to find the correct insertion index for a new matcher.
516
+ *
517
+ * Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
518
+ * with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
519
+ *
520
+ * @param matcher - new matcher to be inserted
521
+ * @param matchers - existing matchers
522
+ */
523
+ function findInsertionIndex (
524
+ matcher : RouteRecordMatcher ,
525
+ matchers : RouteRecordMatcher [ ]
526
+ ) {
527
+ // First phase: binary search based on score
528
+ let lower = 0
529
+ let upper = matchers . length
530
+
531
+ while ( lower !== upper ) {
532
+ const mid = ( lower + upper ) >> 1
533
+ const sortOrder = comparePathParserScore ( matcher , matchers [ mid ] )
534
+
535
+ if ( sortOrder < 0 ) {
536
+ upper = mid
537
+ } else {
538
+ lower = mid + 1
539
+ }
540
+ }
541
+
542
+ // Second phase: check for an ancestor with the same score
543
+ const insertionAncestor = getInsertionAncestor ( matcher )
544
+
545
+ if ( insertionAncestor ) {
546
+ upper = matchers . lastIndexOf ( insertionAncestor , upper - 1 )
547
+
548
+ if ( __DEV__ && upper < 0 ) {
549
+ // This should never happen
550
+ warn (
551
+ `Finding ancestor route "${ insertionAncestor . record . path } " failed for "${ matcher . record . path } "`
552
+ )
553
+ }
554
+ }
555
+
556
+ return upper
557
+ }
558
+
559
+ function getInsertionAncestor ( matcher : RouteRecordMatcher ) {
560
+ let ancestor : RouteRecordMatcher | undefined = matcher
561
+
562
+ while ( ( ancestor = ancestor . parent ) ) {
563
+ if (
564
+ isMatchable ( ancestor ) &&
565
+ comparePathParserScore ( matcher , ancestor ) === 0
566
+ ) {
567
+ return ancestor
568
+ }
569
+ }
570
+
571
+ return
572
+ }
573
+
574
+ /**
575
+ * Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without
576
+ * a component, or name, or redirect, are just used to group other routes.
577
+ * @param matcher
578
+ * @param matcher.record record of the matcher
579
+ * @returns
580
+ */
581
+ function isMatchable ( { record } : RouteRecordMatcher ) : boolean {
582
+ return ! ! (
583
+ record . name ||
584
+ ( record . components && Object . keys ( record . components ) . length ) ||
585
+ record . redirect
534
586
)
535
587
}
536
588
0 commit comments