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