Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
181 lines
6.7 KiB
Text
181 lines
6.7 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2020 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import log from 'lighthouse-logger';
|
|
|
|
import {Audit} from './audit.js';
|
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
import {LargestContentfulPaint as LargestContentfulPaintComputed} from '../computed/metrics/largest-contentful-paint.js';
|
|
import LargestContentfulPaint from './metrics/largest-contentful-paint.js';
|
|
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
import {Sentry} from '../lib/sentry.js';
|
|
|
|
const UIStrings = {
|
|
/** Descriptive title of a diagnostic audit that provides the element that was determined to be the Largest Contentful Paint. */
|
|
title: 'Largest Contentful Paint element',
|
|
/** Description of a Lighthouse audit that tells the user that the element shown was determined to be the Largest Contentful Paint. */
|
|
description: 'This is the largest contentful element painted within the viewport. ' +
|
|
'[Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)',
|
|
/** Label for a column in a data table; entries will be the name of a phase in the Largest Contentful Paint (LCP) metric. */
|
|
columnPhase: 'Phase',
|
|
/** Label for a column in a data table; entries will be the percent of Largest Contentful Paint (LCP) that a phase covers. */
|
|
columnPercentOfLCP: '% of LCP',
|
|
/** Label for a column in a data table; entries will be the amount of time spent in a phase in the Largest Contentful Paint (LCP) metric. */
|
|
columnTiming: 'Timing',
|
|
/** Table item value for the Time To First Byte (TTFB) phase of the Largest Contentful Paint (LCP) metric. */
|
|
itemTTFB: 'TTFB',
|
|
/** Table item value for the load delay phase of the Largest Contentful Paint (LCP) metric. */
|
|
itemLoadDelay: 'Load Delay',
|
|
/** Table item value for the load time phase of the Largest Contentful Paint (LCP) metric. */
|
|
itemLoadTime: 'Load Time',
|
|
/** Table item value for the render delay phase of the Largest Contentful Paint (LCP) metric. */
|
|
itemRenderDelay: 'Render Delay',
|
|
};
|
|
|
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
|
|
class LargestContentfulPaintElement extends Audit {
|
|
/**
|
|
* @return {LH.Audit.Meta}
|
|
*/
|
|
static get meta() {
|
|
return {
|
|
id: 'largest-contentful-paint-element',
|
|
title: str_(UIStrings.title),
|
|
description: str_(UIStrings.description),
|
|
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
guidanceLevel: 1,
|
|
supportedModes: ['navigation'],
|
|
requiredArtifacts:
|
|
['Trace', 'TraceElements', 'DevtoolsLog', 'GatherContext', 'settings', 'URL',
|
|
'SourceMaps'],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {LH.Artifacts} artifacts
|
|
* @return {LH.Audit.Details.Table|undefined}
|
|
*/
|
|
static makeElementTable(artifacts) {
|
|
const lcpElement = artifacts.TraceElements
|
|
.find(element => element.traceEventType === 'largest-contentful-paint');
|
|
if (!lcpElement) return;
|
|
|
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
const headings = [
|
|
{key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
|
|
];
|
|
|
|
const lcpElementDetails = [{node: Audit.makeNodeItem(lcpElement.node)}];
|
|
|
|
return Audit.makeTableDetails(headings, lcpElementDetails);
|
|
}
|
|
|
|
/**
|
|
* @param {number} metricLcp
|
|
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
* @param {LH.Audit.Context} context
|
|
* @return {Promise<LH.Audit.Details.Table>}
|
|
*/
|
|
static async makePhaseTable(metricLcp, metricComputationData, context) {
|
|
const {ttfb, loadStart, loadEnd} = await LCPBreakdown.request(metricComputationData, context);
|
|
|
|
let loadDelay = 0;
|
|
let loadTime = 0;
|
|
let renderDelay = metricLcp - ttfb;
|
|
|
|
if (loadStart && loadEnd) {
|
|
loadDelay = loadStart - ttfb;
|
|
loadTime = loadEnd - loadStart;
|
|
renderDelay = metricLcp - loadEnd;
|
|
}
|
|
|
|
const results = [
|
|
{phase: str_(UIStrings.itemTTFB), timing: ttfb},
|
|
{phase: str_(UIStrings.itemLoadDelay), timing: loadDelay},
|
|
{phase: str_(UIStrings.itemLoadTime), timing: loadTime},
|
|
{phase: str_(UIStrings.itemRenderDelay), timing: renderDelay},
|
|
].map(result => {
|
|
const percent = 100 * result.timing / metricLcp;
|
|
const percentStr = `${percent.toFixed(0)}%`;
|
|
return {...result, percent: percentStr};
|
|
});
|
|
|
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
const headings = [
|
|
{key: 'phase', valueType: 'text', label: str_(UIStrings.columnPhase)},
|
|
{key: 'percent', valueType: 'text', label: str_(UIStrings.columnPercentOfLCP)},
|
|
{key: 'timing', valueType: 'ms', label: str_(UIStrings.columnTiming)},
|
|
];
|
|
|
|
return Audit.makeTableDetails(headings, results);
|
|
}
|
|
|
|
/**
|
|
* @param {LH.Artifacts} artifacts
|
|
* @param {LH.Audit.Context} context
|
|
* @return {Promise<LH.Audit.Product>}
|
|
*/
|
|
static async audit(artifacts, context) {
|
|
const trace = artifacts.Trace;
|
|
const devtoolsLog = artifacts.DevtoolsLog;
|
|
const gatherContext = artifacts.GatherContext;
|
|
const metricComputationData = {
|
|
trace, devtoolsLog, gatherContext,
|
|
settings: context.settings, URL: artifacts.URL,
|
|
SourceMaps: artifacts.SourceMaps, simulator: null,
|
|
};
|
|
|
|
const elementTable = this.makeElementTable(artifacts);
|
|
if (!elementTable) {
|
|
return {
|
|
score: null,
|
|
notApplicable: true,
|
|
metricSavings: {LCP: 0},
|
|
};
|
|
}
|
|
|
|
const items = [elementTable];
|
|
let displayValue;
|
|
let metricLcp = 0;
|
|
|
|
try {
|
|
const lcpResult =
|
|
await LargestContentfulPaintComputed.request(metricComputationData, context);
|
|
metricLcp = lcpResult.timing;
|
|
displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
|
|
|
|
const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
|
|
items.push(phaseTable);
|
|
} catch (err) {
|
|
Sentry.captureException(err, {
|
|
tags: {audit: this.meta.id},
|
|
level: 'error',
|
|
});
|
|
log.error(this.meta.id, err.message);
|
|
}
|
|
|
|
const details = Audit.makeListDetails(items);
|
|
|
|
// Conceptually, this doesn't make much sense as "savings" for this audit since there isn't anything to "fix".
|
|
// However, this audit will always be useful when improving LCP and that should be reflected in our impact calculations.
|
|
const idealLcp = LargestContentfulPaint.defaultOptions[context.settings.formFactor].scoring.p10;
|
|
const lcpSavings = Math.max(0, metricLcp - idealLcp);
|
|
|
|
return {
|
|
score: lcpSavings ? 0 : 1,
|
|
scoreDisplayMode: lcpSavings ? undefined : Audit.SCORING_MODES.INFORMATIVE,
|
|
displayValue,
|
|
details,
|
|
metricSavings: {
|
|
LCP: lcpSavings,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export default LargestContentfulPaintElement;
|
|
export {UIStrings};
|