UNPKG

dev-lamp

Version:

Your friendly lighthouse performance companion - 100% local

215 lines 8.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LighthouseRunner = void 0; const lighthouse_1 = __importDefault(require("lighthouse")); const chrome_manager_1 = require("./chrome-manager"); class LighthouseRunner { chromeManager = chrome_manager_1.ChromeManager.getInstance(); MAX_WAIT_FOR_LOAD = 90000; // 90 seconds MAX_GATHER_TIME = 180000; // 180 seconds (3 minutes) async analyze(url, options = {}) { const chrome = await this.chromeManager.getChrome(options.chrome || {}); try { const categories = this.parseCategories(options.categories); const lighthouseOptions = { port: chrome.port, output: 'json', onlyCategories: categories, formFactor: options.device || 'desktop', screenEmulation: this.getScreenEmulation(options.device || 'desktop'), throttling: this.getThrottling(options.device || 'desktop'), maxWaitForLoad: this.MAX_WAIT_FOR_LOAD, maxWaitForFcp: this.MAX_WAIT_FOR_LOAD, gatherMode: false, disableStorageReset: false, skipAboutBlank: true }; // Add timeout wrapper const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Lighthouse audit timed out after 180 seconds')), this.MAX_GATHER_TIME); }); const lighthousePromise = (0, lighthouse_1.default)(url, lighthouseOptions); const runnerResult = await Promise.race([lighthousePromise, timeoutPromise]); if (!runnerResult?.lhr) { throw new Error('Failed to generate Lighthouse report'); } return this.parseReport(runnerResult.lhr); } finally { if (!options.keepAlive) { await this.chromeManager.cleanup(); } } } parseCategories(categoriesString) { if (!categoriesString) { return ['performance']; } const categories = categoriesString.split(',').map(c => c.trim()); const validCategories = ['performance', 'accessibility', 'best-practices', 'seo', 'pwa']; return categories.filter(c => validCategories.includes(c)); } getScreenEmulation(device) { if (device === 'mobile') { return { mobile: true, width: 412, height: 823, deviceScaleFactor: 2.625, disabled: false }; } return { mobile: false, width: 1350, height: 940, deviceScaleFactor: 1, disabled: false }; } getThrottling(device) { if (device === 'mobile') { return { cpuSlowdownMultiplier: 4, requestLatencyMs: 150, downloadThroughputKbps: 1638.4, uploadThroughputKbps: 675 }; } return { cpuSlowdownMultiplier: 1, requestLatencyMs: 0, downloadThroughputKbps: 0, uploadThroughputKbps: 0 }; } parseReport(lhr) { return { metadata: { url: lhr.finalUrl || lhr.requestedUrl, timestamp: new Date().toISOString(), device: lhr.configSettings?.formFactor, lighthouseVersion: lhr.lighthouseVersion }, scores: this.extractScores(lhr.categories), metrics: this.extractMetrics(lhr.audits), opportunities: this.extractOpportunities(lhr.audits), diagnostics: this.extractDiagnostics(lhr.audits) }; } extractScores(categories) { const scores = {}; if (categories?.performance) { scores.performance = Math.round(categories.performance.score * 100); } if (categories?.accessibility) { scores.accessibility = Math.round(categories.accessibility.score * 100); } if (categories?.['best-practices']) { scores.bestPractices = Math.round(categories['best-practices'].score * 100); } if (categories?.seo) { scores.seo = Math.round(categories.seo.score * 100); } if (categories?.pwa) { scores.pwa = Math.round(categories.pwa.score * 100); } return scores; } extractMetrics(audits) { const metrics = {}; const metricMappings = { 'largest-contentful-paint': 'lcp', 'first-contentful-paint': 'fcp', 'cumulative-layout-shift': 'cls', 'total-blocking-time': 'totalBlockingTime', 'speed-index': 'speedIndex', 'interactive': 'tti', 'server-response-time': 'ttfb' }; for (const [auditKey, metricKey] of Object.entries(metricMappings)) { if (audits[auditKey]) { const audit = audits[auditKey]; metrics[metricKey] = { score: Math.round((audit.score || 0) * 100), value: audit.numericValue || 0, displayValue: audit.displayValue || 'N/A', title: audit.title, description: audit.description }; } } // Add FID placeholder (Lighthouse doesn't measure real FID, uses TBT as proxy) if (metrics.totalBlockingTime) { metrics.fid = { ...metrics.totalBlockingTime, title: 'First Input Delay (estimated)', description: 'Estimated using Total Blocking Time as a proxy' }; } return metrics; } extractOpportunities(audits) { const opportunities = []; const opportunityAudits = [ 'render-blocking-resources', 'unused-css-rules', 'unused-javascript', 'modern-image-formats', 'uses-optimized-images', 'uses-text-compression', 'uses-responsive-images', 'efficient-animated-content', 'duplicated-javascript', 'legacy-javascript' ]; for (const auditKey of opportunityAudits) { if (audits[auditKey] && audits[auditKey].score !== null && audits[auditKey].score < 0.9) { const audit = audits[auditKey]; opportunities.push({ id: auditKey, title: audit.title, description: audit.description, score: Math.round((audit.score || 0) * 100), displayValue: audit.displayValue, details: audit.details }); } } return opportunities.sort((a, b) => a.score - b.score); } extractDiagnostics(audits) { const diagnostics = []; const diagnosticAudits = [ 'font-display', 'tap-targets', 'document-title', 'html-has-lang', 'meta-description', 'http-status-code', 'link-text', 'crawlable-anchors', 'is-crawlable', 'robots-txt' ]; for (const auditKey of diagnosticAudits) { if (audits[auditKey] && audits[auditKey].score !== null && audits[auditKey].score < 1) { const audit = audits[auditKey]; diagnostics.push({ id: auditKey, title: audit.title, description: audit.description, score: Math.round((audit.score || 0) * 100), displayValue: audit.displayValue, details: audit.details }); } } return diagnostics; } } exports.LighthouseRunner = LighthouseRunner; //# sourceMappingURL=lighthouse-runner.js.map