@@ -7,6 +7,7 @@ import type { Context } from "./context";
7
7
import * as Params from "./params" ;
8
8
import * as Route from "./route" ;
9
9
import type { RouteManifestEntry } from "../config/routes" ;
10
+ import exp from "constants" ;
10
11
11
12
export type VirtualFile = { filename : string ; content : string } ;
12
13
@@ -59,7 +60,7 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
59
60
// precompute
60
61
const fileToRoutes = new Map < string , Set < string > > ( ) ;
61
62
const lineages = new Map < string , Array < RouteManifestEntry > > ( ) ;
62
- const pages = new Set < string > ( ) ;
63
+ const allPages = new Set < string > ( ) ;
63
64
const routeToPages = new Map < string , Set < string > > ( ) ;
64
65
for ( const route of Object . values ( ctx . config . routes ) ) {
65
66
// fileToRoutes
@@ -75,9 +76,10 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
75
76
lineages . set ( route . id , lineage ) ;
76
77
77
78
// pages
78
- const page = Route . fullpath ( lineage ) ;
79
- if ( ! page ) continue ;
80
- pages . add ( page ) ;
79
+ const fullpath = Route . fullpath ( lineage ) ;
80
+ if ( ! fullpath ) continue ;
81
+ const pages = explodeOptionalSegments ( fullpath ) ;
82
+ pages . forEach ( ( page ) => allPages . add ( page ) ) ;
81
83
82
84
// routePages
83
85
lineage . forEach ( ( { id } ) => {
@@ -86,7 +88,7 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
86
88
routePages = new Set < string > ( ) ;
87
89
routeToPages . set ( id , routePages ) ;
88
90
}
89
- routePages . add ( page ) ;
91
+ pages . forEach ( ( page ) => routePages . add ( page ) ) ;
90
92
} ) ;
91
93
}
92
94
@@ -107,7 +109,7 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
107
109
}
108
110
` +
109
111
"\n\n" +
110
- Babel . generate ( pagesType ( pages ) ) . code +
112
+ Babel . generate ( pagesType ( allPages ) ) . code +
111
113
"\n\n" +
112
114
Babel . generate ( routeFilesType ( { fileToRoutes, routeToPages } ) ) . code ,
113
115
} ;
@@ -346,3 +348,49 @@ function paramsType(path: string) {
346
348
} )
347
349
) ;
348
350
}
351
+
352
+ // https://github.com/remix-run/react-router/blob/7a7f4b11ca8b26889ad328ba0ee5a749b0c6939e/packages/react-router/lib/router/utils.ts#L894C1-L937C2
353
+ function explodeOptionalSegments ( path : string ) : string [ ] {
354
+ let segments = path . split ( "/" ) ;
355
+ if ( segments . length === 0 ) return [ ] ;
356
+
357
+ let [ first , ...rest ] = segments ;
358
+
359
+ // Optional path segments are denoted by a trailing `?`
360
+ let isOptional = first . endsWith ( "?" ) ;
361
+ // Compute the corresponding required segment: `foo?` -> `foo`
362
+ let required = first . replace ( / \? $ / , "" ) ;
363
+
364
+ if ( rest . length === 0 ) {
365
+ // Interpret empty string as omitting an optional segment
366
+ // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
367
+ return isOptional ? [ required , "" ] : [ required ] ;
368
+ }
369
+
370
+ let restExploded = explodeOptionalSegments ( rest . join ( "/" ) ) ;
371
+
372
+ let result : string [ ] = [ ] ;
373
+
374
+ // All child paths with the prefix. Do this for all children before the
375
+ // optional version for all children, so we get consistent ordering where the
376
+ // parent optional aspect is preferred as required. Otherwise, we can get
377
+ // child sections interspersed where deeper optional segments are higher than
378
+ // parent optional segments, where for example, /:two would explode _earlier_
379
+ // then /:one. By always including the parent as required _for all children_
380
+ // first, we avoid this issue
381
+ result . push (
382
+ ...restExploded . map ( ( subpath ) =>
383
+ subpath === "" ? required : [ required , subpath ] . join ( "/" )
384
+ )
385
+ ) ;
386
+
387
+ // Then, if this is an optional value, add all child versions without
388
+ if ( isOptional ) {
389
+ result . push ( ...restExploded ) ;
390
+ }
391
+
392
+ // for absolute paths, ensure `/` instead of empty segment
393
+ return result . map ( ( exploded ) =>
394
+ path . startsWith ( "/" ) && exploded === "" ? "/" : exploded
395
+ ) ;
396
+ }
0 commit comments