lighthouse 9.5.0-dev.20220313 → 9.5.0-dev.20220316
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/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +0 -1
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -0
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +9 -8
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +11 -12
- package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +9 -10
- package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +10 -6
- package/lighthouse-core/audits/deprecations.js +3 -3
- package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +1 -1
- package/lighthouse-core/audits/dobetterweb/no-document-write.js +1 -1
- package/lighthouse-core/audits/dobetterweb/notification-on-start.js +1 -1
- package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
- package/lighthouse-core/audits/errors-in-console.js +2 -2
- package/lighthouse-core/audits/no-unload-listeners.js +7 -15
- package/lighthouse-core/audits/script-elements-test-audit.js +30 -0
- package/lighthouse-core/audits/script-treemap-data.js +22 -21
- package/lighthouse-core/audits/valid-source-maps.js +12 -14
- package/lighthouse-core/audits/violation-audit.js +1 -1
- package/lighthouse-core/computed/js-bundles.js +11 -11
- package/lighthouse-core/computed/module-duplication.js +6 -5
- package/lighthouse-core/computed/unused-javascript-summary.js +16 -36
- package/lighthouse-core/config/default-config.js +1 -0
- package/lighthouse-core/fraggle-rock/config/default-config.js +3 -0
- package/lighthouse-core/gather/gatherers/console-messages.js +4 -2
- package/lighthouse-core/gather/gatherers/js-usage.js +6 -52
- package/lighthouse-core/gather/gatherers/script-elements.js +7 -56
- package/lighthouse-core/gather/gatherers/scripts.js +136 -0
- package/lighthouse-core/gather/gatherers/source-maps.js +3 -0
- package/lighthouse-core/lib/url-shim.js +2 -2
- package/package.json +1 -1
- package/types/artifacts.d.ts +22 -7
|
@@ -158,6 +158,7 @@ class UnusedBytes extends Audit {
|
|
|
158
158
|
|
|
159
159
|
const simulationBeforeChanges = simulator.simulate(graph, {label: beforeLabel});
|
|
160
160
|
|
|
161
|
+
// TODO: change this to wastedBytesByScriptId
|
|
161
162
|
const wastedBytesByUrl = options.providedWastedBytesByUrl || new Map();
|
|
162
163
|
if (!options.providedWastedBytesByUrl) {
|
|
163
164
|
for (const {url, wastedBytes} of results) {
|
|
@@ -48,7 +48,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
48
48
|
title: str_(UIStrings.title),
|
|
49
49
|
description: str_(UIStrings.description),
|
|
50
50
|
scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
|
|
51
|
-
requiredArtifacts: ['devtoolsLogs', 'traces', 'SourceMaps', '
|
|
51
|
+
requiredArtifacts: ['devtoolsLogs', 'traces', 'SourceMaps', 'Scripts',
|
|
52
52
|
'GatherContext', 'URL'],
|
|
53
53
|
};
|
|
54
54
|
}
|
|
@@ -85,10 +85,10 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
85
85
|
|
|
86
86
|
const normalizedSource = 'node_modules/' + DuplicatedJavascript._getNodeModuleName(source);
|
|
87
87
|
const aggregatedSourceDatas = groupedDuplication.get(normalizedSource) || [];
|
|
88
|
-
for (const {scriptUrl, resourceSize} of sourceDatas) {
|
|
89
|
-
let sourceData = aggregatedSourceDatas.find(d => d.
|
|
88
|
+
for (const {scriptId, scriptUrl, resourceSize} of sourceDatas) {
|
|
89
|
+
let sourceData = aggregatedSourceDatas.find(d => d.scriptId === scriptId);
|
|
90
90
|
if (!sourceData) {
|
|
91
|
-
sourceData = {scriptUrl, resourceSize: 0};
|
|
91
|
+
sourceData = {scriptId, scriptUrl, resourceSize: 0};
|
|
92
92
|
aggregatedSourceDatas.push(sourceData);
|
|
93
93
|
}
|
|
94
94
|
sourceData.resourceSize += resourceSize;
|
|
@@ -157,7 +157,9 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
157
157
|
let wastedBytesTotal = 0;
|
|
158
158
|
for (let i = 0; i < sourceDatas.length; i++) {
|
|
159
159
|
const sourceData = sourceDatas[i];
|
|
160
|
-
const
|
|
160
|
+
const scriptId = sourceData.scriptId;
|
|
161
|
+
const script = artifacts.Scripts.find(script => script.scriptId === scriptId);
|
|
162
|
+
const url = script?.url || '';
|
|
161
163
|
|
|
162
164
|
/** @type {number|undefined} */
|
|
163
165
|
let transferRatio = transferRatioByUrl.get(url);
|
|
@@ -166,13 +168,12 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
166
168
|
mainDocumentRecord :
|
|
167
169
|
networkRecords.find(n => n.url === url);
|
|
168
170
|
|
|
169
|
-
|
|
170
|
-
if (!script || script.content === null) {
|
|
171
|
+
if (!script || script.length === undefined) {
|
|
171
172
|
// This should never happen because we found the wasted bytes from bundles, which required contents in a ScriptElement.
|
|
172
173
|
continue;
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
const contentLength = script.
|
|
176
|
+
const contentLength = script.length;
|
|
176
177
|
transferRatio = DuplicatedJavascript._estimateTransferRatio(networkRecord, contentLength);
|
|
177
178
|
transferRatioByUrl.set(url, transferRatio);
|
|
178
179
|
}
|
|
@@ -109,7 +109,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
109
109
|
scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
|
|
110
110
|
description: str_(UIStrings.description),
|
|
111
111
|
title: str_(UIStrings.title),
|
|
112
|
-
requiredArtifacts: ['devtoolsLogs', 'traces', '
|
|
112
|
+
requiredArtifacts: ['devtoolsLogs', 'traces', 'Scripts', 'SourceMaps',
|
|
113
113
|
'GatherContext', 'URL'],
|
|
114
114
|
};
|
|
115
115
|
}
|
|
@@ -274,7 +274,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
274
274
|
* Returns a collection of match results grouped by script url.
|
|
275
275
|
*
|
|
276
276
|
* @param {CodePatternMatcher} matcher
|
|
277
|
-
* @param {LH.
|
|
277
|
+
* @param {LH.Artifacts['Scripts']} scripts
|
|
278
278
|
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
279
279
|
* @param {LH.Artifacts.Bundle[]} bundles
|
|
280
280
|
* @return {Map<string, PatternMatchResult[]>}
|
|
@@ -284,16 +284,14 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
284
284
|
const urlToMatchResults = new Map();
|
|
285
285
|
const polyfillData = this.getPolyfillData();
|
|
286
286
|
|
|
287
|
-
for (const {
|
|
287
|
+
for (const {scriptId, url, content} of Object.values(scripts)) {
|
|
288
288
|
if (!content) continue;
|
|
289
|
-
const networkRecord = networkRecords.find(record => record.requestId === requestId);
|
|
290
|
-
if (!networkRecord) continue;
|
|
291
289
|
|
|
292
290
|
// Start with pattern matching against the downloaded script.
|
|
293
291
|
const matches = matcher.match(content);
|
|
294
292
|
|
|
295
293
|
// If it's a bundle with source maps, add in the polyfill modules by name too.
|
|
296
|
-
const bundle = bundles.find(b => b.script.
|
|
294
|
+
const bundle = bundles.find(b => b.script.scriptId === scriptId);
|
|
297
295
|
if (bundle) {
|
|
298
296
|
for (const {coreJs2Module, coreJs3Module, name} of polyfillData) {
|
|
299
297
|
// Skip if the pattern matching found a match for this polyfill.
|
|
@@ -313,7 +311,8 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
313
311
|
}
|
|
314
312
|
|
|
315
313
|
if (!matches.length) continue;
|
|
316
|
-
|
|
314
|
+
// TODO: scriptId
|
|
315
|
+
urlToMatchResults.set(url, matches);
|
|
317
316
|
}
|
|
318
317
|
|
|
319
318
|
return urlToMatchResults;
|
|
@@ -374,16 +373,16 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
374
373
|
if (transferRatio !== undefined) return transferRatio;
|
|
375
374
|
|
|
376
375
|
const mainDocumentRecord = NetworkAnalyzer.findOptionalMainDocument(networkRecords);
|
|
376
|
+
const script = artifacts.Scripts.find(script => script.url === url);
|
|
377
377
|
const networkRecord = url === artifacts.URL.finalUrl ?
|
|
378
378
|
mainDocumentRecord :
|
|
379
|
-
networkRecords.find(n => n.url === url);
|
|
380
|
-
const script = artifacts.ScriptElements.find(script => script.src === url);
|
|
379
|
+
networkRecords.find(n => n.url === script?.url);
|
|
381
380
|
|
|
382
381
|
if (!script || script.content === null) {
|
|
383
382
|
// Can't find content, so just use 1.
|
|
384
383
|
transferRatio = 1;
|
|
385
384
|
} else {
|
|
386
|
-
const contentLength = script.
|
|
385
|
+
const contentLength = script.length || 0;
|
|
387
386
|
const transferSize =
|
|
388
387
|
ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
|
|
389
388
|
transferRatio = transferSize / contentLength;
|
|
@@ -415,7 +414,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
415
414
|
const transferRatioByUrl = new Map();
|
|
416
415
|
|
|
417
416
|
const urlToMatchResults =
|
|
418
|
-
this.detectAcrossScripts(matcher, artifacts.
|
|
417
|
+
this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
|
|
419
418
|
for (const [url, matches] of urlToMatchResults.entries()) {
|
|
420
419
|
const transferRatio = await this.estimateTransferRatioForScript(
|
|
421
420
|
transferRatioByUrl, url, artifacts, networkRecords);
|
|
@@ -432,7 +431,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
432
431
|
totalBytes: 0,
|
|
433
432
|
};
|
|
434
433
|
|
|
435
|
-
const bundle = bundles.find(bundle => bundle.script.
|
|
434
|
+
const bundle = bundles.find(bundle => bundle.script.url === url); // TODO: scriptId
|
|
436
435
|
for (const match of matches) {
|
|
437
436
|
const {name, line, column} = match;
|
|
438
437
|
/** @type {SubItem} */
|
|
@@ -42,7 +42,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|
|
42
42
|
title: str_(UIStrings.title),
|
|
43
43
|
description: str_(UIStrings.description),
|
|
44
44
|
scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
|
|
45
|
-
requiredArtifacts: ['
|
|
45
|
+
requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL'],
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -78,15 +78,15 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|
|
78
78
|
/** @type {Array<LH.Audit.ByteEfficiencyItem>} */
|
|
79
79
|
const items = [];
|
|
80
80
|
const warnings = [];
|
|
81
|
-
for (const
|
|
82
|
-
if (!content) continue;
|
|
81
|
+
for (const script of artifacts.Scripts) {
|
|
82
|
+
if (!script.content) continue;
|
|
83
83
|
|
|
84
|
-
const networkRecord = networkRecords.find(record => record.
|
|
85
|
-
const displayUrl =
|
|
86
|
-
`inline: ${content.
|
|
87
|
-
|
|
84
|
+
const networkRecord = networkRecords.find(record => record.url === script.url);
|
|
85
|
+
const displayUrl = script.name === artifacts.URL.finalUrl ?
|
|
86
|
+
`inline: ${script.content.substring(0, 40)}...` :
|
|
87
|
+
script.url;
|
|
88
88
|
try {
|
|
89
|
-
const result = UnminifiedJavaScript.computeWaste(content, displayUrl, networkRecord);
|
|
89
|
+
const result = UnminifiedJavaScript.computeWaste(script.content, displayUrl, networkRecord);
|
|
90
90
|
// If the ratio is minimal, the file is likely already minified, so ignore it.
|
|
91
91
|
// If the total number of bytes to be saved is quite small, it's also safe to ignore.
|
|
92
92
|
if (result.wastedPercent < IGNORE_THRESHOLD_IN_PERCENT ||
|
|
@@ -94,8 +94,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
|
|
|
94
94
|
!Number.isFinite(result.wastedBytes)) continue;
|
|
95
95
|
items.push(result);
|
|
96
96
|
} catch (err) {
|
|
97
|
-
|
|
98
|
-
warnings.push(`Unable to process script ${url}: ${err.message}`);
|
|
97
|
+
warnings.push(`Unable to process script ${script.url}: ${err.message}`);
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -67,7 +67,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
|
67
67
|
title: str_(UIStrings.title),
|
|
68
68
|
description: str_(UIStrings.description),
|
|
69
69
|
scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
|
|
70
|
-
requiredArtifacts: ['JsUsage', '
|
|
70
|
+
requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
|
|
71
71
|
'devtoolsLogs', 'traces'],
|
|
72
72
|
};
|
|
73
73
|
}
|
|
@@ -86,12 +86,16 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
|
86
86
|
} = context.options || {};
|
|
87
87
|
|
|
88
88
|
const items = [];
|
|
89
|
-
for (const [
|
|
90
|
-
const
|
|
89
|
+
for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
|
|
90
|
+
const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
|
|
91
|
+
if (!script) continue; // This should never happen.
|
|
92
|
+
|
|
93
|
+
const networkRecord = networkRecords.find(record => record.url === script.url);
|
|
91
94
|
if (!networkRecord) continue;
|
|
92
|
-
|
|
95
|
+
|
|
96
|
+
const bundle = bundles.find(b => b.script.scriptId === scriptId);
|
|
93
97
|
const unusedJsSummary =
|
|
94
|
-
await UnusedJavaScriptSummary.request({
|
|
98
|
+
await UnusedJavaScriptSummary.request({scriptId, scriptCoverage, bundle}, context);
|
|
95
99
|
if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
|
|
96
100
|
|
|
97
101
|
const transfer = ByteEfficiencyAudit
|
|
@@ -99,7 +103,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
|
|
|
99
103
|
const transferRatio = transfer / unusedJsSummary.totalBytes;
|
|
100
104
|
/** @type {LH.Audit.ByteEfficiencyItem} */
|
|
101
105
|
const item = {
|
|
102
|
-
url:
|
|
106
|
+
url: script.url,
|
|
103
107
|
totalBytes: Math.round(transferRatio * unusedJsSummary.totalBytes),
|
|
104
108
|
wastedBytes: Math.round(transferRatio * unusedJsSummary.wastedBytes),
|
|
105
109
|
wastedPercent: unusedJsSummary.wastedPercent,
|
|
@@ -48,7 +48,7 @@ class Deprecations extends Audit {
|
|
|
48
48
|
title: str_(UIStrings.title),
|
|
49
49
|
failureTitle: str_(UIStrings.failureTitle),
|
|
50
50
|
description: str_(UIStrings.description),
|
|
51
|
-
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', '
|
|
51
|
+
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'Scripts'],
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -65,8 +65,8 @@ class Deprecations extends Audit {
|
|
|
65
65
|
if (artifacts.InspectorIssues.deprecationIssue.length) {
|
|
66
66
|
deprecations = artifacts.InspectorIssues.deprecationIssue
|
|
67
67
|
.map(deprecation => {
|
|
68
|
-
const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
69
|
-
const bundle = bundles.find(bundle => bundle.script.
|
|
68
|
+
const {scriptId, url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
69
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === scriptId);
|
|
70
70
|
return {
|
|
71
71
|
value: deprecation.message || '',
|
|
72
72
|
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
|
|
@@ -38,7 +38,7 @@ class GeolocationOnStart extends ViolationAudit {
|
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
-
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', '
|
|
41
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -54,7 +54,7 @@ class NoDocWriteAudit extends ViolationAudit {
|
|
|
54
54
|
title: str_(UIStrings.title),
|
|
55
55
|
failureTitle: str_(UIStrings.failureTitle),
|
|
56
56
|
description: str_(UIStrings.description),
|
|
57
|
-
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', '
|
|
57
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -38,7 +38,7 @@ class NotificationOnStart extends ViolationAudit {
|
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
-
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', '
|
|
41
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -37,7 +37,7 @@ class PassiveEventsAudit extends ViolationAudit {
|
|
|
37
37
|
title: str_(UIStrings.title),
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
|
-
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', '
|
|
40
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -40,7 +40,7 @@ class ErrorLogs extends Audit {
|
|
|
40
40
|
title: str_(UIStrings.title),
|
|
41
41
|
failureTitle: str_(UIStrings.failureTitle),
|
|
42
42
|
description: str_(UIStrings.description),
|
|
43
|
-
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', '
|
|
43
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -87,7 +87,7 @@ class ErrorLogs extends Audit {
|
|
|
87
87
|
const consoleRows = artifacts.ConsoleMessages
|
|
88
88
|
.filter(item => item.level === 'error')
|
|
89
89
|
.map(item => {
|
|
90
|
-
const bundle = bundles.find(bundle => bundle.script.
|
|
90
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === item.scriptId);
|
|
91
91
|
return {
|
|
92
92
|
source: item.source,
|
|
93
93
|
description: item.text,
|
|
@@ -30,7 +30,7 @@ class NoUnloadListeners extends Audit {
|
|
|
30
30
|
title: str_(UIStrings.title),
|
|
31
31
|
failureTitle: str_(UIStrings.failureTitle),
|
|
32
32
|
description: str_(UIStrings.description),
|
|
33
|
-
requiredArtifacts: ['GlobalListeners', '
|
|
33
|
+
requiredArtifacts: ['GlobalListeners', 'SourceMaps', 'Scripts'],
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -54,33 +54,25 @@ class NoUnloadListeners extends Audit {
|
|
|
54
54
|
{key: 'source', itemType: 'source-location', text: str_(i18n.UIStrings.columnSource)},
|
|
55
55
|
];
|
|
56
56
|
|
|
57
|
-
// Look up scriptId to script URL via the JsUsage artifact.
|
|
58
|
-
/** @type {Map<string, string>} */
|
|
59
|
-
const scriptIdToUrl = new Map();
|
|
60
|
-
for (const [url, usages] of Object.entries(artifacts.JsUsage)) {
|
|
61
|
-
for (const usage of usages) {
|
|
62
|
-
scriptIdToUrl.set(usage.scriptId, url);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
57
|
/** @type {Array<{source: LH.Audit.Details.ItemValue}>} */
|
|
67
58
|
const tableItems = unloadListeners.map(listener => {
|
|
68
|
-
const
|
|
59
|
+
const {lineNumber, columnNumber} = listener;
|
|
60
|
+
const script = artifacts.Scripts.find(s => s.scriptId === listener.scriptId);
|
|
69
61
|
|
|
70
62
|
// If we can't find a url, still show something so the user can manually
|
|
71
63
|
// look for where an `unload` handler is being created.
|
|
72
|
-
if (!
|
|
64
|
+
if (!script) {
|
|
73
65
|
return {
|
|
74
66
|
source: {
|
|
75
67
|
type: 'url',
|
|
76
|
-
value:
|
|
68
|
+
value: `(unknown):${lineNumber}:${columnNumber}`,
|
|
77
69
|
},
|
|
78
70
|
};
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
const bundle = bundles.find(bundle => bundle.script.
|
|
73
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
|
|
82
74
|
return {
|
|
83
|
-
source: Audit.makeSourceLocation(url,
|
|
75
|
+
source: Audit.makeSourceLocation(script.url, lineNumber, columnNumber, bundle),
|
|
84
76
|
};
|
|
85
77
|
});
|
|
86
78
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @fileoverview This is a fake audit used exclusively in smoke tests to force inclusion of ScriptElements artifact.
|
|
10
|
+
* It is included here for complex reasons in the way the bundled smoketests work.
|
|
11
|
+
*
|
|
12
|
+
* The smokehouse configs are evaluated first in the node CLI side (which requires an absolute path using LH_ROOT).
|
|
13
|
+
* The smokehouse configs are then *re-evaluated* in the bundled context for execution by Lighthouse (which *cannot* use an absolute path using LH_ROOT).
|
|
14
|
+
*
|
|
15
|
+
* This mismatch in environment demands that the audit path in the config must be context-aware,
|
|
16
|
+
* yet the require-graph for the config is included before even the CLI knows that it will be using
|
|
17
|
+
* a bundled runner. Rather than force a massive smoketest architecture change, we include a harmless,
|
|
18
|
+
* test-only audit in our core list instead.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
meta: {
|
|
23
|
+
id: 'script-elements-test-audit',
|
|
24
|
+
title: 'ScriptElements',
|
|
25
|
+
failureTitle: 'ScriptElements',
|
|
26
|
+
description: 'Audit to force the inclusion of ScriptElements artifact',
|
|
27
|
+
requiredArtifacts: ['ScriptElements'],
|
|
28
|
+
},
|
|
29
|
+
audit: () => ({score: 1}),
|
|
30
|
+
};
|
|
@@ -31,7 +31,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
31
31
|
title: 'Script Treemap Data',
|
|
32
32
|
description: 'Used for treemap app',
|
|
33
33
|
requiredArtifacts:
|
|
34
|
-
['traces', 'devtoolsLogs', 'SourceMaps', '
|
|
34
|
+
['traces', 'devtoolsLogs', 'SourceMaps', 'Scripts', 'JsUsage', 'URL'],
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -167,11 +167,10 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
167
167
|
const nodes = [];
|
|
168
168
|
|
|
169
169
|
let inlineScriptLength = 0;
|
|
170
|
-
for (const
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
inlineScriptLength += (scriptElement.content || '').length;
|
|
170
|
+
for (const script of artifacts.Scripts) {
|
|
171
|
+
// Combine so that inline scripts show up as a single root node.
|
|
172
|
+
if (script.url === artifacts.URL.finalUrl) {
|
|
173
|
+
inlineScriptLength += (script.content || '').length;
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
176
|
if (inlineScriptLength) {
|
|
@@ -185,25 +184,28 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
185
184
|
const bundles = await JsBundles.request(artifacts, context);
|
|
186
185
|
const duplicationByPath = await ModuleDuplication.request(artifacts, context);
|
|
187
186
|
|
|
188
|
-
for (const
|
|
189
|
-
if (
|
|
187
|
+
for (const script of artifacts.Scripts) {
|
|
188
|
+
if (script.url === artifacts.URL.finalUrl) continue; // Handled above.
|
|
190
189
|
|
|
191
|
-
const name =
|
|
192
|
-
const bundle = bundles.find(bundle =>
|
|
193
|
-
const
|
|
194
|
-
|
|
190
|
+
const name = script.url;
|
|
191
|
+
const bundle = bundles.find(bundle => script.scriptId === bundle.script.scriptId);
|
|
192
|
+
const scriptCoverage = /** @type {Omit<LH.Crdp.Profiler.ScriptCoverage, "url"> | undefined} */
|
|
193
|
+
(artifacts.JsUsage[script.scriptId]);
|
|
194
|
+
if (!bundle && !scriptCoverage) {
|
|
195
195
|
// No bundle and no coverage information, so simply make a single node
|
|
196
196
|
// detailing how big the script is.
|
|
197
197
|
|
|
198
198
|
nodes.push({
|
|
199
199
|
name,
|
|
200
|
-
resourceBytes:
|
|
200
|
+
resourceBytes: script.length || 0,
|
|
201
201
|
});
|
|
202
202
|
continue;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
const unusedJavascriptSummary =
|
|
206
|
-
|
|
205
|
+
const unusedJavascriptSummary = scriptCoverage ?
|
|
206
|
+
await UnusedJavaScriptSummary.request(
|
|
207
|
+
{scriptId: script.scriptId, scriptCoverage, bundle}, context) :
|
|
208
|
+
undefined;
|
|
207
209
|
|
|
208
210
|
/** @type {LH.Treemap.Node} */
|
|
209
211
|
let node;
|
|
@@ -218,7 +220,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
218
220
|
resourceBytes: bundle.sizes.files[source],
|
|
219
221
|
};
|
|
220
222
|
|
|
221
|
-
if (unusedJavascriptSummary
|
|
223
|
+
if (unusedJavascriptSummary?.sourcesWastedBytes) {
|
|
222
224
|
sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes[source];
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -241,20 +243,19 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
241
243
|
const sourceData = {
|
|
242
244
|
resourceBytes: bundle.sizes.unmappedBytes,
|
|
243
245
|
};
|
|
244
|
-
if (unusedJavascriptSummary
|
|
246
|
+
if (unusedJavascriptSummary?.sourcesWastedBytes) {
|
|
245
247
|
sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes['(unmapped)'];
|
|
246
248
|
}
|
|
247
249
|
sourcesData['(unmapped)'] = sourceData;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
node = this.makeScriptNode(
|
|
252
|
+
node = this.makeScriptNode(script.url, bundle.rawMap.sourceRoot || '', sourcesData);
|
|
251
253
|
} else {
|
|
252
254
|
// No valid source map for this script, so we can only produce a single node.
|
|
253
|
-
|
|
254
255
|
node = {
|
|
255
256
|
name,
|
|
256
|
-
resourceBytes: unusedJavascriptSummary.
|
|
257
|
-
unusedBytes: unusedJavascriptSummary
|
|
257
|
+
resourceBytes: unusedJavascriptSummary?.totalBytes ?? script.length ?? 0,
|
|
258
|
+
unusedBytes: unusedJavascriptSummary?.wastedBytes,
|
|
258
259
|
};
|
|
259
260
|
}
|
|
260
261
|
|
|
@@ -44,22 +44,22 @@ class ValidSourceMaps extends Audit {
|
|
|
44
44
|
title: str_(UIStrings.title),
|
|
45
45
|
failureTitle: str_(UIStrings.failureTitle),
|
|
46
46
|
description: str_(UIStrings.description),
|
|
47
|
-
requiredArtifacts: ['
|
|
47
|
+
requiredArtifacts: ['Scripts', 'SourceMaps', 'URL'],
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Returns true if the size of the script exceeds a static threshold.
|
|
53
|
-
* @param {LH.Artifacts.
|
|
53
|
+
* @param {LH.Artifacts.Script} script
|
|
54
54
|
* @param {string} finalURL
|
|
55
55
|
* @return {boolean}
|
|
56
56
|
*/
|
|
57
|
-
static isLargeFirstPartyJS(
|
|
58
|
-
if (
|
|
57
|
+
static isLargeFirstPartyJS(script, finalURL) {
|
|
58
|
+
if (!script.length) return false;
|
|
59
59
|
|
|
60
|
-
const isLargeJS =
|
|
61
|
-
const isFirstPartyJS =
|
|
62
|
-
thirdPartyWeb.isFirstParty(
|
|
60
|
+
const isLargeJS = script.length >= LARGE_JS_BYTE_THRESHOLD;
|
|
61
|
+
const isFirstPartyJS = script.url ?
|
|
62
|
+
thirdPartyWeb.isFirstParty(script.url, thirdPartyWeb.getEntity(finalURL)) : false;
|
|
63
63
|
|
|
64
64
|
return isLargeJS && isFirstPartyJS;
|
|
65
65
|
}
|
|
@@ -75,16 +75,14 @@ class ValidSourceMaps extends Audit {
|
|
|
75
75
|
|
|
76
76
|
let missingMapsForLargeFirstPartyFile = false;
|
|
77
77
|
const results = [];
|
|
78
|
-
for (const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const sourceMap = SourceMaps.find(m => m.scriptUrl === scriptElement.src);
|
|
78
|
+
for (const script of artifacts.Scripts) {
|
|
79
|
+
const sourceMap = SourceMaps.find(m => m.scriptId === script.scriptId);
|
|
82
80
|
const errors = [];
|
|
83
|
-
const isLargeFirstParty = this.isLargeFirstPartyJS(
|
|
81
|
+
const isLargeFirstParty = this.isLargeFirstPartyJS(script, artifacts.URL.finalUrl);
|
|
84
82
|
|
|
85
83
|
if (isLargeFirstParty && (!sourceMap || !sourceMap.map)) {
|
|
86
84
|
missingMapsForLargeFirstPartyFile = true;
|
|
87
|
-
isMissingMapForLargeFirstPartyScriptUrl.add(
|
|
85
|
+
isMissingMapForLargeFirstPartyScriptUrl.add(script.url);
|
|
88
86
|
errors.push({error: str_(UIStrings.missingSourceMapErrorMessage)});
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -107,7 +105,7 @@ class ValidSourceMaps extends Audit {
|
|
|
107
105
|
|
|
108
106
|
if (sourceMap || errors.length) {
|
|
109
107
|
results.push({
|
|
110
|
-
scriptUrl:
|
|
108
|
+
scriptUrl: script.url,
|
|
111
109
|
sourceMapUrl: sourceMap?.sourceMapUrl,
|
|
112
110
|
subItems: {
|
|
113
111
|
type: /** @type {const} */ ('subitems'),
|
|
@@ -31,7 +31,7 @@ class ViolationAudit extends Audit {
|
|
|
31
31
|
return artifacts.ConsoleMessages
|
|
32
32
|
.filter(entry => entry.url && entry.source === 'violation' && pattern.test(entry.text))
|
|
33
33
|
.map(entry => {
|
|
34
|
-
const bundle = bundles.find(bundle => bundle.script.
|
|
34
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === entry.scriptId);
|
|
35
35
|
return Audit.makeSourceLocationFromConsoleMessage(entry, bundle);
|
|
36
36
|
})
|
|
37
37
|
.filter(filterUndefined)
|
|
@@ -12,14 +12,15 @@ const SDK = require('../lib/cdt/SDK.js');
|
|
|
12
12
|
/**
|
|
13
13
|
* Calculate the number of bytes contributed by each source file
|
|
14
14
|
* @param {LH.Artifacts.Bundle['map']} map
|
|
15
|
+
* @param {number} contentLength
|
|
15
16
|
* @param {string} content
|
|
16
17
|
* @return {LH.Artifacts.Bundle['sizes']}
|
|
17
18
|
*/
|
|
18
|
-
function computeGeneratedFileSizes(map, content) {
|
|
19
|
+
function computeGeneratedFileSizes(map, contentLength, content) {
|
|
19
20
|
const lines = content.split('\n');
|
|
20
21
|
/** @type {Record<string, number>} */
|
|
21
22
|
const files = {};
|
|
22
|
-
const totalBytes =
|
|
23
|
+
const totalBytes = contentLength;
|
|
23
24
|
let unmappedBytes = totalBytes;
|
|
24
25
|
|
|
25
26
|
// @ts-expect-error: This function is added in SDK.js. This will eventually be added to CDT.
|
|
@@ -78,10 +79,10 @@ function computeGeneratedFileSizes(map, content) {
|
|
|
78
79
|
|
|
79
80
|
class JSBundles {
|
|
80
81
|
/**
|
|
81
|
-
* @param {Pick<LH.Artifacts, 'SourceMaps'|'
|
|
82
|
+
* @param {Pick<LH.Artifacts, 'SourceMaps'|'Scripts'>} artifacts
|
|
82
83
|
*/
|
|
83
84
|
static async compute_(artifacts) {
|
|
84
|
-
const {SourceMaps,
|
|
85
|
+
const {SourceMaps, Scripts} = artifacts;
|
|
85
86
|
|
|
86
87
|
/** @type {LH.Artifacts.Bundle[]} */
|
|
87
88
|
const bundles = [];
|
|
@@ -89,23 +90,22 @@ class JSBundles {
|
|
|
89
90
|
// Collate map and script, compute file sizes.
|
|
90
91
|
for (const SourceMap of SourceMaps) {
|
|
91
92
|
if (!SourceMap.map) continue;
|
|
92
|
-
const {
|
|
93
|
+
const {scriptId, map: rawMap} = SourceMap;
|
|
93
94
|
|
|
94
95
|
if (!rawMap.mappings) continue;
|
|
95
96
|
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
97
|
+
const script = Scripts.find(s => s.scriptId === scriptId);
|
|
98
|
+
if (!script) continue;
|
|
98
99
|
|
|
99
100
|
const compiledUrl = SourceMap.scriptUrl || 'compiled.js';
|
|
100
101
|
const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
|
|
101
102
|
const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
|
|
102
103
|
|
|
103
|
-
const
|
|
104
|
-
const sizes = computeGeneratedFileSizes(map, content);
|
|
104
|
+
const sizes = computeGeneratedFileSizes(map, script.length || 0, script.content || '');
|
|
105
105
|
|
|
106
106
|
const bundle = {
|
|
107
107
|
rawMap,
|
|
108
|
-
script
|
|
108
|
+
script,
|
|
109
109
|
map,
|
|
110
110
|
sizes,
|
|
111
111
|
};
|
|
@@ -116,4 +116,4 @@ class JSBundles {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
module.exports = makeComputedArtifact(JSBundles, ['
|
|
119
|
+
module.exports = makeComputedArtifact(JSBundles, ['Scripts', 'SourceMaps']);
|