@@ -5,6 +5,8 @@ import {minimatch} from "minimatch"
55import { readdirSync , statSync } from "node:fs"
66import { extname , join , relative , resolve , sep , win32 } from "node:path"
77
8+ import type { RoutingStrategy } from "@qualcomm-ui/mdx-vite"
9+
810export interface ConfigRoute {
911 /**
1012 * Should be `true` if the `path` is case-sensitive. Defaults to `false`.
@@ -95,6 +97,7 @@ export type FlatRoutesOptions = {
9597 paramPrefixChar ?: string
9698 routeDir ?: string | string [ ]
9799 routeRegex ?: RegExp
100+ routingStrategy ?: RoutingStrategy
98101 visitFiles ?: VisitFilesFunction
99102}
100103
@@ -107,10 +110,13 @@ const defaultOptions: FlatRoutesOptions = {
107110 basePath : "/" ,
108111 paramPrefixChar : "$" ,
109112 routeDir : "routes" ,
110- routeRegex :
111- / ( ( [ + ] [ \/ \\ ] [ ^ \/ \\ : ? * ] + ) | [ \/ \\ ] ( ( i n d e x | r o u t e | l a y o u t | p a g e ) | ( _ [ ^ \/ \\ : ? * ] + ) | ( [ ^ \/ \\ : ? * ] + \. r o u t e ) ) ) \. ( t s | t s x | j s | j s x | m d | m d x ) $ $ / ,
112113}
113114
115+ const routeRegex =
116+ / ( ( [ + ] [ \/ \\ ] [ ^ \/ \\ : ? * ] + ) | [ \/ \\ ] ( ( i n d e x | r o u t e | l a y o u t | p a g e ) | ( _ [ ^ \/ \\ : ? * ] + ) | ( [ ^ \/ \\ : ? * ] + \. r o u t e ) ) ) \. ( t s | t s x | j s | j s x | m d | m d x ) $ $ /
117+
118+ const directoryRouteRegex = / ( ( [ + ] ? [ \/ \\ ] [ ^ \/ \\ : ? * ] + ) ) \. ( t s | t s x | j s | j s x | m d | m d x ) $ /
119+
114120export function hybridRoutes (
115121 routeDir : string | string [ ] ,
116122 defineRoutes : DefineRoutesFunction ,
@@ -124,6 +130,11 @@ export function hybridRoutes(
124130 ...options ,
125131 defineRoutes,
126132 routeDir,
133+ routeRegex :
134+ ( options . routeRegex ??
135+ options . routingStrategy === "react-router-directory-groups" )
136+ ? directoryRouteRegex
137+ : routeRegex ,
127138 } ,
128139 )
129140 // update undefined parentIds to 'root'
@@ -168,8 +179,19 @@ function _flatRoutes(
168179 if ( ! defineRoutes ) {
169180 throw new Error ( "You must provide a defineRoutes function" )
170181 }
171- const visitFiles = options . visitFiles ?? defaultVisitFiles
172- const routeRegex = options . routeRegex ?? defaultOptions . routeRegex !
182+ const isDirectoryMode =
183+ options . routingStrategy === "react-router-directory-groups"
184+ const visitFiles =
185+ options . visitFiles ??
186+ ( isDirectoryMode
187+ ? ( dir , visitor , baseDir ) =>
188+ defaultVisitFiles ( dir , visitor , baseDir , {
189+ excludePrivateFolders : true ,
190+ } )
191+ : defaultVisitFiles )
192+ const routeRegex =
193+ options . routeRegex ??
194+ ( isDirectoryMode ? directoryRouteRegex : defaultOptions . routeRegex ! )
173195
174196 for ( const routeDir of routeDirs ) {
175197 visitFiles ( join ( appDir , routeDir ) , ( file ) => {
@@ -281,6 +303,7 @@ export function getRouteInfo(
281303 routeIdWithoutRoutes ,
282304 index ,
283305 options . paramPrefixChar ,
306+ options . routingStrategy ,
284307 )
285308 const routePath = createRoutePath ( routeSegments , index , options )
286309 const routeInfo = {
@@ -361,13 +384,15 @@ export function getRouteSegments(
361384 name : string ,
362385 index : boolean ,
363386 paramPrefixChar : string = "$" ,
387+ routingStrategy ?: RoutingStrategy ,
364388) {
365389 let routeSegments : string [ ] = [ ]
366390 let i = 0
367391 let routeSegment = ""
368392 let state = "START"
369393 let subState = "NORMAL"
370394 let hasPlus = false
395+ const isDirectoryMode = routingStrategy === "react-router-directory-groups"
371396
372397 // name has already been normalized to use / as path separator
373398
@@ -387,6 +412,24 @@ export function getRouteSegments(
387412 name = name . replace ( / \+ \/ / g, "." )
388413 hasPlus = true
389414 }
415+
416+ if ( isDirectoryMode && / \/ / . test ( name ) ) {
417+ /**
418+ * In directory mode, plain folders flatten the same way `+` folders do.
419+ * Preserve trailing `.route` so route-directory naming still works.
420+ */
421+ if ( name . endsWith ( ".route" ) ) {
422+ const lastSlash = name . lastIndexOf ( "/" )
423+ if ( lastSlash >= 0 ) {
424+ const head = name . substring ( 0 , lastSlash ) . replace ( / \/ / g, "." )
425+ name = `${ head } /${ name . substring ( lastSlash + 1 ) } `
426+ }
427+ } else {
428+ name = name . replace ( / \/ / g, "." )
429+ }
430+ hasPlus = true
431+ }
432+
390433 const hasFolder = / \/ / . test ( name )
391434 // if name has plus folder, but we still have regular folders
392435 // then treat ending route as flat-folders
@@ -480,13 +523,21 @@ export function defaultVisitFiles(
480523 dir : string ,
481524 visitor : ( file : string ) => void ,
482525 baseDir = dir ,
526+ visitOptions ?: { excludePrivateFolders ?: boolean } ,
483527) {
484528 for ( const filename of readdirSync ( dir ) ) {
485529 const file = resolve ( dir , filename )
486530 const stat = statSync ( file )
487531
488532 if ( stat . isDirectory ( ) ) {
489- defaultVisitFiles ( file , visitor , baseDir )
533+ if (
534+ visitOptions ?. excludePrivateFolders &&
535+ filename . startsWith ( "_" ) &&
536+ ! filename . includes ( "+" )
537+ ) {
538+ continue
539+ }
540+ defaultVisitFiles ( file , visitor , baseDir , visitOptions )
490541 } else if ( stat . isFile ( ) ) {
491542 visitor ( relative ( baseDir , file ) )
492543 }
0 commit comments