@@ -7,13 +7,22 @@ export const SEGMENT_TYPE_PATHNAME = 0
77export const SEGMENT_TYPE_PARAM = 1
88export const SEGMENT_TYPE_WILDCARD = 2
99export const SEGMENT_TYPE_OPTIONAL_PARAM = 3
10+ const SEGMENT_TYPE_INDEX = 4
1011
12+ /**
13+ * All the kinds of segments that can be present in a route path.
14+ */
1115export type SegmentKind =
1216 | typeof SEGMENT_TYPE_PATHNAME
1317 | typeof SEGMENT_TYPE_PARAM
1418 | typeof SEGMENT_TYPE_WILDCARD
1519 | typeof SEGMENT_TYPE_OPTIONAL_PARAM
1620
21+ /**
22+ * All the kinds of segments that can be present in the segment tree.
23+ */
24+ type ExtendedSegmentKind = SegmentKind | typeof SEGMENT_TYPE_INDEX
25+
1726const PARAM_W_CURLY_BRACES_RE =
1827 / ^ ( [ ^ { ] * ) \{ \$ ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \} ( [ ^ } ] * ) $ / // prefix{$paramName}suffix
1928const OPTIONAL_PARAM_W_CURLY_BRACES_RE =
@@ -326,20 +335,26 @@ function parseSegments<TRouteLike extends RouteLike>(
326335 }
327336 node = nextNode
328337 }
329- if ( ( route . path || ! route . children ) && ! route . isRoot ) {
330- const isIndex = path . endsWith ( '/' )
331- // we cannot fuzzy match an index route,
332- // but if there is *also* a layout route at this path, save it as notFound
333- // we can use it when fuzzy matching to display the NotFound component in the layout route
334- if ( ! isIndex ) node . notFound = route
335- // does the new route take precedence over an existing one?
336- // yes if previous is not an index route and new one is an index route
337- if ( ! node . route || ( ! node . isIndex && isIndex ) ) {
338- node . route = route
339- // when replacing, replace all attributes that are route-specific (`fullPath` only at the moment)
340- node . fullPath = route . fullPath ?? route . from
341- }
342- node . isIndex ||= isIndex
338+
339+ const isLeaf = ( route . path || ! route . children ) && ! route . isRoot
340+
341+ // create index node
342+ if ( isLeaf && path . endsWith ( '/' ) ) {
343+ const indexNode = createStaticNode < TRouteLike > (
344+ route . fullPath ?? route . from ,
345+ )
346+ indexNode . kind = SEGMENT_TYPE_INDEX
347+ indexNode . parent = node
348+ depth ++
349+ indexNode . depth = depth
350+ node . index = indexNode
351+ node = indexNode
352+ }
353+
354+ // make node "matchable"
355+ if ( isLeaf && ! node . route ) {
356+ node . route = route
357+ node . fullPath = route . fullPath ?? route . from
343358 }
344359 }
345360 if ( route . children )
@@ -417,6 +432,7 @@ function createStaticNode<T extends RouteLike>(
417432 return {
418433 kind : SEGMENT_TYPE_PATHNAME ,
419434 depth : 0 ,
435+ index : null ,
420436 static : null ,
421437 staticInsensitive : null ,
422438 dynamic : null ,
@@ -425,8 +441,6 @@ function createStaticNode<T extends RouteLike>(
425441 route : null ,
426442 fullPath,
427443 parent : null ,
428- isIndex : false ,
429- notFound : null ,
430444 }
431445}
432446
@@ -447,6 +461,7 @@ function createDynamicNode<T extends RouteLike>(
447461 return {
448462 kind,
449463 depth : 0 ,
464+ index : null ,
450465 static : null ,
451466 staticInsensitive : null ,
452467 dynamic : null ,
@@ -455,16 +470,14 @@ function createDynamicNode<T extends RouteLike>(
455470 route : null ,
456471 fullPath,
457472 parent : null ,
458- isIndex : false ,
459- notFound : null ,
460473 caseSensitive,
461474 prefix,
462475 suffix,
463476 }
464477}
465478
466479type StaticSegmentNode < T extends RouteLike > = SegmentNode < T > & {
467- kind : typeof SEGMENT_TYPE_PATHNAME
480+ kind : typeof SEGMENT_TYPE_PATHNAME | typeof SEGMENT_TYPE_INDEX
468481}
469482
470483type DynamicSegmentNode < T extends RouteLike > = SegmentNode < T > & {
@@ -482,12 +495,15 @@ type AnySegmentNode<T extends RouteLike> =
482495 | DynamicSegmentNode < T >
483496
484497type SegmentNode < T extends RouteLike > = {
485- kind : SegmentKind
498+ kind : ExtendedSegmentKind
499+
500+ /** Exact index segment (highest priority) */
501+ index : StaticSegmentNode < T > | null
486502
487- /** Static segments (highest priority) */
503+ /** Static segments (2nd priority) */
488504 static : Map < string , StaticSegmentNode < T > > | null
489505
490- /** Case insensitive static segments (second highest priority) */
506+ /** Case insensitive static segments (3rd highest priority) */
491507 staticInsensitive : Map < string , StaticSegmentNode < T > > | null
492508
493509 /** Dynamic segments ($param) */
@@ -508,12 +524,6 @@ type SegmentNode<T extends RouteLike> = {
508524 parent : AnySegmentNode < T > | null
509525
510526 depth : number
511-
512- /** is it an index route (trailing / path), only valid for nodes with a `route` */
513- isIndex : boolean
514-
515- /** Same as `route`, but only present if both an "index route" and a "layout route" exist at this path */
516- notFound : T | null
517527}
518528
519529type RouteLike = {
@@ -713,11 +723,8 @@ function findMatch<T extends RouteLike>(
713723 const leaf = getNodeMatch ( path , parts , segmentTree , fuzzy )
714724 if ( ! leaf ) return null
715725 const params = extractParams ( path , parts , leaf )
716- const isFuzzyMatch = '**' in leaf
717- if ( isFuzzyMatch ) params [ '**' ] = leaf [ '**' ]
718- const route = isFuzzyMatch
719- ? ( leaf . node . notFound ?? leaf . node . route ! )
720- : leaf . node . route !
726+ if ( '**' in leaf ) params [ '**' ] = leaf [ '**' ] !
727+ const route = leaf . node . route !
721728 return {
722729 route,
723730 params,
@@ -837,6 +844,11 @@ function getNodeMatch<T extends RouteLike>(
837844 segmentTree : AnySegmentNode < T > ,
838845 fuzzy : boolean ,
839846) {
847+ // quick check for root index
848+ // this is an optimization, algorithm should work correctly without this block
849+ if ( path === '/' && segmentTree . index )
850+ return { node : segmentTree . index , skipped : 0 }
851+
840852 const trailingSlash = ! last ( parts )
841853 const pathIsIndex = trailingSlash && path !== '/'
842854 const partsLength = parts . length - ( trailingSlash ? 1 : 0 )
@@ -872,22 +884,36 @@ function getNodeMatch<T extends RouteLike>(
872884 let { node, index, skipped, depth, statics, dynamics, optionals } = frame
873885
874886 // In fuzzy mode, track the best partial match we've found so far
875- if ( fuzzy && node . notFound && isFrameMoreSpecific ( bestFuzzy , frame ) ) {
887+ if (
888+ fuzzy &&
889+ node . route &&
890+ node . kind !== SEGMENT_TYPE_INDEX &&
891+ isFrameMoreSpecific ( bestFuzzy , frame )
892+ ) {
876893 bestFuzzy = frame
877894 }
878895
879896 const isBeyondPath = index === partsLength
880897 if ( isBeyondPath ) {
881- if ( node . route && ( ! pathIsIndex || node . isIndex ) ) {
898+ if ( node . route && ( ! pathIsIndex || node . kind === SEGMENT_TYPE_INDEX ) ) {
882899 if ( isFrameMoreSpecific ( bestMatch , frame ) ) {
883900 bestMatch = frame
884901 }
885902
886903 // perfect match, no need to continue
887- if ( statics === partsLength && node . isIndex ) return bestMatch
904+ // this is an optimization, algorithm should work correctly without this block
905+ if (
906+ statics === partsLength &&
907+ ! dynamics &&
908+ ! optionals &&
909+ ! skipped &&
910+ node . kind === SEGMENT_TYPE_INDEX
911+ ) {
912+ return bestMatch
913+ }
888914 }
889- // beyond the length of the path parts, only skipped optional segments or wildcard segments can match
890- if ( ! node . optional && ! node . wildcard ) continue
915+ // beyond the length of the path parts, only index segments, or skipped optional segments, or wildcard segments can match
916+ if ( ! node . optional && ! node . wildcard && ! node . index ) continue
891917 }
892918
893919 const part = isBeyondPath ? undefined : parts [ index ] !
@@ -1022,6 +1048,19 @@ function getNodeMatch<T extends RouteLike>(
10221048 } )
10231049 }
10241050 }
1051+
1052+ // 0. Try index match
1053+ if ( isBeyondPath && node . index ) {
1054+ stack . push ( {
1055+ node : node . index ,
1056+ index,
1057+ skipped,
1058+ depth : depth + 1 ,
1059+ statics,
1060+ dynamics,
1061+ optionals,
1062+ } )
1063+ }
10251064 }
10261065
10271066 if ( bestMatch && wildcardMatch ) {
@@ -1064,8 +1103,10 @@ function isFrameMoreSpecific(
10641103 ( next . dynamics === prev . dynamics &&
10651104 ( next . optionals > prev . optionals ||
10661105 ( next . optionals === prev . optionals &&
1067- ( next . node . isIndex > prev . node . isIndex ||
1068- ( next . node . isIndex === prev . node . isIndex &&
1106+ ( ( next . node . kind === SEGMENT_TYPE_INDEX ) >
1107+ ( prev . node . kind === SEGMENT_TYPE_INDEX ) ||
1108+ ( ( next . node . kind === SEGMENT_TYPE_INDEX ) ===
1109+ ( prev . node . kind === SEGMENT_TYPE_INDEX ) &&
10691110 next . depth > prev . depth ) ) ) ) ) ) )
10701111 )
10711112}
0 commit comments