diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 00000000000..549ec09d8fe --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,34 @@ +name: CodSpeed + +on: + # disable for now + # pull_request: + # push: + # branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write + +jobs: + client-nav: + name: Client Nav Benchmarks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6.0.1 + + - name: Setup Tools + uses: tanstack/config/.github/setup@main + + - name: Run Client Navigation Benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: | + CI=1 NX_DAEMON=false pnpm nx run tanstack-router-benchmark-client-nav:bench --outputStyle=stream --skipNxCache --skipRemoteCache diff --git a/benchmarks/client-nav/package.json b/benchmarks/client-nav/package.json new file mode 100644 index 00000000000..c9416967ea7 --- /dev/null +++ b/benchmarks/client-nav/package.json @@ -0,0 +1,39 @@ +{ + "name": "tanstack-router-benchmark-client-nav", + "private": true, + "type": "module", + "scripts": { + "bench": "pnpm run bench:react && pnpm run bench:solid && pnpm run bench:vue", + "bench:react": "NODE_ENV=production vitest bench --run --config react/vitest.config.ts react/client-nav.bench.tsx", + "bench:solid": "NODE_ENV=production vitest bench --run --config solid/vitest.config.ts solid/client-nav.bench.tsx", + "bench:vue": "NODE_ENV=production vitest bench --run --config vue/vitest.config.ts vue/client-nav.bench.ts" + }, + "dependencies": { + "@tanstack/react-router": "workspace:*", + "@tanstack/solid-router": "workspace:*", + "@tanstack/vue-router": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "solid-js": "^1.9.10", + "vue": "^3.5.25" + }, + "devDependencies": { + "@codspeed/vitest-plugin": "^5.0.1", + "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10", + "vitest": "^4.0.17" + }, + "nx": { + "targets": { + "bench": { + "dependsOn": [ + "^build" + ], + "cache": false + } + } + } +} \ No newline at end of file diff --git a/benchmarks/client-nav/react/client-nav.bench.tsx b/benchmarks/client-nav/react/client-nav.bench.tsx new file mode 100644 index 00000000000..7db90c0f364 --- /dev/null +++ b/benchmarks/client-nav/react/client-nav.bench.tsx @@ -0,0 +1,174 @@ +import { createRoot } from 'react-dom/client' +import { bench } from 'vitest' +import { useEffect } from 'react' +import { + Link, + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '@tanstack/react-router' + +import { + HOOK_COUNT, + LINK_COUNT, + TARGET_ID, + TIMEOUT, + heavySelect, + parseIntOrZero, +} from '../shared' + +function setupBenchmark() { + const rootRoute = createRootRoute({ + component: () => { + const selectedParams = Array.from({ length: HOOK_COUNT }, (_, index) => + rootRoute.useParams({ + strict: false, + select: (params) => heavySelect(params.id, index), + }), + ) + + const selectedSearch = Array.from({ length: HOOK_COUNT }, (_, index) => + rootRoute.useSearch({ + strict: false, + select: (search) => heavySelect(search.n, index + HOOK_COUNT), + }), + ) + + const links = Array.from({ length: LINK_COUNT }, (_, index) => ( + + Link {index} + + )) + + const rootScore = + selectedParams.reduce((sum, value) => sum + value, 0) + + selectedSearch.reduce((sum, value) => sum + value, 0) + + return ( +
+
{rootScore}
+
{links}
+ +
+ ) + }, + }) + + const idRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/$id', + validateSearch: (search: Record) => ({ + n: parseIntOrZero(search.n), + }), + component: () => { + const id = idRoute.useParams({ + select: (params) => parseIntOrZero(params.id), + }) + const n = idRoute.useSearch({ + select: (search) => parseIntOrZero(search.n), + }) + const navigate = idRoute.useNavigate() + + useEffect(() => { + if (id < TARGET_ID) { + const next = id + 1 + void navigate({ + to: '/$id', + params: { id: String(next) }, + search: { n: next }, + }) + } + }, [id, n, navigate]) + + return null + }, + }) + + const routeTree = rootRoute.addChildren([idRoute]) + + const router = createRouter({ + routeTree, + history: createMemoryHistory({ + initialEntries: ['/0?n=0'], + }), + }) + + const container = document.createElement('div') + document.body.appendChild(container) + const reactRoot = createRoot(container) + + reactRoot.render() + + const done = new Promise((resolve, reject) => { + const expectedHref = `/${TARGET_ID}?n=${TARGET_ID}` + + let settled = false + let unsubscribe = () => {} + + const settle = (error?: Error) => { + if (settled) { + return + } + + settled = true + window.clearTimeout(timeoutId) + unsubscribe() + + if (error) { + reject(error) + } else { + resolve() + } + } + + const timeoutId = window.setTimeout(() => { + settle( + new Error( + `React benchmark timed out at ${router.state.location.href}; expected ${expectedHref}`, + ), + ) + }, TIMEOUT) + + unsubscribe = router.subscribe('onResolved', (event) => { + if (event.toLocation.href === expectedHref) { + settle() + } + }) + + if (router.state.location.href === expectedHref) { + settle() + } + }) + + return { + done, + cleanup: () => { + reactRoot.unmount() + container.remove() + }, + } +} + +bench( + 'client-nav.react.10-nav', + async () => { + const { done, cleanup } = setupBenchmark() + + try { + await done + } finally { + cleanup() + } + }, + { + throws: true, + }, +) diff --git a/benchmarks/client-nav/react/vitest.config.ts b/benchmarks/client-nav/react/vitest.config.ts new file mode 100644 index 00000000000..256a46bcea3 --- /dev/null +++ b/benchmarks/client-nav/react/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import codspeedPlugin from '@codspeed/vitest-plugin' + +export default defineConfig({ + plugins: [react(), codspeedPlugin()], + resolve: { + conditions: ['browser'], + }, + test: { + environment: 'jsdom', + include: ['react/**/*.bench.tsx'], + benchmark: { + include: ['react/**/*.bench.tsx'], + }, + }, +}) diff --git a/benchmarks/client-nav/shared.ts b/benchmarks/client-nav/shared.ts new file mode 100644 index 00000000000..c32a0fa9bf6 --- /dev/null +++ b/benchmarks/client-nav/shared.ts @@ -0,0 +1,29 @@ +export const LINK_COUNT = 20 +export const HOOK_COUNT = 20 +export const TARGET_ID = 10 +export const TIMEOUT = 10_000 + +export function heavySelect( + seed: string | number | undefined, + salt: number, +): number { + let value = + typeof seed === 'number' ? seed : Number.parseInt(seed ?? '0', 10) || 0 + + for (let i = 0; i < 5; i++) { + value = (value * 33 + salt + i) % 104_729 + value ^= (value << 5) & 0xffff + value &= 0x7fffffff + } + + return value +} + +export function parseIntOrZero(value: unknown): number { + if (typeof value === 'number') { + return Number.isFinite(value) ? value : 0 + } + + const parsed = Number.parseInt(String(value ?? '0'), 10) + return Number.isFinite(parsed) ? parsed : 0 +} diff --git a/benchmarks/client-nav/solid/client-nav.bench.tsx b/benchmarks/client-nav/solid/client-nav.bench.tsx new file mode 100644 index 00000000000..8490f273324 --- /dev/null +++ b/benchmarks/client-nav/solid/client-nav.bench.tsx @@ -0,0 +1,170 @@ +import { render } from '@solidjs/testing-library' +import { bench } from 'vitest' +import { + Link, + RouterContextProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + useParams, + useSearch, +} from '@tanstack/solid-router' + +import { + HOOK_COUNT, + LINK_COUNT, + TARGET_ID, + TIMEOUT, + heavySelect, + parseIntOrZero, +} from '../shared' + +function BenchmarkComponent() { + const selectedParams = Array.from({ length: HOOK_COUNT }, (_, index) => + useParams({ + from: '/$id', + strict: false, + shouldThrow: false, + select: (params) => heavySelect(params.id, index), + }), + ) + + const selectedSearch = Array.from({ length: HOOK_COUNT }, (_, index) => + useSearch({ + from: '/$id', + strict: false, + shouldThrow: false, + select: (search) => heavySelect(search.n, index + HOOK_COUNT), + }), + ) + + const selectedId = useParams({ + from: '/$id', + strict: false, + shouldThrow: false, + select: (params) => parseIntOrZero(params.id), + }) + + const selectedN = useSearch({ + from: '/$id', + strict: false, + shouldThrow: false, + select: (search) => parseIntOrZero(search.n), + }) + + const rootScore = () => { + const paramsScore = selectedParams.reduce( + (sum, accessor) => sum + accessor(), + 0, + ) + const searchScore = selectedSearch.reduce( + (sum, accessor) => sum + accessor(), + 0, + ) + return paramsScore + searchScore + selectedId() + selectedN() + } + + return ( +
+
{rootScore()}
+
+ {Array.from({ length: LINK_COUNT }, (_, index) => ( + + Link {index} + + ))} +
+
+ ) +} + +function setupBenchmark() { + const rootRoute = createRootRoute() + const idRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/$id', + validateSearch: (search: Record) => ({ + n: parseIntOrZero(search.n), + }), + }) + + const routeTree = rootRoute.addChildren([idRoute]) + + const router = createRouter({ + routeTree, + history: createMemoryHistory({ + initialEntries: ['/0?n=0'], + }), + }) + + const mounted = render(() => ( + + {() => } + + )) + + const done = new Promise((resolve, reject) => { + const expectedHref = `/${TARGET_ID}?n=${TARGET_ID}` + + const timeoutId = window.setTimeout(() => { + reject( + new Error( + `Solid benchmark timed out at ${router.state.location.href}; expected ${expectedHref}`, + ), + ) + }, TIMEOUT) + + void (async () => { + try { + await router.load() + + for (let next = 1; next <= TARGET_ID; next++) { + await router.navigate({ + to: '/$id', + params: { id: String(next) }, + search: { n: next }, + }) + } + + if (router.state.location.href !== expectedHref) { + reject( + new Error( + `Solid benchmark stopped at ${router.state.location.href}; expected ${expectedHref}`, + ), + ) + return + } + + resolve() + } catch (error) { + reject(error) + } finally { + window.clearTimeout(timeoutId) + } + })() + }) + + return { + done, + cleanup: () => { + mounted.unmount() + }, + } +} + +bench( + 'client-nav.solid.10-nav', + async () => { + const { done, cleanup } = setupBenchmark() + + try { + await done + } finally { + cleanup() + } + }, + { + throws: true, + }, +) diff --git a/benchmarks/client-nav/solid/vitest.config.ts b/benchmarks/client-nav/solid/vitest.config.ts new file mode 100644 index 00000000000..db443a516ad --- /dev/null +++ b/benchmarks/client-nav/solid/vitest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config' +import solid from 'vite-plugin-solid' +import codspeedPlugin from '@codspeed/vitest-plugin' + +export default defineConfig({ + plugins: [solid({ ssr: false, dev: false, hot: false }), codspeedPlugin()], + resolve: { + conditions: ['solid', 'browser'], + }, + test: { + environment: 'jsdom', + include: ['solid/**/*.bench.tsx'], + benchmark: { + include: ['solid/**/*.bench.tsx'], + }, + server: { + deps: { + inline: [/@solidjs/, /@tanstack\/solid-store/], + }, + }, + }, +}) diff --git a/benchmarks/client-nav/tsconfig.json b/benchmarks/client-nav/tsconfig.json new file mode 100644 index 00000000000..b088161598e --- /dev/null +++ b/benchmarks/client-nav/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "skipLibCheck": true, + "types": ["vite/client", "vitest/globals"] + }, + "exclude": ["node_modules", "dist"] +} diff --git a/benchmarks/client-nav/vue/client-nav.bench.ts b/benchmarks/client-nav/vue/client-nav.bench.ts new file mode 100644 index 00000000000..f002d7159ad --- /dev/null +++ b/benchmarks/client-nav/vue/client-nav.bench.ts @@ -0,0 +1,185 @@ +import { createApp, defineComponent, h, watchEffect } from 'vue' +import { bench } from 'vitest' +import { + Link, + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '@tanstack/vue-router' + +import { + HOOK_COUNT, + LINK_COUNT, + TARGET_ID, + TIMEOUT, + heavySelect, + parseIntOrZero, +} from '../shared' + +function setupBenchmark() { + const RootComponent = defineComponent(() => { + const selectedParams = Array.from({ length: HOOK_COUNT }, (_, index) => + rootRoute.useParams({ + strict: false, + select: (params) => heavySelect(params.id, index), + }), + ) + + const selectedSearch = Array.from({ length: HOOK_COUNT }, (_, index) => + rootRoute.useSearch({ + strict: false, + select: (search) => heavySelect(search.n, index + HOOK_COUNT), + }), + ) + + return () => { + const rootScore = + selectedParams.reduce((sum, current) => sum + current.value, 0) + + selectedSearch.reduce((sum, current) => sum + current.value, 0) + + return h('div', [ + h('div', { style: 'display: none' }, String(rootScore)), + h( + 'div', + { style: 'display: none' }, + Array.from({ length: LINK_COUNT }, (_, index) => + h( + Link, + { + to: '/$id', + params: { id: String(index) }, + search: { n: index }, + }, + { + default: () => `Link ${index}`, + }, + ), + ), + ), + h(Outlet), + ]) + } + }) + + const IdComponent = defineComponent(() => { + const id = idRoute.useParams({ + select: (params) => parseIntOrZero(params.id), + }) + const n = idRoute.useSearch({ + select: (search) => parseIntOrZero(search.n), + }) + const navigate = idRoute.useNavigate() + + watchEffect(() => { + const currentId = id.value + void n.value + + if (currentId < TARGET_ID) { + const next = currentId + 1 + void navigate({ + to: '/$id', + params: { id: String(next) }, + search: { n: next }, + }) + } + }) + + return () => null + }) + + const rootRoute = createRootRoute({ component: RootComponent }) + const idRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/$id', + validateSearch: (search: Record) => ({ + n: parseIntOrZero(search.n), + }), + component: IdComponent, + }) + + const routeTree = rootRoute.addChildren([idRoute]) + + const router = createRouter({ + routeTree, + history: createMemoryHistory({ + initialEntries: ['/0?n=0'], + }), + }) + + const container = document.createElement('div') + document.body.appendChild(container) + + const app = createApp({ + render: () => h(RouterProvider, { router }), + }) + + app.mount(container) + + const done = new Promise((resolve, reject) => { + const expectedHref = `/${TARGET_ID}?n=${TARGET_ID}` + + let settled = false + let unsubscribe = () => {} + + const settle = (error?: Error) => { + if (settled) { + return + } + + settled = true + window.clearTimeout(timeoutId) + unsubscribe() + + if (error) { + reject(error) + } else { + resolve() + } + } + + const timeoutId = window.setTimeout(() => { + settle( + new Error( + `Vue benchmark timed out at ${router.state.location.href}; expected ${expectedHref}`, + ), + ) + }, TIMEOUT) + + unsubscribe = router.subscribe('onResolved', (event) => { + if (event.toLocation.href === expectedHref) { + settle() + } + }) + + if (router.state.location.href === expectedHref) { + settle() + } + }) + + return { + done, + cleanup: () => { + app.unmount() + container.remove() + }, + } +} + +bench( + 'client-nav.vue.10-nav', + async () => { + const { done, cleanup } = setupBenchmark() + + try { + await done + } finally { + cleanup() + } + }, + { + throws: true, + }, +) diff --git a/benchmarks/client-nav/vue/vitest.config.ts b/benchmarks/client-nav/vue/vitest.config.ts new file mode 100644 index 00000000000..21a9f3d0cf6 --- /dev/null +++ b/benchmarks/client-nav/vue/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config' +import vue from '@vitejs/plugin-vue' +import codspeedPlugin from '@codspeed/vitest-plugin' + +export default defineConfig({ + plugins: [vue(), codspeedPlugin()], + resolve: { + conditions: ['browser'], + }, + test: { + environment: 'jsdom', + include: ['vue/**/*.bench.ts'], + benchmark: { + include: ['vue/**/*.bench.ts'], + }, + }, +}) diff --git a/package.json b/package.json index 5b6c19258f9..dfaaf8e4d36 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:types": "nx affected --target=test:types --exclude=examples/**", "test:e2e": "nx run-many --target=test:e2e", "benchmark:bundle-size": "pnpm nx run tanstack-router-e2e-bundle-size:build", + "benchmark:client-nav": "pnpm nx run tanstack-router-benchmark-client-nav:bench", "build": "nx affected --target=build --exclude=e2e/** --exclude=examples/**", "build:all": "nx run-many --target=build --exclude=examples/** --exclude=e2e/**", "watch": "pnpm run build:all && nx watch --all -- pnpm run build:all", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4580b961153..b3b0e2dc514 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,52 @@ importers: specifier: ^4.0.17 version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + benchmarks/client-nav: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../packages/react-router + '@tanstack/solid-router': + specifier: workspace:* + version: link:../../packages/solid-router + '@tanstack/vue-router': + specifier: workspace:* + version: link:../../packages/vue-router + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + solid-js: + specifier: 1.9.10 + version: 1.9.10 + vue: + specifier: ^3.5.25 + version: 3.5.25(typescript@5.9.3) + devDependencies: + '@codspeed/vitest-plugin': + specifier: ^5.0.1 + version: 5.2.0(tinybench@2.9.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vitest@4.0.17) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.3(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.3)) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + vite-plugin-solid: + specifier: ^2.11.10 + version: 2.11.10(@testing-library/jest-dom@6.6.3)(solid-js@1.9.10)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + vitest: + specifier: ^4.0.17 + version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + e2e/bundle-size: dependencies: '@tanstack/react-router': @@ -13264,6 +13310,16 @@ packages: cpu: [x64] os: [win32] + '@codspeed/core@5.2.0': + resolution: {integrity: sha512-CmDhpWjcOJg2iBOQ/BmBnSBq8qxlM3r4h8uvYDkoUaba+EKRT3T73BZtKuml/48jZMsB+4/FG2UbTBinDWtuvw==} + + '@codspeed/vitest-plugin@5.2.0': + resolution: {integrity: sha512-soXKIQBqJzjVQyWRwe2HNfhCaBgxhG25m8+PI3F5zFFsV3FQxMJXHsMECNtrgm+SRiCiWv/OFTcfCMZRy4nKtw==} + peerDependencies: + tinybench: '>=2.9.0' + vite: ^7.3.1 + vitest: ^3.2 || ^4 + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -20552,6 +20608,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + find-up@7.0.0: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} @@ -25703,6 +25763,24 @@ snapshots: '@cloudflare/workerd-windows-64@1.20251118.0': optional: true + '@codspeed/core@5.2.0': + dependencies: + axios: 1.13.2 + find-up: 6.3.0 + form-data: 4.0.5 + node-gyp-build: 4.8.4 + transitivePeerDependencies: + - debug + + '@codspeed/vitest-plugin@5.2.0(tinybench@2.9.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))(vitest@4.0.17)': + dependencies: + '@codspeed/core': 5.2.0 + tinybench: 2.9.0 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + vitest: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + transitivePeerDependencies: + - debug + '@colors/colors@1.5.0': optional: true @@ -31273,7 +31351,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + vitest: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -33957,6 +34035,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + find-up@7.0.0: dependencies: locate-path: 7.2.0 @@ -38599,6 +38682,45 @@ snapshots: - tsx - yaml + vitest@4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.17 + '@vitest/mocker': 4.0.17(msw@2.7.0(@types/node@25.0.9)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.17 + '@vitest/runner': 4.0.17 + '@vitest/snapshot': 4.0.17 + '@vitest/spy': 4.0.17 + '@vitest/utils': 4.0.17 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.0.9 + '@vitest/ui': 4.0.17(vitest@4.0.17) + jsdom: 27.0.0(postcss@8.5.6) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vscode-uri@3.0.8: {} vue-component-type-helpers@2.2.12: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fe84aeb0379..2810feb95a7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,6 +12,7 @@ packages: - 'examples/react/router-monorepo-simple-lazy/packages/*' - 'e2e/e2e-utils' - 'e2e/bundle-size' + - 'benchmarks/*' - 'e2e/react-router/*' - 'e2e/solid-router/*' - 'e2e/vue-router/*'