Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A curated collection of JavaScript snippets to measure and debug Web Performance

[![CI](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml/badge.svg)](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/nucliweb/webperf-snippets)](https://github.com/nucliweb/webperf-snippets/releases)
[![Snippets](https://img.shields.io/badge/snippets-48-0f766e)](https://webperf-snippets.nucliweb.net)
[![Snippets](https://img.shields.io/badge/snippets-49-0f766e)](https://webperf-snippets.nucliweb.net)
[![License](https://img.shields.io/github/license/nucliweb/webperf-snippets)](./LICENSE)
[![Star History](https://img.shields.io/github/stars/nucliweb/webperf-snippets?style=social)](https://star-history.com/#nucliweb/webperf-snippets&Date)

Expand Down Expand Up @@ -87,7 +87,7 @@ This installs skills to `~/.claude/skills/` for use across any project.

| Skill | Snippets | Description |
| ------------------------- | -------- | ---------------------------------------------------------------- |
| `webperf` | 48 | Main entry point for all web performance analysis |
| `webperf` | 49 | Main entry point for all web performance analysis |
| `webperf-core-web-vitals` | 7 | LCP, CLS, INP measurements with detailed breakdowns |
| `webperf-loading` | 29 | TTFB, FCP, script/font analysis, resource hints, service workers |
| `webperf-interaction` | 8 | Long tasks, animation frames, scroll jank, INP debugging |
Expand Down
4 changes: 2 additions & 2 deletions SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A collection of [Agent Skills](https://agentskills.io/) for measuring and debugg

## Why WebPerf Skills?

These skills transform 48 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:
These skills transform 49 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:

- **Browser Console Integration**: Run performance measurements directly in Chrome DevTools
- **Real-time Analysis**: Measure actual user experience on live pages
Expand Down Expand Up @@ -105,7 +105,7 @@ The main entry point that helps identify the right skill for your performance qu
**What it does:**

- Routes to the appropriate specialized skill
- Provides overview of all 48 available snippets
- Provides overview of all 49 available snippets
- Suggests which skill to use based on your question

### webperf-core-web-vitals
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ const browserGlobals = {
encodeURIComponent: "readonly",
caches: "readonly",
CSSRule: "readonly",
CSSStyleDeclaration: "readonly",
NodeFilter: "readonly",
EventTarget: "readonly",
DOMTokenList: "readonly",
};

export default [
Expand Down
2 changes: 1 addition & 1 deletion skills/webperf/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ metadata:

# WebPerf Snippets Toolkit

A collection of 48 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.
A collection of 49 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.

## Quick Reference

Expand Down
15 changes: 11 additions & 4 deletions snippets/CoreWebVitals/CLS.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// CLS Quick Check
// https://webperf-snippets.nucliweb.net

(() => {
(async () => {
let cls = 0;

const valueToRating = (score) =>
Expand Down Expand Up @@ -63,9 +63,16 @@
""
);

// Synchronous return for agent (buffered entries)
const clsSync = performance.getEntriesByType("layout-shift")
.reduce((sum, e) => !e.hadRecentInput ? sum + e.value : sum, 0);
// Return for agent — collect via buffered observer (getEntriesByType does not
// expose layout-shift entries in Chrome without an active observer).
const clsSync = await new Promise((resolve) => {
let sum = 0;
const obs = new PerformanceObserver((list) => {
for (const e of list.getEntries()) if (!e.hadRecentInput) sum += e.value;
});
obs.observe({ type: "layout-shift", buffered: true });
setTimeout(() => { obs.disconnect(); resolve(sum); }, 0);
});
const clsRating = valueToRating(clsSync);
return {
script: "CLS",
Expand Down
15 changes: 10 additions & 5 deletions snippets/CoreWebVitals/LCP-Sub-Parts.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// LCP Sub-Parts Analysis
// https://webperf-snippets.nucliweb.net

(() => {
(async () => {
const formatMs = (ms) => `${Math.round(ms)}ms`;
const formatPercent = (value, total) => `${Math.round((value / total) * 100)}%`;

Expand Down Expand Up @@ -204,11 +204,16 @@
console.log("%c📊 LCP Sub-Parts Analysis Active", "font-weight: bold; font-size: 14px;");
console.log(" Waiting for LCP...");

// Synchronous return for agent (buffered entries)
const lcpBuffered = performance.getEntriesByType("largest-contentful-paint");
const lcpEntry = lcpBuffered.at(-1);
// Return for agent — collect via buffered observer (getEntriesByType does not
// expose largest-contentful-paint entries in Chrome without an active observer).
const lcpEntry = await new Promise((resolve) => {
const entries = [];
const obs = new PerformanceObserver((list) => entries.push(...list.getEntries()));
obs.observe({ type: "largest-contentful-paint", buffered: true });
setTimeout(() => { obs.disconnect(); resolve(entries.at(-1) ?? null); }, 0);
});
if (!lcpEntry) {
return { script: "LCP-Sub-Parts", status: "error", error: "No LCP entries yet" };
return { script: "LCP-Sub-Parts", status: "error", error: "No LCP entries buffered" };
}
const navEntrySync = getNavigationEntry();
if (!navEntrySync) {
Expand Down
15 changes: 10 additions & 5 deletions snippets/CoreWebVitals/LCP.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// LCP Quick Check
// https://webperf-snippets.nucliweb.net

(() => {
(async () => {
const valueToRating = (ms) =>
ms <= 2500 ? "good" : ms <= 4000 ? "needs-improvement" : "poor";

Expand Down Expand Up @@ -83,11 +83,16 @@
console.log("%c⏱️ LCP Tracking Active", "font-weight: bold; font-size: 14px;");
console.log(" LCP may update as larger elements load.");

// Synchronous return for agent (buffered entries)
const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
const lastLcpEntry = lcpEntries.at(-1);
// Return for agent — collect via buffered observer (getEntriesByType does not
// expose largest-contentful-paint entries in Chrome without an active observer).
const lastLcpEntry = await new Promise((resolve) => {
const entries = [];
const obs = new PerformanceObserver((list) => entries.push(...list.getEntries()));
obs.observe({ type: "largest-contentful-paint", buffered: true });
setTimeout(() => { obs.disconnect(); resolve(entries.at(-1) ?? null); }, 0);
});
if (!lastLcpEntry) {
return { script: "LCP", status: "error", error: "No LCP entries yet" };
return { script: "LCP", status: "error", error: "No LCP entries buffered" };
}
const lcpActivationStart = getActivationStart();
const lcpValue = Math.round(Math.max(0, lastLcpEntry.startTime - lcpActivationStart));
Expand Down
Loading