文件路由
文件路由模块提供了基于文件系统自动生成路由配置的能力。核心是 FileRouter 类,它封装了目录扫描、路由生成、增量更新等完整流程,可在 Vite、Webpack、Rollup 等构建工具中复用,也可在 Node.js 脚本中直接使用。
FileRouter
文件路由管理器,作为编排层组合扫描器、处理器和更新器,对外提供统一的路由管理 API。
构造函数
/**
* 创建文件路由管理器
* @param options - 配置选项
* @param init - 是否在构造时扫描页面目录,默认 true
*/
new FileRouter(options?: FileRouterOptions, init?: boolean)| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| options | [FileRouterOptions](#filerouteroptions) | {} | 文件路由配置选项 |
| init | boolean | true | 是否在构造时立即扫描页面目录 |
实例属性
| 属性 | 类型 | 说明 |
|---|---|---|
| config | ResolvedConfig | 解析后的配置对象(只读) |
| root | string | 项目根目录 |
| nodeTree | ScanNode[] | 顶层路由节点数组 |
| fileMap | Map<string, ScanNode> | 文件映射表,键为文件/目录路径,值为路由节点 |
实例方法
fileRouter.generate()
生成路由代码和类型定义。结果会被缓存,直到调用 clearGenerateResult() 或触发路由变更。
/**
* 生成路由代码和类型定义
* @returns 生成结果,包含 routes、code、dts
*/
fileRouter.generate(): GenerateResult返回值:
| 属性 | 类型 | 说明 |
|---|---|---|
| routes | RouteNode[] | 解析后的路由节点数组 |
| code | string | 生成的路由配置代码 |
| dts | string | 生成的 TypeScript 类型代码 |
fileRouter.reload()
重新加载所有页面,清空现有路由树和文件映射,重新扫描所有页面目录。
/**
* 重新加载所有页面
* @returns 当前实例,支持链式调用
*/
fileRouter.reload(): thisfileRouter.clearGenerateResult()
清空生成结果缓存,下次调用 generate() 时会重新生成。
fileRouter.clearGenerateResult(): voidfileRouter.isPageFile()
检查文件是否为页面文件。
/**
* 检查文件是否为页面文件
* @param file - 文件绝对路径
* @param filter - 可选的过滤配置,覆盖默认的 config.pages
* @returns 是否为页面文件
*/
fileRouter.isPageFile(file: string, filter?: FilterOptions | readonly FilterOptions[]): booleanfileRouter.getRouteFullPath()
获取文件的完整路由路径。非页面文件(布局文件、配置文件等)返回 null。
/**
* 获取文件的完整路由路径
* @param filePath - 文件绝对路径
* @returns 完整路由路径,非页面文件返回 null
*/
fileRouter.getRouteFullPath(filePath: string): string | nullfileRouter.addPage()
添加页面文件到路由树。
/**
* 添加页面文件到路由树
* @param filePath - 文件绝对路径
* @returns 是否创建了新的路由节点
*/
fileRouter.addPage(filePath: string): booleanfileRouter.removePage()
移除指定的文件或目录。移除成功后需调用 clearGenerateResult() 确保下次获取新的生成结果。
/**
* 移除指定的文件或目录
* @param filePath - 文件/目录绝对路径
* @returns 存在则移除并返回 true,不存在返回 false
*/
fileRouter.removePage(filePath: string): booleanfileRouter.updatePage()
更新文件。如果文件未被扫描,则会自动添加。
/**
* 更新文件
* @param filePath - 文件绝对路径
* @returns 是否更新了路由
*/
fileRouter.updatePage(filePath: string): booleanfileRouter.handleChange()
处理文件变化事件,根据事件类型分发到对应的增量操作,并在路由变更时自动清除生成缓存。
/**
* 处理文件变化事件
* @param eventName - 文件变化事件名
* @param path - 文件路径
* @returns 是否影响了路由
*/
fileRouter.handleChange(eventName: FileWatcherEvent, path: string): booleanFileWatcherEvent 类型:
| 值 | 说明 |
|---|---|
'add' | 文件新增 |
'addDir' | 目录新增 |
'change' | 文件内容变更 |
'unlink' | 文件删除 |
'unlinkDir' | 目录删除 |
fileRouter.removeDefinePage()
移除 definePage 宏调用。definePage 在客户端无法运行,构建时必须移除。
/**
* 移除 definePage 宏调用
* @param code - 源代码
* @param filePath - 文件路径
* @returns 转换后的代码,无需转换返回 null
*/
fileRouter.removeDefinePage(code: string, filePath: string): GeneratorResult | null示例
import { FileRouter } from 'vitarx-router/file-router'
// 创建实例并自动扫描
const router = new FileRouter({
root: process.cwd(),
pages: 'src/pages',
pathStrategy: 'kebab',
importMode: 'lazy',
dts: 'typed-router.d.ts',
extendRoute(route) {
route.meta ??= { auth: true }
}
})
// 生成路由代码
const result = router.generate()
console.log(result.code) // 路由配置代码
console.log(result.routes) // 路由节点数组
// 增量更新
router.addPage('/src/pages/new-page.tsx')
router.removePage('/src/pages/old-page.tsx')
router.handleChange('change', '/src/pages/home.tsx')
// 重新加载
router.reload()FileRouterOptions
文件路由配置选项。
interface FileRouterOptions {
root?: string
pages?: PageSource | readonly PageSource[]
pathStrategy?: PathStrategy
importMode?: ImportMode
dts?: boolean | string
layoutFileName?: string
configFileName?: string
injectImports?: readonly string[]
transform?: CodeTransformHook
extendRoute?: ExtendRouteHook
beforeWriteRoutes?: BeforeWriteRoutesHook
pageParser?: PageParser
groupParser?: GroupParser
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| root | string | process.cwd() | 项目根目录 |
| pages | PageSource | readonly PageSource[] | 'src/pages' | 页面来源配置,可以是目录路径字符串或 PageDirOptions 对象 |
| pathStrategy | PathStrategy | 'kebab' | 路径转换策略。'kebab' 转为 kebab-case,'lowercase' 转为小写,'raw' 不转换 |
| importMode | ImportMode | 'lazy' | 组件导入模式。'lazy' 生成懒加载表达式,'sync' 同步导入,函数自定义导入逻辑 |
| dts | boolean | string | false | 是否生成 TypeScript 类型定义文件。true 生成 router.d.ts,字符串指定文件路径 |
| layoutFileName | string | '_layout' | 布局文件名,匹配此名称的文件作为路由布局组件 |
| configFileName | string | '_config' | 分组配置文件名,匹配此名称的文件作为分组配置 |
| injectImports | readonly string[] | — | 注入在路由虚拟模块顶部的导入语句 |
| transform | CodeTransformHook | — | 代码转换钩子,通常用于转换 Markdown 文件内容为 ESModule |
| extendRoute | ExtendRouteHook | — | 扩展路由钩子,在路由生成后可修改路由配置 |
| beforeWriteRoutes | BeforeWriteRoutesHook | — | 写入路由之前的钩子 |
| pageParser | PageParser | — | 页面文件前置解析器,自定义文件名到路由路径的解析逻辑 |
| groupParser | GroupParser | — | 分组目录前置解析器,自定义目录名到路由路径的解析逻辑 |
PageDirOptions
页面目录配置,用于 pages 选项中需要细粒度控制的场景。
interface PageDirOptions {
dir: string
include?: readonly string[]
exclude?: readonly string[]
prefix?: string
group?: boolean
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| dir | string | — | 页面目录路径 |
| include | readonly string[] | ['**/*.{jsx,tsx}'] | 包含规则 |
| exclude | readonly string[] | ['**/node_modules/**', '**/dist/**', '**\/.*'] | 排除规则 |
| prefix | string | — | 路径前缀(group=true 时为分组前缀,否则为字符串前缀) |
| group | boolean | — | 是否作为路由分组 |
PageOptions
页面路由配置选项,用于 definePage 宏和路由节点。
interface PageOptions {
name?: string
meta?: RouteMetaData
pattern?: Record<string, RegExp>
redirect?: string | RedirectConfig
alias?: string | string[]
}| 属性 | 类型 | 说明 |
|---|---|---|
| name | string | 路由名称 |
| meta | RouteMetaData | 路由元数据 |
| pattern | Record<string, RegExp> | 动态参数匹配模式,自定义参数校验正则 |
| redirect | string | RedirectConfig | 重定向配置 |
| alias | string | string[] | 路由别名 |
ScanNode
扫描节点,文件系统扫描后生成的核心中间表示(IR)节点。
interface ScanNode {
dirConfigFile?: string
readonly isGroup: boolean
readonly filePath: string
readonly path: string
parent?: ScanNode
children?: Set<ScanNode>
components?: Record<string, string>
options?: PageOptions
}| 属性 | 类型 | 说明 |
|---|---|---|
| dirConfigFile | string | 目录配置文件路径 |
| isGroup | boolean | 是否为分组 |
| filePath | string | 文件绝对路径 |
| path | string | 当前路径(不含父级) |
| parent | ScanNode | 父节点 |
| children | Set<ScanNode> | 子节点集合 |
| components | Record<string, string> | 组件映射(命名视图) |
| options | PageOptions | 页面配置选项 |
RouteNode
路由节点,解析后的路由结构,用于代码生成。
interface RouteNode extends PageOptions {
isGroup: boolean
filePath: string
path: string
fullPath: string
children?: RouteNode[]
component?: Record<string, string>
}| 属性 | 类型 | 说明 |
|---|---|---|
| isGroup | boolean | 是否为分组 |
| filePath | string | 文件绝对路径 |
| path | string | 当前路径(不含父级) |
| fullPath | string | 完整路径(含父级) |
| children | RouteNode[] | 子路由节点 |
| component | Record<string, string> | 组件映射(命名视图),键为视图名,值为文件路径 |
继承 PageOptions 的 name、meta、pattern、redirect、alias 属性。
钩子类型
ExtendRouteHook
扩展路由钩子,在路由生成后可修改路由配置。
type ExtendRouteHook = (route: RouteNode) => voidWARNING
此时分组路由的 children 还未赋值。如需操作完整的路由树,请使用 BeforeWriteRoutesHook。
{
extendRoute(route) {
if (route.path === '/admin') {
route.meta ??= { auth: true }
}
}
}BeforeWriteRoutesHook
写入路由文件前的钩子,支持直接修改路由数组或返回新的路由数组。
type BeforeWriteRoutesHook = (routes: RouteNode[]) => void | RouteNode[]{
beforeWriteRoutes(routes) {
routes.forEach(route => {
if (route.path === '/') {
route.path = '/home'
}
})
}
}CodeTransformHook
代码转换函数,通常用于将 Markdown 等非标准文件转换为 ESModule。
type CodeTransformHook = (content: string, file: string) => string{
transform(content, file) {
if (file.endsWith('.md')) {
const html = markdownToHtml(content)
return `export default function Page() { return #123;html} }`
}
return content
}
}PageParser
页面文件前置解析器,自定义文件名到路由路径的解析逻辑。
type PageParser = (basename: string, filePath: string) => string | PageParseResult| 参数 | 类型 | 说明 |
|---|---|---|
| basename | string | 文件名称(不包含扩展名) |
| filePath | string | 完整的文件路径 |
返回 string 时交由内置解析器处理,返回 PageParseResult 时使用自定义解析结果。
{
pageParser(basename, filePath) {
// 移除文件名中的数字前缀
return basename.replace(/^d+./, '')
}
}GroupParser
分组目录前置解析器,自定义目录名到路由路径的解析逻辑。
type GroupParser = (dirName: string, dirPath: string) => string | GroupParseResult| 参数 | 类型 | 说明 |
|---|---|---|
| dirName | string | 目录名称 |
| dirPath | string | 目录完整路径 |
{
groupParser(dirName) {
const result = parseOrderAndName(dirName) // 如 '1.admin' → { order: 1, name: 'admin' }
return {
path: result.name,
options: { meta: { order: result.order } }
}
}
}ComponentImportHandler
自定义组件导入处理函数。
type ComponentImportHandler = (context: ImportModeContext) => 'lazy' | 'sync' | stringImportModeContext:
| 属性 | 类型 | 说明 |
|---|---|---|
| importPath | string | 组件文件路径(已 JSON 序列化) |
| filePath | string | 组件文件原始路径 |
| addImport | (statement: string) => void | 添加导入语句到生成代码顶部 |
{
importMode(context) {
context.addImport(`import { lazy } from 'vitarx'`)
return `lazy(() => import(#123;context.importPath}))`
}
}definePage()
页面路由配置宏,用于在页面文件中声明路由元信息。作为全局宏使用,无需导入,构建时会被自动移除。
类型定义
function definePage(options: PageOptions): void参数
| 参数 | 类型 | 说明 |
|---|---|---|
| options | [PageOptions](#pageoptions) | 页面路由配置选项 |
详情
definePage() 是一个编译时宏,仅在开发阶段提供类型提示和配置提取。构建时,插件会自动移除 definePage() 调用语句,不会产生任何运行时代码。
如果文件中存在多个 definePage() 调用,它们的配置会被合并,但建议每个文件只调用一次。
pattern 用于自定义动态路径参数的校验规则:
definePage({
pattern: {
id: /^[0-9]+$/ // id 参数只匹配数字
}
})redirect 支持字符串和对象两种格式:
// 字符串格式
definePage({ redirect: '/new-path' })
// 对象格式
definePage({
redirect: {
index: '/user',
params: { id: '1' },
query: { tab: 'info' }
}
})示例
// src/pages/user/[id].tsx
definePage({
name: 'userDetail',
meta: { title: '用户详情', auth: true },
pattern: { id: /^[0-9]+$/ },
alias: ['/u/{id}']
})
export default function UserDetail() {
return <div>用户详情</div>
}// src/pages/old-page.tsx
definePage({
redirect: '/new-page'
})
export default function OldPage() {
return <div>已重定向</div>
}参考:文件系统路由