@gulibs/vite-plugin-react-auto-routes v0.2.8
@gulibs/vite-plugin-react-auto-routes
一个基于文件系统的自动路由生成 Vite 插件,为 React 应用程序提供基于文件系统的自动路由生成功能,支持路由守卫、中间件、性能优化等高级特性。
📚 目录
✨ 功能特性
🚀 核心功能
- 基于文件系统的路由生成:根据文件结构自动生成路由,零配置即可使用
- 动态路由支持:支持
[param](单参数)和[...slug](通配符)语法 - 嵌套路由:支持布局组件和多层嵌套路由结构
- TypeScript 支持:完整的类型定义和 IntelliSense 支持,开发体验更佳
🛡️ 高级功能
- 路由守卫:基于角色和权限的访问控制,支持自定义守卫条件
- 中间件支持:路由级中间件处理,支持全局和局部中间件
- 用户认证:内置认证守卫和用户状态管理,支持双 Token 机制
- 错误处理:优雅的错误边界和 404 页面处理,提升用户体验
⚡ 性能优化
- 智能缓存:LRU 缓存策略,自动检测文件变更
- 并行处理:多目录并行路由生成,大幅提升构建速度
- 防抖节流:避免频繁重新构建,优化开发体验
- 内存优化:自动垃圾回收和清理机制
- 性能监控:内置性能追踪器,实时监控执行时间
🎯 开发体验
- 热重载:文件变更自动更新路由,无需重启开发服务器
- 调试模式:详细的路由生成日志和性能统计
- 错误提示:友好的错误信息和修复建议
- IDE 支持:完整的代码补全和类型检查
📊 性能表现
基于文件系统的路由生成,具有以下优势:
- 快速构建:增量式路由生成,只处理变更文件
- 智能缓存:自动缓存路由配置,减少重复解析
- 并行处理:多文件并行解析,提升大项目构建速度
- 内存优化:按需加载路由组件,减少内存占用
🚀 快速开始
安装
npm install @gulibs/vite-plugin-react-auto-routes
# 或
yarn add @gulibs/vite-plugin-react-auto-routes
# 或
pnpm add @gulibs/vite-plugin-react-auto-routes注意:从 v0.2.5 开始,客户端功能(如
RouteProtectionWrapper、hooks 等)已分离到独立的包@gulibs/auto-routes-client。如果您需要使用路由保护、中间件或其他客户端功能,请同时安装:
npm install @gulibs/auto-routes-client
# 或
yarn add @gulibs/auto-routes-client
# 或
pnpm add @gulibs/auto-routes-client基础配置
在vite.config.ts中配置插件:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import reactAutoRoutes from '@gulibs/vite-plugin-react-auto-routes'
export default defineConfig({
plugins: [
react(),
reactAutoRoutes({
dirs: [
{
dir: 'src/pages',
baseRoute: '/'
}
]
})
]
})在vite-env.d.ts中配置
/// <reference types="vite/client" />
/// <reference types="@gulibs/vite-plugin-react-auto-routes/react-routes" />高级配置
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import reactAutoRoutes from '@gulibs/vite-plugin-react-auto-routes'
export default defineConfig({
plugins: [
react(),
reactAutoRoutes({
dirs: [
{
dir: 'src/pages',
baseRoute: '/',
include: ['**/*.tsx', '**/*.ts'],
exclude: ['**/components/**', '**/*.test.*']
}
],
// 基础配置
cache: true,
cacheTime: 60000, // 1分钟缓存
debug: true,
dynamic: false,
routeMode: 'flat' // 'flat' | 'nested'
})
]
})使用路由
// src/App.tsx
import { Routes, Route } from 'react-router'
import { routes } from 'virtual:react-routes'
function App() {
return (
<Routes>
{routes.map((route, index) => (
<Route key={index} {...route} />
))}
</Routes>
)
}🎯 应用程序入口配置
基础配置
在应用程序的主入口文件(通常是 src/main.tsx)中,您需要导入自动生成的路由并配置 React Router。
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
import './index.css';
// 创建浏览器路由器
const router = createBrowserRouter(routes);
const root = createRoot(document.getElementById('root')!)
root.render(
<RouterProvider router={router} />
)不同路由器类型配置
根据您的应用需求,可以选择不同类型的路由器:
浏览器路由器(推荐)
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
const router = createBrowserRouter(routes);
const root = createRoot(document.getElementById('root')!)
root.render(<RouterProvider router={router} />)哈希路由器
适用于不支持 HTML5 History API 的环境:
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createHashRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
const router = createHashRouter(routes);
const root = createRoot(document.getElementById('root')!)
root.render(<RouterProvider router={router} />)内存路由器
适用于测试环境或非浏览器环境:
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createMemoryRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
const router = createMemoryRouter(routes, {
initialEntries: ['/'], // 初始路由
initialIndex: 0
});
const root = createRoot(document.getElementById('root')!)
root.render(<RouterProvider router={router} />)完整配置示例
结合状态管理、SEO 优化等功能的完整配置:
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { HelmetProvider } from 'react-helmet-async';
import { createHashRouter, RouterProvider } from 'react-router';
import { RecoilRoot } from 'recoil';
import { routes } from "~react-auto-pages";
import './index.css';
// 创建路由器
const router = createHashRouter(routes);
const root = createRoot(document.getElementById('root')!)
root.render(
<RecoilRoot>
<HelmetProvider>
<RouterProvider router={router} />
</HelmetProvider>
</RecoilRoot>
)添加全局错误边界
为了更好的用户体验,建议添加全局错误边界:
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
import { ErrorBoundary } from 'react-error-boundary';
import { routes } from "~react-auto-pages";
const router = createBrowserRouter(routes);
function ErrorFallback({error, resetErrorBoundary}) {
return (
<div className="error-fallback">
<h2>出现了错误</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
)
}
const root = createRoot(document.getElementById('root')!)
root.render(
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => console.error('Application error:', error)}
>
<RouterProvider router={router} />
</ErrorBoundary>
)路由器配置选项
您可以为路由器添加额外的配置选项:
// src/main.tsx
import { createBrowserRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
const router = createBrowserRouter(routes, {
// 基础路径配置
basename: process.env.NODE_ENV === 'production' ? '/my-app' : '/',
// 未来路由配置
future: {
v7_startTransition: true,
v7_normalizeFormMethod: true
}
});
// 添加路由监听器
router.subscribe((state) => {
console.log('Route changed:', state.location.pathname);
});
const root = createRoot(document.getElementById('root')!)
root.render(<RouterProvider router={router} />)开发环境调试
在开发环境中,您可以添加一些调试功能:
main.tsx
// src/main.tsx
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
import { routes } from "~react-auto-pages";
const router = createBrowserRouter(routes);
// 开发环境路由调试
if (process.env.NODE_ENV === 'development') {
console.log('Generated routes:', routes);
// 监听路由变化
router.subscribe((state) => {
console.log('Navigation:', {
pathname: state.location.pathname,
search: state.location.search,
hash: state.location.hash
});
});
}
const root = createRoot(document.getElementById('root')!)
root.render(<RouterProvider router={router} />)📁 文件系统路由
约定式路由规则
React Auto Routes 遵循以下文件系统路由约定:
支持的文件类型
| 文件类型 | 文件名约定 | 说明 |
|---|---|---|
| 页面组件 | page.tsx, page.ts, index.tsx, index.ts | 定义路由页面组件 |
| 布局组件 | layout.tsx, layout.ts, _layout.tsx, _layout.ts | 定义嵌套布局 |
| 错误页面 | error.tsx, 404.tsx, _error.tsx | 错误边界和 404 页面 |
| 加载组件 | loading.tsx, _loading.tsx | 加载状态组件 |
| 路由处理器 | handle.ts, _handle.ts | 路由数据处理 |
| 中间件 | middleware.ts, _middleware.ts | 路由中间件 |
| 路由守卫 | guard.ts, _guard.ts | 路由守卫 |
| 认证守卫 | auth.ts, _auth.ts | 认证检查 |
动态路由约定
| 路由模式 | 文件路径 | 生成路由 | 说明 |
|---|---|---|---|
| 基础路由 | src/pages/about/page.tsx | /about | 静态路由 |
| 单参数路由 | src/pages/users/[id]/page.tsx | /users/:id | 动态参数 |
| 多参数路由 | src/pages/blog/[year]/[month]/page.tsx | /blog/:year/:month | 多个参数 |
| 通配符路由 | src/pages/docs/[...slug]/page.tsx | /docs/* | 捕获所有 |
文件优先级
当同一目录下存在多个文件时,按以下优先级处理:
page.tsx>index.tsx>page.ts>index.tslayout.tsx>_layout.tsx>layout.ts>_layout.tserror.tsx>404.tsx>_error.tsx
基本路由
src/pages/
├── page.tsx # / (首页)
├── about/
│ └── page.tsx # /about
├── contact/
│ └── page.tsx # /contact
└── 404.tsx # 404 页面动态路由
src/pages/
├── users/
│ ├── page.tsx # /users
│ └── [id]/
│ └── page.tsx # /users/:id
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── docs/
└── [...path]/
└── page.tsx # /docs/* (通配符路由)嵌套路由和布局
src/pages/
├── layout.tsx # 根布局
├── page.tsx # /
├── admin/
│ ├── layout.tsx # 管理布局
│ ├── page.tsx # /admin
│ ├── users/
│ │ └── page.tsx # /admin/users
│ └── settings/
│ └── page.tsx # /admin/settings特殊文件
src/pages/
├── layout.tsx # 布局组件
├── error.tsx # 错误边界
├── loading.tsx # 加载组件
├── handle.ts # 路由处理器
├── middleware.ts # 中间件
├── guard.ts # 路由守卫
└── auth.ts # 认证守卫🛡️ 路由守卫
定义守卫
// src/pages/admin/guard.ts
import { defineGuard } from '@gulibs/auto-routes-client'
export default defineGuard({
name: 'admin-guard',
type: 'role',
redirectTo: '/403',
errorMessage: '需要管理员权限',
condition: (context) => {
return context.user?.role === 'admin'
}
})认证守卫
// src/pages/profile/auth.ts
import { defineAuth } from '@gulibs/auto-routes-client'
export default defineAuth({
name: 'auth-check',
redirectTo: '/login',
errorMessage: '请先登录',
check: (context) => !!context.user,
roles: ['user', 'admin'],
permissions: ['read']
})GuardOptions 接口
interface GuardOptions {
/** 守卫名称 */
name?: string;
/** 守卫类型 */
type?: 'auth' | 'role' | 'permission' | 'custom';
/** 访问被拒绝时的重定向路径 */
redirectTo?: string;
/** 错误信息 */
errorMessage?: string;
/** 守卫条件函数 */
condition: (context: GuardContext) => boolean | Promise<boolean>;
}🔧 中间件
定义中间件
// src/pages/blog/middleware.ts
import { defineMiddleware } from '@gulibs/auto-routes-client'
export default defineMiddleware({
name: 'blog-logger',
handler: async (context, next) => {
console.log(`访问博客: ${context.path}`)
// 执行下一个中间件
await next()
}
})MiddlewareOptions 接口
interface MiddlewareOptions {
/** 中间件名称 */
name?: string;
/** 执行优先级(数字越小越先执行)*/
priority?: number;
/** 是否仅在开发环境执行 */
devOnly?: boolean;
/** 中间件处理函数 */
handler: (context: MiddlewareContext, next: () => Promise<void> | void) => Promise<void> | void;
}全局中间件
// src/middleware/logger.ts
export default defineMiddleware({
name: 'global-logger',
handler: async (context, next) => {
const start = Date.now()
try {
await next()
} finally {
const duration = Date.now() - start
console.log(`[${context.path}] ${duration}ms`)
}
},
priority: 0
})📈 性能监控
使用性能追踪
import { globalPerformanceTracker } from '@gulibs/vite-plugin-react-auto-routes/performance'
// 启动性能监控
globalPerformanceTracker.startTimer('route_generation')
// 结束监控
const duration = globalPerformanceTracker.endTimer('route_generation')
// 获取统计数据
const stats = globalPerformanceTracker.getStats()
console.log('性能统计:', stats)性能优化建议
// 开发环境配置
{
cache: true,
cacheTime: 10000, // 10秒快速响应
debug: true,
performance: {
enableRouteCache: true,
routeCacheSize: 50,
debounceDelay: 200,
enablePerformanceTracking: true
}
}
// 生产环境配置
{
cache: true,
cacheTime: 300000, // 5分钟长缓存
debug: false,
performance: {
enableRouteCache: true,
routeCacheSize: 500,
enableGuardCache: true,
guardCacheSize: 200,
enableParallelProcessing: true,
enableMemoryOptimization: true
}
}🎯 React Hooks
useRouteProtection
综合的路由保护 Hook:
import { useRouteProtection } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function MyComponent() {
const { isLoading, guardError, user, hasAccess, performanceStats } = useRouteProtection(
guards,
middlewares
)
return (
<div>
{isLoading && <div>加载中...</div>}
{guardError && <div>错误: {guardError}</div>}
{hasAccess && <div>欢迎, {user?.name}!</div>}
</div>
)
}useRouteGuards
专门的守卫 Hook:
import { useRouteGuards } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function GuardedComponent({ guards }: { guards: any[] }) {
const { isChecking, guardError, hasAccess, retryCheck } = useRouteGuards(guards)
if (isChecking) return <div>检查权限...</div>
if (!hasAccess) return <div>访问被拒绝: {guardError}</div>
return <div>内容</div>
}useRouteMiddleware
中间件执行 Hook:
import { useRouteMiddleware } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function MiddlewareComponent({ middlewares }: { middlewares: any[] }) {
const { isExecuting, executionTime, middlewareError, executeAgain } = useRouteMiddleware(middlewares)
return (
<div>
{isExecuting && <div>执行中间件...</div>}
{executionTime && <div>执行时间: {executionTime}ms</div>}
{middlewareError && (
<div>
中间件错误: {middlewareError}
<button onClick={executeAgain}>重试</button>
</div>
)}
</div>
)
}usePageConfig
页面配置综合 Hook:
import { usePageConfig } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function PageLayout() {
const { breadcrumbs, meta, layoutSettings, handle } = usePageConfig()
return (
<div>
{/* 面包屑导航 */}
<nav className="breadcrumbs">
{breadcrumbs.map((item, index) => (
<a key={index} href={item.href} className={item.className}>
{item.startContent}
{item.children}
{item.endContent}
</a>
))}
</nav>
{/* 页面标题 */}
<h1>{meta?.title}</h1>
<p>{meta?.description}</p>
{/* 根据布局设置调整页面 */}
<main className={layoutSettings?.layout ? 'with-layout' : 'no-layout'}>
{/* 页面内容 */}
</main>
{/* 页脚 */}
{layoutSettings?.footer && <footer>页脚内容</footer>}
</div>
)
}useHandle
获取页面 Handle 配置:
import { useHandle } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function PageComponent() {
const { meta, extras, layoutSettings, breadcrumbs, matches } = useHandle()
return (
<div>
<h1>{meta?.title}</h1>
{/* 渲染额外内容 */}
{extras}
{/* 显示当前匹配的路由信息 */}
<div>
当前路由层级: {matches?.length}
</div>
</div>
)
}useBreadcrumbs
面包屑导航 Hook:
import { useBreadcrumbs } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function BreadcrumbNav({ handle }) {
const breadcrumbs = useBreadcrumbs(handle)
if (breadcrumbs.length === 0) return null
return (
<nav className="breadcrumb-nav">
{breadcrumbs.map((crumb, index) => (
<div key={index} className="breadcrumb-item">
{crumb.startContent}
<a
href={crumb.href}
className={crumb.className}
aria-disabled={crumb.isDisabled}
aria-current={crumb.isCurrent ? 'page' : undefined}
>
{crumb.children}
</a>
{crumb.endContent}
</div>
))}
</nav>
)
}usePageMeta 和 useLayoutSettings
单独使用元数据和布局设置:
import { usePageMeta, useLayoutSettings } from '@gulibs/vite-plugin-react-auto-routes/hooks'
function DocumentHead() {
const meta = usePageMeta()
useEffect(() => {
if (meta?.title) {
document.title = meta.title
}
if (meta?.description) {
const metaDesc = document.querySelector('meta[name="description"]')
if (metaDesc) {
metaDesc.setAttribute('content', meta.description)
}
}
}, [meta])
return null
}
function LayoutController() {
const layoutSettings = useLayoutSettings()
return (
<div className={`page-container ${layoutSettings?.layout ? 'with-layout' : 'simple-layout'}`}>
{/* 根据布局设置渲染不同的结构 */}
</div>
)
}用户状态管理
基于 @gulibs/react-storage 的持久化用户状态管理,支持浏览器刷新后状态保持。
快速开始
import { useUser } from '@gulibs/auto-routes-client';
function App() {
const { user, isAuthenticated, login, logout } = useUser({
loginApi: async ({ username, password }) => {
// 调用你的登录 API
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
return { user: data.user, token: data.token };
}
});
if (!isAuthenticated) {
return <LoginPage onLogin={login} />;
}
return (
<div>
<h1>欢迎,{user?.username}!</h1>
<button onClick={logout}>退出登录</button>
</div>
);
}完整配置
import { useUser, useUserState, useAuthToken } from '@gulibs/auto-routes-client';
function App() {
// 完整的用户状态管理
const {
user,
token,
refreshToken,
isAuthenticated,
isLoading,
error,
login,
logout,
refreshUser,
refreshTokenAction,
updateUser,
setToken,
setRefreshToken,
clearError
} = useUser({
// 可选配置
storagePrefix: 'myapp', // 存储键前缀,默认 'auth'
useSessionStorage: false, // 是否使用 sessionStorage,默认 false(使用 localStorage)
// 登录 API 函数
loginApi: async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (!response.ok) throw new Error('Login failed');
const data = await response.json();
return { user: data.user, token: data.token, refreshToken: data.refreshToken };
},
// 刷新Token API
refreshTokenApi: async (refreshToken) => {
const response = await fetch('/api/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${refreshToken}`
}
});
if (!response.ok) throw new Error('Token refresh failed');
const data = await response.json();
return {
token: data.newToken,
refreshToken: data.newRefreshToken,
user: data.user
};
},
// 获取用户信息函数
fetchUser: async (token) => {
const response = await fetch('/api/user', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
// 自动刷新用户信息间隔(毫秒,0表示禁用)
autoRefreshInterval: 300000, // 5分钟
// Token 过期检查函数
isTokenExpired: (token) => {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return Date.now() >= payload.exp * 1000;
} catch {
return true;
}
},
// RefreshToken 过期检查函数
isRefreshTokenExpired: (refreshToken) => {
try {
const payload = JSON.parse(atob(refreshToken.split('.')[1]));
return Date.now() >= payload.exp * 1000;
} catch {
return true;
}
}
});
// 手动刷新Token示例
const handleRefreshToken = async () => {
try {
await refreshTokenAction();
console.log('Token刷新成功');
} catch (error) {
console.error('Token刷新失败:', error);
// Token刷新失败,通常意味着需要重新登录
}
};
return (
<div>
{isLoading && <div>加载中...</div>}
{error && (
<div style={{ color: 'red' }}>
错误:{error}
<button onClick={clearError}>清除错误</button>
</div>
)}
{isAuthenticated ? (
<div>
<h1>欢迎,{user?.username}!</h1>
<p>当前Token: {token?.substring(0, 20)}...</p>
<p>RefreshToken: {refreshToken ? '已设置' : '未设置'}</p>
<div>
<button onClick={logout}>登出</button>
<button onClick={refreshUser}>刷新用户信息</button>
<button onClick={handleRefreshToken}>刷新Token</button>
<button onClick={() => updateUser({ lastActive: new Date().toISOString() })}>
更新用户信息
</button>
</div>
</div>
) : (
<LoginForm onLogin={login} />
)}
</div>
);
}
function LoginForm({ onLogin }) {
const [credentials, setCredentials] = useState({ username: '', password: '' });
const handleSubmit = async (e) => {
e.preventDefault();
try {
await onLogin(credentials);
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="用户名"
value={credentials.username}
onChange={(e) => setCredentials(prev => ({ ...prev, username: e.target.value }))}
/>
<input
type="password"
placeholder="密码"
value={credentials.password}
onChange={(e) => setCredentials(prev => ({ ...prev, password: e.target.value }))}
/>
<button type="submit">登录</button>
</form>
);
}只读用户状态
如果只需要读取用户状态,不需要登录等操作:
import { useUserState } from '@gulibs/auto-routes-client';
function UserProfile() {
const { user, token, refreshToken, isAuthenticated } = useUserState('myapp');
if (!isAuthenticated) {
return <div>请先登录</div>;
}
return (
<div>
<h2>{user?.username}</h2>
<p>邮箱:{user?.email}</p>
<p>角色:{user?.roles?.join(', ')}</p>
<p>Token状态:{token ? '有效' : '无效'}</p>
<p>RefreshToken状态:{refreshToken ? '有效' : '无效'}</p>
</div>
);
}Token 管理
单独管理 Token:
import { useAuthToken } from '@gulibs/auto-routes-client';
function ApiService() {
const {
token,
refreshToken,
setToken,
setRefreshToken,
clearToken,
clearRefreshToken,
clearAllTokens,
hasToken,
hasRefreshToken
} = useAuthToken('myapp');
const apiCall = async () => {
if (!hasToken) {
throw new Error('No token available');
}
const response = await fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
};
const handleTokenRotation = async () => {
if (!hasRefreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await fetch('/api/refresh', {
method: 'POST',
headers: { 'Authorization': `Bearer ${refreshToken}` }
});
const data = await response.json();
setToken(data.newToken);
setRefreshToken(data.newRefreshToken);
} catch (error) {
// 刷新失败,清除所有Token
clearAllTokens();
throw error;
}
};
return {
apiCall,
handleTokenRotation,
hasValidTokens: hasToken && hasRefreshToken,
clearAllTokens
};
}⚙️ 配置选项
基础选项
interface UserOptions {
dirs: RouteOption[] // 路由目录配置
cache?: boolean // 启用缓存
cacheTime?: number // 缓存时间(ms)
debug?: boolean // 调试模式
dynamic?: boolean // 动态导入
routeMode?: 'nested' | 'flat' // 路由模式
}路由目录配置
interface RouteOption {
dir: string // 目录路径
baseRoute: string // 基础路由
include?: string[] // 包含文件模式
exclude?: string[] // 排除文件模式
}📚 示例项目
完整演示项目
查看 examples/auto-routes-demo/ 目录中的完整示例项目,包含:
📁 项目结构
src/pages/
├── page.tsx # 首页
├── layout.tsx # 根布局
├── loading.tsx # 全局加载组件
├── 404.tsx # 404 错误页面
├── getting-started/ # 快速开始指南
├── file-system-routing/ # 文件系统路由说明
├── dynamic-routes/ # 动态路由示例
├── route-guards/ # 路由守卫演示
├── middleware/ # 中间件使用
├── performance/ # 性能优化展示
├── hooks/ # React Hooks 示例
├── user-management/ # 用户状态管理
├── users/ # 用户列表和详情(动态路由)
│ ├── page.tsx # 用户列表页
│ └── [id]/page.tsx # 用户详情页
├── blog/ # 博客文章(动态路由)
│ ├── page.tsx # 文章列表页
│ └── [slug]/page.tsx # 文章详情页
└── test-nested/ # 嵌套路由测试
├── layout.tsx # 嵌套布局
├── page.tsx # 嵌套首页
├── loading.tsx # 嵌套加载
├── middleware.ts # 嵌套中间件
├── guard.ts # 嵌套守卫
└── admin/ # 管理员子路由
└── auth.ts # 认证配置🚀 运行示例
cd examples/auto-routes-demo
npm install
npm run dev🎮 功能演示
- ✅ 基础路由:文件系统自动路由生成
- ✅ 动态路由:用户详情页
/users/:id和博客文章/blog/:slug - ✅ 嵌套路由:多层布局和子路由结构
- ✅ 路由守卫:基于角色的访问控制
- ✅ 中间件:路由级数据预处理
- ✅ 性能监控:实时性能数据展示
- ✅ 用户状态:持久化用户认证状态
- ✅ 错误处理:优雅的 404 和错误边界
- ✅ TypeScript 支持:完整的类型定义
🔍 调试和开发
开启调试模式
reactAutoRoutes({
debug: true, // 开启调试输出
dirs: [...]
})调试输出示例:
[react-auto-routes] Generated 12 route(s) with 8 import(s) in 45ms
[react-auto-routes] Cache hit rate: 85%
[react-auto-routes] Guard execution: 0.02ms
[react-auto-routes] Middleware execution: 1.15ms
[react-auto-routes] Using cached routes (12 routes, 8 imports)性能分析
# 运行性能测试
npm run performance-test
# 查看构建分析
npm run build -- --analyze🛠️ 高级用法
自定义路由处理器
// src/pages/api/users/handle.ts
export default {
loader: async ({ params, request }) => {
// 数据加载逻辑
return await fetchUsers()
},
action: async ({ params, request }) => {
// 表单处理逻辑
return await createUser(await request.formData())
},
shouldRevalidate: ({ currentParams, nextParams }) => {
// 重新验证条件
return currentParams.id !== nextParams.id
}
}元数据和面包屑
// src/pages/blog/[slug]/handle.ts
export default {
meta: {
title: '博客详情',
description: '查看博客文章详情',
keywords: ['博客', '文章']
},
breadcrumbs: {
href: '/blog',
className: 'breadcrumb-item',
startContent: <HomeIcon />,
endContent: <ChevronRightIcon />
}
}错误边界
// src/pages/error.tsx
import { useRouteError } from 'react-router'
export default function ErrorBoundary() {
const error = useRouteError()
return (
<div className="error-page">
<h1>出错了!</h1>
<p>{error?.message || '发生了未知错误'}</p>
</div>
)
}📦 导出模块
主模块
import reactAutoRoutes from '@gulibs/vite-plugin-react-auto-routes'客户端工具
import {
defineHandle,
defineGuard,
defineAuth,
defineMiddleware,
defineRoleGuard,
definePermissionGuard,
defineCustomGuard
} from '@gulibs/auto-routes-client'React Hooks
注意:从 v0.2.5 开始,所有 React Hooks 已迁移到
@gulibs/auto-routes-client包中。
import {
useRouteProtection,
useRouteGuards,
useRouteMiddleware,
useUser,
useUserState,
useAuthToken,
useHandle,
useBreadcrumbs,
usePageMeta,
useLayoutSettings,
usePageConfig
} from '@gulibs/auto-routes-client'工具函数
注意:从 v0.2.5 开始,所有工具函数已迁移到
@gulibs/auto-routes-client包中。
import {
// 路由相关
normalizePath,
joinPath,
matchPath,
extractParams,
// 字符串处理
toCamelCase,
toKebabCase,
capitalize,
truncate,
// 对象和数组操作
deepClone,
deepMerge,
unique,
groupBy,
// 缓存
SimpleCache,
// 性能相关
debounce,
throttle,
measureTime,
// URL处理
parseQuery,
buildQuery,
updateQuery,
// 错误处理
safely,
safelyAsync
} from '@gulibs/auto-routes-client'性能工具
注意:从 v0.2.5 开始,所有性能工具已迁移到
@gulibs/auto-routes-client包中。
import {
PerformanceCache,
PerformanceTracker,
BatchProcessor,
Debouncer,
MemoryOptimizer,
globalPerformanceTracker,
globalMemoryOptimizer,
measureAsync,
throttle
} from '@gulibs/auto-routes-client'运行时
注意:从 v0.2.5 开始,所有运行时功能已迁移到
@gulibs/auto-routes-client包中。
import {
RuntimeExecutor,
createRuntimeContext
} from '@gulibs/auto-routes-client'错误处理
import {
AutoRouteError,
ErrorBuilder,
logError,
logWarning,
logInfo,
logSuccess
} from '@gulibs/vite-plugin-react-auto-routes/errors'📄 许可证
MIT © GuLibs
🙏 致谢
感谢所有贡献者和用户的支持!
📞 支持
如果这个项目对你有帮助,请给我们一个 ⭐️ !
功能特性
- ✅ 持久化存储:基于 localStorage/sessionStorage,刷新浏览器状态不丢失
- ✅ 跨标签页同步:多个标签页状态自动同步
- ✅ 完全灵活的泛型支持:无强制字段约束,用户可以定义任意类型结构
- ✅ 智能字段映射:支持任意 API 响应格式,自动映射字段名
- ✅ 强大的数据转换:登录前 / 后、存储前 / 后的完整数据转换链
- ✅ 双 Token 机制:支持 access token 和 refresh token 的完整认证流程
- ✅ 自动 Token 刷新:检测 token 过期并自动使用 refresh token 刷新
- ✅ 自动用户信息刷新:可配置定时刷新用户数据
- ✅ 完善的错误处理:全面的错误捕获和处理机制
- ✅ 完整的 TypeScript 支持:提供完整的类型定义和智能提示
- ✅ 多场景适配:支持微服务、第三方认证(Firebase、Auth0)等各种架构
最佳实践
合理设置存储类型:
- 敏感应用使用
sessionStorage(关闭浏览器后清除) - 一般应用使用
localStorage(持久保存)
- 敏感应用使用
Token 安全:
- 设置合理的过期时间
- 实现自动刷新机制
- 敏感操作时重新验证
错误处理:
- 监听
error状态 - 提供用户友好的错误提示
- 实现重试机制
- 监听
性能优化:
- 使用
useUserState进行只读访问 - 合理设置自动刷新间隔
- 避免不必要的 API 调用
- 使用
⚙️ 配置选项
完全自定义的用户类型
import { useUser, FieldMapping, DataTransformers } from '@gulibs/auto-routes-client';
// 完全自定义的用户类型(无需继承任何基础接口)
interface MyCustomUser {
userId: string;
fullName: string;
email: string;
department: {
id: number;
name: string;
};
settings: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
// 完全自定义的登录凭据
interface MyLoginData {
email: string;
password: string;
captcha: string;
rememberMe: boolean;
}
function App() {
const { user, login, logout } = useUser<MyCustomUser, MyLoginData>({
loginApi: async (credentials) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const data = await response.json();
// 你的API返回什么格式都可以,无需遵循任何规则
return data; // 例如: { userData: {...}, accessToken: "...", refreshToken: "..." }
},
// 字段映射:告诉hook你的API字段名
fieldMapping: {
userField: 'userData', // 你的API用户数据字段名
tokenField: 'accessToken', // 你的API token字段名
refreshTokenField: 'refreshToken'
},
// 数据转换:完全控制数据的转换过程
transformers: {
// 登录前转换凭据(例如加密密码)
transformCredentials: (credentials) => ({
...credentials,
password: btoa(credentials.password), // base64编码密码
deviceInfo: navigator.userAgent
}),
// 从API响应转换用户数据
transformUser: (apiUserData) => ({
userId: apiUserData.id,
fullName: `${apiUserData.firstName} ${apiUserData.lastName}`,
email: apiUserData.emailAddress,
department: apiUserData.dept,
settings: apiUserData.userSettings
}),
// 存储前转换(例如数据压缩、敏感信息移除)
beforeStore: (userData) => ({
...userData,
// 移除敏感信息,只存储必要数据
department: { ...userData.department },
settings: { ...userData.settings }
}),
// 读取后转换(例如数据恢复、默认值设置)
afterRestore: (storedData) => ({
...storedData,
// 添加默认值或计算属性
settings: {
theme: 'light',
notifications: true,
...storedData.settings
}
})
}
});
return (
<div>
{user ? (
<div>
<h1>欢迎,{user.fullName}!</h1>
<p>部门:{user.department.name}</p>
<p>主题:{user.settings.theme}</p>
<button onClick={logout}>登出</button>
</div>
) : (
<LoginForm onLogin={login} />
)}
</div>
);
}
function LoginForm({ onLogin }) {
const [credentials, setCredentials] = useState<MyLoginData>({
email: '',
password: '',
captcha: '',
rememberMe: false
});
const handleSubmit = async (e) => {
e.preventDefault();
await onLogin(credentials);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={credentials.email}
onChange={(e) => setCredentials(prev => ({ ...prev, email: e.target.value }))}
/>
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials(prev => ({ ...prev, password: e.target.value }))}
/>
<input
type="text"
placeholder="验证码"
value={credentials.captcha}
onChange={(e) => setCredentials(prev => ({ ...prev, captcha: e.target.value }))}
/>
<label>
<input
type="checkbox"
checked={credentials.rememberMe}
onChange={(e) => setCredentials(prev => ({ ...prev, rememberMe: e.target.checked }))}
/>
记住我
</label>
<button type="submit">登录</button>
</form>
);
}字段映射和数据转换
// 场景1:API返回的字段名与标准不同
const { user } = useUser<CustomUser, CustomCredentials>({
fieldMapping: {
userField: 'profile', // API返回 { profile: {...}, token: "...", refresh_token: "..." }
tokenField: 'token',
refreshTokenField: 'refresh_token'
}
});
// 场景2:复杂的数据转换
const { user } = useUser<CustomUser, CustomCredentials>({
transformers: {
// 转换API响应的用户数据
transformUser: (apiData) => ({
id: apiData.user_id,
name: `${apiData.first_name} ${apiData.last_name}`,
email: apiData.email_address,
roles: apiData.permissions.map(p => p.role),
avatar: apiData.profile_image_url || '/default-avatar.png'
}),
// 存储前数据处理
beforeStore: (userData) => {
// 移除大对象,只存储关键信息
const { avatar, ...essentialData } = userData;
return essentialData;
},
// 读取后数据恢复
afterRestore: (storedData) => ({
...storedData,
// 恢复默认头像
avatar: storedData.avatar || '/default-avatar.png',
// 添加计算属性
displayName: storedData.name || storedData.email
})
}
});
// 场景3:第三方API集成(例如Firebase、Auth0等)
const { user } = useUser<FirebaseUser, FirebaseCredentials>({
loginApi: async (credentials) => {
const firebaseResponse = await signInWithEmailAndPassword(auth, credentials.email, credentials.password);
const token = await firebaseResponse.user.getIdToken();
return {
user: firebaseResponse.user,
token: token,
refreshToken: firebaseResponse.user.refreshToken
};
},
transformers: {
transformUser: (firebaseUser) => ({
uid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName,
photoURL: firebaseUser.photoURL,
emailVerified: firebaseUser.emailVerified
})
}
});微服务架构场景
// 适配不同微服务的响应格式
interface UserServiceResponse {
success: boolean;
data: {
userInfo: CustomUser;
authToken: string;
refreshToken: string;
};
message: string;
}
const { user } = useUser<CustomUser, CustomCredentials>({
loginApi: async (credentials) => {
const response = await fetch('/api/user-service/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const result: UserServiceResponse = await response.json();
if (!result.success) {
throw new Error(result.message);
}
// 返回标准格式
return {
user: result.data.userInfo,
token: result.data.authToken,
refreshToken: result.data.refreshToken
};
},
// 或者使用字段映射
fieldMapping: {
userField: 'data.userInfo',
tokenField: 'data.authToken',
refreshTokenField: 'data.refreshToken'
}
});5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago