diff --git a/packages/docs/src/components/ComparisonBarChart.astro b/packages/docs/src/components/ComparisonBarChart.astro
new file mode 100644
index 00000000..6f46cdb7
--- /dev/null
+++ b/packages/docs/src/components/ComparisonBarChart.astro
@@ -0,0 +1,346 @@
+---
+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 })
+---
+
+
+
+
+
+
diff --git a/packages/docs/src/components/DependencyStats.astro b/packages/docs/src/components/DependencyStats.astro
index e0b18652..0c25ce7f 100644
--- a/packages/docs/src/components/DependencyStats.astro
+++ b/packages/docs/src/components/DependencyStats.astro
@@ -1,13 +1,45 @@
---
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 +115,65 @@ const ssrStats = runtimeEntries.map((entry) => entry.data)
+
+
+
+
+
+
+
+
@@ -121,6 +212,65 @@ const ssrStats = runtimeEntries.map((entry) => entry.data)
+
+
+
+
+
+
+
+
Runtime Performance
SSR Performance
@@ -473,6 +623,125 @@ 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 +768,66 @@ 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);
}