From 5b03580b92a6af042052808ac468ec21b5561abb Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:35:12 -0600 Subject: [PATCH 1/2] feat(docs): add Deps and Build size comparison graphs with tabs --- .../src/components/ComparisonBarChart.astro | 337 ++++++++++++++++++ .../docs/src/components/DependencyStats.astro | 315 +++++++++++++++- .../docs/src/components/DevTimeChart.astro | 3 +- packages/docs/src/lib/types.ts | 9 + packages/docs/src/lib/utils.ts | 2 +- packages/docs/src/styles/shared.css | 2 + 6 files changed, 664 insertions(+), 4 deletions(-) create mode 100644 packages/docs/src/components/ComparisonBarChart.astro create mode 100644 packages/docs/src/lib/types.ts diff --git a/packages/docs/src/components/ComparisonBarChart.astro b/packages/docs/src/components/ComparisonBarChart.astro new file mode 100644 index 00000000..08ad2299 --- /dev/null +++ b/packages/docs/src/components/ComparisonBarChart.astro @@ -0,0 +1,337 @@ +--- +import type { ChartDatum } from '../lib/types' + +interface Props { + title: string + data: ChartDatum[] + valueFormat: 'count' | 'mb' +} + +const { title, data, valueFormat } = Astro.props +const chartPayload = JSON.stringify({ data, valueFormat }) +--- + +
+

{title}

+
+ +
+
+
+ + + + diff --git a/packages/docs/src/components/DependencyStats.astro b/packages/docs/src/components/DependencyStats.astro index e0b18652..5be522fa 100644 --- a/packages/docs/src/components/DependencyStats.astro +++ b/packages/docs/src/components/DependencyStats.astro @@ -1,13 +1,39 @@ --- import { getCollection } from 'astro:content' -import { formatBytesToMB, formatTimeMs, getFrameworkSlug } from '../lib/utils' +import { + BYTES_PER_MB, + formatBytesToMB, + formatTimeMs, + getFrameworkSlug, +} from '../lib/utils' import '../styles/shared.css' +import ComparisonBarChart from './ComparisonBarChart.astro' const devtimeEntries = await getCollection('devtime') const runtimeEntries = await getCollection('runtime') const starterStats = devtimeEntries.map((entry) => entry.data) const ssrStats = runtimeEntries.map((entry) => entry.data) + +const validForCharts = starterStats.filter( + (f) => + f?.name != null && + Number.isFinite(f.devDependencies) && + Number.isFinite(f.prodDependencies) && + Number.isFinite(f.buildOutputSize) && + Number.isFinite(f.nodeModulesSizeProdOnly), +) + +const depsData = validForCharts.map((f) => ({ name: f.name, value: f.devDependencies })) +const prodDepsData = validForCharts.map((f) => ({ name: f.name, value: f.prodDependencies })) +const buildSizeData = validForCharts.map((f) => ({ + name: f.name, + value: f.buildOutputSize / BYTES_PER_MB, +})) +const buildSizeProdData = validForCharts.map((f) => ({ + name: f.name, + value: f.nodeModulesSizeProdOnly / BYTES_PER_MB, +})) ---
@@ -83,6 +109,58 @@ const ssrStats = runtimeEntries.map((entry) => entry.data)
+
+
+ + +
+
+
+ +
+ +
+
+
@@ -121,6 +199,62 @@ const ssrStats = runtimeEntries.map((entry) => entry.data)
+
+
+ + +
+
+
+ +
+ +
+
+

Runtime Performance

SSR Performance

@@ -473,6 +607,122 @@ const ssrStats = runtimeEntries.map((entry) => entry.data) color: var(--ft-muted); } + .section-with-tabs { + margin-top: 16px; + margin-bottom: 2em; + } + + .chart-tabs-container { + margin-top: 1.5em; + padding: 1em 1.25em; + border: 1px solid var(--ft-border); + border-radius: 12px; + background: var(--ft-bg-muted); + } + + .tablist { + display: flex; + gap: 0; + margin-bottom: 0; + border-bottom: 1px solid var(--ft-border); + } + + .chart-tablist { + margin: 0 0 1em 0; + padding: 0; + border-bottom: none; + gap: 8px; + } + + .tab { + padding: 10px 20px; + font-size: 14px; + font-weight: 500; + color: var(--ft-muted); + background: transparent; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + cursor: pointer; + font-family: inherit; + transition: color 0.2s, border-color 0.2s, background-color 0.2s; + } + + .chart-tablist .tab { + padding: 8px 16px; + border-radius: 8px; + border-bottom: none; + margin-bottom: 0; + } + + .tab:hover { + color: var(--ft-text); + } + + .tab:focus-visible { + outline: 2px solid var(--ft-accent); + outline-offset: 2px; + } + + .tab.active { + color: var(--ft-accent); + border-bottom-color: currentColor; + } + + .chart-tablist .tab.active { + background: var(--ft-accent); + color: white; + border-bottom: none; + } + + .tabpanel { + margin-top: 0; + } + + .tabpanel[hidden] { + display: none; + } + + .chart-tabpanels { + position: relative; + min-height: 320px; + } + + .chart-tabpanel { + transition: opacity 0.2s ease; + } + + .chart-tabpanel[hidden] { + display: none; + opacity: 0; + } + + .chart-tabpanel:not([hidden]) { + opacity: 1; + } + + .methodology-note { + margin-top: 0.5em; + margin-bottom: 1em; + } + + :global(html.dark) .tab { + color: var(--ft-muted); + } + + :global(html.dark) .tab:hover { + color: var(--ft-text); + } + + :global(html.dark) .tab.active { + color: var(--ft-accent); + } + + :global(html.dark) .chart-tablist .tab.active { + background: var(--ft-accent); + color: var(--ft-bg); + } + @media screen and (max-width: 768px) { main { padding: 20px 16px; @@ -499,5 +749,68 @@ const ssrStats = runtimeEntries.map((entry) => entry.data) th { font-size: 12px; } + + .tab { + padding: 8px 14px; + font-size: 13px; + } } + + diff --git a/packages/docs/src/components/DevTimeChart.astro b/packages/docs/src/components/DevTimeChart.astro index 481217ed..24b5988a 100644 --- a/packages/docs/src/components/DevTimeChart.astro +++ b/packages/docs/src/components/DevTimeChart.astro @@ -113,8 +113,7 @@ const chartData = JSON.stringify([ } .chart-wrapper :global(.grid-line) { - stroke: var(--ft-border); - opacity: 0.5; + stroke: var(--ft-chart-grid, var(--ft-border)); } diff --git a/packages/docs/src/lib/types.ts b/packages/docs/src/lib/types.ts new file mode 100644 index 00000000..19e70790 --- /dev/null +++ b/packages/docs/src/lib/types.ts @@ -0,0 +1,9 @@ +export interface ChartDatum { + name: string + value: number +} + +export interface ComparisonChartPayload { + data: ChartDatum[] + valueFormat: 'count' | 'mb' +} diff --git a/packages/docs/src/lib/utils.ts b/packages/docs/src/lib/utils.ts index 43c653b4..6b1512bf 100644 --- a/packages/docs/src/lib/utils.ts +++ b/packages/docs/src/lib/utils.ts @@ -1,5 +1,5 @@ const BYTES_PER_KB = 1024 -const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB +export const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB /** * Returns the URL slug for a framework details page. diff --git a/packages/docs/src/styles/shared.css b/packages/docs/src/styles/shared.css index 1990ed86..890ac6e8 100644 --- a/packages/docs/src/styles/shared.css +++ b/packages/docs/src/styles/shared.css @@ -11,6 +11,7 @@ --ft-border: #e5e7eb; --ft-bg: #ffffff; --ft-bg-muted: #f9fafb; + --ft-chart-grid: rgba(0, 0, 0, 0.18); } html.dark { @@ -19,4 +20,5 @@ html.dark { --ft-border: #2e2e2e; --ft-bg: #1a1a1a; --ft-bg-muted: #242424; + --ft-chart-grid: rgba(255, 255, 255, 0.22); } From 0d4f3f4cc7a6fecd32caff3e15d16efe00c10c27 Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:41:13 -0600 Subject: [PATCH 2/2] format --- .../src/components/ComparisonBarChart.astro | 31 ++++++++++------ .../docs/src/components/DependencyStats.astro | 35 ++++++++++++++----- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/packages/docs/src/components/ComparisonBarChart.astro b/packages/docs/src/components/ComparisonBarChart.astro index 08ad2299..6f46cdb7 100644 --- a/packages/docs/src/components/ComparisonBarChart.astro +++ b/packages/docs/src/components/ComparisonBarChart.astro @@ -114,7 +114,10 @@ const chartPayload = JSON.stringify({ data, valueFormat }) import * as d3 from 'd3' import type { ChartDatum, ComparisonChartPayload } from '../lib/types' - const chartDimensionsCache = new WeakMap() + const chartDimensionsCache = new WeakMap< + HTMLElement, + { width: number; height: number } + >() function formatValue(value: number, format: 'count' | 'mb'): string { if (format === 'count') return String(Math.round(value)) @@ -177,10 +180,14 @@ const chartPayload = JSON.stringify({ data, valueFormat }) .closest('.comparison-chart-container') ?.querySelector('.comparison-chart-title') const chartTitle = titleEl?.textContent ?? 'Comparison' - d3.select(svg).append('title').text(`${chartTitle}. Bar chart comparing frameworks.`) + d3.select(svg) + .append('title') + .text(`${chartTitle}. Bar chart comparing frameworks.`) d3.select(svg) .append('desc') - .text(`Bar chart comparing each framework by ${chartTitle}. See table for exact values.`) + .text( + `Bar chart comparing each framework by ${chartTitle}. See table for exact values.`, + ) const gradientId = `comparisonBarGradient-${crypto.randomUUID()}` const g = d3 @@ -222,7 +229,9 @@ const chartPayload = JSON.stringify({ data, valueFormat }) g.append('g') .attr('transform', `translate(0,${innerHeight})`) .call(d3.axisBottom(xScale).tickSize(0)) - .call((sel) => sel.select('.domain').attr('class', 'comparison-axis-domain')) + .call((sel) => + sel.select('.domain').attr('class', 'comparison-axis-domain'), + ) .call((sel) => sel .selectAll('text') @@ -234,11 +243,15 @@ const chartPayload = JSON.stringify({ data, valueFormat }) g.append('g') .call(d3.axisLeft(yScale).tickSize(0)) - .call((sel) => sel.select('.domain').attr('class', 'comparison-axis-domain')) + .call((sel) => + sel.select('.domain').attr('class', 'comparison-axis-domain'), + ) .call((sel) => sel.selectAll('.tick line').attr('class', 'comparison-axis-tick-line'), ) - .call((sel) => sel.selectAll('text').attr('class', 'comparison-y-axis-text')) + .call((sel) => + sel.selectAll('text').attr('class', 'comparison-y-axis-text'), + ) g.append('g') .attr('class', 'comparison-grid') @@ -275,11 +288,7 @@ const chartPayload = JSON.stringify({ data, valueFormat }) bars .on('mouseenter', function (_event: MouseEvent, d: ChartDatum) { d3.select(this).select('rect').attr('opacity', 1) - setTooltipContent( - tooltip, - d.name, - formatValue(d.value, valueFormat), - ) + setTooltipContent(tooltip, d.name, formatValue(d.value, valueFormat)) tooltip.classList.add('visible') }) .on('mousemove', function (event: MouseEvent) { diff --git a/packages/docs/src/components/DependencyStats.astro b/packages/docs/src/components/DependencyStats.astro index 5be522fa..0c25ce7f 100644 --- a/packages/docs/src/components/DependencyStats.astro +++ b/packages/docs/src/components/DependencyStats.astro @@ -24,8 +24,14 @@ const validForCharts = starterStats.filter( Number.isFinite(f.nodeModulesSizeProdOnly), ) -const depsData = validForCharts.map((f) => ({ name: f.name, value: f.devDependencies })) -const prodDepsData = validForCharts.map((f) => ({ name: f.name, value: f.prodDependencies })) +const depsData = validForCharts.map((f) => ({ + name: f.name, + value: f.devDependencies, +})) +const prodDepsData = validForCharts.map((f) => ({ + name: f.name, + value: f.prodDependencies, +})) const buildSizeData = validForCharts.map((f) => ({ name: f.name, value: f.buildOutputSize / BYTES_PER_MB, @@ -109,7 +115,10 @@ const buildSizeProdData = validForCharts.map((f) => ({ -
+
({ aria-labelledby="deps-deps-tab" class="tabpanel chart-tabpanel active" > - +
({
-
+
({ margin-bottom: -1px; cursor: pointer; font-family: inherit; - transition: color 0.2s, border-color 0.2s, background-color 0.2s; + transition: + color 0.2s, + border-color 0.2s, + background-color 0.2s; } .chart-tablist .tab { @@ -763,9 +782,7 @@ const buildSizeProdData = validForCharts.map((f) => ({ '.chart-tabs-container[data-tab-section]', ) sections.forEach((section: Element) => { - const tabs = section.querySelectorAll( - '[role="tab"]', - ) + const tabs = section.querySelectorAll('[role="tab"]') const panels = section.querySelectorAll('[role="tabpanel"]') if (tabs.length !== 2 || panels.length !== 2) return