Skip to content

Commit bf71530

Browse files
committed
feat(hybrid-routes): add simplified directory routing strategy
Signed-off-by: Ryan Bower <rbower@qti.qualcomm.com>
1 parent a9163f9 commit bf71530

1 file changed

Lines changed: 56 additions & 5 deletions

File tree

packages/frameworks/react-router-utils/src/node/hybrid-routes.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {minimatch} from "minimatch"
55
import {readdirSync, statSync} from "node:fs"
66
import {extname, join, relative, resolve, sep, win32} from "node:path"
77

8+
import type {RoutingStrategy} from "@qualcomm-ui/mdx-vite"
9+
810
export 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-
/(([+][\/\\][^\/\\:?*]+)|[\/\\]((index|route|layout|page)|(_[^\/\\:?*]+)|([^\/\\:?*]+\.route)))\.(ts|tsx|js|jsx|md|mdx)$$/,
112113
}
113114

115+
const routeRegex =
116+
/(([+][\/\\][^\/\\:?*]+)|[\/\\]((index|route|layout|page)|(_[^\/\\:?*]+)|([^\/\\:?*]+\.route)))\.(ts|tsx|js|jsx|md|mdx)$$/
117+
118+
const directoryRouteRegex = /(([+]?[\/\\][^\/\\:?*]+))\.(ts|tsx|js|jsx|md|mdx)$/
119+
114120
export 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

Comments
 (0)