lighthouse 11.4.0-dev.20240102 → 11.4.0-dev.20240104
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/core-tests.js +2 -0
- package/core/audits/audit.d.ts +5 -0
- package/core/audits/audit.js +42 -2
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/core/audits/layout-shift-elements.js +1 -1
- package/core/audits/layout-shifts.d.ts +33 -0
- package/core/audits/layout-shifts.js +158 -0
- package/core/audits/viewport.js +10 -0
- package/core/computed/metrics/cumulative-layout-shift.d.ts +2 -2
- package/core/computed/trace-engine-result.d.ts +40 -0
- package/core/computed/trace-engine-result.js +69 -0
- package/core/computed/viewport-meta.d.ts +4 -0
- package/core/computed/viewport-meta.js +6 -1
- package/core/config/default-config.js +3 -0
- package/core/config/metrics-to-audits.js +1 -0
- package/core/gather/gatherers/root-causes.d.ts +20 -0
- package/core/gather/gatherers/root-causes.js +133 -0
- package/core/gather/gatherers/trace-elements.d.ts +38 -7
- package/core/gather/gatherers/trace-elements.js +113 -34
- package/core/gather/gatherers/trace.js +3 -0
- package/core/lib/i18n/i18n.js +2 -0
- package/core/lib/trace-engine.d.ts +5 -2
- package/core/lib/trace-engine.js +3 -0
- package/dist/report/bundle.esm.js +9 -5
- package/dist/report/flow.js +10 -6
- package/dist/report/standalone.js +9 -5
- package/package.json +2 -2
- package/report/assets/styles.css +4 -0
- package/report/renderer/category-renderer.d.ts +5 -12
- package/report/renderer/category-renderer.js +18 -18
- package/report/renderer/components.js +1 -1
- package/report/renderer/performance-category-renderer.d.ts +2 -1
- package/report/renderer/performance-category-renderer.js +93 -69
- package/report/renderer/pwa-category-renderer.js +5 -2
- package/shared/localization/locales/en-US.json +24 -0
- package/shared/localization/locales/en-XL.json +24 -0
- package/types/artifacts.d.ts +10 -1
- package/types/audit.d.ts +9 -1
- package/types/config.d.ts +1 -0
- package/types/lhr/audit-result.d.ts +1 -7
- package/types/trace-engine.d.ts +1516 -0
|
@@ -211,73 +211,106 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
211
211
|
filmstripEl && timelineEl.append(filmstripEl);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
const allInsights = category.auditRefs
|
|
215
|
+
.filter(audit => this._isPerformanceInsight(audit))
|
|
216
|
+
.map(auditRef => {
|
|
217
|
+
const {overallImpact, overallLinearImpact} = this.overallImpact(auditRef, metricAudits);
|
|
218
|
+
const guidanceLevel = auditRef.result.guidanceLevel || 1;
|
|
219
|
+
const auditEl = this.renderAudit(auditRef);
|
|
220
|
+
|
|
221
|
+
return {auditRef, auditEl, overallImpact, overallLinearImpact, guidanceLevel};
|
|
222
|
+
});
|
|
219
223
|
|
|
220
224
|
// Diagnostics
|
|
221
|
-
const diagnosticAudits =
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const auditImpacts = [];
|
|
227
|
-
diagnosticAudits.forEach(audit => {
|
|
228
|
-
const {
|
|
229
|
-
overallImpact: overallImpact,
|
|
230
|
-
overallLinearImpact: overallLinearImpact,
|
|
231
|
-
} = this.overallImpact(audit, metricAudits);
|
|
232
|
-
const guidanceLevel = audit.result.guidanceLevel ?? 1;
|
|
233
|
-
auditImpacts.push({auditRef: audit, overallImpact, overallLinearImpact, guidanceLevel});
|
|
234
|
-
});
|
|
225
|
+
const diagnosticAudits = allInsights
|
|
226
|
+
.filter(audit => !ReportUtils.showAsPassed(audit.auditRef.result));
|
|
227
|
+
|
|
228
|
+
const passedAudits = allInsights
|
|
229
|
+
.filter(audit => ReportUtils.showAsPassed(audit.auditRef.result));
|
|
235
230
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return b.overallImpact * b.guidanceLevel - a.overallImpact * a.guidanceLevel;
|
|
231
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
|
|
232
|
+
groupEl.classList.add('lh-audit-group--diagnostics');
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {string} acronym
|
|
236
|
+
*/
|
|
237
|
+
function refreshFilteredAudits(acronym) {
|
|
238
|
+
for (const audit of allInsights) {
|
|
239
|
+
if (acronym === 'All') {
|
|
240
|
+
audit.auditEl.hidden = false;
|
|
241
|
+
} else {
|
|
242
|
+
const shouldHide = audit.auditRef.result.metricSavings?.[acronym] === undefined;
|
|
243
|
+
audit.auditEl.hidden = shouldHide;
|
|
244
|
+
}
|
|
251
245
|
}
|
|
252
246
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
a.
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
diagnosticAudits.sort((a, b) => {
|
|
248
|
+
// If the score display mode is "metricSavings", the `score` will be a coarse indicator of the overall impact.
|
|
249
|
+
// Therefore, it makes sense to sort audits by score first to ensure visual clarity with the score icons.
|
|
250
|
+
const scoreA = a.auditRef.result.scoreDisplayMode === 'informative'
|
|
251
|
+
? 100
|
|
252
|
+
: Number(a.auditRef.result.score);
|
|
253
|
+
const scoreB = b.auditRef.result.scoreDisplayMode === 'informative'
|
|
254
|
+
? 100
|
|
255
|
+
: Number(b.auditRef.result.score);
|
|
256
|
+
if (scoreA !== scoreB) return scoreA - scoreB;
|
|
257
|
+
|
|
258
|
+
// If there is a metric filter applied, we should sort by the impact to that specific metric.
|
|
259
|
+
if (acronym !== 'All') {
|
|
260
|
+
const aSavings = a.auditRef.result.metricSavings?.[acronym] ?? -1;
|
|
261
|
+
const bSavings = b.auditRef.result.metricSavings?.[acronym] ?? -1;
|
|
262
|
+
if (aSavings !== bSavings) return bSavings - aSavings;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Overall impact is the estimated improvement to the performance score
|
|
266
|
+
if (a.overallImpact !== b.overallImpact) {
|
|
267
|
+
return b.overallImpact * b.guidanceLevel - a.overallImpact * a.guidanceLevel;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Fall back to the linear impact if the normal impact is rounded down to 0
|
|
271
|
+
if (
|
|
272
|
+
a.overallImpact === 0 && b.overallImpact === 0 &&
|
|
273
|
+
a.overallLinearImpact !== b.overallLinearImpact
|
|
274
|
+
) {
|
|
275
|
+
return b.overallLinearImpact * b.guidanceLevel - a.overallLinearImpact * a.guidanceLevel;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Audits that have no estimated savings should be prioritized by the guidance level
|
|
279
|
+
return b.guidanceLevel - a.guidanceLevel;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
for (const audit of diagnosticAudits) {
|
|
283
|
+
groupEl.insertBefore(audit.auditEl, footerEl);
|
|
259
284
|
}
|
|
285
|
+
}
|
|
260
286
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
287
|
+
/** @type {Set<string>} */
|
|
288
|
+
const filterableMetricAcronyms = new Set();
|
|
289
|
+
for (const audit of diagnosticAudits) {
|
|
290
|
+
const metricSavings = audit.auditRef.result.metricSavings || {};
|
|
291
|
+
for (const [key, value] of Object.entries(metricSavings)) {
|
|
292
|
+
if (typeof value === 'number') filterableMetricAcronyms.add(key);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
264
295
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
296
|
+
const filterableMetrics =
|
|
297
|
+
metricAudits.filter(a => a.acronym && filterableMetricAcronyms.has(a.acronym));
|
|
298
|
+
|
|
299
|
+
// TODO: only add if there are opportunities & diagnostics rendered.
|
|
300
|
+
if (filterableMetrics.length) {
|
|
301
|
+
this.renderMetricAuditFilter(filterableMetrics, element, refreshFilteredAudits);
|
|
270
302
|
}
|
|
271
303
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
304
|
+
refreshFilteredAudits('All');
|
|
305
|
+
|
|
306
|
+
if (diagnosticAudits.length) {
|
|
307
|
+
element.append(groupEl);
|
|
308
|
+
}
|
|
276
309
|
|
|
277
310
|
if (!passedAudits.length) return element;
|
|
278
311
|
|
|
279
312
|
const clumpOpts = {
|
|
280
|
-
|
|
313
|
+
auditRefsOrEls: passedAudits.map(audit => audit.auditEl),
|
|
281
314
|
groupDefinitions: groups,
|
|
282
315
|
};
|
|
283
316
|
const passedElem = this.renderClump('passed', clumpOpts);
|
|
@@ -318,16 +351,17 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
318
351
|
* Render the control to filter the audits by metric. The filtering is done at runtime by CSS only
|
|
319
352
|
* @param {LH.ReportResult.AuditRef[]} filterableMetrics
|
|
320
353
|
* @param {HTMLDivElement} categoryEl
|
|
354
|
+
* @param {(acronym: string) => void} onFilterChange
|
|
321
355
|
*/
|
|
322
|
-
renderMetricAuditFilter(filterableMetrics, categoryEl) {
|
|
356
|
+
renderMetricAuditFilter(filterableMetrics, categoryEl, onFilterChange) {
|
|
323
357
|
const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter');
|
|
324
358
|
const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text');
|
|
325
359
|
textEl.textContent = Globals.strings.showRelevantAudits;
|
|
326
360
|
|
|
327
|
-
const filterChoices =
|
|
328
|
-
({acronym: 'All'}),
|
|
361
|
+
const filterChoices = [
|
|
362
|
+
/** @type {const} */ ({acronym: 'All', id: 'All'}),
|
|
329
363
|
...filterableMetrics,
|
|
330
|
-
]
|
|
364
|
+
];
|
|
331
365
|
|
|
332
366
|
// Form labels need to reference unique IDs, but multiple reports rendered in the same DOM (eg PSI)
|
|
333
367
|
// would mean ID conflict. To address this, we 'scope' these radio inputs with a unique suffix.
|
|
@@ -341,7 +375,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
341
375
|
|
|
342
376
|
const labelEl = this.dom.createChildOf(metricFilterEl, 'label', 'lh-metricfilter__label');
|
|
343
377
|
labelEl.htmlFor = elemId;
|
|
344
|
-
labelEl.title = metric.result
|
|
378
|
+
labelEl.title = 'result' in metric ? metric.result.title : '';
|
|
345
379
|
labelEl.textContent = metric.acronym || metric.id;
|
|
346
380
|
|
|
347
381
|
if (metric.acronym === 'All') {
|
|
@@ -357,17 +391,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
357
391
|
}
|
|
358
392
|
categoryEl.classList.toggle('lh-category--filtered', metric.acronym !== 'All');
|
|
359
393
|
|
|
360
|
-
|
|
361
|
-
if (metric.acronym === 'All') {
|
|
362
|
-
perfAuditEl.hidden = false;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
perfAuditEl.hidden = true;
|
|
367
|
-
if (metric.relevantAudits && metric.relevantAudits.includes(perfAuditEl.id)) {
|
|
368
|
-
perfAuditEl.hidden = false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
394
|
+
onFilterChange(metric.acronym || 'All');
|
|
371
395
|
|
|
372
396
|
// Hide groups/clumps if all child audits are also hidden.
|
|
373
397
|
const groupEls = categoryEl.querySelectorAll('div.lh-audit-group, details.lh-audit-group');
|
|
@@ -29,8 +29,11 @@ export class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
29
29
|
|
|
30
30
|
// Manual audits are still in a manual clump.
|
|
31
31
|
const manualAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode === 'manual');
|
|
32
|
-
const manualElem = this.renderClump('manual',
|
|
33
|
-
|
|
32
|
+
const manualElem = this.renderClump('manual', {
|
|
33
|
+
auditRefsOrEls: manualAuditRefs,
|
|
34
|
+
description: category.manualDescription,
|
|
35
|
+
openByDefault: true,
|
|
36
|
+
});
|
|
34
37
|
categoryElem.append(manualElem);
|
|
35
38
|
|
|
36
39
|
return categoryElem;
|
|
@@ -1166,6 +1166,30 @@
|
|
|
1166
1166
|
"core/audits/layout-shift-elements.js | title": {
|
|
1167
1167
|
"message": "Avoid large layout shifts"
|
|
1168
1168
|
},
|
|
1169
|
+
"core/audits/layout-shifts.js | columnScore": {
|
|
1170
|
+
"message": "Layout shift score"
|
|
1171
|
+
},
|
|
1172
|
+
"core/audits/layout-shifts.js | description": {
|
|
1173
|
+
"message": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)"
|
|
1174
|
+
},
|
|
1175
|
+
"core/audits/layout-shifts.js | displayValueShiftsFound": {
|
|
1176
|
+
"message": "{shiftCount, plural, =1 {1 layout shift found} other {# layout shifts found}}"
|
|
1177
|
+
},
|
|
1178
|
+
"core/audits/layout-shifts.js | rootCauseFontChanges": {
|
|
1179
|
+
"message": "Web font loaded"
|
|
1180
|
+
},
|
|
1181
|
+
"core/audits/layout-shifts.js | rootCauseInjectedIframe": {
|
|
1182
|
+
"message": "Injected iframe"
|
|
1183
|
+
},
|
|
1184
|
+
"core/audits/layout-shifts.js | rootCauseRenderBlockingRequest": {
|
|
1185
|
+
"message": "A late network request adjusted the page layout"
|
|
1186
|
+
},
|
|
1187
|
+
"core/audits/layout-shifts.js | rootCauseUnsizedMedia": {
|
|
1188
|
+
"message": "Media element lacking an explicit size"
|
|
1189
|
+
},
|
|
1190
|
+
"core/audits/layout-shifts.js | title": {
|
|
1191
|
+
"message": "Avoid large layout shifts"
|
|
1192
|
+
},
|
|
1169
1193
|
"core/audits/lcp-lazy-loaded.js | description": {
|
|
1170
1194
|
"message": "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)."
|
|
1171
1195
|
},
|
|
@@ -1166,6 +1166,30 @@
|
|
|
1166
1166
|
"core/audits/layout-shift-elements.js | title": {
|
|
1167
1167
|
"message": "Âv́ôíd̂ ĺâŕĝé l̂áŷóût́ ŝh́îf́t̂ś"
|
|
1168
1168
|
},
|
|
1169
|
+
"core/audits/layout-shifts.js | columnScore": {
|
|
1170
|
+
"message": "L̂áŷóût́ ŝh́îf́t̂ śĉór̂é"
|
|
1171
|
+
},
|
|
1172
|
+
"core/audits/layout-shifts.js | description": {
|
|
1173
|
+
"message": "T̂h́êśê ár̂é t̂h́ê ĺâŕĝéŝt́ l̂áŷóût́ ŝh́îf́t̂ś ôb́ŝér̂v́êd́ ôń t̂h́ê ṕâǵê. Éâćĥ t́âb́l̂é ît́êḿ r̂ép̂ŕêśêńt̂ś â śîńĝĺê ĺâýôút̂ śĥíf̂t́, âńd̂ śĥóŵś t̂h́ê él̂ém̂én̂t́ t̂h́ât́ ŝh́îf́t̂éd̂ t́ĥé m̂óŝt́. B̂él̂óŵ éâćĥ ít̂ém̂ ár̂é p̂óŝśîb́l̂é r̂óôt́ ĉáûśêś t̂h́ât́ l̂éd̂ t́ô t́ĥé l̂áŷóût́ ŝh́îf́t̂. Śôḿê óf̂ t́ĥéŝé l̂áŷóût́ ŝh́îf́t̂ś m̂áŷ ńôt́ b̂é îńĉĺûd́êd́ îń t̂h́ê ĆL̂Ś m̂ét̂ŕîć v̂ál̂úê d́ûé t̂ó [ŵín̂d́ôẃîńĝ](https://web.dev/articles/cls#what_is_cls). [Ĺêár̂ń ĥóŵ t́ô ím̂ṕr̂óv̂é ĈĹŜ](https://web.dev/articles/optimize-cls)"
|
|
1174
|
+
},
|
|
1175
|
+
"core/audits/layout-shifts.js | displayValueShiftsFound": {
|
|
1176
|
+
"message": "{shiftCount, plural, =1 {1 l̂áŷóût́ ŝh́îf́t̂ f́ôún̂d́} other {# l̂áŷóût́ ŝh́îf́t̂ś f̂óûńd̂}}"
|
|
1177
|
+
},
|
|
1178
|
+
"core/audits/layout-shifts.js | rootCauseFontChanges": {
|
|
1179
|
+
"message": "Ŵéb̂ f́ôńt̂ ĺôád̂éd̂"
|
|
1180
|
+
},
|
|
1181
|
+
"core/audits/layout-shifts.js | rootCauseInjectedIframe": {
|
|
1182
|
+
"message": "Îńĵéĉt́êd́ îf́r̂ám̂é"
|
|
1183
|
+
},
|
|
1184
|
+
"core/audits/layout-shifts.js | rootCauseRenderBlockingRequest": {
|
|
1185
|
+
"message": "Â ĺât́ê ńêt́ŵór̂ḱ r̂éq̂úêśt̂ ád̂j́ûśt̂éd̂ t́ĥé p̂áĝé l̂áŷóût́"
|
|
1186
|
+
},
|
|
1187
|
+
"core/audits/layout-shifts.js | rootCauseUnsizedMedia": {
|
|
1188
|
+
"message": "M̂éd̂íâ él̂ém̂én̂t́ l̂áĉḱîńĝ án̂ éx̂ṕl̂íĉít̂ śîźê"
|
|
1189
|
+
},
|
|
1190
|
+
"core/audits/layout-shifts.js | title": {
|
|
1191
|
+
"message": "Âv́ôíd̂ ĺâŕĝé l̂áŷóût́ ŝh́îf́t̂ś"
|
|
1192
|
+
},
|
|
1169
1193
|
"core/audits/lcp-lazy-loaded.js | description": {
|
|
1170
1194
|
"message": "Âb́ôv́ê-t́ĥé-f̂ól̂d́ îḿâǵêś t̂h́ât́ âŕê ĺâźîĺŷ ĺôád̂éd̂ ŕêńd̂ér̂ ĺât́êŕ îń t̂h́ê ṕâǵê ĺîf́êćŷćl̂é, ŵh́îćĥ ćâń d̂él̂áŷ t́ĥé l̂ár̂ǵêśt̂ ćôńt̂én̂t́f̂úl̂ ṕâín̂t́. [L̂éâŕn̂ ḿôŕê áb̂óût́ ôṕt̂ím̂ál̂ ĺâźŷ ĺôád̂ín̂ǵ](https://web.dev/articles/lcp-lazy-loading)."
|
|
1171
1195
|
},
|
package/types/artifacts.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ import LHResult from './lhr/lhr.js'
|
|
|
23
23
|
import Protocol from './protocol.js';
|
|
24
24
|
import Util from './utility-types.js';
|
|
25
25
|
import Audit from './audit.js';
|
|
26
|
+
import {TraceProcessor, LayoutShiftRootCauses} from './trace-engine.js';
|
|
26
27
|
|
|
27
28
|
export type Artifacts = BaseArtifacts & GathererArtifacts;
|
|
28
29
|
|
|
@@ -142,6 +143,8 @@ export interface GathererArtifacts extends PublicGathererArtifacts {
|
|
|
142
143
|
ResponseCompression: {requestId: string, url: string, mimeType: string, transferSize: number, resourceSize: number, gzipSize?: number}[];
|
|
143
144
|
/** Information on fetching and the content of the /robots.txt file. */
|
|
144
145
|
RobotsTxt: {status: number|null, content: string|null, errorMessage?: string};
|
|
146
|
+
/** The result of calling the shared trace engine root cause analysis. */
|
|
147
|
+
RootCauses: Artifacts.TraceEngineRootCauses;
|
|
145
148
|
/** Information on all scripts in the page. */
|
|
146
149
|
Scripts: Artifacts.Script[];
|
|
147
150
|
/** Version information for all ServiceWorkers active after the first page load. */
|
|
@@ -559,13 +562,19 @@ declare module Artifacts {
|
|
|
559
562
|
}
|
|
560
563
|
|
|
561
564
|
interface TraceElement {
|
|
562
|
-
traceEventType: 'largest-contentful-paint'|'layout-shift'|'animation'|'responsiveness';
|
|
565
|
+
traceEventType: 'largest-contentful-paint'|'layout-shift'|'layout-shift-element'|'animation'|'responsiveness';
|
|
563
566
|
node: NodeDetails;
|
|
564
567
|
nodeId: number;
|
|
565
568
|
animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[];
|
|
566
569
|
type?: string;
|
|
567
570
|
}
|
|
568
571
|
|
|
572
|
+
type TraceEngineResult = TraceProcessor['data'];
|
|
573
|
+
|
|
574
|
+
interface TraceEngineRootCauses {
|
|
575
|
+
layoutShifts: Record<number, LayoutShiftRootCauses>;
|
|
576
|
+
}
|
|
577
|
+
|
|
569
578
|
interface ViewportDimensions {
|
|
570
579
|
innerWidth: number;
|
|
571
580
|
innerHeight: number;
|
package/types/audit.d.ts
CHANGED
|
@@ -20,6 +20,14 @@ declare module Audit {
|
|
|
20
20
|
export type ScoreDisplayModes = AuditResult.ScoreDisplayModes;
|
|
21
21
|
export type MetricSavings = AuditResult.MetricSavings;
|
|
22
22
|
|
|
23
|
+
export type ProductMetricSavings = {
|
|
24
|
+
FCP?: number;
|
|
25
|
+
LCP?: number;
|
|
26
|
+
TBT?: number;
|
|
27
|
+
CLS?: number;
|
|
28
|
+
INP?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
23
31
|
type Context = Util.Immutable<{
|
|
24
32
|
/** audit options */
|
|
25
33
|
options: Record<string, any>;
|
|
@@ -87,7 +95,7 @@ declare module Audit {
|
|
|
87
95
|
/** If an audit encounters unusual execution circumstances, strings can be put in this optional array to add top-level warnings to the LHR. */
|
|
88
96
|
runWarnings?: Array<IcuMessage>;
|
|
89
97
|
/** Estimates of how much this audit affects various performance metrics. Values will be in the unit of the respective metrics. */
|
|
90
|
-
metricSavings?:
|
|
98
|
+
metricSavings?: ProductMetricSavings;
|
|
91
99
|
/** Score details including p10 and median for calculating an audit's log-normal score. */
|
|
92
100
|
scoringOptions?: ScoreOptions;
|
|
93
101
|
/** A string identifying how the score should be interpreted for display. Overrides audit meta `scoreDisplayMode` if defined. */
|
package/types/config.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {Audit} from '../core/audits/audit.js';
|
|
|
9
9
|
import {SharedFlagsSettings, ConfigSettings} from './lhr/settings.js';
|
|
10
10
|
import Gatherer from './gatherer.js';
|
|
11
11
|
import {IcuMessage} from './lhr/i18n.js';
|
|
12
|
+
import Result from './lhr/lhr.js';
|
|
12
13
|
|
|
13
14
|
interface ClassOf<T> {
|
|
14
15
|
new (): T;
|
|
@@ -31,13 +31,7 @@ interface ScoreDisplayModes {
|
|
|
31
31
|
|
|
32
32
|
type ScoreDisplayMode = ScoreDisplayModes[keyof ScoreDisplayModes];
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
LCP?: number;
|
|
36
|
-
FCP?: number;
|
|
37
|
-
CLS?: number;
|
|
38
|
-
TBT?: number;
|
|
39
|
-
INP?: number;
|
|
40
|
-
}
|
|
34
|
+
export type MetricSavings = Partial<Record<string, number>>;
|
|
41
35
|
|
|
42
36
|
/** Audit result returned in Lighthouse report. All audits offer a description and score of 0-1. */
|
|
43
37
|
export interface Result {
|