@@ -77,7 +77,8 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
7777 // pages
7878 const fullpath = Route . fullpath ( lineage ) ;
7979 if ( ! fullpath ) continue ;
80- const pages = explodeOptionalSegments ( fullpath ) ;
80+
81+ const pages = expand ( fullpath ) ;
8182 pages . forEach ( ( page ) => allPages . add ( page ) ) ;
8283
8384 // routePages
@@ -357,48 +358,30 @@ function paramsType(path: string) {
357358 ) ;
358359}
359360
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 ] ;
366365
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 ( / \? $ / , "" ) ;
371369
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 ;
377372
378- let restExploded = explodeOptionalSegments ( rest . join ( "/" ) ) ;
373+ const withoutSegment = recurse ( segments , index + 1 ) ;
374+ const withSegment = withoutSegment . map ( ( rest ) => [ kept , rest ] . join ( "/" ) ) ;
379375
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 ] ;
398378 }
399379
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 ;
404387}
0 commit comments