lighthouse 9.5.0-dev.20230206 → 9.5.0-dev.20230208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/audit.d.ts +12 -5
- package/core/audits/audit.js +19 -5
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +2 -1
- package/core/audits/dobetterweb/js-libraries.js +1 -1
- package/core/audits/dobetterweb/uses-http2.js +2 -1
- package/core/audits/{preload-lcp-image.d.ts → prioritize-lcp-image.d.ts} +3 -3
- package/core/audits/{preload-lcp-image.js → prioritize-lcp-image.js} +14 -12
- package/core/audits/redirects.js +2 -1
- package/core/audits/seo/link-text.js +1 -1
- package/core/audits/seo/robots-txt.js +1 -1
- package/core/audits/server-response-time.js +1 -1
- package/core/audits/uses-rel-preconnect.js +2 -1
- package/core/audits/uses-rel-preload.js +4 -2
- package/core/audits/valid-source-maps.js +7 -3
- package/core/config/config.js +20 -1
- package/core/config/default-config.js +42 -90
- package/core/config/metrics-to-audits.js +1 -1
- package/core/gather/gatherers/full-page-screenshot.js +6 -2
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/navigation-runner.d.ts +4 -2
- package/core/gather/navigation-runner.js +16 -4
- package/core/legacy/config/config.js +18 -0
- package/core/lib/page-functions.js +2 -2
- package/dist/report/bundle.esm.js +1 -1
- package/dist/report/flow.js +11 -11
- package/dist/report/standalone.js +1 -1
- package/package.json +2 -2
- package/report/assets/styles.css +6 -3
- package/report/renderer/components.js +1 -1
- package/shared/localization/locales/ar-XB.json +982 -289
- package/shared/localization/locales/ar.json +983 -290
- package/shared/localization/locales/bg.json +981 -288
- package/shared/localization/locales/ca.json +988 -295
- package/shared/localization/locales/cs.json +983 -290
- package/shared/localization/locales/da.json +982 -289
- package/shared/localization/locales/de.json +1118 -425
- package/shared/localization/locales/el.json +981 -288
- package/shared/localization/locales/en-GB.json +982 -289
- package/shared/localization/locales/en-US.json +4 -4
- package/shared/localization/locales/en-XA.json +982 -289
- package/shared/localization/locales/en-XL.json +4 -4
- package/shared/localization/locales/es-419.json +984 -291
- package/shared/localization/locales/es.json +989 -296
- package/shared/localization/locales/fi.json +982 -289
- package/shared/localization/locales/fil.json +983 -290
- package/shared/localization/locales/fr.json +984 -291
- package/shared/localization/locales/he.json +1002 -309
- package/shared/localization/locales/hi.json +985 -292
- package/shared/localization/locales/hr.json +982 -289
- package/shared/localization/locales/hu.json +981 -288
- package/shared/localization/locales/id.json +982 -289
- package/shared/localization/locales/it.json +1002 -309
- package/shared/localization/locales/ja.json +981 -288
- package/shared/localization/locales/ko.json +982 -289
- package/shared/localization/locales/lt.json +982 -289
- package/shared/localization/locales/lv.json +982 -289
- package/shared/localization/locales/nl.json +983 -290
- package/shared/localization/locales/no.json +986 -293
- package/shared/localization/locales/pl.json +981 -288
- package/shared/localization/locales/pt-PT.json +1002 -309
- package/shared/localization/locales/pt.json +983 -290
- package/shared/localization/locales/ro.json +981 -288
- package/shared/localization/locales/ru.json +981 -288
- package/shared/localization/locales/sk.json +981 -288
- package/shared/localization/locales/sl.json +981 -288
- package/shared/localization/locales/sr-Latn.json +982 -289
- package/shared/localization/locales/sr.json +982 -289
- package/shared/localization/locales/sv.json +988 -295
- package/shared/localization/locales/ta.json +982 -289
- package/shared/localization/locales/te.json +1012 -319
- package/shared/localization/locales/th.json +990 -297
- package/shared/localization/locales/tr.json +982 -289
- package/shared/localization/locales/uk.json +981 -288
- package/shared/localization/locales/vi.json +983 -290
- package/shared/localization/locales/zh-HK.json +982 -289
- package/shared/localization/locales/zh-TW.json +985 -292
- package/shared/localization/locales/zh.json +982 -289
package/core/audits/audit.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export type TableOptions = {
|
|
2
|
+
wastedMs?: number | undefined;
|
|
3
|
+
wastedBytes?: number | undefined;
|
|
4
|
+
};
|
|
5
|
+
export type OpportunityOptions = {
|
|
6
|
+
overallSavingsMs: number;
|
|
7
|
+
overallSavingsBytes?: number | undefined;
|
|
8
|
+
};
|
|
1
9
|
export class Audit {
|
|
2
10
|
/**
|
|
3
11
|
* @return {string}
|
|
@@ -55,10 +63,10 @@ export class Audit {
|
|
|
55
63
|
/**
|
|
56
64
|
* @param {LH.Audit.Details.Table['headings']} headings
|
|
57
65
|
* @param {LH.Audit.Details.Table['items']} results
|
|
58
|
-
* @param {
|
|
66
|
+
* @param {TableOptions=} options
|
|
59
67
|
* @return {LH.Audit.Details.Table}
|
|
60
68
|
*/
|
|
61
|
-
static makeTableDetails(headings: LH.Audit.Details.Table['headings'], results: LH.Audit.Details.Table['items'],
|
|
69
|
+
static makeTableDetails(headings: LH.Audit.Details.Table['headings'], results: LH.Audit.Details.Table['items'], options?: TableOptions | undefined): LH.Audit.Details.Table;
|
|
62
70
|
/**
|
|
63
71
|
* @param {LH.Audit.Details.List['items']} items
|
|
64
72
|
* @return {LH.Audit.Details.List}
|
|
@@ -95,11 +103,10 @@ export class Audit {
|
|
|
95
103
|
/**
|
|
96
104
|
* @param {LH.Audit.Details.Opportunity['headings']} headings
|
|
97
105
|
* @param {LH.Audit.Details.Opportunity['items']} items
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {number=} overallSavingsBytes
|
|
106
|
+
* @param {OpportunityOptions} options
|
|
100
107
|
* @return {LH.Audit.Details.Opportunity}
|
|
101
108
|
*/
|
|
102
|
-
static makeOpportunityDetails(headings: LH.Audit.Details.Opportunity['headings'], items: LH.Audit.Details.Opportunity['items'],
|
|
109
|
+
static makeOpportunityDetails(headings: LH.Audit.Details.Opportunity['headings'], items: LH.Audit.Details.Opportunity['items'], options: OpportunityOptions): LH.Audit.Details.Opportunity;
|
|
103
110
|
/**
|
|
104
111
|
* @param {LH.Artifacts.NodeDetails} node
|
|
105
112
|
* @return {LH.Audit.Details.NodeValue}
|
package/core/audits/audit.js
CHANGED
|
@@ -11,6 +11,18 @@ import {Util} from '../../shared/util.js';
|
|
|
11
11
|
|
|
12
12
|
const DEFAULT_PASS = 'defaultPass';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @typedef TableOptions
|
|
16
|
+
* @property {number=} wastedMs
|
|
17
|
+
* @property {number=} wastedBytes
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef OpportunityOptions
|
|
22
|
+
* @property {number} overallSavingsMs
|
|
23
|
+
* @property {number=} overallSavingsBytes
|
|
24
|
+
*/
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* Clamp figure to 2 decimal places
|
|
16
28
|
* @param {number} val
|
|
@@ -123,10 +135,12 @@ class Audit {
|
|
|
123
135
|
/**
|
|
124
136
|
* @param {LH.Audit.Details.Table['headings']} headings
|
|
125
137
|
* @param {LH.Audit.Details.Table['items']} results
|
|
126
|
-
* @param {
|
|
138
|
+
* @param {TableOptions=} options
|
|
127
139
|
* @return {LH.Audit.Details.Table}
|
|
128
140
|
*/
|
|
129
|
-
static makeTableDetails(headings, results,
|
|
141
|
+
static makeTableDetails(headings, results, options = {}) {
|
|
142
|
+
const {wastedBytes, wastedMs} = options;
|
|
143
|
+
const summary = (wastedBytes || wastedMs) ? {wastedBytes, wastedMs} : undefined;
|
|
130
144
|
if (results.length === 0) {
|
|
131
145
|
return {
|
|
132
146
|
type: 'table',
|
|
@@ -215,12 +229,12 @@ class Audit {
|
|
|
215
229
|
/**
|
|
216
230
|
* @param {LH.Audit.Details.Opportunity['headings']} headings
|
|
217
231
|
* @param {LH.Audit.Details.Opportunity['items']} items
|
|
218
|
-
* @param {
|
|
219
|
-
* @param {number=} overallSavingsBytes
|
|
232
|
+
* @param {OpportunityOptions} options
|
|
220
233
|
* @return {LH.Audit.Details.Opportunity}
|
|
221
234
|
*/
|
|
222
|
-
static makeOpportunityDetails(headings, items,
|
|
235
|
+
static makeOpportunityDetails(headings, items, options) {
|
|
223
236
|
Audit.assertHeadingKeysExist(headings, items);
|
|
237
|
+
const {overallSavingsMs, overallSavingsBytes} = options;
|
|
224
238
|
|
|
225
239
|
return {
|
|
226
240
|
type: 'opportunity',
|
|
@@ -228,7 +228,8 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
228
228
|
displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes});
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
const details = Audit.makeOpportunityDetails(result.headings, results,
|
|
231
|
+
const details = Audit.makeOpportunityDetails(result.headings, results,
|
|
232
|
+
{overallSavingsMs: wastedMs, overallSavingsBytes: wastedBytes});
|
|
232
233
|
|
|
233
234
|
return {
|
|
234
235
|
explanation: result.explanation,
|
|
@@ -290,7 +290,8 @@ class RenderBlockingResources extends Audit {
|
|
|
290
290
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
291
291
|
];
|
|
292
292
|
|
|
293
|
-
const details = Audit.makeOpportunityDetails(headings, results,
|
|
293
|
+
const details = Audit.makeOpportunityDetails(headings, results,
|
|
294
|
+
{overallSavingsMs: wastedMs});
|
|
294
295
|
|
|
295
296
|
return {
|
|
296
297
|
displayValue,
|
|
@@ -57,7 +57,7 @@ class JsLibrariesAudit extends Audit {
|
|
|
57
57
|
{key: 'name', valueType: 'text', label: str_(i18n.UIStrings.columnName)},
|
|
58
58
|
{key: 'version', valueType: 'text', label: str_(UIStrings.columnVersion)},
|
|
59
59
|
];
|
|
60
|
-
const details = Audit.makeTableDetails(headings, libDetails
|
|
60
|
+
const details = Audit.makeTableDetails(headings, libDetails);
|
|
61
61
|
|
|
62
62
|
const debugData = {
|
|
63
63
|
type: /** @type {const} */ ('debugdata'),
|
|
@@ -247,7 +247,8 @@ class UsesHTTP2Audit extends Audit {
|
|
|
247
247
|
{key: 'protocol', valueType: 'text', label: str_(UIStrings.columnProtocol)},
|
|
248
248
|
];
|
|
249
249
|
|
|
250
|
-
const details = Audit.makeOpportunityDetails(headings, resources,
|
|
250
|
+
const details = Audit.makeOpportunityDetails(headings, resources,
|
|
251
|
+
{overallSavingsMs: wastedMs});
|
|
251
252
|
|
|
252
253
|
return {
|
|
253
254
|
displayValue,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default PrioritizeLcpImage;
|
|
2
2
|
export type InitiatorPath = Array<{
|
|
3
3
|
url: string;
|
|
4
4
|
initiatorType: string;
|
|
@@ -6,7 +6,7 @@ export type InitiatorPath = Array<{
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
|
|
8
8
|
*/
|
|
9
|
-
declare class
|
|
9
|
+
declare class PrioritizeLcpImage extends Audit {
|
|
10
10
|
/**
|
|
11
11
|
*
|
|
12
12
|
* @param {LH.Artifacts.NetworkRequest} request
|
|
@@ -73,4 +73,4 @@ export namespace UIStrings {
|
|
|
73
73
|
import { Audit } from "./audit.js";
|
|
74
74
|
import { NetworkRequest } from "../lib/network-request.js";
|
|
75
75
|
import { ProcessedNavigation } from "../computed/processed-navigation.js";
|
|
76
|
-
//# sourceMappingURL=
|
|
76
|
+
//# sourceMappingURL=prioritize-lcp-image.d.ts.map
|
|
@@ -27,13 +27,13 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
|
27
27
|
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
class
|
|
30
|
+
class PrioritizeLcpImage extends Audit {
|
|
31
31
|
/**
|
|
32
32
|
* @return {LH.Audit.Meta}
|
|
33
33
|
*/
|
|
34
34
|
static get meta() {
|
|
35
35
|
return {
|
|
36
|
-
id: '
|
|
36
|
+
id: 'prioritize-lcp-image',
|
|
37
37
|
title: str_(UIStrings.title),
|
|
38
38
|
description: str_(UIStrings.description),
|
|
39
39
|
supportedModes: ['navigation'],
|
|
@@ -92,11 +92,11 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
92
92
|
*/
|
|
93
93
|
static getLCPNodeToPreload(mainResource, graph, lcpUrl) {
|
|
94
94
|
if (!lcpUrl) return {};
|
|
95
|
-
const {lcpNode, path} =
|
|
95
|
+
const {lcpNode, path} = PrioritizeLcpImage.findLCPNode(graph, lcpUrl);
|
|
96
96
|
if (!lcpNode || !path) return {};
|
|
97
97
|
|
|
98
98
|
// eslint-disable-next-line max-len
|
|
99
|
-
const shouldPreload =
|
|
99
|
+
const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpNode.record, mainResource, path);
|
|
100
100
|
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
|
|
101
101
|
|
|
102
102
|
const initiatorPath = [
|
|
@@ -119,7 +119,8 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
119
119
|
* @return {string | undefined}
|
|
120
120
|
*/
|
|
121
121
|
static getLcpUrl(trace, processedNavigation) {
|
|
122
|
-
|
|
122
|
+
// Use main-frame-only LCP to match the metric value.
|
|
123
|
+
const lcpEvent = processedNavigation.largestContentfulPaintEvt;
|
|
123
124
|
if (!lcpEvent) return;
|
|
124
125
|
|
|
125
126
|
const lcpImagePaintEvent = trace.traceEvents.filter(e => {
|
|
@@ -229,8 +230,8 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
229
230
|
*/
|
|
230
231
|
static async audit(artifacts, context) {
|
|
231
232
|
const gatherContext = artifacts.GatherContext;
|
|
232
|
-
const trace = artifacts.traces[
|
|
233
|
-
const devtoolsLog = artifacts.devtoolsLogs[
|
|
233
|
+
const trace = artifacts.traces[PrioritizeLcpImage.DEFAULT_PASS];
|
|
234
|
+
const devtoolsLog = artifacts.devtoolsLogs[PrioritizeLcpImage.DEFAULT_PASS];
|
|
234
235
|
const URL = artifacts.URL;
|
|
235
236
|
const metricData = {trace, devtoolsLog, gatherContext, settings: context.settings, URL};
|
|
236
237
|
const lcpElement = artifacts.TraceElements
|
|
@@ -247,13 +248,13 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
247
248
|
LoadSimulator.request({devtoolsLog, settings: context.settings}, context),
|
|
248
249
|
]);
|
|
249
250
|
|
|
250
|
-
const lcpUrl =
|
|
251
|
+
const lcpUrl = PrioritizeLcpImage.getLcpUrl(trace, processedNavigation);
|
|
251
252
|
const graph = lanternLCP.pessimisticGraph;
|
|
252
253
|
// eslint-disable-next-line max-len
|
|
253
|
-
const {lcpNodeToPreload, initiatorPath} =
|
|
254
|
+
const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource, graph, lcpUrl);
|
|
254
255
|
|
|
255
256
|
const {results, wastedMs} =
|
|
256
|
-
|
|
257
|
+
PrioritizeLcpImage.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
257
258
|
|
|
258
259
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
259
260
|
const headings = [
|
|
@@ -261,7 +262,8 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
261
262
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
262
263
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
263
264
|
];
|
|
264
|
-
const details = Audit.makeOpportunityDetails(headings, results,
|
|
265
|
+
const details = Audit.makeOpportunityDetails(headings, results,
|
|
266
|
+
{overallSavingsMs: wastedMs});
|
|
265
267
|
|
|
266
268
|
// If LCP element was an image and had valid network records (regardless of
|
|
267
269
|
// if it should be preloaded), it will be found first in the `initiatorPath`.
|
|
@@ -284,5 +286,5 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
export default
|
|
289
|
+
export default PrioritizeLcpImage;
|
|
288
290
|
export {UIStrings};
|
package/core/audits/redirects.js
CHANGED
|
@@ -142,7 +142,8 @@ class Redirects extends Audit {
|
|
|
142
142
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
143
143
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnTimeSpent)},
|
|
144
144
|
];
|
|
145
|
-
const details = Audit.makeOpportunityDetails(headings, tableRows,
|
|
145
|
+
const details = Audit.makeOpportunityDetails(headings, tableRows,
|
|
146
|
+
{overallSavingsMs: totalWastedMs});
|
|
146
147
|
|
|
147
148
|
return {
|
|
148
149
|
// We award a passing grade if you only have 1 redirect
|
|
@@ -141,7 +141,7 @@ class LinkText extends Audit {
|
|
|
141
141
|
{key: 'text', valueType: 'text', label: 'Link Text'},
|
|
142
142
|
];
|
|
143
143
|
|
|
144
|
-
const details = Audit.makeTableDetails(headings, failingLinks
|
|
144
|
+
const details = Audit.makeTableDetails(headings, failingLinks);
|
|
145
145
|
let displayValue;
|
|
146
146
|
|
|
147
147
|
if (failingLinks.length) {
|
|
@@ -235,7 +235,7 @@ class RobotsTxt extends Audit {
|
|
|
235
235
|
{key: 'message', valueType: 'code', label: 'Error'},
|
|
236
236
|
];
|
|
237
237
|
|
|
238
|
-
const details = Audit.makeTableDetails(headings, validationErrors
|
|
238
|
+
const details = Audit.makeTableDetails(headings, validationErrors);
|
|
239
239
|
let displayValue;
|
|
240
240
|
|
|
241
241
|
if (validationErrors.length) {
|
|
@@ -233,7 +233,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
233
233
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
234
234
|
];
|
|
235
235
|
|
|
236
|
-
const details = Audit.makeOpportunityDetails(headings, results,
|
|
236
|
+
const details = Audit.makeOpportunityDetails(headings, results,
|
|
237
|
+
{overallSavingsMs: maxWasted});
|
|
237
238
|
|
|
238
239
|
return {
|
|
239
240
|
score: ByteEfficiencyAudit.scoreForWastedMs(maxWasted),
|
|
@@ -239,7 +239,8 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
239
239
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
240
240
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
241
241
|
];
|
|
242
|
-
const details = Audit.makeOpportunityDetails(headings, results,
|
|
242
|
+
const details = Audit.makeOpportunityDetails(headings, results,
|
|
243
|
+
{overallSavingsMs: wastedMs});
|
|
243
244
|
|
|
244
245
|
return {
|
|
245
246
|
score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
|
|
@@ -259,7 +260,8 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
259
260
|
static async audit() {
|
|
260
261
|
// Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
|
|
261
262
|
// is resolved.
|
|
262
|
-
return {score: 1, notApplicable: true,
|
|
263
|
+
return {score: 1, notApplicable: true,
|
|
264
|
+
details: Audit.makeOpportunityDetails([], [], {overallSavingsMs: 0})};
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
8
|
import {EntityClassification} from '../computed/entity-classification.js';
|
|
9
9
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
+
import {Util} from '../../shared/util.js';
|
|
11
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
10
12
|
|
|
11
13
|
const UIStrings = {
|
|
12
14
|
/** Title of a Lighthouse audit that provides detail on HTTP to HTTPS redirects. This descriptive title is shown to users when HTTP traffic is redirected to HTTPS. */
|
|
@@ -54,11 +56,13 @@ class ValidSourceMaps extends Audit {
|
|
|
54
56
|
* @return {boolean}
|
|
55
57
|
*/
|
|
56
58
|
static isLargeFirstPartyJS(script, classifiedEntities) {
|
|
57
|
-
|
|
59
|
+
const url = script.url;
|
|
60
|
+
if (!script.length || !url) return false;
|
|
61
|
+
if (!UrlUtils.isValid(url)) return false;
|
|
62
|
+
if (!Util.createOrReturnURL(url).protocol.startsWith('http')) return false;
|
|
58
63
|
|
|
59
64
|
const isLargeJS = script.length >= LARGE_JS_BYTE_THRESHOLD;
|
|
60
|
-
|
|
61
|
-
return isLargeJS && isFirstPartyJS;
|
|
65
|
+
return classifiedEntities.isFirstParty(url) && isLargeJS;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
/**
|
package/core/config/config.js
CHANGED
|
@@ -37,6 +37,18 @@ const defaultConfigPath = path.join(
|
|
|
37
37
|
'../../config/default-config.js'
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Certain gatherers are destructive to the page state.
|
|
42
|
+
* We should ensure that these gatherers run after any custom gatherers.
|
|
43
|
+
* The default priority should be 0.
|
|
44
|
+
* TODO: Make this an official part of the config or design a different solution.
|
|
45
|
+
* @type {Record<string, number|undefined>}
|
|
46
|
+
*/
|
|
47
|
+
const internalArtifactPriorities = {
|
|
48
|
+
FullPageScreenshot: 1,
|
|
49
|
+
BFCacheFailures: 1,
|
|
50
|
+
};
|
|
51
|
+
|
|
40
52
|
/**
|
|
41
53
|
* @param {LH.Config|undefined} config
|
|
42
54
|
* @param {{configPath?: string}} context
|
|
@@ -129,12 +141,19 @@ async function resolveArtifactsToDefns(artifacts, configDir) {
|
|
|
129
141
|
const status = {msg: 'Resolve artifact definitions', id: 'lh:config:resolveArtifactsToDefns'};
|
|
130
142
|
log.time(status, 'verbose');
|
|
131
143
|
|
|
144
|
+
const sortedArtifacts = [...artifacts];
|
|
145
|
+
sortedArtifacts.sort((a, b) => {
|
|
146
|
+
const aPriority = internalArtifactPriorities[a.id] || 0;
|
|
147
|
+
const bPriority = internalArtifactPriorities[b.id] || 0;
|
|
148
|
+
return aPriority - bPriority;
|
|
149
|
+
});
|
|
150
|
+
|
|
132
151
|
/** @type {Map<Symbol, LH.Config.AnyArtifactDefn>} */
|
|
133
152
|
const artifactDefnsBySymbol = new Map();
|
|
134
153
|
|
|
135
154
|
const coreGathererList = Runner.getGathererList();
|
|
136
155
|
const artifactDefns = [];
|
|
137
|
-
for (const artifactJson of
|
|
156
|
+
for (const artifactJson of sortedArtifacts) {
|
|
138
157
|
/** @type {LH.Config.GathererJson} */
|
|
139
158
|
// @ts-expect-error - remove when legacy runner path is removed.
|
|
140
159
|
const gathererJson = artifactJson.gatherer;
|
|
@@ -122,105 +122,57 @@ const UIStrings = {
|
|
|
122
122
|
|
|
123
123
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
124
124
|
|
|
125
|
-
// Ensure all artifact IDs match the typedefs.
|
|
126
|
-
/** @type {Record<keyof LH.FRArtifacts, string>} */
|
|
127
|
-
const artifacts = {
|
|
128
|
-
DevtoolsLog: '',
|
|
129
|
-
Trace: '',
|
|
130
|
-
Accessibility: '',
|
|
131
|
-
AnchorElements: '',
|
|
132
|
-
BFCacheFailures: '',
|
|
133
|
-
CacheContents: '',
|
|
134
|
-
ConsoleMessages: '',
|
|
135
|
-
CSSUsage: '',
|
|
136
|
-
Doctype: '',
|
|
137
|
-
DOMStats: '',
|
|
138
|
-
EmbeddedContent: '',
|
|
139
|
-
FontSize: '',
|
|
140
|
-
Inputs: '',
|
|
141
|
-
FullPageScreenshot: '',
|
|
142
|
-
GlobalListeners: '',
|
|
143
|
-
IFrameElements: '',
|
|
144
|
-
ImageElements: '',
|
|
145
|
-
InstallabilityErrors: '',
|
|
146
|
-
InspectorIssues: '',
|
|
147
|
-
JsUsage: '',
|
|
148
|
-
LinkElements: '',
|
|
149
|
-
MainDocumentContent: '',
|
|
150
|
-
MetaElements: '',
|
|
151
|
-
NetworkUserAgent: '',
|
|
152
|
-
OptimizedImages: '',
|
|
153
|
-
ResponseCompression: '',
|
|
154
|
-
RobotsTxt: '',
|
|
155
|
-
ServiceWorker: '',
|
|
156
|
-
ScriptElements: '',
|
|
157
|
-
Scripts: '',
|
|
158
|
-
SourceMaps: '',
|
|
159
|
-
Stacks: '',
|
|
160
|
-
TagsBlockingFirstPaint: '',
|
|
161
|
-
TapTargets: '',
|
|
162
|
-
TraceElements: '',
|
|
163
|
-
ViewportDimensions: '',
|
|
164
|
-
WebAppManifest: '',
|
|
165
|
-
devtoolsLogs: '',
|
|
166
|
-
traces: '',
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
for (const key of Object.keys(artifacts)) {
|
|
170
|
-
artifacts[/** @type {keyof typeof artifacts} */ (key)] = key;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
125
|
/** @type {LH.Config} */
|
|
174
126
|
const defaultConfig = {
|
|
175
127
|
settings: constants.defaultSettings,
|
|
176
128
|
artifacts: [
|
|
177
129
|
// Artifacts which can be depended on come first.
|
|
178
|
-
{id:
|
|
179
|
-
{id:
|
|
130
|
+
{id: 'DevtoolsLog', gatherer: 'devtools-log'},
|
|
131
|
+
{id: 'Trace', gatherer: 'trace'},
|
|
180
132
|
|
|
181
|
-
{id:
|
|
182
|
-
{id:
|
|
183
|
-
{id:
|
|
184
|
-
{id:
|
|
185
|
-
{id:
|
|
186
|
-
{id:
|
|
187
|
-
{id:
|
|
188
|
-
{id:
|
|
189
|
-
{id:
|
|
190
|
-
{id:
|
|
191
|
-
{id:
|
|
192
|
-
{id:
|
|
193
|
-
{id:
|
|
194
|
-
{id:
|
|
195
|
-
{id:
|
|
196
|
-
{id:
|
|
197
|
-
{id:
|
|
198
|
-
{id:
|
|
199
|
-
{id:
|
|
200
|
-
{id:
|
|
201
|
-
{id:
|
|
202
|
-
{id:
|
|
203
|
-
{id:
|
|
204
|
-
{id:
|
|
205
|
-
{id:
|
|
206
|
-
{id:
|
|
207
|
-
{id:
|
|
208
|
-
{id:
|
|
209
|
-
{id:
|
|
210
|
-
{id:
|
|
211
|
-
{id:
|
|
212
|
-
{id:
|
|
213
|
-
{id:
|
|
133
|
+
{id: 'Accessibility', gatherer: 'accessibility'},
|
|
134
|
+
{id: 'AnchorElements', gatherer: 'anchor-elements'},
|
|
135
|
+
{id: 'CacheContents', gatherer: 'cache-contents'},
|
|
136
|
+
{id: 'ConsoleMessages', gatherer: 'console-messages'},
|
|
137
|
+
{id: 'CSSUsage', gatherer: 'css-usage'},
|
|
138
|
+
{id: 'Doctype', gatherer: 'dobetterweb/doctype'},
|
|
139
|
+
{id: 'DOMStats', gatherer: 'dobetterweb/domstats'},
|
|
140
|
+
{id: 'EmbeddedContent', gatherer: 'seo/embedded-content'},
|
|
141
|
+
{id: 'FontSize', gatherer: 'seo/font-size'},
|
|
142
|
+
{id: 'Inputs', gatherer: 'inputs'},
|
|
143
|
+
{id: 'GlobalListeners', gatherer: 'global-listeners'},
|
|
144
|
+
{id: 'IFrameElements', gatherer: 'iframe-elements'},
|
|
145
|
+
{id: 'ImageElements', gatherer: 'image-elements'},
|
|
146
|
+
{id: 'InstallabilityErrors', gatherer: 'installability-errors'},
|
|
147
|
+
{id: 'InspectorIssues', gatherer: 'inspector-issues'},
|
|
148
|
+
{id: 'JsUsage', gatherer: 'js-usage'},
|
|
149
|
+
{id: 'LinkElements', gatherer: 'link-elements'},
|
|
150
|
+
{id: 'MainDocumentContent', gatherer: 'main-document-content'},
|
|
151
|
+
{id: 'MetaElements', gatherer: 'meta-elements'},
|
|
152
|
+
{id: 'NetworkUserAgent', gatherer: 'network-user-agent'},
|
|
153
|
+
{id: 'OptimizedImages', gatherer: 'dobetterweb/optimized-images'},
|
|
154
|
+
{id: 'ResponseCompression', gatherer: 'dobetterweb/response-compression'},
|
|
155
|
+
{id: 'RobotsTxt', gatherer: 'seo/robots-txt'},
|
|
156
|
+
{id: 'ServiceWorker', gatherer: 'service-worker'},
|
|
157
|
+
{id: 'ScriptElements', gatherer: 'script-elements'},
|
|
158
|
+
{id: 'Scripts', gatherer: 'scripts'},
|
|
159
|
+
{id: 'SourceMaps', gatherer: 'source-maps'},
|
|
160
|
+
{id: 'Stacks', gatherer: 'stacks'},
|
|
161
|
+
{id: 'TagsBlockingFirstPaint', gatherer: 'dobetterweb/tags-blocking-first-paint'},
|
|
162
|
+
{id: 'TapTargets', gatherer: 'seo/tap-targets'},
|
|
163
|
+
{id: 'TraceElements', gatherer: 'trace-elements'},
|
|
164
|
+
{id: 'ViewportDimensions', gatherer: 'viewport-dimensions'},
|
|
165
|
+
{id: 'WebAppManifest', gatherer: 'web-app-manifest'},
|
|
214
166
|
|
|
215
167
|
// Artifact copies are renamed for compatibility with legacy artifacts.
|
|
216
|
-
{id:
|
|
217
|
-
{id:
|
|
168
|
+
{id: 'devtoolsLogs', gatherer: 'devtools-log-compat'},
|
|
169
|
+
{id: 'traces', gatherer: 'trace-compat'},
|
|
218
170
|
|
|
219
171
|
// FullPageScreenshot comes at the end so all other node analysis is captured.
|
|
220
|
-
{id:
|
|
172
|
+
{id: 'FullPageScreenshot', gatherer: 'full-page-screenshot'},
|
|
221
173
|
|
|
222
|
-
//
|
|
223
|
-
{id:
|
|
174
|
+
// BFCacheFailures comes at the very end because it can perform a page navigation.
|
|
175
|
+
{id: 'BFCacheFailures', gatherer: 'bf-cache-failures'},
|
|
224
176
|
],
|
|
225
177
|
audits: [
|
|
226
178
|
'is-on-https',
|
|
@@ -275,7 +227,7 @@ const defaultConfig = {
|
|
|
275
227
|
'non-composited-animations',
|
|
276
228
|
'unsized-images',
|
|
277
229
|
'valid-source-maps',
|
|
278
|
-
'
|
|
230
|
+
'prioritize-lcp-image',
|
|
279
231
|
'csp-xss',
|
|
280
232
|
'script-treemap-data',
|
|
281
233
|
'manual/pwa-cross-browser',
|
|
@@ -494,7 +446,7 @@ const defaultConfig = {
|
|
|
494
446
|
{id: 'efficient-animated-content', weight: 0},
|
|
495
447
|
{id: 'duplicated-javascript', weight: 0},
|
|
496
448
|
{id: 'legacy-javascript', weight: 0},
|
|
497
|
-
{id: '
|
|
449
|
+
{id: 'prioritize-lcp-image', weight: 0},
|
|
498
450
|
{id: 'total-byte-weight', weight: 0},
|
|
499
451
|
{id: 'uses-long-cache-ttl', weight: 0},
|
|
500
452
|
{id: 'dom-size', weight: 0},
|
|
@@ -222,9 +222,13 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
222
222
|
await this._resizeViewport(context, deviceMetrics);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// Issue both commands at once, to reduce the chance that the page changes between capturing
|
|
226
|
+
// a screenshot and resolving the nodes. https://github.com/GoogleChrome/lighthouse/pull/14763
|
|
227
|
+
const [screenshot, nodes] =
|
|
228
|
+
await Promise.all([this._takeScreenshot(context), this._resolveNodes(context)]);
|
|
225
229
|
return {
|
|
226
|
-
screenshot
|
|
227
|
-
nodes
|
|
230
|
+
screenshot,
|
|
231
|
+
nodes,
|
|
228
232
|
};
|
|
229
233
|
} finally {
|
|
230
234
|
if (!settings.usePassiveGathering) {
|
|
@@ -230,8 +230,9 @@ class TraceElements extends FRGatherer {
|
|
|
230
230
|
throw err;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
// Use main-frame-only LCP to match the metric value.
|
|
234
|
+
const lcpData = processedNavigation.largestContentfulPaintEvt?.args?.data;
|
|
233
235
|
// These should exist, but trace types are loose.
|
|
234
|
-
const lcpData = processedNavigation.largestContentfulPaintAllFramesEvt?.args?.data;
|
|
235
236
|
if (lcpData?.nodeId === undefined || !lcpData.type) return;
|
|
236
237
|
|
|
237
238
|
return {
|
|
@@ -66,12 +66,14 @@ export function _navigations(args: {
|
|
|
66
66
|
artifacts: Partial<LH.FRArtifacts & LH.FRBaseArtifacts>;
|
|
67
67
|
}>;
|
|
68
68
|
/**
|
|
69
|
-
* @param {{requestedUrl?: string, driver: Driver, resolvedConfig: LH.Config.ResolvedConfig}} args
|
|
69
|
+
* @param {{requestedUrl?: string, driver: Driver, resolvedConfig: LH.Config.ResolvedConfig, lhBrowser?: LH.Puppeteer.Browser, lhPage?: LH.Puppeteer.Page}} args
|
|
70
70
|
*/
|
|
71
|
-
export function _cleanup({ requestedUrl, driver, resolvedConfig }: {
|
|
71
|
+
export function _cleanup({ requestedUrl, driver, resolvedConfig, lhBrowser, lhPage }: {
|
|
72
72
|
requestedUrl?: string;
|
|
73
73
|
driver: Driver;
|
|
74
74
|
resolvedConfig: LH.Config.ResolvedConfig;
|
|
75
|
+
lhBrowser?: LH.Puppeteer.Browser;
|
|
76
|
+
lhPage?: LH.Puppeteer.Page;
|
|
75
77
|
}): Promise<void>;
|
|
76
78
|
import { Driver } from "./driver.js";
|
|
77
79
|
import { collectPhaseArtifacts } from "./runner-helpers.js";
|
|
@@ -309,13 +309,17 @@ async function _navigations(args) {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
/**
|
|
312
|
-
* @param {{requestedUrl?: string, driver: Driver, resolvedConfig: LH.Config.ResolvedConfig}} args
|
|
312
|
+
* @param {{requestedUrl?: string, driver: Driver, resolvedConfig: LH.Config.ResolvedConfig, lhBrowser?: LH.Puppeteer.Browser, lhPage?: LH.Puppeteer.Page}} args
|
|
313
313
|
*/
|
|
314
|
-
async function _cleanup({requestedUrl, driver, resolvedConfig}) {
|
|
314
|
+
async function _cleanup({requestedUrl, driver, resolvedConfig, lhBrowser, lhPage}) {
|
|
315
315
|
const didResetStorage = !resolvedConfig.settings.disableStorageReset && requestedUrl;
|
|
316
316
|
if (didResetStorage) await storage.clearDataForOrigin(driver.defaultSession, requestedUrl);
|
|
317
317
|
|
|
318
318
|
await driver.disconnect();
|
|
319
|
+
|
|
320
|
+
// If Lighthouse started the Puppeteer instance then we are responsible for closing it.
|
|
321
|
+
await lhPage?.close();
|
|
322
|
+
await lhBrowser?.disconnect();
|
|
319
323
|
}
|
|
320
324
|
|
|
321
325
|
/**
|
|
@@ -338,17 +342,25 @@ async function navigationGather(page, requestor, options = {}) {
|
|
|
338
342
|
async () => {
|
|
339
343
|
const normalizedRequestor = isCallback ? requestor : UrlUtils.normalizeUrl(requestor);
|
|
340
344
|
|
|
345
|
+
/** @type {LH.Puppeteer.Browser|undefined} */
|
|
346
|
+
let lhBrowser = undefined;
|
|
347
|
+
/** @type {LH.Puppeteer.Page|undefined} */
|
|
348
|
+
let lhPage = undefined;
|
|
349
|
+
|
|
341
350
|
// For navigation mode, we shouldn't connect to a browser in audit mode,
|
|
342
351
|
// therefore we connect to the browser in the gatherFn callback.
|
|
343
352
|
if (!page) {
|
|
344
353
|
const {hostname = DEFAULT_HOSTNAME, port = DEFAULT_PORT} = flags;
|
|
345
|
-
|
|
346
|
-
|
|
354
|
+
lhBrowser = await puppeteer.connect({browserURL: `http://${hostname}:${port}`});
|
|
355
|
+
lhPage = await lhBrowser.newPage();
|
|
356
|
+
page = lhPage;
|
|
347
357
|
}
|
|
348
358
|
|
|
349
359
|
const driver = new Driver(page);
|
|
350
360
|
const context = {
|
|
351
361
|
driver,
|
|
362
|
+
lhBrowser,
|
|
363
|
+
lhPage,
|
|
352
364
|
resolvedConfig,
|
|
353
365
|
requestor: normalizedRequestor,
|
|
354
366
|
};
|