lighthouse 12.8.2-dev.20251004 → 12.8.2-dev.20251006

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 (126) 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 +6 -6
  5. package/core/audits/redirects.js +1 -0
  6. package/core/audits/server-response-time.d.ts +0 -5
  7. package/core/audits/server-response-time.js +12 -26
  8. package/core/computed/metrics/lcp-breakdown.js +1 -0
  9. package/core/config/default-config.js +20 -63
  10. package/core/config/experimental-config.js +1 -26
  11. package/core/config/filters.js +6 -9
  12. package/core/config/lr-desktop-config.js +0 -1
  13. package/core/config/lr-mobile-config.js +0 -1
  14. package/core/gather/gatherers/trace-elements.js +1 -0
  15. package/core/lib/proto-preprocessor.js +5 -22
  16. package/dist/report/bundle.esm.js +10 -49
  17. package/dist/report/flow.js +12 -51
  18. package/dist/report/standalone.js +11 -50
  19. package/flow-report/src/i18n/i18n.d.ts +4 -6
  20. package/package.json +3 -3
  21. package/report/assets/styles.css +0 -39
  22. package/report/renderer/api.js +0 -1
  23. package/report/renderer/category-renderer.js +6 -0
  24. package/report/renderer/components.js +1 -1
  25. package/report/renderer/dom.d.ts +0 -13
  26. package/report/renderer/dom.js +0 -38
  27. package/report/renderer/performance-category-renderer.d.ts +0 -26
  28. package/report/renderer/performance-category-renderer.js +10 -142
  29. package/report/renderer/report-ui-features.d.ts +0 -1
  30. package/report/renderer/report-ui-features.js +3 -13
  31. package/report/renderer/report-utils.d.ts +2 -3
  32. package/report/renderer/report-utils.js +4 -6
  33. package/report/types/report-renderer.d.ts +0 -6
  34. package/shared/localization/locales/ar-XB.json +0 -330
  35. package/shared/localization/locales/ar.json +0 -330
  36. package/shared/localization/locales/bg.json +0 -330
  37. package/shared/localization/locales/ca.json +0 -330
  38. package/shared/localization/locales/cs.json +0 -330
  39. package/shared/localization/locales/da.json +0 -330
  40. package/shared/localization/locales/de.json +0 -330
  41. package/shared/localization/locales/el.json +0 -330
  42. package/shared/localization/locales/en-GB.json +0 -330
  43. package/shared/localization/locales/en-US.json +26 -275
  44. package/shared/localization/locales/en-XA.json +0 -330
  45. package/shared/localization/locales/en-XL.json +26 -275
  46. package/shared/localization/locales/es-419.json +0 -330
  47. package/shared/localization/locales/es.json +0 -330
  48. package/shared/localization/locales/fi.json +0 -330
  49. package/shared/localization/locales/fil.json +0 -330
  50. package/shared/localization/locales/fr.json +0 -330
  51. package/shared/localization/locales/he.json +0 -330
  52. package/shared/localization/locales/hi.json +0 -330
  53. package/shared/localization/locales/hr.json +0 -330
  54. package/shared/localization/locales/hu.json +0 -330
  55. package/shared/localization/locales/id.json +0 -330
  56. package/shared/localization/locales/it.json +0 -330
  57. package/shared/localization/locales/ja.json +0 -330
  58. package/shared/localization/locales/ko.json +0 -330
  59. package/shared/localization/locales/lt.json +0 -330
  60. package/shared/localization/locales/lv.json +0 -330
  61. package/shared/localization/locales/nl.json +0 -330
  62. package/shared/localization/locales/no.json +0 -330
  63. package/shared/localization/locales/pl.json +0 -330
  64. package/shared/localization/locales/pt-PT.json +0 -330
  65. package/shared/localization/locales/pt.json +0 -330
  66. package/shared/localization/locales/ro.json +0 -330
  67. package/shared/localization/locales/ru.json +0 -330
  68. package/shared/localization/locales/sk.json +0 -330
  69. package/shared/localization/locales/sl.json +0 -330
  70. package/shared/localization/locales/sr-Latn.json +0 -330
  71. package/shared/localization/locales/sr.json +0 -330
  72. package/shared/localization/locales/sv.json +0 -330
  73. package/shared/localization/locales/ta.json +0 -330
  74. package/shared/localization/locales/te.json +0 -330
  75. package/shared/localization/locales/th.json +0 -330
  76. package/shared/localization/locales/tr.json +0 -330
  77. package/shared/localization/locales/uk.json +0 -330
  78. package/shared/localization/locales/vi.json +0 -330
  79. package/shared/localization/locales/zh-HK.json +0 -330
  80. package/shared/localization/locales/zh-TW.json +0 -330
  81. package/shared/localization/locales/zh.json +0 -330
  82. package/types/artifacts.d.ts +1 -0
  83. package/types/audit.d.ts +1 -1
  84. package/types/lhr/settings.d.ts +1 -1
  85. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -45
  86. package/core/audits/byte-efficiency/duplicated-javascript.js +0 -223
  87. package/core/audits/byte-efficiency/efficient-animated-content.d.ts +0 -22
  88. package/core/audits/byte-efficiency/efficient-animated-content.js +0 -93
  89. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -28
  90. package/core/audits/byte-efficiency/legacy-javascript.js +0 -144
  91. package/core/audits/byte-efficiency/modern-image-formats.d.ts +0 -38
  92. package/core/audits/byte-efficiency/modern-image-formats.js +0 -187
  93. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +0 -53
  94. package/core/audits/byte-efficiency/render-blocking-resources.js +0 -312
  95. package/core/audits/byte-efficiency/uses-long-cache-ttl.d.ts +0 -59
  96. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +0 -293
  97. package/core/audits/byte-efficiency/uses-optimized-images.d.ts +0 -33
  98. package/core/audits/byte-efficiency/uses-optimized-images.js +0 -146
  99. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.d.ts +0 -16
  100. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +0 -106
  101. package/core/audits/byte-efficiency/uses-responsive-images.d.ts +0 -44
  102. package/core/audits/byte-efficiency/uses-responsive-images.js +0 -202
  103. package/core/audits/byte-efficiency/uses-text-compression.d.ts +0 -14
  104. package/core/audits/byte-efficiency/uses-text-compression.js +0 -108
  105. package/core/audits/critical-request-chains.d.ts +0 -44
  106. package/core/audits/critical-request-chains.js +0 -221
  107. package/core/audits/dobetterweb/dom-size.d.ts +0 -32
  108. package/core/audits/dobetterweb/dom-size.js +0 -182
  109. package/core/audits/dobetterweb/uses-http2.d.ts +0 -72
  110. package/core/audits/dobetterweb/uses-http2.js +0 -276
  111. package/core/audits/font-display.d.ts +0 -32
  112. package/core/audits/font-display.js +0 -195
  113. package/core/audits/largest-contentful-paint-element.d.ts +0 -34
  114. package/core/audits/largest-contentful-paint-element.js +0 -181
  115. package/core/audits/lcp-lazy-loaded.d.ts +0 -22
  116. package/core/audits/lcp-lazy-loaded.js +0 -115
  117. package/core/audits/prioritize-lcp-image.d.ts +0 -74
  118. package/core/audits/prioritize-lcp-image.js +0 -297
  119. package/core/audits/third-party-summary.d.ts +0 -78
  120. package/core/audits/third-party-summary.js +0 -236
  121. package/core/audits/uses-rel-preconnect.d.ts +0 -37
  122. package/core/audits/uses-rel-preconnect.js +0 -286
  123. package/core/audits/viewport.d.ts +0 -17
  124. package/core/audits/viewport.js +0 -87
  125. package/core/audits/work-during-interaction.d.ts +0 -81
  126. package/core/audits/work-during-interaction.js +0 -287
@@ -1,187 +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 could be smaller when compressed with WebP.
8
- */
9
-
10
-
11
- import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
12
- import UrlUtils from '../../lib/url-utils.js';
13
- import * as i18n from '../../lib/i18n/i18n.js';
14
-
15
- const UIStrings = {
16
- /** Imperative title of a Lighthouse audit that tells the user to serve images in newer and more efficient image formats in order to enhance the performance of a page. A non-modern image format was designed 20+ years ago. This is displayed in a list of audit titles that Lighthouse generates. */
17
- title: 'Serve images in next-gen formats',
18
- /** Description of a Lighthouse audit that tells the user *why* they should use newer and more efficient image formats. 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. */
19
- description: 'Image formats like WebP and AVIF often provide better ' +
20
- 'compression than PNG or JPEG, which means faster downloads and less data consumption. ' +
21
- '[Learn more about modern image formats](https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images/).',
22
- };
23
-
24
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
25
-
26
- const IGNORE_THRESHOLD_IN_BYTES = 8192;
27
-
28
- class ModernImageFormats extends ByteEfficiencyAudit {
29
- /**
30
- * @return {LH.Audit.Meta}
31
- */
32
- static get meta() {
33
- return {
34
- id: 'modern-image-formats',
35
- title: str_(UIStrings.title),
36
- description: str_(UIStrings.description),
37
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
- guidanceLevel: 3,
39
- requiredArtifacts: ['OptimizedImages', 'DevtoolsLog', 'Trace', 'URL', 'GatherContext',
40
- 'ImageElements', 'SourceMaps'],
41
- };
42
- }
43
-
44
- /**
45
- * @param {{naturalWidth: number, naturalHeight: number}} imageElement
46
- * @return {number}
47
- */
48
- static estimateWebPSizeFromDimensions(imageElement) {
49
- const totalPixels = imageElement.naturalWidth * imageElement.naturalHeight;
50
- // See uses-optimized-images for the rationale behind our 2 byte-per-pixel baseline and
51
- // JPEG compression ratio of 8:1.
52
- // WebP usually gives ~20% additional savings on top of that, so we will use 10:1.
53
- // This is quite pessimistic as their study shows a photographic compression ratio of ~29:1.
54
- // https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study#results
55
- const expectedBytesPerPixel = 2 * 1 / 10;
56
- return Math.round(totalPixels * expectedBytesPerPixel);
57
- }
58
-
59
- /**
60
- * @param {{naturalWidth: number, naturalHeight: number}} imageElement
61
- * @return {number}
62
- */
63
- static estimateAvifSizeFromDimensions(imageElement) {
64
- const totalPixels = imageElement.naturalWidth * imageElement.naturalHeight;
65
- // See above for the rationale behind our 2 byte-per-pixel baseline and WebP ratio of 10:1.
66
- // AVIF usually gives ~20% additional savings on top of that, so we will use 12:1.
67
- // This is quite pessimistic as Netflix study shows a photographic compression ratio of ~40:1
68
- // (0.4 *bits* per pixel at SSIM 0.97).
69
- // https://netflixtechblog.com/avif-for-next-generation-image-coding-b1d75675fe4
70
- const expectedBytesPerPixel = 2 * 1 / 12;
71
- return Math.round(totalPixels * expectedBytesPerPixel);
72
- }
73
-
74
- /**
75
- * @param {{jpegSize: number | undefined, webpSize: number | undefined}} otherFormatSizes
76
- * @return {number|undefined}
77
- */
78
- static estimateAvifSizeFromWebPAndJpegEstimates(otherFormatSizes) {
79
- if (!otherFormatSizes.jpegSize || !otherFormatSizes.webpSize) return undefined;
80
-
81
- // AVIF saves at least ~50% on JPEG, ~20% on WebP at low quality.
82
- // http://downloads.aomedia.org/assets/pdf/symposium-2019/slides/CyrilConcolato_Netflix-AVIF-AOM-Research-Symposium-2019.pdf
83
- // https://jakearchibald.com/2020/avif-has-landed/
84
- // https://www.finally.agency/blog/what-is-avif-image-format
85
- // See https://github.com/GoogleChrome/lighthouse/issues/12295#issue-840261460 for more.
86
- const estimateFromJpeg = otherFormatSizes.jpegSize * 5 / 10;
87
- const estimateFromWebp = otherFormatSizes.webpSize * 8 / 10;
88
- return estimateFromJpeg / 2 + estimateFromWebp / 2;
89
- }
90
-
91
- /**
92
- * @param {LH.Artifacts} artifacts
93
- * @return {import('./byte-efficiency-audit.js').ByteEfficiencyProduct}
94
- */
95
- static audit_(artifacts) {
96
- const pageURL = artifacts.URL.finalDisplayedUrl;
97
- const images = artifacts.OptimizedImages;
98
- const imageElements = artifacts.ImageElements;
99
- /** @type {Map<string, LH.Artifacts.ImageElement>} */
100
- const imageElementsByURL = new Map();
101
- imageElements.forEach(img => imageElementsByURL.set(img.src, img));
102
-
103
- /** @type {Array<LH.Audit.ByteEfficiencyItem>} */
104
- const items = [];
105
- const warnings = [];
106
- for (const image of images) {
107
- const imageElement = imageElementsByURL.get(image.url);
108
-
109
- if (image.failed) {
110
- warnings.push(`Unable to decode ${UrlUtils.getURLDisplayName(image.url)}`);
111
- continue;
112
- }
113
-
114
- // Skip if the image was already using a modern format.
115
- if (image.mimeType === 'image/webp' || image.mimeType === 'image/avif') continue;
116
-
117
- const jpegSize = image.jpegSize;
118
- let webpSize = image.webpSize;
119
- let avifSize = ModernImageFormats.estimateAvifSizeFromWebPAndJpegEstimates({
120
- jpegSize,
121
- webpSize,
122
- });
123
- let fromProtocol = true;
124
-
125
- if (typeof webpSize === 'undefined') {
126
- if (!imageElement) {
127
- warnings.push(`Unable to locate resource ${UrlUtils.getURLDisplayName(image.url)}`);
128
- continue;
129
- }
130
-
131
- // Skip if we couldn't collect natural image size information.
132
- if (!imageElement.naturalDimensions) continue;
133
- const naturalHeight = imageElement.naturalDimensions.height;
134
- const naturalWidth = imageElement.naturalDimensions.width;
135
- // If naturalHeight or naturalWidth are falsy, information is not valid, skip.
136
- if (!naturalWidth || !naturalHeight) continue;
137
-
138
- webpSize = ModernImageFormats.estimateWebPSizeFromDimensions({
139
- naturalHeight,
140
- naturalWidth,
141
- });
142
- avifSize = ModernImageFormats.estimateAvifSizeFromDimensions({
143
- naturalHeight,
144
- naturalWidth,
145
- });
146
- fromProtocol = false;
147
- }
148
-
149
- if (webpSize === undefined || avifSize === undefined) continue;
150
-
151
- // Visible wasted bytes uses AVIF, but we still include the WebP savings in the LHR.
152
- const wastedWebpBytes = image.originalSize - webpSize;
153
- const wastedBytes = image.originalSize - avifSize;
154
- if (wastedBytes < IGNORE_THRESHOLD_IN_BYTES) continue;
155
-
156
- const url = UrlUtils.elideDataURI(image.url);
157
- const isCrossOrigin = !UrlUtils.originsMatch(pageURL, image.url);
158
-
159
- items.push({
160
- node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
161
- url,
162
- fromProtocol,
163
- isCrossOrigin,
164
- totalBytes: image.originalSize,
165
- wastedBytes,
166
- wastedWebpBytes,
167
- });
168
- }
169
-
170
- /** @type {LH.Audit.Details.Opportunity['headings']} */
171
- const headings = [
172
- {key: 'node', valueType: 'node', label: ''},
173
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
174
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
175
- {key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
176
- ];
177
-
178
- return {
179
- warnings,
180
- items,
181
- headings,
182
- };
183
- }
184
- }
185
-
186
- export default ModernImageFormats;
187
- export {UIStrings};
@@ -1,53 +0,0 @@
1
- export default RenderBlockingResources;
2
- declare class RenderBlockingResources extends Audit {
3
- /**
4
- * @param {LH.Artifacts} artifacts
5
- * @param {LH.Audit.Context} context
6
- * @return {Promise<{fcpWastedMs: number, lcpWastedMs: number, results: Array<{url: string, totalBytes: number, wastedMs: number}>}>}
7
- */
8
- static computeResults(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<{
9
- fcpWastedMs: number;
10
- lcpWastedMs: number;
11
- results: Array<{
12
- url: string;
13
- totalBytes: number;
14
- wastedMs: number;
15
- }>;
16
- }>;
17
- /**
18
- * Estimates how much faster this page would reach FCP if we inlined all the used CSS from the
19
- * render blocking stylesheets and deferred all the scripts. This is more conservative than
20
- * removing all the assets and more aggressive than inlining everything.
21
- *
22
- * *Most* of the time, scripts in the head are there accidentally/due to lack of awareness
23
- * rather than necessity, so we're comfortable with this balance. In the worst case, we're telling
24
- * devs that they should be able to get to a reasonable first paint without JS, which is not a bad
25
- * thing.
26
- *
27
- * @param {LH.Gatherer.Simulation.Simulator} simulator
28
- * @param {LH.Gatherer.Simulation.GraphNode} fcpGraph
29
- * @param {Set<string>} deferredIds
30
- * @param {Map<string, number>} wastedCssBytesByUrl
31
- * @param {LH.Artifacts.DetectedStack[]} Stacks
32
- * @return {number}
33
- */
34
- static estimateSavingsWithGraphs(simulator: LH.Gatherer.Simulation.Simulator, fcpGraph: LH.Gatherer.Simulation.GraphNode, deferredIds: Set<string>, wastedCssBytesByUrl: Map<string, number>, Stacks: LH.Artifacts.DetectedStack[]): number;
35
- /**
36
- * @param {LH.Artifacts} artifacts
37
- * @param {LH.Audit.Context} context
38
- * @return {Promise<Map<string, number>>}
39
- */
40
- static computeWastedCSSBytes(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<Map<string, number>>;
41
- /**
42
- * @param {LH.Artifacts} artifacts
43
- * @param {LH.Audit.Context} context
44
- * @return {Promise<LH.Audit.Product>}
45
- */
46
- static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
47
- }
48
- export namespace UIStrings {
49
- let title: string;
50
- let description: string;
51
- }
52
- import { Audit } from '../audit.js';
53
- //# sourceMappingURL=render-blocking-resources.d.ts.map
@@ -1,312 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2018 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- /**
8
- * @fileoverview Audit a page to see if it does have resources that are blocking first paint
9
- */
10
-
11
- import {Audit} from '../audit.js';
12
- import * as i18n from '../../lib/i18n/i18n.js';
13
- import * as Lantern from '../../lib/lantern/lantern.js';
14
- import {UnusedCSS} from '../../computed/unused-css.js';
15
- import {NetworkRequest} from '../../lib/network-request.js';
16
- import {LoadSimulator} from '../../computed/load-simulator.js';
17
- import {FirstContentfulPaint} from '../../computed/metrics/first-contentful-paint.js';
18
- import {LCPImageRecord} from '../../computed/lcp-image-record.js';
19
- import {NavigationInsights} from '../../computed/navigation-insights.js';
20
-
21
- // Because of the way we detect blocking stylesheets, asynchronously loaded
22
- // CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)
23
- // can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough
24
- // to possibly be non-blocking (and they have minimal impact anyway).
25
- const MINIMUM_WASTED_MS = 50;
26
-
27
- const UIStrings = {
28
- /** Imperative title of a Lighthouse audit that tells the user to reduce or remove network resources that block the initial render of the page. This is displayed in a list of audit titles that Lighthouse generates. */
29
- title: 'Eliminate render-blocking resources',
30
- /** Description of a Lighthouse audit that tells the user *why* they should reduce or remove network resources that block the initial render of the page. 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. */
31
- description: 'Resources are blocking the first paint of your page. Consider ' +
32
- 'delivering critical JS/CSS inline and deferring all non-critical ' +
33
- 'JS/styles. [Learn how to eliminate render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/).',
34
- };
35
-
36
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
37
-
38
- /**
39
- * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL
40
- * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
41
- * @return {Map<string, {node: LH.Gatherer.Simulation.GraphNetworkNode, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>}
42
- */
43
- function getNodesAndTimingByRequestId(nodeTimings) {
44
- /** @type {Map<string, {node: LH.Gatherer.Simulation.GraphNetworkNode, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>} */
45
- const requestIdToNode = new Map();
46
-
47
- for (const [node, nodeTiming] of nodeTimings) {
48
- if (node.type !== 'network') continue;
49
-
50
- requestIdToNode.set(node.request.requestId, {node, nodeTiming});
51
- }
52
-
53
- return requestIdToNode;
54
- }
55
-
56
- /**
57
- * Adjust the timing of a node and its dependencies to account for stack specific overrides.
58
- * @param {Map<LH.Gatherer.Simulation.GraphNode, LH.Gatherer.Simulation.NodeTiming>} adjustedNodeTimings
59
- * @param {LH.Gatherer.Simulation.GraphNode} node
60
- * @param {LH.Artifacts.DetectedStack[]} Stacks
61
- */
62
- function adjustNodeTimings(adjustedNodeTimings, node, Stacks) {
63
- const nodeTiming = adjustedNodeTimings.get(node);
64
- if (!nodeTiming) return;
65
- const stackSpecificTiming = computeStackSpecificTiming(node, nodeTiming, Stacks);
66
- const difference = nodeTiming.duration - stackSpecificTiming.duration;
67
- if (!difference) return;
68
-
69
- // AMP's method of removal of stylesheets effectively removes all dependent nodes from the FCP graph
70
- node.traverse(childNode => {
71
- adjustedNodeTimings.delete(childNode);
72
- });
73
- adjustedNodeTimings.set(node, stackSpecificTiming);
74
- }
75
-
76
- /**
77
- * Any stack specific timing overrides should go in this function.
78
- * @see https://github.com/GoogleChrome/lighthouse/issues/2832#issuecomment-591066081
79
- *
80
- * @param {LH.Gatherer.Simulation.GraphNode} node
81
- * @param {LH.Gatherer.Simulation.NodeTiming} nodeTiming
82
- * @param {LH.Artifacts.DetectedStack[]} Stacks
83
- */
84
- function computeStackSpecificTiming(node, nodeTiming, Stacks) {
85
- const stackSpecificTiming = {...nodeTiming};
86
- if (Stacks.some(stack => stack.id === 'amp')) {
87
- // AMP will load a linked stylesheet asynchronously if it has not been loaded after 2.1 seconds:
88
- // https://github.com/ampproject/amphtml/blob/8e03ac2f315774070651584a7e046ff24212c9b1/src/font-stylesheet-timeout.js#L54-L59
89
- // Any potential savings must only include time spent on AMP stylesheet nodes before 2.1 seconds.
90
- if (node.type === Lantern.Graph.BaseNode.types.NETWORK &&
91
- node.request.resourceType === NetworkRequest.TYPES.Stylesheet &&
92
- nodeTiming.endTime > 2100) {
93
- stackSpecificTiming.endTime = Math.max(nodeTiming.startTime, 2100);
94
- stackSpecificTiming.duration = stackSpecificTiming.endTime - stackSpecificTiming.startTime;
95
- }
96
- }
97
- return stackSpecificTiming;
98
- }
99
-
100
- class RenderBlockingResources extends Audit {
101
- /**
102
- * @return {LH.Audit.Meta}
103
- */
104
- static get meta() {
105
- return {
106
- id: 'render-blocking-resources',
107
- title: str_(UIStrings.title),
108
- supportedModes: ['navigation'],
109
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
110
- description: str_(UIStrings.description),
111
- guidanceLevel: 2,
112
- // TODO: look into adding an `optionalArtifacts` property that captures the non-required nature
113
- // of CSSUsage
114
- requiredArtifacts:
115
- // eslint-disable-next-line max-len
116
- ['URL', 'Trace', 'DevtoolsLog', 'Stylesheets', 'CSSUsage', 'GatherContext', 'Stacks', 'SourceMaps'],
117
- };
118
- }
119
-
120
- /**
121
- * @param {LH.Artifacts} artifacts
122
- * @param {LH.Audit.Context} context
123
- * @return {Promise<{fcpWastedMs: number, lcpWastedMs: number, results: Array<{url: string, totalBytes: number, wastedMs: number}>}>}
124
- */
125
- static async computeResults(artifacts, context) {
126
- const settings = context.settings;
127
- const gatherContext = artifacts.GatherContext;
128
- const trace = artifacts.Trace;
129
- const devtoolsLog = artifacts.DevtoolsLog;
130
- const SourceMaps = artifacts.SourceMaps;
131
- const simulatorData = {devtoolsLog, settings: context.settings};
132
- const simulator = await LoadSimulator.request(simulatorData, context);
133
- const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
134
- const navInsights = await NavigationInsights.request({trace, settings, SourceMaps}, context);
135
-
136
- const renderBlocking = navInsights.model.RenderBlocking;
137
- if (renderBlocking instanceof Error) throw renderBlocking;
138
-
139
- /** @type {LH.Audit.Context['settings']} */
140
- const metricSettings = {
141
- ...context.settings,
142
- throttlingMethod: 'simulate',
143
- };
144
-
145
- const metricComputationData = {trace, devtoolsLog, gatherContext, simulator,
146
- settings: metricSettings, URL: artifacts.URL, SourceMaps: artifacts.SourceMaps};
147
-
148
- // Cast to just `LanternMetric` since we explicitly set `throttlingMethod: 'simulate'`.
149
- const fcpSimulation = /** @type {LH.Artifacts.LanternMetric} */
150
- (await FirstContentfulPaint.request(metricComputationData, context));
151
-
152
- const nodesAndTimingsByRequestId =
153
- getNodesAndTimingByRequestId(fcpSimulation.optimisticEstimate.nodeTimings);
154
-
155
- const results = [];
156
- const deferredNodeIds = new Set();
157
- for (const resource of renderBlocking.renderBlockingRequests) {
158
- const nodeAndTiming = nodesAndTimingsByRequestId.get(resource.args.data.requestId);
159
- // TODO: beacon to Sentry, https://github.com/GoogleChrome/lighthouse/issues/7041
160
- if (!nodeAndTiming) continue;
161
-
162
- const {node, nodeTiming} = nodeAndTiming;
163
-
164
- const stackSpecificTiming = computeStackSpecificTiming(node, nodeTiming, artifacts.Stacks);
165
-
166
- // Mark this node and all its dependents as deferrable
167
- node.traverse(node => deferredNodeIds.add(node.id));
168
-
169
- // "wastedMs" is the download time of the network request, responseReceived - requestSent
170
- const wastedMs = Math.round(stackSpecificTiming.duration);
171
- if (wastedMs < MINIMUM_WASTED_MS) continue;
172
-
173
- results.push({
174
- url: resource.args.data.url,
175
- totalBytes: node.request.transferSize,
176
- wastedMs,
177
- });
178
- }
179
-
180
- if (!results.length) {
181
- return {results, fcpWastedMs: 0, lcpWastedMs: 0};
182
- }
183
-
184
- const fcpWastedMs = RenderBlockingResources.estimateSavingsWithGraphs(
185
- simulator,
186
- fcpSimulation.optimisticGraph,
187
- deferredNodeIds,
188
- wastedCssBytes,
189
- artifacts.Stacks
190
- );
191
-
192
- const lcpRecord = await LCPImageRecord.request(metricComputationData, context);
193
-
194
- // In most cases if the LCP is an image, render blocking resources don't affect LCP. For these cases we should reduce its impact.
195
- return {results, fcpWastedMs, lcpWastedMs: lcpRecord ? 0 : fcpWastedMs};
196
- }
197
-
198
- /**
199
- * Estimates how much faster this page would reach FCP if we inlined all the used CSS from the
200
- * render blocking stylesheets and deferred all the scripts. This is more conservative than
201
- * removing all the assets and more aggressive than inlining everything.
202
- *
203
- * *Most* of the time, scripts in the head are there accidentally/due to lack of awareness
204
- * rather than necessity, so we're comfortable with this balance. In the worst case, we're telling
205
- * devs that they should be able to get to a reasonable first paint without JS, which is not a bad
206
- * thing.
207
- *
208
- * @param {LH.Gatherer.Simulation.Simulator} simulator
209
- * @param {LH.Gatherer.Simulation.GraphNode} fcpGraph
210
- * @param {Set<string>} deferredIds
211
- * @param {Map<string, number>} wastedCssBytesByUrl
212
- * @param {LH.Artifacts.DetectedStack[]} Stacks
213
- * @return {number}
214
- */
215
- static estimateSavingsWithGraphs(simulator, fcpGraph, deferredIds, wastedCssBytesByUrl, Stacks) {
216
- const {nodeTimings} = simulator.simulate(fcpGraph);
217
- const adjustedNodeTimings = new Map(nodeTimings);
218
-
219
- let totalChildNetworkBytes = 0;
220
- const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {
221
- adjustNodeTimings(adjustedNodeTimings, node, Stacks);
222
-
223
- // If a node can be deferred, exclude it from the new FCP graph
224
- const canDeferRequest = deferredIds.has(node.id);
225
- if (node.type !== Lantern.Graph.BaseNode.types.NETWORK) return !canDeferRequest;
226
-
227
- const isStylesheet =
228
- node.request.resourceType === NetworkRequest.TYPES.Stylesheet;
229
- if (canDeferRequest && isStylesheet) {
230
- // We'll inline the used bytes of the stylesheet and assume the rest can be deferred
231
- const wastedBytes = wastedCssBytesByUrl.get(node.request.url) || 0;
232
- totalChildNetworkBytes += (node.request.transferSize || 0) - wastedBytes;
233
- }
234
- return !canDeferRequest;
235
- });
236
-
237
- if (minimalFCPGraph.type !== 'network') {
238
- throw new Error('minimalFCPGraph not a NetworkNode');
239
- }
240
-
241
- // Recalculate the "before" time based on our adjusted node timings.
242
- const estimateBeforeInline = Math.max(...Array.from(
243
- Array.from(adjustedNodeTimings).map(timing => timing[1].endTime)
244
- ));
245
-
246
- // Add the inlined bytes to the HTML response
247
- const originalTransferSize = minimalFCPGraph.request.transferSize;
248
- const safeTransferSize = originalTransferSize || 0;
249
- minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;
250
- const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;
251
- minimalFCPGraph.request.transferSize = originalTransferSize;
252
- return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0));
253
- }
254
-
255
- /**
256
- * @param {LH.Artifacts} artifacts
257
- * @param {LH.Audit.Context} context
258
- * @return {Promise<Map<string, number>>}
259
- */
260
- static async computeWastedCSSBytes(artifacts, context) {
261
- const wastedBytesByUrl = new Map();
262
- try {
263
- const unusedCssItems = await UnusedCSS.request({
264
- Stylesheets: artifacts.Stylesheets,
265
- CSSUsage: artifacts.CSSUsage,
266
- devtoolsLog: artifacts.DevtoolsLog,
267
- }, context);
268
- for (const item of unusedCssItems) {
269
- wastedBytesByUrl.set(item.url, item.wastedBytes);
270
- }
271
- } catch {}
272
-
273
- return wastedBytesByUrl;
274
- }
275
-
276
- /**
277
- * @param {LH.Artifacts} artifacts
278
- * @param {LH.Audit.Context} context
279
- * @return {Promise<LH.Audit.Product>}
280
- */
281
- static async audit(artifacts, context) {
282
- const {results, fcpWastedMs, lcpWastedMs} =
283
- await RenderBlockingResources.computeResults(artifacts, context);
284
-
285
- let displayValue;
286
- if (results.length > 0) {
287
- displayValue = str_(i18n.UIStrings.displayValueMsSavings, {wastedMs: fcpWastedMs});
288
- }
289
-
290
- /** @type {LH.Audit.Details.Opportunity['headings']} */
291
- const headings = [
292
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
293
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
294
- {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnDuration)},
295
- ];
296
-
297
- const details = Audit.makeOpportunityDetails(headings, results,
298
- {overallSavingsMs: fcpWastedMs});
299
-
300
- return {
301
- displayValue,
302
- score: results.length ? 0 : 1,
303
- numericValue: fcpWastedMs,
304
- numericUnit: 'millisecond',
305
- details,
306
- metricSavings: {FCP: fcpWastedMs, LCP: lcpWastedMs},
307
- };
308
- }
309
- }
310
-
311
- export default RenderBlockingResources;
312
- export {UIStrings};
@@ -1,59 +0,0 @@
1
- export default CacheHeaders;
2
- declare class CacheHeaders extends Audit {
3
- /**
4
- * @return {LH.Audit.ScoreOptions}
5
- */
6
- static get defaultOptions(): LH.Audit.ScoreOptions;
7
- /**
8
- * Computes the percent likelihood that a return visit will be within the cache lifetime, based on
9
- * Chrome UMA stats see the note below.
10
- * @param {number} maxAgeInSeconds
11
- * @return {number}
12
- */
13
- static getCacheHitProbability(maxAgeInSeconds: number): number;
14
- /**
15
- * Return max-age if defined, otherwise expires header if defined, and null if not.
16
- * @param {Map<string, string>} headers
17
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
18
- * @return {?number}
19
- */
20
- static computeCacheLifetimeInSeconds(headers: Map<string, string>, cacheControl: ReturnType<typeof parseCacheControl>): number | null;
21
- /**
22
- * Given a network record, returns whether we believe the asset is cacheable, i.e. it was a network
23
- * request that satisifed the conditions:
24
- *
25
- * 1. Has a cacheable status code
26
- * 2. Has a resource type that corresponds to static assets (image, script, stylesheet, etc).
27
- *
28
- * Allowing assets with a query string is debatable, PSI considered them non-cacheable with a similar
29
- * caveat.
30
- *
31
- * TODO: Investigate impact in HTTPArchive, experiment with this policy to see what changes.
32
- *
33
- * @param {LH.Artifacts.NetworkRequest} record
34
- * @return {boolean}
35
- */
36
- static isCacheableAsset(record: LH.Artifacts.NetworkRequest): boolean;
37
- /**
38
- * Returns true if headers suggest a record should not be cached for a long time.
39
- * @param {Map<string, string>} headers
40
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
41
- * @return {boolean}
42
- */
43
- static shouldSkipRecord(headers: Map<string, string>, cacheControl: ReturnType<typeof parseCacheControl>): boolean;
44
- /**
45
- * @param {LH.Artifacts} artifacts
46
- * @param {LH.Audit.Context} context
47
- * @return {Promise<LH.Audit.Product>}
48
- */
49
- static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
50
- }
51
- export namespace UIStrings {
52
- let title: string;
53
- let failureTitle: string;
54
- let description: string;
55
- let displayValue: string;
56
- }
57
- import { Audit } from '../audit.js';
58
- import parseCacheControl from 'parse-cache-control';
59
- //# sourceMappingURL=uses-long-cache-ttl.d.ts.map