Metatron_Admin_Backend/controllers/lighthouseController.js
2025-10-09 10:10:50 +05:30

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);
}
};