lighthouse 9.5.0-dev.20230124 → 9.5.0-dev.20230125
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/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-active.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/network-requests.js +7 -6
- package/core/computed/entity-classification.js +1 -1
- package/core/computed/metrics/responsiveness.js +5 -2
- package/core/computed/page-dependency-graph.js +1 -1
- package/core/gather/gatherers/accessibility.js +3 -1
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +3 -0
- package/core/lib/stack-packs.js +4 -0
- package/core/lib/tracehouse/trace-processor.d.ts +14 -5
- package/core/lib/tracehouse/trace-processor.js +85 -30
- package/core/lib/traces/metric-trace-events.js +1 -1
- package/package.json +4 -5
- package/shared/localization/locales/en-US.json +74 -44
- package/shared/localization/locales/en-XL.json +74 -44
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +7 -2
- package/core/lib/minify-trace.d.ts +0 -6
- package/core/lib/minify-trace.js +0 -172
- package/core/util.cjs +0 -845
- package/core/util.d.cts +0 -342
|
@@ -125,6 +125,8 @@ class NetworkRequest {
|
|
|
125
125
|
this.parsedURL = /** @type {ParsedURL} */ ({scheme: ''});
|
|
126
126
|
this.documentURL = '';
|
|
127
127
|
|
|
128
|
+
/** When the renderer process initially discovers a network request, in milliseconds. */
|
|
129
|
+
this.rendererStartTime = -1;
|
|
128
130
|
/**
|
|
129
131
|
* When the network service is about to handle a request, ie. just before going to the
|
|
130
132
|
* HTTP cache or going to the network for DNS/connection setup, in milliseconds.
|
|
@@ -222,6 +224,7 @@ class NetworkRequest {
|
|
|
222
224
|
};
|
|
223
225
|
this.isSecure = UrlUtils.isSecureScheme(this.parsedURL.scheme);
|
|
224
226
|
|
|
227
|
+
this.rendererStartTime = data.timestamp * 1000;
|
|
225
228
|
// Expected to be overriden with better value in `_recomputeTimesWithResourceTiming`.
|
|
226
229
|
this.startTime = data.timestamp * 1000;
|
|
227
230
|
|
package/core/lib/stack-packs.js
CHANGED
|
@@ -148,13 +148,22 @@ export class TraceProcessor {
|
|
|
148
148
|
static getMainThreadTopLevelEvents(trace: LH.Artifacts.ProcessedTrace, startTime?: number | undefined, endTime?: number | undefined): Array<ToplevelEvent>;
|
|
149
149
|
/**
|
|
150
150
|
* @param {LH.TraceEvent[]} events
|
|
151
|
-
* @return {{
|
|
151
|
+
* @return {{startingPid: number, frameId: string}}
|
|
152
152
|
*/
|
|
153
153
|
static findMainFrameIds(events: LH.TraceEvent[]): {
|
|
154
|
-
|
|
155
|
-
tid: number;
|
|
154
|
+
startingPid: number;
|
|
156
155
|
frameId: string;
|
|
157
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* If there were any cross-origin navigations, there'll be more than one pid returned
|
|
159
|
+
* @param {{startingPid: number, frameId: string}} mainFrameInfo
|
|
160
|
+
* @param {LH.TraceEvent[]} keyEvents
|
|
161
|
+
* @return {Map<number, number>} Map where keys are process IDs and their values are thread IDs
|
|
162
|
+
*/
|
|
163
|
+
static findMainFramePidTids(mainFrameInfo: {
|
|
164
|
+
startingPid: number;
|
|
165
|
+
frameId: string;
|
|
166
|
+
}, keyEvents: LH.TraceEvent[]): Map<number, number>;
|
|
158
167
|
/**
|
|
159
168
|
* @param {LH.TraceEvent} evt
|
|
160
169
|
* @return {boolean}
|
|
@@ -239,14 +248,14 @@ export class TraceProcessor {
|
|
|
239
248
|
* Can also be skewed by several hundred milliseconds or even seconds when the browser takes a long
|
|
240
249
|
* time to unload `about:blank`.
|
|
241
250
|
*
|
|
242
|
-
* @param {{keyEvents: Array<LH.TraceEvent>, frameEvents: Array<LH.TraceEvent>,
|
|
251
|
+
* @param {{keyEvents: Array<LH.TraceEvent>, frameEvents: Array<LH.TraceEvent>, mainFrameInfo: {frameId: string}}} traceEventSubsets
|
|
243
252
|
* @param {TimeOriginDeterminationMethod} method
|
|
244
253
|
* @return {LH.TraceEvent}
|
|
245
254
|
*/
|
|
246
255
|
static computeTimeOrigin(traceEventSubsets: {
|
|
247
256
|
keyEvents: Array<LH.TraceEvent>;
|
|
248
257
|
frameEvents: Array<LH.TraceEvent>;
|
|
249
|
-
|
|
258
|
+
mainFrameInfo: {
|
|
250
259
|
frameId: string;
|
|
251
260
|
};
|
|
252
261
|
}, method: TimeOriginDeterminationMethod): LH.TraceEvent;
|
|
@@ -423,7 +423,7 @@ class TraceProcessor {
|
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
425
|
* @param {LH.TraceEvent[]} events
|
|
426
|
-
* @return {{
|
|
426
|
+
* @return {{startingPid: number, frameId: string}}
|
|
427
427
|
*/
|
|
428
428
|
static findMainFrameIds(events) {
|
|
429
429
|
// Prefer the newer TracingStartedInBrowser event first, if it exists
|
|
@@ -433,14 +433,9 @@ class TraceProcessor {
|
|
|
433
433
|
const frameId = mainFrame?.frame;
|
|
434
434
|
const pid = mainFrame?.processId;
|
|
435
435
|
|
|
436
|
-
|
|
437
|
-
e.cat === '__metadata' && e.name === 'thread_name' && e.args.name === 'CrRendererMain');
|
|
438
|
-
const tid = threadNameEvt?.tid;
|
|
439
|
-
|
|
440
|
-
if (pid && tid && frameId) {
|
|
436
|
+
if (pid && frameId) {
|
|
441
437
|
return {
|
|
442
|
-
pid,
|
|
443
|
-
tid,
|
|
438
|
+
startingPid: pid,
|
|
444
439
|
frameId,
|
|
445
440
|
};
|
|
446
441
|
}
|
|
@@ -454,8 +449,7 @@ class TraceProcessor {
|
|
|
454
449
|
const frameId = startedInPageEvt.args.data.page;
|
|
455
450
|
if (frameId) {
|
|
456
451
|
return {
|
|
457
|
-
|
|
458
|
-
tid: startedInPageEvt.tid,
|
|
452
|
+
startingPid: startedInPageEvt.pid,
|
|
459
453
|
frameId,
|
|
460
454
|
};
|
|
461
455
|
}
|
|
@@ -478,8 +472,7 @@ class TraceProcessor {
|
|
|
478
472
|
const frameId = navStartEvt.args.frame;
|
|
479
473
|
if (frameId) {
|
|
480
474
|
return {
|
|
481
|
-
|
|
482
|
-
tid: navStartEvt.tid,
|
|
475
|
+
startingPid: navStartEvt.pid,
|
|
483
476
|
frameId,
|
|
484
477
|
};
|
|
485
478
|
}
|
|
@@ -488,6 +481,47 @@ class TraceProcessor {
|
|
|
488
481
|
throw this.createNoTracingStartedError();
|
|
489
482
|
}
|
|
490
483
|
|
|
484
|
+
/**
|
|
485
|
+
* If there were any cross-origin navigations, there'll be more than one pid returned
|
|
486
|
+
* @param {{startingPid: number, frameId: string}} mainFrameInfo
|
|
487
|
+
* @param {LH.TraceEvent[]} keyEvents
|
|
488
|
+
* @return {Map<number, number>} Map where keys are process IDs and their values are thread IDs
|
|
489
|
+
*/
|
|
490
|
+
static findMainFramePidTids(mainFrameInfo, keyEvents) {
|
|
491
|
+
const frameCommittedEvts = keyEvents.filter(evt =>
|
|
492
|
+
evt.name === 'FrameCommittedInBrowser' &&
|
|
493
|
+
evt.args?.data?.frame === mainFrameInfo.frameId
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
// "Modern" traces with a navigation have a FrameCommittedInBrowser event
|
|
497
|
+
const mainFramePids = frameCommittedEvts.length
|
|
498
|
+
? frameCommittedEvts.map(e => e?.args?.data?.processId)
|
|
499
|
+
// …But old traces and some timespan traces may not. In these situations, we'll assume the
|
|
500
|
+
// primary process ID remains constant (as there were no navigations).
|
|
501
|
+
: [mainFrameInfo.startingPid];
|
|
502
|
+
|
|
503
|
+
const pidToTid = new Map();
|
|
504
|
+
|
|
505
|
+
mainFramePids.forEach(pid => {
|
|
506
|
+
// While renderer tids are generally predictable, we'll doublecheck it
|
|
507
|
+
const threadNameEvt = keyEvents.find(e =>
|
|
508
|
+
e.cat === '__metadata' &&
|
|
509
|
+
e.pid === pid &&
|
|
510
|
+
e.ph === 'M' &&
|
|
511
|
+
e.name === 'thread_name' &&
|
|
512
|
+
e.args.name === 'CrRendererMain'
|
|
513
|
+
);
|
|
514
|
+
const tid = threadNameEvt?.tid;
|
|
515
|
+
|
|
516
|
+
if (!tid) {
|
|
517
|
+
throw new Error('Unable to determine tid for renderer process');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
pidToTid.set(pid, tid);
|
|
521
|
+
});
|
|
522
|
+
return pidToTid;
|
|
523
|
+
}
|
|
524
|
+
|
|
491
525
|
/**
|
|
492
526
|
* @param {LH.TraceEvent} evt
|
|
493
527
|
* @return {boolean}
|
|
@@ -583,6 +617,9 @@ class TraceProcessor {
|
|
|
583
617
|
while (parentFrames.has(cur)) {
|
|
584
618
|
cur = /** @type {string} */ (parentFrames.get(cur));
|
|
585
619
|
}
|
|
620
|
+
if (cur === undefined) {
|
|
621
|
+
throw new Error('Unexpected undefined frameId');
|
|
622
|
+
}
|
|
586
623
|
frameIdToRootFrameId.set(frame.id, cur);
|
|
587
624
|
}
|
|
588
625
|
|
|
@@ -609,7 +646,15 @@ class TraceProcessor {
|
|
|
609
646
|
});
|
|
610
647
|
|
|
611
648
|
// Find the inspected frame
|
|
612
|
-
const
|
|
649
|
+
const mainFrameInfo = this.findMainFrameIds(keyEvents);
|
|
650
|
+
const rendererPidToTid = this.findMainFramePidTids(mainFrameInfo, keyEvents);
|
|
651
|
+
|
|
652
|
+
// Subset all trace events to just our tab's process (incl threads other than main)
|
|
653
|
+
// stable-sort events to keep them correctly nested.
|
|
654
|
+
const processEvents = TraceProcessor
|
|
655
|
+
.filteredTraceSort(trace.traceEvents, e => rendererPidToTid.has(e.pid));
|
|
656
|
+
|
|
657
|
+
// TODO(paulirish): filter down frames (and subsequent actions) to the primary process tree & frame tree
|
|
613
658
|
|
|
614
659
|
/** @type {Map<string, {id: string, url: string, parent?: string}>} */
|
|
615
660
|
const framesById = new Map();
|
|
@@ -645,18 +690,32 @@ class TraceProcessor {
|
|
|
645
690
|
parent: evt.args.data.parent,
|
|
646
691
|
});
|
|
647
692
|
});
|
|
693
|
+
|
|
648
694
|
const frames = [...framesById.values()];
|
|
649
695
|
const frameIdToRootFrameId = this.resolveRootFrames(frames);
|
|
650
696
|
|
|
697
|
+
const inspectedTreeFrameIds = [...frameIdToRootFrameId.entries()]
|
|
698
|
+
.filter(([, rootFrameId]) => rootFrameId === mainFrameInfo.frameId)
|
|
699
|
+
.map(([child]) => child);
|
|
700
|
+
|
|
651
701
|
// Filter to just events matching the main frame ID, just to make sure.
|
|
652
|
-
|
|
702
|
+
/** @param {LH.TraceEvent} e */
|
|
703
|
+
function associatedToMainFrame(e) {
|
|
704
|
+
const frameId = e.args?.data?.frame || e.args.frame;
|
|
705
|
+
return frameId === mainFrameInfo.frameId;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** @param {LH.TraceEvent} e */
|
|
709
|
+
function associatedToAllFrames(e) {
|
|
710
|
+
const frameId = e.args?.data?.frame || e.args.frame;
|
|
711
|
+
return frameId ? inspectedTreeFrameIds.includes(frameId) : false;
|
|
712
|
+
}
|
|
713
|
+
const frameEvents = keyEvents.filter(e => associatedToMainFrame(e));
|
|
653
714
|
|
|
654
715
|
// Filter to just events matching the main frame ID or any child frame IDs.
|
|
655
716
|
let frameTreeEvents = [];
|
|
656
|
-
if (frameIdToRootFrameId.has(
|
|
657
|
-
frameTreeEvents = keyEvents.filter(e =>
|
|
658
|
-
return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
|
|
659
|
-
});
|
|
717
|
+
if (frameIdToRootFrameId.has(mainFrameInfo.frameId)) {
|
|
718
|
+
frameTreeEvents = keyEvents.filter(e => associatedToAllFrames(e));
|
|
660
719
|
} else {
|
|
661
720
|
// In practice, there should always be TracingStartedInBrowser/FrameCommittedInBrowser events to
|
|
662
721
|
// define the frame tree. Unfortunately, many test traces do not that frame info due to minification.
|
|
@@ -665,23 +724,17 @@ class TraceProcessor {
|
|
|
665
724
|
'TraceProcessor',
|
|
666
725
|
'frameTreeEvents may be incomplete, make sure the trace has frame events'
|
|
667
726
|
);
|
|
668
|
-
frameIdToRootFrameId.set(
|
|
727
|
+
frameIdToRootFrameId.set(mainFrameInfo.frameId, mainFrameInfo.frameId);
|
|
669
728
|
frameTreeEvents = frameEvents;
|
|
670
729
|
}
|
|
671
730
|
|
|
672
731
|
// Compute our time origin to use for all relative timings.
|
|
673
732
|
const timeOriginEvt = this.computeTimeOrigin(
|
|
674
|
-
{keyEvents, frameEvents,
|
|
733
|
+
{keyEvents, frameEvents, mainFrameInfo: mainFrameInfo},
|
|
675
734
|
timeOriginDeterminationMethod
|
|
676
735
|
);
|
|
677
736
|
|
|
678
|
-
|
|
679
|
-
// stable-sort events to keep them correctly nested.
|
|
680
|
-
const processEvents = TraceProcessor
|
|
681
|
-
.filteredTraceSort(trace.traceEvents, e => e.pid === mainFrameIds.pid);
|
|
682
|
-
|
|
683
|
-
const mainThreadEvents = processEvents
|
|
684
|
-
.filter(e => e.tid === mainFrameIds.tid);
|
|
737
|
+
const mainThreadEvents = processEvents.filter(e => e.tid === rendererPidToTid.get(e.pid));
|
|
685
738
|
|
|
686
739
|
// Ensure our traceEnd reflects all page activity.
|
|
687
740
|
const traceEnd = this.computeTraceEnd(trace.traceEvents, timeOriginEvt);
|
|
@@ -694,7 +747,7 @@ class TraceProcessor {
|
|
|
694
747
|
frameEvents,
|
|
695
748
|
frameTreeEvents,
|
|
696
749
|
processEvents,
|
|
697
|
-
|
|
750
|
+
mainFrameInfo,
|
|
698
751
|
timeOriginEvt,
|
|
699
752
|
timings: {
|
|
700
753
|
timeOrigin: 0,
|
|
@@ -704,6 +757,8 @@ class TraceProcessor {
|
|
|
704
757
|
timeOrigin: timeOriginEvt.ts,
|
|
705
758
|
traceEnd: traceEnd.timestamp,
|
|
706
759
|
},
|
|
760
|
+
_keyEvents: keyEvents,
|
|
761
|
+
_rendererPidToTid: rendererPidToTid,
|
|
707
762
|
};
|
|
708
763
|
}
|
|
709
764
|
|
|
@@ -807,7 +862,7 @@ class TraceProcessor {
|
|
|
807
862
|
* Can also be skewed by several hundred milliseconds or even seconds when the browser takes a long
|
|
808
863
|
* time to unload `about:blank`.
|
|
809
864
|
*
|
|
810
|
-
* @param {{keyEvents: Array<LH.TraceEvent>, frameEvents: Array<LH.TraceEvent>,
|
|
865
|
+
* @param {{keyEvents: Array<LH.TraceEvent>, frameEvents: Array<LH.TraceEvent>, mainFrameInfo: {frameId: string}}} traceEventSubsets
|
|
811
866
|
* @param {TimeOriginDeterminationMethod} method
|
|
812
867
|
* @return {LH.TraceEvent}
|
|
813
868
|
*/
|
|
@@ -833,7 +888,7 @@ class TraceProcessor {
|
|
|
833
888
|
const fetchStart = traceEventSubsets.keyEvents.find(event => {
|
|
834
889
|
if (event.name !== 'ResourceSendRequest') return false;
|
|
835
890
|
const data = event.args.data || {};
|
|
836
|
-
return data.frame === traceEventSubsets.
|
|
891
|
+
return data.frame === traceEventSubsets.mainFrameInfo.frameId;
|
|
837
892
|
});
|
|
838
893
|
if (!fetchStart) throw this.createNoResourceSendRequestError();
|
|
839
894
|
return fetchStart;
|
|
@@ -128,7 +128,7 @@ class MetricTraceEvents {
|
|
|
128
128
|
if (!timeOriginMetric) return {errorMessage: 'timeorigin Metric not found in definitions'};
|
|
129
129
|
try {
|
|
130
130
|
const frameIds = TraceProcessor.findMainFrameIds(this._traceEvents);
|
|
131
|
-
return {pid: frameIds.
|
|
131
|
+
return {pid: frameIds.startingPid, tid: 1, ts: timeOriginMetric.ts};
|
|
132
132
|
} catch (err) {
|
|
133
133
|
return {errorMessage: err.message};
|
|
134
134
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.5.0-dev.
|
|
4
|
+
"version": "9.5.0-dev.20230125",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
"diff:sample-json": "yarn i18n:checks && bash core/scripts/assert-golden-lhr-unchanged.sh",
|
|
87
87
|
"diff:flow-sample-json": "yarn i18n:collect-strings && bash core/scripts/assert-baseline-flow-result-unchanged.sh",
|
|
88
88
|
"computeBenchmarkIndex": "./core/scripts/benchmark.js",
|
|
89
|
-
"minify-latest-run": "./core/scripts/lantern/minify-trace.js ./latest-run/defaultPass.trace.json ./latest-run/defaultPass.trace.min.json && ./core/scripts/lantern/minify-devtoolslog.js ./latest-run/defaultPass.devtoolslog.json ./latest-run/defaultPass.devtoolslog.min.json",
|
|
90
89
|
"save-latest-run": "./core/scripts/save-latest-run.sh",
|
|
91
90
|
"compile-proto": "protoc --python_out=./ ./proto/lighthouse-result.proto && mv ./proto/*_pb2.py ./proto/scripts || (echo \"❌ Install protobuf = 3.20.x to compile the proto file.\" && false)",
|
|
92
91
|
"build-proto-roundtrip": "mkdir -p .tmp && python3 proto/scripts/json_roundtrip_via_proto.py",
|
|
@@ -187,7 +186,7 @@
|
|
|
187
186
|
},
|
|
188
187
|
"dependencies": {
|
|
189
188
|
"@sentry/node": "^6.17.4",
|
|
190
|
-
"axe-core": "4.
|
|
189
|
+
"axe-core": "4.6.3",
|
|
191
190
|
"chrome-launcher": "^0.15.1",
|
|
192
191
|
"configstore": "^5.0.1",
|
|
193
192
|
"csp_evaluator": "1.1.1",
|
|
@@ -195,9 +194,9 @@
|
|
|
195
194
|
"http-link-header": "^0.8.0",
|
|
196
195
|
"intl-messageformat": "^4.4.0",
|
|
197
196
|
"jpeg-js": "^0.4.4",
|
|
198
|
-
"js-library-detector": "^6.
|
|
197
|
+
"js-library-detector": "^6.6.0",
|
|
199
198
|
"lighthouse-logger": "^1.3.0",
|
|
200
|
-
"lighthouse-stack-packs": "1.
|
|
199
|
+
"lighthouse-stack-packs": "1.9.0",
|
|
201
200
|
"lodash": "^4.17.21",
|
|
202
201
|
"lookup-closest-locale": "6.2.0",
|
|
203
202
|
"metaviewport-parser": "0.3.0",
|