lighthouse 8.3.0-dev.20210907 → 8.3.0
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/changelog.md +1 -1
- package/dist/report/standalone.js +196 -0
- package/jest.config.js +0 -4
- package/lighthouse-cli/cli-flags.js +6 -14
- package/lighthouse-cli/run.js +1 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +3 -9
- package/lighthouse-cli/test/smokehouse/report-assert.js +16 -36
- package/lighthouse-core/audits/large-javascript-libraries.js +166 -0
- package/lighthouse-core/audits/seo/http-status-code.js +6 -2
- package/lighthouse-core/audits/viewport.js +1 -2
- package/lighthouse-core/config/config-helpers.js +75 -9
- package/lighthouse-core/config/config.js +50 -6
- package/lighthouse-core/config/constants.js +0 -1
- package/lighthouse-core/config/default-config.js +0 -3
- package/lighthouse-core/config/experimental-config.js +8 -0
- package/lighthouse-core/config/metrics-to-audits.js +0 -1
- package/lighthouse-core/fraggle-rock/config/config.js +4 -3
- package/lighthouse-core/fraggle-rock/config/default-config.js +1 -3
- package/lighthouse-core/fraggle-rock/config/validation.js +0 -201
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +0 -4
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +8 -16
- package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +0 -4
- package/lighthouse-core/fraggle-rock/gather/session.js +5 -30
- package/lighthouse-core/gather/driver/navigation.js +6 -7
- package/lighthouse-core/gather/driver/wait-for-condition.js +0 -27
- package/lighthouse-core/gather/fetcher.js +7 -17
- package/lighthouse-core/gather/gather-runner.js +0 -1
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +6 -2
- package/lighthouse-core/gather/gatherers/seo/tap-targets.js +2 -2
- package/lighthouse-core/gather/gatherers/stacks.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +2 -2
- package/lighthouse-core/lib/cdt/SDK.js +0 -1
- package/lighthouse-core/lib/csp-evaluator.js +4 -17
- package/{report/generator → lighthouse-core/lib}/file-namer.js +0 -0
- package/lighthouse-core/lib/i18n/README.md +1 -1
- package/lighthouse-core/lib/i18n/i18n.js +7 -7
- package/lighthouse-core/lib/i18n/locales/ar-XB.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ar.json +18 -0
- package/lighthouse-core/lib/i18n/locales/bg.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ca.json +18 -0
- package/lighthouse-core/lib/i18n/locales/cs.json +18 -0
- package/lighthouse-core/lib/i18n/locales/da.json +18 -0
- package/lighthouse-core/lib/i18n/locales/de.json +18 -0
- package/lighthouse-core/lib/i18n/locales/el.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-GB.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-US.json +18 -18
- package/lighthouse-core/lib/i18n/locales/en-XA.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-XL.json +18 -18
- package/lighthouse-core/lib/i18n/locales/es-419.json +18 -0
- package/lighthouse-core/lib/i18n/locales/es.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fil.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/he.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hu.json +18 -0
- package/lighthouse-core/lib/i18n/locales/id.json +18 -0
- package/lighthouse-core/lib/i18n/locales/it.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ja.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ko.json +18 -0
- package/lighthouse-core/lib/i18n/locales/lt.json +18 -0
- package/lighthouse-core/lib/i18n/locales/lv.json +18 -0
- package/lighthouse-core/lib/i18n/locales/nl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/no.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pt-PT.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pt.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ro.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ru.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sk.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sr-Latn.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sv.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ta.json +18 -0
- package/lighthouse-core/lib/i18n/locales/te.json +18 -0
- package/lighthouse-core/lib/i18n/locales/th.json +18 -0
- package/lighthouse-core/lib/i18n/locales/tr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/uk.json +18 -0
- package/lighthouse-core/lib/i18n/locales/vi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh-HK.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh-TW.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh.json +18 -0
- package/lighthouse-core/lib/large-javascript-libraries/bundlephobia-database.json +62 -0
- package/lighthouse-core/lib/large-javascript-libraries/library-suggestions.js +20 -0
- package/lighthouse-core/lib/network-request.js +1 -3
- package/lighthouse-core/lib/traces/pwmetrics-events.js +2 -2
- package/lighthouse-core/lib/url-shim.js +1 -7
- package/lighthouse-core/runner.js +1 -4
- package/lighthouse-core/util-commonjs.js +2 -2
- package/now.json +1 -1
- package/package.json +13 -20
- package/report/README.md +2 -2
- package/report/assets/standalone-template.html +1 -0
- package/report/assets/styles.css +27 -23
- package/report/assets/templates.html +40 -37
- package/report/clients/bundle.js +0 -1
- package/report/{package.json → clients/package.json} +0 -0
- package/report/clients/psi.js +3 -13
- package/report/clients/standalone.js +1 -1
- package/report/{types/buffer.d.ts → clients/treemap.js} +13 -11
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/components.js +48 -46
- package/report/renderer/crc-details-renderer.js +16 -16
- package/report/renderer/details-renderer.js +1 -1
- package/report/renderer/dom.js +0 -17
- package/report/renderer/element-screenshot-renderer.js +9 -9
- package/{flow-report/test/setup/global-setup.ts → report/renderer/file-namer.js} +2 -5
- package/report/renderer/logger.js +4 -4
- package/report/renderer/package.json +4 -0
- package/report/renderer/report-renderer.js +11 -4
- package/report/renderer/report-ui-features.js +656 -43
- package/report/renderer/text-encoding.js +1 -1
- package/report/renderer/util.js +2 -2
- package/report/{generator/report-assets.js → report-assets.js} +3 -9
- package/report/{generator/report-generator.js → report-generator.js} +9 -37
- package/report/test/clients/package.json +4 -0
- package/report/test/clients/psi-test.js +1 -3
- package/report/test/renderer/category-renderer-test.js +3 -8
- package/report/test/renderer/components-test.js +0 -35
- package/report/test/renderer/crc-details-renderer-test.js +4 -5
- package/report/test/renderer/details-renderer-test.js +1 -3
- package/report/test/renderer/dom-test.js +6 -8
- package/report/test/renderer/i18n-test.js +0 -1
- package/report/test/renderer/package.json +4 -0
- package/report/test/renderer/performance-category-renderer-test.js +27 -30
- package/report/test/renderer/pwa-category-renderer-test.js +1 -2
- package/report/test/renderer/report-renderer-test.js +0 -1
- package/report/test/renderer/report-ui-features-test.js +28 -20
- package/report/test/renderer/snippet-renderer-test.js +0 -1
- package/report/test/renderer/text-encoding-test.js +0 -2
- package/report/test/renderer/util-test.js +0 -1
- package/report/test/{generator/report-generator-test.js → report-generator-test.js} +7 -9
- package/third-party/chromium-synchronization/installability-errors-test.js +27 -28
- package/third-party/snyk/snapshot.json +0 -2
- package/tsconfig.json +5 -24
- package/types/artifacts.d.ts +18 -5
- package/types/{lhr/audit-details.d.ts → audit-details.d.ts} +3 -18
- package/types/audit.d.ts +58 -7
- package/types/{lhr/budget.d.ts → budget.d.ts} +0 -0
- package/types/bundle-phobia-cli/index.d.ts +40 -0
- package/types/config.d.ts +8 -3
- package/types/externs.d.ts +119 -5
- package/types/gatherer.d.ts +2 -3
- package/types/global-lh.d.ts +14 -16
- package/{report/types/report-result.d.ts → types/html-renderer.d.ts} +17 -8
- package/types/{lhr/i18n.d.ts → i18n.d.ts} +25 -2
- package/types/{lhr/lhr.d.ts → lhr.d.ts} +9 -41
- package/types/smokehouse.d.ts +3 -3
- package/types/{lhr/treemap.d.ts → treemap.d.ts} +3 -3
- package/flow-report/assets/standalone-flow-template.html +0 -38
- package/flow-report/assets/styles.css +0 -310
- package/flow-report/jest.config.js +0 -24
- package/flow-report/src/app.tsx +0 -40
- package/flow-report/src/common.tsx +0 -72
- package/flow-report/src/icons.tsx +0 -96
- package/flow-report/src/sidebar/flow.tsx +0 -72
- package/flow-report/src/sidebar/sidebar.tsx +0 -89
- package/flow-report/src/summary/summary.tsx +0 -178
- package/flow-report/src/util.ts +0 -160
- package/flow-report/src/wrappers/gauge.tsx +0 -40
- package/flow-report/src/wrappers/report-renderer.tsx +0 -46
- package/flow-report/src/wrappers/report.tsx +0 -71
- package/flow-report/standalone-flow.tsx +0 -23
- package/flow-report/test/app-test.tsx +0 -48
- package/flow-report/test/common-test.tsx +0 -131
- package/flow-report/test/setup/env-setup.ts +0 -25
- package/flow-report/test/sidebar/flow-test.tsx +0 -71
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -53
- package/flow-report/test/summary/summary-test.tsx +0 -132
- package/flow-report/test/util-test.tsx +0 -109
- package/flow-report/tsconfig.json +0 -39
- package/flow-report/types/flow-report.d.ts +0 -27
- package/lighthouse-core/audits/lcp-lazy-loaded.js +0 -83
- package/report/.eslintrc.cjs +0 -33
- package/report/generator/README.md +0 -9
- package/report/generator/flow-report-assets.js +0 -20
- package/report/generator/package.json +0 -4
- package/report/generator/tsconfig.json +0 -28
- package/report/renderer/drop-down-menu.js +0 -215
- package/report/renderer/features-util.js +0 -26
- package/report/renderer/open-tab.js +0 -137
- package/report/renderer/topbar-features.js +0 -309
- package/report/test/generator/file-namer-test.js +0 -28
- package/report/test/generator/package.json +0 -4
- package/report/test-assets/faux-psi-template.html +0 -126
- package/report/test-assets/faux-psi.js +0 -92
- package/report/tsconfig.json +0 -39
- package/report/types/augment-dom.d.ts +0 -24
- package/report/types/html-renderer.d.ts +0 -28
- package/types/lhr/audit-result.d.ts +0 -59
- package/types/lhr/flow.d.ts +0 -16
- package/types/lhr/settings.d.ts +0 -105
- package/types/lhr/tsconfig.json +0 -24
- package/types/query-selector.d.ts +0 -36
|
@@ -159,32 +159,23 @@ function makeComparison(name, actualResult, expectedResult) {
|
|
|
159
159
|
* @param {Smokehouse.ExpectedRunnerResult} expected
|
|
160
160
|
*/
|
|
161
161
|
function pruneExpectations(localConsole, lhr, expected) {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* @returns {number}
|
|
167
|
-
*/
|
|
168
|
-
function getChromeVersion() {
|
|
169
|
-
const userAgent = lhr.environment.hostUserAgent;
|
|
170
|
-
const userAgentMatch = /Chrome\/(\d+)/.exec(userAgent); // Chrome/85.0.4174.0
|
|
171
|
-
if (!userAgentMatch) throw new Error('Could not get chrome version.');
|
|
172
|
-
return Number(userAgentMatch[1]);
|
|
173
|
-
}
|
|
174
|
-
|
|
162
|
+
const userAgent = lhr.environment.hostUserAgent;
|
|
163
|
+
const userAgentMatch = /Chrome\/(\d+)/.exec(userAgent); // Chrome/85.0.4174.0
|
|
164
|
+
if (!userAgentMatch) throw new Error('Could not get chrome version.');
|
|
165
|
+
const actualChromeVersion = Number(userAgentMatch[1]);
|
|
175
166
|
/**
|
|
176
167
|
* @param {*} obj
|
|
177
168
|
*/
|
|
178
169
|
function failsChromeVersionCheck(obj) {
|
|
179
|
-
if (obj._minChromiumMilestone &&
|
|
180
|
-
if (obj._maxChromiumMilestone &&
|
|
170
|
+
if (obj._minChromiumMilestone && actualChromeVersion < obj._minChromiumMilestone) return true;
|
|
171
|
+
if (obj._maxChromiumMilestone && actualChromeVersion > obj._maxChromiumMilestone) return true;
|
|
181
172
|
return false;
|
|
182
173
|
}
|
|
183
174
|
|
|
184
175
|
/**
|
|
185
176
|
* @param {*} obj
|
|
186
177
|
*/
|
|
187
|
-
function
|
|
178
|
+
function pruneNewerChromeExpectations(obj) {
|
|
188
179
|
for (const key of Object.keys(obj)) {
|
|
189
180
|
const value = obj[key];
|
|
190
181
|
if (!value || typeof value !== 'object') {
|
|
@@ -195,36 +186,20 @@ function pruneExpectations(localConsole, lhr, expected) {
|
|
|
195
186
|
localConsole.log([
|
|
196
187
|
`[${key}] failed chrome version check, pruning expectation:`,
|
|
197
188
|
JSON.stringify(value, null, 2),
|
|
198
|
-
`Actual Chromium version: ${
|
|
199
|
-
].join(' '));
|
|
200
|
-
delete obj[key];
|
|
201
|
-
} else if (value._legacyOnly && isFraggleRock) {
|
|
202
|
-
localConsole.log([
|
|
203
|
-
`[${key}] marked legacy only but run is Fraggle Rock, pruning expectation:`,
|
|
204
|
-
JSON.stringify(value, null, 2),
|
|
205
|
-
].join(' '));
|
|
206
|
-
delete obj[key];
|
|
207
|
-
} else if (value._fraggleRockOnly && !isFraggleRock) {
|
|
208
|
-
localConsole.log([
|
|
209
|
-
`[${key}] marked Fraggle Rock only but run is legacy, pruning expectation:`,
|
|
210
|
-
JSON.stringify(value, null, 2),
|
|
211
|
-
`Actual channel: ${lhr.configSettings.channel}`,
|
|
189
|
+
`Actual Chromium version: ${actualChromeVersion}`,
|
|
212
190
|
].join(' '));
|
|
213
191
|
delete obj[key];
|
|
214
192
|
} else {
|
|
215
|
-
|
|
193
|
+
pruneNewerChromeExpectations(value);
|
|
216
194
|
}
|
|
217
195
|
}
|
|
218
|
-
|
|
219
|
-
delete obj._legacyOnly;
|
|
220
|
-
delete obj._fraggleRockOnly;
|
|
221
196
|
delete obj._minChromiumMilestone;
|
|
222
197
|
delete obj._maxChromiumMilestone;
|
|
223
198
|
}
|
|
224
199
|
|
|
225
200
|
const cloned = cloneDeep(expected);
|
|
226
201
|
|
|
227
|
-
|
|
202
|
+
pruneNewerChromeExpectations(cloned);
|
|
228
203
|
return cloned;
|
|
229
204
|
}
|
|
230
205
|
|
|
@@ -290,7 +265,12 @@ function collateResults(localConsole, actual, expected) {
|
|
|
290
265
|
}
|
|
291
266
|
|
|
292
267
|
return [
|
|
293
|
-
|
|
268
|
+
{
|
|
269
|
+
name: 'final url',
|
|
270
|
+
actual: actual.lhr.finalUrl,
|
|
271
|
+
expected: expected.lhr.finalUrl,
|
|
272
|
+
equal: actual.lhr.finalUrl === expected.lhr.finalUrl,
|
|
273
|
+
},
|
|
294
274
|
runtimeErrorAssertion,
|
|
295
275
|
runWarningsAssertion,
|
|
296
276
|
...requestCountAssertion,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2020 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
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview This audit checks a page for any large JS libraries with smaller alternatives.
|
|
9
|
+
* These libraries can be replaced with functionally equivalent, smaller ones.
|
|
10
|
+
* @see https://docs.google.com/document/d/1TgKO3cWqMpcS4dn0xbjDG5fyuqgVvYYoXg--knaxJnM
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
/** @typedef {{repository: string, lastScraped: number|'Error', versions: Record<string, {gzip: number}>}} BundlePhobiaLibrary */
|
|
15
|
+
|
|
16
|
+
/** @typedef {{gzip: number, name: string, repository: string}} MinifiedBundlePhobiaLibrary */
|
|
17
|
+
|
|
18
|
+
/** @type {Record<string, BundlePhobiaLibrary>} */
|
|
19
|
+
const libStats = require('../lib/large-javascript-libraries/bundlephobia-database.json');
|
|
20
|
+
|
|
21
|
+
/** @type {Record<string, string[]>} */
|
|
22
|
+
const librarySuggestions = require('../lib/large-javascript-libraries/library-suggestions.js')
|
|
23
|
+
.suggestions;
|
|
24
|
+
|
|
25
|
+
const Audit = require('./audit.js');
|
|
26
|
+
const i18n = require('../lib/i18n/i18n.js');
|
|
27
|
+
|
|
28
|
+
const UIStrings = {
|
|
29
|
+
/** Title of a Lighthouse audit that provides detail on large Javascript libraries that are used on the page that have better alternatives. This descriptive title is shown when to users when no known unnecessarily large libraries are detected on the page.*/
|
|
30
|
+
title: 'Avoids large JavaScript libraries with smaller alternatives',
|
|
31
|
+
/** Title of a Lighthouse audit that provides detail on large Javascript libraries that are used on the page that have better alternatives. This descriptive title is shown when to users when some known unnecessarily large libraries are detected on the page.*/
|
|
32
|
+
failureTitle: 'Replace unnecessarily large JavaScript libraries',
|
|
33
|
+
/** Description of a Lighthouse audit that tells the user why they should care about the large Javascript libraries that have better alternatives. This is displayed after a user expands the section to see more. No character length limits. */
|
|
34
|
+
description: 'Large JavaScript libraries can lead to poor performance. ' +
|
|
35
|
+
'Prefer smaller, functionally equivalent libraries to reduce your bundle size.' +
|
|
36
|
+
' [Learn more](https://developers.google.com/web/fundamentals/performance/webpack/decrease-frontend-size#optimize_dependencies).',
|
|
37
|
+
/** Label for a column in a data table. Entries will be names of large JavaScript libraries that could be replaced. */
|
|
38
|
+
columnLibraryName: 'Library',
|
|
39
|
+
/** [ICU Syntax] Label for the Large JavaScrip Libraries audit identifying how many large libraries were found. */
|
|
40
|
+
displayValue: `{libraryCount, plural,
|
|
41
|
+
=1 {1 large library found}
|
|
42
|
+
other {# large libraries found}
|
|
43
|
+
}`,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
47
|
+
|
|
48
|
+
class LargeJavascriptLibraries extends Audit {
|
|
49
|
+
/**
|
|
50
|
+
* @return {LH.Audit.Meta}
|
|
51
|
+
*/
|
|
52
|
+
static get meta() {
|
|
53
|
+
return {
|
|
54
|
+
id: 'large-javascript-libraries',
|
|
55
|
+
title: str_(UIStrings.title),
|
|
56
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
57
|
+
description: str_(UIStrings.description),
|
|
58
|
+
requiredArtifacts: ['Stacks'],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {LH.Artifacts} artifacts
|
|
64
|
+
* @return {LH.Audit.Product}
|
|
65
|
+
*/
|
|
66
|
+
static audit(artifacts) {
|
|
67
|
+
/** @type {Array<{original: MinifiedBundlePhobiaLibrary, suggestions: MinifiedBundlePhobiaLibrary[]}>} */
|
|
68
|
+
const libraryPairings = [];
|
|
69
|
+
const detectedLibs = artifacts.Stacks;
|
|
70
|
+
|
|
71
|
+
const seenLibraries = new Set();
|
|
72
|
+
|
|
73
|
+
for (const detectedLib of detectedLibs) {
|
|
74
|
+
if (!detectedLib.npm || !libStats[detectedLib.npm]) continue;
|
|
75
|
+
|
|
76
|
+
const suggestions = librarySuggestions[detectedLib.npm];
|
|
77
|
+
if (!suggestions) continue;
|
|
78
|
+
|
|
79
|
+
if (seenLibraries.has(detectedLib.npm)) continue;
|
|
80
|
+
seenLibraries.add(detectedLib.npm);
|
|
81
|
+
|
|
82
|
+
let version = 'latest';
|
|
83
|
+
if (detectedLib.version && libStats[detectedLib.npm].versions[detectedLib.version]) {
|
|
84
|
+
version = detectedLib.version;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const originalLib = libStats[detectedLib.npm].versions[version];
|
|
88
|
+
|
|
89
|
+
/** @type {Array<{name: string, repository: string, gzip: number}>} */
|
|
90
|
+
const smallerSuggestions = [];
|
|
91
|
+
for (const suggestion of suggestions) {
|
|
92
|
+
if (libStats[suggestion].versions['latest'].gzip >= originalLib.gzip) continue;
|
|
93
|
+
|
|
94
|
+
smallerSuggestions.push({
|
|
95
|
+
name: suggestion,
|
|
96
|
+
repository: libStats[suggestion].repository,
|
|
97
|
+
gzip: libStats[suggestion].versions['latest'].gzip,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
smallerSuggestions.sort((a, b) => a.gzip - b.gzip);
|
|
102
|
+
if (!smallerSuggestions.length) continue;
|
|
103
|
+
|
|
104
|
+
libraryPairings.push({
|
|
105
|
+
original: {
|
|
106
|
+
gzip: originalLib.gzip,
|
|
107
|
+
name: detectedLib.npm,
|
|
108
|
+
repository: libStats[detectedLib.npm].repository,
|
|
109
|
+
},
|
|
110
|
+
suggestions: smallerSuggestions,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
115
|
+
const tableDetails = libraryPairings.map(libraryPairing => {
|
|
116
|
+
const original = libraryPairing.original;
|
|
117
|
+
const suggestions = libraryPairing.suggestions;
|
|
118
|
+
const suggestionItems = suggestions.map(suggestion => {
|
|
119
|
+
return {
|
|
120
|
+
suggestion: {
|
|
121
|
+
type: /** @type {'link'} */ ('link'),
|
|
122
|
+
text: suggestion.name,
|
|
123
|
+
url: suggestion.repository,
|
|
124
|
+
},
|
|
125
|
+
transferSize: suggestion.gzip,
|
|
126
|
+
wastedBytes: original.gzip - suggestion.gzip,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
name: {
|
|
132
|
+
type: /** @type {'link'} */ ('link'),
|
|
133
|
+
text: original.name,
|
|
134
|
+
url: original.repository,
|
|
135
|
+
},
|
|
136
|
+
transferSize: original.gzip,
|
|
137
|
+
subItems: {
|
|
138
|
+
type: /** @type {'subitems'} */ ('subitems'),
|
|
139
|
+
items: suggestionItems,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/** @type {LH.Audit.Details.TableColumnHeading[]} */
|
|
145
|
+
const headings = [
|
|
146
|
+
/* eslint-disable max-len */
|
|
147
|
+
{key: 'name', itemType: 'text', subItemsHeading: {key: 'suggestion'}, text: str_(UIStrings.columnLibraryName)},
|
|
148
|
+
{key: 'transferSize', itemType: 'bytes', subItemsHeading: {key: 'transferSize'}, text: str_(i18n.UIStrings.columnTransferSize)},
|
|
149
|
+
{key: null, itemType: 'bytes', subItemsHeading: {key: 'wastedBytes'}, text: str_(i18n.UIStrings.columnWastedBytes)},
|
|
150
|
+
/* eslint-enable max-len */
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const displayValue = str_(UIStrings.displayValue, {libraryCount: tableDetails.length});
|
|
154
|
+
|
|
155
|
+
const details = Audit.makeTableDetails(headings, tableDetails, {});
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
score: libraryPairings.length > 0 ? 0 : 1,
|
|
159
|
+
displayValue,
|
|
160
|
+
details,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = LargeJavascriptLibraries;
|
|
166
|
+
module.exports.UIStrings = UIStrings;
|
|
@@ -35,7 +35,6 @@ class HTTPStatusCode extends Audit {
|
|
|
35
35
|
failureTitle: str_(UIStrings.failureTitle),
|
|
36
36
|
description: str_(UIStrings.description),
|
|
37
37
|
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
|
|
38
|
-
supportedModes: ['navigation'],
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -48,7 +47,12 @@ class HTTPStatusCode extends Audit {
|
|
|
48
47
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
49
48
|
const URL = artifacts.URL;
|
|
50
49
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
51
|
-
const mainResource = NetworkAnalyzer.
|
|
50
|
+
const mainResource = NetworkAnalyzer.findOptionalMainDocument(networkRecords, URL.finalUrl);
|
|
51
|
+
|
|
52
|
+
if (!mainResource) {
|
|
53
|
+
if (artifacts.GatherContext.gatherMode === 'timespan') return {notApplicable: true, score: 1};
|
|
54
|
+
throw new Error(`Unable to locate main resource`);
|
|
55
|
+
}
|
|
52
56
|
|
|
53
57
|
const statusCode = mainResource.statusCode;
|
|
54
58
|
|
|
@@ -16,8 +16,7 @@ const UIStrings = {
|
|
|
16
16
|
failureTitle: 'Does not have a `<meta name="viewport">` tag with `width` ' +
|
|
17
17
|
'or `initial-scale`',
|
|
18
18
|
/** Description of a Lighthouse audit that tells the user why they should have a viewport meta tag in their html. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
|
|
19
|
-
description: '
|
|
20
|
-
'but also prevents [a 300 millisecond delay to user input](https://developers.google.com/web/updates/2013/12/300ms-tap-delay-gone-away). ' +
|
|
19
|
+
description: 'Add a `<meta name="viewport">` tag to optimize your app for mobile screens. ' +
|
|
21
20
|
'[Learn more](https://web.dev/viewport/).',
|
|
22
21
|
/** Explanatory message stating that no viewport meta tag exists on the page. */
|
|
23
22
|
explanationNoTag: 'No `<meta name="viewport">` tag found',
|
|
@@ -9,24 +9,23 @@ const path = require('path');
|
|
|
9
9
|
const isDeepEqual = require('lodash.isequal');
|
|
10
10
|
const constants = require('./constants.js');
|
|
11
11
|
const Budget = require('./budget.js');
|
|
12
|
+
const Audit = require('../audits/audit.js');
|
|
12
13
|
const Runner = require('../runner.js');
|
|
13
14
|
const i18n = require('../lib/i18n/i18n.js');
|
|
14
|
-
const validation = require('../fraggle-rock/config/validation.js');
|
|
15
15
|
|
|
16
16
|
/** @typedef {typeof import('../gather/gatherers/gatherer.js')} GathererConstructor */
|
|
17
|
-
/** @typedef {typeof import('../audits/audit.js')} Audit */
|
|
18
17
|
/** @typedef {InstanceType<GathererConstructor>} Gatherer */
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* If any items with identical `path` properties are found in the input array,
|
|
22
21
|
* merge their `options` properties into the first instance and then discard any
|
|
23
22
|
* other instances.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
23
|
+
* Until support of jsdoc templates with constraints, type in config.d.ts.
|
|
24
|
+
* See https://github.com/Microsoft/TypeScript/issues/24283
|
|
25
|
+
* @type {LH.Config.MergeOptionsOfItems}
|
|
27
26
|
*/
|
|
28
27
|
const mergeOptionsOfItems = function(items) {
|
|
29
|
-
/** @type {
|
|
28
|
+
/** @type {Array<{id: string, path?: string, options?: Object<string, any>}>} */
|
|
30
29
|
const mergedItems = [];
|
|
31
30
|
|
|
32
31
|
for (const item of items) {
|
|
@@ -129,6 +128,73 @@ function mergeConfigFragmentArrayByKey(baseArray, extensionArray, keyFn) {
|
|
|
129
128
|
return mergedArray;
|
|
130
129
|
}
|
|
131
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Validate the settings after they've been built
|
|
133
|
+
* @param {LH.Config.Settings} settings
|
|
134
|
+
*/
|
|
135
|
+
function assertValidSettings(settings) {
|
|
136
|
+
if (!settings.formFactor) {
|
|
137
|
+
throw new Error(`\`settings.formFactor\` must be defined as 'mobile' or 'desktop'. See https://github.com/GoogleChrome/lighthouse/blob/master/docs/emulation.md`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!settings.screenEmulation.disabled) {
|
|
141
|
+
// formFactor doesn't control emulation. So we don't want a mismatch:
|
|
142
|
+
// Bad mismatch A: user wants mobile emulation but scoring is configured for desktop
|
|
143
|
+
// Bad mismtach B: user wants everything desktop and set formFactor, but accidentally not screenEmulation
|
|
144
|
+
if (settings.screenEmulation.mobile !== (settings.formFactor === 'mobile')) {
|
|
145
|
+
throw new Error(`Screen emulation mobile setting (${settings.screenEmulation.mobile}) does not match formFactor setting (${settings.formFactor}). See https://github.com/GoogleChrome/lighthouse/blob/master/docs/emulation.md`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Throws an error if the provided object does not implement the required properties of an audit
|
|
152
|
+
* definition.
|
|
153
|
+
* @param {LH.Config.AuditDefn} auditDefinition
|
|
154
|
+
*/
|
|
155
|
+
function assertValidAudit(auditDefinition) {
|
|
156
|
+
const {implementation, path: auditPath} = auditDefinition;
|
|
157
|
+
const auditName = auditPath ||
|
|
158
|
+
(implementation && implementation.meta && implementation.meta.id) ||
|
|
159
|
+
'Unknown audit';
|
|
160
|
+
|
|
161
|
+
if (typeof implementation.audit !== 'function' || implementation.audit === Audit.audit) {
|
|
162
|
+
throw new Error(`${auditName} has no audit() method.`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (typeof implementation.meta.id !== 'string') {
|
|
166
|
+
throw new Error(`${auditName} has no meta.id property, or the property is not a string.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!i18n.isStringOrIcuMessage(implementation.meta.title)) {
|
|
170
|
+
throw new Error(`${auditName} has no meta.title property, or the property is not a string.`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// If it'll have a ✔ or ✖ displayed alongside the result, it should have failureTitle
|
|
174
|
+
if (
|
|
175
|
+
!i18n.isStringOrIcuMessage(implementation.meta.failureTitle) &&
|
|
176
|
+
implementation.meta.scoreDisplayMode === Audit.SCORING_MODES.BINARY
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(`${auditName} has no failureTitle and should.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!i18n.isStringOrIcuMessage(implementation.meta.description)) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`${auditName} has no meta.description property, or the property is not a string.`
|
|
184
|
+
);
|
|
185
|
+
} else if (implementation.meta.description === '') {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`${auditName} has an empty meta.description string. Please add a description for the UI.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!Array.isArray(implementation.meta.requiredArtifacts)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`${auditName} has no meta.requiredArtifacts property, or the property is not an array.`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
132
198
|
/**
|
|
133
199
|
* Expands a gatherer from user-specified to an internal gatherer definition format.
|
|
134
200
|
*
|
|
@@ -167,7 +233,7 @@ function expandGathererShorthand(gatherer) {
|
|
|
167
233
|
/**
|
|
168
234
|
* Expands the audits from user-specified JSON to an internal audit definition format.
|
|
169
235
|
* @param {LH.Config.AuditJson} audit
|
|
170
|
-
* @return {{id?: string, path: string, options?: {}} | {id?: string, implementation: Audit, path?: string, options?: {}}}
|
|
236
|
+
* @return {{id?: string, path: string, options?: {}} | {id?: string, implementation: typeof Audit, path?: string, options?: {}}}
|
|
171
237
|
*/
|
|
172
238
|
function expandAuditShorthand(audit) {
|
|
173
239
|
if (typeof audit === 'string') {
|
|
@@ -293,7 +359,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
|
|
|
293
359
|
settingsWithFlags.emulatedUserAgent = constants.userAgents[settingsWithFlags.formFactor];
|
|
294
360
|
}
|
|
295
361
|
|
|
296
|
-
|
|
362
|
+
assertValidSettings(settingsWithFlags);
|
|
297
363
|
return settingsWithFlags;
|
|
298
364
|
}
|
|
299
365
|
|
|
@@ -362,7 +428,7 @@ function resolveAuditsToDefns(audits, configDir) {
|
|
|
362
428
|
});
|
|
363
429
|
|
|
364
430
|
const mergedAuditDefns = mergeOptionsOfItems(auditDefns);
|
|
365
|
-
mergedAuditDefns.forEach(audit =>
|
|
431
|
+
mergedAuditDefns.forEach(audit => assertValidAudit(audit));
|
|
366
432
|
return mergedAuditDefns;
|
|
367
433
|
}
|
|
368
434
|
|
|
@@ -9,7 +9,6 @@ const defaultConfigPath = './default-config.js';
|
|
|
9
9
|
const defaultConfig = require('./default-config.js');
|
|
10
10
|
const constants = require('./constants.js');
|
|
11
11
|
const i18n = require('./../lib/i18n/i18n.js');
|
|
12
|
-
const validation = require('./../fraggle-rock/config/validation.js');
|
|
13
12
|
|
|
14
13
|
const log = require('lighthouse-logger');
|
|
15
14
|
const path = require('path');
|
|
@@ -105,6 +104,49 @@ function assertValidPasses(passes, audits) {
|
|
|
105
104
|
});
|
|
106
105
|
}
|
|
107
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @param {Config['categories']} categories
|
|
109
|
+
* @param {Config['audits']} audits
|
|
110
|
+
* @param {Config['groups']} groups
|
|
111
|
+
*/
|
|
112
|
+
function assertValidCategories(categories, audits, groups) {
|
|
113
|
+
if (!categories) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @type {Map<string, LH.Config.AuditDefn>} */
|
|
118
|
+
const auditsKeyedById = new Map((audits || []).map(audit => {
|
|
119
|
+
return [audit.implementation.meta.id, audit];
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
Object.keys(categories).forEach(categoryId => {
|
|
123
|
+
categories[categoryId].auditRefs.forEach((auditRef, index) => {
|
|
124
|
+
if (!auditRef.id) {
|
|
125
|
+
throw new Error(`missing an audit id at ${categoryId}[${index}]`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const audit = auditsKeyedById.get(auditRef.id);
|
|
129
|
+
if (!audit) {
|
|
130
|
+
throw new Error(`could not find ${auditRef.id} audit for category ${categoryId}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const auditImpl = audit.implementation;
|
|
134
|
+
const isManual = auditImpl.meta.scoreDisplayMode === 'manual';
|
|
135
|
+
if (categoryId === 'accessibility' && !auditRef.group && !isManual) {
|
|
136
|
+
throw new Error(`${auditRef.id} accessibility audit does not have a group`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (auditRef.weight > 0 && isManual) {
|
|
140
|
+
throw new Error(`${auditRef.id} is manual but has a positive weight`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (auditRef.group && (!groups || !groups[auditRef.group])) {
|
|
144
|
+
throw new Error(`${auditRef.id} references unknown group ${auditRef.group}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
108
150
|
/**
|
|
109
151
|
* @param {Gatherer} gathererInstance
|
|
110
152
|
* @param {string=} gathererName
|
|
@@ -229,7 +271,7 @@ class Config {
|
|
|
229
271
|
Config.filterConfigIfNeeded(this);
|
|
230
272
|
|
|
231
273
|
assertValidPasses(this.passes, this.audits);
|
|
232
|
-
|
|
274
|
+
assertValidCategories(this.categories, this.audits, this.groups);
|
|
233
275
|
|
|
234
276
|
log.timeEnd(status);
|
|
235
277
|
}
|
|
@@ -466,10 +508,12 @@ class Config {
|
|
|
466
508
|
|
|
467
509
|
// The `full-page-screenshot` audit belongs to no category, but we still want to include
|
|
468
510
|
// it (unless explictly excluded) because there are audits in every category that can use it.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
511
|
+
if (settings.onlyCategories) {
|
|
512
|
+
const explicitlyExcludesFullPageScreenshot =
|
|
513
|
+
settings.skipAudits && settings.skipAudits.includes('full-page-screenshot');
|
|
514
|
+
if (!explicitlyExcludesFullPageScreenshot) {
|
|
515
|
+
includedAudits.add('full-page-screenshot');
|
|
516
|
+
}
|
|
473
517
|
}
|
|
474
518
|
|
|
475
519
|
return {categories, requestedAuditNames: includedAudits};
|
|
@@ -229,7 +229,6 @@ const defaultConfig = {
|
|
|
229
229
|
'third-party-summary',
|
|
230
230
|
'third-party-facades',
|
|
231
231
|
'largest-contentful-paint-element',
|
|
232
|
-
'lcp-lazy-loaded',
|
|
233
232
|
'layout-shift-elements',
|
|
234
233
|
'long-tasks',
|
|
235
234
|
'no-unload-listeners',
|
|
@@ -467,14 +466,12 @@ const defaultConfig = {
|
|
|
467
466
|
{id: 'third-party-summary', weight: 0, group: 'diagnostics'},
|
|
468
467
|
{id: 'third-party-facades', weight: 0, group: 'diagnostics'},
|
|
469
468
|
{id: 'largest-contentful-paint-element', weight: 0, group: 'diagnostics'},
|
|
470
|
-
{id: 'lcp-lazy-loaded', weight: 0, group: 'diagnostics'},
|
|
471
469
|
{id: 'layout-shift-elements', weight: 0, group: 'diagnostics'},
|
|
472
470
|
{id: 'uses-passive-event-listeners', weight: 0, group: 'diagnostics'},
|
|
473
471
|
{id: 'no-document-write', weight: 0, group: 'diagnostics'},
|
|
474
472
|
{id: 'long-tasks', weight: 0, group: 'diagnostics'},
|
|
475
473
|
{id: 'non-composited-animations', weight: 0, group: 'diagnostics'},
|
|
476
474
|
{id: 'unsized-images', weight: 0, group: 'diagnostics'},
|
|
477
|
-
{id: 'viewport', weight: 0, group: 'diagnostics'},
|
|
478
475
|
// Audits past this point don't belong to a group and will not be shown automatically
|
|
479
476
|
{id: 'network-requests', weight: 0},
|
|
480
477
|
{id: 'network-rtt', weight: 0},
|
|
@@ -15,8 +15,16 @@ const config = {
|
|
|
15
15
|
extends: 'lighthouse:default',
|
|
16
16
|
audits: [
|
|
17
17
|
'autocomplete',
|
|
18
|
+
'large-javascript-libraries',
|
|
18
19
|
],
|
|
19
20
|
categories: {
|
|
21
|
+
// @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default
|
|
22
|
+
// config is awkward - easier to omit the property here. Will defer to default config.
|
|
23
|
+
'performance': {
|
|
24
|
+
auditRefs: [
|
|
25
|
+
{id: 'large-javascript-libraries', weight: 0, group: 'diagnostics'},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
20
28
|
// @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default
|
|
21
29
|
// config is awkward - easier to omit the property here. Will defer to default config.
|
|
22
30
|
'best-practices': {
|
|
@@ -16,7 +16,6 @@ const {
|
|
|
16
16
|
isValidArtifactDependency,
|
|
17
17
|
throwInvalidArtifactDependency,
|
|
18
18
|
assertArtifactTopologicalOrder,
|
|
19
|
-
assertValidConfig,
|
|
20
19
|
} = require('./validation.js');
|
|
21
20
|
const {filterConfigByGatherMode, filterConfigByExplicitFilters} = require('./filters.js');
|
|
22
21
|
const {
|
|
@@ -251,13 +250,15 @@ function initializeConfig(configJSON, context) {
|
|
|
251
250
|
settings,
|
|
252
251
|
};
|
|
253
252
|
|
|
254
|
-
|
|
253
|
+
// TODO(FR-COMPAT): validate navigations
|
|
254
|
+
// TODO(FR-COMPAT): validate audits
|
|
255
|
+
// TODO(FR-COMPAT): validate categories
|
|
255
256
|
|
|
256
257
|
config = filterConfigByGatherMode(config, context.gatherMode);
|
|
257
258
|
config = filterConfigByExplicitFilters(config, settings);
|
|
258
259
|
|
|
259
260
|
log.timeEnd(status);
|
|
260
|
-
return {config, warnings};
|
|
261
|
+
return {config, warnings: []};
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
module.exports = {resolveWorkingCopy, initializeConfig};
|
|
@@ -158,6 +158,7 @@ const defaultConfig = {
|
|
|
158
158
|
artifacts.EmbeddedContent,
|
|
159
159
|
artifacts.FontSize,
|
|
160
160
|
artifacts.FormElements,
|
|
161
|
+
artifacts.FullPageScreenshot,
|
|
161
162
|
artifacts.GatherContext,
|
|
162
163
|
artifacts.GlobalListeners,
|
|
163
164
|
artifacts.IFrameElements,
|
|
@@ -186,9 +187,6 @@ const defaultConfig = {
|
|
|
186
187
|
// Compat artifacts come last.
|
|
187
188
|
artifacts.devtoolsLogs,
|
|
188
189
|
artifacts.traces,
|
|
189
|
-
|
|
190
|
-
// FullPageScreenshot comes at the very end so all other node analysis is captured.
|
|
191
|
-
artifacts.FullPageScreenshot,
|
|
192
190
|
],
|
|
193
191
|
},
|
|
194
192
|
],
|