@@ -77,7 +77,8 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
77
77
// pages
78
78
const fullpath = Route . fullpath ( lineage ) ;
79
79
if ( ! fullpath ) continue ;
80
- const pages = explodeOptionalSegments ( fullpath ) ;
80
+
81
+ const pages = expand ( fullpath ) ;
81
82
pages . forEach ( ( page ) => allPages . add ( page ) ) ;
82
83
83
84
// routePages
@@ -357,48 +358,30 @@ function paramsType(path: string) {
357
358
) ;
358
359
}
359
360
360
- // https://github.com/remix-run/react-router/blob/7a7f4b11ca8b26889ad328ba0ee5a749b0c6939e/packages/react-router/lib/router/utils.ts#L894C1-L937C2
361
- function explodeOptionalSegments ( path : string ) : string [ ] {
362
- let segments = path . split ( "/" ) ;
363
- if ( segments . length === 0 ) return [ ] ;
364
-
365
- let [ first , ...rest ] = segments ;
361
+ function expand ( fullpath : string ) : Set < string > {
362
+ function recurse ( segments : Array < string > , index : number ) : Array < string > {
363
+ if ( index === segments . length ) return [ "" ] ;
364
+ const segment = segments [ index ] ;
366
365
367
- // Optional path segments are denoted by a trailing `?`
368
- let isOptional = first . endsWith ( "?" ) ;
369
- // Compute the corresponding required segment: `foo?` -> `foo`
370
- let required = first . replace ( / \? $ / , "" ) ;
366
+ const isOptional = segment . endsWith ( "?" ) ;
367
+ const isDynamic = segment . startsWith ( ":" ) ;
368
+ const required = segment . replace ( / \? $ / , "" ) ;
371
369
372
- if ( rest . length === 0 ) {
373
- // Interpret empty string as omitting an optional segment
374
- // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
375
- return isOptional ? [ required , "" ] : [ required ] ;
376
- }
370
+ const keep = ! isOptional || isDynamic ;
371
+ const kept = isDynamic ? segment : required ;
377
372
378
- let restExploded = explodeOptionalSegments ( rest . join ( "/" ) ) ;
373
+ const withoutSegment = recurse ( segments , index + 1 ) ;
374
+ const withSegment = withoutSegment . map ( ( rest ) => [ kept , rest ] . join ( "/" ) ) ;
379
375
380
- let result : string [ ] = [ ] ;
381
-
382
- // All child paths with the prefix. Do this for all children before the
383
- // optional version for all children, so we get consistent ordering where the
384
- // parent optional aspect is preferred as required. Otherwise, we can get
385
- // child sections interspersed where deeper optional segments are higher than
386
- // parent optional segments, where for example, /:two would explode _earlier_
387
- // then /:one. By always including the parent as required _for all children_
388
- // first, we avoid this issue
389
- result . push (
390
- ...restExploded . map ( ( subpath ) =>
391
- subpath === "" ? required : [ required , subpath ] . join ( "/" )
392
- )
393
- ) ;
394
-
395
- // Then, if this is an optional value, add all child versions without
396
- if ( isOptional ) {
397
- result . push ( ...restExploded ) ;
376
+ if ( keep ) return withSegment ;
377
+ return [ ...withoutSegment , ...withSegment ] ;
398
378
}
399
379
400
- // for absolute paths, ensure `/` instead of empty segment
401
- return result . map ( ( exploded ) =>
402
- path . startsWith ( "/" ) && exploded === "" ? "/" : exploded
403
- ) ;
380
+ const segments = fullpath . split ( "/" ) ;
381
+ const expanded = new Set < string > ( ) ;
382
+ for ( let result of recurse ( segments , 0 ) ) {
383
+ if ( result !== "/" ) result = result . replace ( / \/ $ / , "" ) ;
384
+ expanded . add ( result ) ;
385
+ }
386
+ return expanded ;
404
387
}
0 commit comments