文件路由

文件路由模块提供了基于文件系统自动生成路由配置的能力。核心是 FileRouter 类,它封装了目录扫描、路由生成、增量更新等完整流程,可在 Vite、Webpack、Rollup 等构建工具中复用,也可在 Node.js 脚本中直接使用。

FileRouter

文件路由管理器,作为编排层组合扫描器、处理器和更新器,对外提供统一的路由管理 API。

构造函数

ts
/**
 * 创建文件路由管理器
 * @param options - 配置选项
 * @param init - 是否在构造时扫描页面目录,默认 true
 */
new FileRouter(options?: FileRouterOptions, init?: boolean)
参数类型默认值说明
options[FileRouterOptions](#filerouteroptions){}文件路由配置选项
initbooleantrue是否在构造时立即扫描页面目录

实例属性

属性类型说明
configResolvedConfig解析后的配置对象(只读)
rootstring项目根目录
nodeTreeScanNode[]顶层路由节点数组
fileMapMap<string, ScanNode>文件映射表,键为文件/目录路径,值为路由节点

实例方法

fileRouter.generate()

生成路由代码和类型定义。结果会被缓存,直到调用 clearGenerateResult() 或触发路由变更。

ts
/**
 * 生成路由代码和类型定义
 * @returns 生成结果,包含 routes、code、dts
 */
fileRouter.generate(): GenerateResult

返回值

属性类型说明
routesRouteNode[]解析后的路由节点数组
codestring生成的路由配置代码
dtsstring生成的 TypeScript 类型代码

fileRouter.reload()

重新加载所有页面,清空现有路由树和文件映射,重新扫描所有页面目录。

ts
/**
 * 重新加载所有页面
 * @returns 当前实例,支持链式调用
 */
fileRouter.reload(): this

fileRouter.clearGenerateResult()

清空生成结果缓存,下次调用 generate() 时会重新生成。

ts
fileRouter.clearGenerateResult(): void

fileRouter.isPageFile()

检查文件是否为页面文件。

ts
/**
 * 检查文件是否为页面文件
 * @param file - 文件绝对路径
 * @param filter - 可选的过滤配置,覆盖默认的 config.pages
 * @returns 是否为页面文件
 */
fileRouter.isPageFile(file: string, filter?: FilterOptions | readonly FilterOptions[]): boolean

fileRouter.getRouteFullPath()

获取文件的完整路由路径。非页面文件(布局文件、配置文件等)返回 null

ts
/**
 * 获取文件的完整路由路径
 * @param filePath - 文件绝对路径
 * @returns 完整路由路径,非页面文件返回 null
 */
fileRouter.getRouteFullPath(filePath: string): string | null

fileRouter.addPage()

添加页面文件到路由树。

ts
/**
 * 添加页面文件到路由树
 * @param filePath - 文件绝对路径
 * @returns 是否创建了新的路由节点
 */
fileRouter.addPage(filePath: string): boolean

fileRouter.removePage()

移除指定的文件或目录。移除成功后需调用 clearGenerateResult() 确保下次获取新的生成结果。

ts
/**
 * 移除指定的文件或目录
 * @param filePath - 文件/目录绝对路径
 * @returns 存在则移除并返回 true,不存在返回 false
 */
fileRouter.removePage(filePath: string): boolean

fileRouter.updatePage()

更新文件。如果文件未被扫描,则会自动添加。

ts
/**
 * 更新文件
 * @param filePath - 文件绝对路径
 * @returns 是否更新了路由
 */
fileRouter.updatePage(filePath: string): boolean

fileRouter.handleChange()

处理文件变化事件,根据事件类型分发到对应的增量操作,并在路由变更时自动清除生成缓存。

ts
/**
 * 处理文件变化事件
 * @param eventName - 文件变化事件名
 * @param path - 文件路径
 * @returns 是否影响了路由
 */
fileRouter.handleChange(eventName: FileWatcherEvent, path: string): boolean

FileWatcherEvent 类型:

说明
'add'文件新增
'addDir'目录新增
'change'文件内容变更
'unlink'文件删除
'unlinkDir'目录删除

fileRouter.removeDefinePage()

移除 definePage 宏调用。definePage 在客户端无法运行,构建时必须移除。

ts
/**
 * 移除 definePage 宏调用
 * @param code - 源代码
 * @param filePath - 文件路径
 * @returns 转换后的代码,无需转换返回 null
 */
fileRouter.removeDefinePage(code: string, filePath: string): GeneratorResult | null

示例

ts
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

文件路由配置选项。

ts
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
}
属性类型默认值说明
rootstringprocess.cwd()项目根目录
pagesPageSource | readonly PageSource[]'src/pages'页面来源配置,可以是目录路径字符串或 PageDirOptions 对象
pathStrategyPathStrategy'kebab'路径转换策略。'kebab' 转为 kebab-case,'lowercase' 转为小写,'raw' 不转换
importModeImportMode'lazy'组件导入模式。'lazy' 生成懒加载表达式,'sync' 同步导入,函数自定义导入逻辑
dtsboolean | stringfalse是否生成 TypeScript 类型定义文件。true 生成 router.d.ts,字符串指定文件路径
layoutFileNamestring'_layout'布局文件名,匹配此名称的文件作为路由布局组件
configFileNamestring'_config'分组配置文件名,匹配此名称的文件作为分组配置
injectImportsreadonly string[]注入在路由虚拟模块顶部的导入语句
transformCodeTransformHook代码转换钩子,通常用于转换 Markdown 文件内容为 ESModule
extendRouteExtendRouteHook扩展路由钩子,在路由生成后可修改路由配置
beforeWriteRoutesBeforeWriteRoutesHook写入路由之前的钩子
pageParserPageParser页面文件前置解析器,自定义文件名到路由路径的解析逻辑
groupParserGroupParser分组目录前置解析器,自定义目录名到路由路径的解析逻辑

PageDirOptions

页面目录配置,用于 pages 选项中需要细粒度控制的场景。

ts
interface PageDirOptions {
  dir: string
  include?: readonly string[]
  exclude?: readonly string[]
  prefix?: string
  group?: boolean
}
属性类型默认值说明
dirstring页面目录路径
includereadonly string[]['**/*.{jsx,tsx}']包含规则
excludereadonly string[]['**/node_modules/**', '**/dist/**', '**\/.*']排除规则
prefixstring路径前缀(group=true 时为分组前缀,否则为字符串前缀)
groupboolean是否作为路由分组

PageOptions

页面路由配置选项,用于 definePage 宏和路由节点。

ts
interface PageOptions {
  name?: string
  meta?: RouteMetaData
  pattern?: Record<string, RegExp>
  redirect?: string | RedirectConfig
  alias?: string | string[]
}
属性类型说明
namestring路由名称
metaRouteMetaData路由元数据
patternRecord<string, RegExp>动态参数匹配模式,自定义参数校验正则
redirectstring | RedirectConfig重定向配置
aliasstring | string[]路由别名

ScanNode

扫描节点,文件系统扫描后生成的核心中间表示(IR)节点。

ts
interface ScanNode {
  dirConfigFile?: string
  readonly isGroup: boolean
  readonly filePath: string
  readonly path: string
  parent?: ScanNode
  children?: Set<ScanNode>
  components?: Record<string, string>
  options?: PageOptions
}
属性类型说明
dirConfigFilestring目录配置文件路径
isGroupboolean是否为分组
filePathstring文件绝对路径
pathstring当前路径(不含父级)
parentScanNode父节点
childrenSet<ScanNode>子节点集合
componentsRecord<string, string>组件映射(命名视图)
optionsPageOptions页面配置选项

RouteNode

路由节点,解析后的路由结构,用于代码生成。

ts
interface RouteNode extends PageOptions {
  isGroup: boolean
  filePath: string
  path: string
  fullPath: string
  children?: RouteNode[]
  component?: Record<string, string>
}
属性类型说明
isGroupboolean是否为分组
filePathstring文件绝对路径
pathstring当前路径(不含父级)
fullPathstring完整路径(含父级)
childrenRouteNode[]子路由节点
componentRecord<string, string>组件映射(命名视图),键为视图名,值为文件路径

继承 PageOptionsnamemetapatternredirectalias 属性。


钩子类型

ExtendRouteHook

扩展路由钩子,在路由生成后可修改路由配置。

ts
type ExtendRouteHook = (route: RouteNode) => void

WARNING

此时分组路由的 children 还未赋值。如需操作完整的路由树,请使用 BeforeWriteRoutesHook

ts
{
  extendRoute(route) {
    if (route.path === '/admin') {
      route.meta ??= { auth: true }
    }
  }
}

BeforeWriteRoutesHook

写入路由文件前的钩子,支持直接修改路由数组或返回新的路由数组。

ts
type BeforeWriteRoutesHook = (routes: RouteNode[]) => void | RouteNode[]
ts
{
  beforeWriteRoutes(routes) {
    routes.forEach(route => {
      if (route.path === '/') {
        route.path = '/home'
      }
    })
  }
}

CodeTransformHook

代码转换函数,通常用于将 Markdown 等非标准文件转换为 ESModule。

ts
type CodeTransformHook = (content: string, file: string) => string
ts
{
  transform(content, file) {
    if (file.endsWith('.md')) {
      const html = markdownToHtml(content)
      return `export default function Page() { return #123;html} }`
    }
    return content
  }
}

PageParser

页面文件前置解析器,自定义文件名到路由路径的解析逻辑。

ts
type PageParser = (basename: string, filePath: string) => string | PageParseResult
参数类型说明
basenamestring文件名称(不包含扩展名)
filePathstring完整的文件路径

返回 string 时交由内置解析器处理,返回 PageParseResult 时使用自定义解析结果。

ts
{
  pageParser(basename, filePath) {
    // 移除文件名中的数字前缀
    return basename.replace(/^d+./, '')
  }
}

GroupParser

分组目录前置解析器,自定义目录名到路由路径的解析逻辑。

ts
type GroupParser = (dirName: string, dirPath: string) => string | GroupParseResult
参数类型说明
dirNamestring目录名称
dirPathstring目录完整路径
ts
{
  groupParser(dirName) {
    const result = parseOrderAndName(dirName) // 如 '1.admin' → { order: 1, name: 'admin' }
    return {
      path: result.name,
      options: { meta: { order: result.order } }
    }
  }
}

ComponentImportHandler

自定义组件导入处理函数。

ts
type ComponentImportHandler = (context: ImportModeContext) => 'lazy' | 'sync' | string

ImportModeContext

属性类型说明
importPathstring组件文件路径(已 JSON 序列化)
filePathstring组件文件原始路径
addImport(statement: string) => void添加导入语句到生成代码顶部
ts
{
  importMode(context) {
    context.addImport(`import { lazy } from 'vitarx'`)
    return `lazy(() => import(#123;context.importPath}))`
  }
}

definePage()

页面路由配置宏,用于在页面文件中声明路由元信息。作为全局宏使用,无需导入,构建时会被自动移除。

类型定义

ts
function definePage(options: PageOptions): void

参数

参数类型说明
options[PageOptions](#pageoptions)页面路由配置选项

详情

definePage() 是一个编译时宏,仅在开发阶段提供类型提示和配置提取。构建时,插件会自动移除 definePage() 调用语句,不会产生任何运行时代码。

如果文件中存在多个 definePage() 调用,它们的配置会被合并,但建议每个文件只调用一次。

pattern 用于自定义动态路径参数的校验规则:

ts
definePage({
  pattern: {
    id: /^[0-9]+$/ // id 参数只匹配数字
  }
})

redirect 支持字符串和对象两种格式:

ts
// 字符串格式
definePage({ redirect: '/new-path' })

// 对象格式
definePage({
  redirect: {
    index: '/user',
    params: { id: '1' },
    query: { tab: 'info' }
  }
})

示例

tsx
// 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>
}
tsx
// src/pages/old-page.tsx
definePage({
  redirect: '/new-page'
})

export default function OldPage() {
  return <div>已重定向</div>
}

参考文件系统路由