core(tsc): add type checking to most byte efficiency audits (#5072)
This commit is contained in:
Родитель
9b15f70971
Коммит
e8e024b6ca
|
@ -8,6 +8,9 @@
|
|||
const Audit = require('../audit');
|
||||
const Interactive = require('../../gather/computed/metrics/lantern-interactive'); // eslint-disable-line max-len
|
||||
const Simulator = require('../../lib/dependency-graph/simulator/simulator'); // eslint-disable-line no-unused-vars
|
||||
const Node = require('../../lib/dependency-graph/node.js'); // eslint-disable-line no-unused-vars
|
||||
|
||||
const NetworkNode = require('../../lib/dependency-graph/network-node.js'); // eslint-disable-line no-unused-vars
|
||||
|
||||
const KB_IN_BYTES = 1024;
|
||||
|
||||
|
@ -33,7 +36,7 @@ class UnusedBytes extends Audit {
|
|||
/**
|
||||
* @param {number} bytes
|
||||
* @param {number} networkThroughput measured in bytes/second
|
||||
* @return {string}
|
||||
* @return {number}
|
||||
*/
|
||||
static bytesToMs(bytes, networkThroughput) {
|
||||
const milliseconds = bytes / networkThroughput * 1000;
|
||||
|
@ -42,9 +45,9 @@ class UnusedBytes extends Audit {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
|
||||
*
|
||||
* @param {?LH.WebInspector.NetworkRequest} networkRecord
|
||||
* @param {LH.WebInspector.NetworkRequest=} networkRecord
|
||||
* @param {number} totalBytes Uncompressed size of the resource
|
||||
* @param {string=} resourceType
|
||||
* @param {number=} compressionRatio
|
||||
|
@ -58,19 +61,21 @@ class UnusedBytes extends Audit {
|
|||
return Math.round(totalBytes * compressionRatio);
|
||||
} else if (networkRecord._resourceType && networkRecord._resourceType._name === resourceType) {
|
||||
// This was a regular standalone asset, just use the transfer size.
|
||||
return networkRecord._transferSize;
|
||||
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 compressionRatio = networkRecord._transferSize / networkRecord._resourceSize || 1;
|
||||
const transferSize = networkRecord._transferSize || 0;
|
||||
const resourceSize = networkRecord._resourceSize;
|
||||
const compressionRatio = resourceSize !== undefined ? (transferSize / resourceSize) : 1;
|
||||
return Math.round(totalBytes * compressionRatio);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Artifacts} artifacts
|
||||
* @param {LH.Audit.Context=} context
|
||||
* @return {Promise<AuditResult>}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
*/
|
||||
static audit(artifacts, context) {
|
||||
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
||||
|
@ -90,42 +95,51 @@ class UnusedBytes extends Audit {
|
|||
artifacts.requestLoadSimulator(simulatorOptions),
|
||||
])
|
||||
)
|
||||
.then(([result, graph, simulator]) => this.createAuditResult(result, graph, simulator));
|
||||
.then(([result, graph, simulator]) => this.createAuditProduct(result, graph, simulator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the estimated effect of all the byte savings on the last long task
|
||||
* in the provided graph.
|
||||
*
|
||||
* @param {Array<{url: string, wastedBytes: number}>} results The array of byte savings results per resource
|
||||
* @param {Array<LH.Audit.ByteEfficiencyResult>} results The array of byte savings results per resource
|
||||
* @param {Node} graph
|
||||
* @param {Simulator} simulator
|
||||
* @return {number}
|
||||
*/
|
||||
static computeWasteWithTTIGraph(results, graph, simulator) {
|
||||
const simulationBeforeChanges = simulator.simulate(graph);
|
||||
/** @type {Map<LH.Audit.ByteEfficiencyResult['url'], LH.Audit.ByteEfficiencyResult>} */
|
||||
const resultsByUrl = new Map();
|
||||
for (const result of results) {
|
||||
resultsByUrl.set(result.url, result);
|
||||
}
|
||||
|
||||
// Update all the transfer sizes to reflect implementing our recommendations
|
||||
/** @type {Map<string, number>} */
|
||||
const originalTransferSizes = new Map();
|
||||
graph.traverse(node => {
|
||||
if (node.type !== 'network') return;
|
||||
if (!resultsByUrl.has(node.record.url)) return;
|
||||
const original = node.record.transferSize;
|
||||
const wastedBytes = resultsByUrl.get(node.record.url).wastedBytes;
|
||||
const networkNode = /** @type {NetworkNode} */ (node);
|
||||
const result = resultsByUrl.get(networkNode.record.url);
|
||||
if (!result) return;
|
||||
const original = networkNode.record.transferSize;
|
||||
// cloning NetworkRequest objects is difficult, so just stash the original transfer size
|
||||
node.record._originalTransferSize = original;
|
||||
node.record._transferSize = Math.max(original - wastedBytes, 0);
|
||||
originalTransferSizes.set(networkNode.record.requestId, original);
|
||||
|
||||
const wastedBytes = result.wastedBytes;
|
||||
networkNode.record._transferSize = Math.max(original - wastedBytes, 0);
|
||||
});
|
||||
|
||||
const simulationAfterChanges = simulator.simulate(graph);
|
||||
|
||||
// Restore the original transfer size after we've done our simulation
|
||||
graph.traverse(node => {
|
||||
if (node.type !== 'network') return;
|
||||
if (!node.record._originalTransferSize) return;
|
||||
node.record._transferSize = node.record._originalTransferSize;
|
||||
const networkNode = /** @type {NetworkNode} */ (node);
|
||||
const originalTransferSize = originalTransferSizes.get(networkNode.record.requestId);
|
||||
if (originalTransferSize === undefined) return;
|
||||
networkNode.record._transferSize = originalTransferSize;
|
||||
});
|
||||
|
||||
const savingsOnTTI = Math.max(
|
||||
|
@ -139,12 +153,12 @@ class UnusedBytes extends Audit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Audit.HeadingsResult} result
|
||||
* @param {LH.Audit.ByteEfficiencyProduct} result
|
||||
* @param {Node} graph
|
||||
* @param {Simulator} simulator
|
||||
* @return {AuditResult}
|
||||
* @return {LH.Audit.Product}
|
||||
*/
|
||||
static createAuditResult(result, graph, simulator) {
|
||||
static createAuditProduct(result, graph, simulator) {
|
||||
const debugString = result.debugString;
|
||||
const results = result.results.sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes);
|
||||
|
||||
|
@ -161,6 +175,8 @@ class UnusedBytes extends Audit {
|
|||
wastedMs,
|
||||
wastedBytes,
|
||||
};
|
||||
|
||||
// @ts-ignore - TODO(bckenny): unify details types. items shouldn't be an indexed type.
|
||||
const details = Audit.makeTableDetails(result.headings, results, summary);
|
||||
|
||||
return {
|
||||
|
@ -179,13 +195,19 @@ class UnusedBytes extends Audit {
|
|||
};
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {LH.Audit.ByteEfficiencyProduct|Promise<LH.Audit.ByteEfficiencyProduct>}
|
||||
*/
|
||||
static audit_() {
|
||||
static audit_(artifacts, networkRecords, context) {
|
||||
throw new Error('audit_ unimplemented');
|
||||
}
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
|
||||
module.exports = UnusedBytes;
|
||||
|
|
|
@ -42,26 +42,25 @@ class EfficientAnimatedContent extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!LH.Artifacts} artifacts
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static async audit_(artifacts) {
|
||||
const devtoolsLogs = artifacts.devtoolsLogs[EfficientAnimatedContent.DEFAULT_PASS];
|
||||
|
||||
const networkRecords = await artifacts.requestNetworkRecords(devtoolsLogs);
|
||||
static audit_(artifacts, networkRecords) {
|
||||
const unoptimizedContent = networkRecords.filter(
|
||||
record => record.mimeType === 'image/gif' &&
|
||||
record => record._mimeType === 'image/gif' &&
|
||||
record._resourceType === WebInspector.resourceTypes.Image &&
|
||||
record.resourceSize > GIF_BYTE_THRESHOLD
|
||||
(record._resourceSize || 0) > GIF_BYTE_THRESHOLD
|
||||
);
|
||||
|
||||
/** @type {Array<{url: string, totalBytes: number, wastedBytes: number}>}*/
|
||||
const results = unoptimizedContent.map(record => {
|
||||
const resourceSize = record._resourceSize || 0;
|
||||
return {
|
||||
url: record.url,
|
||||
totalBytes: record.resourceSize,
|
||||
wastedBytes: Math.round(record.resourceSize *
|
||||
EfficientAnimatedContent.getPercentSavings(record.resourceSize)),
|
||||
totalBytes: resourceSize,
|
||||
wastedBytes: Math.round(resourceSize *
|
||||
EfficientAnimatedContent.getPercentSavings(resourceSize)),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const IGNORE_THRESHOLD_IN_PERCENT = 75;
|
|||
|
||||
class OffscreenImages extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -38,7 +38,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!ClientRect} imageRect
|
||||
* @param {ClientRect} imageRect
|
||||
* @param {{innerWidth: number, innerHeight: number}} viewportDimensions
|
||||
* @return {number}
|
||||
*/
|
||||
|
@ -82,8 +82,10 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {Promise<LH.Audit.ByteEfficiencyProduct>}
|
||||
*/
|
||||
static audit_(artifacts, networkRecords, context) {
|
||||
const images = artifacts.ImageUsage;
|
||||
|
@ -91,6 +93,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
const trace = artifacts.traces[ByteEfficiencyAudit.DEFAULT_PASS];
|
||||
const devtoolsLog = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS];
|
||||
|
||||
/** @type {string|undefined} */
|
||||
let debugString;
|
||||
const resultsMap = images.reduce((results, image) => {
|
||||
if (!image.networkRecord) {
|
||||
|
@ -100,6 +103,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
const processed = OffscreenImages.computeWaste(image, viewportDimensions);
|
||||
if (processed instanceof Error) {
|
||||
debugString = processed.message;
|
||||
// @ts-ignore TODO(bckenny): Sentry type checking
|
||||
Sentry.captureException(processed, {tags: {audit: this.meta.name}, level: 'warning'});
|
||||
return results;
|
||||
}
|
||||
|
@ -116,6 +120,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
// TODO(phulce): move this to always use lantern
|
||||
const settings = context.settings;
|
||||
return artifacts.requestFirstCPUIdle({trace, devtoolsLog, settings}).then(firstInteractive => {
|
||||
// @ts-ignore - see above TODO.
|
||||
const ttiTimestamp = firstInteractive.timestamp / 1000000;
|
||||
const results = Array.from(resultsMap.values()).filter(item => {
|
||||
const isWasteful =
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
/**
|
||||
* @fileoverview Audit a page to see if it does have resources that are blocking first paint
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Audit = require('../audit');
|
||||
|
@ -16,6 +15,9 @@ const ByteEfficiencyAudit = require('./byte-efficiency-audit');
|
|||
const UnusedCSS = require('./unused-css-rules');
|
||||
const WebInspector = require('../../lib/web-inspector');
|
||||
|
||||
const Simulator = require('../../lib/dependency-graph/simulator/simulator'); // eslint-disable-line no-unused-vars
|
||||
const NetworkNode = require('../../lib/dependency-graph/network-node.js'); // eslint-disable-line no-unused-vars
|
||||
|
||||
// Because of the way we detect blocking stylesheets, asynchronously loaded
|
||||
// CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)
|
||||
// can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough
|
||||
|
@ -27,17 +29,25 @@ const MINIMUM_WASTED_MS = 50;
|
|||
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
||||
* @return {Object<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>}
|
||||
*/
|
||||
const getNodesAndTimingByUrl = nodeTimings => {
|
||||
function getNodesAndTimingByUrl(nodeTimings) {
|
||||
/** @type {Object<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>} */
|
||||
const urlMap = {};
|
||||
const nodes = Array.from(nodeTimings.keys());
|
||||
return nodes.reduce((map, node) => {
|
||||
map[node.record && node.record.url] = {node, nodeTiming: nodeTimings.get(node)};
|
||||
return map;
|
||||
}, {});
|
||||
};
|
||||
nodes.forEach(node => {
|
||||
if (node.type !== 'network') return;
|
||||
const networkNode = /** @type {NetworkNode} */ (node);
|
||||
const nodeTiming = nodeTimings.get(node);
|
||||
if (!nodeTiming) return;
|
||||
|
||||
urlMap[networkNode.record.url] = {node, nodeTiming};
|
||||
});
|
||||
|
||||
return urlMap;
|
||||
}
|
||||
|
||||
class RenderBlockingResources extends Audit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -57,8 +67,9 @@ class RenderBlockingResources extends Audit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Artifacts} artifacts
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {Promise<{wastedMs: number, results: Array<{url: string, totalBytes: number, wastedMs: number}>}>}
|
||||
*/
|
||||
static async computeResults(artifacts, context) {
|
||||
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
||||
|
@ -70,6 +81,7 @@ class RenderBlockingResources extends Audit {
|
|||
|
||||
const metricSettings = {throttlingMethod: 'simulate'};
|
||||
const metricComputationData = {trace, devtoolsLog, simulator, settings: metricSettings};
|
||||
// @ts-ignore - TODO(bckenny): allow optional `throttling` settings
|
||||
const fcpSimulation = await artifacts.requestFirstContentfulPaint(metricComputationData);
|
||||
const fcpTsInMs = traceOfTab.timestamps.firstContentfulPaint / 1000;
|
||||
|
||||
|
@ -91,6 +103,7 @@ class RenderBlockingResources extends Audit {
|
|||
node.traverse(node => deferredNodeIds.add(node.id));
|
||||
|
||||
// "wastedMs" is the download time of the network request, responseReceived - requestSent
|
||||
// @ts-ignore - TODO(phulce): nodeTiming.startTime/endTime shouldn't be optional by this point?
|
||||
const wastedMs = Math.round(nodeTiming.endTime - nodeTiming.startTime);
|
||||
if (wastedMs < MINIMUM_WASTED_MS) continue;
|
||||
|
||||
|
@ -135,38 +148,43 @@ class RenderBlockingResources extends Audit {
|
|||
const originalEstimate = simulator.simulate(fcpGraph).timeInMs;
|
||||
|
||||
let totalChildNetworkBytes = 0;
|
||||
const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {
|
||||
const minimalFCPGraph = /** @type {NetworkNode} */ (fcpGraph.cloneWithRelationships(node => {
|
||||
// If a node can be deferred, exclude it from the new FCP graph
|
||||
const canDeferRequest = deferredIds.has(node.id);
|
||||
if (node.type !== Node.TYPES.NETWORK) return !canDeferRequest;
|
||||
|
||||
const networkNode = /** @type {NetworkNode} */ (node);
|
||||
|
||||
const isStylesheet =
|
||||
node.type === Node.TYPES.NETWORK &&
|
||||
node.record._resourceType === WebInspector.resourceTypes.Stylesheet;
|
||||
networkNode.record._resourceType === WebInspector.resourceTypes.Stylesheet;
|
||||
if (canDeferRequest && isStylesheet) {
|
||||
// We'll inline the used bytes of the stylesheet and assume the rest can be deferred
|
||||
const wastedBytes = wastedCssBytesByUrl.get(node.record.url) || 0;
|
||||
totalChildNetworkBytes += node.record._transferSize - wastedBytes;
|
||||
const wastedBytes = wastedCssBytesByUrl.get(networkNode.record.url) || 0;
|
||||
totalChildNetworkBytes += (networkNode.record._transferSize || 0) - wastedBytes;
|
||||
}
|
||||
|
||||
// If a node can be deferred, exclude it from the new FCP graph
|
||||
return !canDeferRequest;
|
||||
});
|
||||
}));
|
||||
|
||||
// Add the inlined bytes to the HTML response
|
||||
minimalFCPGraph.record._transferSize += totalChildNetworkBytes;
|
||||
const originalTransferSize = minimalFCPGraph.record._transferSize;
|
||||
const safeTransferSize = originalTransferSize || 0;
|
||||
minimalFCPGraph.record._transferSize = safeTransferSize + totalChildNetworkBytes;
|
||||
const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;
|
||||
minimalFCPGraph.record._transferSize -= totalChildNetworkBytes;
|
||||
minimalFCPGraph.record._transferSize = originalTransferSize;
|
||||
return Math.round(Math.max(originalEstimate - estimateAfterInline, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {Map<string, number>}
|
||||
* @return {Promise<Map<string, number>>}
|
||||
*/
|
||||
static async computeWastedCSSBytes(artifacts, context) {
|
||||
const wastedBytesByUrl = new Map();
|
||||
try {
|
||||
// TODO(phulce): pull this out into computed artifact
|
||||
const results = await UnusedCSS.audit(artifacts, context);
|
||||
// @ts-ignore - TODO(bckenny): details types.
|
||||
for (const item of results.details.items) {
|
||||
wastedBytesByUrl.set(item.url, item.wastedBytes);
|
||||
}
|
||||
|
@ -176,9 +194,9 @@ class RenderBlockingResources extends Audit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {AuditResult}
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
*/
|
||||
static async audit(artifacts, context) {
|
||||
const {results, wastedMs} = await RenderBlockingResources.computeResults(artifacts, context);
|
||||
|
|
|
@ -10,7 +10,7 @@ const Util = require('../../report/html/renderer/util');
|
|||
|
||||
class TotalByteWeight extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -39,9 +39,9 @@ class TotalByteWeight extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {!Promise<!AuditResult>}
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
*/
|
||||
static audit(artifacts, context) {
|
||||
const devtoolsLogs = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS];
|
||||
|
@ -50,11 +50,12 @@ class TotalByteWeight extends ByteEfficiencyAudit {
|
|||
artifacts.requestNetworkThroughput(devtoolsLogs),
|
||||
]).then(([networkRecords, networkThroughput]) => {
|
||||
let totalBytes = 0;
|
||||
/** @type {Array<{url: string, totalBytes: number, totalMs: number}>} */
|
||||
let results = [];
|
||||
networkRecords.forEach(record => {
|
||||
// exclude data URIs since their size is reflected in other resources
|
||||
// exclude unfinished requests since they won't have transfer size information
|
||||
if (record.scheme === 'data' || !record.finished) return;
|
||||
if (record.parsedURL.scheme === 'data' || !record.finished) return;
|
||||
|
||||
const result = {
|
||||
url: record.url,
|
||||
|
|
|
@ -16,7 +16,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
|||
*/
|
||||
class UnminifiedCSS extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -92,15 +92,16 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {{content: string, header: {sourceURL: string}}} stylesheet
|
||||
* @param {?LH.WebInspector.NetworkRequest} networkRecord
|
||||
* @param {LH.Artifacts.CSSStyleSheetInfo} stylesheet
|
||||
* @param {LH.WebInspector.NetworkRequest=} networkRecord
|
||||
* @param {string} pageUrl
|
||||
* @return {{minifiedLength: number, contentLength: number}}
|
||||
* @return {{url: string|LH.Audit.DetailsRendererCodeDetailJSON, totalBytes: number, wastedBytes: number, wastedPercent: number}}
|
||||
*/
|
||||
static computeWaste(stylesheet, networkRecord, pageUrl) {
|
||||
const content = stylesheet.content;
|
||||
const totalTokenLength = UnminifiedCSS.computeTokenLength(content);
|
||||
|
||||
/** @type {LH.Audit.ByteEfficiencyResult['url']} */
|
||||
let url = stylesheet.header.sourceURL;
|
||||
if (!url || url === pageUrl) {
|
||||
const contentPreview = UnusedCSSRules.determineContentPreview(stylesheet.content);
|
||||
|
@ -121,8 +122,9 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts, networkRecords) {
|
||||
const pageUrl = artifacts.URL.finalUrl;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
'use strict';
|
||||
|
||||
const ByteEfficiencyAudit = require('./byte-efficiency-audit');
|
||||
// @ts-ignore - TODO: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/25410
|
||||
const esprima = require('esprima');
|
||||
|
||||
const IGNORE_THRESHOLD_IN_PERCENT = 10;
|
||||
|
@ -23,7 +24,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
|||
*/
|
||||
class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -39,7 +40,8 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|||
|
||||
/**
|
||||
* @param {string} scriptContent
|
||||
* @return {{minifiedLength: number, contentLength: number}}
|
||||
* @param {LH.WebInspector.NetworkRequest} networkRecord
|
||||
* @return {{url: string, totalBytes: number, wastedBytes: number, wastedPercent: number}}
|
||||
*/
|
||||
static computeWaste(scriptContent, networkRecord) {
|
||||
const contentLength = scriptContent.length;
|
||||
|
@ -68,10 +70,12 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts, networkRecords) {
|
||||
/** @type {Array<LH.Audit.ByteEfficiencyResult>} */
|
||||
const results = [];
|
||||
let debugString;
|
||||
for (const requestId of Object.keys(artifacts.Scripts)) {
|
||||
|
|
|
@ -10,9 +10,11 @@ const ByteEfficiencyAudit = require('./byte-efficiency-audit');
|
|||
const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
||||
const PREVIEW_LENGTH = 100;
|
||||
|
||||
/** @typedef {LH.Artifacts.CSSStyleSheetInfo & {networkRecord: LH.WebInspector.NetworkRequest, usedRules: Array<LH.Crdp.CSS.RuleUsage>}} StyleSheetInfo */
|
||||
|
||||
class UnusedCSSRules extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -28,16 +30,16 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<{header: {styleSheetId: string}}>} styles The output of the Styles gatherer.
|
||||
* @param {Array<LH.Artifacts.CSSStyleSheetInfo>} styles The output of the Styles gatherer.
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @return {!Object} A map of styleSheetId to stylesheet information.
|
||||
* @return {Object<string, StyleSheetInfo>} A map of styleSheetId to stylesheet information.
|
||||
*/
|
||||
static indexStylesheetsById(styles, networkRecords) {
|
||||
const indexedNetworkRecords = networkRecords
|
||||
.reduce((indexed, record) => {
|
||||
indexed[record.url] = record;
|
||||
return indexed;
|
||||
}, {});
|
||||
}, /** @type {Object<string, LH.WebInspector.NetworkRequest>} */ ({}));
|
||||
|
||||
return styles.reduce((indexed, stylesheet) => {
|
||||
indexed[stylesheet.header.styleSheetId] = Object.assign({
|
||||
|
@ -45,13 +47,13 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
networkRecord: indexedNetworkRecords[stylesheet.header.sourceURL],
|
||||
}, stylesheet);
|
||||
return indexed;
|
||||
}, {});
|
||||
}, /** @type {Object<string, StyleSheetInfo>} */ ({}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds used rules to their corresponding stylesheet.
|
||||
* @param {!Array.<{styleSheetId: string, used: boolean}>} rules The output of the CSSUsage gatherer.
|
||||
* @param {!Object} indexedStylesheets Stylesheet information indexed by id.
|
||||
* @param {Array<LH.Crdp.CSS.RuleUsage>} rules The output of the CSSUsage gatherer.
|
||||
* @param {Object<string, StyleSheetInfo>} indexedStylesheets Stylesheet information indexed by id.
|
||||
*/
|
||||
static indexUsedRules(rules, indexedStylesheets) {
|
||||
rules.forEach(rule => {
|
||||
|
@ -68,7 +70,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} stylesheetInfo
|
||||
* @param {StyleSheetInfo} stylesheetInfo
|
||||
* @return {{wastedBytes: number, totalBytes: number, wastedPercent: number}}
|
||||
*/
|
||||
static computeUsage(stylesheetInfo) {
|
||||
|
@ -93,7 +95,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
|
||||
/**
|
||||
* Trims stylesheet content down to the first rule-set definition.
|
||||
* @param {?string} content
|
||||
* @param {string=} content
|
||||
* @return {string}
|
||||
*/
|
||||
static determineContentPreview(content) {
|
||||
|
@ -128,15 +130,12 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} stylesheetInfo The stylesheetInfo object.
|
||||
* @param {StyleSheetInfo} stylesheetInfo The stylesheetInfo object.
|
||||
* @param {string} pageUrl The URL of the page, used to identify inline styles.
|
||||
* @return {?{url: string, wastedBytes: number, totalBytes: number}}
|
||||
* @return {LH.Audit.ByteEfficiencyResult}
|
||||
*/
|
||||
static mapSheetToResult(stylesheetInfo, pageUrl) {
|
||||
if (stylesheetInfo.isDuplicate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {LH.Audit.ByteEfficiencyResult['url']} */
|
||||
let url = stylesheetInfo.header.sourceURL;
|
||||
if (!url || url === pageUrl) {
|
||||
const contentPreview = UnusedCSSRules.determineContentPreview(stylesheetInfo.content);
|
||||
|
@ -148,8 +147,8 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @return {Promise<LH.Audit.ByteEfficiencyProduct>}
|
||||
*/
|
||||
static audit_(artifacts) {
|
||||
const styles = artifacts.CSSUsage.stylesheets;
|
||||
|
|
|
@ -11,7 +11,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
|||
|
||||
class UnusedJavaScript extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -25,7 +25,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!JsUsageArtifact} script
|
||||
* @param {LH.Crdp.Profiler.ScriptCoverage} script
|
||||
* @return {{unusedLength: number, contentLength: number}}
|
||||
*/
|
||||
static computeWaste(script) {
|
||||
|
@ -61,9 +61,9 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<{unusedLength: number, contentLength: number}>} wasteData
|
||||
* @param {Array<{unusedLength: number, contentLength: number}>} wasteData
|
||||
* @param {LH.WebInspector.NetworkRequest} networkRecord
|
||||
* @return {{url: string, totalBytes: number, wastedBytes: number, wastedPercent: number}}
|
||||
* @return {LH.Audit.ByteEfficiencyResult}
|
||||
*/
|
||||
static mergeWaste(wasteData, networkRecord) {
|
||||
let unusedLength = 0;
|
||||
|
@ -87,10 +87,12 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {Array<LH.WebInspector.NetworkRequest>} networkRecords
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts, networkRecords) {
|
||||
/** @type {Map<string, Array<LH.Crdp.Profiler.ScriptCoverage>>} */
|
||||
const scriptsByUrl = new Map();
|
||||
for (const script of artifacts.JsUsage) {
|
||||
const scripts = scriptsByUrl.get(script.url) || [];
|
||||
|
@ -98,6 +100,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|||
scriptsByUrl.set(script.url, scripts);
|
||||
}
|
||||
|
||||
/** @type {Array<LH.Audit.ByteEfficiencyResult>} */
|
||||
const results = [];
|
||||
for (const [url, scripts] of scriptsByUrl.entries()) {
|
||||
const networkRecord = networkRecords.find(record => record.url === url);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
// @ts-ignore - typed where used.
|
||||
const parseCacheControl = require('parse-cache-control');
|
||||
const Audit = require('../audit');
|
||||
const WebInspector = require('../../lib/web-inspector');
|
||||
|
@ -16,11 +17,10 @@ const IGNORE_THRESHOLD_IN_PERCENT = 0.925;
|
|||
|
||||
class CacheHeaders extends Audit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
category: 'Caching',
|
||||
name: 'uses-long-cache-ttl',
|
||||
description: 'Uses efficient cache policy on static assets',
|
||||
failureDescription: 'Uses inefficient cache policy on static assets',
|
||||
|
@ -103,22 +103,24 @@ class CacheHeaders extends Audit {
|
|||
* Computes the user-specified cache lifetime, 0 if explicit no-cache policy is in effect, and null if not
|
||||
* user-specified. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
*
|
||||
* @param {!Map<string,string>} headers
|
||||
* @param {!Object} cacheControl Follows the potential settings of cache-control, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
* @param {Map<string, string>} headers
|
||||
* @param {{'no-cache'?: boolean,'no-store'?: boolean, 'max-age'?: number}} cacheControl Follows the potential settings of cache-control, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
* @return {?number}
|
||||
*/
|
||||
static computeCacheLifetimeInSeconds(headers, cacheControl) {
|
||||
if (cacheControl) {
|
||||
// Cache-Control takes precendence over expires
|
||||
if (cacheControl['no-cache'] || cacheControl['no-store']) return 0;
|
||||
if (Number.isFinite(cacheControl['max-age'])) return Math.max(cacheControl['max-age'], 0);
|
||||
const maxAge = cacheControl['max-age'];
|
||||
if (maxAge !== undefined && Number.isFinite(maxAge)) return Math.max(maxAge, 0);
|
||||
} else if ((headers.get('pragma') || '').includes('no-cache')) {
|
||||
// The HTTP/1.0 Pragma header can disable caching if cache-control is not set, see https://tools.ietf.org/html/rfc7234#section-5.4
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (headers.has('expires')) {
|
||||
const expires = new Date(headers.get('expires')).getTime();
|
||||
const expiresHeaders = headers.get('expires');
|
||||
if (expiresHeaders) {
|
||||
const expires = new Date(expiresHeaders).getTime();
|
||||
// Invalid expires values MUST be treated as already expired
|
||||
if (!expires) return 0;
|
||||
return Math.max(0, Math.ceil((expires - Date.now()) / 1000));
|
||||
|
@ -162,9 +164,9 @@ class CacheHeaders extends Audit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {!AuditResult}
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
*/
|
||||
static audit(artifacts, context) {
|
||||
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
||||
|
@ -176,8 +178,9 @@ class CacheHeaders extends Audit {
|
|||
for (const record of records) {
|
||||
if (!CacheHeaders.isCacheableAsset(record)) continue;
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const headers = new Map();
|
||||
for (const header of record._responseHeaders) {
|
||||
for (const header of record._responseHeaders || []) {
|
||||
headers.set(header.name.toLowerCase(), header.value);
|
||||
}
|
||||
|
||||
|
@ -195,7 +198,7 @@ class CacheHeaders extends Audit {
|
|||
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
|
||||
|
||||
const url = URL.elideDataURI(record._url);
|
||||
const totalBytes = record._transferSize;
|
||||
const totalBytes = record._transferSize || 0;
|
||||
const wastedBytes = (1 - cacheHitProbability) * totalBytes;
|
||||
|
||||
totalWastedBytes += wastedBytes;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
* @fileoverview This audit determines if the images used are sufficiently larger
|
||||
* than JPEG compressed images without metadata at quality 85.
|
||||
*/
|
||||
// @ts-nocheck - TODO(bckenny)
|
||||
'use strict';
|
||||
|
||||
const ByteEfficiencyAudit = require('./byte-efficiency-audit');
|
||||
|
@ -16,7 +17,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 4096;
|
|||
|
||||
class UsesOptimizedImages extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -31,19 +32,18 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {{originalSize: number, webpSize: number, jpegSize: number}} image
|
||||
* @param {string} type
|
||||
* @param {{originalSize: number, jpegSize: number}} image
|
||||
* @return {{bytes: number, percent: number}}
|
||||
*/
|
||||
static computeSavings(image, type) {
|
||||
const bytes = image.originalSize - image[type + 'Size'];
|
||||
static computeSavings(image) {
|
||||
const bytes = image.originalSize - image.jpegSize;
|
||||
const percent = 100 * bytes / image.originalSize;
|
||||
return {bytes, percent};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts) {
|
||||
const images = artifacts.OptimizedImages;
|
||||
|
@ -60,7 +60,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
const url = URL.elideDataURI(image.url);
|
||||
const jpegSavings = UsesOptimizedImages.computeSavings(image, 'jpeg');
|
||||
const jpegSavings = UsesOptimizedImages.computeSavings(image);
|
||||
|
||||
results.push({
|
||||
url,
|
||||
|
|
|
@ -18,11 +18,10 @@ const Sentry = require('../../lib/sentry');
|
|||
const URL = require('../../lib/url-shim');
|
||||
|
||||
const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
||||
const WASTEFUL_THRESHOLD_IN_BYTES = 25 * 1024;
|
||||
|
||||
class UsesResponsiveImages extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -39,9 +38,9 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} image
|
||||
* @param {LH.Artifacts.SingleImageUsage} image
|
||||
* @param {number} DPR devicePixelRatio
|
||||
* @return {?Object}
|
||||
* @return {null|Error|LH.Audit.ByteEfficiencyResult};
|
||||
*/
|
||||
static computeWaste(image, DPR) {
|
||||
const url = URL.elideDataURI(image.src);
|
||||
|
@ -66,19 +65,19 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|||
totalBytes,
|
||||
wastedBytes,
|
||||
wastedPercent: 100 * wastedRatio,
|
||||
isWasteful: wastedBytes > WASTEFUL_THRESHOLD_IN_BYTES,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts) {
|
||||
const images = artifacts.ImageUsage;
|
||||
const DPR = artifacts.ViewportDimensions.devicePixelRatio;
|
||||
|
||||
let debugString;
|
||||
/** @type {Map<LH.Audit.ByteEfficiencyResult['url'], LH.Audit.ByteEfficiencyResult>} */
|
||||
const resultsMap = new Map();
|
||||
images.forEach(image => {
|
||||
// TODO: give SVG a free pass until a detail per pixel metric is available
|
||||
|
@ -91,6 +90,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|||
|
||||
if (processed instanceof Error) {
|
||||
debugString = processed.message;
|
||||
// @ts-ignore TODO(bckenny): Sentry type checking
|
||||
Sentry.captureException(processed, {tags: {audit: this.meta.name}, level: 'warning'});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const IGNORE_THRESHOLD_IN_PERCENT = 0.1;
|
|||
|
||||
class ResponsesAreCompressed extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -33,13 +33,13 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {number} networkThroughput
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts) {
|
||||
const uncompressedResponses = artifacts.ResponseCompression;
|
||||
|
||||
/** @type {Array<LH.Audit.ByteEfficiencyResult>} */
|
||||
const results = [];
|
||||
uncompressedResponses.forEach(record => {
|
||||
const originalSize = record.resourceSize;
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
/*
|
||||
* @fileoverview This audit determines if the images could be smaller when compressed with WebP.
|
||||
*/
|
||||
// @ts-nocheck - TODO(bckenny)
|
||||
'use strict';
|
||||
|
||||
const ByteEfficiencyAudit = require('./byte-efficiency-audit');
|
||||
const OptimizedImages = require('./uses-optimized-images');
|
||||
const URL = require('../../lib/url-shim');
|
||||
|
||||
const IGNORE_THRESHOLD_IN_BYTES = 8192;
|
||||
|
||||
class UsesWebPImages extends ByteEfficiencyAudit {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -32,8 +32,18 @@ class UsesWebPImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!Audit.HeadingsResult}
|
||||
* @param {{originalSize: number, webpSize: number}} image
|
||||
* @return {{bytes: number, percent: number}}
|
||||
*/
|
||||
static computeSavings(image) {
|
||||
const bytes = image.originalSize - image.webpSize;
|
||||
const percent = 100 * bytes / image.originalSize;
|
||||
return {bytes, percent};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @return {LH.Audit.ByteEfficiencyProduct}
|
||||
*/
|
||||
static audit_(artifacts) {
|
||||
const images = artifacts.OptimizedImages;
|
||||
|
@ -49,7 +59,7 @@ class UsesWebPImages extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
const url = URL.elideDataURI(image.url);
|
||||
const webpSavings = OptimizedImages.computeSavings(image, 'webp');
|
||||
const webpSavings = UsesWebPImages.computeSavings(image);
|
||||
|
||||
results.push({
|
||||
url,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
class Node {
|
||||
/**
|
||||
* @param {string|number} id
|
||||
* @param {string} id
|
||||
*/
|
||||
constructor(id) {
|
||||
this._id = id;
|
||||
|
@ -30,7 +30,7 @@ class Node {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return {string|number}
|
||||
* @return {string}
|
||||
*/
|
||||
get id() {
|
||||
return this._id;
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should format details', () => {
|
||||
const result = ByteEfficiencyAudit.createAuditResult({
|
||||
const result = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [],
|
||||
}, graph, simulator);
|
||||
|
@ -85,7 +85,7 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should set the rawValue', () => {
|
||||
const result = ByteEfficiencyAudit.createAuditResult(
|
||||
const result = ByteEfficiencyAudit.createAuditProduct(
|
||||
{
|
||||
headings: baseHeadings,
|
||||
results: [
|
||||
|
@ -101,22 +101,22 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should score the wastedMs', () => {
|
||||
const perfectResult = ByteEfficiencyAudit.createAuditResult({
|
||||
const perfectResult = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [{url: 'http://example.com/', wastedBytes: 1 * 1000}],
|
||||
}, graph, simulator);
|
||||
|
||||
const goodResult = ByteEfficiencyAudit.createAuditResult({
|
||||
const goodResult = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [{url: 'http://example.com/', wastedBytes: 20 * 1000}],
|
||||
}, graph, simulator);
|
||||
|
||||
const averageResult = ByteEfficiencyAudit.createAuditResult({
|
||||
const averageResult = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [{url: 'http://example.com/', wastedBytes: 100 * 1000}],
|
||||
}, graph, simulator);
|
||||
|
||||
const failingResult = ByteEfficiencyAudit.createAuditResult({
|
||||
const failingResult = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [{url: 'http://example.com/', wastedBytes: 400 * 1000}],
|
||||
}, graph, simulator);
|
||||
|
@ -129,7 +129,7 @@ describe('Byte efficiency base audit', () => {
|
|||
|
||||
it('should throw on invalid graph', () => {
|
||||
assert.throws(() => {
|
||||
ByteEfficiencyAudit.createAuditResult({
|
||||
ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [{wastedBytes: 350, totalBytes: 700, wastedPercent: 50}],
|
||||
}, null);
|
||||
|
@ -137,7 +137,7 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should populate KB', () => {
|
||||
const result = ByteEfficiencyAudit.createAuditResult({
|
||||
const result = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [
|
||||
{wastedBytes: 2048, totalBytes: 4096, wastedPercent: 50},
|
||||
|
@ -152,7 +152,7 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should sort on wastedBytes', () => {
|
||||
const result = ByteEfficiencyAudit.createAuditResult({
|
||||
const result = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [
|
||||
{wastedBytes: 350, totalBytes: 700, wastedPercent: 50},
|
||||
|
@ -167,7 +167,7 @@ describe('Byte efficiency base audit', () => {
|
|||
});
|
||||
|
||||
it('should create a display value', () => {
|
||||
const result = ByteEfficiencyAudit.createAuditResult({
|
||||
const result = ByteEfficiencyAudit.createAuditProduct({
|
||||
headings: baseHeadings,
|
||||
results: [
|
||||
{wastedBytes: 512, totalBytes: 700, wastedPercent: 50},
|
||||
|
@ -185,7 +185,7 @@ describe('Byte efficiency base audit', () => {
|
|||
const artifacts = Runner.instantiateComputedArtifacts();
|
||||
const graph = await artifacts.requestPageDependencyGraph({trace, devtoolsLog});
|
||||
const simulator = await artifacts.requestLoadSimulator({devtoolsLog, settings});
|
||||
const result = ByteEfficiencyAudit.createAuditResult(
|
||||
const result = ByteEfficiencyAudit.createAuditProduct(
|
||||
{
|
||||
headings: [{key: 'value', text: 'Label'}],
|
||||
results: [
|
||||
|
|
|
@ -16,23 +16,22 @@ describe('Page uses videos for animated GIFs', () => {
|
|||
const networkRecords = [
|
||||
{
|
||||
_resourceType: WebInspector.resourceTypes.Image,
|
||||
mimeType: 'image/gif',
|
||||
resourceSize: 100240,
|
||||
_mimeType: 'image/gif',
|
||||
_resourceSize: 100240,
|
||||
url: 'https://example.com/example.gif',
|
||||
},
|
||||
{
|
||||
_resourceType: WebInspector.resourceTypes.Image,
|
||||
mimeType: 'image/gif',
|
||||
resourceSize: 110000,
|
||||
_mimeType: 'image/gif',
|
||||
_resourceSize: 110000,
|
||||
url: 'https://example.com/example2.gif',
|
||||
},
|
||||
];
|
||||
const artifacts = {
|
||||
devtoolsLogs: {[EfficientAnimatedContent.DEFAULT_PASS]: []},
|
||||
requestNetworkRecords: () => Promise.resolve(networkRecords),
|
||||
};
|
||||
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts);
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts, networkRecords);
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].url, 'https://example.com/example2.gif');
|
||||
assert.equal(results[0].totalBytes, 110000);
|
||||
|
@ -49,10 +48,9 @@ describe('Page uses videos for animated GIFs', () => {
|
|||
];
|
||||
const artifacts = {
|
||||
devtoolsLogs: {[EfficientAnimatedContent.DEFAULT_PASS]: []},
|
||||
requestNetworkRecords: () => Promise.resolve(networkRecords),
|
||||
};
|
||||
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts);
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts, networkRecords);
|
||||
assert.equal(results.length, 0);
|
||||
});
|
||||
|
||||
|
@ -71,10 +69,9 @@ describe('Page uses videos for animated GIFs', () => {
|
|||
];
|
||||
const artifacts = {
|
||||
devtoolsLogs: {[EfficientAnimatedContent.DEFAULT_PASS]: []},
|
||||
requestNetworkRecords: () => Promise.resolve(networkRecords),
|
||||
};
|
||||
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts);
|
||||
const {results} = await EfficientAnimatedContent.audit_(artifacts, networkRecords);
|
||||
assert.equal(results.length, 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,17 +7,23 @@
|
|||
|
||||
const TotalByteWeight = require('../../../audits/byte-efficiency/total-byte-weight.js');
|
||||
const assert = require('assert');
|
||||
const URL = require('url').URL;
|
||||
const options = TotalByteWeight.defaultOptions;
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
function generateRequest(url, size, baseUrl = 'http://google.com/') {
|
||||
const parsedUrl = new URL(url, baseUrl);
|
||||
const scheme = parsedUrl.protocol.slice(0, -1);
|
||||
return {
|
||||
url: `${baseUrl}${url}`,
|
||||
url: parsedUrl.href,
|
||||
finished: true,
|
||||
transferSize: size * 1024,
|
||||
responseReceivedTime: 1000,
|
||||
endTime: 2000,
|
||||
parsedURL: {
|
||||
scheme,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,6 +31,7 @@ function generateArtifacts(records) {
|
|||
if (records[0] && records[0].length > 1) {
|
||||
records = records.map(args => generateRequest(...args));
|
||||
}
|
||||
|
||||
return {
|
||||
devtoolsLogs: {defaultPass: []},
|
||||
requestNetworkRecords: () => Promise.resolve(records),
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"lighthouse-core/audits/audit.js",
|
||||
"lighthouse-core/audits/accessibility/**/*.js",
|
||||
"lighthouse-core/audits/dobetterweb/**/*.js",
|
||||
"lighthouse-core/audits/byte-efficiency/**/*.js",
|
||||
"lighthouse-core/audits/manual/**/*.js",
|
||||
"lighthouse-core/audits/seo/manual/*.js",
|
||||
"lighthouse-core/audits/seo/robots-txt.js",
|
||||
|
|
|
@ -38,15 +38,24 @@ declare global {
|
|||
key: string;
|
||||
itemType: string;
|
||||
text: string;
|
||||
displayUnit?: string;
|
||||
granularity?: number;
|
||||
}
|
||||
|
||||
export interface HeadingsResult {
|
||||
results: number;
|
||||
export interface ByteEfficiencyProduct {
|
||||
results: Array<ByteEfficiencyResult>;
|
||||
headings: Array<Audit.Heading>;
|
||||
passes: boolean;
|
||||
displayValue?: string;
|
||||
debugString?: string;
|
||||
}
|
||||
|
||||
export interface ByteEfficiencyResult {
|
||||
url: string | DetailsRendererCodeDetailJSON;
|
||||
wastedBytes: number;
|
||||
totalBytes: number;
|
||||
wastedPercent?: number;
|
||||
}
|
||||
|
||||
// TODO: placeholder typedefs until Details are typed
|
||||
export interface DetailsRendererDetailsSummary {
|
||||
wastedMs?: number;
|
||||
|
@ -61,8 +70,13 @@ declare global {
|
|||
summary?: DetailsRendererDetailsSummary;
|
||||
}
|
||||
|
||||
export interface DetailsRendererCodeDetailJSON {
|
||||
type: 'code',
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type DetailsItem = string | number | DetailsRendererNodeDetailsJSON |
|
||||
DetailsRendererLinkDetailsJSON;
|
||||
DetailsRendererLinkDetailsJSON | DetailsRendererCodeDetailJSON;
|
||||
|
||||
export interface DetailsRendererNodeDetailsJSON {
|
||||
type: 'node';
|
||||
|
|
|
@ -26,7 +26,9 @@ declare global {
|
|||
_responseReceivedTime: number;
|
||||
|
||||
transferSize: number;
|
||||
/** Should use a default of 0 if not defined */
|
||||
_transferSize?: number;
|
||||
/** Should use a default of 0 if not defined */
|
||||
_resourceSize?: number;
|
||||
|
||||
finished: boolean;
|
||||
|
|
Загрузка…
Ссылка в новой задаче