pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
189 lines • 17.1 kB
JavaScript
import fs from 'fs/promises';
import Path from 'path';
/**
* Performance metrics to extract from Lighthouse report
* Weights are based on Lighthouse v10+ scoring
*/
const PERFORMANCE_METRICS = [
{ id: 'largest-contentful-paint', abbr: 'LCP', unit: 'ms', weight: 0.25 },
{ id: 'cumulative-layout-shift', abbr: 'CLS', unit: '', weight: 0.25 },
{ id: 'total-blocking-time', abbr: 'TBT', unit: 'ms', weight: 0.3 },
{ id: 'first-contentful-paint', abbr: 'FCP', unit: 'ms', weight: 0.1 },
{ id: 'speed-index', abbr: 'SI', unit: 'ms', weight: 0.1 },
];
/**
* Maps MIME type to resource type category
*/
function getResourceType(mime) {
if (!mime)
return 'other';
if (mime.includes('html'))
return 'document';
if (mime.includes('css'))
return 'stylesheet';
// cspell:ignore ecmascript
if (mime.includes('javascript') || mime.includes('ecmascript'))
return 'script';
if (mime.startsWith('image/'))
return 'image';
if (mime.includes('font') || mime.includes('woff') || mime.includes('ttf') || mime.includes('otf'))
return 'font';
return 'other';
}
/**
* Formats bytes to human-readable string (KB, MB, etc.)
*/
function formatBytes(bytes) {
if (bytes < 1024)
return `${bytes} B`;
if (bytes < 1024 * 1024)
return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}
/**
* Calculates traffic statistics by resource type
*/
async function calculateResourceStats(resources, inventoryDir) {
const statsMap = new Map();
// Initialize all resource types
const types = ['document', 'stylesheet', 'script', 'image', 'font', 'other'];
for (const type of types) {
statsMap.set(type, { type, count: 0, trafficSize: 0, resourceSize: 0 });
}
for (const resource of resources) {
if (!resource.contentFilePath)
continue;
const type = getResourceType(resource.contentTypeMime);
const stat = statsMap.get(type);
if (!stat)
continue;
try {
const filePath = Path.join(inventoryDir, resource.contentFilePath);
const fileStat = await fs.stat(filePath);
const resourceSize = fileStat.size;
// Traffic size: if content-encoding exists, estimate compressed size
// Otherwise use resource size
// Note: The file is stored decompressed, so we estimate traffic size
// based on typical compression ratios for each type
let trafficSize = resourceSize;
if (resource.contentEncoding) {
// For compressed resources, estimate traffic size based on typical ratios
// These are rough estimates - actual values would require re-compression
const compressionRatios = {
document: 0.2,
stylesheet: 0.15,
script: 0.25,
image: 0.95, // Images are usually already compressed
font: 0.7,
other: 0.5,
};
trafficSize = Math.round(resourceSize * compressionRatios[type]);
}
stat.count++;
stat.trafficSize += trafficSize;
stat.resourceSize += resourceSize;
}
catch {
// File not found or other error - skip
}
}
// Filter out types with no resources and sort by fixed order
const typeOrder = ['document', 'stylesheet', 'script', 'image', 'font', 'other'];
const stats = Array.from(statsMap.values())
.filter((s) => s.count > 0)
.sort((a, b) => typeOrder.indexOf(a.type) - typeOrder.indexOf(b.type));
const totals = stats.reduce((acc, s) => ({
trafficSize: acc.trafficSize + s.trafficSize,
resourceSize: acc.resourceSize + s.resourceSize,
}), { trafficSize: 0, resourceSize: 0 });
return { stats, totals };
}
/**
* Formats a score as a percentage integer
*/
function formatScore(score) {
if (score === null)
return 'N/A';
return `${Math.round(score * 100)}`;
}
/**
* Formats a numeric value in milliseconds (rounded to integer)
* For CLS, returns the raw value with 3 decimal places
*/
function formatValue(numericValue, unit) {
if (numericValue === undefined)
return 'N/A';
if (unit === '') {
// CLS - no unit, show decimal value
return numericValue.toFixed(3);
}
// Time-based metrics - round to integer ms
return `${Math.round(numericValue)} ${unit}`;
}
/**
* Generates a Markdown summary from a Lighthouse JSON report
*/
export async function generateLighthouseSummary(opts, dependency) {
// Read and parse the Lighthouse JSON report
const jsonContent = await fs.readFile(opts.jsonPath, 'utf-8');
const report = JSON.parse(jsonContent);
// Extract overall performance score
const overallScore = report.categories?.performance?.score;
// Build the Markdown content
const lines = [];
lines.push('# Lighthouse Performance Summary');
lines.push('');
// Section 1: Metric Values
lines.push('## Metrics');
lines.push('');
for (const metric of PERFORMANCE_METRICS) {
const audit = report.audits[metric.id];
if (audit) {
const value = formatValue(audit.numericValue, metric.unit);
lines.push(`- ${metric.abbr} ${value}`);
}
}
lines.push('');
// Section 2: Scores
lines.push('## Scores');
lines.push('');
for (const metric of PERFORMANCE_METRICS) {
const audit = report.audits[metric.id];
if (audit) {
const score = formatScore(audit.score);
const weight = `x${metric.weight.toFixed(2)}`;
lines.push(`- ${metric.abbr} ${score} ${weight}`);
}
}
lines.push(`- **Overall** ${formatScore(overallScore)}`);
lines.push('');
// Section 3: Resource Traffic (if inventory data is provided)
if (opts.resources && opts.inventoryDir) {
const { stats, totals } = await calculateResourceStats(opts.resources, opts.inventoryDir);
if (stats.length > 0) {
// Traffic section (compressed/transferred size)
lines.push('## Traffic: Type / Count / Size / Bytes');
lines.push('');
for (const stat of stats) {
lines.push(`- ${stat.type} / ${stat.count} / ${formatBytes(stat.trafficSize)} / ${stat.trafficSize}`);
}
lines.push(`- **total** / ${stats.reduce((sum, s) => sum + s.count, 0)} / ${formatBytes(totals.trafficSize)} / ${totals.trafficSize}`);
lines.push('');
// Resource section (decompressed/original size)
lines.push('## Resource: Type / Count / Size / Bytes');
lines.push('');
for (const stat of stats) {
lines.push(`- ${stat.type} / ${stat.count} / ${formatBytes(stat.resourceSize)} / ${stat.resourceSize}`);
}
lines.push(`- **total** / ${stats.reduce((sum, s) => sum + s.count, 0)} / ${formatBytes(totals.resourceSize)} / ${totals.resourceSize}`);
lines.push('');
}
}
// Ensure output directory exists
const outputDir = Path.dirname(opts.outputPath);
await dependency.mkdirp(outputDir);
// Write the summary file
await fs.writeFile(opts.outputPath, lines.join('\n'), 'utf-8');
dependency.logger?.info(`Lighthouse summary saved to ${opts.outputPath}`);
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlnaHRob3VzZS1zdW1tYXJ5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2xpZ2h0aG91c2Utc3VtbWFyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDNUIsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFBO0FBOEJ2Qjs7O0dBR0c7QUFDSCxNQUFNLG1CQUFtQixHQUFHO0lBQzFCLEVBQUUsRUFBRSxFQUFFLDBCQUEwQixFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO0lBQ3pFLEVBQUUsRUFBRSxFQUFFLHlCQUF5QixFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO0lBQ3RFLEVBQUUsRUFBRSxFQUFFLHFCQUFxQixFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFO0lBQ25FLEVBQUUsRUFBRSxFQUFFLHdCQUF3QixFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFO0lBQ3RFLEVBQUUsRUFBRSxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRTtDQUMzRCxDQUFBO0FBd0JEOztHQUVHO0FBQ0gsU0FBUyxlQUFlLENBQUMsSUFBd0I7SUFDL0MsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLE9BQU8sQ0FBQTtJQUN6QixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDO1FBQUUsT0FBTyxVQUFVLENBQUE7SUFDNUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sWUFBWSxDQUFBO0lB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