UNPKG

vont

Version:

A full-stack framework combining Koa and React with file-based routing

139 lines 4.71 kB
import { promises as fs } from 'fs'; import path from 'path'; import { pathToFileURL } from 'url'; /** * 将文件路径转换为 API 路由路径 * 例如: users.ts -> /users * posts/[id].ts -> /posts/:id */ function filePathToRoute(filePath) { // 规范化路径分隔符 let normalizedPath = filePath.replace(/\\/g, '/'); // 移除文件扩展名 normalizedPath = normalizedPath.replace(/\.(ts|tsx|js|jsx)$/, ''); // 处理 index 文件 let routePath = normalizedPath.replace(/\/index$/, '') || '/'; // 将 [param] 替换为 :param routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1'); // 确保路由以 / 开头 if (!routePath.startsWith('/')) { routePath = '/' + routePath; } return routePath; } /** * 递归扫描目录,获取所有模块文件 */ async function scanDirectory(dir) { const files = []; try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { // 递归扫描子目录 const subFiles = await scanDirectory(fullPath); files.push(...subFiles); } else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name) && !entry.name.endsWith('.test.ts') && !entry.name.endsWith('.test.tsx')) { // 添加 TypeScript/JavaScript 文件(排除测试文件) files.push(fullPath); } } } catch (error) { // 目录不存在或无法访问 if (error.code !== 'ENOENT') { throw error; } } return files; } /** * 加载 TypeScript/JavaScript 模块 * 在开发环境中使用 tsx 加载 TypeScript */ async function loadModule(filePath) { const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); const isProduction = process.env.NODE_ENV === 'production'; try { if (isTypeScript && !isProduction) { // 开发环境:使用 tsx 加载 TypeScript // tsx 通过 Node.js loader 注册,支持直接导入 .ts 文件 const fileUrl = pathToFileURL(filePath).href; const module = await import(fileUrl); return module; } else { // 生产环境:加载编译后的 JavaScript const jsFile = isTypeScript ? filePath.replace(/\.tsx?$/, '.js') : filePath; const module = await import(jsFile); return module; } } catch (error) { return null; } } /** * 生成 API 路由 */ export async function generateApiRoutes(apiDir, apiPrefix) { const routes = []; // 扫描 API 目录 const files = await scanDirectory(apiDir); for (const file of files) { try { // 使用新的模块加载函数 const module = await loadModule(file); if (!module) { console.warn(`Warning: Could not load API module: ${file}`); continue; } // 获取文件相对于 API 目录的路径 const relativePath = path.relative(apiDir, file); const routePath = filePathToRoute(relativePath); // 获取模块中的中间件和处理函数 const middleware = module.middleware || []; const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options']; // 为每个 HTTP 方法创建路由 for (const method of httpMethods) { const handler = module[method]; if (handler) { routes.push({ path: apiPrefix + routePath, method: method.toUpperCase(), handler, middleware, }); } } } catch (error) { console.error(`Failed to load API module: ${file}`, error); } } return routes; } /** * 生成页面路由(扫描目录结构) */ export async function scanPageRoutes(pagesDir) { const files = await scanDirectory(pagesDir); return files .map((file) => { const relativePath = path.relative(pagesDir, file); return filePathToRoute(relativePath); }) .filter((route) => route); // 过滤空路由 } /** * 规范化路由路径(处理重复斜杠) */ export function normalizeRoutePath(routePath) { return '/' + routePath.replace(/^\/+/, '').replace(/\/+/g, '/'); } //# sourceMappingURL=router-generator.js.map