UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

201 lines (200 loc) 8.99 kB
/** * Next.js 15 Enhancement Pack * 条件: { languages: ['typescript', 'javascript'], frameworks: ['nextjs'] } * * 覆盖 Next.js 15 App Router 生态: * - App Router 文件约定 (layout/page/loading/error/not-found/route) * - Server Actions ("use server") * - Metadata API (generateMetadata / export metadata) * - Middleware (middleware.ts) * - RSC 数据获取模式 (async Server Components / fetch cache) * - Parallel Routes / Intercepting Routes */ import { EnhancementPack } from './EnhancementPack.js'; class NextjsEnhancement extends EnhancementPack { get id() { return 'nextjs'; } get displayName() { return 'Next.js 15 App Router Enhancement'; } get conditions() { return { languages: ['typescript', 'javascript'], frameworks: ['nextjs'] }; } getExtraDimensions() { return [ { id: 'nextjs-app-router-scan', label: 'App Router 结构分析', guide: 'Next.js App Router 文件约定分析 — layout.tsx/page.tsx/loading.tsx/error.tsx/not-found.tsx 分布、Route Groups、Parallel Routes (@folder)、Intercepting Routes (..)、generateStaticParams 使用', tierHint: 2, knowledgeTypes: ['architecture'], skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-nextjs-app-router', description: 'Next.js App Router file conventions, route groups and parallel routes (auto-generated by enhancement)', }, }, { id: 'nextjs-server-actions-scan', label: 'Server Actions 分析', guide: 'Server Actions 分析 — "use server" 函数分布、表单绑定 (action: any={})、useFormState/useFormStatus 配合、revalidatePath/revalidateTag 缓存策略', tierHint: 2, knowledgeTypes: ['architecture', 'code-pattern'], skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-nextjs-server-actions', description: 'Next.js Server Actions, form bindings and revalidation patterns (auto-generated by enhancement)', }, }, { id: 'nextjs-data-fetching-scan', label: '数据获取模式分析', guide: 'Next.js 数据获取模式 — async Server Component 直接 fetch/DB 查询、fetch() cache/revalidate 选项、generateStaticParams、Streaming + Suspense 加载策略', tierHint: 2, knowledgeTypes: ['code-pattern'], skillWorthy: true, dualOutput: true, skillMeta: { name: 'project-nextjs-data-fetching', description: 'Next.js data fetching — Server Component queries, fetch caching and streaming (auto-generated by enhancement)', }, }, ]; } getGuardRules() { return [ { ruleId: 'nextjs-no-client-in-server-action', category: 'correctness', dimension: 'file', severity: 'error', languages: ['typescript', 'javascript'], pattern: /['"]use server['"]\s*;?\s*\n[\s\S]*?(?:useState|useEffect|useRef|useContext)\s*\(/, message: '"use server" 文件/函数中不能使用 React Hooks — Server Actions 运行在服务端', }, { ruleId: 'nextjs-use-server-top-level', category: 'correctness', dimension: 'file', severity: 'warning', languages: ['typescript', 'javascript'], pattern: /(?:function|const)\s+\w+[\s\S]*?\{[\s\S]*?['"]use server['"]/, message: '"use server" 指令应放在文件顶部(标记整个文件为 Server Actions)或 async 函数体开头', }, { ruleId: 'nextjs-no-window-in-server', category: 'correctness', dimension: 'file', severity: 'error', languages: ['typescript', 'javascript'], pattern: /(?:^(?!.*['"]use client['"])[\s\S]*?)\b(?:window|document|localStorage|sessionStorage)\b/, message: 'Server Component 中不能访问 window/document 等浏览器 API — 添加 "use client" 或检查运行环境', }, { ruleId: 'nextjs-metadata-with-use-client', category: 'correctness', dimension: 'file', severity: 'error', languages: ['typescript', 'javascript'], pattern: /['"]use client['"][\s\S]*?export\s+(?:const\s+metadata|async\s+function\s+generateMetadata)/, message: 'metadata / generateMetadata 只能在 Server Component 中导出 — 不能与 "use client" 共存', }, { ruleId: 'nextjs-fetch-no-store', category: 'performance', dimension: 'file', severity: 'info', languages: ['typescript', 'javascript'], pattern: /fetch\s*\([^)]+\)\s*(?![\s\S]*?(?:cache|revalidate|next))/, message: 'Next.js 15 默认不缓存 fetch,明确指定 { cache: "force-cache" } 或 { next: { revalidate: N } } 以启用缓存', }, ]; } detectPatterns(astSummary) { const patterns = []; // ── App Router file conventions ── for (const m of astSummary.methods || []) { if (!m.className && m.isExported) { // Default export in page/layout files if (m.name === 'default' || /^(?:Page|Layout|Loading|Error|NotFound)$/.test(m.name)) { patterns.push({ type: 'nextjs-app-router-component', methodName: m.name, line: m.line, confidence: 0.8, }); } // generateMetadata / generateStaticParams if (m.name === 'generateMetadata' || m.name === 'generateStaticParams') { patterns.push({ type: 'nextjs-metadata-generator', methodName: m.name, line: m.line, confidence: 0.95, }); } } } // ── Server Actions (async functions with "use server") ── for (const m of astSummary.methods || []) { if (m.isAsync && !m.className) { const nameLower = m.name?.toLowerCase() || ''; if (nameLower.startsWith('create') || nameLower.startsWith('update') || nameLower.startsWith('delete') || nameLower.startsWith('submit') || nameLower.includes('action')) { patterns.push({ type: 'nextjs-server-action', methodName: m.name, line: m.line, confidence: 0.65, }); } } } // ── Route Handlers (GET/POST/PUT/DELETE/PATCH exports) ── for (const m of astSummary.methods || []) { if (m.isExported && !m.className && /^(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/.test(m.name)) { patterns.push({ type: 'nextjs-route-handler', methodName: m.name, line: m.line, confidence: 0.95, }); } } // ── Middleware ── for (const m of astSummary.methods || []) { if (m.isExported && m.name === 'middleware') { patterns.push({ type: 'nextjs-middleware', methodName: m.name, line: m.line, confidence: 0.9, }); } } // ── Next.js specific imports ── const nextImports = (astSummary.imports || []).filter((imp) => imp.includes('next/') || imp.includes('next/server') || imp.includes('next/navigation') || imp.includes('next/image') || imp.includes('next/link') || imp.includes('next/font')); if (nextImports.length > 0) { patterns.push({ type: 'nextjs-framework-usage', importCount: nextImports.length, confidence: 0.9, }); } return patterns; } } export const pack = new NextjsEnhancement();