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>
163 lines
6.1 KiB
Text
163 lines
6.1 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
import {UnusedJavascriptSummary} from '../../computed/unused-javascript-summary.js';
|
|
import {JSBundles} from '../../computed/js-bundles.js';
|
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
|
|
|
|
const UIStrings = {
|
|
/** Imperative title of a Lighthouse audit that tells the user to reduce JavaScript that is never evaluated during page load. This is displayed in a list of audit titles that Lighthouse generates. */
|
|
title: 'Reduce unused JavaScript',
|
|
/** Description of a Lighthouse audit that tells the user *why* they should reduce JavaScript that is never needed/evaluated by the browser. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
description: 'Reduce unused JavaScript and defer loading scripts until they are required to ' +
|
|
'decrease bytes consumed by network activity. [Learn how to reduce unused JavaScript](https://developer.chrome.com/docs/lighthouse/performance/unused-javascript/).',
|
|
};
|
|
|
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
|
|
const UNUSED_BYTES_IGNORE_THRESHOLD = 20 * 1024;
|
|
const UNUSED_BYTES_IGNORE_BUNDLE_SOURCE_THRESHOLD = 512;
|
|
|
|
/**
|
|
* @param {string[]} strings
|
|
*/
|
|
function commonPrefix(strings) {
|
|
if (!strings.length) {
|
|
return '';
|
|
}
|
|
|
|
const maxWord = strings.reduce((a, b) => a > b ? a : b);
|
|
let prefix = strings.reduce((a, b) => a > b ? b : a);
|
|
while (!maxWord.startsWith(prefix)) {
|
|
prefix = prefix.slice(0, -1);
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
/**
|
|
* @param {string} string
|
|
* @param {string} commonPrefix
|
|
* @return {string}
|
|
*/
|
|
function trimCommonPrefix(string, commonPrefix) {
|
|
if (!commonPrefix) return string;
|
|
return string.startsWith(commonPrefix) ? '…' + string.slice(commonPrefix.length) : string;
|
|
}
|
|
|
|
/**
|
|
* @typedef WasteData
|
|
* @property {Uint8Array} unusedByIndex
|
|
* @property {number} unusedLength
|
|
* @property {number} contentLength
|
|
*/
|
|
|
|
class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
/**
|
|
* @return {LH.Audit.Meta}
|
|
*/
|
|
static get meta() {
|
|
return {
|
|
id: 'unused-javascript',
|
|
title: str_(UIStrings.title),
|
|
description: str_(UIStrings.description),
|
|
scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
|
|
guidanceLevel: 1,
|
|
requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
|
|
'DevtoolsLog', 'Trace', 'URL', 'SourceMaps'],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {LH.Artifacts} artifacts
|
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
* @param {LH.Audit.Context} context
|
|
* @return {Promise<import('./byte-efficiency-audit.js').ByteEfficiencyProduct>}
|
|
*/
|
|
static async audit_(artifacts, networkRecords, context) {
|
|
const bundles = await JSBundles.request(artifacts, context);
|
|
const {
|
|
unusedThreshold = UNUSED_BYTES_IGNORE_THRESHOLD,
|
|
bundleSourceUnusedThreshold = UNUSED_BYTES_IGNORE_BUNDLE_SOURCE_THRESHOLD,
|
|
} = context.options || {};
|
|
|
|
/** @type {Map<string, number>} */
|
|
const compressionRatioByUrl = new Map();
|
|
|
|
const items = [];
|
|
for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
|
|
const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
|
|
if (!script) continue; // This should never happen.
|
|
|
|
const bundle = bundles.find(b => b.script.scriptId === scriptId) ?? null;
|
|
const unusedJsSummary =
|
|
await UnusedJavascriptSummary.request({scriptId, scriptCoverage, bundle}, context);
|
|
if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
|
|
|
|
const compressionRatio = estimateCompressionRatioForContent(
|
|
compressionRatioByUrl, script.url, artifacts, networkRecords);
|
|
/** @type {LH.Audit.ByteEfficiencyItem} */
|
|
const item = {
|
|
url: script.url,
|
|
totalBytes: Math.round(compressionRatio * unusedJsSummary.totalBytes),
|
|
wastedBytes: Math.round(compressionRatio * unusedJsSummary.wastedBytes),
|
|
wastedPercent: unusedJsSummary.wastedPercent,
|
|
};
|
|
|
|
if (item.wastedBytes <= unusedThreshold) continue;
|
|
items.push(item);
|
|
|
|
// If there was an error calculating the bundle sizes, we can't
|
|
// create any sub-items.
|
|
if (!bundle || 'errorMessage' in bundle.sizes) continue;
|
|
const sizes = bundle.sizes;
|
|
|
|
// Augment with bundle data.
|
|
if (unusedJsSummary.sourcesWastedBytes) {
|
|
const topUnusedSourceSizes = Object.entries(unusedJsSummary.sourcesWastedBytes)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 5)
|
|
.map(([source, unused]) => {
|
|
const total = source === '(unmapped)' ? sizes.unmappedBytes : sizes.files[source];
|
|
return {
|
|
source,
|
|
unused: Math.round(unused * compressionRatio),
|
|
total: Math.round(total * compressionRatio),
|
|
};
|
|
})
|
|
.filter(d => d.unused >= bundleSourceUnusedThreshold);
|
|
|
|
const commonSourcePrefix = commonPrefix(bundle.map.sourceURLs());
|
|
item.subItems = {
|
|
type: 'subitems',
|
|
items: topUnusedSourceSizes.map(({source, unused, total}) => {
|
|
return {
|
|
source: trimCommonPrefix(source, commonSourcePrefix),
|
|
sourceBytes: total,
|
|
sourceWastedBytes: unused,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
items,
|
|
headings: [
|
|
/* eslint-disable max-len */
|
|
{key: 'url', valueType: 'url', subItemsHeading: {key: 'source', valueType: 'code'}, label: str_(i18n.UIStrings.columnURL)},
|
|
{key: 'totalBytes', valueType: 'bytes', subItemsHeading: {key: 'sourceBytes'}, label: str_(i18n.UIStrings.columnTransferSize)},
|
|
{key: 'wastedBytes', valueType: 'bytes', subItemsHeading: {key: 'sourceWastedBytes'}, label: str_(i18n.UIStrings.columnWastedBytes)},
|
|
/* eslint-enable max-len */
|
|
],
|
|
};
|
|
}
|
|
}
|
|
|
|
export default UnusedJavaScript;
|
|
export {UIStrings};
|