UNPKG

lighthouse

Version:

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

168 lines (155 loc) 7.02 kB
/** * @license * Copyright 2022 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {NetworkRequest} from './network-request.js'; /** * @param {LH.Artifacts.Script} script * @return {boolean} */ function isInline(script) { return Boolean(script.startLine || script.startColumn); } /** * @param {LH.Artifacts.NetworkRequest[]} networkRecords * @param {LH.Artifacts.Script} script * @return {LH.Artifacts.NetworkRequest|undefined} */ function getRequestForScript(networkRecords, script) { let networkRequest = networkRecords.find(request => request.url === script.url); while (networkRequest?.redirectDestination) { networkRequest = networkRequest.redirectDestination; } return networkRequest; } /** * Estimates the number of bytes this network record would have consumed on the network based on the * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable. * * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord * @param {number} totalBytes Uncompressed size of the resource * @param {LH.Crdp.Network.ResourceType=} resourceType * @return {number} */ function estimateTransferSize(networkRecord, totalBytes, resourceType) { if (!networkRecord) { // We don't know how many bytes this asset used on the network, but we can guess it was // roughly the size of the content gzipped. // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers switch (resourceType) { case 'Stylesheet': // Stylesheets tend to compress extremely well. return Math.round(totalBytes * 0.2); case 'Script': case 'Document': // Scripts and HTML compress fairly well too. return Math.round(totalBytes * 0.33); default: // Otherwise we'll just fallback to the average savings in HTTPArchive return Math.round(totalBytes * 0.5); } } else if (networkRecord.resourceType === resourceType) { // This was a regular standalone asset, just use the transfer size. return networkRecord.transferSize || 0; } else { // This was an asset that was inlined in a different resource type (e.g. HTML document). // Use the compression ratio of the resource to estimate the total transferred bytes. const transferSize = networkRecord.transferSize || 0; const resourceSize = networkRecord.resourceSize || 0; // Get the compression ratio, if it's an invalid number, assume no compression. const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ? (transferSize / resourceSize) : 1; return Math.round(totalBytes * compressionRatio); } } /** * Estimates the number of bytes the content of this network record would have consumed on the network based on the * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable, * minus the size of the response headers. * * This differs from `estimateTransferSize` only in that is subtracts the response headers from the estimate. * * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord * @param {number} totalBytes Uncompressed size of the resource * @param {LH.Crdp.Network.ResourceType=} resourceType * @return {number} */ function estimateCompressedContentSize(networkRecord, totalBytes, resourceType) { if (!networkRecord) { // We don't know how many bytes this asset used on the network, but we can guess it was // roughly the size of the content gzipped. // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers switch (resourceType) { case 'Stylesheet': // Stylesheets tend to compress extremely well. return Math.round(totalBytes * 0.2); case 'Script': case 'Document': // Scripts and HTML compress fairly well too. return Math.round(totalBytes * 0.33); default: // Otherwise we'll just fallback to the average savings in HTTPArchive return Math.round(totalBytes * 0.5); } } // Get the size of the response body on the network. let contentTransferSize = networkRecord.transferSize || 0; if (!NetworkRequest.isContentEncoded(networkRecord)) { // This is not encoded, so we can use resourceSize directly. // This would be equivalent to transfer size minus headers transfer size, but transfer size // may also include bytes for SSL connection etc. contentTransferSize = networkRecord.resourceSize; } else if (networkRecord.responseHeadersTransferSize) { // Subtract the size of the encoded headers. contentTransferSize = Math.max(0, contentTransferSize - networkRecord.responseHeadersTransferSize); } if (networkRecord.resourceType === resourceType) { // This was a regular standalone asset, just use the transfer size. return contentTransferSize; } else { // This was an asset that was inlined in a different resource type (e.g. HTML document). // Use the compression ratio of the resource to estimate the total transferred bytes. const resourceSize = networkRecord.resourceSize || 0; // Get the compression ratio, if it's an invalid number, assume no compression. const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ? (contentTransferSize / resourceSize) : 1; return Math.round(totalBytes * compressionRatio); } } /** * Utility function to estimate the ratio of the compression on the resource. * This excludes the size of the response headers. * Also caches the calculation. * @param {Map<string, number>} compressionRatioByUrl * @param {string} url * @param {LH.Artifacts} artifacts * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords */ function estimateCompressionRatioForContent(compressionRatioByUrl, url, artifacts, networkRecords) { let compressionRatio = compressionRatioByUrl.get(url); if (compressionRatio !== undefined) return compressionRatio; const script = artifacts.Scripts.find(script => script.url === url); if (!script) { // Can't find content, so just use 1. compressionRatio = 1; } else { const networkRecord = getRequestForScript(networkRecords, script); const contentLength = networkRecord?.resourceSize || script.length || 0; const compressedSize = estimateCompressedContentSize(networkRecord, contentLength, 'Script'); compressionRatio = compressedSize / contentLength; } compressionRatioByUrl.set(url, compressionRatio); return compressionRatio; } export { getRequestForScript, isInline, estimateCompressedContentSize, estimateTransferSize, estimateCompressionRatioForContent, };