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,282 @@
1
+ // Video Element 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 detectBestCodec(video) {
18
+ const sources = Array.from(video.querySelectorAll("source"));
19
+ const types = sources
20
+ .map((s) => s.getAttribute("type") || "")
21
+ .filter(Boolean)
22
+ .map((t) => t.toLowerCase());
23
+
24
+ const allSrcs = [
25
+ (video.currentSrc || "").toLowerCase(),
26
+ (video.getAttribute("src") || "").toLowerCase(),
27
+ ...sources.map((s) => (s.getAttribute("src") || "").toLowerCase()),
28
+ ];
29
+
30
+ if (types.some((t) => t.includes("av01") || t.includes("av1"))) return "AV1";
31
+ if (types.some((t) => t.includes("vp9"))) return "VP9";
32
+ if (types.some((t) => t.includes("webm")) || allSrcs.some((s) => s.endsWith(".webm")))
33
+ return "WebM";
34
+ if (
35
+ types.some((t) => t.includes("mp4")) ||
36
+ allSrcs.some((s) => s.endsWith(".mp4") || s.endsWith(".mov"))
37
+ )
38
+ return "MP4";
39
+ if (types.some((t) => t.includes("ogg")) || allSrcs.some((s) => s.endsWith(".ogv")))
40
+ return "OGG";
41
+
42
+ return sources.length > 0 || video.getAttribute("src") ? "unknown" : "no source";
43
+ }
44
+
45
+ function isModernCodec(codec) {
46
+ return ["AV1", "VP9", "WebM"].includes(codec);
47
+ }
48
+
49
+ function shortSrc(url) {
50
+ if (!url) return "";
51
+ return url.split("/").pop()?.split("?")[0]?.slice(0, 40) || url.slice(-40);
52
+ }
53
+
54
+ const videos = Array.from(document.querySelectorAll("video"));
55
+
56
+ if (videos.length === 0) {
57
+ console.log("No <video> elements found on this page.");
58
+ return { script: "Video-Element-Audit", status: "ok", count: 0, items: [], issues: [] };
59
+ }
60
+
61
+ const audited = videos.map((video) => {
62
+ const inViewport = isInViewport(video);
63
+ const rect = video.getBoundingClientRect();
64
+ const src = video.currentSrc || video.getAttribute("src") || "";
65
+
66
+ const preload = video.getAttribute("preload");
67
+ const autoplay = video.hasAttribute("autoplay");
68
+ const muted = video.hasAttribute("muted") || video.muted;
69
+ const playsinline = video.hasAttribute("playsinline");
70
+ const loop = video.hasAttribute("loop");
71
+ const controls = video.hasAttribute("controls");
72
+ const poster = video.getAttribute("poster");
73
+ const hasDimensions = video.hasAttribute("width") && video.hasAttribute("height");
74
+ const sources = Array.from(video.querySelectorAll("source"));
75
+ const codec = detectBestCodec(video);
76
+
77
+ const issues = [];
78
+
79
+ if (preload === "auto" && !autoplay) {
80
+ issues.push({
81
+ s: "error",
82
+ msg: 'preload="auto" loads the full video eagerly — use preload="none" or preload="metadata"',
83
+ });
84
+ }
85
+
86
+ if (!autoplay && !inViewport && preload !== "none") {
87
+ issues.push({
88
+ s: "warning",
89
+ msg: 'Off-viewport video without preload="none" — set preload="none" to avoid unnecessary loading',
90
+ });
91
+ }
92
+
93
+ if (autoplay && !muted) {
94
+ issues.push({
95
+ s: "error",
96
+ msg: "autoplay without muted — browsers block unmuted autoplay",
97
+ });
98
+ }
99
+
100
+ if (autoplay && !playsinline) {
101
+ issues.push({
102
+ s: "warning",
103
+ msg: "autoplay without playsinline — iOS Safari forces fullscreen mode",
104
+ });
105
+ }
106
+
107
+ if (!poster) {
108
+ issues.push({
109
+ s: "warning",
110
+ msg: "Missing poster — no preview frame shown before playback starts",
111
+ });
112
+ }
113
+
114
+ if (!hasDimensions) {
115
+ issues.push({
116
+ s: "warning",
117
+ msg: "Missing width/height attributes (CLS risk)",
118
+ });
119
+ }
120
+
121
+ if (!isModernCodec(codec) && codec !== "no source") {
122
+ issues.push({
123
+ s: "info",
124
+ msg: "No modern codec source detected — consider adding a WebM source (VP9 or AV1) for better compression",
125
+ });
126
+ }
127
+
128
+ if (!controls && !autoplay) {
129
+ issues.push({
130
+ s: "info",
131
+ msg: "No controls attribute and no autoplay — users may not be able to play the video",
132
+ });
133
+ }
134
+
135
+ return {
136
+ video,
137
+ inViewport,
138
+ src,
139
+ codec,
140
+ preload: preload ?? "(not set)",
141
+ autoplay,
142
+ muted,
143
+ playsinline,
144
+ loop,
145
+ controls,
146
+ poster: poster ? "āœ“" : "(not set)",
147
+ hasDimensions,
148
+ sourceCount: sources.length,
149
+ dimensions: `${Math.round(rect.width)}Ɨ${Math.round(rect.height)}`,
150
+ issues,
151
+ };
152
+ });
153
+
154
+ const withIssues = audited.filter((r) => r.issues.length > 0);
155
+ const totalErrors = audited.flatMap((r) => r.issues.filter((i) => i.s === "error")).length;
156
+ const totalWarnings = audited.flatMap((r) => r.issues.filter((i) => i.s === "warning")).length;
157
+ const totalInfos = audited.flatMap((r) => r.issues.filter((i) => i.s === "info")).length;
158
+
159
+ console.group("%cšŸŽ¬ Video Element Audit", "font-weight: bold; font-size: 14px;");
160
+
161
+ // Summary
162
+ console.log("");
163
+ console.log("%cSummary", "font-weight: bold;");
164
+ console.log(` Total videos : ${videos.length}`);
165
+ console.log(` In viewport : ${audited.filter((r) => r.inViewport).length}`);
166
+ console.log(` Off viewport : ${audited.filter((r) => !r.inViewport).length}`);
167
+ console.log(
168
+ ` Issues : ${totalErrors} errors Ā· ${totalWarnings} warnings Ā· ${totalInfos} info`,
169
+ );
170
+
171
+ // Full table
172
+ console.log("");
173
+ console.group(`%cšŸ“‹ All Videos (${videos.length})`, "font-weight: bold;");
174
+ console.table(
175
+ audited.map((r) => ({
176
+ src: shortSrc(r.src),
177
+ codec: r.codec,
178
+ viewport: r.inViewport ? "āœ“" : "",
179
+ preload: r.preload,
180
+ autoplay: r.autoplay ? "āœ“" : "",
181
+ muted: r.muted ? "āœ“" : "",
182
+ playsinline: r.playsinline ? "āœ“" : "",
183
+ controls: r.controls ? "āœ“" : "",
184
+ poster: r.poster,
185
+ "w/h": r.hasDimensions ? "āœ“" : "āš ļø",
186
+ sources: r.sourceCount,
187
+ issues:
188
+ r.issues.length === 0
189
+ ? "āœ…"
190
+ : r.issues
191
+ .map((i) => (i.s === "error" ? "šŸ”“" : i.s === "warning" ? "āš ļø" : "ā„¹ļø"))
192
+ .join(" "),
193
+ })),
194
+ );
195
+ console.groupEnd();
196
+
197
+ // Issues detail
198
+ if (withIssues.length > 0) {
199
+ console.log("");
200
+ console.group(
201
+ `%cāš ļø Issues Detail (${totalErrors} errors Ā· ${totalWarnings} warnings Ā· ${totalInfos} info)`,
202
+ "color: #ef4444; font-weight: bold;",
203
+ );
204
+
205
+ withIssues.forEach((r) => {
206
+ const hasError = r.issues.some((i) => i.s === "error");
207
+ const icon = hasError ? "šŸ”“" : "āš ļø";
208
+ console.log("");
209
+ console.log(`%c${icon} ${shortSrc(r.src) || "(no src)"}`, "font-weight: bold;");
210
+ r.issues.forEach((issue) => {
211
+ const prefix = issue.s === "error" ? " šŸ”“" : issue.s === "warning" ? " āš ļø" : " ā„¹ļø";
212
+ console.log(`${prefix} ${issue.msg}`);
213
+ });
214
+ console.log(" Element:", r.video);
215
+ });
216
+
217
+ console.groupEnd();
218
+ } else {
219
+ console.log("");
220
+ console.log("%cāœ… No issues found.", "color: #22c55e; font-weight: bold;");
221
+ }
222
+
223
+ // Quick reference
224
+ console.log("");
225
+ console.group("%cšŸ“ Quick Reference", "color: #3b82f6; font-weight: bold;");
226
+ console.log("");
227
+ console.log("%c šŸŽ¬ Autoplay hero/background video:", "color: #22c55e;");
228
+ console.log(
229
+ '%c <video autoplay muted playsinline loop poster="preview.avif" width="1280" height="720">\n <source src="hero.av1.webm" type="video/webm; codecs=av01.0.04M.08">\n <source src="hero.vp9.webm" type="video/webm; codecs=vp9">\n <source src="hero.mp4" type="video/mp4">\n </video>',
230
+ "font-family: monospace;",
231
+ );
232
+ console.log("");
233
+ console.log("%c āœ… User-controlled video:", "color: #22c55e;");
234
+ console.log(
235
+ '%c <video controls preload="metadata" poster="thumbnail.avif" width="800" height="450">\n <source src="video.vp9.webm" type="video/webm; codecs=vp9">\n <source src="video.mp4" type="video/mp4">\n </video>',
236
+ "font-family: monospace;",
237
+ );
238
+ console.log("");
239
+ console.log("%c āœ… Below-fold video:", "color: #22c55e;");
240
+ console.log(
241
+ '%c <video controls preload="none" poster="thumbnail.avif" width="800" height="450">\n <source src="video.vp9.webm" type="video/webm; codecs=vp9">\n <source src="video.mp4" type="video/mp4">\n </video>',
242
+ "font-family: monospace;",
243
+ );
244
+ console.groupEnd();
245
+
246
+ console.groupEnd();
247
+
248
+ return {
249
+ script: "Video-Element-Audit",
250
+ status: "ok",
251
+ count: videos.length,
252
+ details: {
253
+ totalVideos: videos.length,
254
+ inViewport: audited.filter((r) => r.inViewport).length,
255
+ offViewport: audited.filter((r) => !r.inViewport).length,
256
+ totalErrors,
257
+ totalWarnings,
258
+ totalInfos,
259
+ },
260
+ items: audited.map((r) => ({
261
+ src: r.src,
262
+ codec: r.codec,
263
+ inViewport: r.inViewport,
264
+ preload: r.preload,
265
+ autoplay: r.autoplay,
266
+ muted: r.muted,
267
+ playsinline: r.playsinline,
268
+ loop: r.loop,
269
+ controls: r.controls,
270
+ hasPoster: r.poster !== "(not set)",
271
+ hasDimensions: r.hasDimensions,
272
+ sourceCount: r.sourceCount,
273
+ issues: r.issues.map((i) => ({ severity: i.s, message: i.msg })),
274
+ })),
275
+ issues: audited.flatMap((r) =>
276
+ r.issues.map((i) => ({
277
+ severity: i.s,
278
+ message: `${shortSrc(r.src) || "(no src)"}: ${i.msg}`,
279
+ }))
280
+ ),
281
+ };
282
+ })();
@@ -0,0 +1,190 @@
1
+ ## Common Workflows
2
+
3
+ ### Complete Media Audit
4
+
5
+ When the user asks for media optimization or "audit images and videos":
6
+
7
+ 1. **Image-Element-Audit.js** - Analyze all images (format, lazy loading, sizing, fetchpriority)
8
+ 2. **Video-Element-Audit.js** - Analyze all videos (poster, preload, formats, autoplay)
9
+ 3. **SVG-Embedded-Bitmap-Analysis.js** - Detect inefficient bitmap images embedded in SVGs
10
+
11
+ ### Image Optimization Workflow
12
+
13
+ When the user asks "optimize images" or "check image performance":
14
+
15
+ 1. **Image-Element-Audit.js** - Full image audit
16
+ 2. Cross-reference with **webperf-loading** skill:
17
+ - Find-Above-The-Fold-Lazy-Loaded-Images.js (incorrectly lazy-loaded images)
18
+ - Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js (missing lazy loading)
19
+ - Find-Images-With-Lazy-and-Fetchpriority.js (contradictory attributes)
20
+ - Priority-Hints-Audit.js (LCP image should have fetchpriority="high")
21
+
22
+ ### Video Performance Audit
23
+
24
+ When the user asks "optimize videos" or "check video performance":
25
+
26
+ 1. **Video-Element-Audit.js** - Full video audit
27
+ 2. Cross-reference with **webperf-core-web-vitals** skill:
28
+ - LCP-Video-Candidate.js (check if video/poster is LCP)
29
+ 3. Cross-reference with **webperf-loading** skill:
30
+ - Priority-Hints-Audit.js (video poster priority)
31
+ - Resource-Hints-Validation.js (video preload)
32
+
33
+ ### LCP Image Investigation
34
+
35
+ When LCP is an image and needs optimization:
36
+
37
+ 1. Cross-reference with **webperf-core-web-vitals** skill:
38
+ - LCP.js (measure LCP)
39
+ - LCP-Image-Entropy.js (analyze image complexity)
40
+ 2. **Image-Element-Audit.js** - Check format, dimensions, lazy loading
41
+ 3. Cross-reference with **webperf-loading** skill:
42
+ - Find-Above-The-Fold-Lazy-Loaded-Images.js (should NOT be lazy)
43
+ - Priority-Hints-Audit.js (should have fetchpriority="high")
44
+ - Resource-Hints-Validation.js (consider preload)
45
+
46
+ ### Layout Shift from Images
47
+
48
+ When CLS is caused by images without dimensions:
49
+
50
+ 1. **Image-Element-Audit.js** - Check for missing width/height attributes
51
+ 2. Cross-reference with **webperf-core-web-vitals** skill:
52
+ - CLS.js (measure total CLS)
53
+ 3. Cross-reference with **webperf-interaction** skill:
54
+ - Layout-Shift-Loading-and-Interaction.js (when shifts occur)
55
+
56
+ ### SVG Optimization Audit
57
+
58
+ When the user asks about SVG performance or file sizes are large:
59
+
60
+ 1. **SVG-Embedded-Bitmap-Analysis.js** - Detect raster images embedded in vector SVGs
61
+ 2. Recommend SVGO optimization for SVGs without embedded bitmaps
62
+ 3. Recommend extracting bitmaps to separate image files with proper formats
63
+
64
+ ## Decision Tree
65
+
66
+ Use this decision tree to automatically run follow-up snippets based on results:
67
+
68
+ ### After Image-Element-Audit.js
69
+
70
+ - **If images missing width/height attributes** → Layout shift risk, run:
71
+ 1. **webperf-core-web-vitals:CLS.js** (measure CLS impact)
72
+ 2. **webperf-interaction:Layout-Shift-Loading-and-Interaction.js** (timing of shifts)
73
+ 3. Recommend adding explicit dimensions to all images
74
+
75
+ - **If images using wrong format (JPEG for graphics, PNG for photos)** → Recommend:
76
+ - Modern formats: WebP, AVIF
77
+ - Appropriate format for content type
78
+ - Format-specific compression settings
79
+
80
+ - **If images much larger than display size** → Recommend:
81
+ - Responsive images with srcset
82
+ - Appropriate image CDN sizing
83
+ - srcset with multiple sizes for different viewports
84
+
85
+ - **If above-the-fold images are lazy-loaded** → Run:
86
+ 1. **webperf-loading:Find-Above-The-Fold-Lazy-Loaded-Images.js** (confirm)
87
+ 2. **webperf-core-web-vitals:LCP.js** (measure LCP impact)
88
+ 3. Recommend removing loading="lazy" from above-fold images
89
+
90
+ - **If LCP image lacks fetchpriority="high"** → Run:
91
+ 1. **webperf-core-web-vitals:LCP.js** (measure current LCP)
92
+ 2. **webperf-loading:Priority-Hints-Audit.js** (full priority audit)
93
+ 3. Recommend adding fetchpriority="high" to LCP image
94
+
95
+ - **If below-the-fold images are NOT lazy-loaded** → Run:
96
+ 1. **webperf-loading:Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js** (confirm)
97
+ 2. Recommend adding loading="lazy" to offscreen images
98
+
99
+ - **If images have both loading="lazy" AND fetchpriority="high"** → Run:
100
+ 1. **webperf-loading:Find-Images-With-Lazy-and-Fetchpriority.js** (confirm contradiction)
101
+ 2. Recommend removing one of the conflicting attributes
102
+
103
+ - **If images competing with critical resources** → Run:
104
+ 1. **webperf-loading:Find-render-blocking-resources.js** (resource priority conflicts)
105
+ 2. **webperf-loading:TTFB-Resources.js** (identify slow image CDN)
106
+
107
+ - **If images missing alt text** → Accessibility issue, recommend adding descriptive alt text
108
+
109
+ ### After Video-Element-Audit.js
110
+
111
+ - **If video is LCP candidate** → Run:
112
+ 1. **webperf-core-web-vitals:LCP-Video-Candidate.js** (confirm)
113
+ 2. **webperf-core-web-vitals:LCP.js** (measure LCP)
114
+ 3. **webperf-core-web-vitals:LCP-Subparts.js** (break down timing)
115
+ 4. Optimize video poster image or consider image alternative
116
+
117
+ - **If video missing poster** → Recommend:
118
+ - Adding poster image for better perceived performance
119
+ - Using first frame or custom thumbnail
120
+ - Optimizing poster as you would an image
121
+
122
+ - **If video uses preload="auto"** → Bandwidth concern, evaluate:
123
+ - Is video above-the-fold? Keep preload="auto"
124
+ - Is video below-the-fold? Change to preload="metadata" or "none"
125
+ - Is autoplay intended? Verify preload matches intent
126
+
127
+ - **If autoplay video without muted** → Browser will block, recommend:
128
+ - Adding muted attribute
129
+ - Or removing autoplay
130
+
131
+ - **If video missing multiple formats** → Recommend:
132
+ - WebM for Chrome/Firefox
133
+ - MP4 as fallback for Safari
134
+ - Order sources by efficiency (WebM first)
135
+
136
+ - **If large video files (>5MB)** → Recommend:
137
+ - Compression/transcoding
138
+ - Adaptive bitrate streaming (HLS, DASH)
139
+ - Loading strategy optimization
140
+
141
+ ### After SVG-Embedded-Bitmap-Analysis.js
142
+
143
+ - **If bitmap images found in SVGs** → Recommend:
144
+ 1. Extract bitmaps to separate files
145
+ 2. Use WebP/AVIF format for extracted images
146
+ 3. Reference images from SVG with <image> element
147
+ 4. Or convert to pure vector if possible
148
+
149
+ - **If large embedded bitmaps (>100KB)** → Critical inefficiency:
150
+ - SVG parsing overhead + large bitmap = worst of both worlds
151
+ - Urgently recommend extraction
152
+
153
+ - **If multiple small bitmaps in SVG** → Consider:
154
+ - CSS sprites for small icons
155
+ - SVG symbols for reusable graphics
156
+ - Extracting to individual optimized images
157
+
158
+ ### Performance Budget Thresholds
159
+
160
+ Use these thresholds to trigger recommendations:
161
+
162
+ **Image File Sizes:**
163
+ - **Warning**: Individual image > 500KB → Check format and compression
164
+ - **Critical**: Individual image > 1MB → Urgent optimization needed
165
+ - **Total images**: > 5MB on initial load → Implement lazy loading
166
+
167
+ **Image Formats:**
168
+ - **JPEG for graphics/icons** → Recommend PNG or SVG
169
+ - **PNG for photos** → Recommend JPEG, WebP, or AVIF
170
+ - **GIF for animations** → Recommend video (MP4/WebM) or animated WebP
171
+ - **No modern formats (WebP/AVIF)** → Recommend upgrading
172
+
173
+ **Image Dimensions:**
174
+ - **Intrinsic size > 2x display size** → Recommend responsive images
175
+ - **Intrinsic size < display size** → Upscaling = blurry, provide larger source
176
+
177
+ **Video File Sizes:**
178
+ - **Warning**: Video > 10MB → Consider compression or streaming
179
+ - **Critical**: Video > 50MB → Urgent optimization or streaming needed
180
+
181
+ **Lazy Loading:**
182
+ - **Above-fold images lazy-loaded** → Critical LCP impact, fix immediately
183
+ - **Below-fold images NOT lazy-loaded** → Wasted bandwidth, implement lazy loading
184
+ - **>10 images eager-loaded** → Excessive, implement lazy loading
185
+
186
+ **Priority Hints:**
187
+ - **LCP image without fetchpriority="high"** → Add for 10-30% LCP improvement
188
+ - **Non-LCP images with fetchpriority="high"** → Remove, wasting browser hints
189
+ - **Lazy + fetchpriority="high" conflict** → Fix contradiction
190
+
@@ -0,0 +1,98 @@
1
+ # Snippets Directory
2
+
3
+ This directory contains the source JavaScript files for all web performance measurement snippets, organized by category.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ snippets/
9
+ ā”œā”€ā”€ CoreWebVitals/ # LCP, CLS, INP metrics
10
+ │ ā”œā”€ā”€ *.js # Snippet source files
11
+ │ └── WORKFLOWS.md # Optional workflows & decision trees (if applicable)
12
+ ā”œā”€ā”€ Loading/ # TTFB, FCP, scripts, fonts, resource hints
13
+ │ ā”œā”€ā”€ *.js
14
+ │ └── WORKFLOWS.md # Workflows & decision trees for loading optimization
15
+ ā”œā”€ā”€ Interaction/ # Long tasks, animation frames, scroll performance
16
+ │ ā”œā”€ā”€ *.js
17
+ │ └── WORKFLOWS.md # Optional workflows & decision trees (if applicable)
18
+ ā”œā”€ā”€ Media/ # Images, videos, SVGs
19
+ │ ā”œā”€ā”€ *.js
20
+ │ └── WORKFLOWS.md # Optional workflows & decision trees (if applicable)
21
+ └── Resources/ # Network bandwidth, connection quality
22
+ ā”œā”€ā”€ *.js
23
+ └── WORKFLOWS.md # Optional workflows & decision trees (if applicable)
24
+ ```
25
+
26
+ ## Adding Workflows & Decision Trees
27
+
28
+ To add intelligent workflows and decision trees to a skill category:
29
+
30
+ 1. **Create `WORKFLOWS.md`** in the category directory (e.g., `snippets/Loading/WORKFLOWS.md`)
31
+
32
+ 2. **Structure the file** with two main sections:
33
+
34
+ ```markdown
35
+ ## Common Workflows
36
+
37
+ ### Workflow Name
38
+
39
+ When the user asks about [scenario]:
40
+
41
+ 1. **Snippet1.js** - Brief description
42
+ 2. **Snippet2.js** - Brief description
43
+
44
+ ## Decision Tree
45
+
46
+ Use this decision tree to automatically run follow-up snippets based on results:
47
+
48
+ ### After Snippet1.js
49
+
50
+ - **If condition** → Run **Snippet2.js**
51
+ - **If other condition** → Run **Snippet3.js**
52
+ ```
53
+
54
+ 3. **Run the generator** to inject workflows into skills:
55
+
56
+ ```bash
57
+ node scripts/generate-skills.js
58
+ ```
59
+
60
+ The generator will:
61
+ - Read `WORKFLOWS.md` from each category
62
+ - Inject the content into the generated `SKILL.md`
63
+ - Copy to both `/skills/` (for distribution) and `/.claude/skills/` (for local use)
64
+
65
+ ## Example: Loading Workflows
66
+
67
+ See `snippets/Loading/WORKFLOWS.md` for a complete example with:
68
+
69
+ - 8 common workflow scenarios (Complete Audit, Server Investigation, Font Optimization, etc.)
70
+ - 16 decision trees with conditional logic for follow-up snippets
71
+ - Threshold-based triggers (e.g., "If TTFB > 600ms, run TTFB-Sub-Parts.js")
72
+
73
+ ## Benefits
74
+
75
+ **For agents:**
76
+ - Autonomous multi-snippet execution based on results
77
+ - Context-aware snippet selection
78
+ - Progressive diagnostic workflows
79
+
80
+ **For maintainability:**
81
+ - Workflows persist across regenerations
82
+ - Single source of truth in `snippets/` directory
83
+ - Version-controlled expertise and best practices
84
+
85
+ ## Build System
86
+
87
+ The `generate-skills.js` script:
88
+
89
+ 1. Reads all `.js` files from each category
90
+ 2. Extracts metadata from corresponding MDX documentation in `/pages/`
91
+ 3. Injects `WORKFLOWS.md` content (if exists)
92
+ 4. Generates complete `SKILL.md` files with:
93
+ - Snippet table
94
+ - Execution instructions
95
+ - Workflows & decision trees
96
+ - Individual snippet documentation with thresholds
97
+
98
+ **Note:** Never edit generated files directly. Always modify source files in `/snippets/` and regenerate.
@@ -0,0 +1,103 @@
1
+ // Network Bandwidth & Connection Quality Analysis
2
+ // https://webperf-snippets.nucliweb.net
3
+
4
+ (() => {
5
+ const connection =
6
+ navigator.connection ||
7
+ navigator.mozConnection ||
8
+ navigator.webkitConnection;
9
+
10
+ if (!connection) {
11
+ console.warn(
12
+ "%cāš ļø Network Information API not supported in this browser",
13
+ "color: #f59e0b; font-weight: bold;"
14
+ );
15
+ console.log("Available in Chrome and Android browsers.");
16
+ return { script: "Network-Bandwidth-Connection-Quality", status: "unsupported", error: "Network Information API not supported" };
17
+ }
18
+
19
+ const effectiveTypeRating = {
20
+ "slow-2g": { label: "Slow 2G", color: "#ef4444" },
21
+ "2g": { label: "2G", color: "#f97316" },
22
+ "3g": { label: "3G", color: "#f59e0b" },
23
+ "4g": { label: "4G", color: "#22c55e" },
24
+ };
25
+
26
+ const effectiveType = connection.effectiveType || "unknown";
27
+ const rating = effectiveTypeRating[effectiveType] || {
28
+ label: effectiveType,
29
+ color: "#6b7280",
30
+ };
31
+
32
+ console.group(
33
+ `%cšŸ“” Network Quality: ${rating.label}`,
34
+ `color: ${rating.color}; font-weight: bold; font-size: 14px;`
35
+ );
36
+
37
+ console.log("");
38
+ console.log("%cConnection details:", "font-weight: bold;");
39
+ console.table({
40
+ "Connection Type": connection.type || "unknown",
41
+ "Effective Type": effectiveType,
42
+ "Downlink (Mbps)":
43
+ connection.downlink !== undefined ? connection.downlink : "N/A",
44
+ "RTT (ms)": connection.rtt !== undefined ? connection.rtt : "N/A",
45
+ "Save-Data": connection.saveData ? "Enabled" : "Disabled",
46
+ });
47
+
48
+ // Recommendations
49
+ console.log("");
50
+ if (connection.saveData) {
51
+ console.log(
52
+ "%cšŸ’” Save-Data is enabled — consider serving reduced assets:",
53
+ "color: #3b82f6; font-weight: bold;"
54
+ );
55
+ console.log(" - Omit non-essential images and videos");
56
+ console.log(" - Disable autoplay and animations");
57
+ console.log(" - Defer non-critical resources");
58
+ }
59
+
60
+ if (effectiveType === "slow-2g" || effectiveType === "2g") {
61
+ console.log(
62
+ "%cšŸ’” Slow connection detected — performance tips:",
63
+ "color: #3b82f6; font-weight: bold;"
64
+ );
65
+ console.log(" - Prioritize critical resources with fetchpriority=high");
66
+ console.log(" - Minimize render-blocking scripts and stylesheets");
67
+ console.log(" - Consider skeleton screens over layout shifts");
68
+ }
69
+
70
+ // Monitor connection changes
71
+ const onChange = () => {
72
+ const updated = connection.effectiveType || "unknown";
73
+ const updatedRating = effectiveTypeRating[updated] || {
74
+ label: updated,
75
+ color: "#6b7280",
76
+ };
77
+ console.log(
78
+ `%cšŸ”„ Connection changed → ${updatedRating.label} (${updated})`,
79
+ `color: ${updatedRating.color}; font-weight: bold;`
80
+ );
81
+ };
82
+
83
+ connection.addEventListener("change", onChange);
84
+ console.log("");
85
+ console.log(
86
+ "%cšŸ‘‚ Listening for connection changes... (navigate to trigger)",
87
+ "color: #6b7280;"
88
+ );
89
+
90
+ console.groupEnd();
91
+
92
+ return {
93
+ script: "Network-Bandwidth-Connection-Quality",
94
+ status: "ok",
95
+ details: {
96
+ effectiveType,
97
+ downlink: connection.downlink,
98
+ rtt: connection.rtt,
99
+ saveData: connection.saveData,
100
+ type: connection.type || "unknown",
101
+ },
102
+ };
103
+ })();