我們要感謝 Remix 團隊 開創了虛擬檔案路由的概念。我們從他們的工作中獲得靈感,並將其調整以配合 TanStack Router 現有的基於檔案的路由樹生成功能。
虛擬檔案路由是一個強大的概念,允許您透過程式碼參照專案中的實際檔案來建構路由樹。這在以下情況下特別有用:
以下是一個快速範例,展示如何使用虛擬檔案路由將路由樹映射到專案中的實際檔案:
// routes.ts
import {
rootRoute,
route,
index,
layout,
physical,
} from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
index('index.tsx'),
layout('pathlessLayout.tsx', [
route('/dashboard', 'app/dashboard.tsx', [
index('app/dashboard-index.tsx'),
route('/invoices', 'app/dashboard-invoices.tsx', [
index('app/invoices-index.tsx'),
route('$id', 'app/invoice-detail.tsx'),
]),
]),
physical('/posts', 'posts'),
]),
])
// routes.ts
import {
rootRoute,
route,
index,
layout,
physical,
} from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
index('index.tsx'),
layout('pathlessLayout.tsx', [
route('/dashboard', 'app/dashboard.tsx', [
index('app/dashboard-index.tsx'),
route('/invoices', 'app/dashboard-invoices.tsx', [
index('app/invoices-index.tsx'),
route('$id', 'app/invoice-detail.tsx'),
]),
]),
physical('/posts', 'posts'),
]),
])
虛擬檔案路由可以透過以下方式進行設定:
如果您使用 Vite/Rspack/Webpack 的 TanStackRouter 插件,可以在設定插件時將路由檔案的路徑傳遞給 virtualRoutesConfig 選項來設定虛擬檔案路由:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite({
target: 'solid',
virtualRouteConfig: './routes.ts',
}),
react(),
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite({
target: 'solid',
virtualRouteConfig: './routes.ts',
}),
react(),
],
})
或者,您也可以直接在設定中定義虛擬路由:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
import { rootRoute } from '@tanstack/virtual-file-routes'
const routes = rootRoute('root.tsx', [
// ... 其餘虛擬路由樹
])
export default defineConfig({
plugins: [TanStackRouterVite({ virtualRouteConfig: routes }), react()],
})
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
import { rootRoute } from '@tanstack/virtual-file-routes'
const routes = rootRoute('root.tsx', [
// ... 其餘虛擬路由樹
])
export default defineConfig({
plugins: [TanStackRouterVite({ virtualRouteConfig: routes }), react()],
})
要建立虛擬檔案路由,您需要導入 @tanstack/virtual-file-routes 套件。此套件提供了一組函數,讓您可以建立參照專案中實際檔案的虛擬路由。該套件導出了以下幾個實用函數:
rootRoute 函數用於建立虛擬根路由。它接受一個檔案名稱和一個子路由陣列。以下是虛擬根路由的範例:
// routes.ts
import { rootRoute } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
// ... 子路由
])
// routes.ts
import { rootRoute } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
// ... 子路由
])
route 函數用於建立虛擬路由。它接受一個路徑、一個檔案名稱和一個子路由陣列。以下是虛擬路由的範例:
// routes.ts
import { route } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
route('/about', 'about.tsx', [
// ... 子路由
]),
])
// routes.ts
import { route } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
route('/about', 'about.tsx', [
// ... 子路由
]),
])
您也可以定義一個沒有檔案名稱的虛擬路由。這允許為其子路由設定共同的路徑前綴:
// routes.ts
import { route } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
route('/hello', [
route('/world', 'world.tsx'), // 完整路徑將是 "/hello/world"
route('/universe', 'universe.tsx'), // 完整路徑將是 "/hello/universe"
]),
])
// routes.ts
import { route } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
route('/hello', [
route('/world', 'world.tsx'), // 完整路徑將是 "/hello/world"
route('/universe', 'universe.tsx'), // 完整路徑將是 "/hello/universe"
]),
])
index 函數用於建立虛擬索引路由。它接受一個檔案名稱。以下是虛擬索引路由的範例:
import { index } from '@tanstack/virtual-file-routes'
const routes = rootRoute('root.tsx', [index('index.tsx')])
import { index } from '@tanstack/virtual-file-routes'
const routes = rootRoute('root.tsx', [index('index.tsx')])
layout 函數用於建立虛擬無路徑路由。它接受一個檔案名稱、一個子路由陣列和一個可選的無路徑 ID。以下是虛擬無路徑路由的範例:
// routes.ts
import { layout } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
layout('pathlessLayout.tsx', [
// ... 子路由
]),
])
// routes.ts
import { layout } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
layout('pathlessLayout.tsx', [
// ... 子路由
]),
])
您也可以指定一個無路徑 ID,為路由提供一個與檔案名稱不同的唯一識別碼:
// routes.ts
import { layout } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
layout('my-pathless-layout-id', 'pathlessLayout.tsx', [
// ... 子路由
]),
])
// routes.ts
import { layout } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
layout('my-pathless-layout-id', 'pathlessLayout.tsx', [
// ... 子路由
]),
])
實體虛擬路由是一種將目錄「掛載」到特定 URL 路徑下的方式,這些目錄遵循傳統的 TanStack Router 基於檔案的路由慣例。這在您使用虛擬路由自訂路由樹高層結構的同時,又想對子路由和目錄使用標準的基於檔案的路由慣例時特別有用。
考慮以下檔案結構:
/routes
├── root.tsx
├── index.tsx
├── pathless.tsx
├── app
│ ├── dashboard.tsx
│ ├── dashboard-index.tsx
│ ├── dashboard-invoices.tsx
│ ├── invoices-index.tsx
│ ├── invoice-detail.tsx
└── posts
├── index.tsx
├── $postId.tsx
├── $postId.edit.tsx
├── comments/
│ ├── index.tsx
│ ├── $commentId.tsx
└── likes/
├── index.tsx
├── $likeId.tsx
/routes
├── root.tsx
├── index.tsx
├── pathless.tsx
├── app
│ ├── dashboard.tsx
│ ├── dashboard-index.tsx
│ ├── dashboard-invoices.tsx
│ ├── invoices-index.tsx
│ ├── invoice-detail.tsx
└── posts
├── index.tsx
├── $postId.tsx
├── $postId.edit.tsx
├── comments/
│ ├── index.tsx
│ ├── $commentId.tsx
└── likes/
├── index.tsx
├── $likeId.tsx
讓我們使用虛擬路由來為除 posts 以外的所有內容自訂路由樹,然後使用實體虛擬路由將 posts 目錄掛載到 /posts 路徑下:
// routes.ts
export const routes = rootRoute('root.tsx', [
// 正常設定虛擬路由
index('index.tsx'),
layout('pathlessLayout.tsx', [
route('/dashboard', 'app/dashboard.tsx', [
index('app/dashboard-index.tsx'),
route('/invoices', 'app/dashboard-invoices.tsx', [
index('app/invoices-index.tsx'),
route('$id', 'app/invoice-detail.tsx'),
]),
]),
// 將 `posts` 目錄掛載到 `/posts` 路徑下
physical('/posts', 'posts'),
]),
])
// routes.ts
export const routes = rootRoute('root.tsx', [
// 正常設定虛擬路由
index('index.tsx'),
layout('pathlessLayout.tsx', [
route('/dashboard', 'app/dashboard.tsx', [
index('app/dashboard-index.tsx'),
route('/invoices', 'app/dashboard-invoices.tsx', [
index('app/invoices-index.tsx'),
route('$id', 'app/invoice-detail.tsx'),
]),
]),
// 將 `posts` 目錄掛載到 `/posts` 路徑下
physical('/posts', 'posts'),
]),
])
前一節展示了如何在虛擬路由設定中使用 TanStack Router 的基於檔案的路由慣例。
然而,反過來也是可行的。
您可以使用 TanStack Router 的基於檔案的路由慣例來設定應用程式的主要路由樹,並針對特定的子樹選擇使用虛擬路由設定。
考慮以下檔案結構:
/routes
├── __root.tsx
├── foo
│ ├── bar
│ │ ├── __virtual.ts
│ │ ├── details.tsx
│ │ ├── home.tsx
│ │ └── route.ts
│ └── bar.tsx
└── index.tsx
/routes
├── __root.tsx
├── foo
│ ├── bar
│ │ ├── __virtual.ts
│ │ ├── details.tsx
│ │ ├── home.tsx
│ │ └── route.ts
│ └── bar.tsx
└── index.tsx
讓我們看看包含特殊檔案 __virtual.ts 的 bar 目錄。此檔案指示生成器切換到此目錄(及其子目錄)的虛擬檔案路由設定。
__virtual.ts 為該路由樹的子樹設定虛擬路由。它使用與上述相同的 API,唯一的區別是沒有為該子樹定義 rootRoute:
// routes/foo/bar/__virtual.ts
import {
defineVirtualSubtreeConfig,
index,
route,
} from '@tanstack/virtual-file-routes'
export default defineVirtualSubtreeConfig([
index('home.tsx'),
route('$id', 'details.tsx'),
])
// routes/foo/bar/__virtual.ts
import {
defineVirtualSubtreeConfig,
index,
route,
} from '@tanstack/virtual-file-routes'
export default defineVirtualSubtreeConfig([
index('home.tsx'),
route('$id', 'details.tsx'),
])
輔助函數 defineVirtualSubtreeConfig 緊密模擬了 vite 的 defineConfig,允許您透過預設導出來定義子樹設定。預設導出可以是:
您可以隨意混合使用 TanStack Router 的基於檔案的路由慣例和虛擬路由設定。
讓我們更深入一點!
查看以下範例,它一開始使用基於檔案的路由慣例,切換到 /posts 的虛擬路由設定,再切回 /posts/lets-go 的基於檔案的路由慣例,最後又切換到 /posts/lets-go/deeper 的虛擬路由設定。
├── __root.tsx
├── index.tsx
├── posts
│ ├── __virtual.ts
│ ├── details.tsx
│ ├── home.tsx
│ └── lets-go
│ ├── deeper
│ │ ├── __virtual.ts
│ │ └── home.tsx
│ └── index.tsx
└── posts.tsx
├── __root.tsx
├── index.tsx
├── posts
│ ├── __virtual.ts
│ ├── details.tsx
│ ├── home.tsx
│ └── lets-go
│ ├── deeper
│ │ ├── __virtual.ts
│ │ └── home.tsx
│ └── index.tsx
└── posts.tsx
如果您使用 TanStack Router CLI,可以在 tsr.config.json 檔案中定義路由檔案的路徑來設定虛擬檔案路由:
// tsr.config.json
{
"virtualRouteConfig": "./routes.ts"
}
// tsr.config.json
{
"virtualRouteConfig": "./routes.ts"
}
或者,您也可以直接在設定中定義虛擬路由。雖然這種情況較少見,但可以透過在 tsr.config.json 檔案中添加 virtualRouteConfig 物件並定義您的虛擬路由,然後傳遞由 @tanstack/virtual-file-routes 套件中的 rootRoute/route/index 等函數生成的 JSON:
// tsr.config.json
{
"virtualRouteConfig": {
"type": "root",
"file": "root.tsx",
"children": [
{
"type": "index",
"file": "home.tsx"
},
{
"type": "route",
"file": "posts/posts.tsx",
"path": "/posts",
"children": [
{
"type": "index",
"file": "posts/posts-home.tsx"
},
{
"type": "route",
"file": "posts/posts-detail.tsx",
"path": "$postId"
}
]
},
{
"type": "layout",
"id": "first",
"file": "layout/first-pathless-layout.tsx",
"children": [
{
"type": "layout",
"id": "second",
"file": "layout/second-pathless-layout.tsx",
"children": [
{
"type": "route",
"file": "a.tsx",
"path": "/route-a"
},
{
"type": "route",
"file": "b.tsx",
"path": "/route-b"
}
]
}
]
}
]
}
}
// tsr.config.json
{
"virtualRouteConfig": {
"type": "root",
"file": "root.tsx",
"children": [
{
"type": "index",
"file": "home.tsx"
},
{
"type": "route",
"file": "posts/posts.tsx",
"path": "/posts",
"children": [
{
"type": "index",
"file": "posts/posts-home.tsx"
},
{
"type": "route",
"file": "posts/posts-detail.tsx",
"path": "$postId"
}
]
},
{
"type": "layout",
"id": "first",
"file": "layout/first-pathless-layout.tsx",
"children": [
{
"type": "layout",
"id": "second",
"file": "layout/second-pathless-layout.tsx",
"children": [
{
"type": "route",
"file": "a.tsx",
"path": "/route-a"
},
{
"type": "route",
"file": "b.tsx",
"path": "/route-b"
}
]
}
]
}
]
}
}
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.