Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/bundle-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Bundle Size

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
benchmark-pr:
name: Benchmark PR
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
with:
fetch-depth: 0

- name: Setup Tools
uses: tanstack/config/.github/setup@main

- name: Measure Bundle Size
run: pnpm nx run tanstack-router-e2e-bundle-size:build --outputStyle=stream --skipRemoteCache

- name: Read Historical Data (if available)
run: |
mkdir -p e2e/bundle-size/results
if git fetch --depth=1 origin gh-pages; then
if git show origin/gh-pages:benchmarks/bundle-size/data.js > e2e/bundle-size/results/history-data.js 2>/dev/null; then
echo "Loaded bundle-size history from gh-pages."
else
rm -f e2e/bundle-size/results/history-data.js
echo "No bundle-size history found on gh-pages yet."
fi
fi

- name: Build PR Report
run: |
node scripts/benchmarks/bundle-size/pr-report.mjs \
--current e2e/bundle-size/results/current.json \
--history e2e/bundle-size/results/history-data.js \
--output e2e/bundle-size/results/pr-comment.md \
--base-sha "${{ github.event.pull_request.base.sha }}" \
--dashboard-url "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/benchmarks/bundle-size/"

- name: Upsert Sticky PR Comment
if: github.event.pull_request.head.repo.fork == false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/benchmarks/common/upsert-pr-comment.mjs \
--pr "${{ github.event.pull_request.number }}" \
--body-file e2e/bundle-size/results/pr-comment.md

benchmark-main:
name: Publish Bundle Size History
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository_owner == 'TanStack'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.1
with:
fetch-depth: 0

- name: Setup Tools
uses: tanstack/config/.github/setup@main

- name: Measure Bundle Size
run: pnpm nx run tanstack-router-e2e-bundle-size:build --outputStyle=stream --skipRemoteCache

- name: Publish Benchmark Dashboard
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
with:
tool: customSmallerIsBetter
benchmark-name: Bundle Size (gzip)
output-file-path: e2e/bundle-size/results/benchmark-action.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
gh-pages-branch: gh-pages
benchmark-data-dir-path: benchmarks/bundle-size
max-items-in-chart: 200
summary-always: true
comment-on-alert: false
fail-on-alert: false
8 changes: 8 additions & 0 deletions e2e/bundle-size/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
scenarios/*/src/routeTree.gen.ts

results/*.json
results/*.md
results/*.js
!results/.gitkeep
58 changes: 58 additions & 0 deletions e2e/bundle-size/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Bundle Size Benchmarks

This workspace contains deterministic bundle-size fixtures for:

- `@tanstack/react-router`
- `@tanstack/solid-router`
- `@tanstack/vue-router`
- `@tanstack/react-start`
- `@tanstack/solid-start`

Each package has two scenarios:

- `minimal`: Small route app with `__root` + index route that renders `hello world`
- `full`: Same route shape plus a broad root-level harness that imports/uses the full hooks/components surface
- Start `full` scenarios also exercise `createServerFn`, `createMiddleware`, and `useServerFn`

## Design Notes

- Scenarios use file-based routing as the default app style.
- Router scenarios use `@tanstack/router-plugin/vite` with `autoCodeSplitting: true`.
- Start scenarios use `@tanstack/<framework>-start/plugin/vite` with router code-splitting enabled.
- Full-surface coverage is manually maintained (no strict export-coverage gate).
- Metrics are measured from initial-load JS graph only and reported as raw/gzip/brotli bytes.
- Gzip is the primary tracking signal for PR deltas and historical charting.

## Local Run

```bash
pnpm nx run tanstack-router-e2e-bundle-size:build
```

This writes:

- `e2e/bundle-size/results/current.json`
- `e2e/bundle-size/results/benchmark-action.json`

## CI Reporting

- PR workflow generates a sticky comment with:
- current gzip values
- baseline delta
- inline sparkline trend
- Pushes to `main` publish historical chart data to GitHub Pages via `benchmark-action/github-action-benchmark`.

## Manual Update Policy

When router/start public hooks/components evolve, update the corresponding `*-full/src/routes/__root.tsx` harness to keep full scenarios representative.

## Backfill Readiness

The measurement script supports optional interfaces for historical backfilling:

- `--sha`
- `--measured-at`
- `--append-history`

These are intended for one-off scripts that replay historical commits and append results to the same history dataset shape used for chart generation.
If `--append-history` points at a `data.js` file, output is written as `window.BENCHMARK_DATA = ...` for direct GitHub Pages compatibility.
30 changes: 30 additions & 0 deletions e2e/bundle-size/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "tanstack-router-e2e-bundle-size",
"private": true,
"type": "module",
"scripts": {
"build": "node ../../scripts/benchmarks/bundle-size/measure.mjs"
},
"dependencies": {
"@tanstack/react-router": "workspace:^",
"@tanstack/solid-router": "workspace:^",
"@tanstack/vue-router": "workspace:^",
"@tanstack/react-start": "workspace:^",
"@tanstack/solid-start": "workspace:^",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"solid-js": "^1.9.10",
"vue": "^3.5.25"
},
"devDependencies": {
"@tanstack/router-plugin": "workspace:^",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-solid": "^2.11.10"
}
}
1 change: 1 addition & 0 deletions e2e/bundle-size/results/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

12 changes: 12 additions & 0 deletions e2e/bundle-size/scenarios/react-router-full/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react-router-full</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
22 changes: 22 additions & 0 deletions e2e/bundle-size/scenarios/react-router-full/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({
routeTree,
scrollRestoration: true,
})

declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

const rootElement = document.getElementById('app')
if (!rootElement) {
throw new Error('Root element `#app` not found')
}
if (!rootElement.innerHTML) {
ReactDOM.createRoot(rootElement).render(<RouterProvider router={router} />)
}
Loading
Loading