@@ -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
@@ -223,17 +218,8 @@ export function createRouterMatcher(
223218 }
224219
225220 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 )
237223 // only add the original record to the name map
238224 if ( matcher . record . name && ! isAliasRecord ( matcher ) )
239225 matcherMap . set ( matcher . record . name , matcher )
@@ -525,12 +511,78 @@ function checkMissingParamsInAbsolutePath(
525511 }
526512}
527513
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
534586 )
535587}
536588
0 commit comments