TL;DR: config 選項可以作為 function 提供使用者所需的 functions / env。
前情提要
我的 side project 是用 React Router v7,前身是 Remix,使用 app/routes.ts
定義 routes。
為了讓未來專案可以快速有個 solid base,所以這三天在處理 routes 跟 config,準備把現在這個已經有「Better Auth、Dashboard、TipTap Rich Editor、Email Service、Object Storage(我用 Cloudflare R2)」的專案,擴充一個「services」資料夾,未來其他專案的內容可以模組化的直接放在裡面,並只需要定義 config.tsx
就能在 Dashboard 顯示側邊按鈕,以及自定義任何 route。
先來看看原本的 config type。
export interface Service {
// ...
/**
* Routes specific to the service
* @example
* ```
* routes: [
* route('/new-shop', './routes/services/new-service/shop/layout.tsx', [
* index('./routes/services/new-service/shop/index.tsx'),
* route(
* ':productId',
* './routes/services/new-service/shop/product/route.tsx',
* ),
* ]),
* ]
* ```
*/
routes?: RouteConfig
// ...
}
這邊 routes 請未來的我直接提供 rrv7 官方的 routes.ts
用法,所以必須直接 import { route, index } from "@react-router/dev/routes"
。
實際的 config.ts
看起來像是這樣:
import { route, index } from "@react-router/dev/routes"
export const config = {
dashboard: {
logo: '/logo-100.png',
},
routes: [
route(
'/example-shop',
'./routes/services/example-service/shop/layout.tsx',
[
index('./routes/services/example-service/shop/index.tsx'),
route(
':productId',
'./routes/services/example-service/shop/product/route.tsx',
),
],
),
],
// ...
} satisfies Service
因為 config 會用到 route & index,而且會被其他 runtime 讀取(例如 /dashboard 讀取 service 的 logo),因此會被包進去最終的 bundle,造成在運行時(runtime)找不到位在 package.json
devDependencies 的 react-router/dev
。
解決方法:將原本的 routes Array 轉換成 Function
import type {
index,
layout,
prefix,
route,
RouteConfig,
} from '@react-router/dev/routes'
type RouteHelper = {
index: typeof index
route: typeof route
layout: typeof layout
prefix: typeof prefix
}
export interface Service {
// ...
/**
* Routes specific to the service
* @example
* ```
* routes: ({ route, index }) => [
* route('/new-shop', './routes/services/new-service/shop/layout.tsx', [
* index('./routes/services/new-service/shop/index.tsx'),
* route(
* ':productId',
* './routes/services/new-service/shop/product/route.tsx',
* ),
* ]),
* ]
* ```
*/
routes?: (helper: RouteHelper) => RouteConfig
// ...
}
這樣 service config 的 routes 就會是一個很單純的,不需要任何 import 的 Function!
只在 build 時建立 routes
原本的方式,讓開發者不管怎樣都要 import react-routes/dev
,現在則是只有 type,並且只有在真的會用到 routes 的地方(例如 app/routes.ts
),才會 import,並在使用時丟入 { index, route, prefix, layout }
就能取得。
像是下面這個:
import {
index,
layout,
prefix,
route,
type RouteConfig,
} from '@react-router/dev/routes'
export const servicesRoutes = () => {
const modules = getServiceRoutesModules()
const servicesRoutes: RouteConfig = []
/**
* Automatically includes all service routes without manual imports
*/
for (const [path, service] of Object.entries(modules)) {
try {
if (!service.routes) continue
// 丟入四個 route config functions,取得實際的 routes
const routes = service.routes({
index,
route,
prefix,
layout,
})
if (Array.isArray(routes)) {
servicesRoutes.push(...routes)
}
} catch (error) {
console.error(`Failed to load routes from ${path}:`, error)
}
}
return servicesRoutes
}
就這樣!其實很多大專案也是這樣處理的,像是 vite defineConfig
,或是 better-auth 的 betterAuth()
。
如果對我的 Side Project 有興趣的話可以去 Papa GitHub resource 看看~