lighthouse 9.6.2 → 9.6.5
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/lighthouse-core/audits/network-requests.js +87 -62
- package/lighthouse-core/audits/preload-lcp-image.js +33 -7
- package/lighthouse-core/computed/main-thread-tasks.js +2 -1
- package/lighthouse-core/lib/dependency-graph/network-node.js +1 -1
- package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +4 -3
- package/lighthouse-core/scripts/package.json +4 -0
- package/package.json +2 -2
- package/shared/localization/locales/en-US.json +2 -2
- package/shared/localization/locales/en-XL.json +2 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
9
|
const URL = require('../lib/url-shim.js');
|
|
10
10
|
const NetworkRecords = require('../computed/network-records.js');
|
|
11
|
+
const MainResource = require('../computed/main-resource.js');
|
|
11
12
|
|
|
12
13
|
class NetworkRequests extends Audit {
|
|
13
14
|
/**
|
|
@@ -19,7 +20,7 @@ class NetworkRequests extends Audit {
|
|
|
19
20
|
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
20
21
|
title: 'Network Requests',
|
|
21
22
|
description: 'Lists the network requests that were made during page load.',
|
|
22
|
-
requiredArtifacts: ['devtoolsLogs'],
|
|
23
|
+
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -28,75 +29,99 @@ class NetworkRequests extends Audit {
|
|
|
28
29
|
* @param {LH.Audit.Context} context
|
|
29
30
|
* @return {Promise<LH.Audit.Product>}
|
|
30
31
|
*/
|
|
31
|
-
static audit(artifacts, context) {
|
|
32
|
+
static async audit(artifacts, context) {
|
|
32
33
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const records = await NetworkRecords.request(devtoolsLog, context);
|
|
35
|
+
const earliestStartTime = records.reduce(
|
|
36
|
+
(min, record) => Math.min(min, record.startTime),
|
|
37
|
+
Infinity
|
|
38
|
+
);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Optional mainFrameId check because the main resource is only available for
|
|
41
|
+
// navigations. TODO: https://github.com/GoogleChrome/lighthouse/issues/14157
|
|
42
|
+
// for the general solution to this.
|
|
43
|
+
/** @type {string|undefined} */
|
|
44
|
+
let mainFrameId;
|
|
45
|
+
if (artifacts.GatherContext.gatherMode === 'navigation') {
|
|
46
|
+
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
|
|
47
|
+
mainFrameId = mainResource.frameId;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const requestMs = record.lrStatistics?.requestMs;
|
|
47
|
-
const responseMs = record.lrStatistics?.responseMs;
|
|
50
|
+
/** @param {number} time */
|
|
51
|
+
const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
|
|
52
|
+
undefined : (time - earliestStartTime) * 1000;
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
resourceType: record.resourceType,
|
|
60
|
-
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
61
|
-
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
62
|
-
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
63
|
-
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
68
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
69
|
-
const headings = [
|
|
70
|
-
{key: 'url', itemType: 'url', text: 'URL'},
|
|
71
|
-
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
72
|
-
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
73
|
-
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
74
|
-
{
|
|
75
|
-
key: 'transferSize',
|
|
76
|
-
itemType: 'bytes',
|
|
77
|
-
displayUnit: 'kb',
|
|
78
|
-
granularity: 1,
|
|
79
|
-
text: 'Transfer Size',
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: 'resourceSize',
|
|
83
|
-
itemType: 'bytes',
|
|
84
|
-
displayUnit: 'kb',
|
|
85
|
-
granularity: 1,
|
|
86
|
-
text: 'Resource Size',
|
|
87
|
-
},
|
|
88
|
-
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
89
|
-
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
90
|
-
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
54
|
+
const results = records.map(record => {
|
|
55
|
+
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
|
|
56
|
+
const TCPMs = record.lrStatistics?.TCPMs;
|
|
57
|
+
const requestMs = record.lrStatistics?.requestMs;
|
|
58
|
+
const responseMs = record.lrStatistics?.responseMs;
|
|
59
|
+
// Default these to undefined so omitted from JSON in the negative case.
|
|
60
|
+
const isLinkPreload = record.isLinkPreload || undefined;
|
|
61
|
+
const experimentalFromMainFrame = mainFrameId ?
|
|
62
|
+
((record.frameId === mainFrameId) || undefined) :
|
|
63
|
+
undefined;
|
|
94
64
|
|
|
95
65
|
return {
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
url: URL.elideDataURI(record.url),
|
|
67
|
+
protocol: record.protocol,
|
|
68
|
+
startTime: timeToMs(record.startTime),
|
|
69
|
+
endTime: timeToMs(record.endTime),
|
|
70
|
+
finished: record.finished,
|
|
71
|
+
transferSize: record.transferSize,
|
|
72
|
+
resourceSize: record.resourceSize,
|
|
73
|
+
statusCode: record.statusCode,
|
|
74
|
+
mimeType: record.mimeType,
|
|
75
|
+
resourceType: record.resourceType,
|
|
76
|
+
isLinkPreload,
|
|
77
|
+
experimentalFromMainFrame,
|
|
78
|
+
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
79
|
+
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
80
|
+
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
81
|
+
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
98
82
|
};
|
|
99
83
|
});
|
|
84
|
+
|
|
85
|
+
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
86
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
87
|
+
const headings = [
|
|
88
|
+
{key: 'url', itemType: 'url', text: 'URL'},
|
|
89
|
+
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
90
|
+
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
91
|
+
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
92
|
+
{
|
|
93
|
+
key: 'transferSize',
|
|
94
|
+
itemType: 'bytes',
|
|
95
|
+
displayUnit: 'kb',
|
|
96
|
+
granularity: 1,
|
|
97
|
+
text: 'Transfer Size',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'resourceSize',
|
|
101
|
+
itemType: 'bytes',
|
|
102
|
+
displayUnit: 'kb',
|
|
103
|
+
granularity: 1,
|
|
104
|
+
text: 'Resource Size',
|
|
105
|
+
},
|
|
106
|
+
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
107
|
+
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
108
|
+
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
112
|
+
|
|
113
|
+
// Include starting timestamp to allow syncing requests with navStart/metric timestamps.
|
|
114
|
+
const networkStartTimeTs = Number.isFinite(earliestStartTime) ?
|
|
115
|
+
earliestStartTime * 1_000_000 : undefined;
|
|
116
|
+
tableDetails.debugData = {
|
|
117
|
+
type: 'debugdata',
|
|
118
|
+
networkStartTimeTs,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
score: 1,
|
|
123
|
+
details: tableDetails,
|
|
124
|
+
};
|
|
100
125
|
}
|
|
101
126
|
}
|
|
102
127
|
|
|
@@ -23,6 +23,10 @@ const UIStrings = {
|
|
|
23
23
|
|
|
24
24
|
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
|
|
28
|
+
*/
|
|
29
|
+
|
|
26
30
|
class PreloadLCPImageAudit extends Audit {
|
|
27
31
|
/**
|
|
28
32
|
* @return {LH.Audit.Meta}
|
|
@@ -86,22 +90,33 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
86
90
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
87
91
|
* @param {LH.Artifacts.TraceElement|undefined} lcpElement
|
|
88
92
|
* @param {Array<LH.Artifacts.ImageElement>} imageElements
|
|
89
|
-
* @return {LH.Gatherer.Simulation.GraphNetworkNode
|
|
93
|
+
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
90
94
|
*/
|
|
91
95
|
static getLCPNodeToPreload(mainResource, graph, lcpElement, imageElements) {
|
|
92
|
-
if (!lcpElement) return
|
|
96
|
+
if (!lcpElement) return {};
|
|
93
97
|
|
|
94
98
|
const lcpImageElement = imageElements.find(elem => {
|
|
95
99
|
return elem.node.devtoolsNodePath === lcpElement.node.devtoolsNodePath;
|
|
96
100
|
});
|
|
97
101
|
|
|
98
|
-
if (!lcpImageElement) return
|
|
102
|
+
if (!lcpImageElement) return {};
|
|
99
103
|
const lcpUrl = lcpImageElement.src;
|
|
100
104
|
const {lcpNode, path} = PreloadLCPImageAudit.findLCPNode(graph, lcpUrl);
|
|
101
|
-
if (!lcpNode || !path) return
|
|
105
|
+
if (!lcpNode || !path) return {};
|
|
106
|
+
|
|
102
107
|
// eslint-disable-next-line max-len
|
|
103
108
|
const shouldPreload = PreloadLCPImageAudit.shouldPreloadRequest(lcpNode.record, mainResource, path);
|
|
104
|
-
|
|
109
|
+
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
|
|
110
|
+
|
|
111
|
+
const initiatorPath = [
|
|
112
|
+
{url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
|
|
113
|
+
...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
lcpNodeToPreload,
|
|
118
|
+
initiatorPath,
|
|
119
|
+
};
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
/**
|
|
@@ -215,10 +230,10 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
215
230
|
|
|
216
231
|
const graph = lanternLCP.pessimisticGraph;
|
|
217
232
|
// eslint-disable-next-line max-len
|
|
218
|
-
const
|
|
233
|
+
const {lcpNodeToPreload, initiatorPath} = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
|
|
219
234
|
|
|
220
235
|
const {results, wastedMs} =
|
|
221
|
-
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement,
|
|
236
|
+
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
222
237
|
|
|
223
238
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
224
239
|
const headings = [
|
|
@@ -228,6 +243,17 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
228
243
|
];
|
|
229
244
|
const details = Audit.makeOpportunityDetails(headings, results, wastedMs);
|
|
230
245
|
|
|
246
|
+
// If LCP element was an image and had valid network records (regardless of
|
|
247
|
+
// if it should be preloaded), it will be found first in the `initiatorPath`.
|
|
248
|
+
// Otherwise path and length will be undefined.
|
|
249
|
+
if (initiatorPath) {
|
|
250
|
+
details.debugData = {
|
|
251
|
+
type: 'debugdata',
|
|
252
|
+
initiatorPath,
|
|
253
|
+
pathLength: initiatorPath.length,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
231
257
|
return {
|
|
232
258
|
score: UnusedBytes.scoreForWastedMs(wastedMs),
|
|
233
259
|
numericValue: wastedMs,
|
|
@@ -17,7 +17,8 @@ class MainThreadTasks {
|
|
|
17
17
|
*/
|
|
18
18
|
static async compute_(trace, context) {
|
|
19
19
|
const {mainThreadEvents, frames, timestamps} = await ProcessedTrace.request(trace, context);
|
|
20
|
-
return MainThreadTasks_.getMainThreadTasks(mainThreadEvents, frames, timestamps.traceEnd
|
|
20
|
+
return MainThreadTasks_.getMainThreadTasks(mainThreadEvents, frames, timestamps.traceEnd,
|
|
21
|
+
timestamps.timeOrigin);
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -561,9 +561,10 @@ class MainThreadTasks {
|
|
|
561
561
|
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
562
562
|
* @param {Array<{id: string, url: string}>} frames
|
|
563
563
|
* @param {number} traceEndTs
|
|
564
|
+
* @param {number} [traceStartTs] Optional time-0 ts for tasks. Tasks before this point will have negative start/end times. Defaults to the first task found.
|
|
564
565
|
* @return {TaskNode[]}
|
|
565
566
|
*/
|
|
566
|
-
static getMainThreadTasks(mainThreadEvents, frames, traceEndTs) {
|
|
567
|
+
static getMainThreadTasks(mainThreadEvents, frames, traceEndTs, traceStartTs) {
|
|
567
568
|
const timers = new Map();
|
|
568
569
|
const xhrs = new Map();
|
|
569
570
|
const frameURLsById = new Map();
|
|
@@ -587,8 +588,8 @@ class MainThreadTasks {
|
|
|
587
588
|
priorTaskData.lastTaskURLs = task.attributableURLs;
|
|
588
589
|
}
|
|
589
590
|
|
|
590
|
-
// Rebase all the times to be relative to start of trace
|
|
591
|
-
const firstTs =
|
|
591
|
+
// Rebase all the times to be relative to start of trace and covert to ms.
|
|
592
|
+
const firstTs = traceStartTs ?? tasks[0].startTime;
|
|
592
593
|
for (const task of tasks) {
|
|
593
594
|
task.startTime = (task.startTime - firstTs) / 1000;
|
|
594
595
|
task.endTime = (task.endTime - firstTs) / 1000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.5",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"jpeg-js": "^0.4.3",
|
|
193
193
|
"js-library-detector": "^6.5.0",
|
|
194
194
|
"lighthouse-logger": "^1.3.0",
|
|
195
|
-
"lighthouse-stack-packs": "
|
|
195
|
+
"lighthouse-stack-packs": "1.8.2",
|
|
196
196
|
"lodash": "^4.17.21",
|
|
197
197
|
"lookup-closest-locale": "6.2.0",
|
|
198
198
|
"metaviewport-parser": "0.2.0",
|
|
@@ -2586,7 +2586,7 @@
|
|
|
2586
2586
|
"message": "If you are using React Router, minimize usage of the `<Redirect>` component for [route navigations](https://reacttraining.com/react-router/web/api/Redirect)."
|
|
2587
2587
|
},
|
|
2588
2588
|
"node_modules/lighthouse-stack-packs/packs/react.js | server-response-time": {
|
|
2589
|
-
"message": "If you are server-side rendering any React components, consider using `
|
|
2589
|
+
"message": "If you are server-side rendering any React components, consider using `renderToPipeableStream()` or `renderToStaticNodeStream()` to allow the client to receive and hydrate different parts of the markup instead of all at once. [Learn more](https://reactjs.org/docs/react-dom-server.html#renderToPipeableStream)."
|
|
2590
2590
|
},
|
|
2591
2591
|
"node_modules/lighthouse-stack-packs/packs/react.js | unminified-css": {
|
|
2592
2592
|
"message": "If your build system minifies CSS files automatically, ensure that you are deploying the production build of your application. You can check this with the React Developer Tools extension. [Learn more](https://reactjs.org/docs/optimizing-performance.html#use-the-production-build)."
|
|
@@ -2604,7 +2604,7 @@
|
|
|
2604
2604
|
"message": "Consider uploading your GIF to a service which will make it available to embed as an HTML5 video."
|
|
2605
2605
|
},
|
|
2606
2606
|
"node_modules/lighthouse-stack-packs/packs/wordpress.js | modern-image-formats": {
|
|
2607
|
-
"message": "Consider using
|
|
2607
|
+
"message": "Consider using the [Performance Lab](https://wordpress.org/plugins/performance-lab/) plugin to automatically convert your uploaded JPEG images into WebP, wherever supported."
|
|
2608
2608
|
},
|
|
2609
2609
|
"node_modules/lighthouse-stack-packs/packs/wordpress.js | offscreen-images": {
|
|
2610
2610
|
"message": "Install a [lazy-load WordPress plugin](https://wordpress.org/plugins/search/lazy+load/) that provides the ability to defer any offscreen images, or switch to a theme that provides that functionality. Also consider using [the AMP plugin](https://wordpress.org/plugins/amp/)."
|
|
@@ -2586,7 +2586,7 @@
|
|
|
2586
2586
|
"message": "Îf́ ŷóû ár̂é ûśîńĝ Ŕêáĉt́ R̂óût́êŕ, m̂ín̂ím̂íẑé ûśâǵê óf̂ t́ĥé `<Redirect>` ĉóm̂ṕôńêńt̂ f́ôŕ [r̂óût́ê ńâv́îǵât́îón̂ś](https://reacttraining.com/react-router/web/api/Redirect)."
|
|
2587
2587
|
},
|
|
2588
2588
|
"node_modules/lighthouse-stack-packs/packs/react.js | server-response-time": {
|
|
2589
|
-
"message": "Îf́ ŷóû ár̂é ŝér̂v́êŕ-ŝíd̂é r̂én̂d́êŕîńĝ án̂ý R̂éâćt̂ ćôḿp̂ón̂én̂t́ŝ, ćôńŝíd̂ér̂ úŝín̂ǵ `
|
|
2589
|
+
"message": "Îf́ ŷóû ár̂é ŝér̂v́êŕ-ŝíd̂é r̂én̂d́êŕîńĝ án̂ý R̂éâćt̂ ćôḿp̂ón̂én̂t́ŝ, ćôńŝíd̂ér̂ úŝín̂ǵ `renderToPipeableStream()` ôŕ `renderToStaticNodeStream()` t̂ó âĺl̂óŵ t́ĥé ĉĺîén̂t́ t̂ó r̂éĉéîv́ê án̂d́ ĥýd̂ŕât́ê d́îf́f̂ér̂én̂t́ p̂ár̂t́ŝ óf̂ t́ĥé m̂ár̂ḱûṕ îńŝt́êád̂ óf̂ ál̂ĺ ât́ ôńĉé. [L̂éâŕn̂ ḿôŕê](https://reactjs.org/docs/react-dom-server.html#renderToPipeableStream)."
|
|
2590
2590
|
},
|
|
2591
2591
|
"node_modules/lighthouse-stack-packs/packs/react.js | unminified-css": {
|
|
2592
2592
|
"message": "Îf́ ŷóûŕ b̂úîĺd̂ śŷśt̂ém̂ ḿîńîf́îéŝ ĆŜŚ f̂íl̂éŝ áût́ôḿât́îćâĺl̂ý, êńŝúr̂é t̂h́ât́ ŷóû ár̂é d̂ép̂ĺôýîńĝ t́ĥé p̂ŕôd́ûćt̂íôń b̂úîĺd̂ óf̂ ýôúr̂ áp̂ṕl̂íĉát̂íôń. Ŷóû ćâń ĉh́êćk̂ t́ĥíŝ ẃît́ĥ t́ĥé R̂éâćt̂ D́êv́êĺôṕêŕ T̂óôĺŝ éx̂t́êńŝíôń. [L̂éâŕn̂ ḿôŕê](https://reactjs.org/docs/optimizing-performance.html#use-the-production-build)."
|
|
@@ -2604,7 +2604,7 @@
|
|
|
2604
2604
|
"message": "Ĉón̂śîd́êŕ ûṕl̂óâd́îńĝ ýôúr̂ ǴÎF́ t̂ó â śêŕv̂íĉé ŵh́îćĥ ẃîĺl̂ ḿâḱê ít̂ áv̂áîĺâb́l̂é t̂ó êḿb̂éd̂ áŝ án̂ H́T̂ḾL̂5 v́îd́êó."
|
|
2605
2605
|
},
|
|
2606
2606
|
"node_modules/lighthouse-stack-packs/packs/wordpress.js | modern-image-formats": {
|
|
2607
|
-
"message": "Ĉón̂śîd́êŕ ûśîńĝ
|
|
2607
|
+
"message": "Ĉón̂śîd́êŕ ûśîńĝ t́ĥé [P̂ér̂f́ôŕm̂án̂ćê Ĺâb́](https://wordpress.org/plugins/performance-lab/) p̂ĺûǵîń t̂ó âút̂óm̂át̂íĉál̂ĺŷ ćôńv̂ér̂t́ ŷóûŕ ûṕl̂óâd́êd́ ĴṔÊǴ îḿâǵêś îńt̂ó Ŵéb̂Ṕ, ŵh́êŕêv́êŕ ŝúp̂ṕôŕt̂éd̂."
|
|
2608
2608
|
},
|
|
2609
2609
|
"node_modules/lighthouse-stack-packs/packs/wordpress.js | offscreen-images": {
|
|
2610
2610
|
"message": "Îńŝt́âĺl̂ á [l̂áẑý-l̂óâd́ Ŵór̂d́P̂ŕêśŝ ṕl̂úĝín̂](https://wordpress.org/plugins/search/lazy+load/) t́ĥát̂ ṕr̂óv̂íd̂éŝ t́ĥé âb́îĺît́ŷ t́ô d́êf́êŕ âńŷ óf̂f́ŝćr̂éêń îḿâǵêś, ôŕ ŝẃît́ĉh́ t̂ó â t́ĥém̂é t̂h́ât́ p̂ŕôv́îd́êś t̂h́ât́ f̂ún̂ćt̂íôńâĺît́ŷ. Ál̂śô ćôńŝíd̂ér̂ úŝín̂ǵ [t̂h́ê ÁM̂Ṕ p̂ĺûǵîń](https://wordpress.org/plugins/amp/)."
|