Skip to content

Commit fdb45d2

Browse files
committed
feat(cli): add fonts snippet with alias and detailed terminal output
Adds --snippet fonts as a short alias for the Fonts-Preloaded-Loaded-and-used-above-the-fold snippet. Reporter now shows loaded fonts table (with font-display warnings for block/auto) and used-above-fold table, matching the detail level of the browser console output.
1 parent 2673310 commit fdb45d2

4 files changed

Lines changed: 94 additions & 0 deletions

File tree

cli/src/bin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const SNIPPET_ALIASES = {
1515
LCP: "CoreWebVitals/LCP",
1616
CLS: "CoreWebVitals/CLS",
1717
"LCP-Sub-Parts": "CoreWebVitals/LCP-Sub-Parts",
18+
fonts: "Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold",
19+
"Fonts-Preloaded-Loaded-and-used-above-the-fold":
20+
"Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold",
1821
};
1922

2023
const USAGE = `webperf-snippets <url> [options]
@@ -36,6 +39,7 @@ Examples:
3639
npx webperf-snippets https://web.dev
3740
npx webperf-snippets https://example.com --json
3841
npx webperf-snippets https://example.com --snippet LCP-Sub-Parts
42+
npx webperf-snippets https://example.com --snippet fonts
3943
npx webperf-snippets https://example.com --budget-lcp 2500
4044
`;
4145

cli/src/reporters/human.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,58 @@ function pad(text, len) {
2929
return text + " ".repeat(Math.max(0, len - visibleLen));
3030
}
3131

32+
const DISPLAY_WARN = new Set(["block", "auto", "unknown"]);
33+
34+
function renderFontsResult(r) {
35+
const d = r.details ?? {};
36+
const lines = [];
37+
38+
lines.push(styleText("bold", ` Fonts — preloaded: ${d.preloadedCount ?? 0} loaded: ${d.loadedCount ?? 0} used above fold: ${d.usedAboveFoldCount ?? 0}`));
39+
40+
if (r.items?.length) {
41+
lines.push("");
42+
lines.push(styleText("dim", " Loaded fonts:"));
43+
lines.push(styleText("dim", ` ${"Family".padEnd(20)} ${"Weight".padEnd(8)} ${"Style".padEnd(10)} Display`));
44+
for (const f of r.items) {
45+
const displayWarning = DISPLAY_WARN.has(f.display) ? styleText("yellow", ` ⚠ ${f.display}`) : styleText("green", ` ${f.display}`);
46+
lines.push(` ${f.family.padEnd(20)} ${f.weight.padEnd(8)} ${f.style.padEnd(10)}${displayWarning}`);
47+
}
48+
}
49+
50+
if (r.usedFonts?.length) {
51+
lines.push("");
52+
lines.push(styleText("dim", " Used above fold:"));
53+
lines.push(styleText("dim", ` ${"Family".padEnd(20)} ${"Weight".padEnd(8)} ${"Style".padEnd(10)} Elements`));
54+
const sorted = [...r.usedFonts].sort((a, b) => b.elements - a.elements);
55+
for (const f of sorted) {
56+
lines.push(` ${f.family.padEnd(20)} ${f.weight.padEnd(8)} ${f.style.padEnd(10)} ${f.elements}`);
57+
}
58+
}
59+
60+
if (r.issues?.length) {
61+
lines.push("");
62+
lines.push(styleText("dim", " Issues:"));
63+
for (const issue of r.issues) {
64+
const color = issue.severity === "error" ? "red" : "yellow";
65+
lines.push(` ${styleText(color, issue.severity === "error" ? "✗" : "⚠")} ${issue.message}`);
66+
}
67+
} else {
68+
lines.push("");
69+
lines.push(` ${styleText("green", "✓")} Font loading looks optimized`);
70+
}
71+
72+
return lines.join("\n");
73+
}
74+
3275
function renderResult(r) {
3376
if (r.status === "error") {
3477
return ` ${styleText("red", "✗")} ${pad(r.id, 16)} ${styleText("dim", r.error)}`;
3578
}
3679

80+
if (Array.isArray(r.issues)) {
81+
return renderFontsResult(r);
82+
}
83+
3784
const icon = RATING_ICON[r.rating] ?? "·";
3885
const valueText = paint(r.rating, formatValue(r.value, r.unit));
3986
const ratingText = styleText("dim", r.rating ?? "");

cli/tests/unit/reporters.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,46 @@ describe("reportHuman", () => {
6464
const output = reportHuman(payload);
6565
expect(output).toContain("TypeError: x is not defined");
6666
});
67+
68+
it("renders fonts snippet result with counts and issues", () => {
69+
const payload = {
70+
url: "https://example.com",
71+
navMs: 500,
72+
results: [
73+
{
74+
id: "Fonts-Preloaded-Loaded-and-used-above-the-fold",
75+
status: "ok",
76+
count: 2,
77+
details: { preloadedCount: 1, loadedCount: 2, usedAboveFoldCount: 2, preloadedNotUsedCount: 1, usedNotPreloadedCount: 0 },
78+
items: [],
79+
issues: [{ severity: "warning", message: "Preloaded but not used above fold: font.woff2" }],
80+
},
81+
],
82+
pageErrors: [],
83+
};
84+
const output = reportHuman(payload);
85+
expect(output).toContain("preloaded: 1");
86+
expect(output).toContain("loaded: 2");
87+
expect(output).toContain("Preloaded but not used above fold: font.woff2");
88+
});
89+
90+
it("renders fonts snippet result with no issues as optimized", () => {
91+
const payload = {
92+
url: "https://example.com",
93+
navMs: 500,
94+
results: [
95+
{
96+
id: "Fonts-Preloaded-Loaded-and-used-above-the-fold",
97+
status: "ok",
98+
count: 1,
99+
details: { preloadedCount: 1, loadedCount: 1, usedAboveFoldCount: 1, preloadedNotUsedCount: 0, usedNotPreloadedCount: 0 },
100+
items: [],
101+
issues: [],
102+
},
103+
],
104+
pageErrors: [],
105+
};
106+
const output = reportHuman(payload);
107+
expect(output).toContain("Font loading looks optimized");
108+
});
67109
});

snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
usedNotPreloadedCount: usedNotPreloaded.length,
308308
},
309309
items: uniqueLoadedFonts.map(f => ({ family: f.family, weight: f.weight, style: f.style, display: f.display })),
310+
usedFonts: usedFonts.map(f => ({ family: f.family, weight: f.weight, style: f.style, elements: f.elements })),
310311
issues: [
311312
...preloadedNotUsed.map(f => ({ severity: "warning", message: `Preloaded but not used above fold: ${f.name}` })),
312313
...usedNotPreloaded.map(f => ({ severity: "warning", message: `Used above fold but not preloaded: ${f.family} (${f.weight})` })),

0 commit comments

Comments
 (0)