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.
Files changed (41) hide show
  1. package/core/audits/bootup-time.js +0 -2
  2. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +4 -5
  3. package/core/audits/byte-efficiency/duplicated-javascript.js +9 -5
  4. package/core/audits/byte-efficiency/legacy-javascript.d.ts +2 -2
  5. package/core/audits/byte-efficiency/legacy-javascript.js +17 -5
  6. package/core/audits/byte-efficiency/polyfill-graph-data.json +48 -49
  7. package/core/audits/byte-efficiency/total-byte-weight.js +0 -2
  8. package/core/audits/dobetterweb/dom-size.js +0 -2
  9. package/core/audits/insights/image-delivery-insight.js +31 -6
  10. package/core/audits/insights/insight-audit.d.ts +7 -0
  11. package/core/audits/insights/insight-audit.js +35 -1
  12. package/core/audits/insights/interaction-to-next-paint-insight.js +23 -5
  13. package/core/audits/insights/lcp-phases-insight.d.ts +5 -0
  14. package/core/audits/insights/lcp-phases-insight.js +45 -11
  15. package/core/audits/insights/third-parties-insight.d.ts +17 -0
  16. package/core/audits/insights/third-parties-insight.js +44 -7
  17. package/core/audits/mainthread-work-breakdown.js +0 -2
  18. package/core/audits/seo/is-crawlable.d.ts +1 -0
  19. package/core/audits/server-response-time.js +0 -1
  20. package/core/computed/trace-engine-result.d.ts +4 -0
  21. package/core/computed/trace-engine-result.js +88 -0
  22. package/core/config/default-config.js +14 -14
  23. package/core/gather/gatherers/trace-elements.js +1 -1
  24. package/core/lib/trace-engine.d.ts +1 -0
  25. package/core/lib/trace-engine.js +2 -0
  26. package/dist/report/bundle.esm.js +13 -10
  27. package/dist/report/flow.js +8 -5
  28. package/dist/report/standalone.js +7 -4
  29. package/flow-report/src/i18n/i18n.d.ts +2 -0
  30. package/package.json +2 -2
  31. package/report/assets/styles.css +3 -0
  32. package/report/assets/templates.html +1 -0
  33. package/report/renderer/components.js +8 -2
  34. package/report/renderer/performance-category-renderer.d.ts +10 -0
  35. package/report/renderer/performance-category-renderer.js +34 -23
  36. package/report/renderer/report-utils.d.ts +1 -0
  37. package/report/renderer/report-utils.js +2 -0
  38. package/report/renderer/topbar-features.js +8 -0
  39. package/shared/localization/locales/en-US.json +12 -3
  40. package/shared/localization/locales/en-XL.json +12 -3
  41. 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, makeNodeItemForNodeId} from './insight-audit.js';
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
- return Audit.makeTableDetails(headings, items);
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}),
@@ -12,6 +12,7 @@ declare class IsCrawlable extends Audit {
12
12
  selector?: string;
13
13
  boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect;
14
14
  nodeLabel?: string;
15
+ explanation?: string;
15
16
  };
16
17
  } | undefined;
17
18
  /**
@@ -92,7 +92,6 @@ class ServerResponseTime extends Audit {
92
92
  numericValue: responseTime,
93
93
  numericUnit: 'millisecond',
94
94
  score: Number(passed),
95
- scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
96
95
  displayValue,
97
96
  details,
98
97
  metricSavings: {
@@ -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: 'hidden'},
412
- {id: 'document-latency-insight', weight: 0, group: 'hidden'},
413
- {id: 'dom-size-insight', weight: 0, group: 'hidden'},
414
- {id: 'font-display-insight', weight: 0, group: 'hidden'},
415
- {id: 'forced-reflow-insight', weight: 0, group: 'hidden'},
416
- {id: 'image-delivery-insight', weight: 0, group: 'hidden'},
417
- {id: 'interaction-to-next-paint-insight', weight: 0, group: 'hidden'},
418
- {id: 'lcp-discovery-insight', weight: 0, group: 'hidden'},
419
- {id: 'lcp-phases-insight', weight: 0, group: 'hidden'},
420
- {id: 'long-critical-network-tree-insight', weight: 0, group: 'hidden'},
421
- {id: 'render-blocking-insight', weight: 0, group: 'hidden'},
422
- {id: 'slow-css-selector-insight', weight: 0, group: 'hidden'},
423
- {id: 'third-parties-insight', weight: 0, group: 'hidden'},
424
- {id: 'viewport-insight', weight: 0, group: 'hidden'},
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, string>, key: string) => void} cb
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
@@ -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
  };