UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

173 lines (172 loc) • 7.46 kB
import colors from "piccolore"; import { ACTIVITY_BUCKETS } from "./collector.js"; const NAME_WIDTH = 34; const LEVELS = "▁▂▃▄▅▆▇█"; function formatDuration(ms) { if (ms < 1e3) return `${Math.round(ms)}ms`; if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`; return `${Math.floor(ms / 6e4)}m ${Math.round(ms % 6e4 / 1e3)}s`; } function formatCount(value) { return value.toLocaleString("en-US"); } function plural(count, noun) { return `${formatCount(count)} ${noun}${count === 1 ? "" : "s"}`; } function percent(value, total) { if (total === undefined) return " "; if (!total) return " 0.0%"; return `${(value / total * 100).toFixed(1).padStart(5)}%`; } function level(busy) { if (busy <= .01) return colors.dim("·"); return colors.blue(LEVELS[Math.min(LEVELS.length - 1, Math.ceil(busy * LEVELS.length) - 1)]); } function activityStrip(activity) { // Brief work would otherwise round to an empty strip, so its busiest slot is always drawn. const busiest = activity.indexOf(Math.max(...activity)); return activity.map((busy, slot) => level(busy > 0 && slot === busiest ? Math.max(busy, .02) : busy)).join(""); } function spanStrip(span, window) { const width = window / ACTIVITY_BUCKETS; return Array.from({ length: ACTIVITY_BUCKETS }, (_, slot) => { const overlap = Math.min(span.start + span.duration, (slot + 1) * width) - Math.max(span.start, slot * width); return level(Math.max(overlap, 0) / width); }).join(""); } function truncate(text, width, keep) { if (text.length <= width) return text.padEnd(width); return keep === "tail" ? `…${text.slice(text.length - width + 1)}` : `${text.slice(0, width - 1)}…`; } function formatRow({ name, duration, share, keep = "head", strip, detail }) { const columns = [ " ", truncate(name, NAME_WIDTH, keep), formatDuration(duration).padStart(8), " ", percent(duration, share) ]; if (strip) columns.push(" ", strip); if (detail) columns.push(" ", colors.dim(detail)); return columns.join("").trimEnd(); } function formatRows(rows, limit) { return rows.filter((row) => Math.round(row.duration) > 0).slice(0, limit).map(formatRow); } function breakdown(stats, limit) { return stats.filter((stat) => Math.round(stat.total) > 0).slice(0, limit).map((stat) => `${stat.name} ${formatDuration(stat.total)}`).join(" · "); } function relativeToRoot(path, root) { // Rolldown prefixes virtual modules with a NUL byte, which terminals render as junk. if (path.startsWith("\0")) return `${path.slice(1)} (astro internal)`; const dependency = path.lastIndexOf("/node_modules/"); if (dependency !== -1) return path.slice(dependency + "/node_modules/".length); if (path.startsWith(root)) return path.slice(root.length); return path; } function pushSection(lines, title, subtitle, body) { if (!body.length) return; const heading = subtitle ? `${colors.bold(title)} ${colors.dim(subtitle)}` : colors.bold(title); lines.push(`\n${heading}`, ...body); } function spanTree(spans, total) { const childrenOf = Map.groupBy(spans, (span) => span.parentId); const lines = []; const walk = (parentId, depth) => { for (const span of childrenOf.get(parentId) ?? []) { if (Math.round(span.duration) > 0) { lines.push(formatRow({ name: `${" ".repeat(depth)}${span.name}`, duration: span.duration, share: total, strip: spanStrip(span, total) })); } walk(span.id, depth + 1); } }; walk(null, 0); return lines; } export function renderCliReport(data) { const { root, totalDuration: total } = data; const matters = (duration) => duration >= total * .01; const lines = []; lines.push(`\n${colors.bgBlue(colors.black(" build timings "))}`); lines.push(colors.dim([ `total ${colors.bold(formatDuration(total))}`, plural(data.pages.count, "page"), `output "${data.output}"` ].join(" · "))); pushSection(lines, "Build phases", null, spanTree(data.spans, total)); pushSection(lines, "Vite plugins", `(${formatDuration(data.plugins.wallDuration)} of the build)`, formatRows(data.plugins.entries.map((plugin) => ({ name: plugin.name, duration: plugin.total, strip: activityStrip(plugin.activity), detail: `${plural(plugin.calls, "call")} · ${breakdown(plugin.children, 3)}` })), 8)); pushSection(lines, "Markdown plugins", `(${formatDuration(data.markdown.wallDuration)} across ${plural(data.markdown.files, "file")})`, formatRows(data.markdown.plugins.map((plugin) => ({ name: plugin.name, duration: plugin.total, strip: activityStrip(plugin.activity), detail: `${plural(plugin.calls, "call")}${plugin.pipeline === "mdx" ? " · MDX" : ""}` })), 8)); pushSection(lines, "Syntax highlighting", `(${formatDuration(data.highlight.wallDuration)} across ${plural(data.highlight.blocks, "code block")})`, formatRows(data.highlight.entries.map((language) => ({ name: language.name, duration: language.total, strip: activityStrip(language.activity), detail: plural(language.calls, "block") })), 8)); pushSection(lines, "Slowest files to bundle", `(${formatDuration(data.modules.wallDuration)} in load/transform)`, formatRows(data.modules.entries.map((module) => ({ name: relativeToRoot(module.name, root), duration: module.total, keep: "tail", strip: activityStrip(module.activity), detail: breakdown(module.children, 2) })), 8)); if (matters(data.compiler.wallDuration)) { pushSection(lines, "Astro compiler", `(${plural(data.compiler.count, "component")}, ${formatDuration(data.compiler.wallDuration)})`, formatRows(data.compiler.entries.map((file) => ({ name: relativeToRoot(file.name, root), duration: file.duration, keep: "tail" })), 5)); } if (data.pages.renderDuration) { pushSection(lines, "Page rendering", `(${formatDuration(data.pages.wallDuration)} across ${plural(data.pages.count, "page")})`, [formatRow({ name: "component render", duration: data.pages.renderDuration, share: total, strip: activityStrip(data.pages.renderActivity) }), formatRow({ name: "page pipeline", duration: Math.max(data.pages.wallDuration - data.pages.renderDuration, 0), share: total })]); } const cachedPages = data.pages.cached ? ` · ${formatCount(data.pages.cached)} from cache` : ""; pushSection(lines, "Slowest pages", `(median ${formatDuration(data.pages.median)} · p95 ${formatDuration(data.pages.p95)}${cachedPages})`, formatRows(data.pages.entries.map((page) => ({ name: page.name, duration: page.duration, keep: "tail", detail: page.cached ? "cached" : page.route === page.name ? "" : page.route })), 8)); if (matters(data.images.wallDuration)) { const cachedImages = data.images.cached ? `, ${formatCount(data.images.cached)} from cache` : ""; pushSection(lines, "Images", `(${plural(data.images.count, "transform")}, ${formatDuration(data.images.wallDuration)}${cachedImages})`, formatRows(data.images.entries.map((image) => ({ name: relativeToRoot(image.name, root), duration: image.duration, keep: "tail" })), 5)); } const integrationTotal = data.integrations.reduce((sum, integration) => sum + integration.total, 0); if (matters(integrationTotal)) { pushSection(lines, "Integrations", `(${formatDuration(integrationTotal)} in hooks)`, formatRows(data.integrations.map((integration) => ({ name: integration.name, duration: integration.total, share: total, strip: activityStrip(integration.activity), detail: breakdown(integration.children, 3) })), 8)); } return `${lines.join("\n")}\n`; }