Skip to content

Commit 47b92a0

Browse files
authored
core(network-dependency-tree-insight): add preconnect advice (#16557)
1 parent 755a65d commit 47b92a0

9 files changed

Lines changed: 490 additions & 140 deletions

File tree

core/audits/audit.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,21 @@ class Audit {
194194
};
195195
}
196196

197+
/**
198+
* @param {LH.IcuMessage | string=} title
199+
* @param {LH.IcuMessage | string=} description
200+
* @param {LH.Audit.Details.ListableDetail} value
201+
* @return {LH.Audit.Details.ListSectionItem}
202+
*/
203+
static makeListDetailSectionItem(value, title, description) {
204+
return {
205+
type: 'list-section',
206+
title,
207+
description,
208+
value,
209+
};
210+
}
211+
197212
/** @typedef {{
198213
* content: string;
199214
* title: string;

core/audits/insights/insight-audit.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async function getInsightSet(artifacts, context) {
4242
* @param {LH.Artifacts} artifacts
4343
* @param {LH.Audit.Context} context
4444
* @param {T} insightName
45-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => LH.Audit.Details|undefined} createDetails
45+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings: Array<string | LH.IcuMessage>}|LH.Audit.Details|undefined} createDetails
4646
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
4747
* @return {Promise<LH.Audit.Product>}
4848
*/
@@ -64,10 +64,21 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
6464
};
6565
}
6666

67-
const details = createDetails(insight, {
67+
const cbResult = createDetails(insight, {
6868
parsedTrace,
6969
insights,
7070
});
71+
72+
const warnings = [...insight.warnings ?? []];
73+
74+
let details;
75+
if (cbResult && 'warnings' in cbResult) {
76+
details = cbResult.details;
77+
warnings.push(...cbResult.warnings);
78+
} else {
79+
details = cbResult;
80+
}
81+
7182
if (!details || (details.type === 'table' && details.items.length === 0)) {
7283
return {
7384
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
@@ -116,7 +127,7 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
116127
scoreDisplayMode,
117128
score,
118129
metricSavings,
119-
warnings: insight.warnings,
130+
warnings: warnings.length ? warnings : undefined,
120131
displayValue,
121132
details,
122133
};

core/audits/insights/network-dependency-tree-insight.js

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js';
7+
import {UIStrings, TOO_MANY_PRECONNECTS_THRESHOLD} from '@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js';
88

99
import {Audit} from '../audit.js';
1010
import * as i18n from '../../lib/i18n/i18n.js';
11-
import {adaptInsightToAuditProduct} from './insight-audit.js';
11+
import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
1212

1313
// eslint-disable-next-line max-len
1414
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js', UIStrings);
@@ -24,8 +24,8 @@ class NetworkDependencyTreeInsight extends Audit {
2424
failureTitle: str_(UIStrings.title),
2525
description: str_(UIStrings.description),
2626
guidanceLevel: 1,
27-
requiredArtifacts: ['Trace', 'SourceMaps'],
28-
replacesAudits: ['critical-request-chains'],
27+
requiredArtifacts: ['Trace', 'SourceMaps', 'TraceElements'],
28+
replacesAudits: ['critical-request-chains', 'uses-rel-preconnect'],
2929
};
3030
}
3131

@@ -59,15 +59,92 @@ class NetworkDependencyTreeInsight extends Audit {
5959
*/
6060
static async audit(artifacts, context) {
6161
return adaptInsightToAuditProduct(artifacts, context, 'NetworkDependencyTree', (insight) => {
62-
const chains = this.traceEngineNodesToDetailsNodes(insight.rootNodes);
62+
const list = [];
63+
let sectionDetails;
6364

64-
return {
65+
sectionDetails = /** @type {LH.Audit.Details.NetworkTree} */({
6566
type: 'network-tree',
66-
chains,
67+
chains: this.traceEngineNodesToDetailsNodes(insight.rootNodes),
6768
longestChain: {
6869
duration: Math.round(insight.maxTime / 1000),
6970
},
70-
};
71+
});
72+
list.push(Audit.makeListDetailSectionItem(sectionDetails));
73+
74+
// Preconnected origins table.
75+
if (insight.preconnectedOrigins.length) {
76+
/** @type {LH.Audit.Details.Table['headings']} */
77+
const headings = [
78+
/* eslint-disable max-len */
79+
{key: 'origin', valueType: 'text', subItemsHeading: {key: 'warning'}, label: str_(UIStrings.columnOrigin)},
80+
{key: 'source', valueType: 'node', label: str_(UIStrings.columnSource)},
81+
/* eslint-enable max-len */
82+
];
83+
84+
/** @type {LH.Audit.Details.Table['items']} */
85+
const items = insight.preconnectedOrigins.map(c => {
86+
const warnings = [];
87+
if (c.unused) {
88+
warnings.push(str_(UIStrings.unusedWarning));
89+
}
90+
if (c.crossorigin) {
91+
warnings.push(str_(UIStrings.crossoriginWarning));
92+
}
93+
/** @type {LH.Audit.Details.TableSubItems} */
94+
const subItems = {
95+
type: 'subitems',
96+
items: warnings.map(warning => ({warning})),
97+
};
98+
return {
99+
origin: c.url,
100+
source: c.source === 'DOM' ?
101+
makeNodeItemForNodeId(artifacts.TraceElements, c.node_id) :
102+
{type: 'text', value: c.headerText},
103+
subItems,
104+
};
105+
});
106+
107+
sectionDetails = Audit.makeTableDetails(headings, items);
108+
} else {
109+
sectionDetails = /** @type {LH.Audit.Details.TextValue} */ (
110+
{type: 'text', value: str_(UIStrings.noPreconnectOrigins)});
111+
}
112+
113+
list.push(Audit.makeListDetailSectionItem(
114+
sectionDetails,
115+
str_(UIStrings.preconnectOriginsTableTitle),
116+
str_(UIStrings.preconnectOriginsTableDescription)));
117+
118+
// Estimated savings table.
119+
if (insight.preconnectCandidates.length) {
120+
/** @type {LH.Audit.Details.Table['headings']} */
121+
const headings = [
122+
{key: 'origin', valueType: 'text', label: str_(UIStrings.columnOrigin)},
123+
{key: 'wastedMs', valueType: 'ms', label: str_(UIStrings.columnWastedMs)},
124+
];
125+
126+
/** @type {LH.Audit.Details.Table['items']} */
127+
const items = insight.preconnectCandidates.map(c => {
128+
return {origin: c.origin, wastedMs: c.wastedMs};
129+
});
130+
131+
sectionDetails = Audit.makeTableDetails(headings, items);
132+
} else {
133+
sectionDetails = /** @type {LH.Audit.Details.TextValue} */ (
134+
{type: 'text', value: str_(UIStrings.noPreconnectCandidates)});
135+
}
136+
137+
list.push(Audit.makeListDetailSectionItem(
138+
sectionDetails,
139+
str_(UIStrings.estSavingTableTitle),
140+
str_(UIStrings.estSavingTableDescription)));
141+
142+
const warnings = [];
143+
if (insight.preconnectedOrigins.length > TOO_MANY_PRECONNECTS_THRESHOLD) {
144+
warnings.push(str_(UIStrings.tooManyPreconnectLinksWarning));
145+
}
146+
147+
return {details: Audit.makeListDetails(list), warnings};
71148
});
72149
}
73150
}

0 commit comments

Comments
 (0)