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.
- package/core/audits/audit.js +2 -1
- package/core/audits/bootup-time.js +1 -1
- package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -62
- package/core/audits/byte-efficiency/legacy-javascript.js +9 -357
- package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
- package/core/audits/byte-efficiency/offscreen-images.js +3 -3
- package/core/audits/byte-efficiency/render-blocking-resources.js +5 -3
- package/core/audits/byte-efficiency/unminified-css.js +2 -1
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -1
- package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
- package/core/audits/byte-efficiency/unused-javascript.js +1 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +1 -1
- package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
- package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
- package/core/audits/byte-efficiency/uses-text-compression.js +2 -1
- package/core/audits/critical-request-chains.js +4 -3
- package/core/audits/dobetterweb/dom-size.js +1 -1
- package/core/audits/dobetterweb/uses-http2.js +1 -1
- package/core/audits/insights/cls-culprits-insight.js +1 -1
- package/core/audits/insights/document-latency-insight.js +1 -1
- package/core/audits/insights/dom-size-insight.js +1 -1
- package/core/audits/insights/duplicated-javascript-insight.d.ts +11 -0
- package/core/audits/insights/duplicated-javascript-insight.js +54 -0
- package/core/audits/insights/font-display-insight.js +1 -1
- package/core/audits/insights/forced-reflow-insight.d.ts +10 -0
- package/core/audits/insights/forced-reflow-insight.js +64 -12
- package/core/audits/insights/image-delivery-insight.js +1 -1
- package/core/audits/insights/insight-audit.js +2 -1
- package/core/audits/insights/interaction-to-next-paint-insight.js +1 -1
- package/core/audits/insights/lcp-discovery-insight.js +1 -1
- package/core/audits/insights/lcp-phases-insight.js +1 -1
- package/core/audits/insights/network-dependency-tree-insight.js +1 -1
- package/core/audits/insights/render-blocking-insight.js +1 -1
- package/core/audits/insights/slow-css-selector-insight.js +1 -1
- package/core/audits/insights/third-parties-insight.js +1 -1
- package/core/audits/insights/{duplicate-javascript-insight.d.ts → use-cache-insight.d.ts} +3 -3
- package/core/audits/insights/{duplicate-javascript-insight.js → use-cache-insight.js} +6 -6
- package/core/audits/insights/viewport-insight.js +1 -1
- package/core/audits/largest-contentful-paint-element.js +3 -2
- package/core/audits/layout-shifts.js +4 -2
- package/core/audits/lcp-lazy-loaded.js +1 -1
- package/core/audits/long-tasks.js +4 -4
- package/core/audits/mainthread-work-breakdown.js +1 -1
- package/core/audits/metrics/first-contentful-paint.js +2 -2
- package/core/audits/metrics/interactive.js +2 -2
- package/core/audits/metrics/largest-contentful-paint.js +3 -2
- package/core/audits/metrics/max-potential-fid.js +2 -2
- package/core/audits/metrics/speed-index.js +2 -2
- package/core/audits/metrics/total-blocking-time.js +2 -2
- package/core/audits/metrics.js +4 -3
- package/core/audits/predictive-perf.js +3 -3
- package/core/audits/prioritize-lcp-image.js +4 -3
- package/core/audits/redirects.js +3 -2
- package/core/audits/script-treemap-data.js +1 -1
- package/core/audits/third-party-facades.js +1 -1
- package/core/audits/third-party-summary.js +1 -1
- package/core/audits/uses-rel-preconnect.js +6 -5
- package/core/audits/uses-rel-preload.js +3 -3
- package/core/computed/critical-request-chains.d.ts +3 -1
- package/core/computed/critical-request-chains.js +3 -3
- package/core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
- package/core/computed/metrics/first-contentful-paint.js +1 -1
- package/core/computed/metrics/interactive.js +1 -1
- package/core/computed/metrics/lantern-first-contentful-paint.js +1 -1
- package/core/computed/metrics/lantern-interactive.js +1 -1
- package/core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
- package/core/computed/metrics/lantern-max-potential-fid.js +1 -1
- package/core/computed/metrics/lantern-speed-index.js +1 -1
- package/core/computed/metrics/lantern-total-blocking-time.js +1 -1
- package/core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
- package/core/computed/metrics/largest-contentful-paint.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +1 -1
- package/core/computed/metrics/max-potential-fid.js +1 -1
- package/core/computed/metrics/metric.js +1 -0
- package/core/computed/metrics/speed-index.js +1 -1
- package/core/computed/metrics/time-to-first-byte.js +1 -1
- package/core/computed/metrics/timing-summary.d.ts +5 -2
- package/core/computed/metrics/timing-summary.js +6 -4
- package/core/computed/metrics/total-blocking-time.js +1 -1
- package/core/computed/navigation-insights.d.ts +3 -1
- package/core/computed/navigation-insights.js +6 -4
- package/core/computed/page-dependency-graph.d.ts +3 -1
- package/core/computed/page-dependency-graph.js +5 -4
- package/core/computed/tbt-impact-tasks.js +1 -1
- package/core/computed/trace-engine-result.d.ts +5 -2
- package/core/computed/trace-engine-result.js +18 -4
- package/core/config/default-config.js +4 -2
- package/core/config/experimental-config.js +2 -1
- package/core/gather/gatherers/source-maps.d.ts +1 -0
- package/core/gather/gatherers/source-maps.js +3 -0
- package/core/gather/gatherers/trace-elements.d.ts +4 -4
- package/core/gather/gatherers/trace-elements.js +7 -4
- package/core/gather/gatherers/trace.js +5 -0
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +29 -0
- package/core/lib/legacy-javascript/legacy-javascript.js +351 -0
- package/package.json +3 -2
- package/shared/localization/locales/ar-XB.json +235 -1
- package/shared/localization/locales/ar.json +235 -7
- package/shared/localization/locales/bg.json +235 -7
- package/shared/localization/locales/ca.json +235 -7
- package/shared/localization/locales/cs.json +234 -6
- package/shared/localization/locales/da.json +236 -8
- package/shared/localization/locales/de.json +234 -6
- package/shared/localization/locales/el.json +235 -7
- package/shared/localization/locales/en-GB.json +235 -19
- package/shared/localization/locales/en-US.json +30 -3
- package/shared/localization/locales/en-XA.json +0 -6
- package/shared/localization/locales/en-XL.json +30 -3
- package/shared/localization/locales/es-419.json +235 -7
- package/shared/localization/locales/es.json +234 -6
- package/shared/localization/locales/fi.json +235 -7
- package/shared/localization/locales/fil.json +237 -3
- package/shared/localization/locales/fr.json +236 -8
- package/shared/localization/locales/he.json +234 -0
- package/shared/localization/locales/hi.json +236 -2
- package/shared/localization/locales/hr.json +235 -7
- package/shared/localization/locales/hu.json +235 -7
- package/shared/localization/locales/id.json +234 -0
- package/shared/localization/locales/it.json +238 -10
- package/shared/localization/locales/ja.json +234 -6
- package/shared/localization/locales/ko.json +235 -16
- package/shared/localization/locales/lt.json +235 -7
- package/shared/localization/locales/lv.json +236 -8
- package/shared/localization/locales/nl.json +235 -4
- package/shared/localization/locales/no.json +235 -7
- package/shared/localization/locales/pl.json +236 -8
- package/shared/localization/locales/pt-PT.json +235 -1
- package/shared/localization/locales/pt.json +235 -1
- package/shared/localization/locales/ro.json +234 -0
- package/shared/localization/locales/ru.json +235 -1
- package/shared/localization/locales/sk.json +235 -1
- package/shared/localization/locales/sl.json +236 -2
- package/shared/localization/locales/sr-Latn.json +235 -1
- package/shared/localization/locales/sr.json +235 -1
- package/shared/localization/locales/sv.json +235 -1
- package/shared/localization/locales/ta.json +235 -1
- package/shared/localization/locales/te.json +237 -3
- package/shared/localization/locales/th.json +235 -1
- package/shared/localization/locales/tr.json +236 -2
- package/shared/localization/locales/uk.json +235 -1
- package/shared/localization/locales/vi.json +236 -2
- package/shared/localization/locales/zh-HK.json +235 -1
- package/shared/localization/locales/zh-TW.json +236 -2
- package/shared/localization/locales/zh.json +234 -0
- package/tsconfig.json +2 -1
- package/types/artifacts.d.ts +1 -0
- /package/core/{audits/byte-efficiency → lib/legacy-javascript}/polyfill-graph-data.json +0 -0
- /package/core/{audits/byte-efficiency → lib/legacy-javascript}/polyfill-module-data.json +0 -0
|
@@ -3,6 +3,7 @@ declare const TraceEngineResultComputed: typeof TraceEngineResult & {
|
|
|
3
3
|
request: (dependencies: {
|
|
4
4
|
trace: LH.Trace;
|
|
5
5
|
settings: LH.Audit.Context["settings"];
|
|
6
|
+
SourceMaps: LH.Artifacts["SourceMaps"];
|
|
6
7
|
}, context: LH.Artifacts.ComputedContext) => ReturnType<typeof TraceEngineResult.compute_>;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
@@ -12,9 +13,10 @@ declare class TraceEngineResult {
|
|
|
12
13
|
/**
|
|
13
14
|
* @param {LH.TraceEvent[]} traceEvents
|
|
14
15
|
* @param {LH.Audit.Context['settings']} settings
|
|
16
|
+
* @param {LH.Artifacts['SourceMaps']} SourceMaps
|
|
15
17
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
16
18
|
*/
|
|
17
|
-
static runTraceEngine(traceEvents: LH.TraceEvent[], settings: LH.Audit.Context["settings"]): Promise<LH.Artifacts.TraceEngineResult>;
|
|
19
|
+
static runTraceEngine(traceEvents: LH.TraceEvent[], settings: LH.Audit.Context["settings"], SourceMaps: LH.Artifacts["SourceMaps"]): Promise<LH.Artifacts.TraceEngineResult>;
|
|
18
20
|
/**
|
|
19
21
|
* Adapts the given DevTools function that returns a localized string to one
|
|
20
22
|
* that returns a LH.IcuMessage.
|
|
@@ -47,13 +49,14 @@ declare class TraceEngineResult {
|
|
|
47
49
|
*/
|
|
48
50
|
static localizeInsights(insightSets: import("@paulirish/trace_engine/models/trace/insights/types.js").TraceInsightSets): void;
|
|
49
51
|
/**
|
|
50
|
-
* @param {{trace: LH.Trace, settings: LH.Audit.Context['settings']}} data
|
|
52
|
+
* @param {{trace: LH.Trace, settings: LH.Audit.Context['settings'], SourceMaps: LH.Artifacts['SourceMaps']}} data
|
|
51
53
|
* @param {LH.Artifacts.ComputedContext} context
|
|
52
54
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
53
55
|
*/
|
|
54
56
|
static compute_(data: {
|
|
55
57
|
trace: LH.Trace;
|
|
56
58
|
settings: LH.Audit.Context["settings"];
|
|
59
|
+
SourceMaps: LH.Artifacts["SourceMaps"];
|
|
57
60
|
}, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.TraceEngineResult>;
|
|
58
61
|
}
|
|
59
62
|
import * as LH from '../../types/lh.js';
|
|
@@ -11,6 +11,7 @@ import * as TraceEngine from '../lib/trace-engine.js';
|
|
|
11
11
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
12
12
|
import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js';
|
|
13
13
|
import {ProcessedTrace} from './processed-trace.js';
|
|
14
|
+
import SDK from '../lib/cdt/SDK.js';
|
|
14
15
|
import * as LH from '../../types/lh.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -20,9 +21,10 @@ class TraceEngineResult {
|
|
|
20
21
|
/**
|
|
21
22
|
* @param {LH.TraceEvent[]} traceEvents
|
|
22
23
|
* @param {LH.Audit.Context['settings']} settings
|
|
24
|
+
* @param {LH.Artifacts['SourceMaps']} SourceMaps
|
|
23
25
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
24
26
|
*/
|
|
25
|
-
static async runTraceEngine(traceEvents, settings) {
|
|
27
|
+
static async runTraceEngine(traceEvents, settings, SourceMaps) {
|
|
26
28
|
const processor = new TraceEngine.TraceProcessor(TraceEngine.TraceHandlers);
|
|
27
29
|
|
|
28
30
|
const lanternSettings = {};
|
|
@@ -47,6 +49,16 @@ class TraceEngineResult {
|
|
|
47
49
|
},
|
|
48
50
|
},
|
|
49
51
|
lanternSettings,
|
|
52
|
+
async resolveSourceMap(params) {
|
|
53
|
+
const sourceMap = SourceMaps.find(sm => sm.scriptId === params.scriptId);
|
|
54
|
+
if (!sourceMap) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const compiledUrl = sourceMap.scriptUrl || 'compiled.js';
|
|
59
|
+
const mapUrl = sourceMap.sourceMapUrl || 'compiled.js.map';
|
|
60
|
+
return new SDK.SourceMap(compiledUrl, mapUrl, sourceMap.map);
|
|
61
|
+
},
|
|
50
62
|
});
|
|
51
63
|
if (!processor.parsedTrace) throw new Error('No data');
|
|
52
64
|
if (!processor.insights) throw new Error('No insights');
|
|
@@ -199,7 +211,7 @@ class TraceEngineResult {
|
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
/**
|
|
202
|
-
* @param {{trace: LH.Trace, settings: LH.Audit.Context['settings']}} data
|
|
214
|
+
* @param {{trace: LH.Trace, settings: LH.Audit.Context['settings'], SourceMaps: LH.Artifacts['SourceMaps']}} data
|
|
203
215
|
* @param {LH.Artifacts.ComputedContext} context
|
|
204
216
|
* @return {Promise<LH.Artifacts.TraceEngineResult>}
|
|
205
217
|
*/
|
|
@@ -229,10 +241,12 @@ class TraceEngineResult {
|
|
|
229
241
|
}
|
|
230
242
|
}
|
|
231
243
|
|
|
232
|
-
const result =
|
|
244
|
+
const result =
|
|
245
|
+
await TraceEngineResult.runTraceEngine(traceEvents, data.settings, data.SourceMaps);
|
|
233
246
|
return result;
|
|
234
247
|
}
|
|
235
248
|
}
|
|
236
249
|
|
|
237
|
-
const TraceEngineResultComputed =
|
|
250
|
+
const TraceEngineResultComputed =
|
|
251
|
+
makeComputedArtifact(TraceEngineResult, ['trace', 'settings', 'SourceMaps']);
|
|
238
252
|
export {TraceEngineResultComputed as TraceEngineResult};
|
|
@@ -312,7 +312,7 @@ const defaultConfig = {
|
|
|
312
312
|
'insights/cls-culprits-insight',
|
|
313
313
|
'insights/document-latency-insight',
|
|
314
314
|
'insights/dom-size-insight',
|
|
315
|
-
'insights/
|
|
315
|
+
'insights/duplicated-javascript-insight',
|
|
316
316
|
'insights/font-display-insight',
|
|
317
317
|
'insights/forced-reflow-insight',
|
|
318
318
|
'insights/image-delivery-insight',
|
|
@@ -323,6 +323,7 @@ const defaultConfig = {
|
|
|
323
323
|
'insights/render-blocking-insight',
|
|
324
324
|
'insights/slow-css-selector-insight',
|
|
325
325
|
'insights/third-parties-insight',
|
|
326
|
+
'insights/use-cache-insight',
|
|
326
327
|
'insights/viewport-insight',
|
|
327
328
|
],
|
|
328
329
|
groups: {
|
|
@@ -411,7 +412,7 @@ const defaultConfig = {
|
|
|
411
412
|
{id: 'cls-culprits-insight', weight: 0, group: 'hidden'},
|
|
412
413
|
{id: 'document-latency-insight', weight: 0, group: 'hidden'},
|
|
413
414
|
{id: 'dom-size-insight', weight: 0, group: 'hidden'},
|
|
414
|
-
{id: '
|
|
415
|
+
{id: 'duplicated-javascript-insight', weight: 0, group: 'hidden'},
|
|
415
416
|
{id: 'font-display-insight', weight: 0, group: 'hidden'},
|
|
416
417
|
{id: 'forced-reflow-insight', weight: 0, group: 'hidden'},
|
|
417
418
|
{id: 'image-delivery-insight', weight: 0, group: 'hidden'},
|
|
@@ -422,6 +423,7 @@ const defaultConfig = {
|
|
|
422
423
|
{id: 'render-blocking-insight', weight: 0, group: 'hidden'},
|
|
423
424
|
{id: 'slow-css-selector-insight', weight: 0, group: 'hidden'},
|
|
424
425
|
{id: 'third-parties-insight', weight: 0, group: 'hidden'},
|
|
426
|
+
{id: 'use-cache-insight', weight: 0, group: 'hidden'},
|
|
425
427
|
{id: 'viewport-insight', weight: 0, group: 'hidden'},
|
|
426
428
|
|
|
427
429
|
// These are our "invisible" metrics. Not displayed, but still in the LHR.
|
|
@@ -30,7 +30,7 @@ const config = {
|
|
|
30
30
|
{id: 'cls-culprits-insight', weight: 0, group: 'insights'},
|
|
31
31
|
{id: 'document-latency-insight', weight: 0, group: 'insights'},
|
|
32
32
|
{id: 'dom-size-insight', weight: 0, group: 'insights'},
|
|
33
|
-
{id: '
|
|
33
|
+
{id: 'duplicated-javascript-insight', weight: 0, group: 'insights'},
|
|
34
34
|
{id: 'font-display-insight', weight: 0, group: 'insights'},
|
|
35
35
|
{id: 'forced-reflow-insight', weight: 0, group: 'insights'},
|
|
36
36
|
{id: 'image-delivery-insight', weight: 0, group: 'insights'},
|
|
@@ -41,6 +41,7 @@ const config = {
|
|
|
41
41
|
{id: 'render-blocking-insight', weight: 0, group: 'insights'},
|
|
42
42
|
{id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
|
|
43
43
|
{id: 'third-parties-insight', weight: 0, group: 'insights'},
|
|
44
|
+
{id: 'use-cache-insight', weight: 0, group: 'insights'},
|
|
44
45
|
{id: 'viewport-insight', weight: 0, group: 'insights'},
|
|
45
46
|
],
|
|
46
47
|
},
|
|
@@ -12,8 +12,11 @@ import Scripts from './scripts.js';
|
|
|
12
12
|
* @fileoverview Gets JavaScript source maps.
|
|
13
13
|
*/
|
|
14
14
|
class SourceMaps extends BaseGatherer {
|
|
15
|
+
static symbol = Symbol('SourceMaps');
|
|
16
|
+
|
|
15
17
|
/** @type {LH.Gatherer.GathererMeta<'Scripts'>} */
|
|
16
18
|
meta = {
|
|
19
|
+
symbol: SourceMaps.symbol,
|
|
17
20
|
supportedModes: ['timespan', 'navigation'],
|
|
18
21
|
dependencies: {Scripts: Scripts.symbol},
|
|
19
22
|
};
|
|
@@ -56,8 +56,8 @@ declare class TraceElements extends BaseGatherer {
|
|
|
56
56
|
nodeId: number;
|
|
57
57
|
type: string;
|
|
58
58
|
} | undefined>;
|
|
59
|
-
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
60
|
-
meta: LH.Gatherer.GathererMeta<"Trace">;
|
|
59
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'|'SourceMaps'>} */
|
|
60
|
+
meta: LH.Gatherer.GathererMeta<"Trace" | "SourceMaps">;
|
|
61
61
|
/** @type {Map<string, string>} */
|
|
62
62
|
animationIdToName: Map<string, string>;
|
|
63
63
|
/** @param {LH.Crdp.Animation.AnimationStartedEvent} args */
|
|
@@ -82,10 +82,10 @@ declare class TraceElements extends BaseGatherer {
|
|
|
82
82
|
*/
|
|
83
83
|
getNodeDetails(session: LH.Gatherer.ProtocolSession, backendNodeId: number): Promise<import("devtools-protocol").Protocol.Runtime.CallFunctionOnResponse | null>;
|
|
84
84
|
/**
|
|
85
|
-
* @param {LH.Gatherer.Context<'Trace'|'RootCauses'>} context
|
|
85
|
+
* @param {LH.Gatherer.Context<'Trace'|'RootCauses'|'SourceMaps'>} context
|
|
86
86
|
* @return {Promise<LH.Artifacts.TraceElement[]>}
|
|
87
87
|
*/
|
|
88
|
-
getArtifact(context: LH.Gatherer.Context<"Trace" | "RootCauses">): Promise<LH.Artifacts.TraceElement[]>;
|
|
88
|
+
getArtifact(context: LH.Gatherer.Context<"Trace" | "RootCauses" | "SourceMaps">): Promise<LH.Artifacts.TraceElement[]>;
|
|
89
89
|
}
|
|
90
90
|
import BaseGatherer from '../base-gatherer.js';
|
|
91
91
|
//# sourceMappingURL=trace-elements.d.ts.map
|
|
@@ -24,6 +24,7 @@ import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
|
24
24
|
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
25
25
|
import {ExecutionContext} from '../driver/execution-context.js';
|
|
26
26
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
27
|
+
import SourceMaps from './source-maps.js';
|
|
27
28
|
|
|
28
29
|
/** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
29
30
|
|
|
@@ -45,10 +46,10 @@ function getNodeDetailsData() {
|
|
|
45
46
|
/* c8 ignore stop */
|
|
46
47
|
|
|
47
48
|
class TraceElements extends BaseGatherer {
|
|
48
|
-
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
49
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'|'SourceMaps'>} */
|
|
49
50
|
meta = {
|
|
50
51
|
supportedModes: ['timespan', 'navigation'],
|
|
51
|
-
dependencies: {Trace: Trace.symbol},
|
|
52
|
+
dependencies: {Trace: Trace.symbol, SourceMaps: SourceMaps.symbol},
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
/** @type {Map<string, string>} */
|
|
@@ -379,15 +380,17 @@ class TraceElements extends BaseGatherer {
|
|
|
379
380
|
}
|
|
380
381
|
|
|
381
382
|
/**
|
|
382
|
-
* @param {LH.Gatherer.Context<'Trace'|'RootCauses'>} context
|
|
383
|
+
* @param {LH.Gatherer.Context<'Trace'|'RootCauses'|'SourceMaps'>} context
|
|
383
384
|
* @return {Promise<LH.Artifacts.TraceElement[]>}
|
|
384
385
|
*/
|
|
385
386
|
async getArtifact(context) {
|
|
386
387
|
const session = context.driver.defaultSession;
|
|
387
388
|
|
|
388
389
|
const trace = context.dependencies.Trace;
|
|
390
|
+
const SourceMaps = context.dependencies.SourceMaps;
|
|
389
391
|
const settings = context.settings;
|
|
390
|
-
const traceEngineResult =
|
|
392
|
+
const traceEngineResult =
|
|
393
|
+
await TraceEngineResult.request({trace, settings, SourceMaps}, context);
|
|
391
394
|
const rootCauses = context.dependencies.RootCauses;
|
|
392
395
|
|
|
393
396
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
@@ -59,6 +59,11 @@ class Trace extends BaseGatherer {
|
|
|
59
59
|
// For CLS root causes.
|
|
60
60
|
'disabled-by-default-devtools.timeline.invalidationTracking',
|
|
61
61
|
|
|
62
|
+
// Enhanced traces.
|
|
63
|
+
'disabled-by-default-devtools.target-rundown',
|
|
64
|
+
'disabled-by-default-devtools.v8-source-rundown-sources',
|
|
65
|
+
'disabled-by-default-devtools.v8-source-rundown',
|
|
66
|
+
|
|
62
67
|
// Not used by Lighthouse (yet) but included for users that want JS samples when looking at
|
|
63
68
|
// a trace collected by Lighthouse (e.g. "View Trace" workflow in DevTools)
|
|
64
69
|
// TODO: Re-enable after investigating b/325659693
|
|
@@ -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};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "12.4.0-dev.
|
|
4
|
+
"version": "12.4.0-dev.20250319",
|
|
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",
|
|
@@ -184,7 +185,7 @@
|
|
|
184
185
|
"webtreemap-cdt": "^3.2.1"
|
|
185
186
|
},
|
|
186
187
|
"dependencies": {
|
|
187
|
-
"@paulirish/trace_engine": "0.0.
|
|
188
|
+
"@paulirish/trace_engine": "0.0.50",
|
|
188
189
|
"@sentry/node": "^7.0.0",
|
|
189
190
|
"axe-core": "^4.10.2",
|
|
190
191
|
"chrome-launcher": "^1.1.2",
|