lighthouse 12.8.2-dev.20251005 → 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.
- package/cli/test/smokehouse/config/exclusions.js +0 -2
- package/core/audits/audit.js +0 -1
- package/core/audits/insights/cls-culprits-insight.js +1 -1
- package/core/audits/insights/dom-size-insight.js +6 -6
- package/core/audits/redirects.js +1 -0
- package/core/audits/server-response-time.d.ts +0 -5
- package/core/audits/server-response-time.js +12 -26
- package/core/computed/metrics/lcp-breakdown.js +1 -0
- package/core/config/default-config.js +20 -63
- package/core/config/experimental-config.js +1 -26
- package/core/config/filters.js +6 -9
- package/core/config/lr-desktop-config.js +0 -1
- package/core/config/lr-mobile-config.js +0 -1
- package/core/gather/gatherers/trace-elements.js +1 -0
- package/core/lib/proto-preprocessor.js +5 -22
- package/dist/report/bundle.esm.js +10 -49
- package/dist/report/flow.js +12 -51
- package/dist/report/standalone.js +11 -50
- package/flow-report/src/i18n/i18n.d.ts +4 -6
- package/package.json +3 -3
- package/report/assets/styles.css +0 -39
- package/report/renderer/api.js +0 -1
- package/report/renderer/category-renderer.js +6 -0
- package/report/renderer/components.js +1 -1
- package/report/renderer/dom.d.ts +0 -13
- package/report/renderer/dom.js +0 -38
- package/report/renderer/performance-category-renderer.d.ts +0 -26
- package/report/renderer/performance-category-renderer.js +10 -142
- package/report/renderer/report-ui-features.d.ts +0 -1
- package/report/renderer/report-ui-features.js +3 -13
- package/report/renderer/report-utils.d.ts +2 -3
- package/report/renderer/report-utils.js +4 -6
- package/report/types/report-renderer.d.ts +0 -6
- package/shared/localization/locales/ar-XB.json +0 -330
- package/shared/localization/locales/ar.json +0 -330
- package/shared/localization/locales/bg.json +0 -330
- package/shared/localization/locales/ca.json +0 -330
- package/shared/localization/locales/cs.json +0 -330
- package/shared/localization/locales/da.json +0 -330
- package/shared/localization/locales/de.json +0 -330
- package/shared/localization/locales/el.json +0 -330
- package/shared/localization/locales/en-GB.json +0 -330
- package/shared/localization/locales/en-US.json +26 -275
- package/shared/localization/locales/en-XA.json +0 -330
- package/shared/localization/locales/en-XL.json +26 -275
- package/shared/localization/locales/es-419.json +0 -330
- package/shared/localization/locales/es.json +0 -330
- package/shared/localization/locales/fi.json +0 -330
- package/shared/localization/locales/fil.json +0 -330
- package/shared/localization/locales/fr.json +0 -330
- package/shared/localization/locales/he.json +0 -330
- package/shared/localization/locales/hi.json +0 -330
- package/shared/localization/locales/hr.json +0 -330
- package/shared/localization/locales/hu.json +0 -330
- package/shared/localization/locales/id.json +0 -330
- package/shared/localization/locales/it.json +0 -330
- package/shared/localization/locales/ja.json +0 -330
- package/shared/localization/locales/ko.json +0 -330
- package/shared/localization/locales/lt.json +0 -330
- package/shared/localization/locales/lv.json +0 -330
- package/shared/localization/locales/nl.json +0 -330
- package/shared/localization/locales/no.json +0 -330
- package/shared/localization/locales/pl.json +0 -330
- package/shared/localization/locales/pt-PT.json +0 -330
- package/shared/localization/locales/pt.json +0 -330
- package/shared/localization/locales/ro.json +0 -330
- package/shared/localization/locales/ru.json +0 -330
- package/shared/localization/locales/sk.json +0 -330
- package/shared/localization/locales/sl.json +0 -330
- package/shared/localization/locales/sr-Latn.json +0 -330
- package/shared/localization/locales/sr.json +0 -330
- package/shared/localization/locales/sv.json +0 -330
- package/shared/localization/locales/ta.json +0 -330
- package/shared/localization/locales/te.json +0 -330
- package/shared/localization/locales/th.json +0 -330
- package/shared/localization/locales/tr.json +0 -330
- package/shared/localization/locales/uk.json +0 -330
- package/shared/localization/locales/vi.json +0 -330
- package/shared/localization/locales/zh-HK.json +0 -330
- package/shared/localization/locales/zh-TW.json +0 -330
- package/shared/localization/locales/zh.json +0 -330
- package/types/artifacts.d.ts +1 -0
- package/types/audit.d.ts +1 -1
- package/types/lhr/settings.d.ts +1 -1
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -45
- package/core/audits/byte-efficiency/duplicated-javascript.js +0 -223
- package/core/audits/byte-efficiency/efficient-animated-content.d.ts +0 -22
- package/core/audits/byte-efficiency/efficient-animated-content.js +0 -93
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -28
- package/core/audits/byte-efficiency/legacy-javascript.js +0 -144
- package/core/audits/byte-efficiency/modern-image-formats.d.ts +0 -38
- package/core/audits/byte-efficiency/modern-image-formats.js +0 -187
- package/core/audits/byte-efficiency/render-blocking-resources.d.ts +0 -53
- package/core/audits/byte-efficiency/render-blocking-resources.js +0 -312
- package/core/audits/byte-efficiency/uses-long-cache-ttl.d.ts +0 -59
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +0 -293
- package/core/audits/byte-efficiency/uses-optimized-images.d.ts +0 -33
- package/core/audits/byte-efficiency/uses-optimized-images.js +0 -146
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.d.ts +0 -16
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +0 -106
- package/core/audits/byte-efficiency/uses-responsive-images.d.ts +0 -44
- package/core/audits/byte-efficiency/uses-responsive-images.js +0 -202
- package/core/audits/byte-efficiency/uses-text-compression.d.ts +0 -14
- package/core/audits/byte-efficiency/uses-text-compression.js +0 -108
- package/core/audits/critical-request-chains.d.ts +0 -44
- package/core/audits/critical-request-chains.js +0 -221
- package/core/audits/dobetterweb/dom-size.d.ts +0 -32
- package/core/audits/dobetterweb/dom-size.js +0 -182
- package/core/audits/dobetterweb/uses-http2.d.ts +0 -72
- package/core/audits/dobetterweb/uses-http2.js +0 -276
- package/core/audits/font-display.d.ts +0 -32
- package/core/audits/font-display.js +0 -195
- package/core/audits/largest-contentful-paint-element.d.ts +0 -34
- package/core/audits/largest-contentful-paint-element.js +0 -181
- package/core/audits/lcp-lazy-loaded.d.ts +0 -22
- package/core/audits/lcp-lazy-loaded.js +0 -115
- package/core/audits/prioritize-lcp-image.d.ts +0 -74
- package/core/audits/prioritize-lcp-image.js +0 -297
- package/core/audits/third-party-summary.d.ts +0 -78
- package/core/audits/third-party-summary.js +0 -236
- package/core/audits/uses-rel-preconnect.d.ts +0 -37
- package/core/audits/uses-rel-preconnect.js +0 -286
- package/core/audits/viewport.d.ts +0 -17
- package/core/audits/viewport.js +0 -87
- package/core/audits/work-during-interaction.d.ts +0 -81
- package/core/audits/work-during-interaction.js +0 -287
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2017 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {Audit} from './audit.js';
|
|
8
|
-
import UrlUtils from '../lib/url-utils.js';
|
|
9
|
-
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
-
import {Sentry} from '../lib/sentry.js';
|
|
11
|
-
import {NetworkRecords} from '../computed/network-records.js';
|
|
12
|
-
|
|
13
|
-
const PASSING_FONT_DISPLAY_REGEX = /^(block|fallback|optional|swap)$/;
|
|
14
|
-
const CSS_URL_REGEX = /url\((.*?)\)/;
|
|
15
|
-
const CSS_URL_GLOBAL_REGEX = new RegExp(CSS_URL_REGEX, 'g');
|
|
16
|
-
|
|
17
|
-
const UIStrings = {
|
|
18
|
-
/** Title of a diagnostic audit that provides detail on if all the text on a webpage was visible while the page was loading its webfonts. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
19
|
-
title: 'All text remains visible during webfont loads',
|
|
20
|
-
/** Title of a diagnostic audit that provides detail on the load of the page's webfonts. Often the text is invisible for seconds before the webfont resource is loaded. This imperative title is shown to users when there is a significant amount of execution time that could be reduced. */
|
|
21
|
-
failureTitle: 'Ensure text remains visible during webfont load',
|
|
22
|
-
/** Description of a Lighthouse audit that tells the user *why* they should use the font-display CSS feature. 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. */
|
|
23
|
-
description:
|
|
24
|
-
'Leverage the `font-display` CSS feature to ensure text is user-visible while ' +
|
|
25
|
-
'webfonts are loading. ' +
|
|
26
|
-
'[Learn more about `font-display`](https://developer.chrome.com/docs/lighthouse/performance/font-display/).',
|
|
27
|
-
/**
|
|
28
|
-
* @description [ICU Syntax] A warning message that is shown when Lighthouse couldn't automatically check some of the page's fonts, telling the user that they will need to manually check the fonts coming from a certain URL origin.
|
|
29
|
-
* @example {https://font.cdn.com/} fontOrigin
|
|
30
|
-
*/
|
|
31
|
-
undeclaredFontOriginWarning:
|
|
32
|
-
'{fontCountForOrigin, plural, ' +
|
|
33
|
-
// eslint-disable-next-line max-len
|
|
34
|
-
'=1 {Lighthouse was unable to automatically check the `font-display` value for the origin {fontOrigin}.} ' +
|
|
35
|
-
// eslint-disable-next-line max-len
|
|
36
|
-
'other {Lighthouse was unable to automatically check the `font-display` values for the origin {fontOrigin}.}}',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
40
|
-
|
|
41
|
-
class FontDisplay extends Audit {
|
|
42
|
-
/**
|
|
43
|
-
* @return {LH.Audit.Meta}
|
|
44
|
-
*/
|
|
45
|
-
static get meta() {
|
|
46
|
-
return {
|
|
47
|
-
id: 'font-display',
|
|
48
|
-
title: str_(UIStrings.title),
|
|
49
|
-
failureTitle: str_(UIStrings.failureTitle),
|
|
50
|
-
description: str_(UIStrings.description),
|
|
51
|
-
supportedModes: ['navigation'],
|
|
52
|
-
guidanceLevel: 3,
|
|
53
|
-
requiredArtifacts: ['DevtoolsLog', 'Stylesheets', 'URL'],
|
|
54
|
-
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @param {LH.Artifacts} artifacts
|
|
60
|
-
* @param {RegExp} passingFontDisplayRegex
|
|
61
|
-
* @return {{passingURLs: Set<string>, failingURLs: Set<string>}}
|
|
62
|
-
*/
|
|
63
|
-
static findFontDisplayDeclarations(artifacts, passingFontDisplayRegex) {
|
|
64
|
-
/** @type {Set<string>} */
|
|
65
|
-
const passingURLs = new Set();
|
|
66
|
-
/** @type {Set<string>} */
|
|
67
|
-
const failingURLs = new Set();
|
|
68
|
-
|
|
69
|
-
// Go through all the stylesheets to find all @font-face declarations
|
|
70
|
-
for (const stylesheet of artifacts.Stylesheets) {
|
|
71
|
-
// Eliminate newlines so we can more easily scan through with a regex
|
|
72
|
-
const newlinesStripped = stylesheet.content.replace(/(\r|\n)+/g, ' ');
|
|
73
|
-
// Find the @font-faces
|
|
74
|
-
const fontFaceDeclarations = newlinesStripped.match(/@font-face\s*{(.*?)}/g) || [];
|
|
75
|
-
// Go through all the @font-face declarations to find a declared `font-display: ` property
|
|
76
|
-
for (const declaration of fontFaceDeclarations) {
|
|
77
|
-
// We'll try to find the URL it's referencing.
|
|
78
|
-
const rawFontURLs = declaration.match(CSS_URL_GLOBAL_REGEX);
|
|
79
|
-
// If no URLs, we can't really do anything; bail
|
|
80
|
-
if (!rawFontURLs) continue;
|
|
81
|
-
// Find the font-display value by matching a single token, optionally surrounded by whitespace,
|
|
82
|
-
// followed either by a semicolon or the end of a block.
|
|
83
|
-
const fontDisplayMatch = declaration.match(/font-display\s*:\s*(\w+)\s*(;|\})/);
|
|
84
|
-
const rawFontDisplay = fontDisplayMatch?.[1] || '';
|
|
85
|
-
const hasPassingFontDisplay = passingFontDisplayRegex.test(rawFontDisplay);
|
|
86
|
-
const targetURLSet = hasPassingFontDisplay ? passingURLs : failingURLs;
|
|
87
|
-
|
|
88
|
-
// Finally convert the raw font URLs to the absolute URLs and add them to the set.
|
|
89
|
-
const relativeURLs = rawFontURLs
|
|
90
|
-
// @ts-expect-error - guaranteed to match from previous regex, pull URL group out
|
|
91
|
-
.map(s => s.match(CSS_URL_REGEX)[1].trim())
|
|
92
|
-
.map(s => {
|
|
93
|
-
// remove any quotes surrounding the URL
|
|
94
|
-
if (/^('|").*\1$/.test(s)) {
|
|
95
|
-
return s.substr(1, s.length - 2);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return s;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Convert the relative CSS URL to an absolute URL and add it to the target set.
|
|
102
|
-
for (const relativeURL of relativeURLs) {
|
|
103
|
-
try {
|
|
104
|
-
const relativeRoot = UrlUtils.isValid(stylesheet.header.sourceURL) ?
|
|
105
|
-
stylesheet.header.sourceURL : artifacts.URL.finalDisplayedUrl;
|
|
106
|
-
const absoluteURL = new URL(relativeURL, relativeRoot);
|
|
107
|
-
targetURLSet.add(absoluteURL.href);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
Sentry.captureException(err, {tags: {audit: this.meta.id}});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return {passingURLs, failingURLs};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Some pages load many fonts we can't check, so dedupe on origin.
|
|
120
|
-
* @param {Array<string>} warningUrls
|
|
121
|
-
* @return {Array<LH.IcuMessage>}
|
|
122
|
-
*/
|
|
123
|
-
static getWarningsForFontUrls(warningUrls) {
|
|
124
|
-
/** @type {Map<string, number>} */
|
|
125
|
-
const warningCountByOrigin = new Map();
|
|
126
|
-
for (const warningUrl of warningUrls) {
|
|
127
|
-
const origin = UrlUtils.getOrigin(warningUrl);
|
|
128
|
-
if (!origin) continue;
|
|
129
|
-
|
|
130
|
-
const count = warningCountByOrigin.get(origin) || 0;
|
|
131
|
-
warningCountByOrigin.set(origin, count + 1);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const warnings = [...warningCountByOrigin].map(([fontOrigin, fontCountForOrigin]) => {
|
|
135
|
-
return str_(UIStrings.undeclaredFontOriginWarning, {fontCountForOrigin, fontOrigin});
|
|
136
|
-
});
|
|
137
|
-
return warnings;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* @param {LH.Artifacts} artifacts
|
|
142
|
-
* @param {LH.Audit.Context} context
|
|
143
|
-
* @return {Promise<LH.Audit.Product>}
|
|
144
|
-
*/
|
|
145
|
-
static async audit(artifacts, context) {
|
|
146
|
-
const devtoolsLogs = artifacts.DevtoolsLog;
|
|
147
|
-
const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
|
|
148
|
-
const {passingURLs, failingURLs} =
|
|
149
|
-
FontDisplay.findFontDisplayDeclarations(artifacts, PASSING_FONT_DISPLAY_REGEX);
|
|
150
|
-
/** @type {Array<string>} */
|
|
151
|
-
const warningURLs = [];
|
|
152
|
-
|
|
153
|
-
const results = networkRecords
|
|
154
|
-
// Find all fonts...
|
|
155
|
-
.filter(record => record.resourceType === 'Font')
|
|
156
|
-
// ...and that aren't data URLs, the blocking concern doesn't really apply
|
|
157
|
-
.filter(record => !/^data:/.test(record.url))
|
|
158
|
-
.filter(record => !/^blob:/.test(record.url))
|
|
159
|
-
// ...that have a failing font-display value
|
|
160
|
-
.filter(record => {
|
|
161
|
-
// Failing URLs should be considered.
|
|
162
|
-
if (failingURLs.has(record.url)) return true;
|
|
163
|
-
// Everything else shouldn't be, but we should warn if we don't recognize the URL at all.
|
|
164
|
-
if (!passingURLs.has(record.url)) warningURLs.push(record.url);
|
|
165
|
-
return false;
|
|
166
|
-
})
|
|
167
|
-
.map(record => {
|
|
168
|
-
// In reality the end time should be calculated with paint time included
|
|
169
|
-
// all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
|
|
170
|
-
const wastedMs = Math.min(record.networkEndTime - record.networkRequestTime, 3000);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
url: record.url,
|
|
174
|
-
wastedMs,
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
179
|
-
const headings = [
|
|
180
|
-
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
181
|
-
{key: 'wastedMs', valueType: 'ms', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
const details = Audit.makeTableDetails(headings, results);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
score: Number(results.length === 0),
|
|
188
|
-
details,
|
|
189
|
-
warnings: FontDisplay.getWarningsForFontUrls(warningURLs),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export default FontDisplay;
|
|
195
|
-
export {UIStrings};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export default LargestContentfulPaintElement;
|
|
2
|
-
declare class LargestContentfulPaintElement extends Audit {
|
|
3
|
-
/**
|
|
4
|
-
* @param {LH.Artifacts} artifacts
|
|
5
|
-
* @return {LH.Audit.Details.Table|undefined}
|
|
6
|
-
*/
|
|
7
|
-
static makeElementTable(artifacts: LH.Artifacts): LH.Audit.Details.Table | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* @param {number} metricLcp
|
|
10
|
-
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
11
|
-
* @param {LH.Audit.Context} context
|
|
12
|
-
* @return {Promise<LH.Audit.Details.Table>}
|
|
13
|
-
*/
|
|
14
|
-
static makePhaseTable(metricLcp: number, metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<LH.Audit.Details.Table>;
|
|
15
|
-
/**
|
|
16
|
-
* @param {LH.Artifacts} artifacts
|
|
17
|
-
* @param {LH.Audit.Context} context
|
|
18
|
-
* @return {Promise<LH.Audit.Product>}
|
|
19
|
-
*/
|
|
20
|
-
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
21
|
-
}
|
|
22
|
-
export namespace UIStrings {
|
|
23
|
-
let title: string;
|
|
24
|
-
let description: string;
|
|
25
|
-
let columnPhase: string;
|
|
26
|
-
let columnPercentOfLCP: string;
|
|
27
|
-
let columnTiming: string;
|
|
28
|
-
let itemTTFB: string;
|
|
29
|
-
let itemLoadDelay: string;
|
|
30
|
-
let itemLoadTime: string;
|
|
31
|
-
let itemRenderDelay: string;
|
|
32
|
-
}
|
|
33
|
-
import { Audit } from './audit.js';
|
|
34
|
-
//# sourceMappingURL=largest-contentful-paint-element.d.ts.map
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2020 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import log from 'lighthouse-logger';
|
|
8
|
-
|
|
9
|
-
import {Audit} from './audit.js';
|
|
10
|
-
import * as i18n from '../lib/i18n/i18n.js';
|
|
11
|
-
import {LargestContentfulPaint as LargestContentfulPaintComputed} from '../computed/metrics/largest-contentful-paint.js';
|
|
12
|
-
import LargestContentfulPaint from './metrics/largest-contentful-paint.js';
|
|
13
|
-
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
14
|
-
import {Sentry} from '../lib/sentry.js';
|
|
15
|
-
|
|
16
|
-
const UIStrings = {
|
|
17
|
-
/** Descriptive title of a diagnostic audit that provides the element that was determined to be the Largest Contentful Paint. */
|
|
18
|
-
title: 'Largest Contentful Paint element',
|
|
19
|
-
/** Description of a Lighthouse audit that tells the user that the element shown was determined to be the Largest Contentful Paint. */
|
|
20
|
-
description: 'This is the largest contentful element painted within the viewport. ' +
|
|
21
|
-
'[Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)',
|
|
22
|
-
/** Label for a column in a data table; entries will be the name of a phase in the Largest Contentful Paint (LCP) metric. */
|
|
23
|
-
columnPhase: 'Phase',
|
|
24
|
-
/** Label for a column in a data table; entries will be the percent of Largest Contentful Paint (LCP) that a phase covers. */
|
|
25
|
-
columnPercentOfLCP: '% of LCP',
|
|
26
|
-
/** Label for a column in a data table; entries will be the amount of time spent in a phase in the Largest Contentful Paint (LCP) metric. */
|
|
27
|
-
columnTiming: 'Timing',
|
|
28
|
-
/** Table item value for the Time To First Byte (TTFB) phase of the Largest Contentful Paint (LCP) metric. */
|
|
29
|
-
itemTTFB: 'TTFB',
|
|
30
|
-
/** Table item value for the load delay phase of the Largest Contentful Paint (LCP) metric. */
|
|
31
|
-
itemLoadDelay: 'Load Delay',
|
|
32
|
-
/** Table item value for the load time phase of the Largest Contentful Paint (LCP) metric. */
|
|
33
|
-
itemLoadTime: 'Load Time',
|
|
34
|
-
/** Table item value for the render delay phase of the Largest Contentful Paint (LCP) metric. */
|
|
35
|
-
itemRenderDelay: 'Render Delay',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
39
|
-
|
|
40
|
-
class LargestContentfulPaintElement extends Audit {
|
|
41
|
-
/**
|
|
42
|
-
* @return {LH.Audit.Meta}
|
|
43
|
-
*/
|
|
44
|
-
static get meta() {
|
|
45
|
-
return {
|
|
46
|
-
id: 'largest-contentful-paint-element',
|
|
47
|
-
title: str_(UIStrings.title),
|
|
48
|
-
description: str_(UIStrings.description),
|
|
49
|
-
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
50
|
-
guidanceLevel: 1,
|
|
51
|
-
supportedModes: ['navigation'],
|
|
52
|
-
requiredArtifacts:
|
|
53
|
-
['Trace', 'TraceElements', 'DevtoolsLog', 'GatherContext', 'settings', 'URL',
|
|
54
|
-
'SourceMaps'],
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @param {LH.Artifacts} artifacts
|
|
60
|
-
* @return {LH.Audit.Details.Table|undefined}
|
|
61
|
-
*/
|
|
62
|
-
static makeElementTable(artifacts) {
|
|
63
|
-
const lcpElement = artifacts.TraceElements
|
|
64
|
-
.find(element => element.traceEventType === 'largest-contentful-paint');
|
|
65
|
-
if (!lcpElement) return;
|
|
66
|
-
|
|
67
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
68
|
-
const headings = [
|
|
69
|
-
{key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
const lcpElementDetails = [{node: Audit.makeNodeItem(lcpElement.node)}];
|
|
73
|
-
|
|
74
|
-
return Audit.makeTableDetails(headings, lcpElementDetails);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @param {number} metricLcp
|
|
79
|
-
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
80
|
-
* @param {LH.Audit.Context} context
|
|
81
|
-
* @return {Promise<LH.Audit.Details.Table>}
|
|
82
|
-
*/
|
|
83
|
-
static async makePhaseTable(metricLcp, metricComputationData, context) {
|
|
84
|
-
const {ttfb, loadStart, loadEnd} = await LCPBreakdown.request(metricComputationData, context);
|
|
85
|
-
|
|
86
|
-
let loadDelay = 0;
|
|
87
|
-
let loadTime = 0;
|
|
88
|
-
let renderDelay = metricLcp - ttfb;
|
|
89
|
-
|
|
90
|
-
if (loadStart && loadEnd) {
|
|
91
|
-
loadDelay = loadStart - ttfb;
|
|
92
|
-
loadTime = loadEnd - loadStart;
|
|
93
|
-
renderDelay = metricLcp - loadEnd;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const results = [
|
|
97
|
-
{phase: str_(UIStrings.itemTTFB), timing: ttfb},
|
|
98
|
-
{phase: str_(UIStrings.itemLoadDelay), timing: loadDelay},
|
|
99
|
-
{phase: str_(UIStrings.itemLoadTime), timing: loadTime},
|
|
100
|
-
{phase: str_(UIStrings.itemRenderDelay), timing: renderDelay},
|
|
101
|
-
].map(result => {
|
|
102
|
-
const percent = 100 * result.timing / metricLcp;
|
|
103
|
-
const percentStr = `${percent.toFixed(0)}%`;
|
|
104
|
-
return {...result, percent: percentStr};
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
108
|
-
const headings = [
|
|
109
|
-
{key: 'phase', valueType: 'text', label: str_(UIStrings.columnPhase)},
|
|
110
|
-
{key: 'percent', valueType: 'text', label: str_(UIStrings.columnPercentOfLCP)},
|
|
111
|
-
{key: 'timing', valueType: 'ms', label: str_(UIStrings.columnTiming)},
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
return Audit.makeTableDetails(headings, results);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {LH.Artifacts} artifacts
|
|
119
|
-
* @param {LH.Audit.Context} context
|
|
120
|
-
* @return {Promise<LH.Audit.Product>}
|
|
121
|
-
*/
|
|
122
|
-
static async audit(artifacts, context) {
|
|
123
|
-
const trace = artifacts.Trace;
|
|
124
|
-
const devtoolsLog = artifacts.DevtoolsLog;
|
|
125
|
-
const gatherContext = artifacts.GatherContext;
|
|
126
|
-
const metricComputationData = {
|
|
127
|
-
trace, devtoolsLog, gatherContext,
|
|
128
|
-
settings: context.settings, URL: artifacts.URL,
|
|
129
|
-
SourceMaps: artifacts.SourceMaps, simulator: null,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const elementTable = this.makeElementTable(artifacts);
|
|
133
|
-
if (!elementTable) {
|
|
134
|
-
return {
|
|
135
|
-
score: null,
|
|
136
|
-
notApplicable: true,
|
|
137
|
-
metricSavings: {LCP: 0},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const items = [elementTable];
|
|
142
|
-
let displayValue;
|
|
143
|
-
let metricLcp = 0;
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const lcpResult =
|
|
147
|
-
await LargestContentfulPaintComputed.request(metricComputationData, context);
|
|
148
|
-
metricLcp = lcpResult.timing;
|
|
149
|
-
displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
|
|
150
|
-
|
|
151
|
-
const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
|
|
152
|
-
items.push(phaseTable);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
Sentry.captureException(err, {
|
|
155
|
-
tags: {audit: this.meta.id},
|
|
156
|
-
level: 'error',
|
|
157
|
-
});
|
|
158
|
-
log.error(this.meta.id, err.message);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const details = Audit.makeListDetails(items);
|
|
162
|
-
|
|
163
|
-
// Conceptually, this doesn't make much sense as "savings" for this audit since there isn't anything to "fix".
|
|
164
|
-
// However, this audit will always be useful when improving LCP and that should be reflected in our impact calculations.
|
|
165
|
-
const idealLcp = LargestContentfulPaint.defaultOptions[context.settings.formFactor].scoring.p10;
|
|
166
|
-
const lcpSavings = Math.max(0, metricLcp - idealLcp);
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
score: lcpSavings ? 0 : 1,
|
|
170
|
-
scoreDisplayMode: lcpSavings ? undefined : Audit.SCORING_MODES.INFORMATIVE,
|
|
171
|
-
displayValue,
|
|
172
|
-
details,
|
|
173
|
-
metricSavings: {
|
|
174
|
-
LCP: lcpSavings,
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export default LargestContentfulPaintElement;
|
|
181
|
-
export {UIStrings};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export default LargestContentfulPaintLazyLoaded;
|
|
2
|
-
declare class LargestContentfulPaintLazyLoaded extends Audit {
|
|
3
|
-
/**
|
|
4
|
-
* @param {LH.Artifacts.ImageElement} image
|
|
5
|
-
* @param {LH.Artifacts.ViewportDimensions} viewportDimensions
|
|
6
|
-
* @return {boolean}
|
|
7
|
-
*/
|
|
8
|
-
static isImageInViewport(image: LH.Artifacts.ImageElement, viewportDimensions: LH.Artifacts.ViewportDimensions): boolean;
|
|
9
|
-
/**
|
|
10
|
-
* @param {LH.Artifacts} artifacts
|
|
11
|
-
* @param {LH.Audit.Context} context
|
|
12
|
-
* @return {Promise<LH.Audit.Product>}
|
|
13
|
-
*/
|
|
14
|
-
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
15
|
-
}
|
|
16
|
-
export namespace UIStrings {
|
|
17
|
-
let title: string;
|
|
18
|
-
let failureTitle: string;
|
|
19
|
-
let description: string;
|
|
20
|
-
}
|
|
21
|
-
import { Audit } from './audit.js';
|
|
22
|
-
//# sourceMappingURL=lcp-lazy-loaded.d.ts.map
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2021 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {Audit} from './audit.js';
|
|
8
|
-
import * as i18n from '../lib/i18n/i18n.js';
|
|
9
|
-
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
10
|
-
import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
|
|
11
|
-
|
|
12
|
-
const UIStrings = {
|
|
13
|
-
/** Title of a Lighthouse audit that provides detail on whether the largest above-the-fold image was loaded with sufficient priority. This descriptive title is shown to users when the image was loaded properly. */
|
|
14
|
-
title: 'Largest Contentful Paint image was not lazily loaded',
|
|
15
|
-
/** Title of a Lighthouse audit that provides detail on whether the largest above-the-fold image was loaded with sufficient priority. This descriptive title is shown to users when the image was loaded inefficiently using the `loading=lazy` attribute. */
|
|
16
|
-
failureTitle: 'Largest Contentful Paint image was lazily loaded',
|
|
17
|
-
/** Description of a Lighthouse audit that tells the user why the advice is important. This is displayed after a user expands the section to see more. No character length limits. */
|
|
18
|
-
description: 'Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading).',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
22
|
-
|
|
23
|
-
const ESTIMATED_PERCENT_SAVINGS = 0.15;
|
|
24
|
-
|
|
25
|
-
class LargestContentfulPaintLazyLoaded extends Audit {
|
|
26
|
-
/**
|
|
27
|
-
* @return {LH.Audit.Meta}
|
|
28
|
-
*/
|
|
29
|
-
static get meta() {
|
|
30
|
-
return {
|
|
31
|
-
id: 'lcp-lazy-loaded',
|
|
32
|
-
title: str_(UIStrings.title),
|
|
33
|
-
failureTitle: str_(UIStrings.failureTitle),
|
|
34
|
-
description: str_(UIStrings.description),
|
|
35
|
-
supportedModes: ['navigation'],
|
|
36
|
-
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
37
|
-
guidanceLevel: 3,
|
|
38
|
-
requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements',
|
|
39
|
-
'Trace', 'DevtoolsLog', 'GatherContext', 'URL', 'SourceMaps'],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @param {LH.Artifacts.ImageElement} image
|
|
45
|
-
* @param {LH.Artifacts.ViewportDimensions} viewportDimensions
|
|
46
|
-
* @return {boolean}
|
|
47
|
-
*/
|
|
48
|
-
static isImageInViewport(image, viewportDimensions) {
|
|
49
|
-
const imageTop = image.clientRect.top;
|
|
50
|
-
const viewportHeight = viewportDimensions.innerHeight;
|
|
51
|
-
return imageTop < viewportHeight;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @param {LH.Artifacts} artifacts
|
|
56
|
-
* @param {LH.Audit.Context} context
|
|
57
|
-
* @return {Promise<LH.Audit.Product>}
|
|
58
|
-
*/
|
|
59
|
-
static async audit(artifacts, context) {
|
|
60
|
-
const lcpElement = artifacts.TraceElements.find(element => {
|
|
61
|
-
return element.traceEventType === 'largest-contentful-paint' && element.type === 'image';
|
|
62
|
-
});
|
|
63
|
-
const lcpElementImage = lcpElement ? artifacts.ImageElements.find(elem => {
|
|
64
|
-
return elem.node.devtoolsNodePath === lcpElement.node.devtoolsNodePath;
|
|
65
|
-
}) : undefined;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!lcpElementImage ||
|
|
69
|
-
!this.isImageInViewport(lcpElementImage, artifacts.ViewportDimensions)) {
|
|
70
|
-
return {
|
|
71
|
-
score: null,
|
|
72
|
-
notApplicable: true,
|
|
73
|
-
metricSavings: {LCP: 0},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
78
|
-
const headings = [
|
|
79
|
-
{key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const details = Audit.makeTableDetails(headings, [
|
|
83
|
-
{
|
|
84
|
-
node: Audit.makeNodeItem(lcpElementImage.node),
|
|
85
|
-
},
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
|
-
const wasLazyLoaded = lcpElementImage.loading === 'lazy';
|
|
89
|
-
|
|
90
|
-
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
91
|
-
const {timing: metricLcp} =
|
|
92
|
-
await LargestContentfulPaint.request(metricComputationData, context);
|
|
93
|
-
const lcpBreakdown = await LCPBreakdown.request(metricComputationData, context);
|
|
94
|
-
let lcpSavings = 0;
|
|
95
|
-
if (wasLazyLoaded && lcpBreakdown.loadStart !== undefined) {
|
|
96
|
-
// Estimate the LCP savings using a statistical percentage.
|
|
97
|
-
// https://web.dev/articles/lcp-lazy-loading#causal_performance
|
|
98
|
-
//
|
|
99
|
-
// LCP savings will be at most the LCP load delay.
|
|
100
|
-
const lcpLoadDelay = lcpBreakdown.loadStart - lcpBreakdown.ttfb;
|
|
101
|
-
lcpSavings = Math.min(metricLcp * ESTIMATED_PERCENT_SAVINGS, lcpLoadDelay);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
score: wasLazyLoaded ? 0 : 1,
|
|
106
|
-
metricSavings: {
|
|
107
|
-
LCP: lcpSavings,
|
|
108
|
-
},
|
|
109
|
-
details,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export default LargestContentfulPaintLazyLoaded;
|
|
115
|
-
export {UIStrings};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export default PrioritizeLcpImage;
|
|
2
|
-
export type InitiatorType = LH.Crdp.Network.Initiator["type"] | "redirect" | "fallbackToMain";
|
|
3
|
-
export type InitiatorPath = Array<{
|
|
4
|
-
url: string;
|
|
5
|
-
initiatorType: InitiatorType;
|
|
6
|
-
}>;
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {LH.Crdp.Network.Initiator['type']|'redirect'|'fallbackToMain'} InitiatorType
|
|
9
|
-
* @typedef {Array<{url: string, initiatorType: InitiatorType}>} InitiatorPath
|
|
10
|
-
*/
|
|
11
|
-
declare class PrioritizeLcpImage extends Audit {
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {LH.Artifacts.NetworkRequest} request
|
|
15
|
-
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
16
|
-
* @param {InitiatorPath} initiatorPath
|
|
17
|
-
* @return {boolean}
|
|
18
|
-
*/
|
|
19
|
-
static shouldPreloadRequest(request: LH.Artifacts.NetworkRequest, mainResource: LH.Artifacts.NetworkRequest, initiatorPath: InitiatorPath): boolean;
|
|
20
|
-
/**
|
|
21
|
-
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
22
|
-
* @param {NetworkRequest} lcpRecord
|
|
23
|
-
* @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined}
|
|
24
|
-
*/
|
|
25
|
-
static findLCPNode(graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest): LH.Gatherer.Simulation.GraphNetworkNode | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* Get the initiator path starting with lcpRecord back to mainResource, inclusive.
|
|
28
|
-
* Navigation redirects *to* the mainResource are not included.
|
|
29
|
-
* Path returned will always be at least [lcpRecord, mainResource].
|
|
30
|
-
* @param {NetworkRequest} lcpRecord
|
|
31
|
-
* @param {NetworkRequest} mainResource
|
|
32
|
-
* @return {InitiatorPath}
|
|
33
|
-
*/
|
|
34
|
-
static getLcpInitiatorPath(lcpRecord: NetworkRequest, mainResource: NetworkRequest): InitiatorPath;
|
|
35
|
-
/**
|
|
36
|
-
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
37
|
-
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
38
|
-
* @param {NetworkRequest|undefined} lcpRecord
|
|
39
|
-
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
40
|
-
*/
|
|
41
|
-
static getLCPNodeToPreload(mainResource: LH.Artifacts.NetworkRequest, graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest | undefined): {
|
|
42
|
-
lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode;
|
|
43
|
-
initiatorPath?: InitiatorPath;
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Computes the estimated effect of preloading the LCP image.
|
|
47
|
-
* @param {LH.Artifacts.TraceElement} lcpElement
|
|
48
|
-
* @param {LH.Gatherer.Simulation.GraphNetworkNode|undefined} lcpNode
|
|
49
|
-
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
50
|
-
* @param {LH.Gatherer.Simulation.Simulator} simulator
|
|
51
|
-
* @return {{wastedMs: number, results: Array<{node: LH.Audit.Details.NodeValue, url: string, wastedMs: number}>}}
|
|
52
|
-
*/
|
|
53
|
-
static computeWasteWithGraph(lcpElement: LH.Artifacts.TraceElement, lcpNode: LH.Gatherer.Simulation.GraphNetworkNode | undefined, graph: LH.Gatherer.Simulation.GraphNode, simulator: LH.Gatherer.Simulation.Simulator): {
|
|
54
|
-
wastedMs: number;
|
|
55
|
-
results: Array<{
|
|
56
|
-
node: LH.Audit.Details.NodeValue;
|
|
57
|
-
url: string;
|
|
58
|
-
wastedMs: number;
|
|
59
|
-
}>;
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* @param {LH.Artifacts} artifacts
|
|
63
|
-
* @param {LH.Audit.Context} context
|
|
64
|
-
* @return {Promise<LH.Audit.Product>}
|
|
65
|
-
*/
|
|
66
|
-
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
67
|
-
}
|
|
68
|
-
export namespace UIStrings {
|
|
69
|
-
let title: string;
|
|
70
|
-
let description: string;
|
|
71
|
-
}
|
|
72
|
-
import { Audit } from './audit.js';
|
|
73
|
-
import { NetworkRequest } from '../lib/network-request.js';
|
|
74
|
-
//# sourceMappingURL=prioritize-lcp-image.d.ts.map
|