lighthouse 11.3.0 → 11.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/bf-cache.d.ts +1 -0
- package/core/audits/bf-cache.js +12 -2
- package/core/audits/bootup-time.js +3 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
- package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
- package/core/audits/byte-efficiency/unminified-css.js +2 -2
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
- package/core/audits/byte-efficiency/unused-javascript.js +10 -11
- package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
- package/core/audits/dobetterweb/uses-http2.js +8 -5
- package/core/audits/layout-shift-elements.js +32 -17
- package/core/audits/metrics/cumulative-layout-shift.js +5 -1
- package/core/audits/metrics/max-potential-fid.d.ts +20 -0
- package/core/audits/metrics/max-potential-fid.js +69 -0
- package/core/audits/resource-summary.d.ts +11 -0
- package/core/audits/resource-summary.js +86 -0
- package/core/audits/third-party-cookies.d.ts +22 -0
- package/core/audits/third-party-cookies.js +112 -0
- package/core/audits/third-party-summary.d.ts +20 -2
- package/core/audits/third-party-summary.js +3 -48
- package/core/computed/entity-classification.js +1 -1
- package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
- package/core/computed/metrics/cumulative-layout-shift.js +47 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
- package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
- package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
- package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
- package/core/computed/metrics/lantern-metric.d.ts +2 -2
- package/core/computed/metrics/lantern-metric.js +6 -5
- package/core/computed/resource-summary.d.ts +0 -1
- package/core/computed/resource-summary.js +2 -2
- package/core/computed/unused-css.js +2 -2
- package/core/config/constants.d.ts +2 -2
- package/core/config/constants.js +2 -2
- package/core/config/default-config.js +4 -0
- package/core/config/filters.js +1 -0
- package/core/gather/base-artifacts.js +2 -1
- package/core/gather/gatherers/css-usage.js +5 -0
- package/core/gather/gatherers/trace-elements.d.ts +6 -15
- package/core/gather/gatherers/trace-elements.js +15 -71
- package/core/gather/navigation-runner.js +1 -1
- package/core/lib/i18n/i18n.d.ts +2 -2
- package/core/lib/i18n/i18n.js +2 -2
- package/core/lib/minification-estimator.js +1 -1
- package/core/lib/network-recorder.d.ts +8 -0
- package/core/lib/network-recorder.js +13 -0
- package/core/lib/network-request.d.ts +11 -0
- package/core/lib/network-request.js +21 -1
- package/core/lib/rect-helpers.d.ts +5 -0
- package/core/lib/rect-helpers.js +15 -0
- package/core/lib/script-helpers.d.ts +34 -5
- package/core/lib/script-helpers.js +136 -0
- package/core/lib/url-utils.d.ts +6 -0
- package/core/lib/url-utils.js +14 -2
- package/core/runner.js +5 -1
- package/core/scripts/download-chrome.sh +56 -13
- package/dist/report/bundle.esm.js +30 -13
- package/dist/report/flow.js +32 -15
- package/dist/report/standalone.js +32 -15
- package/flow-report/src/i18n/i18n.d.ts +4 -0
- package/package.json +5 -4
- package/report/assets/styles.css +25 -8
- package/report/assets/templates.html +1 -1
- package/report/clients/standalone.js +1 -0
- package/report/renderer/components.js +2 -2
- package/report/renderer/explodey-gauge.js +0 -1
- package/report/renderer/performance-category-renderer.js +4 -2
- package/report/renderer/report-renderer.js +12 -1
- package/report/renderer/report-ui-features.js +3 -2
- package/report/renderer/report-utils.d.ts +2 -0
- package/report/renderer/report-utils.js +8 -4
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +34 -25
- package/shared/localization/locales/ar.json +35 -26
- package/shared/localization/locales/bg.json +34 -25
- package/shared/localization/locales/ca.json +35 -26
- package/shared/localization/locales/cs.json +34 -25
- package/shared/localization/locales/da.json +51 -42
- package/shared/localization/locales/de.json +38 -29
- package/shared/localization/locales/el.json +34 -25
- package/shared/localization/locales/en-GB.json +35 -26
- package/shared/localization/locales/en-US.json +26 -5
- package/shared/localization/locales/en-XA.json +34 -25
- package/shared/localization/locales/en-XL.json +26 -5
- package/shared/localization/locales/es-419.json +34 -25
- package/shared/localization/locales/es.json +34 -25
- package/shared/localization/locales/fi.json +34 -25
- package/shared/localization/locales/fil.json +35 -26
- package/shared/localization/locales/fr.json +34 -25
- package/shared/localization/locales/he.json +35 -26
- package/shared/localization/locales/hi.json +35 -26
- package/shared/localization/locales/hr.json +35 -26
- package/shared/localization/locales/hu.json +34 -25
- package/shared/localization/locales/id.json +34 -25
- package/shared/localization/locales/it.json +34 -25
- package/shared/localization/locales/ja.json +34 -25
- package/shared/localization/locales/ko.json +35 -26
- package/shared/localization/locales/lt.json +34 -25
- package/shared/localization/locales/lv.json +35 -26
- package/shared/localization/locales/nl.json +35 -26
- package/shared/localization/locales/no.json +34 -25
- package/shared/localization/locales/pl.json +34 -25
- package/shared/localization/locales/pt-PT.json +35 -26
- package/shared/localization/locales/pt.json +35 -26
- package/shared/localization/locales/ro.json +34 -25
- package/shared/localization/locales/ru.json +35 -26
- package/shared/localization/locales/sk.json +35 -26
- package/shared/localization/locales/sl.json +35 -26
- package/shared/localization/locales/sr-Latn.json +34 -25
- package/shared/localization/locales/sr.json +34 -25
- package/shared/localization/locales/sv.json +35 -26
- package/shared/localization/locales/ta.json +35 -26
- package/shared/localization/locales/te.json +35 -26
- package/shared/localization/locales/th.json +34 -25
- package/shared/localization/locales/tr.json +35 -26
- package/shared/localization/locales/uk.json +34 -25
- package/shared/localization/locales/vi.json +34 -25
- package/shared/localization/locales/zh-HK.json +35 -26
- package/shared/localization/locales/zh-TW.json +38 -29
- package/shared/localization/locales/zh.json +34 -25
- package/shared/util.d.ts +14 -2
- package/shared/util.js +25 -3
- package/tsconfig.json +2 -0
- package/types/artifacts.d.ts +5 -2
|
@@ -22,7 +22,7 @@ const UIStrings = {
|
|
|
22
22
|
'the web page. If the `role` values are misspelled, not existing ARIA `role` values, or ' +
|
|
23
23
|
'abstract roles, then the purpose of the element will not be communicated to users of ' +
|
|
24
24
|
'assistive technologies. ' +
|
|
25
|
-
'[Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-
|
|
25
|
+
'[Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-role).',
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
package/core/audits/bf-cache.js
CHANGED
|
@@ -26,6 +26,8 @@ const UIStrings = {
|
|
|
26
26
|
failureReasonColumn: 'Failure reason',
|
|
27
27
|
/** Label for a column in a data table; entries in the column will be a string representing the type of failure preventing the back/forward cache from being used. */
|
|
28
28
|
failureTypeColumn: 'Failure type',
|
|
29
|
+
/** Warning explaining that the back/forward cache results cannot be shown in the old Headless Chrome. "back/forward" refers to the back and forward buttons found in modern browsers. "Headless Chrome" is a product name and should not be translated. */
|
|
30
|
+
warningHeadless: 'Back/forward cache cannot be tested in old Headless Chrome (`--chrome-flags="--headless=old"`). To see audit results, use the new Headless Chrome (`--chrome-flags="--headless=new"`) or standard Chrome.',
|
|
29
31
|
/**
|
|
30
32
|
* @description [ICU Syntax] Label for an audit identifying the number of back/forward cache failure reasons found in the page.
|
|
31
33
|
*/
|
|
@@ -60,8 +62,8 @@ class BFCache extends Audit {
|
|
|
60
62
|
description: str_(UIStrings.description),
|
|
61
63
|
supportedModes: ['navigation', 'timespan'],
|
|
62
64
|
guidanceLevel: 2,
|
|
63
|
-
requiredArtifacts: ['BFCacheFailures'],
|
|
64
|
-
scoreDisplayMode: Audit.SCORING_MODES.
|
|
65
|
+
requiredArtifacts: ['BFCacheFailures', 'HostProduct'],
|
|
66
|
+
scoreDisplayMode: Audit.SCORING_MODES.BINARY,
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -70,6 +72,14 @@ class BFCache extends Audit {
|
|
|
70
72
|
* @return {Promise<LH.Audit.Product>}
|
|
71
73
|
*/
|
|
72
74
|
static async audit(artifacts) {
|
|
75
|
+
if (/HeadlessChrome/.test(artifacts.HostProduct)) {
|
|
76
|
+
return {
|
|
77
|
+
score: null,
|
|
78
|
+
notApplicable: true,
|
|
79
|
+
warnings: [str_(UIStrings.warningHeadless)],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
const failures = artifacts.BFCacheFailures;
|
|
74
84
|
if (!failures.length) return {score: 1};
|
|
75
85
|
|
|
@@ -109,6 +109,9 @@ class BootupTime extends Audit {
|
|
|
109
109
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
110
110
|
|
|
111
111
|
const executionTimings = getExecutionTimingsByURL(tasks, networkRecords);
|
|
112
|
+
// Exclude our own tasks.
|
|
113
|
+
executionTimings.delete('_lighthouse-eval.js');
|
|
114
|
+
|
|
112
115
|
const tbtImpact = await this.getTbtImpact(artifacts, context);
|
|
113
116
|
|
|
114
117
|
let hadExcessiveChromeExtension = false;
|
|
@@ -33,16 +33,6 @@ export class ByteEfficiencyAudit extends Audit {
|
|
|
33
33
|
* @return {number}
|
|
34
34
|
*/
|
|
35
35
|
static scoreForWastedMs(wastedMs: number): number;
|
|
36
|
-
/**
|
|
37
|
-
* Estimates the number of bytes this network record would have consumed on the network based on the
|
|
38
|
-
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
|
|
39
|
-
*
|
|
40
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
41
|
-
* @param {number} totalBytes Uncompressed size of the resource
|
|
42
|
-
* @param {LH.Crdp.Network.ResourceType=} resourceType
|
|
43
|
-
* @return {number}
|
|
44
|
-
*/
|
|
45
|
-
static estimateTransferSize(networkRecord: LH.Artifacts.NetworkRequest | undefined, totalBytes: number, resourceType?: LH.Crdp.Network.ResourceType | undefined): number;
|
|
46
36
|
/**
|
|
47
37
|
* @param {LH.Artifacts} artifacts
|
|
48
38
|
* @param {LH.Audit.Context} context
|
|
@@ -58,48 +58,6 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Estimates the number of bytes this network record would have consumed on the network based on the
|
|
63
|
-
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
|
|
64
|
-
*
|
|
65
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
66
|
-
* @param {number} totalBytes Uncompressed size of the resource
|
|
67
|
-
* @param {LH.Crdp.Network.ResourceType=} resourceType
|
|
68
|
-
* @return {number}
|
|
69
|
-
*/
|
|
70
|
-
static estimateTransferSize(networkRecord, totalBytes, resourceType) {
|
|
71
|
-
if (!networkRecord) {
|
|
72
|
-
// We don't know how many bytes this asset used on the network, but we can guess it was
|
|
73
|
-
// roughly the size of the content gzipped.
|
|
74
|
-
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
|
|
75
|
-
// See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
|
|
76
|
-
switch (resourceType) {
|
|
77
|
-
case 'Stylesheet':
|
|
78
|
-
// Stylesheets tend to compress extremely well.
|
|
79
|
-
return Math.round(totalBytes * 0.2);
|
|
80
|
-
case 'Script':
|
|
81
|
-
case 'Document':
|
|
82
|
-
// Scripts and HTML compress fairly well too.
|
|
83
|
-
return Math.round(totalBytes * 0.33);
|
|
84
|
-
default:
|
|
85
|
-
// Otherwise we'll just fallback to the average savings in HTTPArchive
|
|
86
|
-
return Math.round(totalBytes * 0.5);
|
|
87
|
-
}
|
|
88
|
-
} else if (networkRecord.resourceType === resourceType) {
|
|
89
|
-
// This was a regular standalone asset, just use the transfer size.
|
|
90
|
-
return networkRecord.transferSize || 0;
|
|
91
|
-
} else {
|
|
92
|
-
// This was an asset that was inlined in a different resource type (e.g. HTML document).
|
|
93
|
-
// Use the compression ratio of the resource to estimate the total transferred bytes.
|
|
94
|
-
const transferSize = networkRecord.transferSize || 0;
|
|
95
|
-
const resourceSize = networkRecord.resourceSize || 0;
|
|
96
|
-
// Get the compression ratio, if it's an invalid number, assume no compression.
|
|
97
|
-
const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
|
|
98
|
-
(transferSize / resourceSize) : 1;
|
|
99
|
-
return Math.round(totalBytes * compressionRatio);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
61
|
/**
|
|
104
62
|
* @param {LH.Artifacts} artifacts
|
|
105
63
|
* @param {LH.Audit.Context} context
|
|
@@ -248,10 +206,10 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
248
206
|
if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
|
|
249
207
|
const graph = await PageDependencyGraph.request(metricComputationInput, context);
|
|
250
208
|
const {
|
|
251
|
-
|
|
209
|
+
optimisticGraph: optimisticFCPGraph,
|
|
252
210
|
} = await LanternFirstContentfulPaint.request(metricComputationInput, context);
|
|
253
211
|
const {
|
|
254
|
-
|
|
212
|
+
optimisticGraph: optimisticLCPGraph,
|
|
255
213
|
} = await LanternLargestContentfulPaint.request(metricComputationInput, context);
|
|
256
214
|
|
|
257
215
|
wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
|
|
@@ -260,17 +218,19 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
260
218
|
|
|
261
219
|
const {savings: fcpSavings} = this.computeWasteWithGraph(
|
|
262
220
|
results,
|
|
263
|
-
|
|
221
|
+
optimisticFCPGraph,
|
|
264
222
|
simulator,
|
|
265
223
|
{providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'fcp'}
|
|
266
224
|
);
|
|
225
|
+
// Note: LCP's optimistic graph sometimes unexpectedly yields higher savings than the pessimistic graph.
|
|
267
226
|
const {savings: lcpGraphSavings} = this.computeWasteWithGraph(
|
|
268
227
|
results,
|
|
269
|
-
|
|
228
|
+
optimisticLCPGraph,
|
|
270
229
|
simulator,
|
|
271
230
|
{providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'lcp'}
|
|
272
231
|
);
|
|
273
232
|
|
|
233
|
+
|
|
274
234
|
// The LCP graph can underestimate the LCP savings if there is potential savings on the LCP record itself.
|
|
275
235
|
let lcpRecordSavings = 0;
|
|
276
236
|
const lcpRecord = await LCPImageRecord.request(metricComputationInput, context);
|
|
@@ -25,12 +25,6 @@ declare class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
25
25
|
scriptUrl: string;
|
|
26
26
|
resourceSize: number;
|
|
27
27
|
}[]>>;
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
31
|
-
* @param {number} contentLength
|
|
32
|
-
*/
|
|
33
|
-
static _estimateTransferRatio(networkRecord: LH.Artifacts.NetworkRequest | undefined, contentLength: number): number;
|
|
34
28
|
/**
|
|
35
29
|
* This audit highlights JavaScript modules that appear to be duplicated across all resources,
|
|
36
30
|
* either within the same bundle or between different bundles. Each details item returned is
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
12
12
|
import {ModuleDuplication} from '../../computed/module-duplication.js';
|
|
13
13
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
|
-
import {
|
|
14
|
+
import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
17
17
|
/** Imperative title of a Lighthouse audit that tells the user to remove duplicate JavaScript from their code. This is displayed in a list of audit titles that Lighthouse generates. */
|
|
@@ -103,17 +103,6 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
103
103
|
return groupedDuplication;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/**
|
|
107
|
-
*
|
|
108
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
109
|
-
* @param {number} contentLength
|
|
110
|
-
*/
|
|
111
|
-
static _estimateTransferRatio(networkRecord, contentLength) {
|
|
112
|
-
const transferSize =
|
|
113
|
-
ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
|
|
114
|
-
return transferSize / contentLength;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
106
|
/**
|
|
118
107
|
* This audit highlights JavaScript modules that appear to be duplicated across all resources,
|
|
119
108
|
* either within the same bundle or between different bundles. Each details item returned is
|
|
@@ -132,7 +121,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
132
121
|
await DuplicatedJavascript._getDuplicationGroupedByNodeModules(artifacts, context);
|
|
133
122
|
|
|
134
123
|
/** @type {Map<string, number>} */
|
|
135
|
-
const
|
|
124
|
+
const compressionRatioByUrl = new Map();
|
|
136
125
|
|
|
137
126
|
/** @type {Item[]} */
|
|
138
127
|
const items = [];
|
|
@@ -145,10 +134,10 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
145
134
|
for (const [source, sourceDatas] of duplication.entries()) {
|
|
146
135
|
// One copy of this module is treated as the canonical version - the rest will have
|
|
147
136
|
// non-zero `wastedBytes`. In the case of all copies being the same version, all sizes are
|
|
148
|
-
// equal and the selection doesn't matter. When the copies are
|
|
149
|
-
// matter. Ideally the newest version would be the canonical
|
|
150
|
-
// is not present. Instead, size is used as a heuristic for
|
|
151
|
-
// audit conserative in its estimation.
|
|
137
|
+
// equal and the selection doesn't matter (ignoring compression ratios). When the copies are
|
|
138
|
+
// different versions, it does matter. Ideally the newest version would be the canonical
|
|
139
|
+
// copy, but version information is not present. Instead, size is used as a heuristic for
|
|
140
|
+
// latest version. This makes the audit conserative in its estimation.
|
|
152
141
|
|
|
153
142
|
/** @type {SubItem[]} */
|
|
154
143
|
const subItems = [];
|
|
@@ -159,27 +148,9 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
159
148
|
const scriptId = sourceData.scriptId;
|
|
160
149
|
const script = artifacts.Scripts.find(script => script.scriptId === scriptId);
|
|
161
150
|
const url = script?.url || '';
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (transferRatio === undefined) {
|
|
166
|
-
if (!script || script.length === undefined) {
|
|
167
|
-
// This should never happen because we found the wasted bytes from bundles, which required contents in a Script.
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const contentLength = script.length;
|
|
172
|
-
const networkRecord = getRequestForScript(networkRecords, script);
|
|
173
|
-
transferRatio = DuplicatedJavascript._estimateTransferRatio(networkRecord, contentLength);
|
|
174
|
-
transferRatioByUrl.set(url, transferRatio);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (transferRatio === undefined) {
|
|
178
|
-
// Shouldn't happen for above reasons.
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const transferSize = Math.round(sourceData.resourceSize * transferRatio);
|
|
151
|
+
const compressionRatio = estimateCompressionRatioForContent(
|
|
152
|
+
compressionRatioByUrl, url, artifacts, networkRecords);
|
|
153
|
+
const transferSize = Math.round(sourceData.resourceSize * compressionRatio);
|
|
183
154
|
|
|
184
155
|
subItems.push({
|
|
185
156
|
url,
|
|
@@ -60,17 +60,6 @@ declare class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
60
60
|
* @return {number}
|
|
61
61
|
*/
|
|
62
62
|
static estimateWastedBytes(matches: PatternMatchResult[]): number;
|
|
63
|
-
/**
|
|
64
|
-
* Utility function to estimate transfer size and cache calculation.
|
|
65
|
-
*
|
|
66
|
-
* Note: duplicated-javascript does this exact thing. In the future, consider
|
|
67
|
-
* making a generic estimator on ByteEfficienyAudit.
|
|
68
|
-
* @param {Map<string, number>} transferRatioByUrl
|
|
69
|
-
* @param {string} url
|
|
70
|
-
* @param {LH.Artifacts} artifacts
|
|
71
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
72
|
-
*/
|
|
73
|
-
static estimateTransferRatioForScript(transferRatioByUrl: Map<string, number>, url: string, artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>): Promise<number>;
|
|
74
63
|
/**
|
|
75
64
|
* @param {LH.Artifacts} artifacts
|
|
76
65
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -24,7 +24,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
|
24
24
|
import {EntityClassification} from '../../computed/entity-classification.js';
|
|
25
25
|
import {JSBundles} from '../../computed/js-bundles.js';
|
|
26
26
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
27
|
-
import {
|
|
27
|
+
import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
|
|
28
28
|
import {LH_ROOT} from '../../../shared/root.js';
|
|
29
29
|
|
|
30
30
|
const graphJson = fs.readFileSync(
|
|
@@ -390,37 +390,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
390
390
|
return estimatedWastedBytes;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/**
|
|
394
|
-
* Utility function to estimate transfer size and cache calculation.
|
|
395
|
-
*
|
|
396
|
-
* Note: duplicated-javascript does this exact thing. In the future, consider
|
|
397
|
-
* making a generic estimator on ByteEfficienyAudit.
|
|
398
|
-
* @param {Map<string, number>} transferRatioByUrl
|
|
399
|
-
* @param {string} url
|
|
400
|
-
* @param {LH.Artifacts} artifacts
|
|
401
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
402
|
-
*/
|
|
403
|
-
static async estimateTransferRatioForScript(transferRatioByUrl, url, artifacts, networkRecords) {
|
|
404
|
-
let transferRatio = transferRatioByUrl.get(url);
|
|
405
|
-
if (transferRatio !== undefined) return transferRatio;
|
|
406
|
-
|
|
407
|
-
const script = artifacts.Scripts.find(script => script.url === url);
|
|
408
|
-
|
|
409
|
-
if (!script || script.content === null) {
|
|
410
|
-
// Can't find content, so just use 1.
|
|
411
|
-
transferRatio = 1;
|
|
412
|
-
} else {
|
|
413
|
-
const networkRecord = getRequestForScript(networkRecords, script);
|
|
414
|
-
const contentLength = script.length || 0;
|
|
415
|
-
const transferSize =
|
|
416
|
-
ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
|
|
417
|
-
transferRatio = transferSize / contentLength;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
transferRatioByUrl.set(url, transferRatio);
|
|
421
|
-
return transferRatio;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
393
|
/**
|
|
425
394
|
* @param {LH.Artifacts} artifacts
|
|
426
395
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -443,14 +412,14 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
443
412
|
]);
|
|
444
413
|
|
|
445
414
|
/** @type {Map<string, number>} */
|
|
446
|
-
const
|
|
415
|
+
const compressionRatioByUrl = new Map();
|
|
447
416
|
|
|
448
417
|
const scriptToMatchResults =
|
|
449
418
|
this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
|
|
450
419
|
for (const [script, matches] of scriptToMatchResults.entries()) {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const wastedBytes = Math.round(this.estimateWastedBytes(matches) *
|
|
420
|
+
const compressionRatio = estimateCompressionRatioForContent(
|
|
421
|
+
compressionRatioByUrl, script.url, artifacts, networkRecords);
|
|
422
|
+
const wastedBytes = Math.round(this.estimateWastedBytes(matches) * compressionRatio);
|
|
454
423
|
/** @type {typeof items[number]} */
|
|
455
424
|
const item = {
|
|
456
425
|
url: script.url,
|
|
@@ -8,6 +8,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
|
8
8
|
import {UnusedCSS} from '../../computed/unused-css.js';
|
|
9
9
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
10
10
|
import {computeCSSTokenLength as computeTokenLength} from '../../lib/minification-estimator.js';
|
|
11
|
+
import {estimateTransferSize} from '../../lib/script-helpers.js';
|
|
11
12
|
|
|
12
13
|
const UIStrings = {
|
|
13
14
|
/** Imperative title of a Lighthouse audit that tells the user to minify (remove whitespace) the page's CSS code. This is displayed in a list of audit titles that Lighthouse generates. */
|
|
@@ -65,8 +66,7 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
|
|
|
65
66
|
url = contentPreview;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const totalBytes =
|
|
69
|
-
'Stylesheet');
|
|
69
|
+
const totalBytes = estimateTransferSize(networkRecord, content.length, 'Stylesheet');
|
|
70
70
|
const wastedRatio = 1 - totalTokenLength / content.length;
|
|
71
71
|
const wastedBytes = Math.round(totalBytes * wastedRatio);
|
|
72
72
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
8
8
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
9
9
|
import {computeJSTokenLength as computeTokenLength} from '../../lib/minification-estimator.js';
|
|
10
|
-
import {getRequestForScript, isInline} from '../../lib/script-helpers.js';
|
|
10
|
+
import {estimateCompressedContentSize, getRequestForScript, isInline} from '../../lib/script-helpers.js';
|
|
11
11
|
import {Util} from '../../../shared/util.js';
|
|
12
12
|
|
|
13
13
|
const UIStrings = {
|
|
@@ -58,8 +58,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|
|
58
58
|
const contentLength = scriptContent.length;
|
|
59
59
|
const totalTokenLength = computeTokenLength(scriptContent);
|
|
60
60
|
|
|
61
|
-
const totalBytes =
|
|
62
|
-
'Script');
|
|
61
|
+
const totalBytes = estimateCompressedContentSize(networkRecord, contentLength, 'Script');
|
|
63
62
|
const wastedRatio = 1 - totalTokenLength / contentLength;
|
|
64
63
|
const wastedBytes = Math.round(totalBytes * wastedRatio);
|
|
65
64
|
|
|
@@ -8,7 +8,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
|
8
8
|
import {UnusedJavascriptSummary} from '../../computed/unused-javascript-summary.js';
|
|
9
9
|
import {JSBundles} from '../../computed/js-bundles.js';
|
|
10
10
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
|
-
import {
|
|
11
|
+
import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
|
|
12
12
|
|
|
13
13
|
const UIStrings = {
|
|
14
14
|
/** 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. */
|
|
@@ -86,27 +86,26 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
|
86
86
|
bundleSourceUnusedThreshold = UNUSED_BYTES_IGNORE_BUNDLE_SOURCE_THRESHOLD,
|
|
87
87
|
} = context.options || {};
|
|
88
88
|
|
|
89
|
+
/** @type {Map<string, number>} */
|
|
90
|
+
const compressionRatioByUrl = new Map();
|
|
91
|
+
|
|
89
92
|
const items = [];
|
|
90
93
|
for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
|
|
91
94
|
const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
|
|
92
95
|
if (!script) continue; // This should never happen.
|
|
93
96
|
|
|
94
|
-
const networkRecord = getRequestForScript(networkRecords, script);
|
|
95
|
-
if (!networkRecord) continue;
|
|
96
|
-
|
|
97
97
|
const bundle = bundles.find(b => b.script.scriptId === scriptId);
|
|
98
98
|
const unusedJsSummary =
|
|
99
99
|
await UnusedJavascriptSummary.request({scriptId, scriptCoverage, bundle}, context);
|
|
100
100
|
if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
|
|
101
101
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const transferRatio = transfer / unusedJsSummary.totalBytes;
|
|
102
|
+
const compressionRatio = estimateCompressionRatioForContent(
|
|
103
|
+
compressionRatioByUrl, script.url, artifacts, networkRecords);
|
|
105
104
|
/** @type {LH.Audit.ByteEfficiencyItem} */
|
|
106
105
|
const item = {
|
|
107
106
|
url: script.url,
|
|
108
|
-
totalBytes: Math.round(
|
|
109
|
-
wastedBytes: Math.round(
|
|
107
|
+
totalBytes: Math.round(compressionRatio * unusedJsSummary.totalBytes),
|
|
108
|
+
wastedBytes: Math.round(compressionRatio * unusedJsSummary.wastedBytes),
|
|
110
109
|
wastedPercent: unusedJsSummary.wastedPercent,
|
|
111
110
|
};
|
|
112
111
|
|
|
@@ -127,8 +126,8 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
|
127
126
|
const total = source === '(unmapped)' ? sizes.unmappedBytes : sizes.files[source];
|
|
128
127
|
return {
|
|
129
128
|
source,
|
|
130
|
-
unused: Math.round(unused *
|
|
131
|
-
total: Math.round(total *
|
|
129
|
+
unused: Math.round(unused * compressionRatio),
|
|
130
|
+
total: Math.round(total * compressionRatio),
|
|
132
131
|
};
|
|
133
132
|
})
|
|
134
133
|
.filter(d => d.unused >= bundleSourceUnusedThreshold);
|
|
@@ -42,7 +42,7 @@ declare class UsesHTTP2Audit extends Audit {
|
|
|
42
42
|
* @param {LH.Artifacts.EntityClassification} classifiedEntities
|
|
43
43
|
* @return {boolean}
|
|
44
44
|
*/
|
|
45
|
-
static
|
|
45
|
+
static isMultiplexableStaticAsset(networkRequest: LH.Artifacts.NetworkRequest, classifiedEntities: LH.Artifacts.EntityClassification): boolean;
|
|
46
46
|
/**
|
|
47
47
|
* Determine the set of resources that aren't HTTP/2 but should be.
|
|
48
48
|
* We're a little conservative about what we surface for a few reasons:
|
|
@@ -152,16 +152,19 @@ class UsesHTTP2Audit extends Audit {
|
|
|
152
152
|
* @param {LH.Artifacts.EntityClassification} classifiedEntities
|
|
153
153
|
* @return {boolean}
|
|
154
154
|
*/
|
|
155
|
-
static
|
|
155
|
+
static isMultiplexableStaticAsset(networkRequest, classifiedEntities) {
|
|
156
156
|
if (!STATIC_RESOURCE_TYPES.has(networkRequest.resourceType)) return false;
|
|
157
157
|
|
|
158
158
|
// Resources from third-parties that are less than 100 bytes are usually tracking pixels, not actual resources.
|
|
159
159
|
// They can masquerade as static types though (gifs, documents, etc)
|
|
160
160
|
if (networkRequest.resourceSize < 100) {
|
|
161
|
-
// This logic needs to be revisited.
|
|
162
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/14661
|
|
163
161
|
const entity = classifiedEntities.entityByUrl.get(networkRequest.url);
|
|
164
|
-
if (entity
|
|
162
|
+
if (entity) {
|
|
163
|
+
// Third-party assets are multiplexable in their first-party context.
|
|
164
|
+
if (classifiedEntities.firstParty?.name === entity.name) return true;
|
|
165
|
+
// Skip recognizable third-parties' requests.
|
|
166
|
+
if (!entity.isUnrecognized) return false;
|
|
167
|
+
}
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
return true;
|
|
@@ -199,7 +202,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
199
202
|
/** @type {Map<string, Array<LH.Artifacts.NetworkRequest>>} */
|
|
200
203
|
const groupedByOrigin = new Map();
|
|
201
204
|
for (const record of networkRecords) {
|
|
202
|
-
if (!UsesHTTP2Audit.
|
|
205
|
+
if (!UsesHTTP2Audit.isMultiplexableStaticAsset(record, classifiedEntities)) continue;
|
|
203
206
|
if (UrlUtils.isLikeLocalhost(record.parsedURL.host)) continue;
|
|
204
207
|
const existing = groupedByOrigin.get(record.parsedURL.securityOrigin) || [];
|
|
205
208
|
existing.push(record);
|
|
@@ -9,12 +9,12 @@ import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../compute
|
|
|
9
9
|
import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
|
|
10
10
|
|
|
11
11
|
const UIStrings = {
|
|
12
|
-
/** Descriptive title of a diagnostic audit that provides
|
|
12
|
+
/** Descriptive title of a diagnostic audit that provides the top elements affected by layout shifts. */
|
|
13
13
|
title: 'Avoid large layout shifts',
|
|
14
|
-
/** Description of a diagnostic audit that provides
|
|
15
|
-
description: 'These DOM elements
|
|
16
|
-
/** Label for a column in a data table; entries in this column will be the amount that the corresponding element
|
|
17
|
-
columnContribution: '
|
|
14
|
+
/** Description of a diagnostic audit that provides the top elements affected by layout shifts. "windowing" means the metric value is calculated using the subset of events in a small window of time during the run. "normalization" is a good substitute for "windowing". The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
15
|
+
description: 'These DOM elements were most affected by layout shifts. Some layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)',
|
|
16
|
+
/** Label for a column in a data table; entries in this column will be the amount that the corresponding element affected by layout shifts. */
|
|
17
|
+
columnContribution: 'Layout shift impact',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -40,15 +40,32 @@ class LayoutShiftElements extends Audit {
|
|
|
40
40
|
* @return {Promise<LH.Audit.Product>}
|
|
41
41
|
*/
|
|
42
42
|
static async audit(artifacts, context) {
|
|
43
|
-
const
|
|
44
|
-
.
|
|
43
|
+
const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
|
|
44
|
+
await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
/** @type {Array<{node: LH.Audit.Details.ItemValue, score: number}>} */
|
|
47
|
+
const clsElementData = artifacts.TraceElements
|
|
48
|
+
.filter(element => element.traceEventType === 'layout-shift')
|
|
49
|
+
.map(element => ({
|
|
48
50
|
node: Audit.makeNodeItem(element.node),
|
|
49
|
-
score: element.
|
|
50
|
-
};
|
|
51
|
-
|
|
51
|
+
score: impactByNodeId.get(element.nodeId) || 0,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
if (clsElementData.length < impactByNodeId.size) {
|
|
55
|
+
const displayedNodeImpact = clsElementData.reduce((sum, {score}) => sum += score, 0);
|
|
56
|
+
|
|
57
|
+
// This is not necessarily the same as CLS due to normalization.
|
|
58
|
+
const totalNodeImpact = Array.from(impactByNodeId.values())
|
|
59
|
+
.reduce((sum, score) => sum + score);
|
|
60
|
+
|
|
61
|
+
clsElementData.push({
|
|
62
|
+
node: {
|
|
63
|
+
type: 'code',
|
|
64
|
+
value: str_(i18n.UIStrings.otherResourceType),
|
|
65
|
+
},
|
|
66
|
+
score: totalNodeImpact - displayedNodeImpact,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
52
69
|
|
|
53
70
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
54
71
|
const headings = [
|
|
@@ -58,15 +75,13 @@ class LayoutShiftElements extends Audit {
|
|
|
58
75
|
];
|
|
59
76
|
|
|
60
77
|
const details = Audit.makeTableDetails(headings, clsElementData);
|
|
78
|
+
|
|
61
79
|
let displayValue;
|
|
62
|
-
if (
|
|
80
|
+
if (impactByNodeId.size > 0) {
|
|
63
81
|
displayValue = str_(i18n.UIStrings.displayValueElementsFound,
|
|
64
|
-
{nodeCount:
|
|
82
|
+
{nodeCount: impactByNodeId.size});
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
const {cumulativeLayoutShift: clsSavings} =
|
|
68
|
-
await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
|
|
69
|
-
|
|
70
85
|
const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
|
|
71
86
|
|
|
72
87
|
return {
|
|
@@ -54,7 +54,11 @@ class CumulativeLayoutShift extends Audit {
|
|
|
54
54
|
*/
|
|
55
55
|
static async audit(artifacts, context) {
|
|
56
56
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
// impactByNodeId is unused but we don't want it on debug data
|
|
59
|
+
// eslint-disable-next-line no-unused-vars
|
|
60
|
+
const {cumulativeLayoutShift, impactByNodeId, ...rest} =
|
|
61
|
+
await ComputedCLS.request(trace, context);
|
|
58
62
|
|
|
59
63
|
/** @type {LH.Audit.Details.DebugData} */
|
|
60
64
|
const details = {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
export default MaxPotentialFID;
|
|
2
|
+
export type LoafDebugDetails = {
|
|
3
|
+
type: 'debugdata';
|
|
4
|
+
observedMaxDurationLoaf?: LH.TraceEvent | undefined;
|
|
5
|
+
observedMaxBlockingLoaf?: LH.TraceEvent | undefined;
|
|
6
|
+
observedLoafs: Array<{
|
|
7
|
+
startTime: number;
|
|
8
|
+
duration: number;
|
|
9
|
+
blockingDuration: number;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* @fileoverview This metric is the duration of the longest task after FCP. It is meant to capture
|
|
4
14
|
* the worst case First Input Delay that a user might experience.
|
|
@@ -9,6 +19,14 @@ declare class MaxPotentialFID extends Audit {
|
|
|
9
19
|
* @return {LH.Audit.ScoreOptions}
|
|
10
20
|
*/
|
|
11
21
|
static get defaultOptions(): import("../../../types/audit.js").default.ScoreOptions;
|
|
22
|
+
/**
|
|
23
|
+
* Extract potential LoAF replacements for MPFID from the trace to log in
|
|
24
|
+
* debugdata details.
|
|
25
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
26
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
27
|
+
* @return {LoafDebugDetails|undefined}
|
|
28
|
+
*/
|
|
29
|
+
static getLongAnimationFrameDetails(processedTrace: LH.Artifacts.ProcessedTrace, processedNavigation: LH.Artifacts.ProcessedNavigation): LoafDebugDetails | undefined;
|
|
12
30
|
/**
|
|
13
31
|
* @param {LH.Artifacts} artifacts
|
|
14
32
|
* @param {LH.Audit.Context} context
|
|
@@ -20,4 +38,6 @@ export namespace UIStrings {
|
|
|
20
38
|
const description: string;
|
|
21
39
|
}
|
|
22
40
|
import { Audit } from '../audit.js';
|
|
41
|
+
import { ProcessedTrace } from '../../computed/processed-trace.js';
|
|
42
|
+
import { ProcessedNavigation } from '../../computed/processed-navigation.js';
|
|
23
43
|
//# sourceMappingURL=max-potential-fid.d.ts.map
|