@gulibs/vgrove-autoroutes
Version:
🚀 基于文件系统的自动路由生成 Vite 插件,完整支持 React Router v7+ 和现代化的路由模式
626 lines (618 loc) • 18.8 kB
TypeScript
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 };