diff --git a/docs/router/framework/react/routing/virtual-file-routes.md b/docs/router/framework/react/routing/virtual-file-routes.md index 3e308ee7c9d..a285a49223a 100644 --- a/docs/router/framework/react/routing/virtual-file-routes.md +++ b/docs/router/framework/react/routing/virtual-file-routes.md @@ -224,6 +224,43 @@ export const routes = rootRoute('root.tsx', [ ]) ``` +### Merging Physical Routes at Current Level + +You can also use `physical` with an empty path prefix (or a single argument) to merge routes from a physical directory directly at the current level, without adding a path prefix. This is useful when you want to organize your routes into separate directories but have them appear at the same URL level. + +Consider the following file structure: + +``` +/routes +├── __root.tsx +├── about.tsx +└── features + ├── index.tsx + └── contact.tsx +``` + +You can merge the `features` directory routes at the root level: + +```tsx +// routes.ts +import { physical, rootRoute, route } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('__root.tsx', [ + route('/about', 'about.tsx'), + // Merge features/ routes at root level (no path prefix) + physical('features'), + // Or equivalently: physical('', 'features') +]) +``` + +This will produce the following routes: + +- `/about` - from `about.tsx` +- `/` - from `features/index.tsx` +- `/contact` - from `features/contact.tsx` + +> **Note:** When merging at the same level, ensure there are no conflicting route paths between your virtual routes and the physical directory routes. If a conflict occurs (e.g., both have an `/about` route), the generator will throw an error. + ## Virtual Routes inside of TanStack Router File Based routing The previous section showed you how you can use TanStack Router's File Based routing convention inside of a virtual route configuration. diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 0ecb325ceee..01f485581f8 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -1090,9 +1090,22 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved }) if (transformResult.result === 'no-route-export') { - this.logger.warn( - `Route file "${node.fullPath}" does not contain any route piece. This is likely a mistake.`, - ) + const fileName = path.basename(node.fullPath) + const dirName = path.dirname(node.fullPath) + const ignorePrefix = this.config.routeFileIgnorePrefix + const ignorePattern = this.config.routeFileIgnorePattern + const suggestedFileName = `${ignorePrefix}${fileName}` + const suggestedFullPath = path.join(dirName, suggestedFileName) + + let message = `Warning: Route file "${node.fullPath}" does not export a Route. This file will not be included in the route tree.` + message += `\n\nIf this file is not intended to be a route, you can exclude it using one of these options:` + message += `\n 1. Rename the file to "${suggestedFullPath}" (prefix with "${ignorePrefix}")` + message += `\n 2. Use 'routeFileIgnorePattern' in your config to match this file` + message += `\n\nCurrent configuration:` + message += `\n routeFileIgnorePrefix: "${ignorePrefix}"` + message += `\n routeFileIgnorePattern: ${ignorePattern ? `"${ignorePattern}"` : 'undefined'}` + + this.logger.warn(message) return null } if (transformResult.result === 'error') { diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 42594d7b0c0..40c66e30ed4 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -104,6 +104,18 @@ function rewriteConfigByFolderName(folderName: string, config: Config) { case 'virtual-config-file-default-export': config.virtualRouteConfig = './routes.ts' break + case 'virtual-physical-empty-path-merge': + config.virtualRouteConfig = './routes.ts' + break + case 'virtual-physical-empty-path-conflict-root': + config.virtualRouteConfig = './routes.ts' + break + case 'virtual-physical-empty-path-conflict-virtual': + config.virtualRouteConfig = './routes.ts' + break + case 'virtual-physical-no-prefix': + config.virtualRouteConfig = './routes.ts' + break case 'virtual-with-escaped-underscore': { // Test case for escaped underscores in physical routes mounted via virtual config @@ -243,6 +255,12 @@ function shouldThrow(folderName: string) { if (folderName === 'duplicate-fullPath') { return `Conflicting configuration paths were found for the following routes: "/", "/".` } + if (folderName === 'virtual-physical-empty-path-conflict-root') { + return `Conflicting configuration paths were found for the following routes: "/__root", "/__root".` + } + if (folderName === 'virtual-physical-empty-path-conflict-virtual') { + return `Conflicting configuration paths were found for the following routes: "/about", "/about".` + } return undefined } diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes.ts b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes.ts new file mode 100644 index 00000000000..c2b6660fc3c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes.ts @@ -0,0 +1,5 @@ +import { physical, rootRoute } from '@tanstack/virtual-file-routes' + +// This test verifies that a __root.tsx in a physical directory mounted at root +// produces a proper conflict error with the virtual root +export const routes = rootRoute('__root.tsx', [physical('', 'merged')]) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/__root.tsx new file mode 100644 index 00000000000..f463b796b44 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/__root.tsx @@ -0,0 +1,5 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => , +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/index.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/index.tsx new file mode 100644 index 00000000000..bad00da72c9 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () =>
Index
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/route.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/route.tsx new file mode 100644 index 00000000000..a164afcb81c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/routes/merged/route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +// This route.tsx in a physical directory mounted at root level +// conflicts with the virtual root __root.tsx - can't have two root routes +export const Route = createFileRoute('')({}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/tsr.config.json new file mode 100644 index 00000000000..4d587108e38 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-root/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes.ts b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes.ts new file mode 100644 index 00000000000..1a6c713d514 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes.ts @@ -0,0 +1,8 @@ +import { physical, rootRoute, route } from '@tanstack/virtual-file-routes' + +// This test verifies that a virtual route path conflicts with +// a physical route path when using empty path prefix +export const routes = rootRoute('__root.tsx', [ + route('/about', 'about.tsx'), // virtual /about + physical('', 'merged'), // physical also has about.tsx -> /about +]) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/__root.tsx new file mode 100644 index 00000000000..f463b796b44 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/__root.tsx @@ -0,0 +1,5 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => , +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/about.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/about.tsx new file mode 100644 index 00000000000..47bedea6c8c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/about.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from '@tanstack/react-router' + +// Virtual about route - conflicts with merged/about.tsx +export const Route = createFileRoute('/about')({ + component: () =>
About (virtual)
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/about.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/about.tsx new file mode 100644 index 00000000000..6b15c67503c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/about.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from '@tanstack/react-router' + +// Physical about route - conflicts with virtual about.tsx -> /about +export const Route = createFileRoute('/about')({ + component: () =>
About (physical)
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/index.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/index.tsx new file mode 100644 index 00000000000..bad00da72c9 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/routes/merged/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () =>
Index
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/tsr.config.json new file mode 100644 index 00000000000..4d587108e38 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-conflict-virtual/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts new file mode 100644 index 00000000000..6de1ed69756 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts @@ -0,0 +1,95 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as ContactRouteImport } from './routes/merged/contact' +import { Route as aboutRouteImport } from './routes/about' +import { Route as IndexRouteImport } from './routes/merged/index' + +const ContactRoute = ContactRouteImport.update({ + id: '/contact', + path: '/contact', + getParentRoute: () => rootRouteImport, +} as any) +const aboutRoute = aboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' | '/contact' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' | '/contact' + id: '__root__' | '/' | '/about' | '/contact' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + aboutRoute: typeof aboutRoute + ContactRoute: typeof ContactRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/contact': { + id: '/contact' + path: '/contact' + fullPath: '/contact' + preLoaderRoute: typeof ContactRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof aboutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + aboutRoute: aboutRoute, + ContactRoute: ContactRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes.ts b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes.ts new file mode 100644 index 00000000000..c429574e1a6 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes.ts @@ -0,0 +1,8 @@ +import { physical, rootRoute, route } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('__root.tsx', [ + // Virtual route defined here + route('/about', 'about.tsx'), + // Physical mount with empty path - should merge into root level + physical('', 'merged'), +]) diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/__root.tsx new file mode 100644 index 00000000000..9c657c7d5b4 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/about.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/about.tsx new file mode 100644 index 00000000000..e56767c8ad8 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/about.tsx @@ -0,0 +1,2 @@ +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/about')() diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/contact.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/contact.tsx new file mode 100644 index 00000000000..12a392def8d --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/contact.tsx @@ -0,0 +1,2 @@ +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/contact')() diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/index.tsx b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/index.tsx new file mode 100644 index 00000000000..d1d6296babb --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routes/merged/index.tsx @@ -0,0 +1,2 @@ +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/')() diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/tsr.config.json new file mode 100644 index 00000000000..4d587108e38 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts new file mode 100644 index 00000000000..6de1ed69756 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts @@ -0,0 +1,95 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as ContactRouteImport } from './routes/merged/contact' +import { Route as aboutRouteImport } from './routes/about' +import { Route as IndexRouteImport } from './routes/merged/index' + +const ContactRoute = ContactRouteImport.update({ + id: '/contact', + path: '/contact', + getParentRoute: () => rootRouteImport, +} as any) +const aboutRoute = aboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof aboutRoute + '/contact': typeof ContactRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' | '/contact' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' | '/contact' + id: '__root__' | '/' | '/about' | '/contact' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + aboutRoute: typeof aboutRoute + ContactRoute: typeof ContactRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/contact': { + id: '/contact' + path: '/contact' + fullPath: '/contact' + preLoaderRoute: typeof ContactRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof aboutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + aboutRoute: aboutRoute, + ContactRoute: ContactRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes.ts b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes.ts new file mode 100644 index 00000000000..0fa8033c6d3 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes.ts @@ -0,0 +1,8 @@ +import { physical, rootRoute, route } from '@tanstack/virtual-file-routes' + +// This test verifies the single-argument physical() overload +// which uses an empty path prefix (merges at current level) +export const routes = rootRoute('__root.tsx', [ + route('/about', 'about.tsx'), + physical('merged'), // Single argument - merges at root +]) diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/__root.tsx new file mode 100644 index 00000000000..f463b796b44 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/__root.tsx @@ -0,0 +1,5 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => , +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/about.tsx b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/about.tsx new file mode 100644 index 00000000000..51df2dd30ea --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/about.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/about')({ + component: () =>
About
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/contact.tsx b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/contact.tsx new file mode 100644 index 00000000000..77fd4df532e --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/contact.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/contact')({ + component: () =>
Contact
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/index.tsx b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/index.tsx new file mode 100644 index 00000000000..bad00da72c9 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routes/merged/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () =>
Index
, +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-no-prefix/tsr.config.json new file mode 100644 index 00000000000..4d587108e38 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/virtual-file-routes/src/api.ts b/packages/virtual-file-routes/src/api.ts index dbcaee7a366..f85c6ee44c8 100644 --- a/packages/virtual-file-routes/src/api.ts +++ b/packages/virtual-file-routes/src/api.ts @@ -83,13 +83,36 @@ export function route( } } +/** + * Mount a physical directory of route files at a given path prefix. + * + * @param pathPrefix - The path prefix to mount the directory at. Use empty string '' to merge routes at the current level. + * @param directory - The directory containing the route files, relative to the routes directory. + */ +export function physical(pathPrefix: string, directory: string): PhysicalSubtree +/** + * Mount a physical directory of route files at the current level (empty path prefix). + * This is equivalent to `physical('', directory)`. + * + * @param directory - The directory containing the route files, relative to the routes directory. + */ +export function physical(directory: string): PhysicalSubtree export function physical( - pathPrefix: string, - directory: string, + pathPrefixOrDirectory: string, + directory?: string, ): PhysicalSubtree { + if (directory === undefined) { + // Single argument: directory only, use empty path prefix + return { + type: 'physical', + directory: pathPrefixOrDirectory, + pathPrefix: '', + } + } + // Two arguments: pathPrefix and directory return { type: 'physical', directory, - pathPrefix, + pathPrefix: pathPrefixOrDirectory, } }