lighthouse 12.4.0-dev.20250316 → 12.4.0-dev.20250318

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.
@@ -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,264 +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
-
143
- let expression = '';
144
-
145
- if (object) {
146
- // String.prototype.startsWith =
147
- expression += `${object}\\.${property}\\s?=[^=]`;
148
- } else {
149
- // Promise =
150
- // window.Promise =// Promise =Z
151
- // but not: SomePromise =
152
- expression += `(?:window\\.|[\\s;]+)${property}\\s?=[^=]`;
153
- }
154
-
155
- // String.prototype['startsWith'] =
156
- if (object) {
157
- expression += `|${object}\\[${qt(property)}\\]\\s?=[^=]`;
158
- }
159
-
160
- // Object.defineProperty(String.prototype, 'startsWith'
161
- expression += `|defineProperty\\(${object || 'window'},\\s?${qt(property)}`;
162
-
163
- // es-shims
164
- // no(Object,{entries:r},{entries:function
165
- if (object) {
166
- expression += `|\\(${object},\\s*{${property}:.*},\\s*{${property}`;
167
- }
168
-
169
- // core-js
170
- if (object) {
171
- const objectWithoutPrototype = object.replace('.prototype', '');
172
- // e(e.S,"Object",{values
173
- // Minified + mangled pattern found in CDN babel-polyfill.
174
- // see https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js
175
- // TODO: perhaps this is the wrong place to check for a CDN polyfill. Remove?
176
- // expression += `|;e\\([^,]+,${qt(objectWithoutPrototype)},{${property}:`;
177
-
178
- // core-js@3 minified pattern.
179
- // {target:"Array",proto:true},{fill:fill
180
- // {target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:
181
- expression += `|{target:${qt(objectWithoutPrototype)}\\S*},{${property}:`;
182
- } else {
183
- // Detect polyfills for new classes: Map, Set, WeakSet, etc.
184
- // TODO: so far, no class polyfills are enabled for detection.
185
- // See `modulesToSkip` in create-polyfill-module-data.js
186
-
187
- // collection("Map",
188
- // expression += `|collection\\(${qt(property)},`;
189
- }
190
-
191
- // Un-minified code may have module names.
192
- // core-js/modules/es.object.is-frozen
193
- expression += `|core-js/modules/${coreJs3Module}(?:\\.js)?"`;
194
-
195
- return expression;
196
- }
197
-
198
- static getPolyfillModuleData() {
199
- return polyfillModuleData;
200
- }
201
-
202
- static getCoreJsPolyfillData() {
203
- return this.getPolyfillModuleData().filter(d => d.corejs).map(d => {
204
- return {
205
- name: d.name,
206
- coreJs3Module: d.modules[0],
207
- };
208
- });
209
- }
210
-
211
- /**
212
- * @return {Pattern[]}
213
- */
214
- static getPolyfillPatterns() {
215
- /** @type {Pattern[]} */
216
- const patterns = [];
217
-
218
- for (const {name, coreJs3Module} of this.getCoreJsPolyfillData()) {
219
- const parts = name.split('.');
220
- const object = parts.length > 1 ? parts.slice(0, parts.length - 1).join('.') : null;
221
- const property = parts[parts.length - 1];
222
- patterns.push({
223
- name,
224
- expression: this.buildPolyfillExpression(object, property, coreJs3Module),
225
- });
226
- }
227
-
228
- return patterns;
229
- }
230
-
231
- /**
232
- * @return {Pattern[]}
233
- */
234
- static getTransformPatterns() {
235
- /**
236
- * @param {string} content
237
- * @param {RegExp|string} pattern
238
- * @return {number}
239
- */
240
- const count = (content, pattern) => {
241
- // Split is slightly faster than match.
242
- if (typeof pattern === 'string') {
243
- return content.split(pattern).length - 1;
244
- }
245
-
246
- return (content.match(pattern) ?? []).length;
247
- };
248
-
249
- // For expression: prefer a string that is found in the transform runtime support code (those won't ever be minified).
250
-
251
- return [
252
- // @babel/plugin-transform-classes
253
- //
254
- // input:
255
- //
256
- // class MyTestClass {
257
- // log() {
258
- // console.log(1);
259
- // }
260
- // };
261
- //
262
- // output:
263
- //
264
- // function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
265
- // 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); } }
266
- // function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
267
- // function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
268
- // 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); }
269
- // let MyTestClass = function () {
270
- // function MyTestClass() {
271
- // _classCallCheck(this, MyTestClass);
272
- // }
273
- // return _createClass(MyTestClass, [{
274
- // key: "log",
275
- // value: function log() {
276
- // console.log(1);
277
- // }
278
- // }]);
279
- // }();
280
- {
281
- name: '@babel/plugin-transform-classes',
282
- expression: 'Cannot call a class as a function',
283
- estimateBytes: content => {
284
- return 1000 + (count(content, '_classCallCheck') - 1) * '_classCallCheck()'.length;
285
- },
286
- },
287
- {
288
- name: '@babel/plugin-transform-regenerator',
289
- expression: 'Generator is already running|regeneratorRuntime',
290
- // Example of this transform: https://gist.github.com/connorjclark/af8bccfff377ac44efc104a79bc75da2
291
- // `regeneratorRuntime.awrap` is generated for every usage of `await`, and adds ~80 bytes each.
292
- estimateBytes: content => {
293
- return count(content, /regeneratorRuntime\(?\)?\.a?wrap/g) * 80;
294
- },
295
- },
296
- {
297
- name: '@babel/plugin-transform-spread',
298
- expression: 'Invalid attempt to spread non-iterable instance',
299
- estimateBytes: content => {
300
- const per = '_toConsumableArray()'.length;
301
- return 1169 + count(content, /\.apply\(void 0,\s?_toConsumableArray/g) * per;
302
- },
303
- },
304
- ];
305
- }
306
-
307
- /**
308
- * Returns a collection of match results grouped by script url.
309
- *
310
- * @param {CodePatternMatcher} matcher
311
- * @param {LH.Artifacts['Scripts']} scripts
312
- * @param {LH.Artifacts.Bundle[]} bundles
313
- * @return {Map<LH.Artifacts.Script, PatternMatchResult[]>}
314
- */
315
- static detectAcrossScripts(matcher, scripts, bundles) {
316
- /** @type {Map<LH.Artifacts.Script, PatternMatchResult[]>} */
317
- const scriptToMatchResults = new Map();
318
- const polyfillData = this.getPolyfillModuleData();
319
-
320
- for (const script of Object.values(scripts)) {
321
- if (!script.content) continue;
322
-
323
- // Start with pattern matching against the downloaded script.
324
- const matches = matcher.match(script.content);
325
-
326
- // If it's a bundle with source maps, add in the polyfill modules by name too.
327
- const bundle = bundles.find(b => b.script.scriptId === script.scriptId);
328
- if (bundle) {
329
- for (const {name, modules} of polyfillData) {
330
- // Skip if the pattern matching found a match for this polyfill.
331
- if (matches.some(m => m.name === name)) continue;
332
-
333
- const source = bundle.rawMap.sources.find(source => modules.some(module => {
334
- return source.endsWith(`/${module}.js`) || source.includes(`node_modules/${module}/`);
335
- }));
336
- if (!source) continue;
337
-
338
- const mapping = bundle.map.mappings().find(m => m.sourceURL === source);
339
- if (mapping) {
340
- matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
341
- } else {
342
- matches.push({name, line: 0, column: 0});
343
- }
344
- }
345
- }
346
-
347
- if (!matches.length) continue;
348
- scriptToMatchResults.set(script, matches);
349
- }
350
-
351
- return scriptToMatchResults;
352
- }
353
-
354
- /**
355
- * @param {LH.Artifacts.Script} script
356
- * @param {PatternMatchResult[]} matches
357
- * @return {number}
358
- */
359
- static estimateWastedBytes(script, matches) {
360
- // Split up results based on polyfill / transform. Only transforms start with @.
361
- const polyfillResults = matches.filter(m => !m.name.startsWith('@'));
362
- const transformResults = matches.filter(m => m.name.startsWith('@'));
363
-
364
- let estimatedWastedBytesFromPolyfills = 0;
365
- const modulesSeen = new Set();
366
- for (const result of polyfillResults) {
367
- const modules = graph.dependencies[result.name];
368
- if (!modules) continue; // Shouldn't happen.
369
- for (const module of modules) {
370
- modulesSeen.add(module);
371
- }
372
- }
373
-
374
- estimatedWastedBytesFromPolyfills += [...modulesSeen].reduce((acc, moduleIndex) => {
375
- return acc + graph.moduleSizes[moduleIndex];
376
- }, 0);
377
- estimatedWastedBytesFromPolyfills = Math.min(estimatedWastedBytesFromPolyfills, graph.maxSize);
378
-
379
- let estimatedWastedBytesFromTransforms = 0;
380
-
381
- for (const result of transformResults) {
382
- const pattern = this.getTransformPatterns().find(p => p.name === result.name);
383
- if (!pattern || !pattern.estimateBytes || !script.content) continue;
384
- estimatedWastedBytesFromTransforms += pattern.estimateBytes(script.content);
385
- }
386
-
387
- const estimatedWastedBytes =
388
- estimatedWastedBytesFromPolyfills + estimatedWastedBytesFromTransforms;
389
- return estimatedWastedBytes;
390
- }
391
-
392
56
  /**
393
57
  * @param {LH.Artifacts} artifacts
394
58
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -405,20 +69,18 @@ class LegacyJavascript extends ByteEfficiencyAudit {
405
69
  /** @type {Item[]} */
406
70
  const items = [];
407
71
 
408
- const matcher = new CodePatternMatcher([
409
- ...this.getPolyfillPatterns(),
410
- ...this.getTransformPatterns(),
411
- ]);
412
-
413
72
  /** @type {Map<string, number>} */
414
73
  const compressionRatioByUrl = new Map();
415
74
 
416
- const scriptToMatchResults =
417
- this.detectAcrossScripts(matcher, artifacts.Scripts, bundles);
418
- 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
+
419
81
  const compressionRatio = estimateCompressionRatioForContent(
420
82
  compressionRatioByUrl, script.url, artifacts, networkRecords);
421
- const wastedBytes = Math.round(this.estimateWastedBytes(script, matches) * compressionRatio);
83
+ const wastedBytes = Math.round(estimatedByteSavings * compressionRatio);
422
84
  /** @type {typeof items[number]} */
423
85
  const item = {
424
86
  url: script.url,
@@ -431,7 +93,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
431
93
  totalBytes: 0,
432
94
  };
433
95
 
434
- const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
435
96
  for (const match of matches) {
436
97
  const {name, line, column} = match;
437
98
  /** @type {SubItem} */
@@ -408,21 +408,21 @@ const defaultConfig = {
408
408
  {id: 'interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'},
409
409
 
410
410
  // Insight audits.
411
- {id: 'cls-culprits-insight', weight: 0, group: 'insights'},
412
- {id: 'document-latency-insight', weight: 0, group: 'insights'},
413
- {id: 'dom-size-insight', weight: 0, group: 'insights'},
414
- {id: 'duplicate-javascript-insight', weight: 0, group: 'insights'},
415
- {id: 'font-display-insight', weight: 0, group: 'insights'},
416
- {id: 'forced-reflow-insight', weight: 0, group: 'insights'},
417
- {id: 'image-delivery-insight', weight: 0, group: 'insights'},
418
- {id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
419
- {id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
420
- {id: 'lcp-phases-insight', weight: 0, group: 'insights'},
421
- {id: 'network-dependency-tree-insight', weight: 0, group: 'insights'},
422
- {id: 'render-blocking-insight', weight: 0, group: 'insights'},
423
- {id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
424
- {id: 'third-parties-insight', weight: 0, group: 'insights'},
425
- {id: 'viewport-insight', weight: 0, group: 'insights'},
411
+ {id: 'cls-culprits-insight', weight: 0, group: 'hidden'},
412
+ {id: 'document-latency-insight', weight: 0, group: 'hidden'},
413
+ {id: 'dom-size-insight', weight: 0, group: 'hidden'},
414
+ {id: 'duplicate-javascript-insight', weight: 0, group: 'hidden'},
415
+ {id: 'font-display-insight', weight: 0, group: 'hidden'},
416
+ {id: 'forced-reflow-insight', weight: 0, group: 'hidden'},
417
+ {id: 'image-delivery-insight', weight: 0, group: 'hidden'},
418
+ {id: 'interaction-to-next-paint-insight', weight: 0, group: 'hidden'},
419
+ {id: 'lcp-discovery-insight', weight: 0, group: 'hidden'},
420
+ {id: 'lcp-phases-insight', weight: 0, group: 'hidden'},
421
+ {id: 'network-dependency-tree-insight', weight: 0, group: 'hidden'},
422
+ {id: 'render-blocking-insight', weight: 0, group: 'hidden'},
423
+ {id: 'slow-css-selector-insight', weight: 0, group: 'hidden'},
424
+ {id: 'third-parties-insight', weight: 0, group: 'hidden'},
425
+ {id: 'viewport-insight', weight: 0, group: 'hidden'},
426
426
 
427
427
  // These are our "invisible" metrics. Not displayed, but still in the LHR.
428
428
  {id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'},
@@ -24,6 +24,24 @@ const config = {
24
24
  'performance': {
25
25
  auditRefs: [
26
26
  {id: 'uses-rel-preload', weight: 0, group: 'diagnostics'},
27
+
28
+ // TODO: Remove this when insights aren't hidden by default
29
+ // Insight audits.
30
+ {id: 'cls-culprits-insight', weight: 0, group: 'insights'},
31
+ {id: 'document-latency-insight', weight: 0, group: 'insights'},
32
+ {id: 'dom-size-insight', weight: 0, group: 'insights'},
33
+ {id: 'duplicate-javascript-insight', weight: 0, group: 'insights'},
34
+ {id: 'font-display-insight', weight: 0, group: 'insights'},
35
+ {id: 'forced-reflow-insight', weight: 0, group: 'insights'},
36
+ {id: 'image-delivery-insight', weight: 0, group: 'insights'},
37
+ {id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
38
+ {id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
39
+ {id: 'lcp-phases-insight', weight: 0, group: 'insights'},
40
+ {id: 'network-dependency-tree-insight', weight: 0, group: 'insights'},
41
+ {id: 'render-blocking-insight', weight: 0, group: 'insights'},
42
+ {id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
43
+ {id: 'third-parties-insight', weight: 0, group: 'insights'},
44
+ {id: 'viewport-insight', weight: 0, group: 'insights'},
27
45
  ],
28
46
  },
29
47
  // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default
@@ -0,0 +1,29 @@
1
+ export type Pattern = {
2
+ name: string;
3
+ expression: string;
4
+ estimateBytes?: (content: string) => number;
5
+ };
6
+ export type PatternMatchResult = {
7
+ name: string;
8
+ line: number;
9
+ column: number;
10
+ };
11
+ export type Result = {
12
+ matches: PatternMatchResult[];
13
+ estimatedByteSavings: number;
14
+ };
15
+ /**
16
+ * @param {string} content
17
+ * @param {import('../cdt/generated/SourceMap.js')|null} map
18
+ * @return {Result}
19
+ */
20
+ export function detectLegacyJavaScript(content: string, map: import("../cdt/generated/SourceMap.js") | null): Result;
21
+ /**
22
+ * @return {Pattern[]}
23
+ */
24
+ export function getTransformPatterns(): Pattern[];
25
+ export function getCoreJsPolyfillData(): {
26
+ name: string;
27
+ coreJs3Module: string;
28
+ }[];
29
+ //# sourceMappingURL=legacy-javascript.d.ts.map
@@ -0,0 +1,351 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Identifies polyfills and transforms that should not be present if needing to support only Baseline browsers.
9
+ * @see https://docs.google.com/document/d/1ItjJwAd6e0Ts6yMbvh8TN3BBh_sAd58rYE1whnpuxaA/edit Design document (old, based on module/nomodule pattern)
10
+ * @see https://docs.google.com/spreadsheets/d/1z28Au8wo8-c2UsM2lDVEOJcI3jOkb2c951xEBqzBKCc/edit?usp=sharing Legacy babel transforms / polyfills
11
+ * ./core/scripts/legacy-javascript - verification tool.
12
+ */
13
+
14
+ /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number}} Pattern */
15
+ /** @typedef {{name: string, line: number, column: number}} PatternMatchResult */
16
+ /** @typedef {{matches: PatternMatchResult[], estimatedByteSavings: number}} Result */
17
+
18
+ import fs from 'fs';
19
+
20
+ import {LH_ROOT} from '../../../shared/root.js';
21
+
22
+ const polyfillModuleDataJson = fs.readFileSync(
23
+ `${LH_ROOT}/core/lib/legacy-javascript/polyfill-module-data.json`, 'utf-8');
24
+
25
+ /** @type {import('../../scripts/legacy-javascript/create-polyfill-module-data.js').PolyfillModuleData} */
26
+ const polyfillModuleData = JSON.parse(polyfillModuleDataJson);
27
+
28
+ const graphJson = fs.readFileSync(
29
+ `${LH_ROOT}/core/lib/legacy-javascript/polyfill-graph-data.json`, 'utf-8');
30
+
31
+ /** @type {import('../../scripts/legacy-javascript/create-polyfill-size-estimation.js').PolyfillSizeEstimator} */
32
+ const graph = JSON.parse(graphJson);
33
+
34
+ /**
35
+ * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
36
+ * and via `match` returns match results with line / column information for a given code input.
37
+ * Only returns the first match per pattern given.
38
+ */
39
+ class CodePatternMatcher {
40
+ /**
41
+ * @param {Pattern[]} patterns
42
+ */
43
+ constructor(patterns) {
44
+ this.patterns = patterns;
45
+ }
46
+
47
+ /**
48
+ * @param {string} code
49
+ * @return {PatternMatchResult[]}
50
+ */
51
+ match(code) {
52
+ if (!this.re) {
53
+ const patternsExpression =
54
+ this.patterns.map(pattern => `(${pattern.expression})`).join('|');
55
+ this.re = new RegExp(`(^\r\n|\r|\n)|${patternsExpression}`, 'g');
56
+ }
57
+
58
+ // Reset RegExp state.
59
+ this.re.lastIndex = 0;
60
+
61
+ const seen = new Set();
62
+ /** @type {PatternMatchResult[]} */
63
+ const matches = [];
64
+ /** @type {RegExpExecArray | null} */
65
+ let result;
66
+ let line = 0;
67
+ let lineBeginsAtIndex = 0;
68
+ // Each pattern maps to one subgroup in the generated regex. For each iteration of RegExp.exec,
69
+ // only one subgroup will be defined. Exec until no more matches.
70
+ while ((result = this.re.exec(code)) !== null) {
71
+ // Discard first value in `result` - it's just the entire match.
72
+ const captureGroups = result.slice(1);
73
+ // isNewline - truthy if matching a newline, used to track the line number.
74
+ // `patternExpressionMatches` maps to each possible pattern in `this.patterns`.
75
+ // Only one of [isNewline, ...patternExpressionMatches] is ever truthy.
76
+ const [isNewline, ...patternExpressionMatches] = captureGroups;
77
+ if (isNewline) {
78
+ line++;
79
+ lineBeginsAtIndex = result.index + 1;
80
+ continue;
81
+ }
82
+ const pattern = this.patterns[patternExpressionMatches.findIndex(Boolean)];
83
+
84
+ if (seen.has(pattern)) {
85
+ continue;
86
+ }
87
+ seen.add(pattern);
88
+
89
+ matches.push({
90
+ name: pattern.name,
91
+ line,
92
+ column: result.index - lineBeginsAtIndex,
93
+ });
94
+ }
95
+
96
+ return matches;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @param {string?} object
102
+ * @param {string} property
103
+ * @param {string} coreJs3Module
104
+ */
105
+ function buildPolyfillExpression(object, property, coreJs3Module) {
106
+ const qt = (/** @type {string} */ token) =>
107
+ `['"]${token}['"]`; // don't worry about matching string delims
108
+
109
+ let expression = '';
110
+
111
+ if (object) {
112
+ // String.prototype.startsWith =
113
+ expression += `${object}\\.${property}\\s?=[^=]`;
114
+ } else {
115
+ // Promise =
116
+ // window.Promise =// Promise =Z
117
+ // but not: SomePromise =
118
+ expression += `(?:window\\.|[\\s;]+)${property}\\s?=[^=]`;
119
+ }
120
+
121
+ // String.prototype['startsWith'] =
122
+ if (object) {
123
+ expression += `|${object}\\[${qt(property)}\\]\\s?=[^=]`;
124
+ }
125
+
126
+ // Object.defineProperty(String.prototype, 'startsWith'
127
+ expression += `|defineProperty\\(${object || 'window'},\\s?${qt(property)}`;
128
+
129
+ // es-shims
130
+ // no(Object,{entries:r},{entries:function
131
+ if (object) {
132
+ expression += `|\\(${object},\\s*{${property}:.*},\\s*{${property}`;
133
+ }
134
+
135
+ // core-js
136
+ if (object) {
137
+ const objectWithoutPrototype = object.replace('.prototype', '');
138
+ // e(e.S,"Object",{values
139
+ // Minified + mangled pattern found in CDN babel-polyfill.
140
+ // see https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js
141
+ // TODO: perhaps this is the wrong place to check for a CDN polyfill. Remove?
142
+ // expression += `|;e\\([^,]+,${qt(objectWithoutPrototype)},{${property}:`;
143
+
144
+ // core-js@3 minified pattern.
145
+ // {target:"Array",proto:true},{fill:fill
146
+ // {target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:
147
+ expression += `|{target:${qt(objectWithoutPrototype)}[^;]*},{${property}:`;
148
+ } else {
149
+ // Detect polyfills for new classes: Map, Set, WeakSet, etc.
150
+ // TODO: so far, no class polyfills are enabled for detection.
151
+ // See `modulesToSkip` in create-polyfill-module-data.js
152
+
153
+ // collection("Map",
154
+ // expression += `|collection\\(${qt(property)},`;
155
+ }
156
+
157
+ // Un-minified code may have module names.
158
+ // core-js/modules/es.object.is-frozen
159
+ expression += `|${coreJs3Module.replaceAll('.', '\\.')}(?:\\.js)?"`;
160
+
161
+ return expression;
162
+ }
163
+
164
+ function getCoreJsPolyfillData() {
165
+ return polyfillModuleData.filter(d => d.corejs).map(d => {
166
+ return {
167
+ name: d.name,
168
+ coreJs3Module: d.modules[0],
169
+ };
170
+ });
171
+ }
172
+
173
+ /**
174
+ * @return {Pattern[]}
175
+ */
176
+ function getPolyfillPatterns() {
177
+ /** @type {Pattern[]} */
178
+ const patterns = [];
179
+
180
+ for (const {name, coreJs3Module} of getCoreJsPolyfillData()) {
181
+ const parts = name.split('.');
182
+ const object = parts.length > 1 ? parts.slice(0, parts.length - 1).join('.') : null;
183
+ const property = parts[parts.length - 1];
184
+ patterns.push({
185
+ name,
186
+ expression: buildPolyfillExpression(object, property, coreJs3Module),
187
+ });
188
+ }
189
+
190
+ return patterns;
191
+ }
192
+
193
+ /**
194
+ * @return {Pattern[]}
195
+ */
196
+ function getTransformPatterns() {
197
+ /**
198
+ * @param {string} content
199
+ * @param {RegExp|string} pattern
200
+ * @return {number}
201
+ */
202
+ const count = (content, pattern) => {
203
+ // Split is slightly faster than match.
204
+ if (typeof pattern === 'string') {
205
+ return content.split(pattern).length - 1;
206
+ }
207
+
208
+ return (content.match(pattern) ?? []).length;
209
+ };
210
+
211
+ // For expression: prefer a string that is found in the transform runtime support code (those won't ever be minified).
212
+
213
+ return [
214
+ // @babel/plugin-transform-classes
215
+ //
216
+ // input:
217
+ //
218
+ // class MyTestClass {
219
+ // log() {
220
+ // console.log(1);
221
+ // }
222
+ // };
223
+ //
224
+ // output:
225
+ //
226
+ // function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
227
+ // 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); } }
228
+ // function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
229
+ // function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
230
+ // 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); }
231
+ // let MyTestClass = function () {
232
+ // function MyTestClass() {
233
+ // _classCallCheck(this, MyTestClass);
234
+ // }
235
+ // return _createClass(MyTestClass, [{
236
+ // key: "log",
237
+ // value: function log() {
238
+ // console.log(1);
239
+ // }
240
+ // }]);
241
+ // }();
242
+ {
243
+ name: '@babel/plugin-transform-classes',
244
+ expression: 'Cannot call a class as a function',
245
+ estimateBytes: content => {
246
+ return 1000 + (count(content, '_classCallCheck') - 1) * '_classCallCheck()'.length;
247
+ },
248
+ },
249
+ {
250
+ name: '@babel/plugin-transform-regenerator',
251
+ expression: 'Generator is already running|regeneratorRuntime',
252
+ // Example of this transform: https://gist.github.com/connorjclark/af8bccfff377ac44efc104a79bc75da2
253
+ // `regeneratorRuntime.awrap` is generated for every usage of `await`, and adds ~80 bytes each.
254
+ estimateBytes: content => {
255
+ return count(content, /regeneratorRuntime\(?\)?\.a?wrap/g) * 80;
256
+ },
257
+ },
258
+ {
259
+ name: '@babel/plugin-transform-spread',
260
+ expression: 'Invalid attempt to spread non-iterable instance',
261
+ estimateBytes: content => {
262
+ const per = '_toConsumableArray()'.length;
263
+ return 1169 + count(content, /\.apply\(void 0,\s?_toConsumableArray/g) * per;
264
+ },
265
+ },
266
+ ];
267
+ }
268
+
269
+ /**
270
+ * @param {string} content
271
+ * @param {PatternMatchResult[]} matches
272
+ * @return {number}
273
+ */
274
+ function estimateWastedBytes(content, matches) {
275
+ // Split up results based on polyfill / transform. Only transforms start with @.
276
+ const polyfillResults = matches.filter(m => !m.name.startsWith('@'));
277
+ const transformResults = matches.filter(m => m.name.startsWith('@'));
278
+
279
+ let estimatedWastedBytesFromPolyfills = 0;
280
+ const modulesSeen = new Set();
281
+ for (const result of polyfillResults) {
282
+ const modules = graph.dependencies[result.name];
283
+ if (!modules) continue; // Shouldn't happen.
284
+ for (const module of modules) {
285
+ modulesSeen.add(module);
286
+ }
287
+ }
288
+
289
+ estimatedWastedBytesFromPolyfills += [...modulesSeen].reduce((acc, moduleIndex) => {
290
+ return acc + graph.moduleSizes[moduleIndex];
291
+ }, 0);
292
+ estimatedWastedBytesFromPolyfills = Math.min(estimatedWastedBytesFromPolyfills, graph.maxSize);
293
+
294
+ let estimatedWastedBytesFromTransforms = 0;
295
+
296
+ for (const result of transformResults) {
297
+ const pattern = getTransformPatterns().find(p => p.name === result.name);
298
+ if (!pattern || !pattern.estimateBytes || !content) continue;
299
+ estimatedWastedBytesFromTransforms += pattern.estimateBytes(content);
300
+ }
301
+
302
+ const estimatedWastedBytes =
303
+ estimatedWastedBytesFromPolyfills + estimatedWastedBytesFromTransforms;
304
+ return estimatedWastedBytes;
305
+ }
306
+
307
+ const matcher = new CodePatternMatcher([
308
+ ...getPolyfillPatterns(),
309
+ ...getTransformPatterns(),
310
+ ]);
311
+
312
+ /**
313
+ * @param {string} content
314
+ * @param {import('../cdt/generated/SourceMap.js')|null} map
315
+ * @return {Result}
316
+ */
317
+ function detectLegacyJavaScript(content, map) {
318
+ if (!content) return {matches: [], estimatedByteSavings: 0};
319
+
320
+ // Start with pattern matching against the downloaded script.
321
+ let matches = matcher.match(content);
322
+
323
+ // If it's a bundle with source maps, add in the polyfill modules by name too.
324
+ if (map) {
325
+ for (const {name, modules} of polyfillModuleData) {
326
+ // Skip if the pattern matching found a match for this polyfill.
327
+ if (matches.some(m => m.name === name)) continue;
328
+
329
+ const source = map.sourceURLs().find(source => modules.some(module => {
330
+ return source.endsWith(`/${module}.js`) || source.includes(`node_modules/${module}/`);
331
+ }));
332
+ if (!source) continue;
333
+
334
+ const mapping = map.mappings().find(m => m.sourceURL === source);
335
+ if (mapping) {
336
+ matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
337
+ } else {
338
+ matches.push({name, line: 0, column: 0});
339
+ }
340
+ }
341
+ }
342
+
343
+ matches = matches.sort((a, b) => a.name > b.name ? 1 : a.name === b.name ? 0 : -1);
344
+
345
+ return {
346
+ matches,
347
+ estimatedByteSavings: estimateWastedBytes(content, matches),
348
+ };
349
+ }
350
+
351
+ export {detectLegacyJavaScript, getTransformPatterns, getCoreJsPolyfillData};
@@ -0,0 +1,93 @@
1
+ {
2
+ "moduleSizes": [26070, 498, 282, 294, 281, 467, 161, 236, 229, 765, 546, 339, 1608, 723, 729, 1545, 438, 214, 657, 111, 759, 537, 209, 281, 685, 217, 757, 631, 293, 182, 475, 79, 407, 140, 366, 792, 269, 222, 158, 280, 188, 137, 158, 105, 189, 543, 160, 742, 1436, 88, 904, 146, 314, 375, 183, 1083, 195, 503, 269, 208, 334, 350, 460, 568, 229, 1155, 334, 266, 30, 120, 309, 370, 358, 1952, 1638, 304, 153, 274, 1288, 192, 543, 74, 144, 137, 33, 336, 457, 2122, 535, 711, 1323, 117, 1961, 244, 557, 318, 119, 124, 108, 144, 96, 133, 441, 210, 1627, 1956, 693, 1426, 863, 637, 301, 51, 708, 583, 119, 600, 221, 370, 728, 1085, 552, 629, 125, 1746, 97, 441, 543, 2756, 371, 447, 548, 243, 266, 217, 99, 440, 183, 546, 137, 464, 207, 983, 503, 237, 382, 249, 675, 402, 254, 223, 164, 214, 191, 831, 218, 202, 232, 124, 249, 160, 251, 217, 717, 78, 561, 1627, 256, 386, 225, 432, 499, 394, 364, 445, 634, 667, 177, 346, 470, 663, 142, 588, 414, 617, 1559, 380, 2520, 1040, 417, 289, 238, 220, 214, 303, 163, 141, 510, 397, 137, 137, 133, 133, 390, 266, 137, 183, 215, 191, 485, 328, 575, 799, 533, 148, 215, 589, 589, 130, 362, 562, 471, 179, 186, 1266, 1456, 521, 1536, 427, 444, 406, 912, 150, 283, 144, 485, 470, 205, 1268, 796, 658, 306, 3751, 814, 146, 2328, 1226, 922, 237, 206, 198, 250, 283, 60, 3000],
3
+ "dependencies": {
4
+ "Array.prototype.at": [0, 5, 69, 105, 106, 116, 164],
5
+ "Array.prototype.concat": [0, 16, 21, 22, 26, 34, 40, 76, 78, 155, 165],
6
+ "Array.prototype.copyWithin": [0, 5, 9, 37, 69, 105, 106, 116, 166],
7
+ "Array.prototype.every": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 167],
8
+ "Array.prototype.fill": [0, 5, 10, 69, 105, 106, 116, 168],
9
+ "Array.prototype.filter": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 155, 169],
10
+ "Array.prototype.find": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 155, 173],
11
+ "Array.prototype.findIndex": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 155, 170],
12
+ "Array.prototype.findLast": [0, 5, 14, 53, 59, 69, 105, 106, 116, 172],
13
+ "Array.prototype.findLastIndex": [0, 5, 14, 53, 59, 69, 105, 106, 116, 171],
14
+ "Array.prototype.flat": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 155, 175],
15
+ "Array.prototype.flatMap": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 155, 174],
16
+ "Array.prototype.forEach": [0, 11, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 176],
17
+ "Array.from": [0, 12, 23, 24, 26, 34, 53, 59, 62, 63, 75, 78, 88, 155, 177],
18
+ "Array.prototype.includes": [0, 5, 69, 105, 106, 116, 178],
19
+ "Array.prototype.indexOf": [0, 17, 59, 179],
20
+ "Array.isArray": [0, 76, 180],
21
+ "Array.prototype.join": [0, 17, 181],
22
+ "Array.prototype.map": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 155, 182],
23
+ "Array.of": [0, 26, 34, 78, 155, 183],
24
+ "Array.prototype.slice": [0, 16, 19, 26, 34, 76, 78, 155, 184],
25
+ "Array.prototype.some": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 155, 185],
26
+ "Array.prototype.sort": [0, 17, 19, 20, 26, 37, 42, 43, 46, 155, 156, 186],
27
+ "Array.prototype.unshift": [0, 18, 37, 40, 76, 187],
28
+ "Math.acosh": [0, 97, 188],
29
+ "Math.asinh": [0, 189],
30
+ "Math.atanh": [0, 190],
31
+ "Math.cbrt": [0, 100, 191],
32
+ "Math.clz32": [0, 192],
33
+ "Math.cosh": [0, 93, 193],
34
+ "Math.expm1": [0, 93, 194],
35
+ "Math.fround": [0, 94, 95, 99, 100, 195],
36
+ "Math.hypot": [0, 196],
37
+ "Math.imul": [0, 197],
38
+ "Math.log10": [0, 96, 198],
39
+ "Math.log1p": [0, 97, 199],
40
+ "Math.log2": [0, 98, 200],
41
+ "Math.sign": [0, 100, 201],
42
+ "Math.sinh": [0, 93, 202],
43
+ "Math.tanh": [0, 93, 203],
44
+ "Math.trunc": [0, 204],
45
+ "Object.assign": [0, 104, 116, 205],
46
+ "Object.create": [0, 69, 105, 106, 116, 206],
47
+ "Object.entries": [0, 29, 112, 116, 119, 207],
48
+ "Object.freeze": [0, 8, 19, 51, 73, 109, 113, 208],
49
+ "Object.fromEntries": [0, 26, 34, 53, 59, 62, 63, 75, 87, 88, 155, 209],
50
+ "Object.getOwnPropertyDescriptor": [0, 210],
51
+ "Object.getOwnPropertyDescriptors": [0, 34, 211],
52
+ "Object.getPrototypeOf": [0, 29, 112, 212],
53
+ "Object.hasOwn": [0, 213],
54
+ "Object.is": [0, 134, 217],
55
+ "Object.isExtensible": [0, 8, 113, 214],
56
+ "Object.isFrozen": [0, 8, 215],
57
+ "Object.isSealed": [0, 8, 216],
58
+ "Object.keys": [0, 116, 218],
59
+ "Object.preventExtensions": [0, 8, 19, 51, 73, 109, 113, 219],
60
+ "Object.seal": [0, 8, 19, 51, 73, 109, 113, 220],
61
+ "Object.setPrototypeOf": [0, 4, 58, 83, 118, 221],
62
+ "Object.values": [0, 29, 112, 116, 119, 222],
63
+ "Promise.any": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 155, 224],
64
+ "Reflect.apply": [0, 52, 225],
65
+ "Reflect.construct": [0, 3, 19, 26, 52, 55, 69, 78, 105, 106, 116, 155, 226],
66
+ "Reflect.deleteProperty": [0, 227],
67
+ "Reflect.get": [0, 29, 79, 112, 230],
68
+ "Reflect.getOwnPropertyDescriptor": [0, 228],
69
+ "Reflect.getPrototypeOf": [0, 29, 112, 229],
70
+ "Reflect.has": [0, 231],
71
+ "Reflect.isExtensible": [0, 8, 113, 232],
72
+ "Reflect.ownKeys": [0, 233],
73
+ "Reflect.preventExtensions": [0, 51, 234],
74
+ "Reflect.setPrototypeOf": [0, 4, 58, 83, 118, 235],
75
+ "String.prototype.codePointAt": [0, 26, 141, 155, 156, 236],
76
+ "String.prototype.endsWith": [0, 26, 28, 59, 85, 103, 155, 156, 237],
77
+ "String.fromCodePoint": [0, 238],
78
+ "String.prototype.includes": [0, 26, 28, 85, 103, 155, 156, 239],
79
+ "String.prototype.matchAll": [0, 3, 6, 26, 29, 31, 59, 69, 78, 85, 89, 90, 105, 106, 112, 116, 126, 127, 128, 129, 130, 131, 132, 135, 139, 141, 155, 156, 241],
80
+ "String.raw": [0, 26, 155, 156, 242],
81
+ "String.prototype.repeat": [0, 26, 142, 155, 156, 243],
82
+ "String.prototype.replaceAll": [0, 26, 65, 85, 128, 129, 155, 156, 244],
83
+ "String.prototype.startsWith": [0, 26, 28, 59, 85, 103, 155, 156, 245],
84
+ "String.prototype.substr": [0, 26, 155, 156, 246],
85
+ "String.prototype.trim": [0, 26, 144, 146, 155, 156, 163, 251],
86
+ "String.prototype.trimEnd": [0, 26, 143, 144, 146, 155, 156, 163, 247, 249],
87
+ "String.prototype.trimStart": [0, 26, 144, 145, 146, 155, 156, 163, 248, 250],
88
+ "String.prototype.link": [0, 26, 30, 140, 155, 156, 240],
89
+ "Promise.allSettled": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 155, 223, 252],
90
+ "focus-visible": [253]
91
+ },
92
+ "maxSize": 155835
93
+ }
@@ -506,13 +506,6 @@
506
506
  ],
507
507
  "corejs": true
508
508
  },
509
- {
510
- "name": "Reflect.set",
511
- "modules": [
512
- "es.reflect.set"
513
- ],
514
- "corejs": true
515
- },
516
509
  {
517
510
  "name": "Reflect.setPrototypeOf",
518
511
  "modules": [
@@ -556,20 +549,6 @@
556
549
  ],
557
550
  "corejs": true
558
551
  },
559
- {
560
- "name": "String.prototype.padEnd",
561
- "modules": [
562
- "es.string.pad-end"
563
- ],
564
- "corejs": true
565
- },
566
- {
567
- "name": "String.prototype.padStart",
568
- "modules": [
569
- "es.string.pad-start"
570
- ],
571
- "corejs": true
572
- },
573
552
  {
574
553
  "name": "String.raw",
575
554
  "modules": [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.4.0-dev.20250316",
4
+ "version": "12.4.0-dev.20250318",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -139,6 +139,7 @@
139
139
  "c8": "^7.11.3",
140
140
  "chalk": "^2.4.1",
141
141
  "chrome-devtools-frontend": "1.0.1418433",
142
+ "colors": "^1.4.0",
142
143
  "concurrently": "^6.4.0",
143
144
  "conventional-changelog-cli": "^2.1.1",
144
145
  "core-js-compat": "^3.41.0",
package/tsconfig.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "core/test/results/sample_v2.json",
25
25
  "core/test/fixtures/unresolved-perflog.json",
26
26
  "core/test/fixtures/traces/lcp-m78.devtools.log.json",
27
- "core/audits/byte-efficiency/polyfill-graph-data.json",
27
+ "core/lib/legacy-javascript/polyfill-graph-data.json",
28
28
  "shared/localization/locales/en-US.json",
29
29
  ],
30
30
  "exclude": [
@@ -67,6 +67,7 @@
67
67
  "core/test/lib/emulation-test.js",
68
68
  "core/test/lib/i18n/i18n-test.js",
69
69
  "core/test/lib/icons-test.js",
70
+ "core/test/lib/legacy-javascript-test.js",
70
71
  "core/test/lib/lh-element-test.js",
71
72
  "core/test/lib/lighthouse-compatibility-test.js",
72
73
  "core/test/lib/manifest-parser-test.js",
@@ -1,96 +0,0 @@
1
- {
2
- "moduleSizes": [26070, 498, 282, 294, 281, 467, 161, 236, 229, 765, 546, 339, 1608, 723, 729, 1545, 438, 214, 657, 111, 759, 537, 209, 281, 685, 217, 757, 631, 293, 182, 475, 79, 407, 140, 366, 792, 269, 222, 158, 280, 188, 137, 158, 105, 189, 543, 160, 742, 1436, 88, 904, 146, 314, 375, 183, 1083, 195, 503, 269, 208, 334, 350, 460, 568, 229, 1155, 334, 266, 30, 120, 309, 370, 358, 1952, 1638, 304, 153, 274, 1288, 192, 543, 74, 144, 137, 33, 336, 457, 2122, 535, 711, 1323, 117, 1961, 244, 557, 318, 119, 124, 108, 144, 96, 133, 441, 210, 1627, 1956, 693, 1426, 863, 637, 301, 51, 708, 583, 119, 600, 221, 370, 728, 1085, 552, 629, 125, 1746, 97, 441, 543, 2756, 371, 447, 548, 243, 266, 217, 99, 440, 183, 546, 137, 464, 207, 983, 171, 992, 503, 237, 382, 249, 675, 402, 254, 223, 164, 214, 191, 831, 218, 202, 232, 124, 249, 160, 251, 217, 717, 78, 561, 1627, 256, 386, 225, 432, 499, 394, 364, 445, 634, 667, 177, 346, 470, 663, 142, 588, 414, 617, 1559, 380, 2520, 1040, 417, 289, 238, 220, 214, 303, 163, 141, 510, 397, 137, 137, 133, 133, 390, 266, 137, 183, 215, 191, 485, 328, 575, 799, 533, 148, 215, 589, 589, 130, 362, 562, 471, 179, 186, 1266, 1456, 521, 1536, 427, 444, 406, 912, 150, 283, 144, 485, 470, 1787, 205, 1268, 796, 658, 306, 3751, 321, 331, 814, 146, 2328, 1226, 922, 237, 206, 198, 250, 283, 60, 3000],
3
- "dependencies": {
4
- "Array.prototype.at": [0, 5, 69, 105, 106, 116, 166],
5
- "Array.prototype.concat": [0, 16, 21, 22, 26, 34, 40, 76, 78, 157, 167],
6
- "Array.prototype.copyWithin": [0, 5, 9, 37, 69, 105, 106, 116, 168],
7
- "Array.prototype.every": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 157, 169],
8
- "Array.prototype.fill": [0, 5, 10, 69, 105, 106, 116, 170],
9
- "Array.prototype.filter": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 157, 171],
10
- "Array.prototype.find": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 157, 175],
11
- "Array.prototype.findIndex": [0, 5, 15, 21, 22, 26, 53, 59, 69, 76, 78, 105, 106, 116, 157, 172],
12
- "Array.prototype.findLast": [0, 5, 14, 53, 59, 69, 105, 106, 116, 174],
13
- "Array.prototype.findLastIndex": [0, 5, 14, 53, 59, 69, 105, 106, 116, 173],
14
- "Array.prototype.flat": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 157, 177],
15
- "Array.prototype.flatMap": [0, 21, 22, 26, 40, 50, 53, 59, 76, 78, 157, 176],
16
- "Array.prototype.forEach": [0, 11, 15, 17, 21, 22, 26, 53, 59, 76, 78, 157, 178],
17
- "Array.from": [0, 12, 23, 24, 26, 34, 53, 59, 62, 63, 75, 78, 88, 157, 179],
18
- "Array.prototype.includes": [0, 5, 69, 105, 106, 116, 180],
19
- "Array.prototype.indexOf": [0, 17, 59, 181],
20
- "Array.isArray": [0, 76, 182],
21
- "Array.prototype.join": [0, 17, 183],
22
- "Array.prototype.map": [0, 15, 16, 21, 22, 26, 53, 59, 76, 78, 157, 184],
23
- "Array.of": [0, 26, 34, 78, 157, 185],
24
- "Array.prototype.slice": [0, 16, 19, 26, 34, 76, 78, 157, 186],
25
- "Array.prototype.some": [0, 15, 17, 21, 22, 26, 53, 59, 76, 78, 157, 187],
26
- "Array.prototype.sort": [0, 17, 19, 20, 26, 37, 42, 43, 46, 157, 158, 188],
27
- "Array.prototype.unshift": [0, 18, 37, 40, 76, 189],
28
- "Math.acosh": [0, 97, 190],
29
- "Math.asinh": [0, 191],
30
- "Math.atanh": [0, 192],
31
- "Math.cbrt": [0, 100, 193],
32
- "Math.clz32": [0, 194],
33
- "Math.cosh": [0, 93, 195],
34
- "Math.expm1": [0, 93, 196],
35
- "Math.fround": [0, 94, 95, 99, 100, 197],
36
- "Math.hypot": [0, 198],
37
- "Math.imul": [0, 199],
38
- "Math.log10": [0, 96, 200],
39
- "Math.log1p": [0, 97, 201],
40
- "Math.log2": [0, 98, 202],
41
- "Math.sign": [0, 100, 203],
42
- "Math.sinh": [0, 93, 204],
43
- "Math.tanh": [0, 93, 205],
44
- "Math.trunc": [0, 206],
45
- "Object.assign": [0, 104, 116, 207],
46
- "Object.create": [0, 69, 105, 106, 116, 208],
47
- "Object.entries": [0, 29, 112, 116, 119, 209],
48
- "Object.freeze": [0, 8, 19, 51, 73, 109, 113, 210],
49
- "Object.fromEntries": [0, 26, 34, 53, 59, 62, 63, 75, 87, 88, 157, 211],
50
- "Object.getOwnPropertyDescriptor": [0, 212],
51
- "Object.getOwnPropertyDescriptors": [0, 34, 213],
52
- "Object.getPrototypeOf": [0, 29, 112, 214],
53
- "Object.hasOwn": [0, 215],
54
- "Object.is": [0, 134, 219],
55
- "Object.isExtensible": [0, 8, 113, 216],
56
- "Object.isFrozen": [0, 8, 217],
57
- "Object.isSealed": [0, 8, 218],
58
- "Object.keys": [0, 116, 220],
59
- "Object.preventExtensions": [0, 8, 19, 51, 73, 109, 113, 221],
60
- "Object.seal": [0, 8, 19, 51, 73, 109, 113, 222],
61
- "Object.setPrototypeOf": [0, 4, 58, 83, 118, 223],
62
- "Object.values": [0, 29, 112, 116, 119, 224],
63
- "Promise.any": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 157, 226],
64
- "Reflect.apply": [0, 52, 227],
65
- "Reflect.construct": [0, 3, 19, 26, 52, 55, 69, 78, 105, 106, 116, 157, 228],
66
- "Reflect.deleteProperty": [0, 229],
67
- "Reflect.get": [0, 29, 79, 112, 232],
68
- "Reflect.getOwnPropertyDescriptor": [0, 230],
69
- "Reflect.getPrototypeOf": [0, 29, 112, 231],
70
- "Reflect.has": [0, 233],
71
- "Reflect.isExtensible": [0, 8, 113, 234],
72
- "Reflect.ownKeys": [0, 235],
73
- "Reflect.preventExtensions": [0, 51, 236],
74
- "Reflect.set": [0, 29, 79, 112, 238],
75
- "Reflect.setPrototypeOf": [0, 4, 58, 83, 118, 237],
76
- "String.prototype.codePointAt": [0, 26, 141, 157, 158, 239],
77
- "String.prototype.endsWith": [0, 26, 28, 59, 85, 103, 157, 158, 240],
78
- "String.fromCodePoint": [0, 241],
79
- "String.prototype.includes": [0, 26, 28, 85, 103, 157, 158, 242],
80
- "String.prototype.matchAll": [0, 3, 6, 26, 29, 31, 59, 69, 78, 85, 89, 90, 105, 106, 112, 116, 126, 127, 128, 129, 130, 131, 132, 135, 139, 141, 157, 158, 244],
81
- "String.prototype.padEnd": [0, 26, 142, 143, 144, 157, 158, 245],
82
- "String.prototype.padStart": [0, 26, 142, 143, 144, 157, 158, 246],
83
- "String.raw": [0, 26, 157, 158, 247],
84
- "String.prototype.repeat": [0, 26, 144, 157, 158, 248],
85
- "String.prototype.replaceAll": [0, 26, 65, 85, 128, 129, 157, 158, 249],
86
- "String.prototype.startsWith": [0, 26, 28, 59, 85, 103, 157, 158, 250],
87
- "String.prototype.substr": [0, 26, 157, 158, 251],
88
- "String.prototype.trim": [0, 26, 146, 148, 157, 158, 165, 256],
89
- "String.prototype.trimEnd": [0, 26, 145, 146, 148, 157, 158, 165, 252, 254],
90
- "String.prototype.trimStart": [0, 26, 146, 147, 148, 157, 158, 165, 253, 255],
91
- "String.prototype.link": [0, 26, 30, 140, 157, 158, 243],
92
- "Promise.allSettled": [0, 24, 26, 47, 53, 59, 62, 63, 75, 87, 88, 102, 122, 123, 124, 125, 157, 225, 257],
93
- "focus-visible": [258]
94
- },
95
- "maxSize": 159437
96
- }