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.
Files changed (196) hide show
  1. package/changelog.md +1 -1
  2. package/dist/report/standalone.js +196 -0
  3. package/jest.config.js +0 -4
  4. package/lighthouse-cli/cli-flags.js +6 -14
  5. package/lighthouse-cli/run.js +1 -1
  6. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +3 -9
  7. package/lighthouse-cli/test/smokehouse/report-assert.js +16 -36
  8. package/lighthouse-core/audits/large-javascript-libraries.js +166 -0
  9. package/lighthouse-core/audits/seo/http-status-code.js +6 -2
  10. package/lighthouse-core/audits/viewport.js +1 -2
  11. package/lighthouse-core/config/config-helpers.js +75 -9
  12. package/lighthouse-core/config/config.js +50 -6
  13. package/lighthouse-core/config/constants.js +0 -1
  14. package/lighthouse-core/config/default-config.js +0 -3
  15. package/lighthouse-core/config/experimental-config.js +8 -0
  16. package/lighthouse-core/config/metrics-to-audits.js +0 -1
  17. package/lighthouse-core/fraggle-rock/config/config.js +4 -3
  18. package/lighthouse-core/fraggle-rock/config/default-config.js +1 -3
  19. package/lighthouse-core/fraggle-rock/config/validation.js +0 -201
  20. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +0 -4
  21. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +8 -16
  22. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +0 -4
  23. package/lighthouse-core/fraggle-rock/gather/session.js +5 -30
  24. package/lighthouse-core/gather/driver/navigation.js +6 -7
  25. package/lighthouse-core/gather/driver/wait-for-condition.js +0 -27
  26. package/lighthouse-core/gather/fetcher.js +7 -17
  27. package/lighthouse-core/gather/gather-runner.js +0 -1
  28. package/lighthouse-core/gather/gatherers/seo/robots-txt.js +6 -2
  29. package/lighthouse-core/gather/gatherers/seo/tap-targets.js +2 -2
  30. package/lighthouse-core/gather/gatherers/stacks.js +1 -1
  31. package/lighthouse-core/lib/asset-saver.js +2 -2
  32. package/lighthouse-core/lib/cdt/SDK.js +0 -1
  33. package/lighthouse-core/lib/csp-evaluator.js +4 -17
  34. package/{report/generator → lighthouse-core/lib}/file-namer.js +0 -0
  35. package/lighthouse-core/lib/i18n/README.md +1 -1
  36. package/lighthouse-core/lib/i18n/i18n.js +7 -7
  37. package/lighthouse-core/lib/i18n/locales/ar-XB.json +18 -0
  38. package/lighthouse-core/lib/i18n/locales/ar.json +18 -0
  39. package/lighthouse-core/lib/i18n/locales/bg.json +18 -0
  40. package/lighthouse-core/lib/i18n/locales/ca.json +18 -0
  41. package/lighthouse-core/lib/i18n/locales/cs.json +18 -0
  42. package/lighthouse-core/lib/i18n/locales/da.json +18 -0
  43. package/lighthouse-core/lib/i18n/locales/de.json +18 -0
  44. package/lighthouse-core/lib/i18n/locales/el.json +18 -0
  45. package/lighthouse-core/lib/i18n/locales/en-GB.json +18 -0
  46. package/lighthouse-core/lib/i18n/locales/en-US.json +18 -18
  47. package/lighthouse-core/lib/i18n/locales/en-XA.json +18 -0
  48. package/lighthouse-core/lib/i18n/locales/en-XL.json +18 -18
  49. package/lighthouse-core/lib/i18n/locales/es-419.json +18 -0
  50. package/lighthouse-core/lib/i18n/locales/es.json +18 -0
  51. package/lighthouse-core/lib/i18n/locales/fi.json +18 -0
  52. package/lighthouse-core/lib/i18n/locales/fil.json +18 -0
  53. package/lighthouse-core/lib/i18n/locales/fr.json +18 -0
  54. package/lighthouse-core/lib/i18n/locales/he.json +18 -0
  55. package/lighthouse-core/lib/i18n/locales/hi.json +18 -0
  56. package/lighthouse-core/lib/i18n/locales/hr.json +18 -0
  57. package/lighthouse-core/lib/i18n/locales/hu.json +18 -0
  58. package/lighthouse-core/lib/i18n/locales/id.json +18 -0
  59. package/lighthouse-core/lib/i18n/locales/it.json +18 -0
  60. package/lighthouse-core/lib/i18n/locales/ja.json +18 -0
  61. package/lighthouse-core/lib/i18n/locales/ko.json +18 -0
  62. package/lighthouse-core/lib/i18n/locales/lt.json +18 -0
  63. package/lighthouse-core/lib/i18n/locales/lv.json +18 -0
  64. package/lighthouse-core/lib/i18n/locales/nl.json +18 -0
  65. package/lighthouse-core/lib/i18n/locales/no.json +18 -0
  66. package/lighthouse-core/lib/i18n/locales/pl.json +18 -0
  67. package/lighthouse-core/lib/i18n/locales/pt-PT.json +18 -0
  68. package/lighthouse-core/lib/i18n/locales/pt.json +18 -0
  69. package/lighthouse-core/lib/i18n/locales/ro.json +18 -0
  70. package/lighthouse-core/lib/i18n/locales/ru.json +18 -0
  71. package/lighthouse-core/lib/i18n/locales/sk.json +18 -0
  72. package/lighthouse-core/lib/i18n/locales/sl.json +18 -0
  73. package/lighthouse-core/lib/i18n/locales/sr-Latn.json +18 -0
  74. package/lighthouse-core/lib/i18n/locales/sr.json +18 -0
  75. package/lighthouse-core/lib/i18n/locales/sv.json +18 -0
  76. package/lighthouse-core/lib/i18n/locales/ta.json +18 -0
  77. package/lighthouse-core/lib/i18n/locales/te.json +18 -0
  78. package/lighthouse-core/lib/i18n/locales/th.json +18 -0
  79. package/lighthouse-core/lib/i18n/locales/tr.json +18 -0
  80. package/lighthouse-core/lib/i18n/locales/uk.json +18 -0
  81. package/lighthouse-core/lib/i18n/locales/vi.json +18 -0
  82. package/lighthouse-core/lib/i18n/locales/zh-HK.json +18 -0
  83. package/lighthouse-core/lib/i18n/locales/zh-TW.json +18 -0
  84. package/lighthouse-core/lib/i18n/locales/zh.json +18 -0
  85. package/lighthouse-core/lib/large-javascript-libraries/bundlephobia-database.json +62 -0
  86. package/lighthouse-core/lib/large-javascript-libraries/library-suggestions.js +20 -0
  87. package/lighthouse-core/lib/network-request.js +1 -3
  88. package/lighthouse-core/lib/traces/pwmetrics-events.js +2 -2
  89. package/lighthouse-core/lib/url-shim.js +1 -7
  90. package/lighthouse-core/runner.js +1 -4
  91. package/lighthouse-core/util-commonjs.js +2 -2
  92. package/now.json +1 -1
  93. package/package.json +13 -20
  94. package/report/README.md +2 -2
  95. package/report/assets/standalone-template.html +1 -0
  96. package/report/assets/styles.css +27 -23
  97. package/report/assets/templates.html +40 -37
  98. package/report/clients/bundle.js +0 -1
  99. package/report/{package.json → clients/package.json} +0 -0
  100. package/report/clients/psi.js +3 -13
  101. package/report/clients/standalone.js +1 -1
  102. package/report/{types/buffer.d.ts → clients/treemap.js} +13 -11
  103. package/report/renderer/category-renderer.js +2 -2
  104. package/report/renderer/components.js +48 -46
  105. package/report/renderer/crc-details-renderer.js +16 -16
  106. package/report/renderer/details-renderer.js +1 -1
  107. package/report/renderer/dom.js +0 -17
  108. package/report/renderer/element-screenshot-renderer.js +9 -9
  109. package/{flow-report/test/setup/global-setup.ts → report/renderer/file-namer.js} +2 -5
  110. package/report/renderer/logger.js +4 -4
  111. package/report/renderer/package.json +4 -0
  112. package/report/renderer/report-renderer.js +11 -4
  113. package/report/renderer/report-ui-features.js +656 -43
  114. package/report/renderer/text-encoding.js +1 -1
  115. package/report/renderer/util.js +2 -2
  116. package/report/{generator/report-assets.js → report-assets.js} +3 -9
  117. package/report/{generator/report-generator.js → report-generator.js} +9 -37
  118. package/report/test/clients/package.json +4 -0
  119. package/report/test/clients/psi-test.js +1 -3
  120. package/report/test/renderer/category-renderer-test.js +3 -8
  121. package/report/test/renderer/components-test.js +0 -35
  122. package/report/test/renderer/crc-details-renderer-test.js +4 -5
  123. package/report/test/renderer/details-renderer-test.js +1 -3
  124. package/report/test/renderer/dom-test.js +6 -8
  125. package/report/test/renderer/i18n-test.js +0 -1
  126. package/report/test/renderer/package.json +4 -0
  127. package/report/test/renderer/performance-category-renderer-test.js +27 -30
  128. package/report/test/renderer/pwa-category-renderer-test.js +1 -2
  129. package/report/test/renderer/report-renderer-test.js +0 -1
  130. package/report/test/renderer/report-ui-features-test.js +28 -20
  131. package/report/test/renderer/snippet-renderer-test.js +0 -1
  132. package/report/test/renderer/text-encoding-test.js +0 -2
  133. package/report/test/renderer/util-test.js +0 -1
  134. package/report/test/{generator/report-generator-test.js → report-generator-test.js} +7 -9
  135. package/third-party/chromium-synchronization/installability-errors-test.js +27 -28
  136. package/third-party/snyk/snapshot.json +0 -2
  137. package/tsconfig.json +5 -24
  138. package/types/artifacts.d.ts +18 -5
  139. package/types/{lhr/audit-details.d.ts → audit-details.d.ts} +3 -18
  140. package/types/audit.d.ts +58 -7
  141. package/types/{lhr/budget.d.ts → budget.d.ts} +0 -0
  142. package/types/bundle-phobia-cli/index.d.ts +40 -0
  143. package/types/config.d.ts +8 -3
  144. package/types/externs.d.ts +119 -5
  145. package/types/gatherer.d.ts +2 -3
  146. package/types/global-lh.d.ts +14 -16
  147. package/{report/types/report-result.d.ts → types/html-renderer.d.ts} +17 -8
  148. package/types/{lhr/i18n.d.ts → i18n.d.ts} +25 -2
  149. package/types/{lhr/lhr.d.ts → lhr.d.ts} +9 -41
  150. package/types/smokehouse.d.ts +3 -3
  151. package/types/{lhr/treemap.d.ts → treemap.d.ts} +3 -3
  152. package/flow-report/assets/standalone-flow-template.html +0 -38
  153. package/flow-report/assets/styles.css +0 -310
  154. package/flow-report/jest.config.js +0 -24
  155. package/flow-report/src/app.tsx +0 -40
  156. package/flow-report/src/common.tsx +0 -72
  157. package/flow-report/src/icons.tsx +0 -96
  158. package/flow-report/src/sidebar/flow.tsx +0 -72
  159. package/flow-report/src/sidebar/sidebar.tsx +0 -89
  160. package/flow-report/src/summary/summary.tsx +0 -178
  161. package/flow-report/src/util.ts +0 -160
  162. package/flow-report/src/wrappers/gauge.tsx +0 -40
  163. package/flow-report/src/wrappers/report-renderer.tsx +0 -46
  164. package/flow-report/src/wrappers/report.tsx +0 -71
  165. package/flow-report/standalone-flow.tsx +0 -23
  166. package/flow-report/test/app-test.tsx +0 -48
  167. package/flow-report/test/common-test.tsx +0 -131
  168. package/flow-report/test/setup/env-setup.ts +0 -25
  169. package/flow-report/test/sidebar/flow-test.tsx +0 -71
  170. package/flow-report/test/sidebar/sidebar-test.tsx +0 -53
  171. package/flow-report/test/summary/summary-test.tsx +0 -132
  172. package/flow-report/test/util-test.tsx +0 -109
  173. package/flow-report/tsconfig.json +0 -39
  174. package/flow-report/types/flow-report.d.ts +0 -27
  175. package/lighthouse-core/audits/lcp-lazy-loaded.js +0 -83
  176. package/report/.eslintrc.cjs +0 -33
  177. package/report/generator/README.md +0 -9
  178. package/report/generator/flow-report-assets.js +0 -20
  179. package/report/generator/package.json +0 -4
  180. package/report/generator/tsconfig.json +0 -28
  181. package/report/renderer/drop-down-menu.js +0 -215
  182. package/report/renderer/features-util.js +0 -26
  183. package/report/renderer/open-tab.js +0 -137
  184. package/report/renderer/topbar-features.js +0 -309
  185. package/report/test/generator/file-namer-test.js +0 -28
  186. package/report/test/generator/package.json +0 -4
  187. package/report/test-assets/faux-psi-template.html +0 -126
  188. package/report/test-assets/faux-psi.js +0 -92
  189. package/report/tsconfig.json +0 -39
  190. package/report/types/augment-dom.d.ts +0 -24
  191. package/report/types/html-renderer.d.ts +0 -28
  192. package/types/lhr/audit-result.d.ts +0 -59
  193. package/types/lhr/flow.d.ts +0 -16
  194. package/types/lhr/settings.d.ts +0 -105
  195. package/types/lhr/tsconfig.json +0 -24
  196. 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 isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
163
-
164
- /**
165
- * Lazily compute the Chrome version because some reports are explicitly asserting error conditions.
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 && getChromeVersion() < obj._minChromiumMilestone) return true;
180
- if (obj._maxChromiumMilestone && getChromeVersion() > obj._maxChromiumMilestone) return true;
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 pruneRecursively(obj) {
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: ${getChromeVersion()}`,
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
- pruneRecursively(value);
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
- pruneRecursively(cloned);
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
- makeComparison('final url', actual.lhr.finalUrl, expected.lhr.finalUrl),
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.findMainDocument(networkRecords, URL.finalUrl);
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: 'A `<meta name="viewport">` not only optimizes your app for mobile screen sizes, ' +
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
- * @template {{path?: string, options: Record<string, unknown>}} T
25
- * @param {T[]} items
26
- * @return T[]
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 {T[]} */
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
- validation.assertValidSettings(settingsWithFlags);
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 => validation.assertValidAudit(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
- validation.assertValidCategories(this.categories, this.audits, this.groups);
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
- const explicitlyExcludesFullPageScreenshot =
470
- settings.skipAudits && settings.skipAudits.includes('full-page-screenshot');
471
- if (!explicitlyExcludesFullPageScreenshot && (settings.onlyCategories || settings.skipAudits)) {
472
- includedAudits.add('full-page-screenshot');
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};
@@ -106,7 +106,6 @@ const defaultSettings = {
106
106
  auditMode: false,
107
107
  gatherMode: false,
108
108
  disableStorageReset: false,
109
- debugNavigation: false,
110
109
  channel: 'node',
111
110
 
112
111
  // the following settings have no defaults but we still want ensure that `key in settings`
@@ -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': {
@@ -38,7 +38,6 @@ const tbtRelevantAudits = [
38
38
  'dom-size',
39
39
  'duplicated-javascript',
40
40
  'legacy-javascript',
41
- 'viewport',
42
41
  ];
43
42
 
44
43
  const clsRelevantAudits = [
@@ -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
- const {warnings} = assertValidConfig(config);
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
  ],