lighthouse 11.3.0-dev.20231205 → 11.3.0-dev.20231207
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/bf-cache.d.ts +1 -0
- package/core/audits/bf-cache.js +11 -1
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -24
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +0 -102
- 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 -13
- package/core/audits/byte-efficiency/legacy-javascript.js +2 -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/resource-summary.d.ts +11 -0
- package/core/audits/resource-summary.js +86 -0
- package/core/computed/unused-css.js +2 -2
- package/core/config/default-config.js +2 -0
- package/core/config/filters.js +1 -0
- package/core/gather/base-artifacts.js +2 -1
- package/core/lib/i18n/i18n.d.ts +2 -2
- package/core/lib/i18n/i18n.js +2 -2
- package/core/lib/network-request.js +4 -1
- package/core/lib/script-helpers.d.ts +34 -5
- package/core/lib/script-helpers.js +136 -0
- package/dist/report/bundle.esm.js +24 -9
- package/dist/report/flow.js +25 -10
- package/dist/report/standalone.js +25 -10
- package/package.json +1 -1
- package/report/assets/styles.css +20 -5
- package/report/assets/templates.html +1 -1
- package/report/clients/standalone.js +1 -0
- package/report/renderer/components.js +2 -2
- package/report/renderer/report-renderer.js +4 -0
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/en-US.json +3 -0
- package/shared/localization/locales/en-XL.json +3 -0
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +2 -0
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,7 +62,7 @@ class BFCache extends Audit {
|
|
|
60
62
|
description: str_(UIStrings.description),
|
|
61
63
|
supportedModes: ['navigation', 'timespan'],
|
|
62
64
|
guidanceLevel: 2,
|
|
63
|
-
requiredArtifacts: ['BFCacheFailures'],
|
|
65
|
+
requiredArtifacts: ['BFCacheFailures', 'HostProduct'],
|
|
64
66
|
scoreDisplayMode: Audit.SCORING_MODES.BINARY,
|
|
65
67
|
};
|
|
66
68
|
}
|
|
@@ -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
|
|
|
@@ -33,29 +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
|
-
/**
|
|
47
|
-
* Estimates the number of bytes the content of this network record would have consumed on the network based on the
|
|
48
|
-
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable,
|
|
49
|
-
* minus the size of the response headers.
|
|
50
|
-
*
|
|
51
|
-
* This differs from `estimateTransferSize` only in that is subtracts the response headers from the estimate.
|
|
52
|
-
*
|
|
53
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
54
|
-
* @param {number} totalBytes Uncompressed size of the resource
|
|
55
|
-
* @param {LH.Crdp.Network.ResourceType=} resourceType
|
|
56
|
-
* @return {number}
|
|
57
|
-
*/
|
|
58
|
-
static estimateCompressedContentSize(networkRecord: LH.Artifacts.NetworkRequest | undefined, totalBytes: number, resourceType?: LH.Crdp.Network.ResourceType | undefined): number;
|
|
59
36
|
/**
|
|
60
37
|
* @param {LH.Artifacts} artifacts
|
|
61
38
|
* @param {LH.Audit.Context} context
|
|
@@ -112,5 +89,4 @@ export class ByteEfficiencyAudit extends Audit {
|
|
|
112
89
|
static audit_(artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>, context: LH.Audit.Context): ByteEfficiencyProduct | Promise<ByteEfficiencyProduct>;
|
|
113
90
|
}
|
|
114
91
|
import { Audit } from '../audit.js';
|
|
115
|
-
import { NetworkRequest } from '../../lib/network-request.js';
|
|
116
92
|
//# sourceMappingURL=byte-efficiency-audit.d.ts.map
|
|
@@ -13,7 +13,6 @@ import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
|
|
|
13
13
|
import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
|
|
14
14
|
import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
|
|
15
15
|
import {LCPImageRecord} from '../../computed/lcp-image-record.js';
|
|
16
|
-
import {NetworkRequest} from '../../lib/network-request.js';
|
|
17
16
|
|
|
18
17
|
const str_ = i18n.createIcuMessageFn(import.meta.url, {});
|
|
19
18
|
|
|
@@ -59,107 +58,6 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
59
58
|
);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
/**
|
|
63
|
-
* Estimates the number of bytes this network record would have consumed on the network based on the
|
|
64
|
-
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
|
|
65
|
-
*
|
|
66
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
67
|
-
* @param {number} totalBytes Uncompressed size of the resource
|
|
68
|
-
* @param {LH.Crdp.Network.ResourceType=} resourceType
|
|
69
|
-
* @return {number}
|
|
70
|
-
*/
|
|
71
|
-
static estimateTransferSize(networkRecord, totalBytes, resourceType) {
|
|
72
|
-
if (!networkRecord) {
|
|
73
|
-
// We don't know how many bytes this asset used on the network, but we can guess it was
|
|
74
|
-
// roughly the size of the content gzipped.
|
|
75
|
-
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
|
|
76
|
-
// See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
|
|
77
|
-
switch (resourceType) {
|
|
78
|
-
case 'Stylesheet':
|
|
79
|
-
// Stylesheets tend to compress extremely well.
|
|
80
|
-
return Math.round(totalBytes * 0.2);
|
|
81
|
-
case 'Script':
|
|
82
|
-
case 'Document':
|
|
83
|
-
// Scripts and HTML compress fairly well too.
|
|
84
|
-
return Math.round(totalBytes * 0.33);
|
|
85
|
-
default:
|
|
86
|
-
// Otherwise we'll just fallback to the average savings in HTTPArchive
|
|
87
|
-
return Math.round(totalBytes * 0.5);
|
|
88
|
-
}
|
|
89
|
-
} else if (networkRecord.resourceType === resourceType) {
|
|
90
|
-
// This was a regular standalone asset, just use the transfer size.
|
|
91
|
-
return networkRecord.transferSize || 0;
|
|
92
|
-
} else {
|
|
93
|
-
// This was an asset that was inlined in a different resource type (e.g. HTML document).
|
|
94
|
-
// Use the compression ratio of the resource to estimate the total transferred bytes.
|
|
95
|
-
const transferSize = networkRecord.transferSize || 0;
|
|
96
|
-
const resourceSize = networkRecord.resourceSize || 0;
|
|
97
|
-
// Get the compression ratio, if it's an invalid number, assume no compression.
|
|
98
|
-
const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
|
|
99
|
-
(transferSize / resourceSize) : 1;
|
|
100
|
-
return Math.round(totalBytes * compressionRatio);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Estimates the number of bytes the content of this network record would have consumed on the network based on the
|
|
106
|
-
* uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable,
|
|
107
|
-
* minus the size of the response headers.
|
|
108
|
-
*
|
|
109
|
-
* This differs from `estimateTransferSize` only in that is subtracts the response headers from the estimate.
|
|
110
|
-
*
|
|
111
|
-
* @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
|
|
112
|
-
* @param {number} totalBytes Uncompressed size of the resource
|
|
113
|
-
* @param {LH.Crdp.Network.ResourceType=} resourceType
|
|
114
|
-
* @return {number}
|
|
115
|
-
*/
|
|
116
|
-
static estimateCompressedContentSize(networkRecord, totalBytes, resourceType) {
|
|
117
|
-
if (!networkRecord) {
|
|
118
|
-
// We don't know how many bytes this asset used on the network, but we can guess it was
|
|
119
|
-
// roughly the size of the content gzipped.
|
|
120
|
-
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
|
|
121
|
-
// See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
|
|
122
|
-
switch (resourceType) {
|
|
123
|
-
case 'Stylesheet':
|
|
124
|
-
// Stylesheets tend to compress extremely well.
|
|
125
|
-
return Math.round(totalBytes * 0.2);
|
|
126
|
-
case 'Script':
|
|
127
|
-
case 'Document':
|
|
128
|
-
// Scripts and HTML compress fairly well too.
|
|
129
|
-
return Math.round(totalBytes * 0.33);
|
|
130
|
-
default:
|
|
131
|
-
// Otherwise we'll just fallback to the average savings in HTTPArchive
|
|
132
|
-
return Math.round(totalBytes * 0.5);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Get the size of the response body on the network.
|
|
137
|
-
let contentTransferSize = networkRecord.transferSize || 0;
|
|
138
|
-
if (!NetworkRequest.isContentEncoded(networkRecord)) {
|
|
139
|
-
// This is not encoded, so we can use resourceSize directly.
|
|
140
|
-
// This would be equivalent to transfer size minus headers transfer size, but transfer size
|
|
141
|
-
// may also include bytes for SSL connection etc.
|
|
142
|
-
contentTransferSize = networkRecord.resourceSize;
|
|
143
|
-
} else if (networkRecord.responseHeadersTransferSize) {
|
|
144
|
-
// Subtract the size of the encoded headers.
|
|
145
|
-
contentTransferSize =
|
|
146
|
-
Math.max(0, contentTransferSize - networkRecord.responseHeadersTransferSize);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (networkRecord.resourceType === resourceType) {
|
|
150
|
-
// This was a regular standalone asset, just use the transfer size.
|
|
151
|
-
return contentTransferSize;
|
|
152
|
-
} else {
|
|
153
|
-
// This was an asset that was inlined in a different resource type (e.g. HTML document).
|
|
154
|
-
// Use the compression ratio of the resource to estimate the total transferred bytes.
|
|
155
|
-
const resourceSize = networkRecord.resourceSize || 0;
|
|
156
|
-
// Get the compression ratio, if it's an invalid number, assume no compression.
|
|
157
|
-
const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
|
|
158
|
-
(contentTransferSize / resourceSize) : 1;
|
|
159
|
-
return Math.round(totalBytes * compressionRatio);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
61
|
/**
|
|
164
62
|
* @param {LH.Artifacts} artifacts
|
|
165
63
|
* @param {LH.Audit.Context} 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,19 +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 the ratio of the compression on the resource.
|
|
65
|
-
* This excludes the size of the response headers.
|
|
66
|
-
* Also caches the calculation.
|
|
67
|
-
*
|
|
68
|
-
* Note: duplicated-javascript does this exact thing. In the future, consider
|
|
69
|
-
* making a generic estimator on ByteEfficiencyAudit.
|
|
70
|
-
* @param {Map<string, number>} compressionRatioByUrl
|
|
71
|
-
* @param {string} url
|
|
72
|
-
* @param {LH.Artifacts} artifacts
|
|
73
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
74
|
-
*/
|
|
75
|
-
static estimateCompressionRatioForContent(compressionRatioByUrl: Map<string, number>, url: string, artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>): Promise<number>;
|
|
76
63
|
/**
|
|
77
64
|
* @param {LH.Artifacts} artifacts
|
|
78
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,40 +390,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
390
390
|
return estimatedWastedBytes;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/**
|
|
394
|
-
* Utility function to estimate the ratio of the compression on the resource.
|
|
395
|
-
* This excludes the size of the response headers.
|
|
396
|
-
* Also caches the calculation.
|
|
397
|
-
*
|
|
398
|
-
* Note: duplicated-javascript does this exact thing. In the future, consider
|
|
399
|
-
* making a generic estimator on ByteEfficiencyAudit.
|
|
400
|
-
* @param {Map<string, number>} compressionRatioByUrl
|
|
401
|
-
* @param {string} url
|
|
402
|
-
* @param {LH.Artifacts} artifacts
|
|
403
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
404
|
-
*/
|
|
405
|
-
static async estimateCompressionRatioForContent(compressionRatioByUrl, url,
|
|
406
|
-
artifacts, networkRecords) {
|
|
407
|
-
let compressionRatio = compressionRatioByUrl.get(url);
|
|
408
|
-
if (compressionRatio !== undefined) return compressionRatio;
|
|
409
|
-
|
|
410
|
-
const script = artifacts.Scripts.find(script => script.url === url);
|
|
411
|
-
|
|
412
|
-
if (!script) {
|
|
413
|
-
// Can't find content, so just use 1.
|
|
414
|
-
compressionRatio = 1;
|
|
415
|
-
} else {
|
|
416
|
-
const networkRecord = getRequestForScript(networkRecords, script);
|
|
417
|
-
const contentLength = networkRecord?.resourceSize || script.length || 0;
|
|
418
|
-
const compressedSize =
|
|
419
|
-
ByteEfficiencyAudit.estimateCompressedContentSize(networkRecord, contentLength, 'Script');
|
|
420
|
-
compressionRatio = compressedSize / contentLength;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
compressionRatioByUrl.set(url, compressionRatio);
|
|
424
|
-
return compressionRatio;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
393
|
/**
|
|
428
394
|
* @param {LH.Artifacts} artifacts
|
|
429
395
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -451,7 +417,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
451
417
|
const scriptToMatchResults =
|
|
452
418
|
this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
|
|
453
419
|
for (const [script, matches] of scriptToMatchResults.entries()) {
|
|
454
|
-
const compressionRatio =
|
|
420
|
+
const compressionRatio = estimateCompressionRatioForContent(
|
|
455
421
|
compressionRatioByUrl, script.url, artifacts, networkRecords);
|
|
456
422
|
const wastedBytes = Math.round(this.estimateWastedBytes(matches) * compressionRatio);
|
|
457
423
|
/** @type {typeof items[number]} */
|
|
@@ -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);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default ResourceSummary;
|
|
2
|
+
declare class ResourceSummary extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from './audit.js';
|
|
11
|
+
//# sourceMappingURL=resource-summary.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Audit} from './audit.js';
|
|
8
|
+
import {ResourceSummary as ComputedResourceSummary} from '../computed/resource-summary.js';
|
|
9
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
+
|
|
11
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url);
|
|
12
|
+
|
|
13
|
+
class ResourceSummary extends Audit {
|
|
14
|
+
/**
|
|
15
|
+
* @return {LH.Audit.Meta}
|
|
16
|
+
*/
|
|
17
|
+
static get meta() {
|
|
18
|
+
return {
|
|
19
|
+
id: 'resource-summary',
|
|
20
|
+
title: 'Resources Summary',
|
|
21
|
+
description: 'Aggregates all network requests and groups them by type',
|
|
22
|
+
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
23
|
+
requiredArtifacts: ['devtoolsLogs', 'URL'],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {LH.Artifacts} artifacts
|
|
29
|
+
* @param {LH.Audit.Context} context
|
|
30
|
+
* @return {Promise<LH.Audit.Product>}
|
|
31
|
+
*/
|
|
32
|
+
static async audit(artifacts, context) {
|
|
33
|
+
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
34
|
+
const summary = await ComputedResourceSummary
|
|
35
|
+
.request({devtoolsLog, URL: artifacts.URL, budgets: context.settings.budgets}, context);
|
|
36
|
+
|
|
37
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
38
|
+
const headings = [
|
|
39
|
+
{key: 'label', valueType: 'text', label: str_(i18n.UIStrings.columnResourceType)},
|
|
40
|
+
{key: 'requestCount', valueType: 'numeric', label: str_(i18n.UIStrings.columnRequests)},
|
|
41
|
+
{key: 'transferSize', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/** @type {Record<LH.Budget.ResourceType, LH.IcuMessage>} */
|
|
46
|
+
const strMappings = {
|
|
47
|
+
'total': str_(i18n.UIStrings.totalResourceType),
|
|
48
|
+
'document': str_(i18n.UIStrings.documentResourceType),
|
|
49
|
+
'script': str_(i18n.UIStrings.scriptResourceType),
|
|
50
|
+
'stylesheet': str_(i18n.UIStrings.stylesheetResourceType),
|
|
51
|
+
'image': str_(i18n.UIStrings.imageResourceType),
|
|
52
|
+
'media': str_(i18n.UIStrings.mediaResourceType),
|
|
53
|
+
'font': str_(i18n.UIStrings.fontResourceType),
|
|
54
|
+
'other': str_(i18n.UIStrings.otherResourceType),
|
|
55
|
+
'third-party': str_(i18n.UIStrings.thirdPartyResourceType),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const types = /** @type {Array<LH.Budget.ResourceType>} */ (Object.keys(summary));
|
|
59
|
+
const rows = types.map(type => {
|
|
60
|
+
return {
|
|
61
|
+
// ResourceType is included as an "id" for ease of use.
|
|
62
|
+
// It does not appear directly in the table.
|
|
63
|
+
resourceType: type,
|
|
64
|
+
label: strMappings[type],
|
|
65
|
+
requestCount: summary[type].count,
|
|
66
|
+
transferSize: summary[type].transferSize,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// Force third-party to be last, descending by size otherwise
|
|
70
|
+
const thirdPartyRow = rows.find(r => r.resourceType === 'third-party') || [];
|
|
71
|
+
const otherRows = rows.filter(r => r.resourceType !== 'third-party')
|
|
72
|
+
.sort((a, b) => {
|
|
73
|
+
return b.transferSize - a.transferSize;
|
|
74
|
+
});
|
|
75
|
+
const tableItems = otherRows.concat(thirdPartyRow);
|
|
76
|
+
|
|
77
|
+
const tableDetails = Audit.makeTableDetails(headings, tableItems);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
details: tableDetails,
|
|
81
|
+
score: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default ResourceSummary;
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
|
-
import {ByteEfficiencyAudit} from '../audits/byte-efficiency/byte-efficiency-audit.js';
|
|
9
8
|
import {NetworkRecords} from './network-records.js';
|
|
10
9
|
import {Util} from '../../shared/util.js';
|
|
10
|
+
import {estimateTransferSize} from '../lib/script-helpers.js';
|
|
11
11
|
|
|
12
12
|
const PREVIEW_LENGTH = 100;
|
|
13
13
|
|
|
@@ -70,7 +70,7 @@ class UnusedCSS {
|
|
|
70
70
|
usedUncompressedBytes += usedRule.endOffset - usedRule.startOffset;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const totalTransferredBytes =
|
|
73
|
+
const totalTransferredBytes = estimateTransferSize(
|
|
74
74
|
stylesheetInfo.networkRecord, totalUncompressedBytes, 'Stylesheet');
|
|
75
75
|
const percentUnused = (totalUncompressedBytes - usedUncompressedBytes) / totalUncompressedBytes;
|
|
76
76
|
const wastedBytes = Math.round(percentUnused * totalTransferredBytes);
|
|
@@ -215,6 +215,7 @@ const defaultConfig = {
|
|
|
215
215
|
'metrics',
|
|
216
216
|
'performance-budget',
|
|
217
217
|
'timing-budget',
|
|
218
|
+
'resource-summary',
|
|
218
219
|
'third-party-summary',
|
|
219
220
|
'third-party-facades',
|
|
220
221
|
'largest-contentful-paint-element',
|
|
@@ -499,6 +500,7 @@ const defaultConfig = {
|
|
|
499
500
|
{id: 'screenshot-thumbnails', weight: 0, group: 'hidden'},
|
|
500
501
|
{id: 'final-screenshot', weight: 0, group: 'hidden'},
|
|
501
502
|
{id: 'script-treemap-data', weight: 0, group: 'hidden'},
|
|
503
|
+
{id: 'resource-summary', weight: 0, group: 'hidden'},
|
|
502
504
|
],
|
|
503
505
|
},
|
|
504
506
|
'accessibility': {
|
package/core/config/filters.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
*/
|
|
20
20
|
async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
21
21
|
const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
|
|
22
|
-
const {userAgent} = await getBrowserVersion(driver.defaultSession);
|
|
22
|
+
const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
|
|
23
23
|
|
|
24
24
|
return {
|
|
25
25
|
// Meta artifacts.
|
|
@@ -32,6 +32,7 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
|
32
32
|
HostUserAgent: userAgent,
|
|
33
33
|
HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
|
|
34
34
|
'mobile' : 'desktop',
|
|
35
|
+
HostProduct: product,
|
|
35
36
|
// Contextual artifacts whose collection changes based on gather mode.
|
|
36
37
|
URL: {
|
|
37
38
|
finalDisplayedUrl: '',
|
package/core/lib/i18n/i18n.d.ts
CHANGED
|
@@ -69,9 +69,9 @@ export function lookupLocale(locales?: (string | string[]) | undefined, possible
|
|
|
69
69
|
* Returns a function that generates `LH.IcuMessage` objects to localize the
|
|
70
70
|
* messages in `fileStrings` and the shared `i18n.UIStrings`.
|
|
71
71
|
* @param {string} filename
|
|
72
|
-
* @param {Record<string, string
|
|
72
|
+
* @param {Record<string, string>=} fileStrings
|
|
73
73
|
*/
|
|
74
|
-
export function createIcuMessageFn(filename: string, fileStrings
|
|
74
|
+
export function createIcuMessageFn(filename: string, fileStrings?: Record<string, string> | undefined): (message: string, values?: Record<string, string | number> | undefined) => LH.IcuMessage;
|
|
75
75
|
/**
|
|
76
76
|
* Returns true if the given value is a string or an LH.IcuMessage.
|
|
77
77
|
* @param {unknown} value
|
package/core/lib/i18n/i18n.js
CHANGED
|
@@ -168,9 +168,9 @@ function lookupLocale(locales, possibleLocales) {
|
|
|
168
168
|
* Returns a function that generates `LH.IcuMessage` objects to localize the
|
|
169
169
|
* messages in `fileStrings` and the shared `i18n.UIStrings`.
|
|
170
170
|
* @param {string} filename
|
|
171
|
-
* @param {Record<string, string
|
|
171
|
+
* @param {Record<string, string>=} fileStrings
|
|
172
172
|
*/
|
|
173
|
-
function createIcuMessageFn(filename, fileStrings) {
|
|
173
|
+
function createIcuMessageFn(filename, fileStrings = {}) {
|
|
174
174
|
if (filename.startsWith('file://')) filename = url.fileURLToPath(filename);
|
|
175
175
|
|
|
176
176
|
// In the common case, `filename` is an absolute path that needs to be transformed
|