dev-lamp
Version:
Your friendly lighthouse performance companion - 100% local
215 lines • 8.2 kB
JavaScript
;
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