lighthouse 12.4.0-dev.20250317 → 12.4.0-dev.20250319

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 (149) hide show
  1. package/core/audits/audit.js +2 -1
  2. package/core/audits/bootup-time.js +1 -1
  3. package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  4. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -62
  5. package/core/audits/byte-efficiency/legacy-javascript.js +9 -357
  6. package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
  7. package/core/audits/byte-efficiency/offscreen-images.js +3 -3
  8. package/core/audits/byte-efficiency/render-blocking-resources.js +5 -3
  9. package/core/audits/byte-efficiency/unminified-css.js +2 -1
  10. package/core/audits/byte-efficiency/unminified-javascript.js +2 -1
  11. package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
  12. package/core/audits/byte-efficiency/unused-javascript.js +1 -1
  13. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +1 -1
  14. package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
  15. package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  16. package/core/audits/byte-efficiency/uses-text-compression.js +2 -1
  17. package/core/audits/critical-request-chains.js +4 -3
  18. package/core/audits/dobetterweb/dom-size.js +1 -1
  19. package/core/audits/dobetterweb/uses-http2.js +1 -1
  20. package/core/audits/insights/cls-culprits-insight.js +1 -1
  21. package/core/audits/insights/document-latency-insight.js +1 -1
  22. package/core/audits/insights/dom-size-insight.js +1 -1
  23. package/core/audits/insights/duplicated-javascript-insight.d.ts +11 -0
  24. package/core/audits/insights/duplicated-javascript-insight.js +54 -0
  25. package/core/audits/insights/font-display-insight.js +1 -1
  26. package/core/audits/insights/forced-reflow-insight.d.ts +10 -0
  27. package/core/audits/insights/forced-reflow-insight.js +64 -12
  28. package/core/audits/insights/image-delivery-insight.js +1 -1
  29. package/core/audits/insights/insight-audit.js +2 -1
  30. package/core/audits/insights/interaction-to-next-paint-insight.js +1 -1
  31. package/core/audits/insights/lcp-discovery-insight.js +1 -1
  32. package/core/audits/insights/lcp-phases-insight.js +1 -1
  33. package/core/audits/insights/network-dependency-tree-insight.js +1 -1
  34. package/core/audits/insights/render-blocking-insight.js +1 -1
  35. package/core/audits/insights/slow-css-selector-insight.js +1 -1
  36. package/core/audits/insights/third-parties-insight.js +1 -1
  37. package/core/audits/insights/{duplicate-javascript-insight.d.ts → use-cache-insight.d.ts} +3 -3
  38. package/core/audits/insights/{duplicate-javascript-insight.js → use-cache-insight.js} +6 -6
  39. package/core/audits/insights/viewport-insight.js +1 -1
  40. package/core/audits/largest-contentful-paint-element.js +3 -2
  41. package/core/audits/layout-shifts.js +4 -2
  42. package/core/audits/lcp-lazy-loaded.js +1 -1
  43. package/core/audits/long-tasks.js +4 -4
  44. package/core/audits/mainthread-work-breakdown.js +1 -1
  45. package/core/audits/metrics/first-contentful-paint.js +2 -2
  46. package/core/audits/metrics/interactive.js +2 -2
  47. package/core/audits/metrics/largest-contentful-paint.js +3 -2
  48. package/core/audits/metrics/max-potential-fid.js +2 -2
  49. package/core/audits/metrics/speed-index.js +2 -2
  50. package/core/audits/metrics/total-blocking-time.js +2 -2
  51. package/core/audits/metrics.js +4 -3
  52. package/core/audits/predictive-perf.js +3 -3
  53. package/core/audits/prioritize-lcp-image.js +4 -3
  54. package/core/audits/redirects.js +3 -2
  55. package/core/audits/script-treemap-data.js +1 -1
  56. package/core/audits/third-party-facades.js +1 -1
  57. package/core/audits/third-party-summary.js +1 -1
  58. package/core/audits/uses-rel-preconnect.js +6 -5
  59. package/core/audits/uses-rel-preload.js +3 -3
  60. package/core/computed/critical-request-chains.d.ts +3 -1
  61. package/core/computed/critical-request-chains.js +3 -3
  62. package/core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
  63. package/core/computed/metrics/first-contentful-paint.js +1 -1
  64. package/core/computed/metrics/interactive.js +1 -1
  65. package/core/computed/metrics/lantern-first-contentful-paint.js +1 -1
  66. package/core/computed/metrics/lantern-interactive.js +1 -1
  67. package/core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
  68. package/core/computed/metrics/lantern-max-potential-fid.js +1 -1
  69. package/core/computed/metrics/lantern-speed-index.js +1 -1
  70. package/core/computed/metrics/lantern-total-blocking-time.js +1 -1
  71. package/core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
  72. package/core/computed/metrics/largest-contentful-paint.js +1 -1
  73. package/core/computed/metrics/lcp-breakdown.js +1 -1
  74. package/core/computed/metrics/max-potential-fid.js +1 -1
  75. package/core/computed/metrics/metric.js +1 -0
  76. package/core/computed/metrics/speed-index.js +1 -1
  77. package/core/computed/metrics/time-to-first-byte.js +1 -1
  78. package/core/computed/metrics/timing-summary.d.ts +5 -2
  79. package/core/computed/metrics/timing-summary.js +6 -4
  80. package/core/computed/metrics/total-blocking-time.js +1 -1
  81. package/core/computed/navigation-insights.d.ts +3 -1
  82. package/core/computed/navigation-insights.js +6 -4
  83. package/core/computed/page-dependency-graph.d.ts +3 -1
  84. package/core/computed/page-dependency-graph.js +5 -4
  85. package/core/computed/tbt-impact-tasks.js +1 -1
  86. package/core/computed/trace-engine-result.d.ts +5 -2
  87. package/core/computed/trace-engine-result.js +18 -4
  88. package/core/config/default-config.js +4 -2
  89. package/core/config/experimental-config.js +2 -1
  90. package/core/gather/gatherers/source-maps.d.ts +1 -0
  91. package/core/gather/gatherers/source-maps.js +3 -0
  92. package/core/gather/gatherers/trace-elements.d.ts +4 -4
  93. package/core/gather/gatherers/trace-elements.js +7 -4
  94. package/core/gather/gatherers/trace.js +5 -0
  95. package/core/lib/legacy-javascript/legacy-javascript.d.ts +29 -0
  96. package/core/lib/legacy-javascript/legacy-javascript.js +351 -0
  97. package/package.json +3 -2
  98. package/shared/localization/locales/ar-XB.json +235 -1
  99. package/shared/localization/locales/ar.json +235 -7
  100. package/shared/localization/locales/bg.json +235 -7
  101. package/shared/localization/locales/ca.json +235 -7
  102. package/shared/localization/locales/cs.json +234 -6
  103. package/shared/localization/locales/da.json +236 -8
  104. package/shared/localization/locales/de.json +234 -6
  105. package/shared/localization/locales/el.json +235 -7
  106. package/shared/localization/locales/en-GB.json +235 -19
  107. package/shared/localization/locales/en-US.json +30 -3
  108. package/shared/localization/locales/en-XA.json +0 -6
  109. package/shared/localization/locales/en-XL.json +30 -3
  110. package/shared/localization/locales/es-419.json +235 -7
  111. package/shared/localization/locales/es.json +234 -6
  112. package/shared/localization/locales/fi.json +235 -7
  113. package/shared/localization/locales/fil.json +237 -3
  114. package/shared/localization/locales/fr.json +236 -8
  115. package/shared/localization/locales/he.json +234 -0
  116. package/shared/localization/locales/hi.json +236 -2
  117. package/shared/localization/locales/hr.json +235 -7
  118. package/shared/localization/locales/hu.json +235 -7
  119. package/shared/localization/locales/id.json +234 -0
  120. package/shared/localization/locales/it.json +238 -10
  121. package/shared/localization/locales/ja.json +234 -6
  122. package/shared/localization/locales/ko.json +235 -16
  123. package/shared/localization/locales/lt.json +235 -7
  124. package/shared/localization/locales/lv.json +236 -8
  125. package/shared/localization/locales/nl.json +235 -4
  126. package/shared/localization/locales/no.json +235 -7
  127. package/shared/localization/locales/pl.json +236 -8
  128. package/shared/localization/locales/pt-PT.json +235 -1
  129. package/shared/localization/locales/pt.json +235 -1
  130. package/shared/localization/locales/ro.json +234 -0
  131. package/shared/localization/locales/ru.json +235 -1
  132. package/shared/localization/locales/sk.json +235 -1
  133. package/shared/localization/locales/sl.json +236 -2
  134. package/shared/localization/locales/sr-Latn.json +235 -1
  135. package/shared/localization/locales/sr.json +235 -1
  136. package/shared/localization/locales/sv.json +235 -1
  137. package/shared/localization/locales/ta.json +235 -1
  138. package/shared/localization/locales/te.json +237 -3
  139. package/shared/localization/locales/th.json +235 -1
  140. package/shared/localization/locales/tr.json +236 -2
  141. package/shared/localization/locales/uk.json +235 -1
  142. package/shared/localization/locales/vi.json +236 -2
  143. package/shared/localization/locales/zh-HK.json +235 -1
  144. package/shared/localization/locales/zh-TW.json +236 -2
  145. package/shared/localization/locales/zh.json +234 -0
  146. package/tsconfig.json +2 -1
  147. package/types/artifacts.d.ts +1 -0
  148. /package/core/{audits/byte-efficiency → lib/legacy-javascript}/polyfill-graph-data.json +0 -0
  149. /package/core/{audits/byte-efficiency → lib/legacy-javascript}/polyfill-module-data.json +0 -0
@@ -491,7 +491,8 @@ class Audit {
491
491
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
492
492
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
493
493
  const gatherContext = artifacts.GatherContext;
494
- return {trace, devtoolsLog, gatherContext, settings: context.settings, URL: artifacts.URL};
494
+ const {URL, SourceMaps} = artifacts;
495
+ return {trace, devtoolsLog, gatherContext, settings: context.settings, URL, SourceMaps};
495
496
  }
496
497
  }
497
498
 
@@ -49,7 +49,7 @@ class BootupTime extends Audit {
49
49
  description: str_(UIStrings.description),
50
50
  scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
51
51
  guidanceLevel: 1,
52
- requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
52
+ requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext', 'SourceMaps'],
53
53
  };
54
54
  }
55
55
 
@@ -38,7 +38,7 @@ class EfficientAnimatedContent extends ByteEfficiencyAudit {
38
38
  description: str_(UIStrings.description),
39
39
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
40
40
  guidanceLevel: 3,
41
- requiredArtifacts: ['devtoolsLogs', 'traces', 'GatherContext', 'URL'],
41
+ requiredArtifacts: ['devtoolsLogs', 'traces', 'GatherContext', 'URL', 'SourceMaps'],
42
42
  };
43
43
  }
44
44
 
@@ -1,14 +1,4 @@
1
1
  export default LegacyJavascript;
2
- export type Pattern = {
3
- name: string;
4
- expression: string;
5
- estimateBytes?: (content: string) => number;
6
- };
7
- export type PatternMatchResult = {
8
- name: string;
9
- line: number;
10
- column: number;
11
- };
12
2
  export type ByteEfficiencyProduct = import("./byte-efficiency-audit.js").ByteEfficiencyProduct;
13
3
  export type Item = LH.Audit.ByteEfficiencyItem & {
14
4
  subItems: {
@@ -21,40 +11,6 @@ export type SubItem = {
21
11
  location: LH.Audit.Details.SourceLocationValue;
22
12
  };
23
13
  declare class LegacyJavascript extends ByteEfficiencyAudit {
24
- /**
25
- * @param {string?} object
26
- * @param {string} property
27
- * @param {string} coreJs3Module
28
- */
29
- static buildPolyfillExpression(object: string | null, property: string, coreJs3Module: string): string;
30
- static getPolyfillModuleData(): import("../../scripts/legacy-javascript/create-polyfill-module-data.js").PolyfillModuleData;
31
- static getCoreJsPolyfillData(): {
32
- name: string;
33
- coreJs3Module: string;
34
- }[];
35
- /**
36
- * @return {Pattern[]}
37
- */
38
- static getPolyfillPatterns(): Pattern[];
39
- /**
40
- * @return {Pattern[]}
41
- */
42
- static getTransformPatterns(): Pattern[];
43
- /**
44
- * Returns a collection of match results grouped by script url.
45
- *
46
- * @param {CodePatternMatcher} matcher
47
- * @param {LH.Artifacts['Scripts']} scripts
48
- * @param {LH.Artifacts.Bundle[]} bundles
49
- * @return {Map<LH.Artifacts.Script, PatternMatchResult[]>}
50
- */
51
- static detectAcrossScripts(matcher: CodePatternMatcher, scripts: LH.Artifacts["Scripts"], bundles: LH.Artifacts.Bundle[]): Map<LH.Artifacts.Script, PatternMatchResult[]>;
52
- /**
53
- * @param {LH.Artifacts.Script} script
54
- * @param {PatternMatchResult[]} matches
55
- * @return {number}
56
- */
57
- static estimateWastedBytes(script: LH.Artifacts.Script, matches: PatternMatchResult[]): number;
58
14
  /**
59
15
  * @param {LH.Artifacts} artifacts
60
16
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -69,22 +25,4 @@ export namespace UIStrings {
69
25
  let detectedCoreJs2Warning: string;
70
26
  }
71
27
  import { ByteEfficiencyAudit } from './byte-efficiency-audit.js';
72
- /**
73
- * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
74
- * and via `match` returns match results with line / column information for a given code input.
75
- * Only returns the first match per pattern given.
76
- */
77
- declare class CodePatternMatcher {
78
- /**
79
- * @param {Pattern[]} patterns
80
- */
81
- constructor(patterns: Pattern[]);
82
- re: RegExp;
83
- patterns: Pattern[];
84
- /**
85
- * @param {string} code
86
- * @return {PatternMatchResult[]}
87
- */
88
- match(code: string): PatternMatchResult[];
89
- }
90
28
  //# sourceMappingURL=legacy-javascript.d.ts.map
@@ -11,33 +11,17 @@
11
11
  * ./core/scripts/legacy-javascript - verification tool.
12
12
  */
13
13
 
14
- /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number}} Pattern */
15
- /** @typedef {{name: string, line: number, column: number}} PatternMatchResult */
16
14
  /** @typedef {import('./byte-efficiency-audit.js').ByteEfficiencyProduct} ByteEfficiencyProduct */
17
15
  /** @typedef {LH.Audit.ByteEfficiencyItem & {subItems: {type: 'subitems', items: SubItem[]}}} Item */
18
16
  /** @typedef {{signal: string, location: LH.Audit.Details.SourceLocationValue}} SubItem */
19
17
 
20
- import fs from 'fs';
21
-
22
18
  import {Audit} from '../audit.js';
23
19
  import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
24
20
  import {EntityClassification} from '../../computed/entity-classification.js';
25
21
  import {JSBundles} from '../../computed/js-bundles.js';
26
22
  import * as i18n from '../../lib/i18n/i18n.js';
27
23
  import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
28
- import {LH_ROOT} from '../../../shared/root.js';
29
-
30
- const polyfillModuleDataJson = fs.readFileSync(
31
- `${LH_ROOT}/core/audits/byte-efficiency/polyfill-module-data.json`, 'utf-8');
32
-
33
- /** @type {import('../../scripts/legacy-javascript/create-polyfill-module-data.js').PolyfillModuleData} */
34
- const polyfillModuleData = JSON.parse(polyfillModuleDataJson);
35
-
36
- const graphJson = fs.readFileSync(
37
- `${LH_ROOT}/core/audits/byte-efficiency/polyfill-graph-data.json`, 'utf-8');
38
-
39
- /** @type {import('../../scripts/legacy-javascript/create-polyfill-size-estimation.js').PolyfillSizeEstimator} */
40
- const graph = JSON.parse(graphJson);
24
+ import {detectLegacyJavaScript} from '../../lib/legacy-javascript/legacy-javascript.js';
41
25
 
42
26
  const UIStrings = {
43
27
  /** Title of a Lighthouse audit that tells the user about legacy polyfills and transforms used on the page. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -53,68 +37,6 @@ const UIStrings = {
53
37
 
54
38
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
55
39
 
56
- /**
57
- * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
58
- * and via `match` returns match results with line / column information for a given code input.
59
- * Only returns the first match per pattern given.
60
- */
61
- class CodePatternMatcher {
62
- /**
63
- * @param {Pattern[]} patterns
64
- */
65
- constructor(patterns) {
66
- const patternsExpression = patterns.map(pattern => `(${pattern.expression})`).join('|');
67
- this.re = new RegExp(`(^\r\n|\r|\n)|${patternsExpression}`, 'g');
68
- this.patterns = patterns;
69
- }
70
-
71
- /**
72
- * @param {string} code
73
- * @return {PatternMatchResult[]}
74
- */
75
- match(code) {
76
- // Reset RegExp state.
77
- this.re.lastIndex = 0;
78
-
79
- const seen = new Set();
80
- /** @type {PatternMatchResult[]} */
81
- const matches = [];
82
- /** @type {RegExpExecArray | null} */
83
- let result;
84
- let line = 0;
85
- let lineBeginsAtIndex = 0;
86
- // Each pattern maps to one subgroup in the generated regex. For each iteration of RegExp.exec,
87
- // only one subgroup will be defined. Exec until no more matches.
88
- while ((result = this.re.exec(code)) !== null) {
89
- // Discard first value in `result` - it's just the entire match.
90
- const captureGroups = result.slice(1);
91
- // isNewline - truthy if matching a newline, used to track the line number.
92
- // `patternExpressionMatches` maps to each possible pattern in `this.patterns`.
93
- // Only one of [isNewline, ...patternExpressionMatches] is ever truthy.
94
- const [isNewline, ...patternExpressionMatches] = captureGroups;
95
- if (isNewline) {
96
- line++;
97
- lineBeginsAtIndex = result.index + 1;
98
- continue;
99
- }
100
- const pattern = this.patterns[patternExpressionMatches.findIndex(Boolean)];
101
-
102
- if (seen.has(pattern)) {
103
- continue;
104
- }
105
- seen.add(pattern);
106
-
107
- matches.push({
108
- name: pattern.name,
109
- line,
110
- column: result.index - lineBeginsAtIndex,
111
- });
112
- }
113
-
114
- return matches;
115
- }
116
- }
117
-
118
40
  class LegacyJavascript extends ByteEfficiencyAudit {
119
41
  /**
120
42
  * @return {LH.Audit.Meta}
@@ -131,271 +53,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
131
53
  };
132
54
  }
133
55
 
134
- /**
135
- * @param {string?} object
136
- * @param {string} property
137
- * @param {string} coreJs3Module
138
- */
139
- static buildPolyfillExpression(object, property, coreJs3Module) {
140
- const qt = (/** @type {string} */ token) =>
141
- `['"]${token}['"]`; // don't worry about matching string delims
142
- const kebabCaseToCamelCase = (/** @type {string} */ str) =>
143
- str.replace(/(-\w)/g, m => m[1].toUpperCase());
144
-
145
- let expression = '';
146
-
147
- if (object) {
148
- // String.prototype.startsWith =
149
- expression += `${object}\\.${property}\\s?=[^=]`;
150
- } else {
151
- // Promise =
152
- // window.Promise =// Promise =Z
153
- // but not: SomePromise =
154
- expression += `(?:window\\.|[\\s;]+)${property}\\s?=[^=]`;
155
- }
156
-
157
- // String.prototype['startsWith'] =
158
- if (object) {
159
- expression += `|${object}\\[${qt(property)}\\]\\s?=[^=]`;
160
- }
161
-
162
- // Object.defineProperty(String.prototype, 'startsWith'
163
- expression += `|defineProperty\\(${object || 'window'},\\s?${qt(property)}`;
164
-
165
- // es-shims
166
- // no(Object,{entries:r},{entries:function
167
- if (object) {
168
- expression += `|\\(${object},\\s*{${property}:.*},\\s*{${property}`;
169
- }
170
-
171
- // core-js
172
- if (object) {
173
- const objectWithoutPrototype = object.replace('.prototype', '');
174
- // e(e.S,"Object",{values
175
- // Minified + mangled pattern found in CDN babel-polyfill.
176
- // see https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js
177
- // TODO: perhaps this is the wrong place to check for a CDN polyfill. Remove?
178
- // expression += `|;e\\([^,]+,${qt(objectWithoutPrototype)},{${property}:`;
179
-
180
- // core-js@3 minified pattern.
181
- // {target:"Array",proto:true},{fill:fill
182
- // {target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:
183
- expression += `|{target:${qt(objectWithoutPrototype)}[^;]*},{${property}:`;
184
- } else {
185
- // Detect polyfills for new classes: Map, Set, WeakSet, etc.
186
- // TODO: so far, no class polyfills are enabled for detection.
187
- // See `modulesToSkip` in create-polyfill-module-data.js
188
-
189
- // collection("Map",
190
- // expression += `|collection\\(${qt(property)},`;
191
- }
192
-
193
- // Un-minified code may have module names.
194
- // core-js/modules/es.object.is-frozen
195
- expression += `|core-js/modules/${coreJs3Module}(?:\\.js)?"`;
196
- // rollup unminified output for commonjs modules.
197
- // ex: es.reflect.own-keys -> function requireEs_reflect_ownKeys ()
198
- let rollupSlug = kebabCaseToCamelCase(coreJs3Module).replaceAll('.', '_');
199
- rollupSlug = rollupSlug[0].toUpperCase() + rollupSlug.slice(1);
200
- expression += `|require${rollupSlug} \\(`;
201
-
202
- return expression;
203
- }
204
-
205
- static getPolyfillModuleData() {
206
- return polyfillModuleData;
207
- }
208
-
209
- static getCoreJsPolyfillData() {
210
- return this.getPolyfillModuleData().filter(d => d.corejs).map(d => {
211
- return {
212
- name: d.name,
213
- coreJs3Module: d.modules[0],
214
- };
215
- });
216
- }
217
-
218
- /**
219
- * @return {Pattern[]}
220
- */
221
- static getPolyfillPatterns() {
222
- /** @type {Pattern[]} */
223
- const patterns = [];
224
-
225
- for (const {name, coreJs3Module} of this.getCoreJsPolyfillData()) {
226
- const parts = name.split('.');
227
- const object = parts.length > 1 ? parts.slice(0, parts.length - 1).join('.') : null;
228
- const property = parts[parts.length - 1];
229
- patterns.push({
230
- name,
231
- expression: this.buildPolyfillExpression(object, property, coreJs3Module),
232
- });
233
- }
234
-
235
- return patterns;
236
- }
237
-
238
- /**
239
- * @return {Pattern[]}
240
- */
241
- static getTransformPatterns() {
242
- /**
243
- * @param {string} content
244
- * @param {RegExp|string} pattern
245
- * @return {number}
246
- */
247
- const count = (content, pattern) => {
248
- // Split is slightly faster than match.
249
- if (typeof pattern === 'string') {
250
- return content.split(pattern).length - 1;
251
- }
252
-
253
- return (content.match(pattern) ?? []).length;
254
- };
255
-
256
- // For expression: prefer a string that is found in the transform runtime support code (those won't ever be minified).
257
-
258
- return [
259
- // @babel/plugin-transform-classes
260
- //
261
- // input:
262
- //
263
- // class MyTestClass {
264
- // log() {
265
- // console.log(1);
266
- // }
267
- // };
268
- //
269
- // output:
270
- //
271
- // function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
272
- // function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
273
- // function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
274
- // function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
275
- // function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
276
- // let MyTestClass = function () {
277
- // function MyTestClass() {
278
- // _classCallCheck(this, MyTestClass);
279
- // }
280
- // return _createClass(MyTestClass, [{
281
- // key: "log",
282
- // value: function log() {
283
- // console.log(1);
284
- // }
285
- // }]);
286
- // }();
287
- {
288
- name: '@babel/plugin-transform-classes',
289
- expression: 'Cannot call a class as a function',
290
- estimateBytes: content => {
291
- return 1000 + (count(content, '_classCallCheck') - 1) * '_classCallCheck()'.length;
292
- },
293
- },
294
- {
295
- name: '@babel/plugin-transform-regenerator',
296
- expression: 'Generator is already running|regeneratorRuntime',
297
- // Example of this transform: https://gist.github.com/connorjclark/af8bccfff377ac44efc104a79bc75da2
298
- // `regeneratorRuntime.awrap` is generated for every usage of `await`, and adds ~80 bytes each.
299
- estimateBytes: content => {
300
- return count(content, /regeneratorRuntime\(?\)?\.a?wrap/g) * 80;
301
- },
302
- },
303
- {
304
- name: '@babel/plugin-transform-spread',
305
- expression: 'Invalid attempt to spread non-iterable instance',
306
- estimateBytes: content => {
307
- const per = '_toConsumableArray()'.length;
308
- return 1169 + count(content, /\.apply\(void 0,\s?_toConsumableArray/g) * per;
309
- },
310
- },
311
- ];
312
- }
313
-
314
- /**
315
- * Returns a collection of match results grouped by script url.
316
- *
317
- * @param {CodePatternMatcher} matcher
318
- * @param {LH.Artifacts['Scripts']} scripts
319
- * @param {LH.Artifacts.Bundle[]} bundles
320
- * @return {Map<LH.Artifacts.Script, PatternMatchResult[]>}
321
- */
322
- static detectAcrossScripts(matcher, scripts, bundles) {
323
- /** @type {Map<LH.Artifacts.Script, PatternMatchResult[]>} */
324
- const scriptToMatchResults = new Map();
325
- const polyfillData = this.getPolyfillModuleData();
326
-
327
- for (const script of Object.values(scripts)) {
328
- if (!script.content) continue;
329
-
330
- // Start with pattern matching against the downloaded script.
331
- const matches = matcher.match(script.content);
332
-
333
- // If it's a bundle with source maps, add in the polyfill modules by name too.
334
- const bundle = bundles.find(b => b.script.scriptId === script.scriptId);
335
- if (bundle) {
336
- for (const {name, modules} of polyfillData) {
337
- // Skip if the pattern matching found a match for this polyfill.
338
- if (matches.some(m => m.name === name)) continue;
339
-
340
- const source = bundle.rawMap.sources.find(source => modules.some(module => {
341
- return source.endsWith(`/${module}.js`) || source.includes(`node_modules/${module}/`);
342
- }));
343
- if (!source) continue;
344
-
345
- const mapping = bundle.map.mappings().find(m => m.sourceURL === source);
346
- if (mapping) {
347
- matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
348
- } else {
349
- matches.push({name, line: 0, column: 0});
350
- }
351
- }
352
- }
353
-
354
- if (!matches.length) continue;
355
- scriptToMatchResults.set(script, matches);
356
- }
357
-
358
- return scriptToMatchResults;
359
- }
360
-
361
- /**
362
- * @param {LH.Artifacts.Script} script
363
- * @param {PatternMatchResult[]} matches
364
- * @return {number}
365
- */
366
- static estimateWastedBytes(script, matches) {
367
- // Split up results based on polyfill / transform. Only transforms start with @.
368
- const polyfillResults = matches.filter(m => !m.name.startsWith('@'));
369
- const transformResults = matches.filter(m => m.name.startsWith('@'));
370
-
371
- let estimatedWastedBytesFromPolyfills = 0;
372
- const modulesSeen = new Set();
373
- for (const result of polyfillResults) {
374
- const modules = graph.dependencies[result.name];
375
- if (!modules) continue; // Shouldn't happen.
376
- for (const module of modules) {
377
- modulesSeen.add(module);
378
- }
379
- }
380
-
381
- estimatedWastedBytesFromPolyfills += [...modulesSeen].reduce((acc, moduleIndex) => {
382
- return acc + graph.moduleSizes[moduleIndex];
383
- }, 0);
384
- estimatedWastedBytesFromPolyfills = Math.min(estimatedWastedBytesFromPolyfills, graph.maxSize);
385
-
386
- let estimatedWastedBytesFromTransforms = 0;
387
-
388
- for (const result of transformResults) {
389
- const pattern = this.getTransformPatterns().find(p => p.name === result.name);
390
- if (!pattern || !pattern.estimateBytes || !script.content) continue;
391
- estimatedWastedBytesFromTransforms += pattern.estimateBytes(script.content);
392
- }
393
-
394
- const estimatedWastedBytes =
395
- estimatedWastedBytesFromPolyfills + estimatedWastedBytesFromTransforms;
396
- return estimatedWastedBytes;
397
- }
398
-
399
56
  /**
400
57
  * @param {LH.Artifacts} artifacts
401
58
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -412,20 +69,18 @@ class LegacyJavascript extends ByteEfficiencyAudit {
412
69
  /** @type {Item[]} */
413
70
  const items = [];
414
71
 
415
- const matcher = new CodePatternMatcher([
416
- ...this.getPolyfillPatterns(),
417
- ...this.getTransformPatterns(),
418
- ]);
419
-
420
72
  /** @type {Map<string, number>} */
421
73
  const compressionRatioByUrl = new Map();
422
74
 
423
- const scriptToMatchResults =
424
- this.detectAcrossScripts(matcher, artifacts.Scripts, bundles);
425
- for (const [script, matches] of scriptToMatchResults.entries()) {
75
+ for (const script of artifacts.Scripts) {
76
+ const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
77
+ const {matches, estimatedByteSavings} =
78
+ detectLegacyJavaScript(script.content ?? '', bundle?.map ?? null);
79
+ if (matches.length === 0) continue;
80
+
426
81
  const compressionRatio = estimateCompressionRatioForContent(
427
82
  compressionRatioByUrl, script.url, artifacts, networkRecords);
428
- const wastedBytes = Math.round(this.estimateWastedBytes(script, matches) * compressionRatio);
83
+ const wastedBytes = Math.round(estimatedByteSavings * compressionRatio);
429
84
  /** @type {typeof items[number]} */
430
85
  const item = {
431
86
  url: script.url,
@@ -438,10 +93,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
438
93
  totalBytes: 0,
439
94
  };
440
95
 
441
- const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
442
- const matchesSorted =
443
- matches.sort((a, b) => a.name > b.name ? 1 : a.name === b.name ? 0 : -1);
444
- for (const match of matchesSorted) {
96
+ for (const match of matches) {
445
97
  const {name, line, column} = match;
446
98
  /** @type {SubItem} */
447
99
  const subItem = {
@@ -37,7 +37,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
37
37
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
38
  guidanceLevel: 3,
39
39
  requiredArtifacts: ['OptimizedImages', 'devtoolsLogs', 'traces', 'URL', 'GatherContext',
40
- 'ImageElements'],
40
+ 'ImageElements', 'SourceMaps'],
41
41
  };
42
42
  }
43
43
 
@@ -52,7 +52,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
52
52
  supportedModes: ['navigation'],
53
53
  guidanceLevel: 2,
54
54
  requiredArtifacts: ['ImageElements', 'ViewportDimensions', 'GatherContext', 'devtoolsLogs',
55
- 'traces', 'URL'],
55
+ 'traces', 'URL', 'SourceMaps'],
56
56
  };
57
57
  }
58
58
 
@@ -163,12 +163,12 @@ class OffscreenImages extends ByteEfficiencyAudit {
163
163
  * @return {Promise<import('./byte-efficiency-audit.js').ByteEfficiencyProduct>}
164
164
  */
165
165
  static async audit_(artifacts, networkRecords, context) {
166
+ const {URL, SourceMaps} = artifacts;
166
167
  const images = artifacts.ImageElements;
167
168
  const viewportDimensions = artifacts.ViewportDimensions;
168
169
  const gatherContext = artifacts.GatherContext;
169
170
  const trace = artifacts.traces[ByteEfficiencyAudit.DEFAULT_PASS];
170
171
  const devtoolsLog = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS];
171
- const URL = artifacts.URL;
172
172
 
173
173
  /** @type {string[]} */
174
174
  const warnings = [];
@@ -199,7 +199,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
199
199
  const unfilteredResults = Array.from(resultsMap.values());
200
200
  // get the interactive time or fallback to getting the end of trace time
201
201
  try {
202
- const metricComputationData = {trace, devtoolsLog, gatherContext, settings, URL};
202
+ const metricComputationData = {trace, devtoolsLog, gatherContext, settings, URL, SourceMaps};
203
203
  const interactive = await Interactive.request(metricComputationData, context);
204
204
 
205
205
  // use interactive to generate items
@@ -112,7 +112,8 @@ class RenderBlockingResources extends Audit {
112
112
  // TODO: look into adding an `optionalArtifacts` property that captures the non-required nature
113
113
  // of CSSUsage
114
114
  requiredArtifacts:
115
- ['URL', 'traces', 'devtoolsLogs', 'Stylesheets', 'CSSUsage', 'GatherContext', 'Stacks'],
115
+ // eslint-disable-next-line max-len
116
+ ['URL', 'traces', 'devtoolsLogs', 'Stylesheets', 'CSSUsage', 'GatherContext', 'Stacks', 'SourceMaps'],
116
117
  };
117
118
  }
118
119
 
@@ -126,10 +127,11 @@ class RenderBlockingResources extends Audit {
126
127
  const gatherContext = artifacts.GatherContext;
127
128
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
128
129
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
130
+ const SourceMaps = artifacts.SourceMaps;
129
131
  const simulatorData = {devtoolsLog, settings: context.settings};
130
132
  const simulator = await LoadSimulator.request(simulatorData, context);
131
133
  const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
132
- const navInsights = await NavigationInsights.request({trace, settings}, context);
134
+ const navInsights = await NavigationInsights.request({trace, settings, SourceMaps}, context);
133
135
 
134
136
  const renderBlocking = navInsights.model.RenderBlocking;
135
137
  if (renderBlocking instanceof Error) throw renderBlocking;
@@ -141,7 +143,7 @@ class RenderBlockingResources extends Audit {
141
143
  };
142
144
 
143
145
  const metricComputationData = {trace, devtoolsLog, gatherContext, simulator,
144
- settings: metricSettings, URL: artifacts.URL};
146
+ settings: metricSettings, URL: artifacts.URL, SourceMaps: artifacts.SourceMaps};
145
147
 
146
148
  // Cast to just `LanternMetric` since we explicitly set `throttlingMethod: 'simulate'`.
147
149
  const fcpSimulation = /** @type {LH.Artifacts.LanternMetric} */
@@ -37,7 +37,8 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
37
37
  description: str_(UIStrings.description),
38
38
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
39
39
  guidanceLevel: 3,
40
- requiredArtifacts: ['Stylesheets', 'devtoolsLogs', 'traces', 'URL', 'GatherContext'],
40
+ requiredArtifacts: ['Stylesheets', 'devtoolsLogs', 'traces', 'URL', 'GatherContext',
41
+ 'SourceMaps'],
41
42
  };
42
43
  }
43
44
 
@@ -44,7 +44,8 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
44
44
  description: str_(UIStrings.description),
45
45
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
46
46
  guidanceLevel: 3,
47
- requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL'],
47
+ requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL',
48
+ 'SourceMaps'],
48
49
  };
49
50
  }
50
51
 
@@ -35,7 +35,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
35
35
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
36
36
  guidanceLevel: 1,
37
37
  requiredArtifacts:
38
- ['Stylesheets', 'CSSUsage', 'URL', 'devtoolsLogs', 'traces', 'GatherContext'],
38
+ ['Stylesheets', 'CSSUsage', 'URL', 'devtoolsLogs', 'traces', 'GatherContext', 'SourceMaps'],
39
39
  };
40
40
  }
41
41
 
@@ -69,7 +69,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
69
69
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
70
70
  guidanceLevel: 1,
71
71
  requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
72
- 'devtoolsLogs', 'traces', 'URL'],
72
+ 'devtoolsLogs', 'traces', 'URL', 'SourceMaps'],
73
73
  };
74
74
  }
75
75
 
@@ -46,7 +46,7 @@ class CacheHeaders extends Audit {
46
46
  description: str_(UIStrings.description),
47
47
  scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
48
48
  guidanceLevel: 3,
49
- requiredArtifacts: ['devtoolsLogs'],
49
+ requiredArtifacts: ['devtoolsLogs', 'SourceMaps'],
50
50
  };
51
51
  }
52
52
 
@@ -37,7 +37,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
37
37
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
38
  guidanceLevel: 2,
39
39
  requiredArtifacts: ['OptimizedImages', 'ImageElements', 'GatherContext', 'devtoolsLogs',
40
- 'traces', 'URL'],
40
+ 'traces', 'URL', 'SourceMaps'],
41
41
  };
42
42
  }
43
43
 
@@ -48,7 +48,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
48
48
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
49
49
  guidanceLevel: 2,
50
50
  requiredArtifacts: ['ImageElements', 'ViewportDimensions', 'GatherContext',
51
- 'devtoolsLogs', 'traces', 'URL'],
51
+ 'devtoolsLogs', 'traces', 'URL', 'SourceMaps'],
52
52
  };
53
53
  }
54
54