@@ -174,6 +174,7 @@ function parseSegments<TRouteLike extends RouteLike>(
174174 const path = route . fullPath ?? route . from
175175 const length = path . length
176176 const caseSensitive = route . options ?. caseSensitive ?? defaultCaseSensitive
177+ const parse = route . options ?. params ?. parse ?? null
177178 while ( cursor < length ) {
178179 const segment = parseSegment ( path , cursor , data )
179180 let nextNode : AnySegmentNode < TRouteLike >
@@ -232,12 +233,15 @@ function parseSegments<TRouteLike extends RouteLike>(
232233 : actuallyCaseSensitive
233234 ? suffix_raw
234235 : suffix_raw . toLowerCase ( )
235- const existingNode = node . dynamic ?. find (
236- ( s ) =>
237- s . caseSensitive === actuallyCaseSensitive &&
238- s . prefix === prefix &&
239- s . suffix === suffix ,
240- )
236+ const existingNode =
237+ ! parse &&
238+ node . dynamic ?. find (
239+ ( s ) =>
240+ ! s . parse &&
241+ s . caseSensitive === actuallyCaseSensitive &&
242+ s . prefix === prefix &&
243+ s . suffix === suffix ,
244+ )
241245 if ( existingNode ) {
242246 nextNode = existingNode
243247 } else {
@@ -271,12 +275,15 @@ function parseSegments<TRouteLike extends RouteLike>(
271275 : actuallyCaseSensitive
272276 ? suffix_raw
273277 : suffix_raw . toLowerCase ( )
274- const existingNode = node . optional ?. find (
275- ( s ) =>
276- s . caseSensitive === actuallyCaseSensitive &&
277- s . prefix === prefix &&
278- s . suffix === suffix ,
279- )
278+ const existingNode =
279+ ! parse &&
280+ node . optional ?. find (
281+ ( s ) =>
282+ ! s . parse &&
283+ s . caseSensitive === actuallyCaseSensitive &&
284+ s . prefix === prefix &&
285+ s . suffix === suffix ,
286+ )
280287 if ( existingNode ) {
281288 nextNode = existingNode
282289 } else {
@@ -326,6 +333,7 @@ function parseSegments<TRouteLike extends RouteLike>(
326333 }
327334 node = nextNode
328335 }
336+ node . parse = parse
329337 if ( ( route . path || ! route . children ) && ! route . isRoot ) {
330338 const isIndex = path . endsWith ( '/' )
331339 // we cannot fuzzy match an index route,
@@ -351,9 +359,21 @@ function parseSegments<TRouteLike extends RouteLike>(
351359}
352360
353361function sortDynamic (
354- a : { prefix ?: string ; suffix ?: string ; caseSensitive : boolean } ,
355- b : { prefix ?: string ; suffix ?: string ; caseSensitive : boolean } ,
362+ a : {
363+ prefix ?: string
364+ suffix ?: string
365+ caseSensitive : boolean
366+ parse : null | ( ( params : Record < string , string > ) => any )
367+ } ,
368+ b : {
369+ prefix ?: string
370+ suffix ?: string
371+ caseSensitive : boolean
372+ parse : null | ( ( params : Record < string , string > ) => any )
373+ } ,
356374) {
375+ if ( a . parse && ! b . parse ) return - 1
376+ if ( ! a . parse && b . parse ) return 1
357377 if ( a . prefix && b . prefix && a . prefix !== b . prefix ) {
358378 if ( a . prefix . startsWith ( b . prefix ) ) return - 1
359379 if ( b . prefix . startsWith ( a . prefix ) ) return 1
@@ -421,6 +441,7 @@ function createStaticNode<T extends RouteLike>(
421441 parent : null ,
422442 isIndex : false ,
423443 notFound : null ,
444+ parse : null ,
424445 }
425446}
426447
@@ -451,6 +472,7 @@ function createDynamicNode<T extends RouteLike>(
451472 parent : null ,
452473 isIndex : false ,
453474 notFound : null ,
475+ parse : null ,
454476 caseSensitive,
455477 prefix,
456478 suffix,
@@ -508,6 +530,9 @@ type SegmentNode<T extends RouteLike> = {
508530
509531 /** Same as `route`, but only present if both an "index route" and a "layout route" exist at this path */
510532 notFound : T | null
533+
534+ /** route.options.params.parse function, set on the last node of the route */
535+ parse : null | ( ( params : Record < string , string > ) => any )
511536}
512537
513538type RouteLike = {
@@ -517,6 +542,9 @@ type RouteLike = {
517542 isRoot ?: boolean
518543 options ?: {
519544 caseSensitive ?: boolean
545+ params ?: {
546+ parse ?: ( params : Record < string , string > ) => any
547+ }
520548 }
521549} &
522550 // router tree
@@ -706,7 +734,7 @@ function findMatch<T extends RouteLike>(
706734 const parts = path . split ( '/' )
707735 const leaf = getNodeMatch ( path , parts , segmentTree , fuzzy )
708736 if ( ! leaf ) return null
709- const params = extractParams ( path , parts , leaf )
737+ const [ params ] = extractParams ( path , parts , leaf )
710738 const isFuzzyMatch = '**' in leaf
711739 if ( isFuzzyMatch ) params [ '**' ] = leaf [ '**' ]
712740 const route = isFuzzyMatch
@@ -721,16 +749,23 @@ function findMatch<T extends RouteLike>(
721749function extractParams < T extends RouteLike > (
722750 path : string ,
723751 parts : Array < string > ,
724- leaf : { node : AnySegmentNode < T > ; skipped : number } ,
725- ) {
752+ leaf : {
753+ node : AnySegmentNode < T >
754+ skipped : number
755+ extract ?: { part : number ; node : number ; path : number }
756+ params ?: Record < string , string >
757+ } ,
758+ ) : [
759+ params : Record < string , string > ,
760+ state : { part : number ; node : number ; path : number } ,
761+ ] {
726762 const list = buildBranch ( leaf . node )
727763 let nodeParts : Array < string > | null = null
728764 const params : Record < string , string > = { }
729- for (
730- let partIndex = 0 , nodeIndex = 0 , pathIndex = 0 ;
731- nodeIndex < list . length ;
732- partIndex ++ , nodeIndex ++ , pathIndex ++
733- ) {
765+ let partIndex = leaf . extract ?. part ?? 0
766+ let nodeIndex = leaf . extract ?. node ?? 0
767+ let pathIndex = leaf . extract ?. path ?? 0
768+ for ( ; nodeIndex < list . length ; partIndex ++ , nodeIndex ++ , pathIndex ++ ) {
734769 const node = list [ nodeIndex ] !
735770 const part = parts [ partIndex ]
736771 const currentPathIndex = pathIndex
@@ -785,7 +820,8 @@ function extractParams<T extends RouteLike>(
785820 break
786821 }
787822 }
788- return params
823+ if ( leaf . params ) Object . assign ( params , leaf . params )
824+ return [ params , { part : partIndex , node : nodeIndex , path : pathIndex } ]
789825}
790826
791827function buildRouteBranch < T extends RouteLike > ( route : T ) {
@@ -823,6 +859,10 @@ type MatchStackFrame<T extends RouteLike> = {
823859 statics : number
824860 dynamics : number
825861 optionals : number
862+ /** intermediary state for param extraction */
863+ extract ?: { part : number ; node : number ; path : number }
864+ /** intermediary params from param extraction */
865+ params ?: Record < string , string >
826866}
827867
828868function getNodeMatch < T extends RouteLike > (
@@ -862,8 +902,22 @@ function getNodeMatch<T extends RouteLike>(
862902
863903 while ( stack . length ) {
864904 const frame = stack . pop ( ) !
865- // eslint-disable-next-line prefer-const
866- let { node, index, skipped, depth, statics, dynamics, optionals } = frame
905+ const { node, index, skipped, depth, statics, dynamics, optionals } = frame
906+ let { extract, params } = frame
907+
908+ if ( node . parse ) {
909+ // if there is a parse function, we need to extract the params that we have so far and run it.
910+ // if this function throws, we cannot consider this a valid match
911+ try {
912+ ; [ params , extract ] = extractParams ( path , parts , frame )
913+ // TODO: can we store the parsed value somewhere to avoid re-parsing later?
914+ node . parse ( params )
915+ frame . extract = extract
916+ frame . params = params
917+ } catch {
918+ continue
919+ }
920+ }
867921
868922 // In fuzzy mode, track the best partial match we've found so far
869923 if ( fuzzy && node . notFound && isFrameMoreSpecific ( bestFuzzy , frame ) ) {
@@ -913,6 +967,8 @@ function getNodeMatch<T extends RouteLike>(
913967 statics,
914968 dynamics,
915969 optionals,
970+ extract,
971+ params,
916972 }
917973 break
918974 }
@@ -933,6 +989,8 @@ function getNodeMatch<T extends RouteLike>(
933989 statics,
934990 dynamics,
935991 optionals,
992+ extract,
993+ params,
936994 } ) // enqueue skipping the optional
937995 }
938996 if ( ! isBeyondPath ) {
@@ -954,6 +1012,8 @@ function getNodeMatch<T extends RouteLike>(
9541012 statics,
9551013 dynamics,
9561014 optionals : optionals + 1 ,
1015+ extract,
1016+ params,
9571017 } )
9581018 }
9591019 }
@@ -979,6 +1039,8 @@ function getNodeMatch<T extends RouteLike>(
9791039 statics,
9801040 dynamics : dynamics + 1 ,
9811041 optionals,
1042+ extract,
1043+ params,
9821044 } )
9831045 }
9841046 }
@@ -997,6 +1059,8 @@ function getNodeMatch<T extends RouteLike>(
9971059 statics : statics + 1 ,
9981060 dynamics,
9991061 optionals,
1062+ extract,
1063+ params,
10001064 } )
10011065 }
10021066 }
@@ -1013,6 +1077,8 @@ function getNodeMatch<T extends RouteLike>(
10131077 statics : statics + 1 ,
10141078 dynamics,
10151079 optionals,
1080+ extract,
1081+ params,
10161082 } )
10171083 }
10181084 }
@@ -1031,6 +1097,8 @@ function getNodeMatch<T extends RouteLike>(
10311097 return {
10321098 node : bestFuzzy . node ,
10331099 skipped : bestFuzzy . skipped ,
1100+ extract : bestFuzzy . extract ,
1101+ params : bestFuzzy . params ,
10341102 '**' : decodeURIComponent ( splat ) ,
10351103 }
10361104 }
0 commit comments