UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

262 lines 12.5 kB
import { BaseToolHandler } from '../base-handler.js'; import { formatBundleSize, formatNoBundlesMessage } from './utils/performance-formatters.js'; /** * Handler for Next.js performance analysis tools (data fetching, bundles, components) */ export class NextJSPerformanceHandler extends BaseToolHandler { nextjsEngine; constructor(nextjsEngine) { super(); this.nextjsEngine = nextjsEngine; } tools = [ { name: 'nextjs_data_fetching', description: 'Analyze Next.js data fetching patterns including cache status, waterfalls, and optimization opportunities.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } }, { name: 'nextjs_bundle_analyze', description: 'Analyze Next.js bundle sizes, code splitting effectiveness, and identify unused dependencies.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } }, { name: 'nextjs_server_components', description: 'Analyze Server/Client component boundaries, data flow, and provide optimization recommendations.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } } ]; async handle(toolName, args, sessions) { const session = this.validateSession(args.sessionId, sessions); switch (toolName) { case 'nextjs_data_fetching': return this.analyzeDataFetching(args, session); case 'nextjs_bundle_analyze': return this.analyzeBundles(args, session); case 'nextjs_server_components': return this.analyzeServerComponents(args, session); default: throw new Error(`Unknown Next.js performance tool: ${toolName}`); } } validateSession(sessionId, sessions) { const session = sessions.get(sessionId); if (!session) { throw new Error(`Debug session ${sessionId} not found`); } if (session.framework !== 'nextjs' && session.framework !== 'next' && !(session.framework === 'react' && session.isNextJS)) { throw new Error('This tool only works with Next.js applications'); } return session; } async analyzeDataFetching(args, session) { try { const analysis = await this.nextjsEngine.getDataFetchingAnalysis(); let content = '📊 **Next.js Data Fetching Analysis**\n\n'; if (analysis.length === 0) { content += 'No data fetching detected on this page.\n'; content += 'This might be a static page or all data is client-side.\n'; return this.createTextResponse(content); } // Analyze each data fetching pattern analysis.forEach((pattern, index) => { content += `### Fetch ${index + 1}: ${pattern.method}\n`; content += `- **Cache Status:** ${pattern.cacheStatus}\n`; if (pattern.revalidateTime) { content += `- **Revalidate:** ${pattern.revalidateTime}s\n`; } if (pattern.waterfalls && pattern.waterfalls.length > 0) { content += `- **Waterfalls Detected:**\n`; pattern.waterfalls.forEach(waterfall => { content += ` - ${waterfall.component} (${waterfall.fetchTime})`; if (waterfall.blocking) { content += ' ⚠️ Blocking'; } content += '\n'; }); } content += '\n'; }); // Overall recommendations content += '### 💡 Recommendations:\n'; const hasWaterfalls = analysis.some(p => p.waterfalls && p.waterfalls.some(w => w.blocking)); if (hasWaterfalls) { content += '- ⚠️ Sequential data fetching detected\n'; content += ' - Use Promise.all() to parallelize independent fetches\n'; content += ' - Consider React Suspense for better loading states\n'; } return this.createTextResponse(content); } catch (error) { throw new Error(`Failed to analyze data fetching: ${error instanceof Error ? error.message : String(error)}`); } } async analyzeBundles(args, session) { try { const result = await this.nextjsEngine.analyzeBundle(); const { totalSize, gzippedSize, chunks, recommendations } = result; // Check if no bundles detected (dev mode) if (totalSize === 0 || !chunks || chunks.length === 0) { return { content: [{ type: 'text', text: formatNoBundlesMessage() }] }; } let content = '📦 **Next.js Bundle Analysis**\n\n'; // Total size content += `**Total Bundle Size:** ${formatBundleSize(totalSize)}\n`; content += `**Gzipped Size:** ${formatBundleSize(gzippedSize)}\n\n`; // Chunks analysis if (chunks && chunks.length > 0) { content += '**Chunks by Type:**\n'; const initialChunks = chunks.filter(chunk => chunk.type === 'initial'); const asyncChunks = chunks.filter(chunk => chunk.type === 'async'); const runtimeChunks = chunks.filter(chunk => chunk.type === 'runtime'); if (initialChunks.length > 0) { content += `\n**Initial Chunks (${initialChunks.length}):**\n`; initialChunks .sort((a, b) => b.size - a.size) .slice(0, 5) .forEach(chunk => { const sizeKB = (chunk.size / 1024).toFixed(0); const indicator = chunk.size > 200 * 1024 ? '⚠️' : '✅'; content += `- ${indicator} **${chunk.name}**: ${sizeKB}KB\n`; }); } if (asyncChunks.length > 0) { content += `\n**Async Chunks (${asyncChunks.length}):**\n`; asyncChunks .sort((a, b) => b.size - a.size) .slice(0, 3) .forEach(chunk => { const sizeKB = (chunk.size / 1024).toFixed(0); content += `- **${chunk.name}**: ${sizeKB}KB\n`; }); } if (runtimeChunks.length > 0) { content += `\n**Runtime Chunks (${runtimeChunks.length}):**\n`; runtimeChunks.forEach(chunk => { const sizeKB = (chunk.size / 1024).toFixed(0); content += `- **${chunk.name}**: ${sizeKB}KB\n`; }); } content += '\n'; } // Recommendations if (recommendations && recommendations.length > 0) { content += '**💡 Recommendations:**\n'; recommendations.forEach((rec) => { content += `- ${rec}\n`; }); } // Additional static recommendations content += '\n**📚 Common Dependencies & Alternatives:**\n'; content += '- **moment** (290KB) → **date-fns** (89KB) or **dayjs** (7KB)\n'; content += '- **lodash** (71KB) → **lodash-es** with tree-shaking\n'; content += '- **react-icons** → Import only needed icons\n'; return this.createTextResponse(content); } catch (error) { throw new Error(`Failed to analyze Next.js bundles: ${error instanceof Error ? error.message : String(error)}`); } } async analyzeServerComponents(args, session) { try { const analysis = await this.nextjsEngine.analyzeServerClientFlow(); let content = '🖥️ **Next.js Server Components Analysis**\n\n'; // Summary stats content += `**Component Distribution:**\n`; content += `- Server Components: ${analysis.serverComponents}\n`; content += `- Client Components: ${analysis.clientComponents}\n`; content += `- Total Components: ${analysis.serverComponents + analysis.clientComponents}\n\n`; // Bundle splitting information if (analysis.bundleSplitting) { content += '**Bundle Splitting:**\n'; content += `- Server Chunks: ${analysis.bundleSplitting.serverChunks.length}\n`; content += `- Client Chunks: ${analysis.bundleSplitting.clientChunks.length}\n`; content += `- Shared Chunks: ${analysis.bundleSplitting.sharedChunks.length}\n\n`; if (analysis.bundleSplitting.sharedChunks.length > 0) { content += '**Shared Chunks:**\n'; analysis.bundleSplitting.sharedChunks.forEach(chunk => { content += `- ${chunk}\n`; }); content += '\n'; } } // Hydration mismatches if (analysis.hydrationMismatches && analysis.hydrationMismatches.length > 0) { content += '**⚠️ Hydration Mismatches:**\n'; analysis.hydrationMismatches.forEach(mismatch => { content += `- ${mismatch}\n`; }); content += '\n'; } // Boundaries analysis if (analysis.boundaries && analysis.boundaries.length > 0) { content += '**🔍 Component Boundaries:**\n'; analysis.boundaries.forEach((boundary) => { content += `- ${boundary.parent}${boundary.child}: ${boundary.issue}\n`; }); content += '\n'; } // Optimizations if (analysis.optimizations && analysis.optimizations.length > 0) { content += '**🚀 Optimization Opportunities:**\n'; analysis.optimizations.forEach((opt) => { content += `- ${opt.component}: ${opt.suggestion} (${opt.impact})\n`; }); content += '\n'; } // Performance metrics if (analysis.performance) { content += '**📊 Performance Metrics:**\n'; content += `- Server Render Time: ${analysis.performance.serverRenderTime}\n`; content += `- Client Hydration Time: ${analysis.performance.clientHydrationTime || 'N/A'}\n`; content += `- Total Time: ${analysis.performance.totalTime || 'N/A'}\n\n`; } // Recommendations content += '**💡 Recommendations:**\n'; if (analysis.clientComponents > analysis.serverComponents * 2) { content += '- Consider converting more components to Server Components\n'; content += '- Move data fetching to Server Components\n'; } content += '- Keep client components small and focused\n'; content += '- Use Server Components for data-heavy operations\n'; content += '- Minimize props passed from Server to Client Components\n'; return this.createTextResponse(content); } catch (error) { throw new Error(`Failed to analyze server components: ${error instanceof Error ? error.message : String(error)}`); } } } //# sourceMappingURL=nextjs-performance-handler.js.map