UNPKG

lighthouse

Version:

Automated auditing, performance metrics, and best practices for the web.

155 lines (133 loc) 4.51 kB
/** * @license Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {makeComputedArtifact} from './computed-artifact.js'; /** * @typedef WasteData * @property {Uint8Array} unusedByIndex * @property {number} unusedLength * @property {number} contentLength */ /** * @typedef ComputeInput * @property {string} scriptId * @property {Omit<LH.Crdp.Profiler.ScriptCoverage, 'url'>} scriptCoverage * @property {LH.Artifacts.Bundle|null} bundle */ /** * @typedef Summary * @property {string} scriptId * @property {number} wastedBytes * @property {number} totalBytes * @property {number} wastedBytes * @property {number=} wastedPercent * @property {Record<string, number>=} sourcesWastedBytes Keyed by file name. Includes (unmapped) key too. */ class UnusedJavascriptSummary { /** * @param {Omit<LH.Crdp.Profiler.ScriptCoverage, 'url'>} scriptCoverage * @return {WasteData} */ static computeWaste(scriptCoverage) { let maximumEndOffset = 0; for (const func of scriptCoverage.functions) { maximumEndOffset = Math.max(maximumEndOffset, ...func.ranges.map(r => r.endOffset)); } // We only care about unused ranges of the script, so we can ignore all the nesting and safely // assume that if a range is unexecuted, all nested ranges within it will also be unexecuted. const unusedByIndex = new Uint8Array(maximumEndOffset); for (const func of scriptCoverage.functions) { for (const range of func.ranges) { if (range.count === 0) { for (let i = range.startOffset; i < range.endOffset; i++) { unusedByIndex[i] = 1; } } } } let unused = 0; for (const x of unusedByIndex) { unused += x; } return { unusedByIndex, unusedLength: unused, contentLength: maximumEndOffset, }; } /** * @param {string} scriptId * @param {WasteData} wasteData * @return {Summary} */ static createItem(scriptId, wasteData) { const wastedRatio = (wasteData.unusedLength / wasteData.contentLength) || 0; const wastedBytes = Math.round(wasteData.contentLength * wastedRatio); return { scriptId, totalBytes: wasteData.contentLength, wastedBytes, wastedPercent: 100 * wastedRatio, }; } /** * @param {WasteData} wasteData * @param {LH.Artifacts.Bundle} bundle */ static createSourceWastedBytes(wasteData, bundle) { if (!bundle.script.content) return; /** @type {Record<string, number>} */ const files = {}; const lineLengths = bundle.script.content.split('\n').map(l => l.length); let totalSoFar = 0; const lineOffsets = lineLengths.map(len => { const retVal = totalSoFar; totalSoFar += len + 1; return retVal; }); // @ts-expect-error: We will upstream computeLastGeneratedColumns to CDT eventually. bundle.map.computeLastGeneratedColumns(); for (const mapping of bundle.map.mappings()) { let offset = lineOffsets[mapping.lineNumber]; offset += mapping.columnNumber; const lastColumnOfMapping = mapping.lastColumnNumber !== undefined ? mapping.lastColumnNumber - 1 : lineLengths[mapping.lineNumber]; for (let i = mapping.columnNumber; i <= lastColumnOfMapping; i++) { if (wasteData.unusedByIndex[offset] === 1) { const key = mapping.sourceURL || '(unmapped)'; files[key] = (files[key] || 0) + 1; } offset += 1; } } const dataSorted = Object.entries(files) .sort(([_, unusedBytes1], [__, unusedBytes2]) => unusedBytes2 - unusedBytes1); /** @type {Record<string, number>} */ const bundleData = {}; for (const [key, unusedBytes] of dataSorted) { bundleData[key] = unusedBytes; } return bundleData; } /** * @param {ComputeInput} data * @return {Promise<Summary>} */ static async compute_(data) { const {scriptId, scriptCoverage, bundle} = data; const wasteData = UnusedJavascriptSummary.computeWaste(scriptCoverage); const item = UnusedJavascriptSummary.createItem(scriptId, wasteData); if (!bundle) return item; return { ...item, sourcesWastedBytes: UnusedJavascriptSummary.createSourceWastedBytes(wasteData, bundle), }; } } const UnusedJavascriptSummaryComputed = makeComputedArtifact( UnusedJavascriptSummary, ['bundle', 'scriptCoverage', 'scriptId'] ); export {UnusedJavascriptSummaryComputed as UnusedJavascriptSummary};