webperf-snippets 0.1.0

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 (67) hide show
  1. package/README.md +139 -0
  2. package/package.json +54 -0
  3. package/snippets/CoreWebVitals/CLS.js +88 -0
  4. package/snippets/CoreWebVitals/INP.js +432 -0
  5. package/snippets/CoreWebVitals/LCP-Image-Entropy.js +198 -0
  6. package/snippets/CoreWebVitals/LCP-Subparts.js +286 -0
  7. package/snippets/CoreWebVitals/LCP-Trail.js +184 -0
  8. package/snippets/CoreWebVitals/LCP-Video-Candidate.js +228 -0
  9. package/snippets/CoreWebVitals/LCP.js +135 -0
  10. package/snippets/CoreWebVitals/WORKFLOWS.md +203 -0
  11. package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js +73 -0
  12. package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js +191 -0
  13. package/snippets/Interaction/Forced-Synchronous-Layout.js +286 -0
  14. package/snippets/Interaction/Input-Latency-Breakdown.js +199 -0
  15. package/snippets/Interaction/Interactions.js +220 -0
  16. package/snippets/Interaction/Layout-Shift-Loading-and-Interaction.js +266 -0
  17. package/snippets/Interaction/Long-Animation-Frames-Helpers.js +397 -0
  18. package/snippets/Interaction/Long-Animation-Frames-Script-Attribution.js +137 -0
  19. package/snippets/Interaction/Long-Animation-Frames.js +298 -0
  20. package/snippets/Interaction/LongTask.js +203 -0
  21. package/snippets/Interaction/Scroll-Performance.js +359 -0
  22. package/snippets/Interaction/WORKFLOWS.md +205 -0
  23. package/snippets/Loading/Back-Forward-Cache.js +473 -0
  24. package/snippets/Loading/CSS-Media-Queries-Analysis.js +512 -0
  25. package/snippets/Loading/Cache-Strategy-Analysis.js +808 -0
  26. package/snippets/Loading/Client-Side-Redirect-Detection.js +337 -0
  27. package/snippets/Loading/Content-Visibility.js +352 -0
  28. package/snippets/Loading/Critical-CSS-Detection.js +329 -0
  29. package/snippets/Loading/Event-Processing-Time.js +179 -0
  30. package/snippets/Loading/FCP.js +101 -0
  31. package/snippets/Loading/Find-Above-The-Fold-Lazy-Loaded-Images.js +264 -0
  32. package/snippets/Loading/Find-Images-With-Lazy-and-Fetchpriority.js +155 -0
  33. package/snippets/Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js +300 -0
  34. package/snippets/Loading/Find-render-blocking-resources.js +161 -0
  35. package/snippets/Loading/First-And-Third-Party-Script-Info.js +269 -0
  36. package/snippets/Loading/First-And-Third-Party-Script-Timings.js +295 -0
  37. package/snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js +317 -0
  38. package/snippets/Loading/Inline-CSS-Info-and-Size.js +234 -0
  39. package/snippets/Loading/Inline-Script-Info-and-Size.js +323 -0
  40. package/snippets/Loading/JS-Execution-Time-Breakdown.js +336 -0
  41. package/snippets/Loading/Prefetch-Resource-Validation.js +456 -0
  42. package/snippets/Loading/Priority-Hints-Audit.js +336 -0
  43. package/snippets/Loading/Resource-Hints-Validation.js +503 -0
  44. package/snippets/Loading/Resource-Hints.js +400 -0
  45. package/snippets/Loading/SSR-Hydration-Data-Analysis.js +364 -0
  46. package/snippets/Loading/Script-Loading.js +332 -0
  47. package/snippets/Loading/Service-Worker-Analysis.js +192 -0
  48. package/snippets/Loading/TTFB-Resources.js +115 -0
  49. package/snippets/Loading/TTFB-Sub-Parts.js +117 -0
  50. package/snippets/Loading/TTFB.js +37 -0
  51. package/snippets/Loading/Validate-Preload-Async-Defer-Scripts.js +394 -0
  52. package/snippets/Loading/WORKFLOWS.md +163 -0
  53. package/snippets/Media/Image-Element-Audit.js +350 -0
  54. package/snippets/Media/SVG-Embedded-Bitmap-Analysis.js +225 -0
  55. package/snippets/Media/Video-Element-Audit.js +282 -0
  56. package/snippets/Media/WORKFLOWS.md +190 -0
  57. package/snippets/README.md +98 -0
  58. package/snippets/Resources/Network-Bandwidth-Connection-Quality.js +103 -0
  59. package/snippets/Resources/WORKFLOWS.md +173 -0
  60. package/src/bin.js +229 -0
  61. package/src/decision-tree.js +19 -0
  62. package/src/load-snippet.js +23 -0
  63. package/src/reporters/human.js +179 -0
  64. package/src/reporters/json.js +3 -0
  65. package/src/runner.js +61 -0
  66. package/src/workflows/audit.js +21 -0
  67. package/src/workflows/cwv.js +10 -0
@@ -0,0 +1,456 @@
1
+ // Prefetch Resource Validation
2
+ // https://webperf-snippets.nucliweb.net
3
+
4
+ (() => {
5
+ // Get all prefetch hints
6
+ const prefetchLinks = Array.from(document.querySelectorAll('link[rel="prefetch"]'));
7
+
8
+ if (prefetchLinks.length === 0) {
9
+ // Early return is safe here - console.group hasn't been opened yet
10
+ console.log(
11
+ '%c✅ No prefetch hints found (rel="prefetch").',
12
+ "color: #22c55e; font-weight: bold;",
13
+ );
14
+ console.log(
15
+ "%cℹ️ Prefetch is for future navigation resources. Use it sparingly for predictable user journeys.",
16
+ "color: #3b82f6;",
17
+ );
18
+ return { script: "Prefetch-Resource-Validation", status: "ok", count: 0, items: [], issues: [] };
19
+ }
20
+
21
+ // Get performance entries for all resources
22
+ const performanceEntries = performance.getEntriesByType("resource");
23
+
24
+ // Pre-normalize URLs for efficient matching (avoids repeated URL parsing in nested loops)
25
+ const entryUrlMap = new Map();
26
+ performanceEntries.forEach((entry) => {
27
+ try {
28
+ const normalizedUrl = new URL(entry.name, location.origin).href;
29
+ if (!entryUrlMap.has(normalizedUrl)) {
30
+ entryUrlMap.set(normalizedUrl, []);
31
+ }
32
+ entryUrlMap.get(normalizedUrl).push(entry);
33
+ } catch {
34
+ // Ignore malformed URLs in performance entries
35
+ }
36
+ });
37
+
38
+ // Valid 'as' attribute values for prefetch (per HTML spec)
39
+ const validAsValues = new Set([
40
+ "script",
41
+ "style",
42
+ "font",
43
+ "image",
44
+ "video",
45
+ "audio",
46
+ "document",
47
+ "fetch",
48
+ "track",
49
+ "worker",
50
+ // Note: "media" is not a valid spec value, use "video" or "audio" instead
51
+ ]);
52
+
53
+ // Thresholds
54
+ const THRESHOLDS = {
55
+ maxCount: 10,
56
+ largeFileSize: 500 * 1024, // 500KB
57
+ largeScriptSize: 1024 * 1024, // 1MB for scripts specifically
58
+ totalSizeWarning: 2 * 1024 * 1024, // 2MB
59
+ totalSizeCritical: 5 * 1024 * 1024, // 5MB
60
+ };
61
+
62
+ // Analyze each prefetch
63
+ const issues = [];
64
+ const validPrefetch = [];
65
+ const seenUrls = new Set(); // Track duplicates
66
+ let totalSize = 0;
67
+ let totalTransferSize = 0;
68
+
69
+ prefetchLinks.forEach((link) => {
70
+ const href = link.href;
71
+ const as = link.getAttribute("as") || "unknown";
72
+ // Extract filename from URL; fallback to full href if URL is just origin
73
+ // Note: Removes query params but preserves hash (e.g., page#section)
74
+ const shortUrl = href.split("/").pop()?.split("?")[0]?.split("#")[0] || href;
75
+
76
+ // Normalize URL for matching
77
+ let normalizedUrl;
78
+ try {
79
+ normalizedUrl = new URL(href, location.origin).href;
80
+ } catch {
81
+ // Invalid href - will be caught later
82
+ normalizedUrl = href;
83
+ }
84
+
85
+ // Find matching performance entries using pre-built map
86
+ const matchingEntries = entryUrlMap.get(normalizedUrl) || [];
87
+ const perfEntry = matchingEntries[0]; // Use first matching entry
88
+
89
+ const analysis = {
90
+ link,
91
+ href,
92
+ shortUrl,
93
+ as,
94
+ size: 0,
95
+ transferSize: 0,
96
+ duration: 0,
97
+ loaded: false,
98
+ isCurrentPage: false,
99
+ warnings: [],
100
+ };
101
+
102
+ // Check for missing or invalid 'as' attribute
103
+ if (as === "unknown") {
104
+ analysis.warnings.push({
105
+ type: "missing-as",
106
+ severity: "warning",
107
+ message: "Missing 'as' attribute - browser cannot apply correct MIME type matching",
108
+ });
109
+ } else if (!validAsValues.has(as)) {
110
+ analysis.warnings.push({
111
+ type: "invalid-as",
112
+ severity: "warning",
113
+ message: `Invalid 'as' value: "${as}" - should be one of: ${Array.from(validAsValues).join(", ")}`,
114
+ });
115
+ }
116
+
117
+ // Check for duplicate prefetch
118
+ if (seenUrls.has(normalizedUrl)) {
119
+ analysis.warnings.push({
120
+ type: "duplicate-prefetch",
121
+ severity: "warning",
122
+ message: "Duplicate prefetch - this URL is prefetched multiple times",
123
+ });
124
+ }
125
+ seenUrls.add(normalizedUrl);
126
+
127
+ // Check if resource was actually loaded
128
+ if (perfEntry) {
129
+ analysis.loaded = true;
130
+ analysis.size = perfEntry.decodedBodySize || 0;
131
+ analysis.transferSize = perfEntry.transferSize || 0;
132
+ analysis.duration = perfEntry.duration || 0;
133
+
134
+ // Cache status detection with CORS considerations
135
+ // transferSize === 0 can mean: (1) cached, or (2) cross-origin without Timing-Allow-Origin
136
+ if (perfEntry.encodedBodySize > 0 && perfEntry.transferSize === 0) {
137
+ analysis.cacheStatus = "cached";
138
+ } else if (perfEntry.encodedBodySize === 0 && perfEntry.transferSize === 0) {
139
+ // Both zero = likely CORS-blocked timing info
140
+ analysis.cacheStatus = "unknown (CORS)";
141
+ } else {
142
+ analysis.cacheStatus = "network";
143
+ }
144
+
145
+ totalSize += analysis.size;
146
+ totalTransferSize += analysis.transferSize;
147
+
148
+ // Check if it's a current page resource
149
+ // Uses 5s heuristic: resources loaded in first 5s are likely for current page, not prefetch
150
+ // This cutoff works for most pages but may flag slow pages incorrectly
151
+ const isCurrentPageResource = matchingEntries.some(
152
+ (entry) => entry.initiatorType !== "link" && entry.startTime < 5000,
153
+ );
154
+
155
+ if (isCurrentPageResource) {
156
+ analysis.isCurrentPage = true;
157
+ analysis.warnings.push({
158
+ type: "wrong-hint",
159
+ severity: "error",
160
+ message: "Resource used on current page - should use preload instead",
161
+ });
162
+ }
163
+
164
+ // Check file size
165
+ if (analysis.size > THRESHOLDS.largeFileSize) {
166
+ const sizeMB = (analysis.size / (1024 * 1024)).toFixed(2);
167
+ analysis.warnings.push({
168
+ type: "large-file",
169
+ severity: "warning",
170
+ message: `Large file (${sizeMB}MB) - consider if prefetch is appropriate`,
171
+ });
172
+ }
173
+
174
+ // Check resource type appropriateness (size-based)
175
+ // Note: Only checks if resource has loaded (has perfEntry with size)
176
+ let isInappropriate = false;
177
+ let inappropriateReason = "";
178
+
179
+ if (as === "video" || as === "audio") {
180
+ isInappropriate = true;
181
+ inappropriateReason = "Video/audio files are typically too large for prefetch";
182
+ } else if (as === "image" && analysis.size > 200 * 1024) {
183
+ isInappropriate = true;
184
+ inappropriateReason = `Image is ${(analysis.size / 1024).toFixed(0)}KB (>200KB threshold)`;
185
+ } else if (as === "script" && analysis.size > THRESHOLDS.largeScriptSize) {
186
+ isInappropriate = true;
187
+ inappropriateReason = `Script is ${(analysis.size / 1024).toFixed(0)}KB (>1MB threshold)`;
188
+ }
189
+
190
+ if (isInappropriate) {
191
+ analysis.warnings.push({
192
+ type: "inappropriate-type",
193
+ severity: "warning",
194
+ message: `Type "${as}" may not be suitable for prefetch: ${inappropriateReason}`,
195
+ });
196
+ }
197
+ } else {
198
+ // Prefetch defined but not loaded yet (or failed)
199
+ analysis.warnings.push({
200
+ type: "not-loaded",
201
+ severity: "info",
202
+ message: "Not loaded yet or failed to load",
203
+ });
204
+ }
205
+
206
+ // Categorize
207
+ if (analysis.warnings.length > 0) {
208
+ issues.push(analysis);
209
+ } else {
210
+ validPrefetch.push(analysis);
211
+ }
212
+ });
213
+
214
+ // Check total count
215
+ const countIssues = [];
216
+ if (prefetchLinks.length > THRESHOLDS.maxCount) {
217
+ countIssues.push({
218
+ type: "excessive-count",
219
+ severity: "warning",
220
+ message: `${prefetchLinks.length} prefetch hints found (recommended: <${THRESHOLDS.maxCount})`,
221
+ explanation: "Excessive prefetch can waste bandwidth, especially on mobile networks",
222
+ });
223
+ }
224
+
225
+ // Check total size
226
+ const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
227
+ const totalTransferMB = (totalTransferSize / (1024 * 1024)).toFixed(2);
228
+
229
+ if (totalSize > THRESHOLDS.totalSizeCritical) {
230
+ countIssues.push({
231
+ type: "excessive-size",
232
+ severity: "error",
233
+ message: `Total prefetch size: ${totalSizeMB}MB (critical threshold exceeded)`,
234
+ explanation: "This is very high and will significantly impact mobile users",
235
+ });
236
+ } else if (totalSize > THRESHOLDS.totalSizeWarning) {
237
+ countIssues.push({
238
+ type: "high-size",
239
+ severity: "warning",
240
+ message: `Total prefetch size: ${totalSizeMB}MB (warning threshold exceeded)`,
241
+ explanation: "Consider reducing prefetch to improve mobile experience",
242
+ });
243
+ }
244
+
245
+ // Display results
246
+ console.group("%c🔍 Prefetch Resource Validation", "font-weight: bold; font-size: 14px;");
247
+
248
+ // Count actually loaded resources
249
+ const loadedCount = [...issues, ...validPrefetch].filter((item) => item.loaded).length;
250
+
251
+ // Count meaningful issues (exclude "info" severity)
252
+ const meaningfulIssues = issues.filter((i) =>
253
+ i.warnings.some((w) => w.severity === "error" || w.severity === "warning"),
254
+ );
255
+ const totalMeaningfulIssues = meaningfulIssues.length + countIssues.length;
256
+
257
+ console.log("");
258
+ console.log("%cSummary:", "font-weight: bold;");
259
+ console.log(` Total prefetch hints: ${prefetchLinks.length}`);
260
+ console.log(` Loaded resources: ${loadedCount}`);
261
+ console.log(` Total size: ${totalSizeMB} MB (decompressed)`);
262
+ console.log(` Transfer size: ${totalTransferMB} MB (over network)`);
263
+ console.log(` Issues found: ${totalMeaningfulIssues} (excluding "not loaded" info)`);
264
+
265
+ // Show count/size issues first
266
+ if (countIssues.length > 0) {
267
+ console.log("");
268
+ console.group(
269
+ `%c⚠️ Overall Issues (${countIssues.length})`,
270
+ "color: #ef4444; font-weight: bold;",
271
+ );
272
+
273
+ countIssues.forEach((issue) => {
274
+ const color = issue.severity === "error" ? "#ef4444" : "#f59e0b";
275
+ const icon = issue.severity === "error" ? "🔴" : "⚠️";
276
+ console.log("");
277
+ console.log(`%c${icon} ${issue.message}`, `color: ${color}; font-weight: bold;`);
278
+ console.log(` ${issue.explanation}`);
279
+ });
280
+
281
+ console.groupEnd();
282
+ }
283
+
284
+ // Show resource-specific issues
285
+ const errors = issues.filter((i) => i.warnings.some((w) => w.severity === "error"));
286
+ const warnings = issues.filter(
287
+ (i) =>
288
+ !i.warnings.some((w) => w.severity === "error") &&
289
+ i.warnings.some((w) => w.severity === "warning"),
290
+ );
291
+ const infos = issues.filter((i) => i.warnings.every((w) => w.severity === "info"));
292
+
293
+ if (errors.length > 0) {
294
+ console.log("");
295
+ console.group(`%c🔴 Critical Issues (${errors.length})`, "color: #ef4444; font-weight: bold;");
296
+
297
+ const errorTable = errors.map((item) => ({
298
+ Resource: item.shortUrl,
299
+ Type: item.as,
300
+ Size: item.size > 0 ? `${(item.size / 1024).toFixed(1)} KB` : "Unknown",
301
+ Duration: item.duration > 0 ? `${item.duration.toFixed(0)} ms` : "N/A",
302
+ Issue: item.warnings.map((w) => w.message).join("; "),
303
+ }));
304
+
305
+ console.table(errorTable);
306
+
307
+ console.log("");
308
+ console.log("%cDetails:", "font-weight: bold;");
309
+ errors.forEach((item, i) => {
310
+ console.log("");
311
+ console.log(`%c${i + 1}. ${item.shortUrl}`, "font-weight: bold;");
312
+ console.log(` URL: ${item.href}`);
313
+ console.log(` Type: ${item.as}`);
314
+ if (item.size > 0) {
315
+ console.log(` Size: ${(item.size / 1024).toFixed(1)} KB`);
316
+ }
317
+ item.warnings.forEach((w) => {
318
+ console.log(` ❌ ${w.message}`);
319
+ });
320
+ console.log(" Element:", item.link);
321
+ });
322
+
323
+ console.groupEnd();
324
+ }
325
+
326
+ if (warnings.length > 0) {
327
+ console.log("");
328
+ console.group(`%c⚠️ Warnings (${warnings.length})`, "color: #f59e0b; font-weight: bold;");
329
+
330
+ const warningTable = warnings.map((item) => ({
331
+ Resource: item.shortUrl,
332
+ Type: item.as,
333
+ Size: item.size > 0 ? `${(item.size / 1024).toFixed(1)} KB` : "Unknown",
334
+ Duration: item.duration > 0 ? `${item.duration.toFixed(0)} ms` : "N/A",
335
+ Issue: item.warnings.map((w) => w.message).join("; "),
336
+ }));
337
+
338
+ console.table(warningTable);
339
+
340
+ console.groupEnd();
341
+ }
342
+
343
+ if (infos.length > 0) {
344
+ console.log("");
345
+ console.group(`%cℹ️ Info (${infos.length})`, "color: #3b82f6; font-weight: bold;");
346
+
347
+ const infoTable = infos.map((item) => ({
348
+ Resource: item.shortUrl,
349
+ Type: item.as,
350
+ Status: item.warnings.map((w) => w.message).join("; "),
351
+ }));
352
+
353
+ console.table(infoTable);
354
+
355
+ console.groupEnd();
356
+ }
357
+
358
+ // Show valid prefetch
359
+ if (validPrefetch.length > 0) {
360
+ console.log("");
361
+ console.group(
362
+ `%c✅ Valid Prefetch (${validPrefetch.length})`,
363
+ "color: #22c55e; font-weight: bold;",
364
+ );
365
+
366
+ const validTable = validPrefetch.map((item) => ({
367
+ Resource: item.shortUrl,
368
+ Type: item.as,
369
+ Size: item.size > 0 ? `${(item.size / 1024).toFixed(1)} KB` : "Unknown",
370
+ Duration: item.duration > 0 ? `${item.duration.toFixed(0)} ms` : "N/A",
371
+ Status: item.loaded ? "✅ Loaded" : "Pending",
372
+ Cache: item.cacheStatus || "N/A",
373
+ }));
374
+
375
+ console.table(validTable);
376
+
377
+ console.groupEnd();
378
+ }
379
+
380
+ // Best practices
381
+ console.log("");
382
+ console.group("%c📝 Best Practices", "color: #3b82f6; font-weight: bold;");
383
+ console.log("");
384
+ console.log("%c✅ Good uses of prefetch:", "font-weight: bold; color: #22c55e;");
385
+ console.log(" • Next page chunks in predictable navigation (e.g., multi-step forms)");
386
+ console.log(" • Fonts used on commonly visited next pages");
387
+ console.log(" • Small scripts/styles for main navigation targets");
388
+ console.log(" • Resources based on user behavior analytics");
389
+ console.log("");
390
+ console.log("%c❌ Bad uses of prefetch:", "font-weight: bold; color: #ef4444;");
391
+ console.log(" • Resources needed on current page (use preload instead)");
392
+ console.log(" • Large images/videos without user intent analysis");
393
+ console.log(" • Excessive prefetch (>10 resources) hurting mobile users");
394
+ console.log(" • Resources without proper cache headers");
395
+ console.log("");
396
+ console.log("%cRecommended thresholds:", "font-weight: bold;");
397
+ console.log(` • Resource count: <${THRESHOLDS.maxCount} resources`);
398
+ console.log(` • Individual file size: <${THRESHOLDS.largeFileSize / 1024} KB`);
399
+ console.log(` • Total prefetch size: <${THRESHOLDS.totalSizeWarning / (1024 * 1024)} MB`);
400
+ console.log("");
401
+ console.log("%cCode examples:", "font-weight: bold;");
402
+ console.log("");
403
+ console.log("%c ✅ Good: Prefetch next page script", "color: #22c55e;");
404
+ console.log(
405
+ '%c <link rel="prefetch" href="/checkout-page.js" as="script">',
406
+ "font-family: monospace;",
407
+ );
408
+ console.log("");
409
+ console.log("%c ❌ Bad: Prefetch current page resource", "color: #ef4444;");
410
+ console.log(
411
+ '%c <link rel="prefetch" href="/hero-image.jpg" as="image">',
412
+ "font-family: monospace;",
413
+ );
414
+ console.log('%c <!-- Should be: rel="preload" -->', "font-family: monospace; color: #22c55e;");
415
+
416
+ console.groupEnd();
417
+
418
+ // Summary
419
+ if (totalMeaningfulIssues === 0) {
420
+ console.log("");
421
+ console.log(
422
+ "%c✅ Great! No issues detected with prefetch usage.",
423
+ "color: #22c55e; font-weight: bold; font-size: 14px;",
424
+ );
425
+ console.log(
426
+ "%cAll prefetch hints appear appropriate for future navigation.",
427
+ "color: #22c55e;",
428
+ );
429
+ } else {
430
+ console.log("");
431
+ console.log(
432
+ `%c⚠️ Found ${totalMeaningfulIssues} issue(s). Review recommendations above.`,
433
+ "color: #ef4444; font-weight: bold;",
434
+ );
435
+ }
436
+
437
+ console.groupEnd();
438
+
439
+ return {
440
+ script: "Prefetch-Resource-Validation",
441
+ status: "ok",
442
+ count: prefetchLinks.length,
443
+ details: {
444
+ loadedCount,
445
+ validCount: validPrefetch.length,
446
+ issueCount: totalMeaningfulIssues,
447
+ totalSizeBytes: totalSize,
448
+ totalTransferBytes: totalTransferSize,
449
+ },
450
+ items: [...validPrefetch, ...issues].map(item => ({ href: item.shortUrl, as: item.as, sizeBytes: item.size, loaded: item.loaded, isCurrentPage: item.isCurrentPage || false })),
451
+ issues: [
452
+ ...countIssues.map(i => ({ severity: i.severity === "error" ? "error" : "warning", message: i.message })),
453
+ ...issues.flatMap(item => item.warnings.filter(w => w.severity !== "info").map(w => ({ severity: w.severity, message: `${item.shortUrl}: ${w.message}` }))),
454
+ ],
455
+ };
456
+ })();