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,336 @@
|
|
|
1
|
+
// Priority Hints Audit
|
|
2
|
+
// https://webperf-snippets.nucliweb.net
|
|
3
|
+
|
|
4
|
+
(() => {
|
|
5
|
+
function isInViewport(el) {
|
|
6
|
+
const rect = el.getBoundingClientRect();
|
|
7
|
+
return (
|
|
8
|
+
rect.top < window.innerHeight &&
|
|
9
|
+
rect.bottom > 0 &&
|
|
10
|
+
rect.left < window.innerWidth &&
|
|
11
|
+
rect.right > 0 &&
|
|
12
|
+
rect.width > 0 &&
|
|
13
|
+
rect.height > 0
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function findLcpCandidate() {
|
|
18
|
+
const imgs = Array.from(document.querySelectorAll("img")).filter(isInViewport);
|
|
19
|
+
let candidate = null;
|
|
20
|
+
let maxArea = 0;
|
|
21
|
+
imgs.forEach((img) => {
|
|
22
|
+
const { width, height } = img.getBoundingClientRect();
|
|
23
|
+
const area = width * height;
|
|
24
|
+
if (area > maxArea) {
|
|
25
|
+
maxArea = area;
|
|
26
|
+
candidate = img;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function shortUrl(el) {
|
|
33
|
+
const src = el.src || el.href || el.getAttribute("href") || "";
|
|
34
|
+
return src.split("/").pop()?.split("?")[0] || src.slice(-50) || "(unknown)";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Build performance entry map for timing lookups
|
|
38
|
+
const entryMap = new Map();
|
|
39
|
+
performance.getEntriesByType("resource").forEach((e) => {
|
|
40
|
+
try {
|
|
41
|
+
entryMap.set(new URL(e.name, location.origin).href, e);
|
|
42
|
+
} catch {}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function getEntry(el) {
|
|
46
|
+
const src = el.src || el.href || el.getAttribute("href") || "";
|
|
47
|
+
if (!src) return null;
|
|
48
|
+
try {
|
|
49
|
+
return entryMap.get(new URL(src, location.origin).href) || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// All elements with fetchpriority
|
|
56
|
+
const allElements = Array.from(document.querySelectorAll("[fetchpriority]"));
|
|
57
|
+
// Exclude images ā covered by "Find Images With Lazy and Fetchpriority"
|
|
58
|
+
const nonImageElements = allElements.filter((el) => el.tagName !== "IMG");
|
|
59
|
+
const images = allElements.filter((el) => el.tagName === "IMG");
|
|
60
|
+
const preloadsWithPriority = Array.from(
|
|
61
|
+
document.querySelectorAll('link[rel="preload"][fetchpriority]')
|
|
62
|
+
);
|
|
63
|
+
const lcpCandidate = findLcpCandidate();
|
|
64
|
+
|
|
65
|
+
const issues = [];
|
|
66
|
+
|
|
67
|
+
// Issue 1: <link rel="preload"> with fetchpriority="low" (contradictory)
|
|
68
|
+
preloadsWithPriority
|
|
69
|
+
.filter((link) => link.getAttribute("fetchpriority") === "low")
|
|
70
|
+
.forEach((link) => {
|
|
71
|
+
issues.push({
|
|
72
|
+
severity: "error",
|
|
73
|
+
element: link,
|
|
74
|
+
resource: shortUrl(link),
|
|
75
|
+
message: 'preload + fetchpriority="low" is contradictory',
|
|
76
|
+
detail:
|
|
77
|
+
'preload signals this resource is critical for the current page; fetchpriority="low" contradicts that.',
|
|
78
|
+
fix: 'Remove fetchpriority="low", or use fetchpriority="high" if the resource is truly critical',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Issue 2: fetchpriority="low" on resources loaded in the first 500ms
|
|
83
|
+
allElements
|
|
84
|
+
.filter((el) => el.getAttribute("fetchpriority") === "low")
|
|
85
|
+
.forEach((el) => {
|
|
86
|
+
const entry = getEntry(el);
|
|
87
|
+
if (entry && entry.startTime < 500) {
|
|
88
|
+
issues.push({
|
|
89
|
+
severity: "warning",
|
|
90
|
+
element: el,
|
|
91
|
+
resource: shortUrl(el),
|
|
92
|
+
message: `fetchpriority="low" but loaded at ${entry.startTime.toFixed(0)}ms`,
|
|
93
|
+
detail:
|
|
94
|
+
"This resource loaded very early. If it's genuinely non-critical, the browser is deprioritizing it correctly. Otherwise, reconsider the priority.",
|
|
95
|
+
fix: 'Verify the resource is non-critical, or remove fetchpriority="low"',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Issue 3: fetchpriority="high" on resources loaded after 3000ms
|
|
101
|
+
allElements
|
|
102
|
+
.filter((el) => el.getAttribute("fetchpriority") === "high")
|
|
103
|
+
.forEach((el) => {
|
|
104
|
+
const entry = getEntry(el);
|
|
105
|
+
if (entry && entry.startTime > 3000) {
|
|
106
|
+
issues.push({
|
|
107
|
+
severity: "warning",
|
|
108
|
+
element: el,
|
|
109
|
+
resource: shortUrl(el),
|
|
110
|
+
message: `fetchpriority="high" but loaded at ${entry.startTime.toFixed(0)}ms`,
|
|
111
|
+
detail:
|
|
112
|
+
"High-priority resources should load early. A late start may indicate render-blocking dependencies or incorrect placement in the document.",
|
|
113
|
+
fix: "Move the element earlier in the HTML or investigate render-blocking dependencies",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Issue 4: LCP candidate image without fetchpriority="high"
|
|
119
|
+
if (lcpCandidate && lcpCandidate.getAttribute("fetchpriority") !== "high") {
|
|
120
|
+
const fp = lcpCandidate.getAttribute("fetchpriority");
|
|
121
|
+
issues.push({
|
|
122
|
+
severity: "warning",
|
|
123
|
+
element: lcpCandidate,
|
|
124
|
+
resource:
|
|
125
|
+
(lcpCandidate.currentSrc || lcpCandidate.src).split("/").pop()?.split("?")[0] ||
|
|
126
|
+
"LCP image",
|
|
127
|
+
message: `LCP candidate image ${fp ? `has fetchpriority="${fp}"` : "has no fetchpriority set"}`,
|
|
128
|
+
detail:
|
|
129
|
+
'The largest visible image is likely the LCP element and benefits most from fetchpriority="high".',
|
|
130
|
+
fix: 'Add fetchpriority="high" to the LCP image to improve Largest Contentful Paint',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Display results
|
|
135
|
+
console.group("%cšÆ Priority Hints Audit", "font-weight: bold; font-size: 14px;");
|
|
136
|
+
|
|
137
|
+
const highCount = allElements.filter(
|
|
138
|
+
(el) => el.getAttribute("fetchpriority") === "high"
|
|
139
|
+
).length;
|
|
140
|
+
const lowCount = allElements.filter(
|
|
141
|
+
(el) => el.getAttribute("fetchpriority") === "low"
|
|
142
|
+
).length;
|
|
143
|
+
const autoCount = allElements.filter(
|
|
144
|
+
(el) => el.getAttribute("fetchpriority") === "auto"
|
|
145
|
+
).length;
|
|
146
|
+
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log("%cInventory:", "font-weight: bold;");
|
|
149
|
+
console.log(` Total elements with fetchpriority: ${allElements.length}`);
|
|
150
|
+
console.log(
|
|
151
|
+
` ⢠Images: ${images.length} (see "Find Images With Lazy and Fetchpriority" for image-specific checks)`
|
|
152
|
+
);
|
|
153
|
+
console.log(` ⢠Non-image resources: ${nonImageElements.length}`);
|
|
154
|
+
console.log(` ⢠Preloads with fetchpriority: ${preloadsWithPriority.length}`);
|
|
155
|
+
console.log(
|
|
156
|
+
` Priority breakdown: high=${highCount}, low=${lowCount}, auto=${autoCount}`
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (allElements.length === 0 && !lcpCandidate) {
|
|
160
|
+
console.log("");
|
|
161
|
+
console.log(
|
|
162
|
+
"%cā¹ļø No fetchpriority attributes found on this page.",
|
|
163
|
+
"color: #3b82f6;"
|
|
164
|
+
);
|
|
165
|
+
console.groupEnd();
|
|
166
|
+
return { script: "Priority-Hints-Audit", status: "ok", count: 0, items: [], issues: [] };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// LCP candidate status
|
|
170
|
+
if (lcpCandidate) {
|
|
171
|
+
const fp = lcpCandidate.getAttribute("fetchpriority");
|
|
172
|
+
const status =
|
|
173
|
+
fp === "high"
|
|
174
|
+
? 'ā
fetchpriority="high"'
|
|
175
|
+
: fp
|
|
176
|
+
? `ā ļø fetchpriority="${fp}"`
|
|
177
|
+
: "ā ļø No fetchpriority set";
|
|
178
|
+
console.log("");
|
|
179
|
+
console.log("%cLCP Candidate:", "font-weight: bold;");
|
|
180
|
+
console.log(` ${status}`);
|
|
181
|
+
console.log(" Element:", lcpCandidate);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Non-image resources table
|
|
185
|
+
if (nonImageElements.length > 0) {
|
|
186
|
+
console.log("");
|
|
187
|
+
console.group(
|
|
188
|
+
`%cš Non-Image Resources with fetchpriority (${nonImageElements.length})`,
|
|
189
|
+
"font-weight: bold;"
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
console.table(
|
|
193
|
+
nonImageElements.map((el) => {
|
|
194
|
+
const entry = getEntry(el);
|
|
195
|
+
const rel = el.getAttribute("rel") || "";
|
|
196
|
+
const as = el.getAttribute("as") || "";
|
|
197
|
+
const tag =
|
|
198
|
+
el.tagName.toLowerCase() +
|
|
199
|
+
(rel ? `[rel="${rel}"]` : "") +
|
|
200
|
+
(as ? `[as="${as}"]` : "");
|
|
201
|
+
return {
|
|
202
|
+
Tag: tag,
|
|
203
|
+
Priority: el.getAttribute("fetchpriority"),
|
|
204
|
+
"Start (ms)": entry ? entry.startTime.toFixed(0) : "N/A",
|
|
205
|
+
"Size (KB)":
|
|
206
|
+
entry?.decodedBodySize
|
|
207
|
+
? (entry.decodedBodySize / 1024).toFixed(1)
|
|
208
|
+
: "N/A",
|
|
209
|
+
Resource: shortUrl(el),
|
|
210
|
+
};
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
console.log("%cš Elements:", "font-weight: bold;");
|
|
215
|
+
nonImageElements.forEach((el, i) => console.log(`${i + 1}.`, el));
|
|
216
|
+
console.groupEnd();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Preload links with fetchpriority
|
|
220
|
+
if (preloadsWithPriority.length > 0) {
|
|
221
|
+
console.log("");
|
|
222
|
+
console.group(
|
|
223
|
+
`%cš Preload Links with fetchpriority (${preloadsWithPriority.length})`,
|
|
224
|
+
"font-weight: bold;"
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
console.table(
|
|
228
|
+
preloadsWithPriority.map((link) => {
|
|
229
|
+
const fp = link.getAttribute("fetchpriority");
|
|
230
|
+
const entry = getEntry(link);
|
|
231
|
+
return {
|
|
232
|
+
fetchpriority: fp,
|
|
233
|
+
as: link.getAttribute("as") || "(missing)",
|
|
234
|
+
Status:
|
|
235
|
+
fp === "low" ? "ā ļø Conflict" : fp === "high" ? "ā
Good" : "ā¹ļø Auto",
|
|
236
|
+
"Start (ms)": entry ? entry.startTime.toFixed(0) : "N/A",
|
|
237
|
+
Resource: shortUrl(link),
|
|
238
|
+
};
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
console.groupEnd();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Issues
|
|
246
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
247
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
248
|
+
|
|
249
|
+
if (issues.length > 0) {
|
|
250
|
+
console.log("");
|
|
251
|
+
console.group(
|
|
252
|
+
`%cā ļø Issues Found (${errors.length} errors, ${warnings.length} warnings)`,
|
|
253
|
+
"color: #ef4444; font-weight: bold;"
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
[...errors, ...warnings].forEach((issue) => {
|
|
257
|
+
const color = issue.severity === "error" ? "#ef4444" : "#f59e0b";
|
|
258
|
+
const icon = issue.severity === "error" ? "š“" : "ā ļø";
|
|
259
|
+
console.log("");
|
|
260
|
+
console.log(
|
|
261
|
+
`%c${icon} ${issue.message}`,
|
|
262
|
+
`font-weight: bold; color: ${color};`
|
|
263
|
+
);
|
|
264
|
+
console.log(` ${issue.detail}`);
|
|
265
|
+
console.log(` Fix: ${issue.fix}`);
|
|
266
|
+
console.log(" Element:", issue.element);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
console.groupEnd();
|
|
270
|
+
} else {
|
|
271
|
+
console.log("");
|
|
272
|
+
console.log(
|
|
273
|
+
"%cā
No priority hints issues detected.",
|
|
274
|
+
"color: #22c55e; font-weight: bold;"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Quick reference
|
|
279
|
+
console.log("");
|
|
280
|
+
console.group(
|
|
281
|
+
"%cš fetchpriority Quick Reference",
|
|
282
|
+
"color: #3b82f6; font-weight: bold;"
|
|
283
|
+
);
|
|
284
|
+
console.log("");
|
|
285
|
+
console.log("%c ā
LCP hero image:", "color: #22c55e;");
|
|
286
|
+
console.log(
|
|
287
|
+
'%c <img src="hero.webp" fetchpriority="high" alt="...">',
|
|
288
|
+
"font-family: monospace;"
|
|
289
|
+
);
|
|
290
|
+
console.log("");
|
|
291
|
+
console.log("%c ā
Preloaded critical font:", "color: #22c55e;");
|
|
292
|
+
console.log(
|
|
293
|
+
'%c <link rel="preload" href="font.woff2" as="font" fetchpriority="high" crossorigin>',
|
|
294
|
+
"font-family: monospace;"
|
|
295
|
+
);
|
|
296
|
+
console.log("");
|
|
297
|
+
console.log("%c ā
Below-fold image:", "color: #22c55e;");
|
|
298
|
+
console.log(
|
|
299
|
+
'%c <img src="content.jpg" loading="lazy" fetchpriority="low" alt="...">',
|
|
300
|
+
"font-family: monospace;"
|
|
301
|
+
);
|
|
302
|
+
console.log("");
|
|
303
|
+
console.log("%c ā Contradictory ā preload + low priority:", "color: #ef4444;");
|
|
304
|
+
console.log(
|
|
305
|
+
'%c <link rel="preload" href="font.woff2" as="font" fetchpriority="low">',
|
|
306
|
+
"font-family: monospace;"
|
|
307
|
+
);
|
|
308
|
+
console.groupEnd();
|
|
309
|
+
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log(
|
|
312
|
+
'%cš” Related: "Find Images With Lazy and Fetchpriority" detects the lazy+high conflict on images. https://webperf-snippets.nucliweb.net/Loading/Find-Images-With-Lazy-and-Fetchpriority',
|
|
313
|
+
"color: #6b7280;"
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
console.groupEnd();
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
script: "Priority-Hints-Audit",
|
|
320
|
+
status: "ok",
|
|
321
|
+
count: allElements.length,
|
|
322
|
+
details: {
|
|
323
|
+
highCount,
|
|
324
|
+
lowCount,
|
|
325
|
+
autoCount,
|
|
326
|
+
preloadsWithPriorityCount: preloadsWithPriority.length,
|
|
327
|
+
lcpCandidateHasFetchpriority: lcpCandidate ? lcpCandidate.getAttribute("fetchpriority") === "high" : null,
|
|
328
|
+
},
|
|
329
|
+
items: allElements.map(el => ({
|
|
330
|
+
tag: el.tagName.toLowerCase(),
|
|
331
|
+
fetchpriority: el.getAttribute("fetchpriority"),
|
|
332
|
+
resource: (el.src || el.href || el.getAttribute("href") || "").split("/").pop()?.split("?")[0] || "",
|
|
333
|
+
})),
|
|
334
|
+
issues: issues.map(i => ({ severity: i.severity, message: i.message })),
|
|
335
|
+
};
|
|
336
|
+
})();
|