lighthouse 12.3.0-dev.20250209 → 12.3.0-dev.20250211
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/bootup-time.js +0 -2
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +4 -5
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -5
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +2 -2
- package/core/audits/byte-efficiency/legacy-javascript.js +17 -5
- package/core/audits/byte-efficiency/polyfill-graph-data.json +48 -49
- package/core/audits/byte-efficiency/total-byte-weight.js +0 -2
- package/core/audits/dobetterweb/dom-size.js +0 -2
- package/core/audits/insights/image-delivery-insight.js +31 -6
- package/core/audits/insights/insight-audit.d.ts +7 -0
- package/core/audits/insights/insight-audit.js +35 -1
- package/core/audits/insights/interaction-to-next-paint-insight.js +23 -5
- package/core/audits/insights/lcp-phases-insight.d.ts +5 -0
- package/core/audits/insights/lcp-phases-insight.js +45 -11
- package/core/audits/insights/third-parties-insight.d.ts +17 -0
- package/core/audits/insights/third-parties-insight.js +44 -7
- package/core/audits/mainthread-work-breakdown.js +0 -2
- package/core/audits/seo/is-crawlable.d.ts +1 -0
- package/core/audits/server-response-time.js +0 -1
- package/core/computed/trace-engine-result.d.ts +4 -0
- package/core/computed/trace-engine-result.js +88 -0
- package/core/config/default-config.js +14 -14
- package/core/gather/gatherers/trace-elements.js +1 -1
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/dist/report/bundle.esm.js +13 -10
- package/dist/report/flow.js +8 -5
- package/dist/report/standalone.js +7 -4
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/package.json +2 -2
- package/report/assets/styles.css +3 -0
- package/report/assets/templates.html +1 -0
- package/report/renderer/components.js +8 -2
- package/report/renderer/performance-category-renderer.d.ts +10 -0
- package/report/renderer/performance-category-renderer.js +34 -23
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/topbar-features.js +8 -0
- package/shared/localization/locales/en-US.json +12 -3
- package/shared/localization/locales/en-XL.json +12 -3
- package/types/lhr/audit-details.d.ts +2 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
export default ThirdPartiesInsight;
|
|
2
|
+
export type URLSummary = {
|
|
3
|
+
transferSize: number;
|
|
4
|
+
mainThreadTime: number;
|
|
5
|
+
url: string | LH.IcuMessage;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @typedef URLSummary
|
|
9
|
+
* @property {number} transferSize
|
|
10
|
+
* @property {number} mainThreadTime
|
|
11
|
+
* @property {string | LH.IcuMessage} url
|
|
12
|
+
*/
|
|
2
13
|
declare class ThirdPartiesInsight extends Audit {
|
|
14
|
+
/**
|
|
15
|
+
* @param {LH.Artifacts.Entity} entity
|
|
16
|
+
* @param {import('@paulirish/trace_engine/models/trace/insights/ThirdParties.js').ThirdPartiesInsightModel} insight
|
|
17
|
+
* @return {Array<URLSummary>}
|
|
18
|
+
*/
|
|
19
|
+
static makeSubItems(entity: LH.Artifacts.Entity, insight: import("@paulirish/trace_engine/models/trace/insights/ThirdParties.js").ThirdPartiesInsightModel): Array<URLSummary>;
|
|
3
20
|
/**
|
|
4
21
|
* @param {LH.Artifacts} artifacts
|
|
5
22
|
* @param {LH.Audit.Context} context
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-unused-vars */ // TODO: remove once implemented.
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @license
|
|
5
3
|
* Copyright 2025 Google LLC
|
|
@@ -10,11 +8,18 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ThirdPart
|
|
|
10
8
|
|
|
11
9
|
import {Audit} from '../audit.js';
|
|
12
10
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
-
import {adaptInsightToAuditProduct
|
|
11
|
+
import {adaptInsightToAuditProduct} from './insight-audit.js';
|
|
14
12
|
|
|
15
13
|
// eslint-disable-next-line max-len
|
|
16
14
|
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js', UIStrings);
|
|
17
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @typedef URLSummary
|
|
18
|
+
* @property {number} transferSize
|
|
19
|
+
* @property {number} mainThreadTime
|
|
20
|
+
* @property {string | LH.IcuMessage} url
|
|
21
|
+
*/
|
|
22
|
+
|
|
18
23
|
class ThirdPartiesInsight extends Audit {
|
|
19
24
|
/**
|
|
20
25
|
* @return {LH.Audit.Meta}
|
|
@@ -30,21 +35,53 @@ class ThirdPartiesInsight extends Audit {
|
|
|
30
35
|
};
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @param {LH.Artifacts.Entity} entity
|
|
40
|
+
* @param {import('@paulirish/trace_engine/models/trace/insights/ThirdParties.js').ThirdPartiesInsightModel} insight
|
|
41
|
+
* @return {Array<URLSummary>}
|
|
42
|
+
*/
|
|
43
|
+
static makeSubItems(entity, insight) {
|
|
44
|
+
const urls = [...insight.urlsByEntity.get(entity) ?? []];
|
|
45
|
+
return urls
|
|
46
|
+
.map(url => ({
|
|
47
|
+
url,
|
|
48
|
+
mainThreadTime: 0,
|
|
49
|
+
transferSize: 0,
|
|
50
|
+
...insight.summaryByUrl.get(url),
|
|
51
|
+
}))
|
|
52
|
+
// Sort by main thread time first, then transfer size to break ties.
|
|
53
|
+
.sort((a, b) => (b.mainThreadTime - a.mainThreadTime) || (b.transferSize - a.transferSize));
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
/**
|
|
34
57
|
* @param {LH.Artifacts} artifacts
|
|
35
58
|
* @param {LH.Audit.Context} context
|
|
36
59
|
* @return {Promise<LH.Audit.Product>}
|
|
37
60
|
*/
|
|
38
61
|
static async audit(artifacts, context) {
|
|
39
|
-
// TODO: implement.
|
|
40
62
|
return adaptInsightToAuditProduct(artifacts, context, 'ThirdParties', (insight) => {
|
|
63
|
+
const thirdPartyEntities = [...insight.summaryByEntity.entries()]
|
|
64
|
+
.filter((([entity, _]) => entity !== insight.firstPartyEntity));
|
|
65
|
+
|
|
41
66
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
42
67
|
const headings = [
|
|
68
|
+
/* eslint-disable max-len */
|
|
69
|
+
{key: 'entity', valueType: 'text', label: str_(UIStrings.columnThirdParty), subItemsHeading: {key: 'url', valueType: 'url'}},
|
|
70
|
+
{key: 'transferSize', granularity: 1, valueType: 'bytes', label: str_(UIStrings.columnTransferSize), subItemsHeading: {key: 'transferSize'}},
|
|
71
|
+
{key: 'mainThreadTime', granularity: 1, valueType: 'ms', label: str_(UIStrings.columnMainThreadTime), subItemsHeading: {key: 'mainThreadTime'}},
|
|
72
|
+
/* eslint-enable max-len */
|
|
43
73
|
];
|
|
44
74
|
/** @type {LH.Audit.Details.Table['items']} */
|
|
45
|
-
const items = [
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
const items = thirdPartyEntities.map(([entity, summary]) => ({
|
|
76
|
+
entity: entity.name,
|
|
77
|
+
transferSize: summary.transferSize,
|
|
78
|
+
mainThreadTime: summary.mainThreadTime,
|
|
79
|
+
subItems: {
|
|
80
|
+
type: /** @type {const} */ ('subitems'),
|
|
81
|
+
items: ThirdPartiesInsight.makeSubItems(entity, insight),
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
84
|
+
return Audit.makeTableDetails(headings, items, {isEntityGrouped: true});
|
|
48
85
|
});
|
|
49
86
|
}
|
|
50
87
|
}
|
|
@@ -16,7 +16,6 @@ import * as i18n from '../lib/i18n/i18n.js';
|
|
|
16
16
|
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
17
17
|
import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';
|
|
18
18
|
import {Sentry} from '../lib/sentry.js';
|
|
19
|
-
import {Util} from '../../shared/util.js';
|
|
20
19
|
|
|
21
20
|
const UIStrings = {
|
|
22
21
|
/** Title of a diagnostic audit that provides detail on the main thread work the browser did to load the page. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -142,7 +141,6 @@ class MainThreadWorkBreakdown extends Audit {
|
|
|
142
141
|
|
|
143
142
|
return {
|
|
144
143
|
score,
|
|
145
|
-
scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
|
|
146
144
|
numericValue: totalExecutionTime,
|
|
147
145
|
numericUnit: 'millisecond',
|
|
148
146
|
displayValue: str_(i18n.UIStrings.seconds, {timeInMs: totalExecutionTime}),
|
|
@@ -13,6 +13,10 @@ declare class TraceEngineResult {
|
|
|
13
13
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
14
14
|
*/
|
|
15
15
|
static runTraceEngine(traceEvents: LH.TraceEvent[]): Promise<LH.Artifacts.TraceEngineResult>;
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('@paulirish/trace_engine/models/trace/insights/types.js').TraceInsightSets} insightSets
|
|
18
|
+
*/
|
|
19
|
+
static localizeInsights(insightSets: import("@paulirish/trace_engine/models/trace/insights/types.js").TraceInsightSets): void;
|
|
16
20
|
/**
|
|
17
21
|
* @param {{trace: LH.Trace}} data
|
|
18
22
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
7
8
|
import * as TraceEngine from '../lib/trace-engine.js';
|
|
8
9
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
9
10
|
import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js';
|
|
@@ -27,9 +28,96 @@ class TraceEngineResult {
|
|
|
27
28
|
), {});
|
|
28
29
|
if (!processor.parsedTrace) throw new Error('No data');
|
|
29
30
|
if (!processor.insights) throw new Error('No insights');
|
|
31
|
+
this.localizeInsights(processor.insights);
|
|
30
32
|
return {data: processor.parsedTrace, insights: processor.insights};
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {import('@paulirish/trace_engine/models/trace/insights/types.js').TraceInsightSets} insightSets
|
|
37
|
+
*/
|
|
38
|
+
static localizeInsights(insightSets) {
|
|
39
|
+
/**
|
|
40
|
+
* Execute `cb(traceEngineI18nObject)` on every i18n object, recursively. The cb return
|
|
41
|
+
* value replaces traceEngineI18nObject.
|
|
42
|
+
* @param {any} obj
|
|
43
|
+
* @param {(traceEngineI18nObject: {i18nId: string, values?: {}}) => LH.IcuMessage} cb
|
|
44
|
+
* @param {Set<object>} seen
|
|
45
|
+
*/
|
|
46
|
+
function recursiveReplaceLocalizableStrings(obj, cb, seen) {
|
|
47
|
+
if (seen.has(seen)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
seen.add(obj);
|
|
52
|
+
|
|
53
|
+
if (obj instanceof Map) {
|
|
54
|
+
for (const [key, value] of obj) {
|
|
55
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
56
|
+
obj.set(key, cb(value));
|
|
57
|
+
} else {
|
|
58
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
62
|
+
Object.keys(obj).forEach(key => {
|
|
63
|
+
const value = obj[key];
|
|
64
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
65
|
+
obj[key] = cb(value);
|
|
66
|
+
} else {
|
|
67
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} else if (Array.isArray(obj)) {
|
|
71
|
+
for (let i = 0; i < obj.length; i++) {
|
|
72
|
+
const value = obj[i];
|
|
73
|
+
if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
74
|
+
obj[i] = cb(value);
|
|
75
|
+
} else {
|
|
76
|
+
recursiveReplaceLocalizableStrings(value, cb, seen);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const insightSet of insightSets.values()) {
|
|
83
|
+
for (const [name, model] of Object.entries(insightSet.model)) {
|
|
84
|
+
if (model instanceof Error) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @type {Record<string, string>} */
|
|
89
|
+
let traceEngineUIStrings;
|
|
90
|
+
if (name in TraceEngine.Insights.Models) {
|
|
91
|
+
const nameAsKey = /** @type {keyof typeof TraceEngine.Insights.Models} */ (name);
|
|
92
|
+
traceEngineUIStrings = TraceEngine.Insights.Models[nameAsKey].UIStrings;
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(`insight missing UIStrings: ${name}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const key = `node_modules/@paulirish/trace_engine/models/trace/insights/${name}.js`;
|
|
98
|
+
const str_ = i18n.createIcuMessageFn(key, traceEngineUIStrings);
|
|
99
|
+
|
|
100
|
+
// Pass `{i18nId: string, values?: {}}` through Lighthouse's i18n pipeline.
|
|
101
|
+
// This is equivalent to if we directly did `str_(UIStrings.whatever, ...)`
|
|
102
|
+
recursiveReplaceLocalizableStrings(model, (traceEngineI18nObject) => {
|
|
103
|
+
let values = traceEngineI18nObject.values;
|
|
104
|
+
if (values) {
|
|
105
|
+
values = structuredClone(values);
|
|
106
|
+
for (const [key, value] of Object.entries(values)) {
|
|
107
|
+
if (value && typeof value === 'object' && '__i18nBytes' in value) {
|
|
108
|
+
// @ts-expect-error
|
|
109
|
+
values[key] = value.__i18nBytes;
|
|
110
|
+
// TODO: use an actual byte formatter. Right now, this shows the exact number of bytes.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return str_(traceEngineI18nObject.i18nId, values);
|
|
116
|
+
}, new Set());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
33
121
|
/**
|
|
34
122
|
* @param {{trace: LH.Trace}} data
|
|
35
123
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -408,20 +408,20 @@ const defaultConfig = {
|
|
|
408
408
|
{id: 'interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'},
|
|
409
409
|
|
|
410
410
|
// Insight audits.
|
|
411
|
-
{id: 'cls-culprits-insight', weight: 0, group: '
|
|
412
|
-
{id: 'document-latency-insight', weight: 0, group: '
|
|
413
|
-
{id: 'dom-size-insight', weight: 0, group: '
|
|
414
|
-
{id: 'font-display-insight', weight: 0, group: '
|
|
415
|
-
{id: 'forced-reflow-insight', weight: 0, group: '
|
|
416
|
-
{id: 'image-delivery-insight', weight: 0, group: '
|
|
417
|
-
{id: 'interaction-to-next-paint-insight', weight: 0, group: '
|
|
418
|
-
{id: 'lcp-discovery-insight', weight: 0, group: '
|
|
419
|
-
{id: 'lcp-phases-insight', weight: 0, group: '
|
|
420
|
-
{id: 'long-critical-network-tree-insight', weight: 0, group: '
|
|
421
|
-
{id: 'render-blocking-insight', weight: 0, group: '
|
|
422
|
-
{id: 'slow-css-selector-insight', weight: 0, group: '
|
|
423
|
-
{id: 'third-parties-insight', weight: 0, group: '
|
|
424
|
-
{id: 'viewport-insight', weight: 0, group: '
|
|
411
|
+
{id: 'cls-culprits-insight', weight: 0, group: 'insights'},
|
|
412
|
+
{id: 'document-latency-insight', weight: 0, group: 'insights'},
|
|
413
|
+
{id: 'dom-size-insight', weight: 0, group: 'insights'},
|
|
414
|
+
{id: 'font-display-insight', weight: 0, group: 'insights'},
|
|
415
|
+
{id: 'forced-reflow-insight', weight: 0, group: 'insights'},
|
|
416
|
+
{id: 'image-delivery-insight', weight: 0, group: 'insights'},
|
|
417
|
+
{id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
|
|
418
|
+
{id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
|
|
419
|
+
{id: 'lcp-phases-insight', weight: 0, group: 'insights'},
|
|
420
|
+
{id: 'long-critical-network-tree-insight', weight: 0, group: 'insights'},
|
|
421
|
+
{id: 'render-blocking-insight', weight: 0, group: 'insights'},
|
|
422
|
+
{id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
|
|
423
|
+
{id: 'third-parties-insight', weight: 0, group: 'insights'},
|
|
424
|
+
{id: 'viewport-insight', weight: 0, group: 'insights'},
|
|
425
425
|
|
|
426
426
|
// These are our "invisible" metrics. Not displayed, but still in the LHR.
|
|
427
427
|
{id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'},
|
|
@@ -92,7 +92,7 @@ class TraceElements extends BaseGatherer {
|
|
|
92
92
|
/**
|
|
93
93
|
* Execute `cb(obj, key)` on every object property (non-objects only), recursively.
|
|
94
94
|
* @param {any} obj
|
|
95
|
-
* @param {(obj: Record<string,
|
|
95
|
+
* @param {(obj: Record<string, unknown>, key: string) => void} cb
|
|
96
96
|
* @param {Set<object>} seen
|
|
97
97
|
*/
|
|
98
98
|
function recursiveObjectEnumerate(obj, cb, seen) {
|
|
@@ -7,5 +7,6 @@ export type SaneSyntheticLayoutShift = SyntheticLayoutShift & {
|
|
|
7
7
|
export const TraceProcessor: typeof TraceEngine.Processor.TraceProcessor;
|
|
8
8
|
export const TraceHandlers: typeof TraceEngine.Handlers.ModelHandlers;
|
|
9
9
|
export const RootCauses: typeof TraceEngine.RootCauses.RootCauses.RootCauses;
|
|
10
|
+
export const Insights: typeof TraceEngine.Insights;
|
|
10
11
|
import * as TraceEngine from '@paulirish/trace_engine';
|
|
11
12
|
//# sourceMappingURL=trace-engine.d.ts.map
|
package/core/lib/trace-engine.js
CHANGED
|
@@ -10,9 +10,11 @@ polyfillDOMRect();
|
|
|
10
10
|
const TraceProcessor = TraceEngine.Processor.TraceProcessor;
|
|
11
11
|
const TraceHandlers = TraceEngine.Handlers.ModelHandlers;
|
|
12
12
|
const RootCauses = TraceEngine.RootCauses.RootCauses.RootCauses;
|
|
13
|
+
const Insights = TraceEngine.Insights;
|
|
13
14
|
|
|
14
15
|
export {
|
|
15
16
|
TraceProcessor,
|
|
16
17
|
TraceHandlers,
|
|
17
18
|
RootCauses,
|
|
19
|
+
Insights,
|
|
18
20
|
};
|