lighthouse 13.5.0-dev.20261006 → 13.5.0-dev.20261008

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 (59) hide show
  1. package/core/audits/insights/insight-audit.d.ts +9 -0
  2. package/core/audits/insights/insight-audit.js +2 -1
  3. package/core/computed/trace-engine-result.js +3 -1
  4. package/core/config/constants.d.ts +2 -28
  5. package/core/lib/legacy-javascript/legacy-javascript.d.ts +1 -0
  6. package/core/lib/legacy-javascript/legacy-javascript.js +234 -110
  7. package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
  8. package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
  9. package/core/lib/minification-estimator.js +10 -10
  10. package/core/lib/trace-engine.d.ts +1 -0
  11. package/core/lib/trace-engine.js +2 -0
  12. package/package.json +2 -2
  13. package/shared/localization/locales/ar-XB.json +157 -136
  14. package/shared/localization/locales/ar.json +157 -136
  15. package/shared/localization/locales/bg.json +154 -133
  16. package/shared/localization/locales/ca.json +157 -136
  17. package/shared/localization/locales/cs.json +148 -127
  18. package/shared/localization/locales/da.json +153 -132
  19. package/shared/localization/locales/de.json +152 -131
  20. package/shared/localization/locales/el.json +153 -132
  21. package/shared/localization/locales/en-GB.json +160 -139
  22. package/shared/localization/locales/en-US.json +153 -141
  23. package/shared/localization/locales/en-XL.json +153 -141
  24. package/shared/localization/locales/es-419.json +150 -129
  25. package/shared/localization/locales/es.json +149 -128
  26. package/shared/localization/locales/fi.json +151 -130
  27. package/shared/localization/locales/fil.json +154 -133
  28. package/shared/localization/locales/fr.json +150 -129
  29. package/shared/localization/locales/he.json +160 -139
  30. package/shared/localization/locales/hi.json +156 -135
  31. package/shared/localization/locales/hr.json +154 -133
  32. package/shared/localization/locales/hu.json +111 -90
  33. package/shared/localization/locales/id.json +153 -132
  34. package/shared/localization/locales/it.json +153 -132
  35. package/shared/localization/locales/ja.json +150 -129
  36. package/shared/localization/locales/ko.json +42 -21
  37. package/shared/localization/locales/lt.json +152 -131
  38. package/shared/localization/locales/lv.json +93 -72
  39. package/shared/localization/locales/nl.json +149 -128
  40. package/shared/localization/locales/no.json +152 -131
  41. package/shared/localization/locales/pl.json +153 -132
  42. package/shared/localization/locales/pt-PT.json +152 -131
  43. package/shared/localization/locales/pt.json +157 -136
  44. package/shared/localization/locales/ro.json +149 -128
  45. package/shared/localization/locales/ru.json +73 -52
  46. package/shared/localization/locales/sk.json +153 -132
  47. package/shared/localization/locales/sl.json +149 -128
  48. package/shared/localization/locales/sr-Latn.json +151 -130
  49. package/shared/localization/locales/sr.json +151 -130
  50. package/shared/localization/locales/sv.json +152 -131
  51. package/shared/localization/locales/ta.json +156 -135
  52. package/shared/localization/locales/te.json +157 -136
  53. package/shared/localization/locales/th.json +111 -90
  54. package/shared/localization/locales/tr.json +153 -132
  55. package/shared/localization/locales/uk.json +149 -128
  56. package/shared/localization/locales/vi.json +162 -141
  57. package/shared/localization/locales/zh-HK.json +150 -129
  58. package/shared/localization/locales/zh-TW.json +154 -133
  59. package/shared/localization/locales/zh.json +152 -131
@@ -21,6 +21,15 @@ export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/tr
21
21
  numericValue?: number;
22
22
  numericUnit?: LH.Audit.NumericProduct["numericUnit"];
23
23
  } | LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
24
+ /**
25
+ * @param {LH.Artifacts} artifacts
26
+ * @param {LH.Audit.Context} context
27
+ * @return {Promise<{insights: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet|undefined, data: LH.Artifacts.TraceEngineResult['data']}>}
28
+ */
29
+ export function getInsightSet(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<{
30
+ insights: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightSet | undefined;
31
+ data: LH.Artifacts.TraceEngineResult["data"];
32
+ }>;
24
33
  /**
25
34
  * @param {LH.Artifacts.TraceElement[]} traceElements
26
35
  * @param {number|null|undefined} nodeId
@@ -29,7 +29,7 @@ async function getInsightSet(artifacts, context) {
29
29
  const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
30
30
  const insights = navigationId ?
31
31
  [...traceEngineResult.insights.values()]
32
- .find(insightSet => insightSet.navigation?.args.data?.navigationId) :
32
+ .find(insightSet => insightSet.navigation?.args.data?.navigationId === navigationId) :
33
33
  traceEngineResult.insights.get(NO_NAVIGATION);
34
34
 
35
35
  return {insights, data: traceEngineResult.data};
@@ -225,5 +225,6 @@ function makeNodeItemForNodeId(traceElements, nodeId) {
225
225
 
226
226
  export {
227
227
  adaptInsightToAuditProduct,
228
+ getInsightSet,
228
229
  makeNodeItemForNodeId,
229
230
  };
@@ -26,7 +26,9 @@ class TraceEngineResult {
26
26
  * @return {Promise<LH.Artifacts.TraceEngineResult>}
27
27
  */
28
28
  static async runTraceEngine(_traceEvents, settings, SourceMaps, HostDPR) {
29
- const processor = new TraceEngine.TraceProcessor(TraceEngine.TraceHandlers);
29
+ const config = TraceEngine.Types.Configuration.defaults();
30
+ config.enableSoftNavigation = false;
31
+ const processor = new TraceEngine.TraceProcessor(TraceEngine.TraceHandlers, config);
30
32
  const traceEvents =
31
33
  /** @type {import('@paulirish/trace_engine').Types.Events.Event[]} */ (_traceEvents);
32
34
 
@@ -1,31 +1,4 @@
1
- export const throttling: {
2
- DEVTOOLS_RTT_ADJUSTMENT_FACTOR: number;
3
- DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR: number;
4
- mobileSlow4G: {
5
- rttMs: number;
6
- throughputKbps: number;
7
- requestLatencyMs: number;
8
- downloadThroughputKbps: number;
9
- uploadThroughputKbps: number;
10
- cpuSlowdownMultiplier: number;
11
- };
12
- mobileRegular3G: {
13
- rttMs: number;
14
- throughputKbps: number;
15
- requestLatencyMs: number;
16
- downloadThroughputKbps: number;
17
- uploadThroughputKbps: number;
18
- cpuSlowdownMultiplier: number;
19
- };
20
- desktopDense4G: {
21
- rttMs: number;
22
- throughputKbps: number;
23
- cpuSlowdownMultiplier: number;
24
- requestLatencyMs: number;
25
- downloadThroughputKbps: number;
26
- uploadThroughputKbps: number;
27
- };
28
- };
1
+ export const throttling: Lantern.Simulation.ThrottlingSettings;
29
2
  export namespace screenEmulationMetrics {
30
3
  export { MOTOGPOWER_EMULATION_METRICS as mobile };
31
4
  export { DESKTOP_EMULATION_METRICS as desktop };
@@ -42,6 +15,7 @@ export namespace nonSimulatedSettingsOverrides {
42
15
  let networkQuietThresholdMs: number;
43
16
  let cpuQuietThresholdMs: number;
44
17
  }
18
+ import * as Lantern from '../lib/lantern/lantern.js';
45
19
  /**
46
20
  * @type {Required<LH.SharedFlagsSettings['screenEmulation']>}
47
21
  */
@@ -2,6 +2,7 @@ export type Pattern = {
2
2
  name: string;
3
3
  expression: string;
4
4
  estimateBytes?: (content: string) => number;
5
+ resolveName?: (match: string) => string;
5
6
  };
6
7
  export type PatternMatchResult = {
7
8
  name: string;
@@ -11,7 +11,7 @@
11
11
  * ./core/scripts/legacy-javascript - verification tool.
12
12
  */
13
13
 
14
- /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number}} Pattern */
14
+ /** @typedef {{name: string, expression: string, estimateBytes?: (content: string) => number, resolveName?: (match: string) => string}} Pattern */
15
15
  /** @typedef {{name: string, line: number, column: number}} PatternMatchResult */
16
16
  /** @typedef {{matches: PatternMatchResult[], estimatedByteSavings: number}} Result */
17
17
 
@@ -27,7 +27,7 @@ const graph = graph_;
27
27
  /**
28
28
  * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
29
29
  * and via `match` returns match results with line / column information for a given code input.
30
- * Only returns the first match per pattern given.
30
+ * Only returns the first match per pattern name.
31
31
  */
32
32
  class CodePatternMatcher {
33
33
  /**
@@ -45,12 +45,13 @@ class CodePatternMatcher {
45
45
  if (!this.re) {
46
46
  const patternsExpression =
47
47
  this.patterns.map(pattern => `(${pattern.expression})`).join('|');
48
- this.re = new RegExp(`(^\r\n|\r|\n)|${patternsExpression}`, 'g');
48
+ this.re = new RegExp(patternsExpression, 'g');
49
49
  }
50
50
 
51
51
  // Reset RegExp state.
52
52
  this.re.lastIndex = 0;
53
53
 
54
+ /** @type {Set<string>} */
54
55
  const seen = new Set();
55
56
  /** @type {PatternMatchResult[]} */
56
57
  const matches = [];
@@ -58,29 +59,41 @@ class CodePatternMatcher {
58
59
  let result;
59
60
  let line = 0;
60
61
  let lineBeginsAtIndex = 0;
62
+ let scannedToIndex = 0;
61
63
  // Each pattern maps to one subgroup in the generated regex. For each iteration of RegExp.exec,
62
64
  // only one subgroup will be defined. Exec until no more matches.
63
65
  while ((result = this.re.exec(code)) !== null) {
64
- // Discard first value in `result` - it's just the entire match.
65
- const captureGroups = result.slice(1);
66
- // isNewline - truthy if matching a newline, used to track the line number.
67
- // `patternExpressionMatches` maps to each possible pattern in `this.patterns`.
68
- // Only one of [isNewline, ...patternExpressionMatches] is ever truthy.
69
- const [isNewline, ...patternExpressionMatches] = captureGroups;
70
- if (isNewline) {
71
- line++;
72
- lineBeginsAtIndex = result.index + 1;
73
- continue;
66
+ let patternIndex = -1;
67
+ for (let i = 1; i < result.length; i++) {
68
+ if (result[i] !== undefined) {
69
+ patternIndex = i - 1;
70
+ break;
71
+ }
74
72
  }
75
- const pattern = this.patterns[patternExpressionMatches.findIndex(Boolean)];
73
+ if (patternIndex === -1) continue;
76
74
 
77
- if (seen.has(pattern)) {
75
+ const pattern = this.patterns[patternIndex];
76
+ const name = pattern.resolveName ? pattern.resolveName(result[0]) : pattern.name;
77
+ if (seen.has(name)) {
78
78
  continue;
79
79
  }
80
- seen.add(pattern);
80
+ seen.add(name);
81
+
82
+ // Lazily advance line and column tracking up to `result.index`.
83
+ // Because `[scannedToIndex, result.index)` includes the text of any previous matches,
84
+ // this also accounts for newlines inside multi-line matches.
85
+ // Scanning for `\n` handles both LF (`\n`) and CRLF (`\r\n`) line endings since
86
+ // the next line always begins at `nlIdx + 1`.
87
+ let nlIdx = code.indexOf('\n', scannedToIndex);
88
+ while (nlIdx !== -1 && nlIdx < result.index) {
89
+ line++;
90
+ lineBeginsAtIndex = nlIdx + 1;
91
+ nlIdx = code.indexOf('\n', lineBeginsAtIndex);
92
+ }
93
+ scannedToIndex = result.index;
81
94
 
82
95
  matches.push({
83
- name: pattern.name,
96
+ name,
84
97
  line,
85
98
  column: result.index - lineBeginsAtIndex,
86
99
  });
@@ -90,70 +103,6 @@ class CodePatternMatcher {
90
103
  }
91
104
  }
92
105
 
93
- /**
94
- * @param {string?} object
95
- * @param {string} property
96
- * @param {string} coreJs3Module
97
- */
98
- function buildPolyfillExpression(object, property, coreJs3Module) {
99
- const qt = (/** @type {string} */ token) =>
100
- `['"]${token}['"]`; // don't worry about matching string delims
101
-
102
- let expression = '';
103
-
104
- if (object) {
105
- // String.prototype.startsWith =
106
- expression += `${object}\\.${property}\\s?=[^=]`;
107
- } else {
108
- // Promise =
109
- // window.Promise =// Promise =Z
110
- // but not: SomePromise =
111
- expression += `(?:window\\.|[\\s;]+)${property}\\s?=[^=]`;
112
- }
113
-
114
- // String.prototype['startsWith'] =
115
- if (object) {
116
- expression += `|${object}\\[${qt(property)}\\]\\s?=[^=]`;
117
- }
118
-
119
- // Object.defineProperty(String.prototype, 'startsWith'
120
- expression += `|defineProperty\\(${object || 'window'},\\s?${qt(property)}`;
121
-
122
- // es-shims
123
- // no(Object,{entries:r},{entries:function
124
- if (object) {
125
- expression += `|\\(${object},\\s*{${property}:.*},\\s*{${property}`;
126
- }
127
-
128
- // core-js
129
- if (object) {
130
- const objectWithoutPrototype = object.replace('.prototype', '');
131
- // e(e.S,"Object",{values
132
- // Minified + mangled pattern found in CDN babel-polyfill.
133
- // see https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js
134
- // TODO: perhaps this is the wrong place to check for a CDN polyfill. Remove?
135
- // expression += `|;e\\([^,]+,${qt(objectWithoutPrototype)},{${property}:`;
136
-
137
- // core-js@3 minified pattern.
138
- // {target:"Array",proto:true},{fill:fill
139
- // {target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:
140
- expression += `|{target:${qt(objectWithoutPrototype)}[^;]*},{${property}:`;
141
- } else {
142
- // Detect polyfills for new classes: Map, Set, WeakSet, etc.
143
- // TODO: so far, no class polyfills are enabled for detection.
144
- // See `modulesToSkip` in create-polyfill-module-data.js
145
-
146
- // collection("Map",
147
- // expression += `|collection\\(${qt(property)},`;
148
- }
149
-
150
- // Un-minified code may have module names.
151
- // core-js/modules/es.object.is-frozen
152
- expression += `|${coreJs3Module.replaceAll('.', '\\.')}(?:\\.js)?"`;
153
-
154
- return expression;
155
- }
156
-
157
106
  function getCoreJsPolyfillData() {
158
107
  return polyfillModuleData.filter(d => d.corejs).map(d => {
159
108
  return {
@@ -164,34 +113,171 @@ function getCoreJsPolyfillData() {
164
113
  }
165
114
 
166
115
  /**
116
+ * Builds factored polyfill patterns grouped by receiver object, core-js target, and
117
+ * core-js module prefix so the regex engine evaluates a few shared branches instead
118
+ * of repeating common prefixes (like `defineProperty(`, `{target:`, `Array.prototype.`)
119
+ * once per polyfill.
120
+ *
167
121
  * @return {Pattern[]}
168
122
  */
169
123
  function getPolyfillPatterns() {
170
- /** @type {Pattern[]} */
171
- const patterns = [];
124
+ /** @param {string} str */
125
+ const escapeRegExp = str => str.replaceAll('.', '\\.');
126
+
127
+ /** @type {Map<string, string[]>} */
128
+ const byObject = new Map();
129
+ /** @type {Map<string, string[]>} */
130
+ const byTarget = new Map();
131
+ /** @type {Map<string, string>} */
132
+ const targetPropToName = new Map();
133
+ /** @type {Map<string, string>} */
134
+ const coreJsModuleToName = new Map();
135
+ /** @type {Map<string, string[]>} */
136
+ const byModulePrefix = new Map();
172
137
 
173
138
  for (const {name, coreJs3Module} of getCoreJsPolyfillData()) {
174
139
  const parts = name.split('.');
175
- const object = parts.length > 1 ? parts.slice(0, parts.length - 1).join('.') : null;
140
+ const object = parts.slice(0, parts.length - 1).join('.');
176
141
  const property = parts[parts.length - 1];
177
- patterns.push({
178
- name,
179
- expression: buildPolyfillExpression(object, property, coreJs3Module),
180
- });
142
+
143
+ let objProps = byObject.get(object);
144
+ if (!objProps) {
145
+ objProps = [];
146
+ byObject.set(object, objProps);
147
+ }
148
+ objProps.push(property);
149
+
150
+ const target = object.replace('.prototype', '');
151
+ let targetProps = byTarget.get(target);
152
+ if (!targetProps) {
153
+ targetProps = [];
154
+ byTarget.set(target, targetProps);
155
+ }
156
+ targetProps.push(property);
157
+ targetPropToName.set(`${target}.${property}`, name);
158
+
159
+ coreJsModuleToName.set(coreJs3Module, name);
160
+ const modParts = coreJs3Module.split('.');
161
+ const modPrefix = modParts.slice(0, 2).join('.');
162
+ const modRest = modParts.slice(2).join('.');
163
+ let modRests = byModulePrefix.get(modPrefix);
164
+ if (!modRests) {
165
+ modRests = [];
166
+ byModulePrefix.set(modPrefix, modRests);
167
+ }
168
+ modRests.push(modRest);
169
+ }
170
+
171
+ // 1. Direct property assignment: `String.prototype.startsWith =`
172
+ const directBranches = [];
173
+ for (const [object, props] of byObject) {
174
+ directBranches.push(`${escapeRegExp(object)}\\.(?:${props.join('|')})\\s?=[^=]`);
175
+ }
176
+
177
+ // 2. Bracket property assignment: `String.prototype['startsWith'] =`
178
+ const bracketBranches = [];
179
+ for (const [object, props] of byObject) {
180
+ bracketBranches.push(`${escapeRegExp(object)}\\[['"](?:${props.join('|')})['"]\\]\\s?=[^=]`);
181
+ }
182
+
183
+ // 3. Object.defineProperty: `Object.defineProperty(String.prototype, 'startsWith'`
184
+ const definePropBranches = [];
185
+ for (const [object, props] of byObject) {
186
+ definePropBranches.push(`${escapeRegExp(object)},\\s?['"](?:${props.join('|')})['"]`);
187
+ }
188
+
189
+ // 4. es-shims: `no(Object,{entries:r},{entries:function`
190
+ const esShimsBranches = [];
191
+ for (const [object, props] of byObject) {
192
+ const propAlt = `(?:${props.join('|')})`;
193
+ esShimsBranches.push(`${escapeRegExp(object)},\\s*{${propAlt}:.*},\\s*{${propAlt}`);
194
+ }
195
+
196
+ // 5. core-js@3 minified pattern:
197
+ // `{target:"Array",proto:true},{fill:fill`
198
+ // `{target:"Array",proto:true,forced:!HAS_SPECIES_SUPPORT||!USES_TO_LENGTH},{filter:`
199
+ const targetBranches = [];
200
+ for (const [target, props] of byTarget) {
201
+ targetBranches.push(`['"]${escapeRegExp(target)}['"][^;]*?},{(?:${props.join('|')}):`);
202
+ }
203
+
204
+ // 6. Un-minified core-js module names: `core-js/modules/es.object.is-frozen`
205
+ const moduleBranches = [];
206
+ for (const [prefix, rests] of byModulePrefix) {
207
+ moduleBranches.push(
208
+ `${escapeRegExp(prefix)}\\.(?:${rests.map(escapeRegExp).join('|')})`
209
+ );
181
210
  }
182
211
 
183
- return patterns;
212
+ return [
213
+ {
214
+ name: 'direct-assignment',
215
+ expression: directBranches.join('|'),
216
+ resolveName: match => match.slice(0, match.indexOf('=')).trimEnd(),
217
+ },
218
+ {
219
+ name: 'bracket-assignment',
220
+ expression: bracketBranches.join('|'),
221
+ resolveName: match => {
222
+ const bracketIdx = match.indexOf('[');
223
+ const closeIdx = match.indexOf(']', bracketIdx);
224
+ return `${match.slice(0, bracketIdx)}.${match.slice(bracketIdx + 2, closeIdx - 1)}`;
225
+ },
226
+ },
227
+ {
228
+ name: 'defineProperty',
229
+ expression: `defineProperty\\((?:${definePropBranches.join('|')})`,
230
+ resolveName: match => {
231
+ const openIdx = match.indexOf('(');
232
+ const commaIdx = match.indexOf(',', openIdx);
233
+ const obj = match.slice(openIdx + 1, commaIdx);
234
+ const prop = match.slice(commaIdx + 1).trimStart().slice(1, -1);
235
+ return `${obj}.${prop}`;
236
+ },
237
+ },
238
+ {
239
+ name: 'es-shims',
240
+ expression: `\\((?:${esShimsBranches.join('|')})`,
241
+ resolveName: match => {
242
+ const commaIdx = match.indexOf(',');
243
+ const obj = match.slice(1, commaIdx);
244
+ const braceIdx = match.indexOf('{', commaIdx);
245
+ const colonIdx = match.indexOf(':', braceIdx);
246
+ const prop = match.slice(braceIdx + 1, colonIdx);
247
+ return `${obj}.${prop}`;
248
+ },
249
+ },
250
+ {
251
+ name: 'core-js-target',
252
+ expression: `{target:(?:${targetBranches.join('|')})`,
253
+ resolveName: match => {
254
+ const targetEnd = match.indexOf(match[8], 9);
255
+ const target = match.slice(9, targetEnd);
256
+ const lastBrace = match.lastIndexOf('{');
257
+ const prop = match.slice(lastBrace + 1, -1);
258
+ return /** @type {string} */ (targetPropToName.get(`${target}.${prop}`));
259
+ },
260
+ },
261
+ {
262
+ name: 'core-js-module',
263
+ expression: `(?:${moduleBranches.join('|')})(?:\\.js)?"`,
264
+ resolveName: match => {
265
+ const mod = match.endsWith('.js"') ? match.slice(0, -4) : match.slice(0, -1);
266
+ return /** @type {string} */ (coreJsModuleToName.get(mod));
267
+ },
268
+ },
269
+ ];
184
270
  }
185
271
 
186
272
  /**
187
273
  * @return {Pattern[]}
188
274
  */
189
275
  function getTransformPatterns() {
190
- /**
191
- * @param {string} content
192
- * @param {RegExp|string} pattern
193
- * @return {number}
194
- */
276
+ /**
277
+ * @param {string} content
278
+ * @param {RegExp|string} pattern
279
+ * @return {number}
280
+ */
195
281
  const count = (content, pattern) => {
196
282
  // Split is slightly faster than match.
197
283
  if (typeof pattern === 'string') {
@@ -259,6 +345,17 @@ function getTransformPatterns() {
259
345
  ];
260
346
  }
261
347
 
348
+ const transformPatterns = getTransformPatterns();
349
+ const transformPatternsByName = new Map(transformPatterns.map(p => [p.name, p]));
350
+
351
+ /** @type {Map<string, string>} */
352
+ const moduleToPolyfillName = new Map();
353
+ for (const {name, modules} of polyfillModuleData) {
354
+ for (const mod of modules) {
355
+ moduleToPolyfillName.set(mod, name);
356
+ }
357
+ }
358
+
262
359
  /**
263
360
  * @param {string} content
264
361
  * @param {PatternMatchResult[]} matches
@@ -287,7 +384,7 @@ function estimateWastedBytes(content, matches) {
287
384
  let estimatedWastedBytesFromTransforms = 0;
288
385
 
289
386
  for (const result of transformResults) {
290
- const pattern = getTransformPatterns().find(p => p.name === result.name);
387
+ const pattern = transformPatternsByName.get(result.name);
291
388
  if (!pattern || !pattern.estimateBytes || !content) continue;
292
389
  estimatedWastedBytesFromTransforms += pattern.estimateBytes(content);
293
390
  }
@@ -299,7 +396,7 @@ function estimateWastedBytes(content, matches) {
299
396
 
300
397
  const matcher = new CodePatternMatcher([
301
398
  ...getPolyfillPatterns(),
302
- ...getTransformPatterns(),
399
+ ...transformPatterns,
303
400
  ]);
304
401
 
305
402
  /**
@@ -315,20 +412,47 @@ function detectLegacyJavaScript(content, map) {
315
412
 
316
413
  // If it's a bundle with source maps, add in the polyfill modules by name too.
317
414
  if (map) {
318
- for (const {name, modules} of polyfillModuleData) {
319
- // Skip if the pattern matching found a match for this polyfill.
320
- if (matches.some(m => m.name === name)) continue;
321
-
322
- const source = map.sourceURLs().find(source => modules.some(module => {
323
- return source.endsWith(`/${module}.js`) || source.includes(`node_modules/${module}/`);
324
- }));
325
- if (!source) continue;
326
-
327
- const mapping = map.mappings().find(m => m.sourceURL === source);
328
- if (mapping) {
329
- matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
330
- } else {
331
- matches.push({name, line: 0, column: 0});
415
+ const matchedNames = new Set(matches.map(m => m.name));
416
+ /** @type {Array<{name: string, source: string}>} */
417
+ const foundInMap = [];
418
+
419
+ for (const source of map.sourceURLs()) {
420
+ if (source.endsWith('.js')) {
421
+ const slashIdx = source.lastIndexOf('/');
422
+ if (slashIdx !== -1) {
423
+ const polyfillName = moduleToPolyfillName.get(source.slice(slashIdx + 1, -3));
424
+ if (polyfillName && !matchedNames.has(polyfillName)) {
425
+ matchedNames.add(polyfillName);
426
+ foundInMap.push({name: polyfillName, source});
427
+ }
428
+ }
429
+ }
430
+ if (source.includes('node_modules/')) {
431
+ let nmIdx = source.indexOf('node_modules/');
432
+ while (nmIdx !== -1) {
433
+ const modStart = nmIdx + 13; // 'node_modules/'.length
434
+ const nextSlash = source.indexOf('/', modStart);
435
+ if (nextSlash !== -1) {
436
+ const polyfillName = moduleToPolyfillName.get(source.slice(modStart, nextSlash));
437
+ if (polyfillName && !matchedNames.has(polyfillName)) {
438
+ matchedNames.add(polyfillName);
439
+ foundInMap.push({name: polyfillName, source});
440
+ }
441
+ }
442
+ nmIdx = source.indexOf('node_modules/', modStart);
443
+ }
444
+ }
445
+ }
446
+
447
+ if (foundInMap.length > 0) {
448
+ const mappings = map.mappings();
449
+ for (const {name, source} of foundInMap) {
450
+ const mapping = mappings.find(m => m.sourceURL === source);
451
+ if (mapping) {
452
+ matches.push({name, line: mapping.lineNumber, column: mapping.columnNumber});
453
+ } else {
454
+ matches.push({name, line: 0, column: 0});
455
+ }
332
456
  }
333
457
  }
334
458
  }