diff --git a/packages/docs/package.json b/packages/docs/package.json
index c1fe28bd..62cdc7c8 100644
--- a/packages/docs/package.json
+++ b/packages/docs/package.json
@@ -18,10 +18,12 @@
"@tanstack/charts": "^1.1.0",
"astro": "^7.4.0",
"chart.js": "^4.5.1",
+ "d3-shape": "3.2.0",
"@framework-tracker/results": "workspace:*"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
+ "@types/d3-shape": "3.2.0",
"@typescript-eslint/parser": "8.62.1",
"eslint": "10.6.0",
"eslint-plugin-astro": "^2.1.1"
diff --git a/packages/docs/src/components/BuildSizeChart.astro b/packages/docs/src/components/BuildSizeChart.astro
index f5db0d2a..a5537862 100644
--- a/packages/docs/src/components/BuildSizeChart.astro
+++ b/packages/docs/src/components/BuildSizeChart.astro
@@ -1,9 +1,14 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareColdBuildTimeComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareColdBuildTimeComparison,
+ prepareColdBuildTimeMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareColdBuildTimeComparison(starterStats)
+const bar = prepareColdBuildTimeComparison(starterStats)
+const line = prepareColdBuildTimeMonthlyComparison(starterHistory, starterStats)
---
-
+
diff --git a/packages/docs/src/components/BuildSizeProdChart.astro b/packages/docs/src/components/BuildSizeProdChart.astro
index 933cdb1f..fa8465e4 100644
--- a/packages/docs/src/components/BuildSizeProdChart.astro
+++ b/packages/docs/src/components/BuildSizeProdChart.astro
@@ -1,9 +1,14 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareWarmBuildTimeComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareWarmBuildTimeComparison,
+ prepareWarmBuildTimeMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareWarmBuildTimeComparison(starterStats)
+const bar = prepareWarmBuildTimeComparison(starterStats)
+const line = prepareWarmBuildTimeMonthlyComparison(starterHistory, starterStats)
---
-
+
diff --git a/packages/docs/src/components/CodeComparison.astro b/packages/docs/src/components/CodeComparison.astro
index 72c68ed8..137da26f 100644
--- a/packages/docs/src/components/CodeComparison.astro
+++ b/packages/docs/src/components/CodeComparison.astro
@@ -1,7 +1,7 @@
---
import { render } from 'astro:content'
import { getCodeComparison } from '../lib/prepare-table'
-import { frameworkLogos } from '../lib/framework-logos'
+import { getBrandByPackage } from '../lib/framework-logos'
type Props = {
example: string
@@ -21,7 +21,7 @@ const tabs = await Promise.all(
frameworks.map(async ({ name, slug, package: packageName, entry }) => ({
name,
slug,
- logo: frameworkLogos[packageName],
+ logo: getBrandByPackage(packageName)?.logo,
docs: entry?.data.docs,
Content: entry == null ? null : (await render(entry)).Content,
tabId: `${example}-tab-${slug}`,
diff --git a/packages/docs/src/components/ComparisonBarChart.astro b/packages/docs/src/components/ComparisonBarChart.astro
index 9a7bdcdc..3d628f89 100644
--- a/packages/docs/src/components/ComparisonBarChart.astro
+++ b/packages/docs/src/components/ComparisonBarChart.astro
@@ -1,5 +1,5 @@
---
-import { formatChartValue, getComparisonChartHeight } from '../lib/chart'
+import { COMPARISON_CHART_HEIGHT, formatChartValue } from '../lib/chart'
import type { ChartDatum, ChartValueFormat } from '../lib/types'
interface Props {
@@ -19,7 +19,6 @@ const chartData = data.map((d) => ({
focused: d.focused,
version: d.version,
}))
-const chartHeight = getComparisonChartHeight(chartData.length)
const chartPayload = JSON.stringify({
id,
@@ -36,7 +35,7 @@ const chartPayload = JSON.stringify({
@@ -152,8 +151,8 @@ const chartPayload = JSON.stringify({
import { decorative } from '@tanstack/charts/mark/decorative'
import { tooltip } from '@tanstack/charts/tooltip'
import {
+ COMPARISON_CHART_HEIGHT,
formatChartValue,
- getComparisonChartHeight,
getFrameworkLogoSource,
} from '../lib/chart'
import type { ChartDatum, ComparisonChartPayload } from '../lib/types'
@@ -293,8 +292,7 @@ const chartPayload = JSON.stringify({
const values = rows.map((d) => d.value)
const max = Math.max(...values)
const median = getMedian(values)
- const height = getComparisonChartHeight(rows.length)
- wrapper.style.setProperty('--comparison-chart-height', `${height}px`)
+ const height = COMPARISON_CHART_HEIGHT
const barColor = getCssVar('--ft-chart-bar', BAR_COLOR)
const barHoverColor = getCssVar('--ft-chart-bar-hover', BAR_HOVER_COLOR)
diff --git a/packages/docs/src/components/ComparisonComposition.astro b/packages/docs/src/components/ComparisonComposition.astro
new file mode 100644
index 00000000..56de1224
--- /dev/null
+++ b/packages/docs/src/components/ComparisonComposition.astro
@@ -0,0 +1,120 @@
+---
+import type { ComponentProps } from 'astro/types'
+import type { MonthlyComparisonChartPayload } from '../lib/types'
+import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonLineChart from './ComparisonLineChart.astro'
+
+interface Props {
+ bar: ComponentProps
+ line: MonthlyComparisonChartPayload
+}
+
+const { bar, line } = Astro.props
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/components/ComparisonLineChart.astro b/packages/docs/src/components/ComparisonLineChart.astro
new file mode 100644
index 00000000..f1291541
--- /dev/null
+++ b/packages/docs/src/components/ComparisonLineChart.astro
@@ -0,0 +1,465 @@
+---
+import { COMPARISON_CHART_HEIGHT, formatChartValue } from '../lib/chart'
+import { LATEST_POINT } from '../lib/monthly-history'
+import type { MonthlyComparisonChartPayload } from '../lib/types'
+
+type Props = MonthlyComparisonChartPayload
+
+const { title, description, months, series, valueFormat } = Astro.props
+const chartPayload = JSON.stringify(Astro.props)
+
+function formatMonth(month: string) {
+ if (month === LATEST_POINT) return 'Latest'
+ return new Date(`${month}-01T00:00:00Z`).toLocaleDateString('en-US', {
+ month: 'short',
+ year: 'numeric',
+ timeZone: 'UTC',
+ })
+}
+
+function seriesColorVars(
+ color: MonthlyComparisonChartPayload['series'][number]['color'],
+) {
+ return color == null
+ ? ''
+ : `--series-dark: ${color.dark}; --series-light: ${color.light ?? color.dark}`
+}
+---
+
+
+ {title}
+ {description ? {description}
: null}
+
+
+ {
+ series.map((s) => (
+ -
+
+ {s.name}
+
+ ))
+ }
+
+
+
+
+
+
+
+ {title} by month, using the version current on the first of each month,
+ then the latest results
+
+
+
+ | Framework |
+ {months.map((month) => {formatMonth(month)} | )}
+
+
+
+ {
+ series.map((s) => (
+
+ | {s.name} |
+ {s.points.map((point) => (
+
+ {point.value == null
+ ? '—'
+ : `${formatChartValue(point.value, valueFormat)} (v${point.version})`}
+ |
+ ))}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/components/CorePackageDependencyCharts.astro b/packages/docs/src/components/CorePackageDependencyCharts.astro
index 076e86e1..f4053841 100644
--- a/packages/docs/src/components/CorePackageDependencyCharts.astro
+++ b/packages/docs/src/components/CorePackageDependencyCharts.astro
@@ -1,6 +1,8 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import { starterHistory, starterStats } from '../lib/prepare-chart'
+import { monthlyComparison } from '../lib/monthly-history'
+import type { MetricDefinition } from '../lib/types'
+import ComparisonComposition from './ComparisonComposition.astro'
type DependencyMetric =
'allDependencies' | 'devDependencies' | 'prodDependencies'
@@ -22,23 +24,37 @@ function getChartData(metric: DependencyMetric) {
})
.sort((a, b) => a.value - b.value || a.name.localeCompare(b.name))
}
+
+function getCharts(metric: DependencyMetric, title: string) {
+ const definition: MetricDefinition = {
+ id: `frameworkDependencies.${metric}`,
+ title,
+ valueFormat: 'count',
+ yAxisLabel: 'Dependency count',
+ }
+ return {
+ bar: { ...definition, data: getChartData(metric) },
+ line: monthlyComparison(
+ definition,
+ starterHistory,
+ starterStats,
+ (record) => {
+ const value = record.frameworkDependencies?.[metric]
+ return typeof value === 'number' && Number.isFinite(value)
+ ? value
+ : null
+ },
+ ),
+ }
+}
---
-
-
-
diff --git a/packages/docs/src/components/DepsChart.astro b/packages/docs/src/components/DepsChart.astro
index ddad303c..c552e82e 100644
--- a/packages/docs/src/components/DepsChart.astro
+++ b/packages/docs/src/components/DepsChart.astro
@@ -1,9 +1,17 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareDevDependenciesComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareDevDependenciesComparison,
+ prepareDevDependenciesMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareDevDependenciesComparison(starterStats)
+const bar = prepareDevDependenciesComparison(starterStats)
+const line = prepareDevDependenciesMonthlyComparison(
+ starterHistory,
+ starterStats,
+)
---
-
+
diff --git a/packages/docs/src/components/DevServerStartChart.astro b/packages/docs/src/components/DevServerStartChart.astro
index e43d3de8..e55494d5 100644
--- a/packages/docs/src/components/DevServerStartChart.astro
+++ b/packages/docs/src/components/DevServerStartChart.astro
@@ -1,9 +1,17 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareDevServerStartTimeComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareDevServerStartTimeComparison,
+ prepareDevServerStartTimeMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareDevServerStartTimeComparison(starterStats)
+const bar = prepareDevServerStartTimeComparison(starterStats)
+const line = prepareDevServerStartTimeMonthlyComparison(
+ starterHistory,
+ starterStats,
+)
---
-
+
diff --git a/packages/docs/src/components/DuplicateDependencyChart.astro b/packages/docs/src/components/DuplicateDependencyChart.astro
index d270f77d..b1704293 100644
--- a/packages/docs/src/components/DuplicateDependencyChart.astro
+++ b/packages/docs/src/components/DuplicateDependencyChart.astro
@@ -1,9 +1,17 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareDuplicateDependenciesComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareDuplicateDependenciesComparison,
+ prepareDuplicateDependenciesMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareDuplicateDependenciesComparison(starterStats)
+const bar = prepareDuplicateDependenciesComparison(starterStats)
+const line = prepareDuplicateDependenciesMonthlyComparison(
+ starterHistory,
+ starterStats,
+)
---
-
+
diff --git a/packages/docs/src/components/FirstPartyDependencyCharts.astro b/packages/docs/src/components/FirstPartyDependencyCharts.astro
index 4a4bd69c..08f45dde 100644
--- a/packages/docs/src/components/FirstPartyDependencyCharts.astro
+++ b/packages/docs/src/components/FirstPartyDependencyCharts.astro
@@ -1,6 +1,8 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import { starterHistory, starterStats } from '../lib/prepare-chart'
+import { monthlyComparison } from '../lib/monthly-history'
+import type { MetricDefinition } from '../lib/types'
+import ComparisonComposition from './ComparisonComposition.astro'
type DependencyMetric =
| 'allDependencies'
@@ -25,29 +27,49 @@ function getDependencyChartData(metric: DependencyMetric) {
})
.sort((a, b) => a.value - b.value || a.name.localeCompare(b.name))
}
+
+function getDependencyCharts(metric: DependencyMetric, title: string) {
+ const definition: MetricDefinition = {
+ id: `firstPartyDependencies.${metric}`,
+ title,
+ valueFormat: 'count',
+ yAxisLabel: 'Dependency count',
+ }
+ return {
+ bar: { ...definition, data: getDependencyChartData(metric) },
+ line: monthlyComparison(
+ definition,
+ starterHistory,
+ starterStats,
+ (record) => {
+ const value = record.firstPartyDependencies?.[metric]
+ return typeof value === 'number' && Number.isFinite(value)
+ ? value
+ : null
+ },
+ ),
+ }
+}
---
-
-
-
-
diff --git a/packages/docs/src/components/InstallTimeMedianChart.astro b/packages/docs/src/components/InstallTimeMedianChart.astro
index b7a42c47..cc643616 100644
--- a/packages/docs/src/components/InstallTimeMedianChart.astro
+++ b/packages/docs/src/components/InstallTimeMedianChart.astro
@@ -1,9 +1,14 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareInstallTimeComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareInstallTimeComparison,
+ prepareInstallTimeMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareInstallTimeComparison(starterStats)
+const bar = prepareInstallTimeComparison(starterStats)
+const line = prepareInstallTimeMonthlyComparison(starterHistory, starterStats)
---
-
+
diff --git a/packages/docs/src/components/LighthouseClientSideRenderedFCPChart.astro b/packages/docs/src/components/LighthouseClientSideRenderedFCPChart.astro
index e8b68fc1..0ba2d32d 100644
--- a/packages/docs/src/components/LighthouseClientSideRenderedFCPChart.astro
+++ b/packages/docs/src/components/LighthouseClientSideRenderedFCPChart.astro
@@ -1,11 +1,19 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareLighthouseClientSideRenderedTestsFcpMsComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareLighthouseClientSideRenderedTestsFcpMsComparison,
+ prepareLighthouseClientSideRenderedTestsFcpMsMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareLighthouseClientSideRenderedTestsFcpMsComparison(
+const bar = prepareLighthouseClientSideRenderedTestsFcpMsComparison(
+ ssrRequestThroughputStats,
+)
+const line = prepareLighthouseClientSideRenderedTestsFcpMsMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
)
---
-
+
diff --git a/packages/docs/src/components/LighthouseClientSideRenderedFPChart.astro b/packages/docs/src/components/LighthouseClientSideRenderedFPChart.astro
index f0232051..621430ac 100644
--- a/packages/docs/src/components/LighthouseClientSideRenderedFPChart.astro
+++ b/packages/docs/src/components/LighthouseClientSideRenderedFPChart.astro
@@ -1,11 +1,20 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareLighthouseClientSideRenderedTestsFirstPaintMsComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareLighthouseClientSideRenderedTestsFirstPaintMsComparison,
+ prepareLighthouseClientSideRenderedTestsFirstPaintMsMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareLighthouseClientSideRenderedTestsFirstPaintMsComparison(
+const bar = prepareLighthouseClientSideRenderedTestsFirstPaintMsComparison(
ssrRequestThroughputStats,
)
+const line =
+ prepareLighthouseClientSideRenderedTestsFirstPaintMsMonthlyComparison(
+ runtimeHistory,
+ ssrRequestThroughputStats,
+ )
---
-
+
diff --git a/packages/docs/src/components/LighthouseServerSideRenderedFCPChart.astro b/packages/docs/src/components/LighthouseServerSideRenderedFCPChart.astro
index afcd6132..93f98e9c 100644
--- a/packages/docs/src/components/LighthouseServerSideRenderedFCPChart.astro
+++ b/packages/docs/src/components/LighthouseServerSideRenderedFCPChart.astro
@@ -1,11 +1,19 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareLighthouseServerSideRenderedTestsFcpMsComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareLighthouseServerSideRenderedTestsFcpMsComparison,
+ prepareLighthouseServerSideRenderedTestsFcpMsMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareLighthouseServerSideRenderedTestsFcpMsComparison(
+const bar = prepareLighthouseServerSideRenderedTestsFcpMsComparison(
+ ssrRequestThroughputStats,
+)
+const line = prepareLighthouseServerSideRenderedTestsFcpMsMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
)
---
-
+
diff --git a/packages/docs/src/components/LighthouseServerSideRenderedFPChart.astro b/packages/docs/src/components/LighthouseServerSideRenderedFPChart.astro
index 760447ab..a930751e 100644
--- a/packages/docs/src/components/LighthouseServerSideRenderedFPChart.astro
+++ b/packages/docs/src/components/LighthouseServerSideRenderedFPChart.astro
@@ -1,11 +1,20 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareLighthouseServerSideRenderedTestsFirstPaintMsComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareLighthouseServerSideRenderedTestsFirstPaintMsComparison,
+ prepareLighthouseServerSideRenderedTestsFirstPaintMsMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareLighthouseServerSideRenderedTestsFirstPaintMsComparison(
+const bar = prepareLighthouseServerSideRenderedTestsFirstPaintMsComparison(
ssrRequestThroughputStats,
)
+const line =
+ prepareLighthouseServerSideRenderedTestsFirstPaintMsMonthlyComparison(
+ runtimeHistory,
+ ssrRequestThroughputStats,
+ )
---
-
+
diff --git a/packages/docs/src/components/NodeModulesSizeChart.astro b/packages/docs/src/components/NodeModulesSizeChart.astro
index 56f405e7..7df67d56 100644
--- a/packages/docs/src/components/NodeModulesSizeChart.astro
+++ b/packages/docs/src/components/NodeModulesSizeChart.astro
@@ -1,9 +1,17 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareNodeModulesSizeComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareNodeModulesSizeComparison,
+ prepareNodeModulesSizeMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareNodeModulesSizeComparison(starterStats)
+const bar = prepareNodeModulesSizeComparison(starterStats)
+const line = prepareNodeModulesSizeMonthlyComparison(
+ starterHistory,
+ starterStats,
+)
---
-
+
diff --git a/packages/docs/src/components/ProdDepsChart.astro b/packages/docs/src/components/ProdDepsChart.astro
index 9983d9a8..1e3f7e2b 100644
--- a/packages/docs/src/components/ProdDepsChart.astro
+++ b/packages/docs/src/components/ProdDepsChart.astro
@@ -1,9 +1,17 @@
---
-import { starterStats } from '../lib/prepare-chart'
-import { prepareProdDependenciesComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareProdDependenciesComparison,
+ prepareProdDependenciesMonthlyComparison,
+ starterHistory,
+ starterStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareProdDependenciesComparison(starterStats)
+const bar = prepareProdDependenciesComparison(starterStats)
+const line = prepareProdDependenciesMonthlyComparison(
+ starterHistory,
+ starterStats,
+)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker100P75LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker100P75LatencyChart.astro
index 95400da0..fbd33d5a 100644
--- a/packages/docs/src/components/SSRLoadWorker100P75LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker100P75LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 100,
+ 'p75LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
100,
'p75LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker100P90LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker100P90LatencyChart.astro
index ea2e900e..23f0eb9c 100644
--- a/packages/docs/src/components/SSRLoadWorker100P90LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker100P90LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 100,
+ 'p90LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
100,
'p90LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker100P99LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker100P99LatencyChart.astro
index f9c7f997..36e241ee 100644
--- a/packages/docs/src/components/SSRLoadWorker100P99LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker100P99LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 100,
+ 'p99LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
100,
'p99LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker25P75LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker25P75LatencyChart.astro
index 4e641c8f..9bc9f88e 100644
--- a/packages/docs/src/components/SSRLoadWorker25P75LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker25P75LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 25,
+ 'p75LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
25,
'p75LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker25P90LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker25P90LatencyChart.astro
index debfce1d..1101d354 100644
--- a/packages/docs/src/components/SSRLoadWorker25P90LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker25P90LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 25,
+ 'p90LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
25,
'p90LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker25P99LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker25P99LatencyChart.astro
index 7249fde4..16aa6519 100644
--- a/packages/docs/src/components/SSRLoadWorker25P99LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker25P99LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 25,
+ 'p99LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
25,
'p99LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker50P75LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker50P75LatencyChart.astro
index 734c2ca2..ce5cdfb9 100644
--- a/packages/docs/src/components/SSRLoadWorker50P75LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker50P75LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 50,
+ 'p75LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
50,
'p75LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker50P90LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker50P90LatencyChart.astro
index b765878a..7991612e 100644
--- a/packages/docs/src/components/SSRLoadWorker50P90LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker50P90LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 50,
+ 'p90LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
50,
'p90LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRLoadWorker50P99LatencyChart.astro b/packages/docs/src/components/SSRLoadWorker50P99LatencyChart.astro
index d629d7c6..027d741e 100644
--- a/packages/docs/src/components/SSRLoadWorker50P99LatencyChart.astro
+++ b/packages/docs/src/components/SSRLoadWorker50P99LatencyChart.astro
@@ -1,16 +1,25 @@
---
import {
+ prepareSSRLatencyComparison,
+ prepareSSRLatencyMonthlyComparison,
+ runtimeHistory,
ssrRequestThroughputStats,
type SSRLoadKey,
} from '../lib/prepare-chart'
-import { prepareSSRLatencyComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import ComparisonComposition from './ComparisonComposition.astro'
interface Props {
testsKey?: SSRLoadKey
}
const { testsKey = 'ssrLoadTests' } = Astro.props
-const chart = prepareSSRLatencyComparison(
+const bar = prepareSSRLatencyComparison(
+ ssrRequestThroughputStats,
+ 50,
+ 'p99LatencyMs',
+ testsKey,
+)
+const line = prepareSSRLatencyMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
50,
'p99LatencyMs',
@@ -18,4 +27,4 @@ const chart = prepareSSRLatencyComparison(
)
---
-
+
diff --git a/packages/docs/src/components/SSRRequestThroughputBodySizeChart.astro b/packages/docs/src/components/SSRRequestThroughputBodySizeChart.astro
index 4c7f80ac..5cde5657 100644
--- a/packages/docs/src/components/SSRRequestThroughputBodySizeChart.astro
+++ b/packages/docs/src/components/SSRRequestThroughputBodySizeChart.astro
@@ -1,11 +1,19 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareSsrRequestThroughputTestsBodySizeKbComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareSsrRequestThroughputTestsBodySizeKbComparison,
+ prepareSsrRequestThroughputTestsBodySizeKbMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareSsrRequestThroughputTestsBodySizeKbComparison(
+const bar = prepareSsrRequestThroughputTestsBodySizeKbComparison(
+ ssrRequestThroughputStats,
+)
+const line = prepareSsrRequestThroughputTestsBodySizeKbMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
)
---
-
+
diff --git a/packages/docs/src/components/SSRRequestThroughputLatencyChart.astro b/packages/docs/src/components/SSRRequestThroughputLatencyChart.astro
index 3ccebc2d..31daca57 100644
--- a/packages/docs/src/components/SSRRequestThroughputLatencyChart.astro
+++ b/packages/docs/src/components/SSRRequestThroughputLatencyChart.astro
@@ -1,11 +1,19 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareSsrRequestThroughputTestsLatencyMsComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareSsrRequestThroughputTestsLatencyMsComparison,
+ prepareSsrRequestThroughputTestsLatencyMsMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareSsrRequestThroughputTestsLatencyMsComparison(
+const bar = prepareSsrRequestThroughputTestsLatencyMsComparison(
+ ssrRequestThroughputStats,
+)
+const line = prepareSsrRequestThroughputTestsLatencyMsMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
)
---
-
+
diff --git a/packages/docs/src/components/SSRRequestThroughputOpsChart.astro b/packages/docs/src/components/SSRRequestThroughputOpsChart.astro
index 0a7c8878..b122c6ed 100644
--- a/packages/docs/src/components/SSRRequestThroughputOpsChart.astro
+++ b/packages/docs/src/components/SSRRequestThroughputOpsChart.astro
@@ -1,11 +1,19 @@
---
-import { ssrRequestThroughputStats } from '../lib/prepare-chart'
-import { prepareSsrRequestThroughputTestsOpsPerSecComparison } from '../lib/prepare-chart'
-import ComparisonBarChart from './ComparisonBarChart.astro'
+import {
+ prepareSsrRequestThroughputTestsOpsPerSecComparison,
+ prepareSsrRequestThroughputTestsOpsPerSecMonthlyComparison,
+ runtimeHistory,
+ ssrRequestThroughputStats,
+} from '../lib/prepare-chart'
+import ComparisonComposition from './ComparisonComposition.astro'
-const chart = prepareSsrRequestThroughputTestsOpsPerSecComparison(
+const bar = prepareSsrRequestThroughputTestsOpsPerSecComparison(
+ ssrRequestThroughputStats,
+)
+const line = prepareSsrRequestThroughputTestsOpsPerSecMonthlyComparison(
+ runtimeHistory,
ssrRequestThroughputStats,
)
---
-
+
diff --git a/packages/docs/src/lib/chart-measurements.ts b/packages/docs/src/lib/chart-measurements.ts
index 575835d3..cf7896ab 100644
--- a/packages/docs/src/lib/chart-measurements.ts
+++ b/packages/docs/src/lib/chart-measurements.ts
@@ -1,11 +1,13 @@
import { INTERACTION_PHASES } from './interaction-phases.ts'
import { metricCatalogue } from './metrics.ts'
+import { monthlyComparison } from './monthly-history.ts'
import type {
ChartMeasurement,
InteractionHistoryDatum,
InteractionTimings,
InstallChartMeasurement,
MetricDefinition,
+ MonthlyComparisonChartPayload,
PreparedComparisonChart,
VersionLineChartPayload,
} from './types'
@@ -88,6 +90,20 @@ export function prepareDevServerStartTimeComparison(
)
}
+export function prepareDevServerStartTimeMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['devServerStartTime.medianMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.devServerStartTime?.medianMs, 1000),
+ now,
+ )
+}
+
export function prepareFrameworkDependenciesProdDependenciesHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -148,6 +164,20 @@ export function prepareProdDependenciesComparison(
)
}
+export function prepareProdDependenciesMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['prodDependencies'],
+ history,
+ latest,
+ (record) => finiteValue(record.prodDependencies),
+ now,
+ )
+}
+
export function prepareDevDependenciesHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -172,6 +202,20 @@ export function prepareDevDependenciesComparison(
)
}
+export function prepareDevDependenciesMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['devDependencies'],
+ history,
+ latest,
+ (record) => finiteValue(record.devDependencies),
+ now,
+ )
+}
+
export function prepareAllDependenciesHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -208,6 +252,20 @@ export function prepareDuplicateDependenciesComparison(
)
}
+export function prepareDuplicateDependenciesMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['duplicateDependencies'],
+ history,
+ latest,
+ (record) => finiteValue(record.duplicateDependencies),
+ now,
+ )
+}
+
export function prepareLighthouseClientSideRenderedTestsFirstPaintMsHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -232,6 +290,20 @@ export function prepareLighthouseClientSideRenderedTestsFirstPaintMsComparison(
)
}
+export function prepareLighthouseClientSideRenderedTestsFirstPaintMsMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['clientSideRenderedTests.firstPaintMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.clientSideRenderedTests?.firstPaintMs),
+ now,
+ )
+}
+
export function prepareLighthouseClientSideRenderedTestsFcpMsHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -314,6 +386,20 @@ export function prepareLighthouseServerSideRenderedTestsInteractionHistory(
return interactionHistory(records, 'serverSideRenderedTests')
}
+export function prepareLighthouseClientSideRenderedTestsFcpMsMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['clientSideRenderedTests.fcpMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.clientSideRenderedTests?.fcpMs),
+ now,
+ )
+}
+
export function prepareInstallTimeHistory(
records: readonly InstallChartMeasurement[],
): VersionLineChartPayload {
@@ -338,6 +424,20 @@ export function prepareInstallTimeComparison(
)
}
+export function prepareInstallTimeMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['installTime.medianMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.install?.installTimeMs?.mean, 1000),
+ now,
+ )
+}
+
export function prepareColdBuildTimeHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -362,6 +462,20 @@ export function prepareColdBuildTimeComparison(
)
}
+export function prepareColdBuildTimeMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['coldBuildTime.medianMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.coldBuildTime?.medianMs, 1000),
+ now,
+ )
+}
+
export function prepareWarmBuildTimeHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -386,6 +500,20 @@ export function prepareWarmBuildTimeComparison(
)
}
+export function prepareWarmBuildTimeMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['warmBuildTime.medianMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.warmBuildTime?.medianMs, 1000),
+ now,
+ )
+}
+
export function prepareCoreJsSizeHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -422,6 +550,20 @@ export function prepareLighthouseServerSideRenderedTestsFirstPaintMsComparison(
)
}
+export function prepareLighthouseServerSideRenderedTestsFirstPaintMsMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['serverSideRenderedTests.firstPaintMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.serverSideRenderedTests?.firstPaintMs),
+ now,
+ )
+}
+
export function prepareLighthouseServerSideRenderedTestsFcpMsHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -446,6 +588,20 @@ export function prepareLighthouseServerSideRenderedTestsFcpMsComparison(
)
}
+export function prepareLighthouseServerSideRenderedTestsFcpMsMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['serverSideRenderedTests.fcpMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.serverSideRenderedTests?.fcpMs),
+ now,
+ )
+}
+
export function prepareSsrRequestThroughputTestsOpsPerSecHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -471,6 +627,20 @@ export function prepareSsrRequestThroughputTestsOpsPerSecComparison(
)
}
+export function prepareSsrRequestThroughputTestsOpsPerSecMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['ssrRequestThroughputTests.opsPerSec'],
+ history,
+ latest,
+ (record) => finiteValue(record.ssrRequestThroughputTests?.opsPerSec),
+ now,
+ )
+}
+
export function prepareSsrRequestThroughputTestsBodySizeKbHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -495,6 +665,20 @@ export function prepareSsrRequestThroughputTestsBodySizeKbComparison(
)
}
+export function prepareSsrRequestThroughputTestsBodySizeKbMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['ssrRequestThroughputTests.bodySizeKb'],
+ history,
+ latest,
+ (record) => finiteValue(record.ssrRequestThroughputTests?.bodySizeKb),
+ now,
+ )
+}
+
export function prepareSsrRequestThroughputTestsLatencyMsHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -519,6 +703,20 @@ export function prepareSsrRequestThroughputTestsLatencyMsComparison(
)
}
+export function prepareSsrRequestThroughputTestsLatencyMsMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['ssrRequestThroughputTests.latencyMs'],
+ history,
+ latest,
+ (record) => finiteValue(record.ssrRequestThroughputTests?.latencyMs),
+ now,
+ )
+}
+
export function prepareNodeModulesSizeHistory(
records: readonly InstallChartMeasurement[],
): VersionLineChartPayload {
@@ -543,6 +741,20 @@ export function prepareNodeModulesSizeComparison(
)
}
+export function prepareNodeModulesSizeMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue['nodeModulesSize'],
+ history,
+ latest,
+ (record) => finiteValue(record.install?.nodeModulesSizeBytes, 1048576),
+ now,
+ )
+}
+
export function prepareDependencyInstallSizeHistory(
records: readonly ChartMeasurement[],
): VersionLineChartPayload {
@@ -609,3 +821,25 @@ export function prepareSSRLatencyComparison(
export type SSRLoadKey = 'ssrLoadTests' | 'ssrRouterLinkLoadTests'
export type LatencyPercentile = 'p75LatencyMs' | 'p90LatencyMs' | 'p99LatencyMs'
export type ConnectionCount = 25 | 50 | 100
+
+export function prepareSSRLatencyMonthlyComparison(
+ history: readonly ChartMeasurement[],
+ latest: readonly ChartMeasurement[],
+ workers: ConnectionCount,
+ percentile: LatencyPercentile,
+ testsKey: SSRLoadKey = 'ssrLoadTests',
+ now?: Date,
+): MonthlyComparisonChartPayload {
+ return monthlyComparison(
+ metricCatalogue[`${testsKey}.latency.${workers}.${percentile}`],
+ history,
+ latest,
+ (record) =>
+ finiteValue(
+ record[testsKey]?.stages?.find((stage) => stage.workers === workers)?.[
+ percentile
+ ],
+ ),
+ now,
+ )
+}
diff --git a/packages/docs/src/lib/chart.ts b/packages/docs/src/lib/chart.ts
index ec217f05..8ff069fa 100644
--- a/packages/docs/src/lib/chart.ts
+++ b/packages/docs/src/lib/chart.ts
@@ -1,3 +1,4 @@
+import { getBrandByDisplayName, resolveThemedValue } from './framework-logos'
import type { ChartValueFormat } from './types'
export function formatChartValue(
@@ -25,36 +26,13 @@ export function formatChartValue(
return prefixPositive && value > 0 ? `+${formatted}` : formatted
}
-export function getComparisonChartHeight(itemCount: number) {
- return Math.max(240, itemCount * 56 + 24)
-}
-
-const FRAMEWORK_LOGO_SOURCES: Record = {
- Astro: '/framework-logos/astro-gradient.svg',
- 'Baseline HTML': '/framework-logos/html5.svg',
- Gatsby: '/framework-logos/gatsby.svg',
- 'Next.js': '/framework-logos/nextdotjs.svg',
- Nuxt: '/framework-logos/nuxt.svg',
- 'Nuxt.js': '/framework-logos/nuxt.svg',
- Pracht: '/framework-logos/pracht.svg',
- 'React Router': '/framework-logos/reactrouter.svg',
- SolidStart: '/framework-logos/solid-start.svg',
- SvelteKit: '/framework-logos/svelte.svg',
- 'TanStack Start': '/framework-logos/tanstack.svg',
- WordPress: '/framework-logos/wordpress.svg',
-}
+/** Every comparison chart shares one height so they line up on the page. */
+export const COMPARISON_CHART_HEIGHT = 480
+/** Browser-only: resolves the logo for the page's current theme. */
export function getFrameworkLogoSource(name: string) {
- if (name === 'Astro' && document.documentElement.dataset.theme === 'light') {
- return '/framework-logos/astro-dark.svg'
- }
-
- if (
- name === 'Next.js' &&
- document.documentElement.dataset.theme !== 'light'
- ) {
- return '/framework-logos/nextdotjs-light.svg'
- }
-
- return FRAMEWORK_LOGO_SOURCES[name]
+ const brand = getBrandByDisplayName(name)
+ return brand == null
+ ? undefined
+ : resolveThemedValue(brand.logo, document.documentElement.dataset.theme)
}
diff --git a/packages/docs/src/lib/framework-cards.ts b/packages/docs/src/lib/framework-cards.ts
index 9f363114..3e5edf0e 100644
--- a/packages/docs/src/lib/framework-cards.ts
+++ b/packages/docs/src/lib/framework-cards.ts
@@ -1,9 +1,9 @@
import { starterStats } from './prepare-chart'
-import { frameworkLogos } from './framework-logos'
+import { getBrandByPackage } from './framework-logos'
import { getFrameworkSlug } from './utils'
function getFrameworkTitle(name: string, packageName: string) {
- const logo = frameworkLogos[packageName]
+ const logo = getBrandByPackage(packageName)?.logo
let visual: string
if (logo == null) {
visual = `◼`
diff --git a/packages/docs/src/lib/framework-logos.ts b/packages/docs/src/lib/framework-logos.ts
index 3ce99c2e..4a0eab2e 100644
--- a/packages/docs/src/lib/framework-logos.ts
+++ b/packages/docs/src/lib/framework-logos.ts
@@ -1,19 +1,86 @@
-export type FrameworkLogo = { dark: string; light?: string }
-
-/** Keyed by devtime starter package name. */
-export const frameworkLogos: Record = {
- 'starter-astro': {
- dark: '/framework-logos/astro-gradient.svg',
- light: '/framework-logos/astro-dark.svg',
- },
- 'starter-next-js': {
- dark: '/framework-logos/nextdotjs-light.svg',
- light: '/framework-logos/nextdotjs.svg',
- },
- 'starter-nuxt': { dark: '/framework-logos/nuxt.svg' },
- 'starter-pracht': { dark: '/framework-logos/pracht.svg' },
- 'starter-react-router': { dark: '/framework-logos/reactrouter.svg' },
- 'starter-solid-start': { dark: '/framework-logos/solid-start.svg' },
- 'starter-sveltekit': { dark: '/framework-logos/svelte.svg' },
- 'starter-tanstack-start-react': { dark: '/framework-logos/tanstack.svg' },
+import { frameworks } from '@framework-tracker/results/frameworks'
+
+export type ThemedValue = { dark: string; light?: string }
+export type FrameworkLogo = ThemedValue
+
+export interface FrameworkBrand {
+ logo: FrameworkLogo
+ color?: ThemedValue
+}
+
+export const frameworkBrands: Record = {
+ 'baseline-html': { logo: { dark: '/framework-logos/html5.svg' } },
+ astro: {
+ logo: {
+ dark: '/framework-logos/astro-gradient.svg',
+ light: '/framework-logos/astro-dark.svg',
+ },
+ color: { dark: '#BC52EE' },
+ },
+ next: {
+ logo: {
+ dark: '/framework-logos/nextdotjs-light.svg',
+ light: '/framework-logos/nextdotjs.svg',
+ },
+ color: { dark: '#FFFFFF', light: '#000000' },
+ },
+ nuxt: {
+ logo: { dark: '/framework-logos/nuxt.svg' },
+ color: { dark: '#00DC82' },
+ },
+ pracht: {
+ logo: { dark: '/framework-logos/pracht.svg' },
+ color: { dark: '#FBBF24' },
+ },
+ 'react-router': {
+ logo: { dark: '/framework-logos/reactrouter.svg' },
+ color: { dark: '#CA4245' },
+ },
+ 'solid-start': {
+ logo: { dark: '/framework-logos/solid-start.svg' },
+ color: { dark: '#15ABFF', light: '#0084CE' },
+ },
+ sveltekit: {
+ logo: { dark: '/framework-logos/svelte.svg' },
+ color: { dark: '#FF3E00' },
+ },
+ tanstack: {
+ logo: { dark: '/framework-logos/tanstack.svg' },
+ color: { dark: '#FFA05C' },
+ },
+ // Not benchmarked, but shown in the Core Web Vitals charts.
+ gatsby: { logo: { dark: '/framework-logos/gatsby.svg' } },
+ wordpress: { logo: { dark: '/framework-logos/wordpress.svg' } },
+}
+
+const brandKeyByDisplayName = new Map([
+ ...frameworks.map((f) => [f.displayName, f.name] as const),
+ ['Nuxt.js', 'nuxt'],
+ ['Gatsby', 'gatsby'],
+ ['WordPress', 'wordpress'],
+])
+
+const brandKeyByPackage = new Map(
+ frameworks.flatMap((f) =>
+ [f.starter?.package, f.app?.package].flatMap((packageName) =>
+ packageName ? [[packageName, f.name] as const] : [],
+ ),
+ ),
+)
+
+export function getBrandByDisplayName(name: string) {
+ const key = brandKeyByDisplayName.get(name)
+ return key == null ? undefined : frameworkBrands[key]
+}
+
+export function getBrandByPackage(packageName: string) {
+ const key = brandKeyByPackage.get(packageName)
+ return key == null ? undefined : frameworkBrands[key]
+}
+
+export function resolveThemedValue(
+ value: ThemedValue,
+ theme: string | undefined,
+) {
+ return theme === 'light' ? (value.light ?? value.dark) : value.dark
}
diff --git a/packages/docs/src/lib/monthly-history.test.ts b/packages/docs/src/lib/monthly-history.test.ts
new file mode 100644
index 00000000..bb047c31
--- /dev/null
+++ b/packages/docs/src/lib/monthly-history.test.ts
@@ -0,0 +1,106 @@
+import assert from 'node:assert/strict'
+import { test } from 'node:test'
+import {
+ getMonthStarts,
+ LATEST_POINT,
+ selectVersionAt,
+} from './monthly-history.ts'
+import {
+ prepareProdDependenciesComparison,
+ prepareProdDependenciesMonthlyComparison,
+} from './chart-measurements.ts'
+
+const now = new Date('2026-10-27T12:00:00Z')
+
+test('month starts cover the year ending with the current month', () => {
+ const months = getMonthStarts(now)
+ assert.equal(months.length, 12)
+ assert.equal(months[0]!.toISOString(), '2025-11-01T00:00:00.000Z')
+ assert.equal(months.at(-1)!.toISOString(), '2026-10-01T00:00:00.000Z')
+})
+
+test('selects the latest version released on or before the date', () => {
+ const versions = [
+ { v: '1', releasedAt: '2026-08-31T19:00:00Z' },
+ { v: '2', releasedAt: '2026-10-01T00:00:00Z' },
+ { v: '3', releasedAt: '2026-10-02T00:00:00Z' },
+ { v: 'undated' },
+ ]
+ const date = new Date('2026-10-01T00:00:00Z')
+ assert.equal(selectVersionAt(versions, date)?.v, '2')
+ assert.equal(
+ selectVersionAt(versions, new Date('2026-08-01T00:00:00Z')),
+ undefined,
+ )
+})
+
+const astroVersions = [
+ {
+ package: 'starter-astro',
+ name: 'Astro',
+ isFocused: true,
+ frameworkVersion: '7.1.6',
+ releasedAt: '2026-07-29T14:21:53Z',
+ prodDependencies: 10,
+ },
+ {
+ package: 'starter-astro',
+ name: 'Astro',
+ isFocused: true,
+ frameworkVersion: '7.2.10',
+ releasedAt: '2026-08-31T19:39:50Z',
+ prodDependencies: 12,
+ },
+]
+const astroLatest = {
+ package: 'starter-astro',
+ name: 'Astro',
+ isFocused: true,
+ frameworkVersion: '7.4.0',
+ releasedAt: '2026-10-15T14:49:24Z',
+ prodDependencies: 14,
+}
+
+test('leaves gaps before a framework’s first tracked release', () => {
+ const chart = prepareProdDependenciesMonthlyComparison(
+ astroVersions,
+ [astroLatest],
+ now,
+ )
+
+ assert.equal(chart.id, 'prodDependencies-monthly')
+ const [astro] = chart.series
+ assert.deepEqual(astro!.color, { dark: '#BC52EE' })
+ assert.deepEqual(
+ astro!.points.slice(-5, -1).map((p) => [p.month, p.value, p.version]),
+ [
+ ['2026-07', null, null],
+ ['2026-08', 10, '7.1.6'],
+ ['2026-09', 12, '7.2.10'],
+ ['2026-10', 12, '7.2.10'],
+ ],
+ )
+})
+
+test('the Latest point uses the same latest result as the bar chart', () => {
+ const line = prepareProdDependenciesMonthlyComparison(
+ astroVersions,
+ [astroLatest],
+ now,
+ )
+ const bar = prepareProdDependenciesComparison([astroLatest])
+
+ assert.equal(line.months.at(-1), LATEST_POINT)
+ const nowPoint = line.series[0]!.points.at(-1)!
+ assert.deepEqual(
+ [nowPoint.month, nowPoint.value, nowPoint.version],
+ [LATEST_POINT, bar.data[0]!.value, bar.data[0]!.version],
+ )
+})
+
+test('a framework with only latest results still gets a Latest point', () => {
+ const line = prepareProdDependenciesMonthlyComparison([], [astroLatest], now)
+ const points = line.series[0]!.points
+ assert.ok(points.slice(0, -1).every((p) => p.value == null))
+ assert.equal(points.at(-1)!.value, 14)
+})
diff --git a/packages/docs/src/lib/monthly-history.ts b/packages/docs/src/lib/monthly-history.ts
new file mode 100644
index 00000000..9bfdd819
--- /dev/null
+++ b/packages/docs/src/lib/monthly-history.ts
@@ -0,0 +1,104 @@
+import { getBrandByPackage } from './framework-logos.ts'
+import type {
+ ChartMeasurement,
+ MetricDefinition,
+ MonthlyComparisonChartPayload,
+ MonthlyComparisonSeries,
+} from './types'
+
+const MONTH_COUNT = 12
+const BASELINE_PACKAGE = 'app-baseline-html'
+
+function toMonthKey(date: Date) {
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`
+}
+
+export function getMonthStarts(now: Date, count = MONTH_COUNT) {
+ return Array.from(
+ { length: count },
+ (_, index) =>
+ new Date(
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (count - 1 - index)),
+ ),
+ )
+}
+
+export function selectVersionAt(
+ records: readonly T[],
+ date: Date,
+): T | undefined {
+ let selected: T | undefined
+ let selectedTime = -Infinity
+ for (const record of records) {
+ const time = record.releasedAt ? Date.parse(record.releasedAt) : NaN
+ if (time <= date.getTime() && time > selectedTime) {
+ selected = record
+ selectedTime = time
+ }
+ }
+ return selected
+}
+
+export const LATEST_POINT = 'latest'
+
+export function monthlyComparison(
+ metric: MetricDefinition,
+ history: readonly T[],
+ latest: readonly T[],
+ getValue: (record: T) => number | null,
+ now = new Date(),
+): MonthlyComparisonChartPayload {
+ const monthStarts = getMonthStarts(now)
+ const byPackage = new Map()
+ for (const record of history) {
+ if (record.package == null) continue
+ byPackage.set(record.package, [
+ ...(byPackage.get(record.package) ?? []),
+ record,
+ ])
+ }
+ const latestByPackage = new Map(
+ latest.flatMap((record) =>
+ record.package == null ? [] : [[record.package, record] as const],
+ ),
+ )
+ const packages = new Set([...byPackage.keys(), ...latestByPackage.keys()])
+
+ const series = [...packages].map((packageName): MonthlyComparisonSeries => {
+ const versions = byPackage.get(packageName) ?? []
+ const current = latestByPackage.get(packageName)
+ const identity = current ?? versions[0]!
+ const baseline = packageName === BASELINE_PACKAGE
+ return {
+ id: packageName,
+ name: identity.name ?? packageName,
+ focused: baseline || identity.isFocused !== false,
+ baseline,
+ color: baseline ? null : (getBrandByPackage(packageName)?.color ?? null),
+ points: [
+ ...monthStarts.map((date) => {
+ const record = selectVersionAt(versions, date)
+ return {
+ month: toMonthKey(date),
+ value: record ? getValue(record) : null,
+ version: record?.frameworkVersion ?? null,
+ }
+ }),
+ {
+ month: LATEST_POINT,
+ value: current ? getValue(current) : null,
+ version: current?.frameworkVersion ?? null,
+ },
+ ],
+ }
+ })
+
+ return {
+ ...metric,
+ id: `${metric.id}-monthly`,
+ months: [...monthStarts.map(toMonthKey), LATEST_POINT],
+ series: series
+ .filter((s) => s.points.some((point) => point.value != null))
+ .sort((a, b) => a.name.localeCompare(b.name)),
+ }
+}
diff --git a/packages/docs/src/lib/prepare-chart.ts b/packages/docs/src/lib/prepare-chart.ts
index cd08dc44..7569f0a2 100644
--- a/packages/docs/src/lib/prepare-chart.ts
+++ b/packages/docs/src/lib/prepare-chart.ts
@@ -25,10 +25,10 @@ export const starterStats = latest
export const ssrRequestThroughputStats = latest
.filter((result) => result.projects.app)
.map(prepareRuntimeResult)
-const starterHistory = history
+export const starterHistory = history
.filter((result) => result.projects.starter)
.map(prepareStarterResult)
-const runtimeHistory = history
+export const runtimeHistory = history
.filter((result) => result.projects.app)
.map(prepareRuntimeResult)
type DevtimeVersionData = (typeof starterHistory)[number]
diff --git a/packages/docs/src/lib/types.ts b/packages/docs/src/lib/types.ts
index 58549f0d..baf39ed8 100644
--- a/packages/docs/src/lib/types.ts
+++ b/packages/docs/src/lib/types.ts
@@ -24,6 +24,29 @@ export interface VersionLineChartPayload {
yAxisLabel: string
}
+export interface MonthlyComparisonPoint {
+ /** First day of the month as `YYYY-MM`, or `latest` for the latest results. */
+ month: string
+ value: number | null
+ version: string | null
+}
+
+export interface MonthlyComparisonSeries {
+ id: string
+ name: string
+ focused: boolean
+ /** Reference series (Baseline HTML) drawn as a neutral dashed line. */
+ baseline: boolean
+ /** Brand colour from `framework-logos.ts`; null draws a neutral line. */
+ color: { dark: string; light?: string } | null
+ points: MonthlyComparisonPoint[]
+}
+
+export interface MonthlyComparisonChartPayload extends MetricDefinition {
+ months: string[]
+ series: MonthlyComparisonSeries[]
+}
+
export interface MetricDefinition {
id: string
title: string
@@ -60,10 +83,15 @@ export interface ChartMeasurement {
package?: string
name?: string
frameworkVersion?: string
+ releasedAt?: string
isFocused?: boolean
devServerStartTime?: {
medianMs?: number
}
+ install?: {
+ installTimeMs?: { mean?: number }
+ nodeModulesSizeBytes?: number
+ }
frameworkDependencies?: {
prodDependencies?: number
devDependencies?: number
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9c16217c..ca63b0cf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -72,10 +72,16 @@ importers:
chart.js:
specifier: ^4.5.1
version: 4.5.1
+ d3-shape:
+ specifier: 3.2.0
+ version: 3.2.0
devDependencies:
'@astrojs/check':
specifier: ^0.9.10
version: 0.9.10(prettier-plugin-astro@0.14.1)(prettier@3.9.6)(typescript@6.0.3)
+ '@types/d3-shape':
+ specifier: 3.2.0
+ version: 3.2.0
'@typescript-eslint/parser':
specifier: 8.62.1
version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)
@@ -3027,8 +3033,8 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
- legacy-javascript@0.0.3:
- resolution: {integrity: sha512-1r6MT4dVaJyXt4G07Cm2cQ0AUaLJdefiEp4SmIAylA++gnEQ82YvmzE58RkM5EP0fk5hROLCslUPGnwIYdZQVA==}
+ legacy-javascript@0.0.4:
+ resolution: {integrity: sha512-UTmeKBVxnfJcpmpOvzzNnLsQT99BXHpPlc/ZKddIRmWmrR0fw6335I3lq4+sJoP60UTbBNaYsGYVPc65YB1x3A==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
@@ -5589,7 +5595,7 @@ snapshots:
'@paulirish/trace_engine@0.0.65':
dependencies:
- legacy-javascript: 0.0.3
+ legacy-javascript: 0.0.4
third-party-web: 0.30.0
'@pkgr/core@0.3.6': {}
@@ -7340,7 +7346,7 @@ snapshots:
yaml: 2.9.0
zod: 4.6.7
- legacy-javascript@0.0.3: {}
+ legacy-javascript@0.0.4: {}
levn@0.4.1:
dependencies: