114 lines
4.1 KiB
JavaScript
114 lines
4.1 KiB
JavaScript
import lighthouse from 'lighthouse';
|
|
import { launch } from 'chrome-launcher';
|
|
import PageSpeedTest from '../models/pageSpeedTest.model.js';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
|
|
const reportsDir = path.join(process.cwd(), 'public', 'lighthouse-treemap');
|
|
// Ensure folder exists
|
|
if (!fs.existsSync(reportsDir)) fs.mkdirSync(reportsDir, { recursive: true });
|
|
|
|
const launchChromeAndRunLighthouse = async (url, device = 'mobile') => {
|
|
const chrome = await launch({ chromeFlags: ['--headless'] });
|
|
|
|
const options = {
|
|
port: chrome.port,
|
|
emulatedFormFactor: device,
|
|
throttlingMethod: device === 'mobile' ? 'simulate' : 'devtools',
|
|
output: 'json', // JSON for metrics
|
|
};
|
|
|
|
const runnerResult = await lighthouse(url, options);
|
|
const lhr = runnerResult.lhr;
|
|
|
|
// Create HTML treemap report (only once, for mobile)
|
|
let treemapFile = null;
|
|
if (device === 'mobile') {
|
|
const fileName = `treemap-${Date.now()}.html`;
|
|
treemapFile = `/lighthouse-treemap/${fileName}`;
|
|
|
|
// Generate HTML report
|
|
const htmlReport = await lighthouse(url, {
|
|
port: chrome.port,
|
|
emulatedFormFactor: device,
|
|
throttlingMethod: 'simulate',
|
|
output: 'html',
|
|
});
|
|
|
|
fs.writeFileSync(path.join(reportsDir, fileName), htmlReport.report);
|
|
}
|
|
|
|
await chrome.kill();
|
|
|
|
// Structured result
|
|
const result = {
|
|
url,
|
|
device,
|
|
scores: {
|
|
performance: Math.round(lhr.categories.performance?.score * 100),
|
|
accessibility: Math.round(lhr.categories.accessibility?.score * 100),
|
|
bestPractices: Math.round(lhr.categories['best-practices']?.score * 100),
|
|
seo: Math.round(lhr.categories.seo?.score * 100),
|
|
pwa: lhr.categories.pwa?.score ? Math.round(lhr.categories.pwa.score * 100) : null,
|
|
},
|
|
metrics: {
|
|
firstContentfulPaint: lhr.audits['first-contentful-paint']?.displayValue || null,
|
|
largestContentfulPaint: lhr.audits['largest-contentful-paint']?.displayValue || null,
|
|
totalBlockingTime: lhr.audits['total-blocking-time']?.displayValue || null,
|
|
timeToInteractive: lhr.audits['interactive']?.displayValue || null,
|
|
speedIndex: lhr.audits['speed-index']?.displayValue || null,
|
|
cumulativeLayoutShift: lhr.audits['cumulative-layout-shift']?.displayValue || null,
|
|
},
|
|
opportunities: Object.values(lhr.audits)
|
|
.filter(a => a.details?.type === 'opportunity')
|
|
.map(a => ({
|
|
title: a.title,
|
|
description: a.description,
|
|
estimatedSavings: a.details?.overallSavingsMs
|
|
? `${Math.round(a.details.overallSavingsMs)} ms`
|
|
: null,
|
|
})),
|
|
diagnostics: {
|
|
usesHTTPS: lhr.audits['is-on-https']?.score === 1,
|
|
usesEfficientCachePolicy: lhr.audits['uses-long-cache-ttl']?.score === 1,
|
|
imageCompression: lhr.audits['uses-optimized-images']?.score === 1,
|
|
},
|
|
failedAudits: Object.values(lhr.audits)
|
|
.filter(a => a.score !== null && a.score !== 1 && a.scoreDisplayMode !== 'notApplicable')
|
|
.map(a => ({ title: a.title, description: a.description })),
|
|
passedAudits: Object.values(lhr.audits)
|
|
.filter(a => a.score === 1 && a.scoreDisplayMode !== 'notApplicable' && !a.details?.type)
|
|
.map(a => a.title),
|
|
notApplicableAudits: Object.values(lhr.audits)
|
|
.filter(a => a.scoreDisplayMode === 'notApplicable')
|
|
.map(a => a.title),
|
|
screenshot: lhr.audits['final-screenshot']?.details?.data || null,
|
|
createdAt: new Date(),
|
|
treemapPath: treemapFile,
|
|
};
|
|
|
|
const report = await PageSpeedTest.create(result);
|
|
return { report };
|
|
};
|
|
|
|
export const runAudit = async (req, res, next) => {
|
|
try {
|
|
const { url } = req.body;
|
|
if (!url) return res.status(400).json({ message: 'URL is required' });
|
|
|
|
const mobileResult = await launchChromeAndRunLighthouse(url, 'mobile');
|
|
const desktopResult = await launchChromeAndRunLighthouse(url, 'desktop');
|
|
|
|
res.status(200).json({
|
|
message: 'Audit completed successfully',
|
|
results: {
|
|
mobile: mobileResult.report,
|
|
desktop: desktopResult.report,
|
|
treemap: mobileResult.report.treemapPath, // HTML report
|
|
},
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|