lighthouse 12.8.2-dev.20251005 → 12.8.2-dev.20251007

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.
Files changed (142) hide show
  1. package/cli/test/smokehouse/config/exclusions.js +0 -2
  2. package/core/audits/audit.js +0 -1
  3. package/core/audits/insights/cls-culprits-insight.js +1 -1
  4. package/core/audits/insights/dom-size-insight.js +11 -7
  5. package/core/audits/insights/insight-audit.d.ts +4 -2
  6. package/core/audits/insights/insight-audit.js +22 -3
  7. package/core/audits/predictive-perf.js +2 -2
  8. package/core/audits/seo/crawlable-anchors.js +2 -3
  9. package/core/audits/server-response-time.d.ts +0 -5
  10. package/core/audits/server-response-time.js +12 -26
  11. package/core/computed/metrics/lcp-breakdown.d.ts +10 -5
  12. package/core/computed/metrics/lcp-breakdown.js +50 -22
  13. package/core/computed/metrics/time-to-first-byte.js +33 -10
  14. package/core/computed/metrics/timing-summary.js +3 -2
  15. package/core/config/default-config.js +20 -63
  16. package/core/config/experimental-config.js +1 -26
  17. package/core/config/filters.js +6 -9
  18. package/core/config/lr-desktop-config.js +0 -1
  19. package/core/config/lr-mobile-config.js +0 -1
  20. package/core/gather/gatherers/anchor-elements.js +8 -24
  21. package/core/gather/gatherers/inspector-issues.js +1 -28
  22. package/core/gather/gatherers/trace-elements.d.ts +0 -9
  23. package/core/gather/gatherers/trace-elements.js +0 -35
  24. package/core/lib/network-request.d.ts +0 -7
  25. package/core/lib/network-request.js +0 -16
  26. package/core/lib/proto-preprocessor.js +5 -22
  27. package/dist/report/bundle.esm.js +10 -49
  28. package/dist/report/flow.js +12 -51
  29. package/dist/report/standalone.js +11 -50
  30. package/flow-report/src/i18n/i18n.d.ts +4 -6
  31. package/package.json +4 -5
  32. package/report/assets/styles.css +0 -39
  33. package/report/renderer/api.js +0 -1
  34. package/report/renderer/category-renderer.js +6 -0
  35. package/report/renderer/components.js +1 -1
  36. package/report/renderer/dom.d.ts +0 -13
  37. package/report/renderer/dom.js +0 -38
  38. package/report/renderer/performance-category-renderer.d.ts +0 -26
  39. package/report/renderer/performance-category-renderer.js +10 -142
  40. package/report/renderer/report-ui-features.d.ts +0 -1
  41. package/report/renderer/report-ui-features.js +3 -13
  42. package/report/renderer/report-utils.d.ts +2 -3
  43. package/report/renderer/report-utils.js +4 -6
  44. package/report/types/report-renderer.d.ts +0 -6
  45. package/shared/localization/locales/ar-XB.json +20 -341
  46. package/shared/localization/locales/ar.json +20 -341
  47. package/shared/localization/locales/bg.json +9 -330
  48. package/shared/localization/locales/ca.json +9 -330
  49. package/shared/localization/locales/cs.json +9 -330
  50. package/shared/localization/locales/da.json +9 -330
  51. package/shared/localization/locales/de.json +9 -330
  52. package/shared/localization/locales/el.json +9 -330
  53. package/shared/localization/locales/en-GB.json +9 -330
  54. package/shared/localization/locales/en-US.json +44 -293
  55. package/shared/localization/locales/en-XA.json +0 -330
  56. package/shared/localization/locales/en-XL.json +44 -293
  57. package/shared/localization/locales/es-419.json +9 -330
  58. package/shared/localization/locales/es.json +9 -330
  59. package/shared/localization/locales/fi.json +9 -330
  60. package/shared/localization/locales/fil.json +9 -330
  61. package/shared/localization/locales/fr.json +9 -330
  62. package/shared/localization/locales/he.json +31 -352
  63. package/shared/localization/locales/hi.json +9 -330
  64. package/shared/localization/locales/hr.json +9 -330
  65. package/shared/localization/locales/hu.json +9 -330
  66. package/shared/localization/locales/id.json +9 -330
  67. package/shared/localization/locales/it.json +9 -330
  68. package/shared/localization/locales/ja.json +9 -330
  69. package/shared/localization/locales/ko.json +10 -331
  70. package/shared/localization/locales/lt.json +9 -330
  71. package/shared/localization/locales/lv.json +10 -331
  72. package/shared/localization/locales/nl.json +9 -330
  73. package/shared/localization/locales/no.json +9 -330
  74. package/shared/localization/locales/pl.json +9 -330
  75. package/shared/localization/locales/pt-PT.json +9 -330
  76. package/shared/localization/locales/pt.json +9 -330
  77. package/shared/localization/locales/ro.json +10 -331
  78. package/shared/localization/locales/ru.json +9 -330
  79. package/shared/localization/locales/sk.json +9 -330
  80. package/shared/localization/locales/sl.json +9 -330
  81. package/shared/localization/locales/sr-Latn.json +9 -330
  82. package/shared/localization/locales/sr.json +9 -330
  83. package/shared/localization/locales/sv.json +9 -330
  84. package/shared/localization/locales/ta.json +9 -330
  85. package/shared/localization/locales/te.json +10 -331
  86. package/shared/localization/locales/th.json +9 -330
  87. package/shared/localization/locales/tr.json +9 -330
  88. package/shared/localization/locales/uk.json +9 -330
  89. package/shared/localization/locales/vi.json +9 -330
  90. package/shared/localization/locales/zh-HK.json +9 -330
  91. package/shared/localization/locales/zh-TW.json +10 -331
  92. package/shared/localization/locales/zh.json +9 -330
  93. package/types/artifacts.d.ts +5 -6
  94. package/types/audit.d.ts +1 -1
  95. package/types/lhr/settings.d.ts +1 -1
  96. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -45
  97. package/core/audits/byte-efficiency/duplicated-javascript.js +0 -223
  98. package/core/audits/byte-efficiency/efficient-animated-content.d.ts +0 -22
  99. package/core/audits/byte-efficiency/efficient-animated-content.js +0 -93
  100. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -28
  101. package/core/audits/byte-efficiency/legacy-javascript.js +0 -144
  102. package/core/audits/byte-efficiency/modern-image-formats.d.ts +0 -38
  103. package/core/audits/byte-efficiency/modern-image-formats.js +0 -187
  104. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +0 -53
  105. package/core/audits/byte-efficiency/render-blocking-resources.js +0 -312
  106. package/core/audits/byte-efficiency/uses-long-cache-ttl.d.ts +0 -59
  107. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +0 -293
  108. package/core/audits/byte-efficiency/uses-optimized-images.d.ts +0 -33
  109. package/core/audits/byte-efficiency/uses-optimized-images.js +0 -146
  110. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.d.ts +0 -16
  111. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +0 -106
  112. package/core/audits/byte-efficiency/uses-responsive-images.d.ts +0 -44
  113. package/core/audits/byte-efficiency/uses-responsive-images.js +0 -202
  114. package/core/audits/byte-efficiency/uses-text-compression.d.ts +0 -14
  115. package/core/audits/byte-efficiency/uses-text-compression.js +0 -108
  116. package/core/audits/critical-request-chains.d.ts +0 -44
  117. package/core/audits/critical-request-chains.js +0 -221
  118. package/core/audits/dobetterweb/dom-size.d.ts +0 -32
  119. package/core/audits/dobetterweb/dom-size.js +0 -182
  120. package/core/audits/dobetterweb/uses-http2.d.ts +0 -72
  121. package/core/audits/dobetterweb/uses-http2.js +0 -276
  122. package/core/audits/font-display.d.ts +0 -32
  123. package/core/audits/font-display.js +0 -195
  124. package/core/audits/largest-contentful-paint-element.d.ts +0 -34
  125. package/core/audits/largest-contentful-paint-element.js +0 -181
  126. package/core/audits/lcp-lazy-loaded.d.ts +0 -22
  127. package/core/audits/lcp-lazy-loaded.js +0 -115
  128. package/core/audits/prioritize-lcp-image.d.ts +0 -74
  129. package/core/audits/prioritize-lcp-image.js +0 -297
  130. package/core/audits/third-party-summary.d.ts +0 -78
  131. package/core/audits/third-party-summary.js +0 -236
  132. package/core/audits/uses-rel-preconnect.d.ts +0 -37
  133. package/core/audits/uses-rel-preconnect.js +0 -286
  134. package/core/audits/viewport.d.ts +0 -17
  135. package/core/audits/viewport.js +0 -87
  136. package/core/audits/work-during-interaction.d.ts +0 -81
  137. package/core/audits/work-during-interaction.js +0 -287
  138. package/core/computed/critical-request-chains.d.ts +0 -42
  139. package/core/computed/critical-request-chains.js +0 -143
  140. package/core/computed/viewport-meta.d.ts +0 -37
  141. package/core/computed/viewport-meta.js +0 -71
  142. package/types/internal/metaviewport-parser.d.ts +0 -13
@@ -1,293 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2017 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import parseCacheControl from 'parse-cache-control';
8
-
9
- import {Audit} from '../audit.js';
10
- import {NetworkRequest} from '../../lib/network-request.js';
11
- import UrlUtils from '../../lib/url-utils.js';
12
- import {linearInterpolation} from '../../../shared/statistics.js';
13
- import * as i18n from '../../lib/i18n/i18n.js';
14
- import {NetworkRecords} from '../../computed/network-records.js';
15
-
16
- const UIStrings = {
17
- /** Title of a diagnostic audit that provides detail on the cache policy applies to the page's static assets. Cache refers to browser disk cache, which keeps old versions of network resources around for future use. This is displayed in a list of audit titles that Lighthouse generates. */
18
- title: 'Uses efficient cache policy on static assets',
19
- /** Title of a diagnostic audit that provides details on the any page resources that could have been served with more efficient cache policies. Cache refers to browser disk cache, which keeps old versions of network resources around for future use. This imperative title is shown to users when there is a significant amount of assets served with poor cache policies. */
20
- failureTitle: 'Serve static assets with an efficient cache policy',
21
- /** Description of a Lighthouse audit that tells the user *why* they need to adopt a long cache lifetime policy. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
22
- description:
23
- 'A long cache lifetime can speed up repeat visits to your page. ' +
24
- '[Learn more about efficient cache policies](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/).',
25
- /** [ICU Syntax] Label for the audit identifying network resources with inefficient cache values. Clicking this will expand the audit to show the resources. */
26
- displayValue: `{itemCount, plural,
27
- =1 {1 resource found}
28
- other {# resources found}
29
- }`,
30
- };
31
-
32
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
33
-
34
- // Ignore assets that have very high likelihood of cache hit
35
- const IGNORE_THRESHOLD_IN_PERCENT = 0.925;
36
-
37
- class CacheHeaders extends Audit {
38
- /**
39
- * @return {LH.Audit.Meta}
40
- */
41
- static get meta() {
42
- return {
43
- id: 'uses-long-cache-ttl',
44
- title: str_(UIStrings.title),
45
- failureTitle: str_(UIStrings.failureTitle),
46
- description: str_(UIStrings.description),
47
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
48
- guidanceLevel: 3,
49
- requiredArtifacts: ['DevtoolsLog', 'SourceMaps'],
50
- };
51
- }
52
-
53
- /**
54
- * @return {LH.Audit.ScoreOptions}
55
- */
56
- static get defaultOptions() {
57
- return {
58
- // 50th and 25th percentiles HTTPArchive -> 50 and 75, with p10 derived from them.
59
- // https://bigquery.cloud.google.com/table/httparchive:lighthouse.2018_04_01_mobile?pli=1
60
- // see https://www.desmos.com/calculator/uzsyl2hbcb
61
- p10: 28 * 1024,
62
- median: 128 * 1024,
63
- };
64
- }
65
-
66
- /**
67
- * Computes the percent likelihood that a return visit will be within the cache lifetime, based on
68
- * Chrome UMA stats see the note below.
69
- * @param {number} maxAgeInSeconds
70
- * @return {number}
71
- */
72
- static getCacheHitProbability(maxAgeInSeconds) {
73
- // This array contains the hand wavy distribution of the age of a resource in hours at the time of
74
- // cache hit at 0th, 10th, 20th, 30th, etc percentiles. This is used to compute `wastedMs` since there
75
- // are clearly diminishing returns to cache duration i.e. 6 months is not 2x better than 3 months.
76
- // Based on UMA stats for HttpCache.StaleEntry.Validated.Age, see https://www.desmos.com/calculator/7v0qh1nzvh
77
- // Example: a max-age of 12 hours already covers ~50% of cases, doubling to 24 hours covers ~10% more.
78
- const RESOURCE_AGE_IN_HOURS_DECILES = [0, 0.2, 1, 3, 8, 12, 24, 48, 72, 168, 8760, Infinity];
79
- if (RESOURCE_AGE_IN_HOURS_DECILES.length !== 12) {
80
- throw new Error('deciles 0-10 and 1 for overflow');
81
- }
82
-
83
- const maxAgeInHours = maxAgeInSeconds / 3600;
84
- const upperDecileIndex = RESOURCE_AGE_IN_HOURS_DECILES.findIndex(
85
- decile => decile >= maxAgeInHours
86
- );
87
-
88
- // Clip the likelihood between 0 and 1
89
- if (upperDecileIndex === RESOURCE_AGE_IN_HOURS_DECILES.length - 1) return 1;
90
- if (upperDecileIndex === 0) return 0;
91
-
92
- // Use the two closest decile points as control points
93
- const upperDecileValue = RESOURCE_AGE_IN_HOURS_DECILES[upperDecileIndex];
94
- const lowerDecileValue = RESOURCE_AGE_IN_HOURS_DECILES[upperDecileIndex - 1];
95
- const upperDecile = upperDecileIndex / 10;
96
- const lowerDecile = (upperDecileIndex - 1) / 10;
97
-
98
- // Approximate the real likelihood with linear interpolation
99
- return linearInterpolation(
100
- lowerDecileValue,
101
- lowerDecile,
102
- upperDecileValue,
103
- upperDecile,
104
- maxAgeInHours
105
- );
106
- }
107
-
108
- /**
109
- * Return max-age if defined, otherwise expires header if defined, and null if not.
110
- * @param {Map<string, string>} headers
111
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
112
- * @return {?number}
113
- */
114
- static computeCacheLifetimeInSeconds(headers, cacheControl) {
115
- if (cacheControl && cacheControl['max-age'] !== undefined) {
116
- return cacheControl['max-age'];
117
- }
118
-
119
- const expiresHeaders = headers.get('expires');
120
- if (expiresHeaders) {
121
- const expires = new Date(expiresHeaders).getTime();
122
- // Invalid expires values MUST be treated as already expired
123
- if (!expires) return 0;
124
- return Math.ceil((expires - Date.now()) / 1000);
125
- }
126
-
127
- return null;
128
- }
129
-
130
- /**
131
- * Given a network record, returns whether we believe the asset is cacheable, i.e. it was a network
132
- * request that satisifed the conditions:
133
- *
134
- * 1. Has a cacheable status code
135
- * 2. Has a resource type that corresponds to static assets (image, script, stylesheet, etc).
136
- *
137
- * Allowing assets with a query string is debatable, PSI considered them non-cacheable with a similar
138
- * caveat.
139
- *
140
- * TODO: Investigate impact in HTTPArchive, experiment with this policy to see what changes.
141
- *
142
- * @param {LH.Artifacts.NetworkRequest} record
143
- * @return {boolean}
144
- */
145
- static isCacheableAsset(record) {
146
- const CACHEABLE_STATUS_CODES = new Set([200, 203, 206]);
147
-
148
- /** @type {Set<LH.Crdp.Network.ResourceType>} */
149
- const STATIC_RESOURCE_TYPES = new Set([
150
- NetworkRequest.TYPES.Font,
151
- NetworkRequest.TYPES.Image,
152
- NetworkRequest.TYPES.Media,
153
- NetworkRequest.TYPES.Script,
154
- NetworkRequest.TYPES.Stylesheet,
155
- ]);
156
-
157
- // It's not a request loaded over the network, caching makes no sense
158
- if (NetworkRequest.isNonNetworkRequest(record)) return false;
159
-
160
-
161
- return (
162
- CACHEABLE_STATUS_CODES.has(record.statusCode) &&
163
- STATIC_RESOURCE_TYPES.has(record.resourceType || 'Other')
164
- );
165
- }
166
-
167
- /**
168
- * Returns true if headers suggest a record should not be cached for a long time.
169
- * @param {Map<string, string>} headers
170
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
171
- * @return {boolean}
172
- */
173
- static shouldSkipRecord(headers, cacheControl) {
174
- // 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
175
- if (!cacheControl && (headers.get('pragma') || '').includes('no-cache')) {
176
- return true;
177
- }
178
-
179
- // Ignore assets where policy implies they should not be cached long periods
180
- if (cacheControl &&
181
- (
182
- cacheControl['must-revalidate'] ||
183
- cacheControl['no-cache'] ||
184
- cacheControl['no-store'] ||
185
- cacheControl['stale-while-revalidate'] ||
186
- cacheControl['private'])) {
187
- return true;
188
- }
189
-
190
- return false;
191
- }
192
-
193
- /**
194
- * @param {LH.Artifacts} artifacts
195
- * @param {LH.Audit.Context} context
196
- * @return {Promise<LH.Audit.Product>}
197
- */
198
- static async audit(artifacts, context) {
199
- const devtoolsLogs = artifacts.DevtoolsLog;
200
- const records = await NetworkRecords.request(devtoolsLogs, context);
201
- const results = [];
202
- let totalWastedBytes = 0;
203
-
204
- for (const record of records) {
205
- if (!CacheHeaders.isCacheableAsset(record)) continue;
206
-
207
- /** @type {Map<string, string>} */
208
- const headers = new Map();
209
- for (const header of record.responseHeaders || []) {
210
- if (headers.has(header.name.toLowerCase())) {
211
- const previousHeaderValue = headers.get(header.name.toLowerCase());
212
- headers.set(header.name.toLowerCase(),
213
- `${previousHeaderValue}, ${header.value}`);
214
- } else {
215
- headers.set(header.name.toLowerCase(), header.value);
216
- }
217
- }
218
-
219
- const cacheControl = parseCacheControl(headers.get('cache-control'));
220
- if (this.shouldSkipRecord(headers, cacheControl)) {
221
- continue;
222
- }
223
-
224
- // Ignore if cacheLifetimeInSeconds is a nonpositive number.
225
- let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
226
- headers, cacheControl);
227
- if (cacheLifetimeInSeconds !== null &&
228
- (!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
229
- continue;
230
- }
231
- cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
232
-
233
- // Ignore assets whose cache lifetime is already high enough
234
- const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
235
- if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
236
-
237
- const url = UrlUtils.elideDataURI(record.url);
238
- const totalBytes = record.transferSize || 0;
239
- const wastedBytes = (1 - cacheHitProbability) * totalBytes;
240
-
241
- totalWastedBytes += wastedBytes;
242
-
243
- // Include cacheControl info (if it exists) per url as a diagnostic.
244
- /** @type {LH.Audit.Details.DebugData|undefined} */
245
- let debugData;
246
- if (cacheControl) {
247
- debugData = {
248
- type: 'debugdata',
249
- ...cacheControl,
250
- };
251
- }
252
-
253
- results.push({
254
- url,
255
- debugData,
256
- cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
257
- cacheHitProbability,
258
- totalBytes,
259
- wastedBytes,
260
- });
261
- }
262
-
263
- results.sort((a, b) => {
264
- return a.cacheLifetimeMs - b.cacheLifetimeMs ||
265
- b.totalBytes - a.totalBytes ||
266
- a.url.localeCompare(b.url);
267
- });
268
-
269
- /** @type {LH.Audit.Details.Table['headings']} */
270
- const headings = [
271
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
272
- // TODO(i18n): pre-compute localized duration
273
- {key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
274
- displayUnit: 'duration'},
275
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
276
- displayUnit: 'kb', granularity: 1},
277
- ];
278
-
279
- const details = Audit.makeTableDetails(headings, results,
280
- {wastedBytes: totalWastedBytes, sortedBy: ['totalBytes'], skipSumming: ['cacheLifetimeMs']});
281
-
282
- return {
283
- score: results.length ? 0 : 1,
284
- numericValue: totalWastedBytes,
285
- numericUnit: 'byte',
286
- displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
287
- details,
288
- };
289
- }
290
- }
291
-
292
- export default CacheHeaders;
293
- export {UIStrings};
@@ -1,33 +0,0 @@
1
- export default UsesOptimizedImages;
2
- declare class UsesOptimizedImages extends ByteEfficiencyAudit {
3
- /**
4
- * @param {{originalSize: number, jpegSize: number}} image
5
- * @return {{bytes: number, percent: number}}
6
- */
7
- static computeSavings(image: {
8
- originalSize: number;
9
- jpegSize: number;
10
- }): {
11
- bytes: number;
12
- percent: number;
13
- };
14
- /**
15
- * @param {{naturalWidth: number, naturalHeight: number}} imageElement
16
- * @return {number}
17
- */
18
- static estimateJPEGSizeFromDimensions(imageElement: {
19
- naturalWidth: number;
20
- naturalHeight: number;
21
- }): number;
22
- /**
23
- * @param {LH.Artifacts} artifacts
24
- * @return {import('./byte-efficiency-audit.js').ByteEfficiencyProduct}
25
- */
26
- static audit_(artifacts: LH.Artifacts): import("./byte-efficiency-audit.js").ByteEfficiencyProduct;
27
- }
28
- export namespace UIStrings {
29
- let title: string;
30
- let description: string;
31
- }
32
- import { ByteEfficiencyAudit } from './byte-efficiency-audit.js';
33
- //# sourceMappingURL=uses-optimized-images.d.ts.map
@@ -1,146 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2017 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- /*
7
- * @fileoverview This audit determines if the images used are sufficiently larger
8
- * than JPEG compressed images without metadata at quality 85.
9
- */
10
-
11
-
12
- import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
13
- import UrlUtils from '../../lib/url-utils.js';
14
- import * as i18n from '../../lib/i18n/i18n.js';
15
-
16
- const UIStrings = {
17
- /** Imperative title of a Lighthouse audit that tells the user to encode images with optimization (better compression). This is displayed in a list of audit titles that Lighthouse generates. */
18
- title: 'Efficiently encode images',
19
- /** Description of a Lighthouse audit that tells the user *why* they need to efficiently encode images. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
20
- description: 'Optimized images load faster and consume less cellular data. ' +
21
- '[Learn how to efficiently encode images](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/).',
22
- };
23
-
24
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
25
-
26
- const IGNORE_THRESHOLD_IN_BYTES = 4096;
27
-
28
- class UsesOptimizedImages extends ByteEfficiencyAudit {
29
- /**
30
- * @return {LH.Audit.Meta}
31
- */
32
- static get meta() {
33
- return {
34
- id: 'uses-optimized-images',
35
- title: str_(UIStrings.title),
36
- description: str_(UIStrings.description),
37
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
- guidanceLevel: 2,
39
- requiredArtifacts: ['OptimizedImages', 'ImageElements', 'GatherContext', 'DevtoolsLog',
40
- 'Trace', 'URL', 'SourceMaps'],
41
- };
42
- }
43
-
44
- /**
45
- * @param {{originalSize: number, jpegSize: number}} image
46
- * @return {{bytes: number, percent: number}}
47
- */
48
- static computeSavings(image) {
49
- const bytes = image.originalSize - image.jpegSize;
50
- const percent = 100 * bytes / image.originalSize;
51
- return {bytes, percent};
52
- }
53
-
54
- /**
55
- * @param {{naturalWidth: number, naturalHeight: number}} imageElement
56
- * @return {number}
57
- */
58
- static estimateJPEGSizeFromDimensions(imageElement) {
59
- const totalPixels = imageElement.naturalWidth * imageElement.naturalHeight;
60
- // Even JPEGs with lots of detail can usually be compressed down to <1 byte per pixel
61
- // Using 4:2:2 subsampling already gets an uncompressed bitmap to 2 bytes per pixel.
62
- // The compression ratio for JPEG is usually somewhere around 10:1 depending on content, so
63
- // 8:1 is a reasonable expectation for web content which is 1.5MB for a 6MP image.
64
- const expectedBytesPerPixel = 2 * 1 / 8;
65
- return Math.round(totalPixels * expectedBytesPerPixel);
66
- }
67
-
68
- /**
69
- * @param {LH.Artifacts} artifacts
70
- * @return {import('./byte-efficiency-audit.js').ByteEfficiencyProduct}
71
- */
72
- static audit_(artifacts) {
73
- const pageURL = artifacts.URL.finalDisplayedUrl;
74
- const images = artifacts.OptimizedImages;
75
- const imageElements = artifacts.ImageElements;
76
- /** @type {Map<string, LH.Artifacts.ImageElement>} */
77
- const imageElementsByURL = new Map();
78
- imageElements.forEach(img => imageElementsByURL.set(img.src, img));
79
-
80
- /** @type {Array<{node?: LH.Audit.Details.NodeValue, url: string, fromProtocol: boolean, isCrossOrigin: boolean, totalBytes: number, wastedBytes: number}>} */
81
- const items = [];
82
- const warnings = [];
83
- for (const image of images) {
84
- const imageElement = imageElementsByURL.get(image.url);
85
-
86
- if (image.failed) {
87
- warnings.push(`Unable to decode ${UrlUtils.getURLDisplayName(image.url)}`);
88
- continue;
89
- } else if (/(jpeg|bmp)/.test(image.mimeType) === false) {
90
- continue;
91
- }
92
-
93
- let jpegSize = image.jpegSize;
94
- let fromProtocol = true;
95
-
96
- if (typeof jpegSize === 'undefined') {
97
- if (!imageElement) {
98
- warnings.push(`Unable to locate resource ${UrlUtils.getURLDisplayName(image.url)}`);
99
- continue;
100
- }
101
-
102
- // Skip if we couldn't collect natural image size information.
103
- if (!imageElement.naturalDimensions) continue;
104
- const naturalHeight = imageElement.naturalDimensions.height;
105
- const naturalWidth = imageElement.naturalDimensions.width;
106
- // If naturalHeight or naturalWidth are falsy, information is not valid, skip.
107
- if (!naturalHeight || !naturalWidth) continue;
108
- jpegSize =
109
- UsesOptimizedImages.estimateJPEGSizeFromDimensions({naturalHeight, naturalWidth});
110
- fromProtocol = false;
111
- }
112
-
113
- if (image.originalSize < jpegSize + IGNORE_THRESHOLD_IN_BYTES) continue;
114
-
115
- const url = UrlUtils.elideDataURI(image.url);
116
- const isCrossOrigin = !UrlUtils.originsMatch(pageURL, image.url);
117
- const jpegSavings = UsesOptimizedImages.computeSavings({...image, jpegSize});
118
-
119
- items.push({
120
- node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
121
- url,
122
- fromProtocol,
123
- isCrossOrigin,
124
- totalBytes: image.originalSize,
125
- wastedBytes: jpegSavings.bytes,
126
- });
127
- }
128
-
129
- /** @type {LH.Audit.Details.Opportunity['headings']} */
130
- const headings = [
131
- {key: 'node', valueType: 'node', label: ''},
132
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
133
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
134
- {key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
135
- ];
136
-
137
- return {
138
- warnings,
139
- items,
140
- headings,
141
- };
142
- }
143
- }
144
-
145
- export default UsesOptimizedImages;
146
- export {UIStrings};
@@ -1,16 +0,0 @@
1
- export default UsesResponsiveImagesSnapshot;
2
- declare class UsesResponsiveImagesSnapshot extends Audit {
3
- /**
4
- * @param {LH.Artifacts} artifacts
5
- * @return {Promise<LH.Audit.Product>}
6
- */
7
- static audit(artifacts: LH.Artifacts): Promise<LH.Audit.Product>;
8
- }
9
- export namespace UIStrings {
10
- let title: string;
11
- let failureTitle: string;
12
- let columnDisplayedDimensions: string;
13
- let columnActualDimensions: string;
14
- }
15
- import { Audit } from '../audit.js';
16
- //# sourceMappingURL=uses-responsive-images-snapshot.d.ts.map
@@ -1,106 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2021 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- /**
7
- * @fileoverview Checks to see if the images used on the page are larger than
8
- * their display sizes. The audit will list all images that are larger than
9
- * their display size with DPR (a 1000px wide image displayed as a
10
- * 500px high-res image on a Retina display is 100% used);
11
- */
12
-
13
-
14
- import {Audit} from '../audit.js';
15
- import * as UsesResponsiveImages from './uses-responsive-images.js';
16
- import UrlUtils from '../../lib/url-utils.js';
17
- import * as i18n from '../../lib/i18n/i18n.js';
18
-
19
- const UIStrings = {
20
- /** Descriptive title of a Lighthouse audit that checks if images match their displayed dimensions. This is displayed when the audit is passing. */
21
- title: 'Images were appropriate for their displayed size',
22
- /** Descriptive title of a Lighthouse audit that checks if images match their displayed dimensions. This is displayed when the audit is failing. */
23
- failureTitle: 'Images were larger than their displayed size',
24
- /** Label for a column in a data table; entries will be the dimensions of an image as it appears on the page. */
25
- columnDisplayedDimensions: 'Displayed dimensions',
26
- /** Label for a column in a data table; entries will be the dimensions of an image from it's source file. */
27
- columnActualDimensions: 'Actual dimensions',
28
- };
29
-
30
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
31
-
32
- // Based on byte threshold of 4096, with 3 bytes per pixel.
33
- const IGNORE_THRESHOLD_IN_PIXELS = 1365;
34
-
35
- class UsesResponsiveImagesSnapshot extends Audit {
36
- /**
37
- * @return {LH.Audit.Meta}
38
- */
39
- static get meta() {
40
- return {
41
- id: 'uses-responsive-images-snapshot',
42
- title: str_(UIStrings.title),
43
- failureTitle: str_(UIStrings.failureTitle),
44
- description: UsesResponsiveImages.str_(UsesResponsiveImages.UIStrings.description),
45
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
46
- supportedModes: ['snapshot'],
47
- guidanceLevel: 2,
48
- requiredArtifacts: ['ImageElements', 'ViewportDimensions'],
49
- };
50
- }
51
-
52
- /**
53
- * @param {LH.Artifacts} artifacts
54
- * @return {Promise<LH.Audit.Product>}
55
- */
56
- static async audit(artifacts) {
57
- let score = 1;
58
- /** @type {LH.Audit.Details.TableItem[]} */
59
- const items = [];
60
- for (const image of artifacts.ImageElements) {
61
- // Ignore CSS images because it's difficult to determine what is a spritesheet,
62
- // and the reward-to-effort ratio for responsive CSS images is quite low https://css-tricks.com/responsive-images-css/.
63
- if (image.isCss) continue;
64
-
65
- if (!image.naturalDimensions) continue;
66
- const actual = image.naturalDimensions;
67
- const displayed = UsesResponsiveImages.default.getDisplayedDimensions(
68
- {...image, naturalWidth: actual.width, naturalHeight: actual.height},
69
- artifacts.ViewportDimensions
70
- );
71
-
72
- const actualPixels = actual.width * actual.height;
73
- const usedPixels = displayed.width * displayed.height;
74
-
75
- if (actualPixels <= usedPixels) continue;
76
- if (actualPixels - usedPixels > IGNORE_THRESHOLD_IN_PIXELS) score = 0;
77
-
78
- items.push({
79
- node: Audit.makeNodeItem(image.node),
80
- url: UrlUtils.elideDataURI(image.src),
81
- displayedDimensions: `${displayed.width}x${displayed.height}`,
82
- actualDimensions: `${actual.width}x${actual.height}`,
83
- });
84
- }
85
-
86
- /** @type {LH.Audit.Details.Table['headings']} */
87
- const headings = [
88
- /* eslint-disable max-len */
89
- {key: 'node', valueType: 'node', label: ''},
90
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
91
- {key: 'displayedDimensions', valueType: 'text', label: str_(UIStrings.columnDisplayedDimensions)},
92
- {key: 'actualDimensions', valueType: 'text', label: str_(UIStrings.columnActualDimensions)},
93
- /* eslint-enable max-len */
94
- ];
95
-
96
- const details = Audit.makeTableDetails(headings, items);
97
-
98
- return {
99
- score,
100
- details,
101
- };
102
- }
103
- }
104
-
105
- export default UsesResponsiveImagesSnapshot;
106
- export {UIStrings};
@@ -1,44 +0,0 @@
1
- export default UsesResponsiveImages;
2
- declare class UsesResponsiveImages extends ByteEfficiencyAudit {
3
- /**
4
- * @param {LH.Artifacts.ImageElement & {naturalWidth: number, naturalHeight: number}} image
5
- * @param {LH.Artifacts.ViewportDimensions} ViewportDimensions
6
- * @return {{width: number, height: number}};
7
- */
8
- static getDisplayedDimensions(image: LH.Artifacts.ImageElement & {
9
- naturalWidth: number;
10
- naturalHeight: number;
11
- }, ViewportDimensions: LH.Artifacts.ViewportDimensions): {
12
- width: number;
13
- height: number;
14
- };
15
- /**
16
- * @param {LH.Artifacts.ImageElement & {naturalWidth: number, naturalHeight: number}} image
17
- * @param {LH.Artifacts.ViewportDimensions} ViewportDimensions
18
- * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
19
- * @return {null|LH.Audit.ByteEfficiencyItem};
20
- */
21
- static computeWaste(image: LH.Artifacts.ImageElement & {
22
- naturalWidth: number;
23
- naturalHeight: number;
24
- }, ViewportDimensions: LH.Artifacts.ViewportDimensions, networkRecords: Array<LH.Artifacts.NetworkRequest>): null | LH.Audit.ByteEfficiencyItem;
25
- /**
26
- * @param {LH.Artifacts.ImageElement} image
27
- * @return {number};
28
- */
29
- static determineAllowableWaste(image: LH.Artifacts.ImageElement): number;
30
- /**
31
- * @param {LH.Artifacts} artifacts
32
- * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
33
- * @param {LH.Audit.Context} context
34
- * @return {Promise<import('./byte-efficiency-audit.js').ByteEfficiencyProduct>}
35
- */
36
- static audit_(artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>, context: LH.Audit.Context): Promise<import("./byte-efficiency-audit.js").ByteEfficiencyProduct>;
37
- }
38
- export namespace UIStrings {
39
- let title: string;
40
- let description: string;
41
- }
42
- export const str_: (message: string, values?: Record<string, string | number>) => LH.IcuMessage;
43
- import { ByteEfficiencyAudit } from './byte-efficiency-audit.js';
44
- //# sourceMappingURL=uses-responsive-images.d.ts.map