UNPKG

@gulibs/vgrove-autoroutes

Version:

🚀 基于文件系统的自动路由生成 Vite 插件,完整支持 React Router v7+ 和现代化的路由模式

626 lines (618 loc) 18.8 kB
import { RouteObject, unstable_MiddlewareFunction } from 'react-router'; import * as React from 'react'; import { ViteDevServer, PluginOption } from 'vite'; /** * 路由错误组件属性 */ interface RouteErrorComponentProps { /** 错误对象 */ error?: Error; /** 重试函数 */ retry?: () => void; /** 重置错误状态 */ reset?: () => void; } /** * 路由加载组件属性 */ interface RouteLoadingComponentProps { /** 加载进度 (0-1) */ progress?: number; /** 是否显示取消按钮 */ showCancel?: boolean; /** 取消加载回调 */ onCancel?: () => void; } /** * 路由风格类型 */ type RouteStyle = 'directory' | 'flat' | 'mixed'; /** * 中间件函数类型 */ type MiddlewareFunction = (request: Request, context: any, next: () => Promise<Response>) => Response | Promise<Response> | void | Promise<void>; /** * 页面路由配置 */ interface PageRoute extends Omit<RouteObject, 'element' | 'errorElement' | 'loader' | 'action' | 'children'> { /** 路由路径 */ path: string; /** 路由处理配置 */ handle?: any; /** 页面元素 */ element: string; /** 错误元素 */ errorElement?: string; /** 错误边界组件 - React Router v7+ */ ErrorBoundary?: React.ComponentType<{}> | null; /** 数据加载器 */ loader?: any; /** 路由动作(表单提交等) */ action?: any; /** 水合回退元素(加载组件) */ hydrateFallbackElement?: string; /** 加载元素(用于 Suspense fallback) */ loadingElement?: string; /** 加载组件(支持props的加载组件) */ loadingComponent?: string; /** 错误组件(支持props的错误组件) */ errorComponent?: string; /** 子路由 */ children?: PageRoutes; /** 路由索引标识 */ index?: boolean; /** 路由 ID */ id?: string; /** 是否为捕获所有路由 */ catchAll?: boolean; /** 是否应该重新验证数据 */ shouldRevalidate?: any; /** 懒加载路由配置 */ lazy?: any; /** loader 文件引用 */ loaderFile?: string; /** action 文件引用 */ actionFile?: string; /** 是否使用新的 loader/action 系统 */ useNewSystem?: boolean; /** React Router v7 中间件 */ unstable_middleware?: unstable_MiddlewareFunction<unknown>[]; /** 中间件文件引用 */ middlewareFiles?: string[]; /** loader 优先级来源 */ loaderSource?: 'file' | 'page' | 'both'; /** action 优先级来源 */ actionSource?: 'file' | 'page' | 'both'; /** 路由风格 */ routeStyle?: RouteStyle; } type PageRoutes = PageRoute[]; /** * 路由信息映射 */ interface RouteInfo { /** 页面组件映射 */ pageMap: Map<string, string>; /** 文件名数组 */ filenames: string[]; } /** * 路由配置文件导出的配置项(用于用户配置文件中的类型检查) */ interface RouteConfigs { /** 错误边界组件 */ error?: React.ComponentType<RouteErrorComponentProps>; /** 加载组件 */ loading?: React.ComponentType<RouteLoadingComponentProps>; /** loader 配置 */ loader?: any; /** action 配置 */ action?: any; } /** * 内部解析后的配置项(用于代码生成) */ interface InternalRouteConfigs { /** 错误边界组件字符串引用 */ error?: string; /** 加载组件字符串引用 */ loading?: string; /** loader 配置字符串引用 */ loader?: string; /** action 配置字符串引用 */ action?: string; } /** * 导入信息配置 */ interface ImportInfo { /** 是否启用动态导入 */ dynamic?: boolean; /** 导入语句 */ imported: string; /** 组件名称 */ component: string; } /** * 计算后的路由信息 */ interface ComputedInfo { /** 导入语句数组 */ imports: string[]; /** 路由映射 */ routeMap: Map<string, PageRoute>; } /** * 生成的路由信息 */ interface GeneratedInfo { /** 最终路由配置 */ finalRoutes: PageRoutes; /** 最终导入语句 */ finalImports: string[]; } /** * 路由目录配置选项 */ type RouteOption = { /** 路由目录路径 */ dir: string; /** 基础路由路径 */ baseRoute: string; /** 自定义文件匹配模式 */ include?: string[]; /** 排除的文件模式 */ exclude?: string[]; /** 是否启用嵌套路由 */ nested?: boolean; }; /** * 路由风格检测结果 */ interface RouteStyleDetection { /** 检测到的路由风格 */ style: RouteStyle; /** 置信度 (0-1) */ confidence: number; /** 检测依据 */ evidence: { /** 目录约定式文件数量 */ directoryFiles: number; /** 扁平风格文件数量 */ flatFiles: number; /** 混合风格文件数量 */ mixedFiles: number; }; } /** * 中间件文件信息 */ interface MiddlewareFileInfo { /** 文件路径 */ filePath: string; /** 中间件名称 */ name: string; /** 适用路径 */ paths: string[]; /** 优先级 */ priority: number; /** 导入名称 */ importName: string; } /** * Loader/Action 优先级配置 */ interface LoaderActionPriority { /** 优先使用独立文件 */ preferFiles?: boolean; /** 优先使用页面导出 */ preferPageExports?: boolean; /** 当两者都存在时的处理策略 */ conflictStrategy?: 'file' | 'page' | 'merge' | 'error'; } /** * 用户配置选项 */ interface IAutoRoutesPluginOptions { /** 路由目录配置 */ dirs: RouteOption[]; /** 是否启用动态导入 */ dynamic?: boolean; /** 是否启用缓存 */ cache?: boolean; /** 缓存时间(毫秒) */ cacheTime?: number; /** 是否启用调试模式 */ debug?: boolean; /** 路由模式 */ routeMode?: 'nested' | 'flat'; /** 全局文件匹配模式(应用到所有目录) */ include?: string[]; /** 全局排除文件模式(应用到所有目录) */ exclude?: string[]; /** 是否启用新的 loader/action 系统 */ useNewSystem?: boolean; /** 路由风格检测配置 */ routeStyleDetection?: { /** 是否启用自动检测 */ enabled?: boolean; /** 强制使用特定风格 */ forceStyle?: RouteStyle; }; /** Loader/Action 优先级配置 */ loaderActionPriority?: LoaderActionPriority; /** 是否启用中间件支持 */ enableMiddleware?: boolean; } /** * 解析后的配置集合 */ interface ParsedConfigs { /** 配置映射 */ configs: Map<string, InternalRouteConfigs>; /** 全局配置 */ global?: InternalRouteConfigs; } declare const MODULE_IDS: string[]; declare const MODULE_ID_VIRTUAL = "virtual:@gulibs/vgrove-autoroutes/generated-routes"; declare const DEFAULT_WATCH_PATH = "src/pages"; declare const SUPPORTED_PAGE_EXTENSIONS: string[]; declare const SPECIAL_FILE_NAMES: { PAGE: string[]; LAYOUT: string[]; ERROR: string[]; LOADING: string[]; HANDLE: string[]; MIDDLEWARE: string[]; GUARD: string[]; AUTH: string[]; LOADER: string[]; ACTION: string[]; }; declare const FILE_PATTERNS: { PAGE: string; LAYOUT: string; ERROR: string; LOADING: string; HANDLE: string; MIDDLEWARE: string; GUARD: string; AUTH: string; LOADER: string; ACTION: string; ALL: string; }; declare const DEFAULT_GLOBAL_EXCLUDE: string[]; declare const DEFAULT_ROUTE_OPTION: RouteOption; /** * 路由解析器 */ declare class RouteParser { private options?; private debug; private routeCache; private validFilesCache; private middlewareParser; private middlewareFiles; private importInfoMap; constructor(options?: IAutoRoutesPluginOptions); /** * 获取文件前缀,用于将具有相同前缀的文件分组到同一路由 * 例如: users.page.tsx -> users, [id].layout.ts -> [id] * * 根据PROBLEMS.md的要求修复: * - 对于目录结构,同一目录下的layout和page应该分组在一起 * - users/layout.tsx 和 users/page.tsx 应该分组为 'users' * - users/auth/layout.tsx 应该分组为 'users.auth' */ private getFilePrefix; /** * 根据文件前缀将文件分组 */ private getRouteGroup; /** * 清除缓存 */ clearCache(): void; /** * 检测路由风格 * @param files 文件列表 * @returns 路由风格检测结果 */ detectRouteStyle(files: string[]): RouteStyleDetection; /** * 解析中间件文件 * @param files 文件列表 * @param baseDir 基础目录 * @returns 中间件文件信息数组 */ parseMiddlewareFiles(files: string[], baseDir: string): MiddlewareFileInfo[]; /** * 获取中间件适用的路由路径 */ private getMiddlewareRoutePath; /** * 获取中间件优先级 */ private getMiddlewarePriority; /** * 生成中间件导入名称 */ private generateMiddlewareImportName; private checkBaseRoutes; generateRoutes(dirs: RouteOption[]): Promise<GeneratedInfo>; private generateFlatRoutes; private generateNestedRoutes; private computeNestedRoutes; private pathParts; computeRoutes(options: RouteOption): Promise<ComputedInfo>; /** * 生成路由代码 */ code(computedInfo: GeneratedInfo): Promise<string>; /** * 生成中间件管理器代码(增强版) */ private generateMiddlewareManagerCode; /** * 转换为驼峰命名 */ private camelCase; /** * 文件类型检查 */ private validFiles; /** * 提取路由创建的通用逻辑 */ private buildRouteFromInfo; /** * 创建路由配置 */ private createRoute; /** * 创建嵌套路由配置 */ private createNestedRoute; /** * 检查loader文件是否导出ErrorBoundary * 这是一个简化的实现,基于文件名约定来判断 * 在实际应用中,可以通过 AST 解析或其他方式来检测 */ private checkLoaderExportsErrorBoundary; /** * 检查action文件是否导出ErrorBoundary */ private checkActionExportsErrorBoundary; /** * 检查页面模块是否导出特定功能 * 这是一个简化的实现,基于文件名约定来判断 * 在实际应用中,可以通过 AST 解析或其他方式来检测 */ private checkPageModuleExports; /** * 处理 loader 的优先级逻辑 */ private processLoaderWithPriority; /** * 处理 action 的优先级逻辑 */ private processActionWithPriority; /** * 查找适用于当前路由的中间件 */ private findApplicableMiddleware; /** * 文件匹配辅助方法 */ private getFirstMatchingFile; /** * 查找页面文件,支持新的 .page.tsx 格式 */ private getFirstMatchingPageFile; /** * 添加嵌套路由 */ private addNestedRoute; /** * 处理通配符路由 * 将 [...param] 格式转换为 React Router 的通配符格式 */ private processWildcardRoute; /** * 处理路由路径,支持动态参数 */ private processRoutePath; private pageToRoute; private combineRoutes; private getImportInfo; /** * 生成稳定的文件标识符,用于变量名生成 * 基于文件的相对路径生成稳定的标识符,避免热更新时的变量名冲突 */ private generateStableId; /** * 检查文件是否为有效的路由文件类型 */ private isValidFileType; private getRouteInfos; } declare class RoutesContext { private server?; private parser; private options?; private routeCache; private pendingUpdate; private fileHashes; private isGenerating; private generationQueue; constructor(options?: IAutoRoutesPluginOptions); private resolveOptions; setupServer(server: ViteDevServer): void; updateServer(): void; isRouteDir(filePath: string): boolean; /** * 检查是否应该忽略该路径的文件变化 */ private shouldIgnorePath; checkRouteDirOrUpdate(path: string): void; invalidateCache(): void; logWatcher(path: string, operation: string): void; setupWatcher(watcher: ViteDevServer['watcher']): void; invalidateModules(server: ViteDevServer): void; parsePageRequest(id: string): { moduleId: string; query: URLSearchParams; pageId: string | null; }; private generateCacheKey; private calculateFileHashes; private hasFilesChanged; generateRoutes(): Promise<string>; private _generateRoutesInternal; dispose(): void; } /** 日志级别 */ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success'; /** 日志模块 */ type LogModule = 'parser' | 'context' | 'plugin' | 'stringifier' | 'file-watcher' | 'cache' | 'route-generation' | 'middleware' | 'general'; /** 日志配置 */ interface LoggerConfig { /** 全局调试开关 */ enabled?: boolean; /** 是否写入文件 */ writeToFile?: boolean; /** 日志文件路径 */ logFilePath?: string; /** 是否在控制台输出 */ console?: boolean; /** 日志级别 */ level?: LogLevel; /** 模块开关 */ modules?: Partial<Record<LogModule, boolean>>; /** 是否包含时间戳 */ includeTimestamp?: boolean; /** 是否包含调用栈 */ includeStack?: boolean; /** 最大日志文件大小(MB) */ maxFileSize?: number; /** 保留的日志文件数量 */ maxFiles?: number; /** 是否显示用户友好的错误信息 */ showUserFriendlyErrors?: boolean; } /** 自动路由日志器 */ declare class AutoRoutesLogger { private static instance; private config; private logBuffer; private isWriting; private writeQueue; private constructor(); /** 获取单例实例 */ static getInstance(config?: LoggerConfig): AutoRoutesLogger; /** 更新配置 */ static updateConfig(config: Partial<LoggerConfig>): void; /** 确保日志目录存在 */ private ensureLogDirectory; /** 检查是否应该记录日志 */ private shouldLog; /** 格式化日志消息 */ private formatMessage; /** 获取控制台颜色 */ private getConsoleColor; /** 记录日志 */ private log; /** 写入文件 */ private writeToFile; /** 处理写入队列 */ private processWriteQueue; /** 检查并轮转日志文件 */ private checkAndRotateLog; /** 轮转日志文件 */ private rotateLogFile; /** 模块化日志方法 */ parser: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; context: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; plugin: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; stringifier: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; fileWatcher: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; cache: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; routeGeneration: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; middleware: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; general: { debug: (message: string, data?: any) => void; info: (message: string, data?: any) => void; warn: (message: string, data?: any) => void; error: (message: string, data?: any) => void; success: (message: string, data?: any) => void; }; /** 获取日志统计信息 */ getStats(): { totalLogs: number; queueSize: number; bufferSize: number; config: Required<LoggerConfig>; }; /** 清空日志缓冲区 */ clearBuffer(): void; /** 强制刷新日志到文件 */ flush(): Promise<void>; /** 释放资源 */ dispose(): void; /** 获取当前配置 */ getConfig(): Required<LoggerConfig>; } declare const logger: AutoRoutesLogger; declare const updateLoggerConfig: typeof AutoRoutesLogger.updateConfig; declare const autoRouter: (options: IAutoRoutesPluginOptions) => PluginOption[]; export { DEFAULT_GLOBAL_EXCLUDE, DEFAULT_ROUTE_OPTION, DEFAULT_WATCH_PATH, FILE_PATTERNS, MODULE_IDS, MODULE_ID_VIRTUAL, RouteParser, RoutesContext, SPECIAL_FILE_NAMES, SUPPORTED_PAGE_EXTENSIONS, autoRouter as default, logger, updateLoggerConfig }; export type { ComputedInfo, GeneratedInfo, IAutoRoutesPluginOptions, ImportInfo, InternalRouteConfigs, LoaderActionPriority, LogLevel, LogModule, LoggerConfig, MiddlewareFileInfo, MiddlewareFunction, PageRoute, PageRoutes, ParsedConfigs, RouteConfigs, RouteErrorComponentProps, RouteInfo, RouteLoadingComponentProps, RouteOption, RouteStyle, RouteStyleDetection };