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.
- package/README.md +139 -0
- package/package.json +54 -0
- package/snippets/CoreWebVitals/CLS.js +88 -0
- package/snippets/CoreWebVitals/INP.js +432 -0
- package/snippets/CoreWebVitals/LCP-Image-Entropy.js +198 -0
- package/snippets/CoreWebVitals/LCP-Subparts.js +286 -0
- package/snippets/CoreWebVitals/LCP-Trail.js +184 -0
- package/snippets/CoreWebVitals/LCP-Video-Candidate.js +228 -0
- package/snippets/CoreWebVitals/LCP.js +135 -0
- package/snippets/CoreWebVitals/WORKFLOWS.md +203 -0
- package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js +73 -0
- package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js +191 -0
- package/snippets/Interaction/Forced-Synchronous-Layout.js +286 -0
- package/snippets/Interaction/Input-Latency-Breakdown.js +199 -0
- package/snippets/Interaction/Interactions.js +220 -0
- package/snippets/Interaction/Layout-Shift-Loading-and-Interaction.js +266 -0
- package/snippets/Interaction/Long-Animation-Frames-Helpers.js +397 -0
- package/snippets/Interaction/Long-Animation-Frames-Script-Attribution.js +137 -0
- package/snippets/Interaction/Long-Animation-Frames.js +298 -0
- package/snippets/Interaction/LongTask.js +203 -0
- package/snippets/Interaction/Scroll-Performance.js +359 -0
- package/snippets/Interaction/WORKFLOWS.md +205 -0
- package/snippets/Loading/Back-Forward-Cache.js +473 -0
- package/snippets/Loading/CSS-Media-Queries-Analysis.js +512 -0
- package/snippets/Loading/Cache-Strategy-Analysis.js +808 -0
- package/snippets/Loading/Client-Side-Redirect-Detection.js +337 -0
- package/snippets/Loading/Content-Visibility.js +352 -0
- package/snippets/Loading/Critical-CSS-Detection.js +329 -0
- package/snippets/Loading/Event-Processing-Time.js +179 -0
- package/snippets/Loading/FCP.js +101 -0
- package/snippets/Loading/Find-Above-The-Fold-Lazy-Loaded-Images.js +264 -0
- package/snippets/Loading/Find-Images-With-Lazy-and-Fetchpriority.js +155 -0
- package/snippets/Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js +300 -0
- package/snippets/Loading/Find-render-blocking-resources.js +161 -0
- package/snippets/Loading/First-And-Third-Party-Script-Info.js +269 -0
- package/snippets/Loading/First-And-Third-Party-Script-Timings.js +295 -0
- package/snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js +317 -0
- package/snippets/Loading/Inline-CSS-Info-and-Size.js +234 -0
- package/snippets/Loading/Inline-Script-Info-and-Size.js +323 -0
- package/snippets/Loading/JS-Execution-Time-Breakdown.js +336 -0
- package/snippets/Loading/Prefetch-Resource-Validation.js +456 -0
- package/snippets/Loading/Priority-Hints-Audit.js +336 -0
- package/snippets/Loading/Resource-Hints-Validation.js +503 -0
- package/snippets/Loading/Resource-Hints.js +400 -0
- package/snippets/Loading/SSR-Hydration-Data-Analysis.js +364 -0
- package/snippets/Loading/Script-Loading.js +332 -0
- package/snippets/Loading/Service-Worker-Analysis.js +192 -0
- package/snippets/Loading/TTFB-Resources.js +115 -0
- package/snippets/Loading/TTFB-Sub-Parts.js +117 -0
- package/snippets/Loading/TTFB.js +37 -0
- package/snippets/Loading/Validate-Preload-Async-Defer-Scripts.js +394 -0
- package/snippets/Loading/WORKFLOWS.md +163 -0
- package/snippets/Media/Image-Element-Audit.js +350 -0
- package/snippets/Media/SVG-Embedded-Bitmap-Analysis.js +225 -0
- package/snippets/Media/Video-Element-Audit.js +282 -0
- package/snippets/Media/WORKFLOWS.md +190 -0
- package/snippets/README.md +98 -0
- package/snippets/Resources/Network-Bandwidth-Connection-Quality.js +103 -0
- package/snippets/Resources/WORKFLOWS.md +173 -0
- package/src/bin.js +229 -0
- package/src/decision-tree.js +19 -0
- package/src/load-snippet.js +23 -0
- package/src/reporters/human.js +179 -0
- package/src/reporters/json.js +3 -0
- package/src/runner.js +61 -0
- package/src/workflows/audit.js +21 -0
- package/src/workflows/cwv.js +10 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// Resource Hints Analysis
|
|
2
|
+
// https://webperf-snippets.nucliweb.net
|
|
3
|
+
|
|
4
|
+
(() => {
|
|
5
|
+
const formatBytes = (bytes) => {
|
|
6
|
+
if (!bytes || bytes === 0) return "-";
|
|
7
|
+
const k = 1024;
|
|
8
|
+
const sizes = ["B", "KB", "MB"];
|
|
9
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
10
|
+
return (bytes / Math.pow(k, i)).toFixed(1) + " " + sizes[i];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Resource hint definitions
|
|
14
|
+
const hintTypes = [
|
|
15
|
+
{
|
|
16
|
+
rel: "preload",
|
|
17
|
+
icon: "ā”",
|
|
18
|
+
description: "Critical resource for current page",
|
|
19
|
+
requiresAs: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
rel: "modulepreload",
|
|
23
|
+
icon: "š¦",
|
|
24
|
+
description: "ES module for current page",
|
|
25
|
+
requiresAs: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
rel: "preconnect",
|
|
29
|
+
icon: "š",
|
|
30
|
+
description: "Early connection to origin",
|
|
31
|
+
requiresAs: false,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rel: "dns-prefetch",
|
|
35
|
+
icon: "š",
|
|
36
|
+
description: "DNS lookup for origin",
|
|
37
|
+
requiresAs: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
rel: "prefetch",
|
|
41
|
+
icon: "š„",
|
|
42
|
+
description: "Resource for future navigation",
|
|
43
|
+
requiresAs: false,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
rel: "prerender",
|
|
47
|
+
icon: "š¼ļø",
|
|
48
|
+
description: "Pre-render page",
|
|
49
|
+
requiresAs: false,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Get all link elements with resource hints
|
|
54
|
+
const allHints = [];
|
|
55
|
+
const issues = [];
|
|
56
|
+
|
|
57
|
+
hintTypes.forEach((type) => {
|
|
58
|
+
const links = document.querySelectorAll(`link[rel="${type.rel}"]`);
|
|
59
|
+
links.forEach((link) => {
|
|
60
|
+
const href = link.href || link.getAttribute("href") || "";
|
|
61
|
+
let hostname = "";
|
|
62
|
+
try {
|
|
63
|
+
hostname = new URL(href, location.origin).hostname;
|
|
64
|
+
} catch {}
|
|
65
|
+
|
|
66
|
+
const hint = {
|
|
67
|
+
rel: type.rel,
|
|
68
|
+
icon: type.icon,
|
|
69
|
+
href,
|
|
70
|
+
shortHref: href.length > 50 ? "..." + href.slice(-47) : href,
|
|
71
|
+
hostname,
|
|
72
|
+
as: link.getAttribute("as") || "",
|
|
73
|
+
crossorigin: link.crossOrigin || link.getAttribute("crossorigin") || "",
|
|
74
|
+
fetchpriority: link.fetchPriority || link.getAttribute("fetchpriority") || "",
|
|
75
|
+
type: link.type || "",
|
|
76
|
+
media: link.media || "",
|
|
77
|
+
element: link,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
allHints.push(hint);
|
|
81
|
+
|
|
82
|
+
// Check for issues
|
|
83
|
+
if (type.requiresAs && !hint.as) {
|
|
84
|
+
issues.push({
|
|
85
|
+
hint,
|
|
86
|
+
issue: "Missing 'as' attribute (required for preload)",
|
|
87
|
+
severity: "error",
|
|
88
|
+
fix: `Add as="script|style|font|image|fetch"`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hint.as === "font" && !hint.crossorigin) {
|
|
93
|
+
issues.push({
|
|
94
|
+
hint,
|
|
95
|
+
issue: "Font preload without crossorigin (causes double fetch)",
|
|
96
|
+
severity: "error",
|
|
97
|
+
fix: 'Add crossorigin="anonymous"',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (type.rel === "preconnect" && !hint.crossorigin) {
|
|
102
|
+
// Check if we load CORS resources from this origin
|
|
103
|
+
const corsResources = performance
|
|
104
|
+
.getEntriesByType("resource")
|
|
105
|
+
.filter(
|
|
106
|
+
(r) =>
|
|
107
|
+
r.name.includes(hostname) &&
|
|
108
|
+
(r.initiatorType === "script" ||
|
|
109
|
+
r.initiatorType === "css" ||
|
|
110
|
+
r.initiatorType === "font")
|
|
111
|
+
);
|
|
112
|
+
if (corsResources.length > 0) {
|
|
113
|
+
issues.push({
|
|
114
|
+
hint,
|
|
115
|
+
issue: "Preconnect may need crossorigin for CORS resources",
|
|
116
|
+
severity: "warning",
|
|
117
|
+
fix: 'Consider adding crossorigin="anonymous"',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Check for unused preloads
|
|
125
|
+
const preloads = allHints.filter((h) => h.rel === "preload");
|
|
126
|
+
const loadedResources = performance.getEntriesByType("resource");
|
|
127
|
+
|
|
128
|
+
preloads.forEach((preload) => {
|
|
129
|
+
const wasUsed = loadedResources.some((r) => r.name === preload.href);
|
|
130
|
+
if (!wasUsed) {
|
|
131
|
+
issues.push({
|
|
132
|
+
hint: preload,
|
|
133
|
+
issue: "Preloaded resource not used within page load",
|
|
134
|
+
severity: "warning",
|
|
135
|
+
fix: "Remove unused preload or verify it's needed",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Check for preconnects without loaded resources
|
|
141
|
+
const preconnects = allHints.filter(
|
|
142
|
+
(h) => h.rel === "preconnect" || h.rel === "dns-prefetch"
|
|
143
|
+
);
|
|
144
|
+
preconnects.forEach((pc) => {
|
|
145
|
+
const hasResources = loadedResources.some((r) => {
|
|
146
|
+
try {
|
|
147
|
+
return new URL(r.name).hostname === pc.hostname;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (!hasResources && pc.hostname) {
|
|
153
|
+
issues.push({
|
|
154
|
+
hint: pc,
|
|
155
|
+
issue: `No resources loaded from ${pc.hostname}`,
|
|
156
|
+
severity: "warning",
|
|
157
|
+
fix: "Remove unused preconnect or verify it's needed for future resources",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Check for too many preloads
|
|
163
|
+
if (preloads.length > 6) {
|
|
164
|
+
issues.push({
|
|
165
|
+
hint: { rel: "preload", href: `${preloads.length} preloads` },
|
|
166
|
+
issue: `Too many preloads (${preloads.length}) - they compete for bandwidth`,
|
|
167
|
+
severity: "warning",
|
|
168
|
+
fix: "Limit to 5-6 most critical resources",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Get third-party origins that could benefit from preconnect
|
|
173
|
+
const thirdPartyOrigins = new Map();
|
|
174
|
+
loadedResources.forEach((r) => {
|
|
175
|
+
try {
|
|
176
|
+
const url = new URL(r.name);
|
|
177
|
+
if (url.hostname !== location.hostname) {
|
|
178
|
+
if (!thirdPartyOrigins.has(url.origin)) {
|
|
179
|
+
thirdPartyOrigins.set(url.origin, { count: 0, size: 0, types: new Set() });
|
|
180
|
+
}
|
|
181
|
+
const data = thirdPartyOrigins.get(url.origin);
|
|
182
|
+
data.count++;
|
|
183
|
+
data.size += r.transferSize || 0;
|
|
184
|
+
data.types.add(r.initiatorType);
|
|
185
|
+
}
|
|
186
|
+
} catch {}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Find origins without preconnect
|
|
190
|
+
const preconnectedOrigins = new Set(
|
|
191
|
+
[...preconnects].map((p) => {
|
|
192
|
+
try {
|
|
193
|
+
return new URL(p.href).origin;
|
|
194
|
+
} catch {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const missingPreconnects = [];
|
|
201
|
+
thirdPartyOrigins.forEach((data, origin) => {
|
|
202
|
+
if (!preconnectedOrigins.has(origin) && data.count >= 2) {
|
|
203
|
+
missingPreconnects.push({ origin, ...data });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Display results
|
|
208
|
+
console.group("%cš Resource Hints Analysis", "font-weight: bold; font-size: 14px;");
|
|
209
|
+
|
|
210
|
+
// Summary
|
|
211
|
+
console.log("");
|
|
212
|
+
console.log("%cSummary:", "font-weight: bold;");
|
|
213
|
+
|
|
214
|
+
const summary = hintTypes.map((type) => {
|
|
215
|
+
const count = allHints.filter((h) => h.rel === type.rel).length;
|
|
216
|
+
return { type: type.rel, count, icon: type.icon };
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
summary.forEach((s) => {
|
|
220
|
+
const status = s.count > 0 ? "š¢" : "āŖ";
|
|
221
|
+
console.log(` ${status} ${s.icon} ${s.type}: ${s.count}`);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
console.log(` Total hints: ${allHints.length}`);
|
|
225
|
+
|
|
226
|
+
// Details by type
|
|
227
|
+
hintTypes.forEach((type) => {
|
|
228
|
+
const hints = allHints.filter((h) => h.rel === type.rel);
|
|
229
|
+
if (hints.length === 0) return;
|
|
230
|
+
|
|
231
|
+
console.log("");
|
|
232
|
+
console.group(
|
|
233
|
+
`%c${type.icon} ${type.rel} (${hints.length})`,
|
|
234
|
+
"color: #3b82f6; font-weight: bold;"
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const tableData = hints.map((h) => {
|
|
238
|
+
const row = {
|
|
239
|
+
Resource: h.shortHref,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
if (type.rel === "preload" || type.rel === "modulepreload") {
|
|
243
|
+
row.As = h.as || "ā ļø missing";
|
|
244
|
+
row.Crossorigin = h.crossorigin || "-";
|
|
245
|
+
if (h.fetchpriority) row.Priority = h.fetchpriority;
|
|
246
|
+
|
|
247
|
+
// Check if used
|
|
248
|
+
const used = loadedResources.some((r) => r.name === h.href);
|
|
249
|
+
row.Used = used ? "ā
" : "ā ļø No";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (type.rel === "preconnect" || type.rel === "dns-prefetch") {
|
|
253
|
+
row.Origin = h.hostname;
|
|
254
|
+
row.Crossorigin = h.crossorigin || "-";
|
|
255
|
+
|
|
256
|
+
// Check resources from this origin
|
|
257
|
+
const originResources = loadedResources.filter((r) => {
|
|
258
|
+
try {
|
|
259
|
+
return new URL(r.name).hostname === h.hostname;
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
row["Resources Loaded"] = originResources.length;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return row;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
console.table(tableData);
|
|
271
|
+
|
|
272
|
+
// Elements for inspection
|
|
273
|
+
console.log("%cš Elements:", "font-weight: bold;");
|
|
274
|
+
hints.forEach((h, i) => {
|
|
275
|
+
console.log(`${i + 1}.`, h.element);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
console.groupEnd();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Issues
|
|
282
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
283
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
284
|
+
|
|
285
|
+
if (issues.length > 0) {
|
|
286
|
+
console.log("");
|
|
287
|
+
console.group(
|
|
288
|
+
`%cā ļø Issues Found (${errors.length} errors, ${warnings.length} warnings)`,
|
|
289
|
+
"color: #ef4444; font-weight: bold;"
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (errors.length > 0) {
|
|
293
|
+
console.log("");
|
|
294
|
+
console.log("%cš“ Errors:", "font-weight: bold; color: #ef4444;");
|
|
295
|
+
errors.forEach((issue) => {
|
|
296
|
+
console.log(` ${issue.hint.rel}: ${issue.issue}`);
|
|
297
|
+
console.log(` Resource: ${issue.hint.href}`);
|
|
298
|
+
console.log(` Fix: ${issue.fix}`);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (warnings.length > 0) {
|
|
303
|
+
console.log("");
|
|
304
|
+
console.log("%cš” Warnings:", "font-weight: bold; color: #f59e0b;");
|
|
305
|
+
warnings.forEach((issue) => {
|
|
306
|
+
console.log(` ${issue.hint.rel}: ${issue.issue}`);
|
|
307
|
+
if (issue.hint.href) {
|
|
308
|
+
console.log(` Resource: ${issue.hint.href}`);
|
|
309
|
+
}
|
|
310
|
+
console.log(` Fix: ${issue.fix}`);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.groupEnd();
|
|
315
|
+
} else {
|
|
316
|
+
console.log("");
|
|
317
|
+
console.log(
|
|
318
|
+
"%cā
No issues found with resource hints.",
|
|
319
|
+
"color: #22c55e; font-weight: bold;"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Recommendations
|
|
324
|
+
if (missingPreconnects.length > 0) {
|
|
325
|
+
console.log("");
|
|
326
|
+
console.group("%cš” Optimization Opportunities", "color: #3b82f6; font-weight: bold;");
|
|
327
|
+
|
|
328
|
+
console.log("");
|
|
329
|
+
console.log("%cThird-party origins without preconnect:", "font-weight: bold;");
|
|
330
|
+
console.log("These origins load multiple resources and could benefit from preconnect:");
|
|
331
|
+
console.log("");
|
|
332
|
+
|
|
333
|
+
const recTable = missingPreconnects
|
|
334
|
+
.sort((a, b) => b.count - a.count)
|
|
335
|
+
.slice(0, 5)
|
|
336
|
+
.map((o) => ({
|
|
337
|
+
Origin: o.origin,
|
|
338
|
+
Resources: o.count,
|
|
339
|
+
Size: formatBytes(o.size),
|
|
340
|
+
Types: Array.from(o.types).join(", "),
|
|
341
|
+
}));
|
|
342
|
+
console.table(recTable);
|
|
343
|
+
|
|
344
|
+
console.log("");
|
|
345
|
+
console.log("%cAdd preconnect for critical origins:", "font-weight: bold;");
|
|
346
|
+
missingPreconnects.slice(0, 3).forEach((o) => {
|
|
347
|
+
console.log(
|
|
348
|
+
`%c<link rel="preconnect" href="${o.origin}" crossorigin>`,
|
|
349
|
+
"font-family: monospace; color: #22c55e;"
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
console.groupEnd();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Best practices
|
|
357
|
+
console.log("");
|
|
358
|
+
console.group("%cš Best Practices", "color: #3b82f6; font-weight: bold;");
|
|
359
|
+
console.log("");
|
|
360
|
+
console.log("%cPreload (current page critical resources):", "font-weight: bold;");
|
|
361
|
+
console.log(
|
|
362
|
+
'%c<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>',
|
|
363
|
+
"font-family: monospace; color: #22c55e;"
|
|
364
|
+
);
|
|
365
|
+
console.log(
|
|
366
|
+
'%c<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">',
|
|
367
|
+
"font-family: monospace; color: #22c55e;"
|
|
368
|
+
);
|
|
369
|
+
console.log("");
|
|
370
|
+
console.log("%cPreconnect (third-party origins):", "font-weight: bold;");
|
|
371
|
+
console.log(
|
|
372
|
+
'%c<link rel="preconnect" href="https://fonts.googleapis.com">',
|
|
373
|
+
"font-family: monospace; color: #22c55e;"
|
|
374
|
+
);
|
|
375
|
+
console.log(
|
|
376
|
+
'%c<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
|
|
377
|
+
"font-family: monospace; color: #22c55e;"
|
|
378
|
+
);
|
|
379
|
+
console.log("");
|
|
380
|
+
console.log("%cPrefetch (next page resources):", "font-weight: bold;");
|
|
381
|
+
console.log(
|
|
382
|
+
'%c<link rel="prefetch" href="/next-page.js" as="script">',
|
|
383
|
+
"font-family: monospace; color: #22c55e;"
|
|
384
|
+
);
|
|
385
|
+
console.groupEnd();
|
|
386
|
+
|
|
387
|
+
console.groupEnd();
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
script: "Resource-Hints",
|
|
391
|
+
status: "ok",
|
|
392
|
+
count: allHints.length,
|
|
393
|
+
details: {
|
|
394
|
+
byType: Object.fromEntries(hintTypes.map(t => [t.rel, allHints.filter(h => h.rel === t.rel).length])),
|
|
395
|
+
missingPreconnectsCount: missingPreconnects.length,
|
|
396
|
+
},
|
|
397
|
+
items: allHints.map(h => ({ rel: h.rel, href: h.shortHref, as: h.as, crossorigin: h.crossorigin, fetchpriority: h.fetchpriority })),
|
|
398
|
+
issues: issues.map(i => ({ severity: i.severity, message: `${i.hint.rel}: ${i.issue}. Fix: ${i.fix}` })),
|
|
399
|
+
};
|
|
400
|
+
})();
|