forked from checkinholiday/lighthouse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcritical-request-chains.js
219 lines (197 loc) · 7.71 KB
/
critical-request-chains.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Audit} from './audit.js';
import * as i18n from '../lib/i18n/i18n.js';
import {CriticalRequestChains as ComputedChains} from '../computed/critical-request-chains.js';
const UIStrings = {
/** Imperative title of a Lighthouse audit that tells the user to reduce the depth of critical network requests to enhance initial load of a page. Critical request chains are series of dependent network requests that are important for page rendering. For example, here's a 4-request-deep chain: The biglogo.jpg image is required, but is requested via the styles.css style code, which is requested by the initialize.js javascript, which is requested by the page's HTML. This is displayed in a list of audit titles that Lighthouse generates. */
title: 'Avoid chaining critical requests',
/** Description of a Lighthouse audit that tells the user *why* they should reduce the depth of critical network requests to enhance initial load of a page . This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
description: 'The Critical Request Chains below show you what resources are ' +
'loaded with a high priority. Consider reducing ' +
'the length of chains, reducing the download size of resources, or ' +
'deferring the download of unnecessary resources to improve page load. ' +
'[Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).',
/** [ICU Syntax] Label for an audit identifying the number of sequences of dependent network requests used to load the page. */
displayValue: `{itemCount, plural,
=1 {1 chain found}
other {# chains found}
}`,
};
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
class CriticalRequestChains extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'critical-request-chains',
title: str_(UIStrings.title),
description: str_(UIStrings.description),
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
supportedModes: ['navigation'],
guidanceLevel: 1,
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
};
}
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
/**
* @param {LH.Artifacts.CriticalRequestNode} tree
* @param {function(CrcNodeInfo): void} cb
*/
static _traverse(tree, cb) {
/**
* @param {LH.Artifacts.CriticalRequestNode} node
* @param {number} depth
* @param {number=} networkRequestTime
* @param {number=} transferSize
*/
function walk(node, depth, networkRequestTime, transferSize = 0) {
const children = Object.keys(node);
if (children.length === 0) {
return;
}
children.forEach(id => {
const child = node[id];
if (!networkRequestTime) {
networkRequestTime = child.request.networkRequestTime;
}
// Call the callback with the info for this child.
cb({
depth,
id,
node: child,
chainDuration: child.request.networkEndTime - networkRequestTime,
chainTransferSize: transferSize + child.request.transferSize,
});
// Carry on walking.
if (child.children) {
walk(child.children, depth + 1, networkRequestTime);
}
}, '');
}
walk(tree, 0);
}
/**
* Get stats about the longest initiator chain (as determined by time duration)
* @param {LH.Artifacts.CriticalRequestNode} tree
* @return {{duration: number, length: number, transferSize: number}}
*/
static _getLongestChain(tree) {
const longest = {
duration: 0,
length: 0,
transferSize: 0,
};
CriticalRequestChains._traverse(tree, opts => {
const duration = opts.chainDuration;
if (duration > longest.duration) {
longest.duration = duration;
longest.transferSize = opts.chainTransferSize;
longest.length = opts.depth;
}
});
// Always return the longest chain + 1 because the depth is zero indexed.
longest.length++;
return longest;
}
/**
* @param {LH.Artifacts.CriticalRequestNode} tree
* @return {LH.Audit.Details.SimpleCriticalRequestNode}
*/
static flattenRequests(tree) {
/** @type {LH.Audit.Details.SimpleCriticalRequestNode} */
const flattendChains = {};
/** @type {Map<string, LH.Audit.Details.SimpleCriticalRequestNode[string]>} */
const chainMap = new Map();
/** @param {CrcNodeInfo} opts */
function flatten(opts) {
const request = opts.node.request;
const simpleRequest = {
url: request.url,
startTime: request.networkRequestTime / 1000,
endTime: request.networkEndTime / 1000,
responseReceivedTime: request.responseHeadersEndTime / 1000,
transferSize: request.transferSize,
};
let chain = chainMap.get(opts.id);
if (chain) {
chain.request = simpleRequest;
} else {
chain = {
request: simpleRequest,
};
flattendChains[opts.id] = chain;
}
if (opts.node.children) {
for (const chainId of Object.keys(opts.node.children)) {
// Note: cast should be Partial<>, but filled in when child node is traversed.
const childChain = /** @type {LH.Audit.Details.SimpleCriticalRequestNode[string]} */ ({
request: {},
});
chainMap.set(chainId, childChain);
if (!chain.children) {
chain.children = {};
}
chain.children[chainId] = childChain;
}
}
chainMap.set(opts.id, chain);
}
CriticalRequestChains._traverse(tree, flatten);
return flattendChains;
}
/**
* Audits the page to give a score for First Meaningful Paint.
* @param {LH.Artifacts} artifacts The artifacts from the gather phase.
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const URL = artifacts.URL;
const chains = await ComputedChains.request({devtoolsLog, trace, URL}, context);
let chainCount = 0;
/**
* @param {LH.Audit.Details.SimpleCriticalRequestNode} node
* @param {number} depth
*/
function walk(node, depth) {
const childIds = Object.keys(node);
childIds.forEach(id => {
const child = node[id];
if (child.children) {
walk(child.children, depth + 1);
} else {
// if the node doesn't have a children field, then it is a leaf, so +1
chainCount++;
}
}, '');
}
// Convert
const flattenedChains = CriticalRequestChains.flattenRequests(chains);
// Account for initial navigation
const initialNavKey = Object.keys(flattenedChains)[0];
const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
walk(initialNavChildren, 0);
}
const longestChain = CriticalRequestChains._getLongestChain(chains);
return {
score: Number(chainCount === 0),
notApplicable: chainCount === 0,
displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
details: {
type: 'criticalrequestchain',
chains: flattenedChains,
longestChain,
},
};
}
}
export default CriticalRequestChains;
export {UIStrings};