lighthouse 9.0.0 → 9.1.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 (123) hide show
  1. package/changelog.md +5681 -0
  2. package/dist/report/bundle.esm.js +36 -7
  3. package/dist/report/flow.js +97 -30
  4. package/dist/report/standalone.js +5 -5
  5. package/flow-report/api.ts +18 -0
  6. package/flow-report/assets/standalone-flow-template.html +2 -5
  7. package/flow-report/{standalone-flow.tsx → clients/standalone.ts} +5 -6
  8. package/flow-report/src/app.tsx +13 -8
  9. package/flow-report/src/sidebar/flow.tsx +1 -3
  10. package/flow-report/src/sidebar/sidebar.tsx +1 -3
  11. package/flow-report/src/topbar.tsx +24 -18
  12. package/flow-report/src/util.ts +35 -1
  13. package/flow-report/src/wrappers/category-score.tsx +9 -24
  14. package/flow-report/src/wrappers/markdown.tsx +4 -13
  15. package/flow-report/src/wrappers/report.tsx +26 -57
  16. package/flow-report/src/wrappers/styles.tsx +16 -0
  17. package/flow-report/test/setup/env-setup.ts +11 -5
  18. package/flow-report/test/summary/category-test.tsx +3 -6
  19. package/flow-report/test/summary/summary-test.tsx +3 -6
  20. package/flow-report/test/topbar-test.tsx +30 -14
  21. package/flow-report/test/util-test.tsx +38 -1
  22. package/flow-report/test/wrappers/category-score-test.tsx +3 -6
  23. package/flow-report/test/wrappers/markdown-test.tsx +1 -13
  24. package/flow-report/types/flow-report.d.ts +4 -0
  25. package/lighthouse-cli/cli-flags.js +4 -4
  26. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  27. package/lighthouse-core/audits/audit.js +39 -7
  28. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -26
  29. package/lighthouse-core/audits/deprecations.js +10 -14
  30. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +5 -4
  31. package/lighthouse-core/audits/dobetterweb/js-libraries.js +1 -1
  32. package/lighthouse-core/audits/dobetterweb/no-document-write.js +6 -4
  33. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +6 -4
  34. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +6 -4
  35. package/lighthouse-core/audits/errors-in-console.js +7 -4
  36. package/lighthouse-core/audits/installable-manifest.js +11 -4
  37. package/lighthouse-core/audits/no-unload-listeners.js +9 -10
  38. package/lighthouse-core/audits/seo/canonical.js +0 -14
  39. package/lighthouse-core/audits/seo/font-size.js +4 -2
  40. package/lighthouse-core/audits/seo/is-crawlable.js +2 -2
  41. package/lighthouse-core/audits/third-party-summary.js +2 -2
  42. package/lighthouse-core/audits/valid-source-maps.js +1 -1
  43. package/lighthouse-core/audits/violation-audit.js +10 -3
  44. package/lighthouse-core/computed/js-bundles.js +1 -3
  45. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +3 -3
  46. package/lighthouse-core/gather/driver/execution-context.js +1 -1
  47. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +7 -7
  48. package/lighthouse-core/gather/gatherers/stacks.js +0 -3
  49. package/lighthouse-core/lib/page-functions.js +2 -1
  50. package/lighthouse-core/lib/stack-packs.js +12 -8
  51. package/lighthouse-core/lib/statistics.js +26 -39
  52. package/lighthouse-core/runner.js +7 -7
  53. package/lighthouse-core/scripts/package.json +4 -0
  54. package/package.json +4 -6
  55. package/report/assets/styles.css +2 -2
  56. package/report/generator/file-namer.js +12 -1
  57. package/report/generator/flow-report-assets.js +2 -1
  58. package/report/generator/report-assets.js +2 -5
  59. package/report/generator/report-generator.js +0 -1
  60. package/report/renderer/api.js +49 -1
  61. package/report/renderer/category-renderer.js +17 -3
  62. package/report/renderer/components.js +1 -1
  63. package/report/renderer/report-renderer.js +5 -1
  64. package/report/renderer/report-ui-features.js +1 -1
  65. package/report/renderer/swap-locale-feature.js +0 -5
  66. package/report/test/clients/bundle-test.js +2 -2
  67. package/report/test/renderer/category-renderer-test.js +28 -0
  68. package/report/test/renderer/report-renderer-axe-test.js +2 -2
  69. package/report/test/renderer/report-renderer-test.js +29 -0
  70. package/report/test/renderer/report-ui-features-test.js +0 -1
  71. package/report/types/report-renderer.d.ts +7 -0
  72. package/shared/localization/locales/ar-XB.json +0 -3
  73. package/shared/localization/locales/ar.json +0 -3
  74. package/shared/localization/locales/bg.json +17 -20
  75. package/shared/localization/locales/ca.json +0 -3
  76. package/shared/localization/locales/cs.json +0 -3
  77. package/shared/localization/locales/da.json +16 -19
  78. package/shared/localization/locales/de.json +0 -3
  79. package/shared/localization/locales/el.json +0 -3
  80. package/shared/localization/locales/en-GB.json +19 -22
  81. package/shared/localization/locales/en-US.json +33 -3
  82. package/shared/localization/locales/en-XA.json +0 -3
  83. package/shared/localization/locales/en-XL.json +33 -3
  84. package/shared/localization/locales/es-419.json +17 -20
  85. package/shared/localization/locales/es.json +0 -3
  86. package/shared/localization/locales/fi.json +16 -19
  87. package/shared/localization/locales/fil.json +33 -36
  88. package/shared/localization/locales/fr.json +16 -19
  89. package/shared/localization/locales/he.json +16 -19
  90. package/shared/localization/locales/hi.json +0 -3
  91. package/shared/localization/locales/hr.json +16 -19
  92. package/shared/localization/locales/hu.json +16 -19
  93. package/shared/localization/locales/id.json +16 -19
  94. package/shared/localization/locales/it.json +37 -40
  95. package/shared/localization/locales/ja.json +16 -19
  96. package/shared/localization/locales/ko.json +16 -19
  97. package/shared/localization/locales/lt.json +17 -20
  98. package/shared/localization/locales/lv.json +0 -3
  99. package/shared/localization/locales/nl.json +16 -19
  100. package/shared/localization/locales/no.json +17 -20
  101. package/shared/localization/locales/pl.json +16 -19
  102. package/shared/localization/locales/pt-PT.json +37 -40
  103. package/shared/localization/locales/pt.json +16 -19
  104. package/shared/localization/locales/ro.json +0 -3
  105. package/shared/localization/locales/ru.json +16 -19
  106. package/shared/localization/locales/sk.json +0 -3
  107. package/shared/localization/locales/sl.json +0 -3
  108. package/shared/localization/locales/sr-Latn.json +16 -19
  109. package/shared/localization/locales/sr.json +16 -19
  110. package/shared/localization/locales/sv.json +17 -20
  111. package/shared/localization/locales/ta.json +0 -3
  112. package/shared/localization/locales/te.json +0 -3
  113. package/shared/localization/locales/th.json +0 -3
  114. package/shared/localization/locales/tr.json +0 -3
  115. package/shared/localization/locales/uk.json +17 -20
  116. package/shared/localization/locales/vi.json +0 -3
  117. package/shared/localization/locales/zh-HK.json +0 -3
  118. package/shared/localization/locales/zh-TW.json +0 -3
  119. package/shared/localization/locales/zh.json +0 -3
  120. package/third-party/chromium-synchronization/installability-errors-test.js +1 -2
  121. package/third-party/snyk/snapshot.json +4 -1
  122. package/types/externs.d.ts +5 -24
  123. package/flow-report/src/wrappers/report-renderer.tsx +0 -53
@@ -98,9 +98,7 @@ class JSBundles {
98
98
 
99
99
  const compiledUrl = SourceMap.scriptUrl || 'compiled.js';
100
100
  const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
101
- // Hack: CDT expects undefined properties to be explicit.
102
- const rawMapForCdt = /** @type {any} */ (rawMap);
103
- const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMapForCdt);
101
+ const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
104
102
 
105
103
  const content = scriptElement && scriptElement.content ? scriptElement.content : '';
106
104
  const sizes = computeGeneratedFileSizes(map, content);
@@ -142,8 +142,8 @@ async function _collectDebugData(navigationContext, phaseState) {
142
142
  /**
143
143
  * @param {NavigationContext} navigationContext
144
144
  * @param {PhaseState} phaseState
145
- * @param {UnPromise<ReturnType<typeof _setupNavigation>>} setupResult
146
- * @param {UnPromise<ReturnType<typeof _navigate>>} navigateResult
145
+ * @param {Awaited<ReturnType<typeof _setupNavigation>>} setupResult
146
+ * @param {Awaited<ReturnType<typeof _navigate>>} navigateResult
147
147
  * @return {Promise<{finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
148
148
  */
149
149
  async function _computeNavigationResult(
@@ -195,7 +195,7 @@ async function _computeNavigationResult(
195
195
  async function _navigation(navigationContext) {
196
196
  const artifactState = getEmptyArtifactState();
197
197
  const phaseState = {
198
- gatherMode: /** @type {'navigation'} */ ('navigation'),
198
+ gatherMode: /** @type {const} */ ('navigation'),
199
199
  driver: navigationContext.driver,
200
200
  computedCache: navigationContext.computedCache,
201
201
  artifactDefinitions: navigationContext.navigation.artifacts,
@@ -166,7 +166,7 @@ class ExecutionContext {
166
166
  * @param {{args: T, useIsolation?: boolean, deps?: Array<Function|string>}} options `args` should
167
167
  * match the args of `mainFn`, and can be any serializable value. `deps` are functions that must be
168
168
  * defined for `mainFn` to work.
169
- * @return {FlattenedPromise<R>}
169
+ * @return {Promise<Awaited<R>>}
170
170
  */
171
171
  evaluate(mainFn, options) {
172
172
  const argsSerialized = ExecutionContext.serializeArguments(options.args);
@@ -16,19 +16,19 @@ const pageFunctions = require('../../lib/page-functions.js');
16
16
  const FULL_PAGE_SCREENSHOT_QUALITY = 30;
17
17
 
18
18
  /**
19
- * @param {string} str
19
+ * @template {string} S
20
+ * @param {S} str
20
21
  */
21
- function snakeCaseToCamelCase(str) {
22
- return str.replace(/(-\w)/g, m => m[1].toUpperCase());
22
+ function kebabCaseToCamelCase(str) {
23
+ return /** @type {KebabToCamelCase<S>} */ (str.replace(/(-\w)/g, m => m[1].toUpperCase()));
23
24
  }
24
25
 
25
26
  /* c8 ignore start */
26
27
 
27
28
  // eslint-disable-next-line no-inner-declarations
28
29
  function getObservedDeviceMetrics() {
29
- // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
30
- const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
31
- snakeCaseToCamelCase(window.screen.orientation.type));
30
+ // Convert the Web API's kebab case (landscape-primary) to camel case (landscapePrimary).
31
+ const screenOrientationType = kebabCaseToCamelCase(window.screen.orientation.type);
32
32
  return {
33
33
  width: document.documentElement.clientWidth,
34
34
  height: document.documentElement.clientHeight,
@@ -168,7 +168,7 @@ class FullPageScreenshot extends FRGatherer {
168
168
  observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
169
169
  args: [],
170
170
  useIsolation: true,
171
- deps: [snakeCaseToCamelCase],
171
+ deps: [kebabCaseToCamelCase],
172
172
  });
173
173
  }
174
174
 
@@ -82,9 +82,6 @@ class Stacks extends FRGatherer {
82
82
  constructor() {
83
83
  super();
84
84
 
85
- // Because this file uses `fs.readFile` it gets parsed by a different branch of the browserify internals
86
- // that cannot handle the latest ECMAScript features.
87
- // See https://github.com/GoogleChrome/lighthouse/issues/12134
88
85
  /** @type {LH.Gatherer.GathererMeta} */
89
86
  this.meta = {
90
87
  supportedModes: ['snapshot', 'navigation'],
@@ -208,7 +208,8 @@ function computeBenchmarkIndex() {
208
208
 
209
209
  while (Date.now() - start < 500) {
210
210
  let s = '';
211
- for (let j = 0; j < 10000; j++) s += 'a'; // eslint-disable-line no-unused-vars
211
+ for (let j = 0; j < 10000; j++) s += 'a';
212
+ if (s.length === 1) throw new Error('will never happen, but prevents compiler optimizations');
212
213
 
213
214
  iterations++;
214
215
  }
@@ -23,14 +23,6 @@ const stackPacksToInclude = [
23
23
  packId: 'drupal',
24
24
  requiredStacks: ['js:drupal'],
25
25
  },
26
- {
27
- packId: 'react',
28
- requiredStacks: ['js:react'],
29
- },
30
- {
31
- packId: 'angular',
32
- requiredStacks: ['js:@angular/core'],
33
- },
34
26
  {
35
27
  packId: 'amp',
36
28
  requiredStacks: ['js:amp'],
@@ -47,6 +39,18 @@ const stackPacksToInclude = [
47
39
  packId: 'joomla',
48
40
  requiredStacks: ['js:joomla'],
49
41
  },
42
+ {
43
+ packId: 'next.js',
44
+ requiredStacks: ['js:next'],
45
+ },
46
+ {
47
+ packId: 'angular',
48
+ requiredStacks: ['js:@angular/core'],
49
+ },
50
+ {
51
+ packId: 'react',
52
+ requiredStacks: ['js:react'],
53
+ },
50
54
  ];
51
55
 
52
56
  /**
@@ -5,6 +5,12 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ // The exact double values for the max and min scores possible in each range.
9
+ const MIN_PASSING_SCORE = 0.90000000000000002220446049250313080847263336181640625;
10
+ const MAX_AVERAGE_SCORE = 0.899999999999999911182158029987476766109466552734375;
11
+ const MIN_AVERAGE_SCORE = 0.5;
12
+ const MAX_FAILING_SCORE = 0.499999999999999944488848768742172978818416595458984375;
13
+
8
14
  /**
9
15
  * Approximates the Gauss error function, the probability that a random variable
10
16
  * from the standard normal distribution lies within [-x, x]. Moved from
@@ -28,37 +34,6 @@ function erf(x) {
28
34
  return sign * (1 - y * Math.exp(-x * x));
29
35
  }
30
36
 
31
- /**
32
- * Creates a log-normal distribution à la traceviewer's statistics package.
33
- * Specified by providing the median value, at which the score will be 0.5,
34
- * and the falloff, the initial point of diminishing returns where any
35
- * improvement in value will yield increasingly smaller gains in score. Both
36
- * values should be in the same units (e.g. milliseconds). See
37
- * https://www.desmos.com/calculator/tx1wcjk8ch
38
- * for an interactive view of the relationship between these parameters and
39
- * the typical parameterization (location and shape) of the log-normal
40
- * distribution.
41
- * @param {number} median
42
- * @param {number} falloff
43
- * @return {{computeComplementaryPercentile: function(number): number}}
44
- */
45
- function getLogNormalDistribution(median, falloff) {
46
- const location = Math.log(median);
47
-
48
- // The "falloff" value specified the location of the smaller of the positive
49
- // roots of the third derivative of the log-normal CDF. Calculate the shape
50
- // parameter in terms of that value and the median.
51
- const logRatio = Math.log(falloff / median);
52
- const shape = Math.sqrt(1 - 3 * logRatio - Math.sqrt((logRatio - 3) * (logRatio - 3) - 8)) / 2;
53
-
54
- return {
55
- computeComplementaryPercentile(x) {
56
- const standardizedX = (Math.log(x) - location) / (Math.SQRT2 * shape);
57
- return (1 - erf(standardizedX)) / 2;
58
- },
59
- };
60
- }
61
-
62
37
  /**
63
38
  * Returns the score (1 - percentile) of `value` in a log-normal distribution
64
39
  * specified by the `median` value, at which the score will be 0.5, and a 10th
@@ -76,24 +51,37 @@ function getLogNormalScore({median, p10}, value) {
76
51
  // Required for the log-normal distribution.
77
52
  if (median <= 0) throw new Error('median must be greater than zero');
78
53
  if (p10 <= 0) throw new Error('p10 must be greater than zero');
79
- // Not required, but if p10 > median, it flips around and becomes the p90 point.
54
+ // Not strictly required, but if p10 > median, it flips around and becomes the p90 point.
80
55
  if (p10 >= median) throw new Error('p10 must be less than the median');
81
56
 
82
57
  // Non-positive values aren't in the distribution, so always 1.
83
58
  if (value <= 0) return 1;
84
59
 
85
- // Closest double to `erfc-1(2 * 1/10)`.
60
+ // Closest double to `erfc-1(1/5)`.
86
61
  const INVERSE_ERFC_ONE_FIFTH = 0.9061938024368232;
87
62
 
88
- // Shape (σ) is `log(p10/median) / (sqrt(2)*erfc^-1(2 * 1/10))` and
63
+ // Shape (σ) is `|log(p10/median) / (sqrt(2)*erfc^-1(1/5))|` and
89
64
  // standardizedX is `1/2 erfc(log(value/median) / (sqrt(2)*σ))`, so simplify a bit.
90
- const xLogRatio = Math.log(value / median);
91
- const p10LogRatio = -Math.log(p10 / median); // negate to keep σ positive.
65
+ const xRatio = Math.max(Number.MIN_VALUE, value / median); // value and median are > 0, so is ratio.
66
+ const xLogRatio = Math.log(xRatio);
67
+ const p10Ratio = Math.max(Number.MIN_VALUE, p10 / median); // p10 and median are > 0, so is ratio.
68
+ const p10LogRatio = -Math.log(p10Ratio); // negate to keep σ positive.
92
69
  const standardizedX = xLogRatio * INVERSE_ERFC_ONE_FIFTH / p10LogRatio;
93
70
  const complementaryPercentile = (1 - erf(standardizedX)) / 2;
94
71
 
95
- // Clamp to [0, 1] to avoid any floating-point out-of-bounds issues.
96
- return Math.min(1, Math.max(0, complementaryPercentile));
72
+ // Clamp to avoid floating-point out-of-bounds issues and keep score in expected range.
73
+ let score;
74
+ if (value <= p10) {
75
+ // Passing. Clamp to [0.9, 1].
76
+ score = Math.max(MIN_PASSING_SCORE, Math.min(1, complementaryPercentile));
77
+ } else if (value <= median) {
78
+ // Average. Clamp to [0.5, 0.9).
79
+ score = Math.max(MIN_AVERAGE_SCORE, Math.min(MAX_AVERAGE_SCORE, complementaryPercentile));
80
+ } else {
81
+ // Failing. Clamp to [0, 0.5).
82
+ score = Math.max(0, Math.min(MAX_FAILING_SCORE, complementaryPercentile));
83
+ }
84
+ return score;
97
85
  }
98
86
 
99
87
  /**
@@ -112,6 +100,5 @@ function linearInterpolation(x0, y0, x1, y1, x) {
112
100
 
113
101
  module.exports = {
114
102
  linearInterpolation,
115
- getLogNormalDistribution,
116
103
  getLogNormalScore,
117
104
  };
@@ -132,7 +132,13 @@ class Runner {
132
132
 
133
133
  /** @type {LH.RawIcu<LH.Result>} */
134
134
  const i18nLhr = {
135
+ lighthouseVersion,
136
+ requestedUrl,
137
+ finalUrl: artifacts.URL.finalUrl,
138
+ fetchTime: artifacts.fetchTime,
135
139
  gatherMode: artifacts.GatherContext.gatherMode,
140
+ runtimeError: Runner.getArtifactRuntimeError(artifacts),
141
+ runWarnings: lighthouseRunWarnings,
136
142
  userAgent: artifacts.HostUserAgent,
137
143
  environment: {
138
144
  networkUserAgent: artifacts.NetworkUserAgent,
@@ -140,22 +146,16 @@ class Runner {
140
146
  benchmarkIndex: artifacts.BenchmarkIndex,
141
147
  credits,
142
148
  },
143
- lighthouseVersion,
144
- fetchTime: artifacts.fetchTime,
145
- requestedUrl: requestedUrl,
146
- finalUrl: artifacts.URL.finalUrl,
147
- runWarnings: lighthouseRunWarnings,
148
- runtimeError: Runner.getArtifactRuntimeError(artifacts),
149
149
  audits: auditResultsById,
150
150
  configSettings: settings,
151
151
  categories,
152
152
  categoryGroups: runOpts.config.groups || undefined,
153
+ stackPacks: stackPacks.getStackPacks(artifacts.Stacks),
153
154
  timing: this._getTiming(artifacts),
154
155
  i18n: {
155
156
  rendererFormattedStrings: format.getRendererFormattedStrings(settings.locale),
156
157
  icuMessagePaths: {},
157
158
  },
158
- stackPacks: stackPacks.getStackPacks(artifacts.Stacks),
159
159
  };
160
160
 
161
161
  // Replace ICU message references with localized strings; save replaced paths in lhr.
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "//": "Any directory that uses `import ... from` or `export ...` must be type module. Temporary file until root package.json is type: module"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.0.0",
3
+ "version": "9.1.0",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -108,7 +108,6 @@
108
108
  "@testing-library/preact": "^2.0.1",
109
109
  "@testing-library/preact-hooks": "^1.1.0",
110
110
  "@types/archiver": "^2.1.2",
111
- "@types/browserify": "^12.0.36",
112
111
  "@types/chrome": "^0.0.154",
113
112
  "@types/configstore": "^4.0.0",
114
113
  "@types/cpy": "^5.1.0",
@@ -138,7 +137,6 @@
138
137
  "acorn": "^8.5.0",
139
138
  "angular": "^1.7.4",
140
139
  "archiver": "^3.0.0",
141
- "browserify": "^17.0.0",
142
140
  "c8": "^7.4.0",
143
141
  "chalk": "^2.4.1",
144
142
  "chrome-devtools-frontend": "1.0.922924",
@@ -181,8 +179,8 @@
181
179
  "tabulator-tables": "^4.9.3",
182
180
  "terser": "^5.3.8",
183
181
  "ts-jest": "^27.0.4",
184
- "typed-query-selector": "^2.4.0",
185
- "typescript": "4.4.3",
182
+ "typed-query-selector": "^2.6.1",
183
+ "typescript": "^4.5.2",
186
184
  "wait-for-expect": "^3.0.2",
187
185
  "webtreemap-cdt": "^3.2.1"
188
186
  },
@@ -198,7 +196,7 @@
198
196
  "jpeg-js": "^0.4.1",
199
197
  "js-library-detector": "^6.4.0",
200
198
  "lighthouse-logger": "^1.3.0",
201
- "lighthouse-stack-packs": "^1.5.0",
199
+ "lighthouse-stack-packs": "^1.6.0",
202
200
  "lodash.clonedeep": "^4.5.0",
203
201
  "lodash.get": "^4.4.2",
204
202
  "lodash.isequal": "^4.5.0",
@@ -943,7 +943,7 @@
943
943
 
944
944
 
945
945
  .lh-audit--error .lh-audit__display-text {
946
- color: var(--color-fail);
946
+ color: var(--color-fail-secondary);
947
947
  }
948
948
 
949
949
  /* Audit Group */
@@ -1055,7 +1055,7 @@
1055
1055
  }
1056
1056
 
1057
1057
  .lh-audit--fail .lh-audit-explanation {
1058
- color: var(--color-fail);
1058
+ color: var(--color-fail-secondary);
1059
1059
  }
1060
1060
 
1061
1061
  /* Report */
@@ -43,4 +43,15 @@ function getLhrFilenamePrefix(lhr) {
43
43
  return getFilenamePrefix(hostname, lhr.fetchTime);
44
44
  }
45
45
 
46
- module.exports = {getLhrFilenamePrefix, getFilenamePrefix};
46
+ /**
47
+ * Generate a filenamePrefix of name_YYYY-MM-DD_HH-MM-SS.
48
+ * @param {{name: string, steps: Array<{lhr: {fetchTime: string}}>}} flowResult
49
+ * @return {string}
50
+ */
51
+ function getFlowResultFilenamePrefix(flowResult) {
52
+ const lhr = flowResult.steps[0].lhr;
53
+ const name = flowResult.name.replace(/\s/g, '-');
54
+ return getFilenamePrefix(name, lhr.fetchTime);
55
+ }
56
+
57
+ module.exports = {getLhrFilenamePrefix, getFilenamePrefix, getFlowResultFilenamePrefix};
@@ -9,12 +9,13 @@ const fs = require('fs');
9
9
 
10
10
  /* eslint-disable max-len */
11
11
  const FLOW_REPORT_TEMPLATE = fs.readFileSync(`${__dirname}/../../flow-report/assets/standalone-flow-template.html`, 'utf8');
12
+ const REGULAR_REPORT_CSS = fs.readFileSync(__dirname + '/../assets/styles.css', 'utf8');
12
13
  const FLOW_REPORT_CSS = fs.readFileSync(`${__dirname}/../../flow-report/assets/styles.css`, 'utf8');
13
14
  const FLOW_REPORT_JAVASCRIPT = fs.readFileSync(`${__dirname}/../../dist/report/flow.js`, 'utf8');
14
15
  /* eslint-enable max-len */
15
16
 
16
17
  module.exports = {
17
18
  FLOW_REPORT_TEMPLATE,
18
- FLOW_REPORT_CSS,
19
+ FLOW_REPORT_CSS: [REGULAR_REPORT_CSS, FLOW_REPORT_CSS].join('\n'),
19
20
  FLOW_REPORT_JAVASCRIPT,
20
21
  };
@@ -12,15 +12,12 @@ const flowReportAssets = require('./flow-report-assets.js');
12
12
  const REPORT_TEMPLATE = fs.readFileSync(__dirname + '/../assets/standalone-template.html',
13
13
  'utf8');
14
14
  const REPORT_JAVASCRIPT = fs.readFileSync(__dirname + '/../../dist/report/standalone.js', 'utf8');
15
- const REPORT_CSS = fs.readFileSync(__dirname + '/../assets/styles.css', 'utf8');
16
15
 
17
- // Changes to this export interface should be reflected in build/build-dt-report-resources.js
18
- // and clients/devtools-report-assets.js
19
16
  module.exports = {
20
17
  REPORT_TEMPLATE,
21
18
  REPORT_JAVASCRIPT,
22
- REPORT_CSS,
23
19
  // Flow report assets are not needed for every bundle.
24
- // Ignoring flow-report-assets.js (e.g. `browserify.ignore`) will remove the flow assets from the bundle.
20
+ // Replacing/ignoring flow-report-assets.js (e.g. `rollupPlugins.shim`) will
21
+ // remove the flow assets from the bundle.
25
22
  ...flowReportAssets,
26
23
  };
@@ -70,7 +70,6 @@ class ReportGenerator {
70
70
  {search: '%%LIGHTHOUSE_FLOW_JSON%%', replacement: sanitizedJson},
71
71
  {search: '%%LIGHTHOUSE_FLOW_JAVASCRIPT%%', replacement: htmlReportAssets.FLOW_REPORT_JAVASCRIPT},
72
72
  {search: '/*%%LIGHTHOUSE_FLOW_CSS%%*/', replacement: htmlReportAssets.FLOW_REPORT_CSS},
73
- {search: '/*%%LIGHTHOUSE_CSS%%*/', replacement: htmlReportAssets.REPORT_CSS},
74
73
  /* eslint-enable max-len */
75
74
  ]);
76
75
  }
@@ -9,13 +9,15 @@
9
9
  import {DOM} from '../renderer/dom.js';
10
10
  import {ReportRenderer} from '../renderer/report-renderer.js';
11
11
  import {ReportUIFeatures} from '../renderer/report-ui-features.js';
12
+ import {CategoryRenderer} from './category-renderer.js';
13
+ import {DetailsRenderer} from './details-renderer.js';
12
14
 
13
15
  /**
14
16
  * @param {LH.Result} lhr
15
17
  * @param {LH.Renderer.Options} opts
16
18
  * @return {HTMLElement}
17
19
  */
18
- export function renderReport(lhr, opts = {}) {
20
+ function renderReport(lhr, opts = {}) {
19
21
  const rootEl = document.createElement('article');
20
22
  rootEl.classList.add('lh-root', 'lh-vars');
21
23
 
@@ -30,3 +32,49 @@ export function renderReport(lhr, opts = {}) {
30
32
  features.initFeatures(lhr);
31
33
  return rootEl;
32
34
  }
35
+
36
+ /**
37
+ * @param {LH.ReportResult.Category} category
38
+ * @param {Parameters<CategoryRenderer['renderCategoryScore']>[2]=} options
39
+ * @return {DocumentFragment}
40
+ */
41
+ function renderCategoryScore(category, options) {
42
+ const dom = new DOM(document, document.documentElement);
43
+ const detailsRenderer = new DetailsRenderer(dom);
44
+ const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
45
+ return categoryRenderer.renderCategoryScore(category, {}, options);
46
+ }
47
+
48
+ /**
49
+ * @param {Blob} blob
50
+ * @param {string} filename
51
+ */
52
+ function saveFile(blob, filename) {
53
+ const dom = new DOM(document, document.documentElement);
54
+ dom.saveFile(blob, filename);
55
+ }
56
+
57
+ /**
58
+ * @param {string} markdownText
59
+ * @return {Element}
60
+ */
61
+ function convertMarkdownCodeSnippets(markdownText) {
62
+ const dom = new DOM(document, document.documentElement);
63
+ return dom.convertMarkdownCodeSnippets(markdownText);
64
+ }
65
+
66
+ /**
67
+ * @return {DocumentFragment}
68
+ */
69
+ function createStylesElement() {
70
+ const dom = new DOM(document, document.documentElement);
71
+ return dom.createComponent('styles');
72
+ }
73
+
74
+ export {
75
+ renderReport,
76
+ renderCategoryScore,
77
+ saveFile,
78
+ convertMarkdownCodeSnippets,
79
+ createStylesElement,
80
+ };
@@ -348,14 +348,28 @@ export class CategoryRenderer {
348
348
  /**
349
349
  * @param {LH.ReportResult.Category} category
350
350
  * @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
351
- * @param {{gatherMode: LH.Result.GatherMode}=} options
351
+ * @param {{gatherMode: LH.Result.GatherMode, omitLabel?: boolean, onPageAnchorRendered?: (link: HTMLAnchorElement) => void}=} options
352
352
  * @return {DocumentFragment}
353
353
  */
354
354
  renderCategoryScore(category, groupDefinitions, options) {
355
+ let categoryScore;
355
356
  if (options && Util.shouldDisplayAsFraction(options.gatherMode)) {
356
- return this.renderCategoryFraction(category);
357
+ categoryScore = this.renderCategoryFraction(category);
358
+ } else {
359
+ categoryScore = this.renderScoreGauge(category, groupDefinitions);
360
+ }
361
+
362
+ if (options?.omitLabel) {
363
+ const label = this.dom.find('.lh-gauge__label,.lh-fraction__label', categoryScore);
364
+ label.remove();
357
365
  }
358
- return this.renderScoreGauge(category, groupDefinitions);
366
+
367
+ if (options?.onPageAnchorRendered) {
368
+ const anchor = this.dom.find('a', categoryScore);
369
+ options.onPageAnchorRendered(anchor);
370
+ }
371
+
372
+ return categoryScore;
359
373
  }
360
374
 
361
375
  /**