diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..8f4e24c72a0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,171 @@ +# AGENTS.md + +## Project overview + +TanStack Router is a type-safe router with built-in caching and URL state management for React and Solid applications. This monorepo contains two main products: + +- **TanStack Router** - Core routing library with type-safe navigation, search params, and path params +- **TanStack Start** - Full-stack framework built on top of TanStack Router + +## Setup commands + +- Install deps: `pnpm install` +- Setup e2e testing: `pnpm exec playwright install` +- Build packages: `pnpm build` (affected) or `pnpm build:all` (force all) +- Start dev server: `pnpm dev` +- Run tests: `pnpm test` + +## Code style + +- TypeScript strict mode with extensive type safety +- Framework-agnostic core logic separated from React/Solid bindings +- Type-safe routing with search params and path params +- Use workspace protocol for internal dependencies (`workspace:*`) + +## Dev environment tips + +- This is a pnpm workspace monorepo with packages organized by functionality +- Nx provides caching, affected testing, targeting, and parallel execution for efficiency +- Use `npx nx show projects` to list all available packages +- Target specific packages: `npx nx run @tanstack/react-router:test:unit` +- Target multiple packages: `npx nx run-many --target=test:eslint --projects=@tanstack/history,@tanstack/router-core` +- Run affected tests only: `npx nx affected --target=test:unit` +- Exclude patterns: `npx nx run-many --target=test:unit --exclude="examples/**,e2e/**"` +- Navigate to examples and run `pnpm dev` to test changes: `cd examples/react/basic && pnpm dev` +- **Granular Vitest testing within packages:** + - Navigate first: `cd packages/react-router` + - Specific files: `npx vitest run tests/link.test.tsx tests/Scripts.test.tsx` + - Test patterns: `npx vitest run tests/ClientOnly.test.tsx -t "should render fallback"` + - Name patterns: `npx vitest run -t "navigation"` (all tests with "navigation" in name) + - Exclude patterns: `npx vitest run --exclude="**/*link*" tests/` + - List tests: `npx vitest list tests/link.test.tsx` or `npx vitest list` (all) + - Through nx: `npx nx run @tanstack/react-router:test:unit -- tests/ClientOnly.test.tsx` +- **Available test targets per package:** `test:unit`, `test:types`, `test:eslint`, `test:build`, `test:perf`, `build` +- **Testing strategy:** Package level (nx) → File level (vitest) → Test level (-t flag) → Pattern level (exclude) + +## Testing instructions + +- **Critical**: Always run unit and type tests during development - do not proceed if they fail +- **Test types:** `pnpm test:unit`, `pnpm test:types`, `pnpm test:eslint`, `pnpm test:e2e`, `pnpm test:build` +- **Full CI suite:** `pnpm test:ci` +- **Fix formatting:** `pnpm prettier:write` +- **Efficient targeted testing workflow:** + 1. **Affected only:** `npx nx affected --target=test:unit` (compares to main branch) + 2. **Specific packages:** `npx nx run @tanstack/react-router:test:unit` + 3. **Specific files:** `cd packages/react-router && npx vitest run tests/link.test.tsx` + 4. **Specific patterns:** `npx vitest run tests/link.test.tsx -t "preloading"` +- **Pro tips:** + - Use `npx vitest list` to explore available tests before running + - Use `-t "pattern"` to focus on specific functionality during development + - Use `--exclude` patterns to skip unrelated tests + - Combine nx package targeting with vitest file targeting for maximum precision +- **Example workflow:** `npx nx run @tanstack/react-router:test:unit` → `cd packages/react-router && npx vitest run tests/link.test.tsx` → `npx vitest run tests/link.test.tsx -t "preloading"` + +## PR instructions + +- Always run `pnpm test:eslint`, `pnpm test:types`, and `pnpm test:unit` before committing +- Test changes in relevant example apps: `cd examples/react/basic && pnpm dev` +- Update corresponding documentation in `docs/` directory when adding features +- Add or update tests for any code changes +- Use internal docs links relative to `docs/` folder (e.g., `./guide/data-loading`) + +## Package structure + +**Core packages:** + +- `packages/router-core/` - Framework-agnostic core router logic +- `packages/react-router/`, `packages/solid-router/` - React/Solid bindings and components +- `packages/history/` - Browser history management + +**Tooling:** + +- `packages/router-cli/` - CLI tools for code generation +- `packages/router-generator/` - Route generation utilities +- `packages/router-plugin/` - Universal bundler plugins (Vite, Webpack, ESBuild, Rspack) +- `packages/virtual-file-routes/` - Virtual file routing system + +**Developer experience:** + +- `packages/router-devtools/`, `packages/*-router-devtools/` - Development tools +- `packages/eslint-plugin-router/` - ESLint rules for router + +**Validation adapters:** + +- `packages/zod-adapter/`, `packages/valibot-adapter/`, `packages/arktype-adapter/` + +**Start framework:** + +- `packages/*-start/`, `packages/start-*/` - Full-stack framework packages + +**Examples & testing:** + +- `examples/react/`, `examples/solid/` - Example applications (test changes here) +- `e2e/` - End-to-end tests (requires Playwright) +- `docs/router/`, `docs/start/` - Documentation with React/Solid subdirectories + +**Dependencies:** Uses workspace protocol (`workspace:*`) - core → framework → start packages + +## Common development tasks + +**Adding new routes:** + +- Use file-based routing in `src/routes/` directories +- Or use code-based routing with route definitions +- Run route generation with CLI tools + +**Testing changes:** + +- Build packages: `pnpm build` or `pnpm dev` (watch mode) +- Run example apps to test functionality +- Use devtools for debugging router state + +**Documentation updates:** + +- Update relevant docs in `docs/` directory +- Ensure examples reflect documentation changes +- Test documentation links and references +- Use relative links to `docs/` folder format + +## Framework-specific notes + +**React:** + +- Uses React Router components and hooks +- Supports React Server Components (RSC) +- Examples include React Query integration +- Package: `@tanstack/react-router` + +**Solid:** + +- Uses Solid Router components and primitives +- Supports Solid Start for full-stack applications +- Examples include Solid Query integration +- Package: `@tanstack/solid-router` + +## Environment requirements + +- **Node.js** - Required for development +- **pnpm** - Package manager (required for workspace features) +- **Playwright** - Required for e2e tests (`pnpm exec playwright install`) + +## Key architecture patterns + +- **Type Safety**: Extensive TypeScript for type-safe routing +- **Framework Agnostic**: Core logic separated from framework bindings +- **Plugin Architecture**: Universal bundler plugins using unplugin +- **File-based Routing**: Support for both code-based and file-based routing +- **Search Params**: First-class support for type-safe search parameters + +## Development workflow + +1. **Setup**: `pnpm install` and `pnpm exec playwright install` +2. **Build**: `pnpm build:all` or `pnpm dev` for watch mode +3. **Test**: Make changes and run relevant tests (use nx for targeted testing) +4. **Examples**: Navigate to examples and run `pnpm dev` to test changes +5. **Quality**: Run `pnpm test:eslint`, `pnpm test:types`, `pnpm test:unit` before committing + +## References + +- **Documentation**: https://tanstack.com/router +- **GitHub**: https://github.com/TanStack/router +- **Discord Community**: https://discord.com/invite/WrRKjPJ diff --git a/AI.md b/AI.md deleted file mode 100644 index 5860ac0188b..00000000000 --- a/AI.md +++ /dev/null @@ -1,348 +0,0 @@ -# AI.md - -This file provides guidance to AI assistants when working with the TanStack Router codebase. - -## Project Overview - -TanStack Router is a type-safe router with built-in caching and URL state management for React and Solid applications. This monorepo contains two main products: - -- **TanStack Router** - Core routing library with type-safe navigation, search params, and path params -- **TanStack Start** - Full-stack framework built on top of TanStack Router - -## Repository Structure - -### Core Packages - -**Router Core:** - -- `router-core/` - Framework-agnostic core router logic -- `react-router/` - React bindings and components -- `solid-router/` - Solid bindings and components -- `history/` - Browser history management - -**Tooling:** - -- `router-cli/` - CLI tools for code generation -- `router-generator/` - Route generation utilities -- `router-plugin/` - Universal bundler plugins (Vite, Webpack, ESBuild, Rspack) -- `router-vite-plugin/` - Vite-specific plugin wrapper -- `virtual-file-routes/` - Virtual file routing system - -**Developer Experience:** - -- `router-devtools/` - Router development tools -- `router-devtools-core/` - Core devtools functionality -- `react-router-devtools/` - React-specific devtools -- `solid-router-devtools/` - Solid-specific devtools -- `eslint-plugin-router/` - ESLint rules for router - -**Adapters:** - -- `zod-adapter/` - Zod validation adapter -- `valibot-adapter/` - Valibot validation adapter -- `arktype-adapter/` - ArkType validation adapter - -**Start Framework:** - -- `start/` - Core start framework -- `react-start/` - React Start framework -- `solid-start/` - Solid Start framework -- `start-*` packages - Various start framework utilities - -### Documentation - -Documentation is organized in `docs/`: - -- `docs/router/` - Router-specific documentation -- `docs/start/` - Start framework documentation -- Each has `framework/react/` and `framework/solid/` subdirectories - -### Examples - -Extensive examples in `examples/`: - -- `examples/react/` - React router examples -- `examples/solid/` - Solid router examples -- Examples range from basic usage to complex applications - -### Testing - -- `e2e/` - End-to-end tests organized by framework -- Individual packages have `tests/` directories -- Uses Playwright for e2e testing - -## Essential Commands - -### Development - -```bash -# Install dependencies -pnpm install - -# Build all packages (affected only) -pnpm build - -# Build all packages (force all) -pnpm build:all - -# Development mode with watch -pnpm dev - -# Run all tests -pnpm test - -# Run tests in CI mode -pnpm test:ci -``` - -### Testing - -```bash -# Run unit tests -pnpm test:unit - -# Run e2e tests -pnpm test:e2e - -# Run type checking -pnpm test:types - -# Run linting -pnpm test:eslint - -# Run formatting check -pnpm test:format - -# Fix formatting -pnpm prettier:write -``` - -### Targeted Testing with Nx - -```bash -# Target specific package -npx nx run @tanstack/react-router:test:unit -npx nx run @tanstack/router-core:test:types -npx nx run @tanstack/history:test:eslint - -# Target multiple packages -npx nx run-many --target=test:eslint --projects=@tanstack/history,@tanstack/router-core - -# Run affected tests only (compares to main branch) -npx nx affected --target=test:unit - -# Exclude certain patterns -npx nx run-many --target=test:unit --exclude="examples/**,e2e/**" - -# List all available projects -npx nx show projects -``` - -### Granular Vitest Testing - -For even more precise test targeting within packages: - -```bash -# Navigate to package directory first -cd packages/react-router - -# Run specific test files -npx vitest run tests/link.test.tsx -npx vitest run tests/ClientOnly.test.tsx tests/Scripts.test.tsx - -# Run tests by name pattern -npx vitest run tests/ClientOnly.test.tsx -t "should render fallback" -npx vitest run -t "navigation" # Run all tests with "navigation" in name - -# Exclude test patterns -npx vitest run --exclude="**/*link*" tests/ - -# List available tests -npx vitest list tests/link.test.tsx -npx vitest list # List all tests in package - -# Through nx (passes args to vitest) -npx nx run @tanstack/react-router:test:unit -- tests/ClientOnly.test.tsx -npx nx run @tanstack/react-router:test:unit -- tests/link.test.tsx tests/Scripts.test.tsx -``` - -### Example Development - -```bash -# Navigate to an example -cd examples/react/basic - -# Run the example -pnpm dev -``` - -## Development Workflow - -1. **Setup**: `pnpm install` and `pnpm exec playwright install` -2. **Build**: `pnpm build:all` or `pnpm dev` for watch mode -3. **Test**: Make changes and run relevant tests (use nx for targeted testing) -4. **Examples**: Navigate to examples and run `pnpm dev` to test changes - -### Nx-Powered Development - -This repository uses Nx for efficient task execution: - -- **Caching**: Nx caches task results - repeated commands are faster -- **Affected**: Only runs tasks for changed code (`nx affected`) -- **Targeting**: Run tasks for specific packages or combinations -- **Parallel Execution**: Multiple tasks run in parallel automatically -- **Dependency Management**: Nx handles build order and dependencies - -## Code Organization - -### Monorepo Structure - -This is a pnpm workspace with packages organized by functionality: - -- Core packages provide the fundamental router logic -- Framework packages provide React/Solid bindings -- Tool packages provide development utilities -- Start packages provide full-stack framework capabilities - -### Key Patterns - -- **Type Safety**: Extensive use of TypeScript for type-safe routing -- **Framework Agnostic**: Core logic separated from framework bindings -- **Plugin Architecture**: Universal bundler plugins using unplugin -- **File-based Routing**: Support for both code-based and file-based routing -- **Search Params**: First-class support for type-safe search parameters - -## Documentation Guidelines - -- **Internal Links**: Always write relative to `docs/` folder (e.g., `./guide/data-loading`) -- **Examples**: Each major feature should have corresponding examples -- **Type Safety**: Document TypeScript patterns and type inference -- **Framework Specific**: Separate docs for React and Solid when needed - -## Critical Quality Checks - -**During prompt-driven development, always run unit and type tests to ensure validity. If either of these fail, do not stop or proceed (unless you have repeatedly failed and need human intervention).** - -**You can run these (or the ones you are working on) after each big change:** - -```bash -pnpm test:eslint # Linting -pnpm test:types # TypeScript compilation -pnpm test:unit # Unit tests -pnpm test:build # Build verification -``` - -**For comprehensive testing:** - -```bash -pnpm test:ci # Full CI test suite -pnpm test:e2e # End-to-end tests -``` - -**For targeted testing (recommended for efficiency):** - -```bash -# Test only affected packages -npx nx affected --target=test:unit -npx nx affected --target=test:types -npx nx affected --target=test:eslint - -# Test specific packages you're working on -npx nx run @tanstack/react-router:test:unit -npx nx run @tanstack/router-core:test:types - -# Test specific files/functionality you're working on -cd packages/react-router -npx vitest run tests/link.test.tsx -t "preloading" -npx vitest run tests/useNavigate.test.tsx tests/useParams.test.tsx -``` - -**Pro Tips:** - -- Use `npx vitest list` to explore available tests before running -- Use `-t "pattern"` to focus on specific functionality during development -- Use `--exclude` patterns to skip unrelated tests -- Combine nx package targeting with vitest file targeting for maximum precision - -## Package Dependencies - -The monorepo uses workspace dependencies extensively: - -- Core packages are dependencies of framework packages -- Framework packages are dependencies of start packages -- All packages use workspace protocol (`workspace:*`) - -## Environment Setup - -- **Node.js**: Required for development -- **pnpm**: Package manager (required for workspace features) -- **Playwright**: Required for e2e tests (`pnpm exec playwright install`) - -## Common Tasks - -### Adding New Routes - -- Use file-based routing in `src/routes/` directories -- Or use code-based routing with route definitions -- Run route generation with CLI tools - -### Testing Changes - -- Build packages: `pnpm build` or `pnpm dev` -- Run example apps to test functionality -- Use devtools for debugging router state - -**Available Test Targets per Package:** - -- `test:unit` - Unit tests with Vitest -- `test:types` - TypeScript compilation across multiple TS versions -- `test:eslint` - Linting with ESLint -- `test:build` - Build verification (publint + attw) -- `test:perf` - Performance benchmarks -- `build` - Package building - -**Granular Test Targeting Strategies:** - -1. **Package Level**: Use nx to target specific packages -2. **File Level**: Use vitest directly to target specific test files -3. **Test Level**: Use vitest `-t` flag to target specific test names -4. **Pattern Level**: Use vitest exclude patterns to skip certain tests - -Example workflow: - -```bash -# 1. Test specific package -npx nx run @tanstack/react-router:test:unit - -# 2. Test specific files within package -cd packages/react-router && npx vitest run tests/link.test.tsx - -# 3. Test specific functionality -npx vitest run tests/link.test.tsx -t "preloading" -``` - -### Documentation Updates - -- Update relevant docs in `docs/` directory -- Ensure examples reflect documentation -- Test documentation links and references - -## Framework-Specific Notes - -### React - -- Uses React Router components and hooks -- Supports React Server Components (RSC) -- Examples include React Query integration - -### Solid - -- Uses Solid Router components and primitives -- Supports Solid Start for full-stack applications -- Examples include Solid Query integration - -## References - -- Main Documentation: https://tanstack.com/router -- GitHub Repository: https://github.com/TanStack/router -- Discord Community: https://discord.com/invite/WrRKjPJ diff --git a/docs/start/framework/react/build-from-scratch.md b/docs/start/framework/react/build-from-scratch.md index 74989fb9ea8..50639f050c6 100644 --- a/docs/start/framework/react/build-from-scratch.md +++ b/docs/start/framework/react/build-from-scratch.md @@ -58,13 +58,15 @@ npm i @tanstack/react-start @tanstack/react-router vite You'll also need React: ```shell -npm i react react-dom +npm i react react-dom @vitejs/plugin-react ``` +Alternatively, you can also use `@vitejs/plugin-react-oxc` or `@vitejs/plugin-react-swc`. + and some TypeScript: ```shell -npm i -D typescript @types/react @types/react-dom vite-tsconfig-paths @vitejs/plugin-react +npm i -D typescript @types/react @types/react-dom vite-tsconfig-paths ``` ## Update Configuration Files @@ -84,9 +86,6 @@ We'll then update our `package.json` to use Vite's CLI and set `"type": "module" Then configure TanStack Start's Vite plugin in `vite.config.ts`: -> [!NOTE] -> TanStack Start will stop auto-configuring React/Solid Vite plugins. You’ll get full control - choose `@vitejs/plugin-react`, `@vitejs/plugin-react-oxc`, etc. Set `customViteReactPlugin: true` to opt in to this feature right now! - ```ts // vite.config.ts import { defineConfig } from 'vite' @@ -100,7 +99,8 @@ export default defineConfig({ }, plugins: [ tsConfigPaths(), - tanstackStart({ customViteReactPlugin: true }), + tanstackStart(), + // react's vite plugin must come after start's vite plugin viteReact(), ], }) @@ -137,12 +137,12 @@ from the default [preloading functionality](/router/latest/docs/framework/react/ > You won't have a `routeTree.gen.ts` file yet. This file will be generated when you run TanStack Start for the first time. ```tsx -// src/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +// src/start.tsx +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -152,7 +152,7 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } ``` @@ -238,7 +238,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await readCount() await fs.promises.writeFile(filePath, `${count + data}`) diff --git a/docs/start/framework/react/hosting.md b/docs/start/framework/react/hosting.md index 1ccca16ef13..6d947dcfedd 100644 --- a/docs/start/framework/react/hosting.md +++ b/docs/start/framework/react/hosting.md @@ -45,19 +45,29 @@ Once you've chosen a deployment target, you can follow the deployment guidelines ### Netlify -Set the `target` value to `'netlify'` in the TanStack Start Vite plugin in `vite.config.ts` file. +Install and add the [`@netlify/vite-plugin-tanstack-start`](https://www.npmjs.com/package/@netlify/vite-plugin-tanstack-start) plugin, which configures your build for Netlify deployment and provides full Netlify production platform emulation in local dev. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import netlify from '@netlify/vite-plugin-tanstack-start' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tanstackStart({ target: 'netlify' })], + plugins: [tanstackStart(), netlify(), viteReact()], }) ``` -Deploy your application to Netlify using their one-click deployment process, and you're ready to go! +Add a `netlify.toml` file to your project root: + +```toml +[build] + command = "vite build" + publish = "dist/client" +``` + +Deploy your application using their one-click deployment process, and you're ready to go! ### Vercel @@ -65,11 +75,12 @@ Set the `target` value to `'vercel'` in the TanStack Start Vite plugin in `vite. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tanstackStart({ target: 'vercel' })], + plugins: [tanstackStart({ target: 'vercel' }), viteReact()], }) ``` @@ -85,11 +96,12 @@ Set the `target` value to `cloudflare-module` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tanstackStart({ target: 'cloudflare-module' })], + plugins: [tanstackStart({ target: 'cloudflare-module' }), viteReact()], }) ``` @@ -150,11 +162,12 @@ Set the `target` value to `node-server` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tanstackStart({ target: 'node-server' })], + plugins: [tanstackStart({ target: 'node-server' }), viteReact()], }) ``` @@ -185,11 +198,12 @@ Set the `target` value to `bun` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tanstackStart({ target: 'bun' })], + plugins: [tanstackStart({ target: 'bun' }), viteReact()], }) ``` diff --git a/docs/start/framework/react/how-to/use-environment-variables.md b/docs/start/framework/react/how-to/use-environment-variables.md index ee185fbb560..bd15f007b5b 100644 --- a/docs/start/framework/react/how-to/use-environment-variables.md +++ b/docs/start/framework/react/how-to/use-environment-variables.md @@ -47,7 +47,7 @@ const connectToDatabase = createServerFn().handler(async () => { // Authentication (server-only) const authenticateUser = createServerFn() - .validator(z.object({ token: z.string() })) + .inputValidator(z.object({ token: z.string() })) .handler(async ({ data }) => { const jwtSecret = process.env.JWT_SECRET // Server-only return jwt.verify(data.token, jwtSecret) @@ -185,7 +185,7 @@ import { createServerFn } from '@tanstack/react-start' // Server-side API calls (can use secret keys) const fetchUserData = createServerFn() - .validator(z.object({ userId: z.string() })) + .inputValidator(z.object({ userId: z.string() })) .handler(async ({ data }) => { const response = await fetch( `${process.env.EXTERNAL_API_URL}/users/${data.userId}`, diff --git a/docs/start/framework/react/how-to/write-isomorphic-client-server-code.md b/docs/start/framework/react/how-to/write-isomorphic-client-server-code.md index 9835639c341..341858b29cb 100644 --- a/docs/start/framework/react/how-to/write-isomorphic-client-server-code.md +++ b/docs/start/framework/react/how-to/write-isomorphic-client-server-code.md @@ -35,39 +35,39 @@ export const Route = createFileRoute('/products')({ ### Server-Only APIs -| API | Use Case | Client Behavior | -| ------------------ | ------------------------- | ------------------------- | -| `createServerFn()` | RPC calls, data mutations | Network request to server | -| `serverOnly(fn)` | Utility functions | Throws error | +| API | Use Case | Client Behavior | +| ------------------------ | ------------------------- | ------------------------- | +| `createServerFn()` | RPC calls, data mutations | Network request to server | +| `createServerOnlyFn(fn)` | Utility functions | Throws error | ```tsx -import { createServerFn, serverOnly } from '@tanstack/react-start' +import { createServerFn, createServerOnlyFn } from '@tanstack/react-start' // RPC: Server execution, callable from client const updateUser = createServerFn({ method: 'POST' }) - .validator((data: UserData) => data) + .inputValidator((data: UserData) => data) .handler(async ({ data }) => { // Only runs on server, but client can call it return await db.users.update(data) }) // Utility: Server-only, client crashes if called -const getEnvVar = serverOnly(() => process.env.DATABASE_URL) +const getEnvVar = createServerOnlyFn(() => process.env.DATABASE_URL) ``` ### Client-Only APIs -| API | Use Case | Server Behavior | -| ---------------- | ------------------------------- | ---------------- | -| `clientOnly(fn)` | Browser utilities | Throws error | -| `` | Components needing browser APIs | Renders fallback | +| API | Use Case | Server Behavior | +| ------------------------ | ------------------------------- | ---------------- | +| `createClientOnlyFn(fn)` | Browser utilities | Throws error | +| `` | Components needing browser APIs | Renders fallback | ```tsx -import { clientOnly } from '@tanstack/react-start' +import { createClientOnlyFn } from '@tanstack/react-start' import { ClientOnly } from '@tanstack/react-router' // Utility: Client-only, server crashes if called -const saveToStorage = clientOnly((key: string, value: any) => { +const saveToStorage = createClientOnlyFn((key: string, value: any) => { localStorage.setItem(key, JSON.stringify(value)) }) @@ -96,7 +96,7 @@ const getDeviceInfo = createIsomorphicFn() ## Key Distinctions -### `createServerFn()` vs `serverOnly()` +### `createServerFn()` vs `createServerOnlyFn()` ```tsx // createServerFn: RPC pattern - server execution, client callable @@ -105,8 +105,8 @@ const fetchUser = createServerFn().handler(async () => await db.users.find()) // Usage from client component: const user = await fetchUser() // ✅ Network request -// serverOnly: Crashes if called from client -const getSecret = serverOnly(() => process.env.SECRET) +// createServerOnlyFn: Crashes if called from client +const getSecret = createServerOnlyFn(() => process.env.SECRET) // Usage from client: const secret = getSecret() // ❌ Throws error @@ -182,7 +182,7 @@ const storage = createIsomorphicFn() const apiKey = process.env.SECRET_KEY // ✅ Server-only access -const apiKey = serverOnly(() => process.env.SECRET_KEY) +const apiKey = createServerOnlyFn(() => process.env.SECRET_KEY) ``` ### Incorrect Loader Assumptions @@ -233,7 +233,7 @@ function CurrentTime() { ## Production Checklist - [ ] **Bundle Analysis**: Verify server-only code isn't in client bundle -- [ ] **Environment Variables**: Ensure secrets use `serverOnly()` or `createServerFn()` +- [ ] **Environment Variables**: Ensure secrets use `createServerOnlyFn()` or `createServerFn()` - [ ] **Loader Logic**: Remember loaders are isomorphic, not server-only - [ ] **ClientOnly Fallbacks**: Provide appropriate fallbacks to prevent layout shift - [ ] **Error Boundaries**: Handle server/client execution errors gracefully diff --git a/docs/start/framework/react/learn-the-basics.md b/docs/start/framework/react/learn-the-basics.md index d9f9939be6c..03bec0690d0 100644 --- a/docs/start/framework/react/learn-the-basics.md +++ b/docs/start/framework/react/learn-the-basics.md @@ -18,12 +18,12 @@ This is the file that will dictate the behavior of TanStack Router used within S from the default [preloading functionality](/router/latest/docs/framework/react/guide/preloading) to [caching staleness](/router/latest/docs/framework/react/guide/data-loading). ```tsx -// src/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +// src/start.tsx +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -33,7 +33,7 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } ``` @@ -49,7 +49,7 @@ The `routeTree.gen.ts` file is generated when you run TanStack Start (via `npm r > [!NOTE] > The server entry point is **optional** out of the box. If not provided, TanStack Start will automatically handle the server entry point for you using the below as a default. -This is done via the `src/server.ts` file: +This is done via the `src/server.ts` file. ```tsx // src/server.ts @@ -59,9 +59,23 @@ import { } from '@tanstack/react-start/server' import { createRouter } from './router' -export default createStartHandler({ +const fetch = createStartHandler({ createRouter, })(defaultStreamHandler) + +export default { + fetch, +} +``` + +The entry point must conform to the following interface: + +```tsx +export default { + fetch(req: Request): Promise { + // ... + }, +} ``` Whether we are statically generating our app or serving it dynamically, the `server.ts` file is the entry point for doing all SSR-related work. @@ -185,7 +199,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await readCount() await fs.promises.writeFile(filePath, `${count + data}`) @@ -255,7 +269,7 @@ import { z } from 'zod' const getUserById = createServerFn({ method: 'GET' }) // Always validate data sent to the function, here we use Zod - .validator(z.string()) + .inputValidator(z.string()) // The handler function is where you perform the server-side logic .handler(async ({ data }) => { return db.query.users.findFirst({ where: eq(users.id, data) }) @@ -288,7 +302,7 @@ const UserSchema = z.object({ export type User = z.infer export const updateUser = createServerFn({ method: 'POST' }) - .validator(UserSchema) + .inputValidator(UserSchema) .handler(({ data }) => dbUpdateUser(data)) // Somewhere else in your application diff --git a/docs/start/framework/react/middleware.md b/docs/start/framework/react/middleware.md index ecae7a9a87c..83db4920f70 100644 --- a/docs/start/framework/react/middleware.md +++ b/docs/start/framework/react/middleware.md @@ -19,7 +19,7 @@ Middleware allows you to customize the behavior of server functions created with ## Defining Middleware for Server Functions -Middleware is defined using the `createMiddleware` function. This function returns a `Middleware` object that can be used to continue customizing the middleware with methods like `middleware`, `validator`, `server`, and `client`. +Middleware is defined using the `createMiddleware` function. This function returns a `Middleware` object that can be used to continue customizing the middleware with methods like `middleware`, `inputValidator`, `server`, and `client`. ```tsx import { createMiddleware } from '@tanstack/react-start' @@ -54,7 +54,7 @@ const fn = createServerFn() Several methods are available to customize the middleware. If you are (hopefully) using TypeScript, the order of these methods is enforced by the type system to ensure maximum inference and type safety. - `middleware`: Add a middleware to the chain. -- `validator`: Modify the data object before it is passed to this middleware and any nested middleware. +- `inputValidator`: Modify the data object before it is passed to this middleware and any nested middleware. - `server`: Define server-side logic that the middleware will execute before any nested middleware and ultimately a server function, and also provide the result to the next middleware. - `client`: Define client-side logic that the middleware will execute before any nested middleware and ultimately the client-side RPC function (or the server-side function), and also provide the result to the next middleware. @@ -73,9 +73,9 @@ const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([ Type-safe context and payload validation are also inherited from parent middlewares! -## The `validator` method +## The `inputValidator` method -The `validator` method is used to modify the data object before it is passed to this middleware, nested middleware, and ultimately the server function. This method should receive a function that takes the data object and returns a validated (and optionally modified) data object. It's common to use a validation library like `zod` to do this. Here is an example: +The `inputValidator` method is used to modify the data object before it is passed to this middleware, nested middleware, and ultimately the server function. This method should receive a function that takes the data object and returns a validated (and optionally modified) data object. It's common to use a validation library like `zod` to do this. Here is an example: ```tsx import { createMiddleware } from '@tanstack/react-start' @@ -87,7 +87,7 @@ const mySchema = z.object({ }) const workspaceMiddleware = createMiddleware({ type: 'function' }) - .validator(zodValidator(mySchema)) + .inputValidator(zodValidator(mySchema)) .server(({ next, data }) => { console.log('Workspace ID:', data.workspaceId) return next() @@ -148,21 +148,13 @@ const loggingMiddleware = createMiddleware({ type: 'function' }) Despite server functions being mostly server-side bound operations, there is still plenty of client-side logic surrounding the outgoing RPC request from the client. This means that we can also define client-side logic in middleware that will execute on the client side around any nested middleware and ultimately the RPC function and its response to the client. -## Client-side Payload Validation - -By default, middleware validation is only performed on the server to keep the client bundle size small. However, you may also choose to validate data on the client side by passing the `validateClient: true` option to the `createMiddleware` function. This will cause the data to be validated on the client side before being sent to the server, potentially saving a round trip. - -> Why can't I pass a different validation schema for the client? -> -> The client-side validation schema is derived from the server-side schema. This is because the client-side validation schema is used to validate the data before it is sent to the server. If the client-side schema were different from the server-side schema, the server would receive data that it did not expect, which could lead to unexpected behavior. - ```tsx import { createMiddleware } from '@tanstack/react-start' import { zodValidator } from '@tanstack/zod-adapter' import { z } from 'zod' -const workspaceMiddleware = createMiddleware({ validateClient: true }) - .validator(zodValidator(mySchema)) +const workspaceMiddleware = createMiddleware() + .inputValidator(zodValidator(mySchema)) .server(({ next, data }) => { console.log('Workspace ID:', data.workspaceId) return next() @@ -425,4 +417,4 @@ const fn = createServerFn() Middleware functionality is tree-shaken based on the environment for each bundle produced. - On the server, nothing is tree-shaken, so all code used in middleware will be included in the server bundle. -- On the client, all server-specific code is removed from the client bundle. This means any code used in the `server` method is always removed from the client bundle. If `validateClient` is set to `true`, the client-side validation code will be included in the client bundle, otherwise `data` validation code will also be removed. +- On the client, all server-specific code is removed from the client bundle. This means any code used in the `server` method is always removed from the client bundle. `data` validation code will also be removed. diff --git a/docs/start/framework/react/migrate-from-next-js.md b/docs/start/framework/react/migrate-from-next-js.md index ea080417f76..5420b97c05a 100644 --- a/docs/start/framework/react/migrate-from-next-js.md +++ b/docs/start/framework/react/migrate-from-next-js.md @@ -85,10 +85,11 @@ Now that you've installed the necessary dependencies, update your project config ```ts // vite.config.ts -import tailwindcss from '@tailwindcss/vite' -import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' import tsconfigPaths from 'vite-tsconfig-paths' +import tailwindcss from '@tailwindcss/vite' export default defineConfig({ server: { @@ -99,11 +100,12 @@ export default defineConfig({ // Enables Vite to resolve imports using path aliases. tsconfigPaths(), tanstackStart({ - tsr: { + router: { // Specifies the directory TanStack Router uses for your routes. routesDirectory: 'src/app', // Defaults to "src/routes" }, }), + viteReact(), ], }) ``` @@ -209,16 +211,16 @@ Instead of `page.tsx`, create an `index.tsx` file for the `/` route. ### 6. Are we migrated yet? -Before you can run the development server, you need to create a router file that will define the behavior of TanStack Router within TanStack Start. +Before you can run the development server, you need to create a file that will define the behavior of TanStack Router within TanStack Start. -- `src/router.tsx` +- `src/start.tsx` ```tsx -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -228,7 +230,7 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } ``` @@ -326,10 +328,14 @@ Learn more about the [Server Functions](../server-functions.md). ```ts - export async function GET() { // [!code --] -+ export const ServerRoute = createServerFileRoute().methods({ // [!code ++] -+ GET: async () => { // [!code ++] - return Response.json("Hello, World!") - } ++ export const Route = createFileRoute('/api/hello')({ // [!code ++] ++ server: { // [!code ++] ++ handlers: { // [!code ++] ++ GET: async () => { // [!code ++] ++ return Response.json("Hello, World!") ++ } // [!code ++] ++ } // [!code ++] ++ } // [!code ++] + }) // [!code ++] ``` diff --git a/docs/start/framework/react/reading-writing-file.md b/docs/start/framework/react/reading-writing-file.md index 3748dcca857..784ba21aa61 100644 --- a/docs/start/framework/react/reading-writing-file.md +++ b/docs/start/framework/react/reading-writing-file.md @@ -264,7 +264,7 @@ import { v4 as uuidv4 } from 'uuid' // Add this import import type { Joke, JokesData } from '../types' export const addJoke = createServerFn({ method: 'POST' }) - .validator((data: { question: string; answer: string }) => { + .inputValidator((data: { question: string; answer: string }) => { // Validate input data if (!data.question || !data.question.trim()) { throw new Error('Joke question is required') @@ -307,7 +307,7 @@ export const addJoke = createServerFn({ method: 'POST' }) In this code: - We are using `createServerFn` to create server functions that run on the server but can be called from the client. This server function is used to write data to the file. -- We are going to first use `validator` to validate the input data. This is a good practice to ensure that the data we are receiving is in the correct format. +- We are going to first use `inputValidator` to validate the input data. This is a good practice to ensure that the data we are receiving is in the correct format. - We are going to perform the actual write operation in the `handler` function. - `getJokes` reads the jokes from our JSON file. - `addJoke` validates the input data and adds a new joke to our file. diff --git a/docs/start/framework/react/server-functions.md b/docs/start/framework/react/server-functions.md index 1c129d12dc9..964f409c5be 100644 --- a/docs/start/framework/react/server-functions.md +++ b/docs/start/framework/react/server-functions.md @@ -61,7 +61,6 @@ import { createServerFn } from '@tanstack/react-start' export const getData = createServerFn({ method: 'GET', // HTTP method to use - response: 'data', // Response handling mode }).handler(async () => { // Function implementation }) @@ -79,18 +78,6 @@ method?: 'GET' | 'POST' By default, server functions use `GET` if not specified. -**`response`** - -Controls how responses are processed and returned: - -```tsx -response?: 'data' | 'full' | 'raw' -``` - -- `'data'` (default): Automatically parses JSON responses and returns just the data -- `'full'`: Returns a response object with result data, error information, and context -- `'raw'`: Returns the raw Response object directly, enabling streaming responses and custom headers - ## Where can I call server functions? - From server-side code @@ -120,7 +107,7 @@ import { createServerFn } from '@tanstack/react-start' export const greet = createServerFn({ method: 'GET', }) - .validator((data: string) => data) + .inputValidator((data: string) => data) .handler(async (ctx) => { return `Hello, ${ctx.data}!` }) @@ -134,7 +121,7 @@ greet({ Server functions can be configured to validate their input data at runtime, while adding type safety. This is useful for ensuring the input is of the correct type before executing the server function, and providing more friendly error messages. -This is done with the `validator` method. It will accept whatever input is passed to the server function. The value (and type) you return from this function will become the input passed to the actual server function handler. +This is done with the `inputValidator` method. It will accept whatever input is passed to the server function. The value (and type) you return from this function will become the input passed to the actual server function handler. Validators also integrate seamlessly with external validators, if you want to use something like Zod. @@ -150,7 +137,7 @@ type Person = { } export const greet = createServerFn({ method: 'GET' }) - .validator((person: unknown): Person => { + .inputValidator((person: unknown): Person => { if (typeof person !== 'object' || person === null) { throw new Error('Person must be an object') } @@ -180,7 +167,7 @@ const Person = z.object({ }) export const greet = createServerFn({ method: 'GET' }) - .validator((person: unknown) => { + .inputValidator((person: unknown) => { return Person.parse(person) }) .handler(async (ctx) => { @@ -196,7 +183,7 @@ greet({ ## Type Safety -Since server-functions cross the network boundary, it's important to ensure the data being passed to them is not only the right type, but also validated at runtime. This is especially important when dealing with user input, as it can be unpredictable. To ensure developers validate their I/O data, types are reliant on validation. The return type of the `validator` function will be the input to the server function's handler. +Since server-functions cross the network boundary, it's important to ensure the data being passed to them is not only the right type, but also validated at runtime. This is especially important when dealing with user input, as it can be unpredictable. To ensure developers validate their I/O data, types are reliant on validation. The return type of the `inputValidator` function will be the input to the server function's handler. ```tsx import { createServerFn } from '@tanstack/react-start' @@ -206,7 +193,7 @@ type Person = { } export const greet = createServerFn({ method: 'GET' }) - .validator((person: unknown): Person => { + .inputValidator((person: unknown): Person => { if (typeof person !== 'object' || person === null) { throw new Error('Person must be an object') } @@ -233,7 +220,7 @@ function test() { ## Inference -Server functions infer their input, and output types based on the input to the `validator`, and return value of `handler` functions, respectively. In fact, the `validator` you define can even have its own separate input/output types, which can be useful if your validator performs transformations on the input data. +Server functions infer their input, and output types based on the input to the `inputValidator`, and return value of `handler` functions, respectively. In fact, the `inputValidator` you define can even have its own separate input/output types, which can be useful if your input validator performs transformations on the input data. To illustrate this, let's take a look at an example using the `zod` validation library: @@ -246,7 +233,7 @@ const transactionSchema = z.object({ }) const createTransaction = createServerFn() - .validator(transactionSchema) + .inputValidator(transactionSchema) .handler(({ data }) => { return data.amount // Returns a number }) @@ -260,7 +247,7 @@ createTransaction({ ## Non-Validated Inference -While we highly recommend using a validation library to validate your network I/O data, you may, for whatever reason _not_ want to validate your data, but still have type safety. To do this, provide type information to the server function using an identity function as the `validator`, that types the input, and or output to the correct types: +While we highly recommend using a validation library to validate your network I/O data, you may, for whatever reason _not_ want to validate your data, but still have type safety. To do this, provide type information to the server function using an identity function as the `inputValidator`, that types the input, and or output to the correct types: ```tsx import { createServerFn } from '@tanstack/react-start' @@ -270,7 +257,7 @@ type Person = { } export const greet = createServerFn({ method: 'GET' }) - .validator((d: Person) => d) + .inputValidator((d: Person) => d) .handler(async (ctx) => { return `Hello, ${ctx.data.name}!` }) @@ -295,7 +282,7 @@ type Person = { } export const greet = createServerFn({ method: 'GET' }) - .validator((data: Person) => data) + .inputValidator((data: Person) => data) .handler(async ({ data }) => { return `Hello, ${data.name}! You are ${data.age} years old.` }) @@ -316,7 +303,7 @@ Server functions can accept `FormData` objects as parameters import { createServerFn } from '@tanstack/react-start' export const greetUser = createServerFn({ method: 'POST' }) - .validator((data) => { + .inputValidator((data) => { if (!(data instanceof FormData)) { throw new Error('Invalid form data') } @@ -357,7 +344,7 @@ function Test() { ## Server Function Context -In addition to the single parameter that server functions accept, you can also access server request context from within any server function using utilities from `@tanstack/react-start/server`. Under the hood, we use [Unjs](https://unjs.io/)'s `h3` package to perform cross-platform HTTP requests. +In addition to the single parameter that server functions accept, you can also access server request context from within any server function using utilities from `@tanstack/react-start/server`. There are many context functions available to you for things like: @@ -366,23 +353,20 @@ There are many context functions available to you for things like: - Accessing/setting sessions/cookies - Setting response status codes and status messages - Dealing with multi-part form data -- Reading/Setting custom server context properties - -For a full list of available context functions, see all of the available [h3 Methods](https://h3.unjs.io/utils/request) or inspect the [@tanstack/start-server-core Source Code](https://github.com/TanStack/router/tree/main/packages/start-server-core/src). For starters, here are a few examples: ## Accessing the Request Context -Let's use the `getWebRequest` function to access the request itself from within a server function: +Let's use the `getRequest` function to access the request itself from within a server function: ```tsx import { createServerFn } from '@tanstack/react-start' -import { getWebRequest } from '@tanstack/react-start/server' +import { getRequest } from '@tanstack/react-start/server' export const getServerTime = createServerFn({ method: 'GET' }).handler( async () => { - const request = getWebRequest() + const request = getRequest() console.log(request.method) // GET @@ -393,15 +377,15 @@ export const getServerTime = createServerFn({ method: 'GET' }).handler( ## Accessing Headers -Use the `getHeaders` function to access all headers from within a server function: +Use the `getRequestHeaders` function to access all headers from within a server function: ```tsx import { createServerFn } from '@tanstack/react-start' -import { getHeaders } from '@tanstack/react-start/server' +import { getRequestHeaders } from '@tanstack/react-start/server' export const getServerTime = createServerFn({ method: 'GET' }).handler( async () => { - console.log(getHeaders()) + console.log(getRequestHeaders()) // { // "accept": "*/*", // "accept-encoding": "gzip, deflate, br", @@ -414,15 +398,15 @@ export const getServerTime = createServerFn({ method: 'GET' }).handler( ) ``` -You can also access individual headers using the `getHeader` function: +You can also access individual headers using the `getRequestHeader` function: ```tsx import { createServerFn } from '@tanstack/react-start' -import { getHeader } from '@tanstack/react-start/server' +import { getRequestHeader } from '@tanstack/react-start/server' export const getServerTime = createServerFn({ method: 'GET' }).handler( async () => { - console.log(getHeader('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 + console.log(getRequestHeader('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 }, ) ``` @@ -463,15 +447,15 @@ By default, server functions assume that any non-Response object returned is eit ## Responding with Custom Headers -To respond with custom headers, you can use the `setHeader` function: +To respond with custom headers, you can use the `setResponseHeader` function: ```tsx import { createServerFn } from '@tanstack/react-start' -import { setHeader } from '@tanstack/react-start/server' +import { setResponseHeader } from '@tanstack/react-start/server' export const getServerTime = createServerFn({ method: 'GET' }).handler( async () => { - setHeader('X-Custom-Header', 'value') + setResponseHeader('X-Custom-Header', 'value') return new Date().toISOString() }, ) @@ -495,28 +479,26 @@ export const getServerTime = createServerFn({ method: 'GET' }).handler( ## Returning Raw Response objects -To return a raw Response object, return a Response object from the server function and set `response: 'raw'`: +To return a raw Response object, return a Response object from the server function: ```tsx import { createServerFn } from '@tanstack/react-start' export const getServerTime = createServerFn({ method: 'GET', - response: 'raw', }).handler(async () => { // Read a file from s3 return fetch('https://example.com/time.txt') }) ``` -The response: 'raw' option also allows for streaming responses among other things: +This also allows for streaming responses among other things: ```tsx import { createServerFn } from '@tanstack/react-start' export const streamEvents = createServerFn({ method: 'GET', - response: 'raw', }).handler(async ({ signal }) => { // Create a ReadableStream to send chunks of data const stream = new ReadableStream({ @@ -566,7 +548,7 @@ export const streamEvents = createServerFn({ }) ``` -The `response: 'raw'` option is particularly useful for: +Returning raw responses is particularly useful for: - Streaming APIs where data is sent incrementally - Server-sent events @@ -745,8 +727,6 @@ export const auth = createServerFn({ method: 'GET' }).handler(async () => { }) ``` -> ⚠️ Do not use `@tanstack/react-start/server`'s `sendRedirect` function to send soft redirects from within server functions. This will send the redirect using the `Location` header and will force a full page hard navigation on the client. - ## Redirect Headers You can also set custom headers on a redirect by passing a `headers` option: @@ -848,7 +828,7 @@ To do this, we can utilize the `url` property of the server function: ```ts const yourFn = createServerFn({ method: 'POST' }) - .validator((formData) => { + .inputValidator((formData) => { if (!(formData instanceof FormData)) { throw new Error('Invalid form data') } @@ -891,7 +871,7 @@ server function: ```tsx const yourFn = createServerFn({ method: 'POST' }) - .validator((formData) => { + .inputValidator((formData) => { if (!(formData instanceof FormData)) { throw new Error('Invalid form data') } @@ -969,7 +949,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((formData) => { + .inputValidator((formData) => { if (!(formData instanceof FormData)) { throw new Error('Invalid form data') } diff --git a/docs/start/framework/react/server-routes.md b/docs/start/framework/react/server-routes.md index 0f2a92199a0..66651d9b8bb 100644 --- a/docs/start/framework/react/server-routes.md +++ b/docs/start/framework/react/server-routes.md @@ -12,9 +12,13 @@ Here's what a simple server route looks like: ```ts // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return new Response('Hello, World!') +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return new Response('Hello, World!') + }, + }, }, }) ``` @@ -26,14 +30,15 @@ Because server routes can be defined in the same directory as your app routes, y ```tsx // routes/hello.tsx -export const ServerRoute = createServerFileRoute().methods({ - POST: async ({ request }) => { - const body = await request.json() - return new Response(JSON.stringify({ message: `Hello, ${body.name}!` })) - }, -}) - export const Route = createFileRoute('/hello')({ + server: { + handlers: { + POST: async ({ request }) => { + const body = await request.json() + return new Response(JSON.stringify({ message: `Hello, ${body.name}!` })) + }, + }, + }, component: HelloComponent, }) @@ -64,7 +69,7 @@ function HelloComponent() { ## File Route Conventions -Server routes in TanStack Start, follow the same file-based routing conventions as TanStack Router. This means that each file in your `routes` directory with a `ServerRoute` export will be treated as an API route. Here are a few examples: +Server routes in TanStack Start, follow the same file-based routing conventions as TanStack Router. This means that each file in your `routes` directory with a `Route` export will be treated as an API route. Here are a few examples: - `/routes/users.ts` will create an API route at `/users` - `/routes/users.index.ts` will **also** create an API route at `/users` (but will error if duplicate methods are defined) @@ -99,50 +104,28 @@ In the examples above, you may have noticed that the file naming conventions are ## Handling Server Route Requests -Server route requests are handled by Start's `createStartHandler` in your `server.ts` entry file. - -```tsx -// server.ts -import { - createStartHandler, - defaultStreamHandler, -} from '@tanstack/react-start/server' -import { createRouter } from './router' - -export default createStartHandler({ - createRouter, -})(defaultStreamHandler) -``` +Server route requests are handled by Start automatically by default or by Start's `createStartHandler` in your custom `src/server.ts` entry point file. The start handler is responsible for matching an incoming request to a server route and executing the appropriate middleware and handler. -Remember, if you need to customize the server handler, you can do so by creating a custom handler and then passing the event to the start handler: - -```tsx -// server.ts -import { createStartHandler } from '@tanstack/react-start/server' - -export default defineHandler((event) => { - const startHandler = createStartHandler({ - createRouter, - })(defaultStreamHandler) - - return startHandler(event) -}) -``` +If you need to customize the server handler, you can do so by creating a custom handler and then passing the event to the start handler. See [The Server Entry Point](../learn-the-basics#the-server-entry-point-optional). ## Defining a Server Route -Server routes are created by exporting a `ServerRoute` from a route file. The `ServerRoute` export should be created by calling the `createServerFileRoute` function. The resulting builder object can then be used to: +Server routes are created by exporting a `Route` from a route file. The `Route` export should be created by calling the `createFileRoute` function. The resulting builder object can then be used to: - Add route-level middleware - Define handlers for each HTTP method ```ts // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return new Response('Hello, World! from ' + request.url) +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return new Response('Hello, World! from ' + request.url) + }, + }, }, }) ``` @@ -160,23 +143,41 @@ For simple use cases, you can provide a handler function directly to the method. ```ts // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return new Response('Hello, World! from ' + request.url) +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return new Response('Hello, World! from ' + request.url) + }, + }, }, }) ``` ### Providing a handler function via the method builder object -For more complex use cases, you can provide a handler function via the method builder object. This allows you to add middleware to the method. +For more complex use cases, you can provide a function to `server.handlers`. That function receives a `createHandlers` function which you can use to specify each handler method along with its corresponding middleware. This allows you to add specific middleware on a per-method basis. ```tsx // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods((api) => ({ - GET: api.middleware([loggerMiddleware]).handler(async ({ request }) => { - return new Response('Hello, World! from ' + request.url) - }), +export const Route = createFileRoute('/hello')((api) => ({ + server: { + handlers: ({ createHandlers }) => + createHandlers({ + GET: { + middleware: [loggerMiddleware], + handler: async ({ request }) => { + return new Response('Hello, World! from ' + request.url) + }, + }, + POST: { + middleware: [authMiddleware], + handler: async ({ request }) => { + return new Response('Hello, World! from ' + request.url) + }, + }, + }), + }, })) ``` @@ -196,10 +197,14 @@ Server routes support dynamic path parameters in the same way as TanStack Router ```ts // routes/users/$id.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ params }) => { - const { id } = params - return new Response(`User ID: ${id}`) +export const Route = createFileRoute('/users/$id')({ + server: { + handlers: { + GET: async ({ params }) => { + const { id } = params + return new Response(`User ID: ${id}`) + }, + }, }, }) @@ -211,10 +216,14 @@ You can also have multiple dynamic path parameters in a single route. For exampl ```ts // routes/users/$id/posts/$postId.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ params }) => { - const { id, postId } = params - return new Response(`User ID: ${id}, Post ID: ${postId}`) +export const Route = createFileRoute('/users/$id/posts/$postId')({ + server: { + handlers: { + GET: async ({ params }) => { + const { id, postId } = params + return new Response(`User ID: ${id}, Post ID: ${postId}`) + }, + }, }, }) @@ -228,10 +237,14 @@ Server routes also support wildcard parameters at the end of the path, which are ```ts // routes/file/$.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ params }) => { - const { _splat } = params - return new Response(`File: ${_splat}`) +export const Route = createFileRoute('/file/')({ + server: { + handlers: { + GET: async ({ params }) => { + const { _splat } = params + return new Response(`File: ${_splat}`) + }, + }, }, }) @@ -245,10 +258,14 @@ To handle POST requests,you can add a `POST` handler to the route object. The ha ```ts // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods({ - POST: async ({ request }) => { - const body = await request.json() - return new Response(`Hello, ${body.name}!`) +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + POST: async ({ request }) => { + const body = await request.json() + return new Response(`Hello, ${body.name}!`) + }, + }, }, }) @@ -268,13 +285,17 @@ When returning JSON using a Response object, this is a common pattern: ```ts // routes/hello.ts -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return new Response(JSON.stringify({ message: 'Hello, World!' }), { - headers: { - 'Content-Type': 'application/json', +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return new Response(JSON.stringify({ message: 'Hello, World!' }), { + headers: { + 'Content-Type': 'application/json', + }, + }) }, - }) + }, }, }) @@ -290,9 +311,13 @@ Or you can use the `json` helper function to automatically set the `Content-Type // routes/hello.ts import { json } from '@tanstack/react-start' -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return json({ message: 'Hello, World!' }) +export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return json({ message: 'Hello, World!' }) + }, + }, }, }) @@ -310,15 +335,19 @@ You can set the status code of the response by either: // routes/hello.ts import { json } from '@tanstack/react-start' - export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request, params }) => { - const user = await findUser(params.id) - if (!user) { - return new Response('User not found', { - status: 404, - }) - } - return json(user) + export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request, params }) => { + const user = await findUser(params.id) + if (!user) { + return new Response('User not found', { + status: 404, + }) + } + return json(user) + }, + }, }, }) ``` @@ -330,14 +359,18 @@ You can set the status code of the response by either: import { json } from '@tanstack/react-start' import { setResponseStatus } from '@tanstack/react-start/server' - export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request, params }) => { - const user = await findUser(params.id) - if (!user) { - setResponseStatus(404) - return new Response('User not found') - } - return json(user) + export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request, params }) => { + const user = await findUser(params.id) + if (!user) { + setResponseStatus(404) + return new Response('User not found') + } + return json(user) + }, + }, }, }) ``` @@ -352,13 +385,17 @@ Sometimes you may need to set headers in the response. You can do this by either ```ts // routes/hello.ts - export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - return new Response('Hello, World!', { - headers: { - 'Content-Type': 'text/plain', + export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + return new Response('Hello, World!', { + headers: { + 'Content-Type': 'text/plain', + }, + }) }, - }) + }, }, }) @@ -366,18 +403,22 @@ Sometimes you may need to set headers in the response. You can do this by either // Hello, World! ``` -- Or using the `setHeaders` helper function from `@tanstack/react-start/server`. +- Or using the `setResponseHeaders` helper function from `@tanstack/react-start/server`. ```ts // routes/hello.ts - import { setHeaders } from '@tanstack/react-start/server' - - export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - setHeaders({ - 'Content-Type': 'text/plain', - }) - return new Response('Hello, World!') + import { setResponseHeaders } from '@tanstack/react-start/server' + + export const Route = createFileRoute('/hello')({ + server: { + handlers: { + GET: async ({ request }) => { + setResponseHeaders({ + 'Content-Type': 'text/plain', + }) + return new Response('Hello, World!') + }, + }, }, }) ``` diff --git a/docs/start/framework/react/static-prerendering.md b/docs/start/framework/react/static-prerendering.md index 995c5c47280..3f1c0048fde 100644 --- a/docs/start/framework/react/static-prerendering.md +++ b/docs/start/framework/react/static-prerendering.md @@ -13,6 +13,7 @@ TanStack Start can prerender your application to static HTML files, which can th // vite.config.ts import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -53,6 +54,7 @@ export default defineConfig({ }, ], }), + viteReact(), ], }) ``` diff --git a/docs/start/framework/react/static-server-functions.md b/docs/start/framework/react/static-server-functions.md index 92c7adc9325..79c79d63134 100644 --- a/docs/start/framework/react/static-server-functions.md +++ b/docs/start/framework/react/static-server-functions.md @@ -3,50 +3,31 @@ id: static-server-functions title: Static Server Functions --- +> [!WARNING] +> Static Server Functions are experimental! + ## What are Static Server Functions? -Static server functions are server functions that are executed at build time and cached as static assets when using prerendering/static-generation. They can be set to "static" mode by passing the `type: 'static'` option to `createServerFn`: +Static server functions are server functions that are executed at build time and cached as static assets when using prerendering/static-generation. They can be set to "static" mode by applying the `staticFunctionMiddleware` middleware to `createServerFn`: ```tsx -const myServerFn = createServerFn({ type: 'static' }).handler(async () => { +import { createServerFn } from '@tanstack/react-start' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' + +const myServerFn = createServerFn({ method: 'GET' })..middleware([staticFunctionMiddleware]).handler(async () => { return 'Hello, world!' }) ``` +Ensure that `staticFunctionMiddleware` is the final middleware! + This pattern goes as follows: - Build-time - - During build-time prerendering, a server function with `type: 'static'` is executed + - During build-time prerendering, a server function with `staticFunctionMiddleware` is executed - The result is cached with your build output as a static JSON file under a derived key (function ID + params/payload hash) - The result is returned as normal during prerendering/static-generation and used to prerender the page - Runtime - Initially, the prerendered page's html is served and the server function data is embedded in the html - When the client mounts, the embedded server function data is hydrated - For future client-side invocations, the server function is replaced with a fetch call to the static JSON file - -## Customizing the Server Functions Static Cache - -By default, the static server function cache implementation stores and retrieves static data in the build output directory via node's `fs` module and likewise fetches the data at runtime using a `fetch` call to the same static file. - -This interface can be customized by importing and calling the `createServerFnStaticCache` function to create a custom cache implementation and then calling `setServerFnStaticCache` to set it: - -```tsx -import { - createServerFnStaticCache, - setServerFnStaticCache, -} from '@tanstack/react-start/client' - -const myCustomStaticCache = createServerFnStaticCache({ - setItem: async (ctx, data) => { - // Store the static data in your custom cache - }, - getItem: async (ctx) => { - // Retrieve the static data from your custom cache - }, - fetchItem: async (ctx) => { - // During runtime, fetch the static data from your custom cache - }, -}) - -setServerFnStaticCache(myCustomStaticCache) -``` diff --git a/docs/start/framework/react/tailwind-integration.md b/docs/start/framework/react/tailwind-integration.md index 90d674ca782..b57a95fb380 100644 --- a/docs/start/framework/react/tailwind-integration.md +++ b/docs/start/framework/react/tailwind-integration.md @@ -29,12 +29,13 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import tailwindcss from '@tailwindcss/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { port: 3000, }, - plugins: [tsConfigPaths(), tanstackStart(), tailwindcss()], + plugins: [tsConfigPaths(), tanstackStart(), viteReact(), tailwindcss()], }) ``` diff --git a/docs/start/framework/solid/build-from-scratch.md b/docs/start/framework/solid/build-from-scratch.md index b83876b70a2..5d0f5dcc6bd 100644 --- a/docs/start/framework/solid/build-from-scratch.md +++ b/docs/start/framework/solid/build-from-scratch.md @@ -59,7 +59,7 @@ npm i @tanstack/solid-start @tanstack/solid-router vite You'll also need Solid: ```shell -npm i solid-js +npm i solid-js vite-plugin-solid ``` and some TypeScript: @@ -85,9 +85,6 @@ We'll then update our `package.json` to use Vite's CLI and set `"type": "module" Then configure TanStack Start's Vite plugin in `vite.config.ts`: -> [!NOTE] -> TanStack Start will stop auto-configuring React/Solid Vite plugins. You’ll get full control - choose `vite-plugin-solid`. Set `customViteSolidPlugin: true` to opt in to this feature right now! - ```ts // vite.config.ts import { defineConfig } from 'vite' @@ -101,7 +98,8 @@ export default defineConfig({ }, plugins: [ tsConfigPaths(), - tanstackStart({ customViteSolidPlugin: true }), + tanstackStart(), + // solid's vite plugin must come after start's vite plugin viteSolid({ ssr: true }), ], }) @@ -138,12 +136,12 @@ from the default [preloading functionality](/router/latest/docs/framework/solid/ > You won't have a `routeTree.gen.ts` file yet. This file will be generated when you run TanStack Start for the first time. ```tsx -// src/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +// src/start.tsx +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -153,7 +151,7 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } ``` @@ -222,7 +220,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await readCount() await fs.promises.writeFile(filePath, `${count + data}`) diff --git a/docs/start/framework/solid/hosting.md b/docs/start/framework/solid/hosting.md index f6d15dd1889..b6a5e9240bf 100644 --- a/docs/start/framework/solid/hosting.md +++ b/docs/start/framework/solid/hosting.md @@ -44,19 +44,29 @@ Once you've chosen a deployment target, you can follow the deployment guidelines ### Netlify -Set the `target` value to `netlify` in your `vite.config.ts` file. +Install and add the [`@netlify/vite-plugin-tanstack-start`](https://www.npmjs.com/package/@netlify/vite-plugin-tanstack-start) plugin, which configures your build for Netlify deployment and provides full Netlify production platform emulation in local dev. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import netlify from '@netlify/vite-plugin-tanstack-start' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ - plugins: [tanstackStart({ target: 'netlify' })], + plugins: [tanstackStart(), netlify(), viteSolid({ ssr: true })], }) ``` -Deploy your application to Netlify using their one-click deployment process, and you're ready to go! +Add a `netlify.toml` file to your project root: + +```toml +[build] + command = "vite build" + publish = "dist/client" +``` + +Deploy your application using their one-click deployment process, and you're ready to go! ### Vercel @@ -64,11 +74,12 @@ Deploying your TanStack Start application to Vercel is easy and straightforward. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ - plugins: [tanstackStart({ target: 'vercel' })], + plugins: [tanstackStart({ target: 'vercel' }), viteSolid({ ssr: true })], }) ``` @@ -84,11 +95,15 @@ Set the `target` value to `cloudflare-pages` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ - plugins: [tanstackStart({ target: 'cloudflare-module' })], + plugins: [ + tanstackStart({ target: 'cloudflare-module' }), + viteSolid({ ssr: true }), + ], }) ``` @@ -110,11 +125,12 @@ Set the `target` value to `node-server` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ - plugins: [tanstackStart({ target: 'node-server' })], + plugins: [tanstackStart({ target: 'node-server' }), viteSolid({ ssr: true })], }) ``` @@ -142,11 +158,12 @@ Set the `target` value to `bun` in your `vite.config.ts` file. ```ts // vite.config.ts -import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ - plugins: [tanstackStart({ target: 'bun' })], + plugins: [tanstackStart({ target: 'bun' }), viteSolid({ ssr: true })], }) ``` diff --git a/docs/start/framework/solid/learn-the-basics.md b/docs/start/framework/solid/learn-the-basics.md index 37a6ecff039..80348985b1f 100644 --- a/docs/start/framework/solid/learn-the-basics.md +++ b/docs/start/framework/solid/learn-the-basics.md @@ -18,11 +18,11 @@ This is the file that will dictate the behavior of TanStack Router used within S from the default [preloading functionality](/router/latest/docs/framework/solid/guide/preloading) to [caching staleness](/router/latest/docs/framework/solid/guide/data-loading). ```tsx -// app/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +// src/start.tsx +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { +export function getRouter() { const router = createTanStackRouter({ routeTree, scrollRestoration: true, @@ -33,7 +33,7 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } ``` @@ -60,9 +60,23 @@ import { import { createRouter } from './router' -export default createStartHandler({ +const fetch = createStartHandler({ createRouter, })(defaultStreamHandler) + +export default { + fetch, +} +``` + +The entry point must conform to the following interface: + +```tsx +export default { + fetch(req: Request): Promise { + // ... + }, +} ``` Whether we are statically generating our app or serving it dynamically, the `server.ts` file is the entry point for doing all SSR-related work. @@ -161,7 +175,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await readCount() await fs.promises.writeFile(filePath, `${count + data}`) @@ -231,7 +245,7 @@ import { z } from 'zod' const getUserById = createServerFn({ method: 'GET' }) // Always validate data sent to the function, here we use Zod - .validator(z.string()) + .inputValidator(z.string()) // The handler function is where you perform the server-side logic .handler(async ({ data }) => { return db.query.users.findFirst({ where: eq(users.id, data) }) @@ -261,7 +275,7 @@ const UserSchema = z.object({ }) const updateUser = createServerFn({ method: 'POST' }) - .validator(UserSchema) + .inputValidator(UserSchema) .handler(async ({ data }) => { return db .update(users) diff --git a/docs/start/framework/solid/tailwind-integration.md b/docs/start/framework/solid/tailwind-integration.md index 8e7683acbc6..b4b23e57dee 100644 --- a/docs/start/framework/solid/tailwind-integration.md +++ b/docs/start/framework/solid/tailwind-integration.md @@ -29,12 +29,18 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import tailwindcss from '@tailwindcss/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { port: 3000, }, - plugins: [tsConfigPaths(), tanstackStart(), tailwindcss()], + plugins: [ + tsConfigPaths(), + tanstackStart(), + viteSolid({ ssr: true }), + tailwindcss(), + ], }) ``` diff --git a/e2e/e2e-utils/package.json b/e2e/e2e-utils/package.json index 7376c0bca45..ad1b3f10e0e 100644 --- a/e2e/e2e-utils/package.json +++ b/e2e/e2e-utils/package.json @@ -9,7 +9,8 @@ "type": "module", "exports": { ".": { - "import": "./src/index.ts" + "import": "./src/index.ts", + "default": "./src/index.ts" }, "./package.json": "./package.json" }, diff --git a/e2e/react-router/sentry-integration/tests/fixture.ts b/e2e/e2e-utils/src/fixture.ts similarity index 100% rename from e2e/react-router/sentry-integration/tests/fixture.ts rename to e2e/e2e-utils/src/fixture.ts diff --git a/e2e/e2e-utils/src/index.ts b/e2e/e2e-utils/src/index.ts index 59cbb0bc62c..2a4219ddcf1 100644 --- a/e2e/e2e-utils/src/index.ts +++ b/e2e/e2e-utils/src/index.ts @@ -4,3 +4,4 @@ export { toRuntimePath } from './to-runtime-path' export { resolveRuntimeSuffix } from './resolve-runtime-suffix' export { e2eStartDummyServer, e2eStopDummyServer } from './e2eSetupTeardown' export type { Post } from './posts' +export { test } from './fixture' diff --git a/e2e/react-router/basic-file-based-code-splitting/package.json b/e2e/react-router/basic-file-based-code-splitting/package.json index f77929bac73..f6b16f53d81 100644 --- a/e2e/react-router/basic-file-based-code-splitting/package.json +++ b/e2e/react-router/basic-file-based-code-splitting/package.json @@ -29,6 +29,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-file-based-code-splitting/src/routeTree.gen.ts b/e2e/react-router/basic-file-based-code-splitting/src/routeTree.gen.ts index 70f74043617..442312342af 100644 --- a/e2e/react-router/basic-file-based-code-splitting/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based-code-splitting/src/routeTree.gen.ts @@ -147,18 +147,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/without-loader': { + id: '/without-loader' + path: '/without-loader' + fullPath: '/without-loader' + preLoaderRoute: typeof WithoutLoaderRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutRouteImport + '/viewport-test': { + id: '/viewport-test' + path: '/viewport-test' + fullPath: '/viewport-test' + preLoaderRoute: typeof ViewportTestRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -168,26 +168,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/viewport-test': { - id: '/viewport-test' - path: '/viewport-test' - fullPath: '/viewport-test' - preLoaderRoute: typeof ViewportTestRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/without-loader': { - id: '/without-loader' - path: '/without-loader' - fullPath: '/without-loader' - preLoaderRoute: typeof WithoutLoaderRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } '/posts/$postId': { id: '/posts/$postId' @@ -196,19 +196,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute - } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } '/_layout/_layout-2/layout-b': { id: '/_layout/_layout-2/layout-b' @@ -217,6 +210,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport parentRoute: typeof LayoutLayout2Route } + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route + } } } diff --git a/e2e/react-router/basic-file-based/package.json b/e2e/react-router/basic-file-based/package.json index bd25d40f69c..4b528e0735c 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -30,6 +30,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "combinate": "^1.1.11", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-file-based/tests/redirect.spec.ts b/e2e/react-router/basic-file-based/tests/redirect.spec.ts index 63d36d96104..8ec1a1698e4 100644 --- a/e2e/react-router/basic-file-based/tests/redirect.spec.ts +++ b/e2e/react-router/basic-file-based/tests/redirect.spec.ts @@ -53,6 +53,7 @@ test.describe('redirects', () => { setTimeout(resolve, expectRequestHappened ? 5000 : 500), ) await Promise.race([requestPromise, timeoutPromise]) + await page.waitForLoadState('networkidle') expect(requestHappened).toBe(expectRequestHappened) await link.click() let fullPageLoad = false diff --git a/e2e/react-router/basic-react-query-file-based/package.json b/e2e/react-router/basic-react-query-file-based/package.json index d19c42b64bb..07f8813421a 100644 --- a/e2e/react-router/basic-react-query-file-based/package.json +++ b/e2e/react-router/basic-react-query-file-based/package.json @@ -30,6 +30,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-react-query/package.json b/e2e/react-router/basic-react-query/package.json index b1d36927050..e3e11f23832 100644 --- a/e2e/react-router/basic-react-query/package.json +++ b/e2e/react-router/basic-react-query/package.json @@ -28,6 +28,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-scroll-restoration/package.json b/e2e/react-router/basic-scroll-restoration/package.json index dab75904e2c..e84569ebb3e 100644 --- a/e2e/react-router/basic-scroll-restoration/package.json +++ b/e2e/react-router/basic-scroll-restoration/package.json @@ -27,6 +27,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-scroll-restoration/src/main.tsx b/e2e/react-router/basic-scroll-restoration/src/main.tsx index 0273978dcd3..8bc4c54c1c5 100644 --- a/e2e/react-router/basic-scroll-restoration/src/main.tsx +++ b/e2e/react-router/basic-scroll-restoration/src/main.tsx @@ -43,7 +43,7 @@ function RootComponent() { const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: IndexComponent, }) @@ -75,7 +75,7 @@ function IndexComponent() { const aboutRoute = createRoute({ getParentRoute: () => rootRoute, path: '/about', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: AboutComponent, }) @@ -103,7 +103,7 @@ function AboutComponent() { const byElementRoute = createRoute({ getParentRoute: () => rootRoute, path: '/by-element', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: ByElementComponent, }) @@ -205,7 +205,7 @@ function ByElementComponent() { const fooRoute = createRoute({ getParentRoute: () => rootRoute, path: '/foo', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: FooComponent, }) @@ -233,7 +233,7 @@ function FooComponent() { const barRoute = createRoute({ getParentRoute: () => rootRoute, path: '/bar', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: BarComponent, }) diff --git a/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts b/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts index af79f97c52c..c7360ce8a4a 100644 --- a/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts +++ b/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts @@ -5,6 +5,7 @@ test('restore scroll positions by page, home pages top message should not displa }) => { // Step 1: Navigate to the home page await page.goto('/') + await page.waitForURL('/') await expect(page.locator('#greeting')).toContainText('Welcome Home!') await expect(page.locator('#top-message')).toBeInViewport() @@ -45,6 +46,7 @@ test('restore scroll positions by element, first regular list item should not di }) => { // Step 1: Navigate to the by-element page await page.goto('/by-element') + await page.waitForURL('/by-element') // Step 2: Scroll to a position that hides the first list item in regular list const targetScrollPosition = 1000 @@ -86,6 +88,7 @@ test('scroll to top when not scrolled, regression test for #4782', async ({ page, }) => { await page.goto('/foo') + await page.waitForURL('/foo') await expect(page.getByTestId('foo-route-component')).toBeVisible() diff --git a/e2e/react-router/basic-virtual-file-based/package.json b/e2e/react-router/basic-virtual-file-based/package.json index ccfaa1ff493..49e8a3fe1ac 100644 --- a/e2e/react-router/basic-virtual-file-based/package.json +++ b/e2e/react-router/basic-virtual-file-based/package.json @@ -29,6 +29,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-virtual-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-virtual-file-based/src/routeTree.gen.ts index e5756ba8d7c..ab3e216c966 100644 --- a/e2e/react-router/basic-virtual-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-virtual-file-based/src/routeTree.gen.ts @@ -168,11 +168,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -182,25 +182,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport - parentRoute: typeof rootRouteImport - } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof postsPostsHomeRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport parentRoute: typeof postsPostsRoute } '/_first/_second': { @@ -210,32 +203,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/_first/_second/layout-a': { - id: '/_first/_second/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof aRouteImport - parentRoute: typeof layoutSecondLayoutRoute - } - '/_first/_second/layout-b': { - id: '/_first/_second/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport } - '/classic/hello/universe': { - id: '/classic/hello/universe' - path: '/universe' - fullPath: '/classic/hello/universe' - preLoaderRoute: typeof ClassicHelloUniverseRouteImport + '/classic/hello/': { + id: '/classic/hello/' + path: '/' + fullPath: '/classic/hello/' + preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } '/classic/hello/world': { @@ -245,13 +231,27 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloWorldRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/': { - id: '/classic/hello/' - path: '/' - fullPath: '/classic/hello/' - preLoaderRoute: typeof ClassicHelloIndexRouteImport + '/classic/hello/universe': { + id: '/classic/hello/universe' + path: '/universe' + fullPath: '/classic/hello/universe' + preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } + '/_first/_second/layout-b': { + id: '/_first/_second/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } + '/_first/_second/layout-a': { + id: '/_first/_second/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof aRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/e2e/react-router/basic-virtual-named-export-config-file-based/package.json b/e2e/react-router/basic-virtual-named-export-config-file-based/package.json index cc0549be9c3..74d013545fa 100644 --- a/e2e/react-router/basic-virtual-named-export-config-file-based/package.json +++ b/e2e/react-router/basic-virtual-named-export-config-file-based/package.json @@ -29,6 +29,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/basic-virtual-named-export-config-file-based/tests/app.spec.ts b/e2e/react-router/basic-virtual-named-export-config-file-based/tests/app.spec.ts index 3e5a69ccaae..0aedada786e 100644 --- a/e2e/react-router/basic-virtual-named-export-config-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-virtual-named-export-config-file-based/tests/app.spec.ts @@ -5,12 +5,14 @@ test.beforeEach(async ({ page }) => { }) test('Navigating to a post page', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'Posts' }).click() await page.getByRole('link', { name: 'sunt aut facere repe' }).click() await expect(page.getByRole('heading')).toContainText('sunt aut facere') }) test('Navigating nested layouts', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") @@ -24,6 +26,7 @@ test('Navigating nested layouts', async ({ page }) => { }) test('Navigating to a not-found route', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() await expect(page.getByRole('paragraph')).toContainText( 'This is the notFoundComponent configured on root route', diff --git a/e2e/react-router/basic/package.json b/e2e/react-router/basic/package.json index bf74680fad6..0cb559c6b99 100644 --- a/e2e/react-router/basic/package.json +++ b/e2e/react-router/basic/package.json @@ -26,6 +26,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/generator-cli-only/package.json b/e2e/react-router/generator-cli-only/package.json index 10b802ae62b..08f90293f34 100644 --- a/e2e/react-router/generator-cli-only/package.json +++ b/e2e/react-router/generator-cli-only/package.json @@ -27,6 +27,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/js-only-file-based/package.json b/e2e/react-router/js-only-file-based/package.json index f8881a588af..46a351989dd 100644 --- a/e2e/react-router/js-only-file-based/package.json +++ b/e2e/react-router/js-only-file-based/package.json @@ -27,6 +27,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/tests/app.spec.ts b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/tests/app.spec.ts index 10beff655f0..a689f459e3a 100644 --- a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/tests/app.spec.ts +++ b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/tests/app.spec.ts @@ -5,12 +5,14 @@ test.beforeEach(async ({ page }) => { }) test('Navigating to a post page', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'Posts' }).click() await page.getByRole('link', { name: 'sunt aut facere repe' }).click() await expect(page.getByRole('heading')).toContainText('sunt aut facere') }) test('Navigating nested layouts', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#root')).toContainText("I'm a layout") @@ -24,6 +26,7 @@ test('Navigating nested layouts', async ({ page }) => { }) test('Navigating to a not-found route', async ({ page }) => { + await page.waitForURL('/') await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() await expect(page.getByRole('paragraph')).toContainText( 'This is the notFoundComponent configured on root route', diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/package.json b/e2e/react-router/scroll-restoration-sandbox-vite/package.json index 1a28218b4f3..8469b9a79c6 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/react-router/scroll-restoration-sandbox-vite/package.json @@ -32,6 +32,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/sentry-integration/package.json b/e2e/react-router/sentry-integration/package.json index e653d74ffb7..44fa594fa05 100644 --- a/e2e/react-router/sentry-integration/package.json +++ b/e2e/react-router/sentry-integration/package.json @@ -29,6 +29,6 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/react-router/sentry-integration/tests/app.spec.ts b/e2e/react-router/sentry-integration/tests/app.spec.ts index 9a3b53a966e..1de2800c80a 100644 --- a/e2e/react-router/sentry-integration/tests/app.spec.ts +++ b/e2e/react-router/sentry-integration/tests/app.spec.ts @@ -1,6 +1,5 @@ import { expect } from '@playwright/test' - -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' test.beforeEach(async ({ page }) => { await page.goto('/') diff --git a/e2e/react-start/basic-auth/package.json b/e2e/react-start/basic-auth/package.json index f5ee14762e0..4578e784a35 100644 --- a/e2e/react-start/basic-auth/package.json +++ b/e2e/react-start/basic-auth/package.json @@ -21,7 +21,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@playwright/test": "^1.50.1", diff --git a/e2e/react-start/basic-auth/src/routeTree.gen.ts b/e2e/react-start/basic-auth/src/routeTree.gen.ts index 0d9f3c20242..19a8180edba 100644 --- a/e2e/react-start/basic-auth/src/routeTree.gen.ts +++ b/e2e/react-start/basic-auth/src/routeTree.gen.ts @@ -214,3 +214,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/basic-auth/src/router.tsx b/e2e/react-start/basic-auth/src/router.tsx index c76eb0210cc..fef35c9e067 100644 --- a/e2e/react-start/basic-auth/src/router.tsx +++ b/e2e/react-start/basic-auth/src/router.tsx @@ -1,22 +1,16 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, + scrollRestoration: true, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, defaultNotFoundComponent: () => , - scrollRestoration: true, }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/basic-auth/src/routes/_authed.tsx b/e2e/react-start/basic-auth/src/routes/_authed.tsx index c457cf5e57e..221627851ab 100644 --- a/e2e/react-start/basic-auth/src/routes/_authed.tsx +++ b/e2e/react-start/basic-auth/src/routes/_authed.tsx @@ -8,7 +8,7 @@ import { useAppSession } from '~/utils/session' export const loginFn = createServerFn({ method: 'POST', }) - .validator((payload: { email: string; password: string }) => payload) + .inputValidator((payload: { email: string; password: string }) => payload) .handler(async ({ data }) => { // Find the user const user = await prismaClient.user.findUnique({ diff --git a/e2e/react-start/basic-auth/src/routes/signup.tsx b/e2e/react-start/basic-auth/src/routes/signup.tsx index 817362dc052..d6ef1537861 100644 --- a/e2e/react-start/basic-auth/src/routes/signup.tsx +++ b/e2e/react-start/basic-auth/src/routes/signup.tsx @@ -9,7 +9,7 @@ import { useAppSession } from '~/utils/session' export const signupFn = createServerFn({ method: 'POST', }) - .validator( + .inputValidator( (data: { email: string; password: string; redirectUrl?: string }) => data, ) .handler(async ({ data: payload }) => { diff --git a/e2e/react-start/basic-auth/src/utils/posts.ts b/e2e/react-start/basic-auth/src/utils/posts.ts index 532e883ac0c..b30810a5a3a 100644 --- a/e2e/react-start/basic-auth/src/utils/posts.ts +++ b/e2e/react-start/basic-auth/src/utils/posts.ts @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/basic-auth/vite.config.ts b/e2e/react-start/basic-auth/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/basic-auth/vite.config.ts +++ b/e2e/react-start/basic-auth/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/basic-react-query/package.json b/e2e/react-start/basic-react-query/package.json index 284e0626f75..ac0c200bd60 100644 --- a/e2e/react-start/basic-react-query/package.json +++ b/e2e/react-start/basic-react-query/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -21,7 +21,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@playwright/test": "^1.50.1", @@ -32,6 +32,7 @@ "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/react-start/basic-react-query/src/routeTree.gen.ts b/e2e/react-start/basic-react-query/src/routeTree.gen.ts index 02c8e9e10d9..68f522b0699 100644 --- a/e2e/react-start/basic-react-query/src/routeTree.gen.ts +++ b/e2e/react-start/basic-react-query/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as RedirectRouteImport } from './routes/redirect' @@ -21,14 +19,12 @@ import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api.users' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api.users' -import { ServerRoute as ApiUsersIdServerRouteImport } from './routes/api/users.$id' - -const rootServerRouteImport = createServerRootRoute() const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -79,6 +75,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -88,6 +89,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', @@ -98,16 +104,6 @@ const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ path: '/layout-a', getParentRoute: () => LayoutLayout2Route, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersIdServerRoute = ApiUsersIdServerRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -115,24 +111,28 @@ export interface FileRoutesByFullPath { '/posts': typeof PostsRouteWithChildren '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { @@ -144,12 +144,14 @@ export interface FileRoutesById { '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { @@ -160,24 +162,28 @@ export interface FileRouteTypes { | '/posts' | '/redirect' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/layout-a' | '/layout-b' + | '/api/users/$id' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' | '/deferred' | '/redirect' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' | '/layout-a' | '/layout-b' + | '/api/users/$id' | '/posts/$postId/deep' id: | '__root__' @@ -188,12 +194,14 @@ export interface FileRouteTypes { | '/redirect' | '/users' | '/_layout/_layout-2' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/_layout/_layout-2/layout-a' | '/_layout/_layout-2/layout-b' + | '/api/users/$id' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } @@ -204,32 +212,9 @@ export interface RootRouteChildren { PostsRoute: typeof PostsRouteWithChildren RedirectRoute: typeof RedirectRoute UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$id' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$id' - id: '__root__' | '/api/users' | '/api/users/$id' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -303,6 +288,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_layout-2': { id: '/_layout/_layout-2' path: '' @@ -317,6 +309,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } '/_layout/_layout-2/layout-b': { id: '/_layout/_layout-2/layout-b' path: '/layout-b' @@ -333,24 +332,6 @@ declare module '@tanstack/react-router' { } } } -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users/$id': { - id: '/api/users/$id' - path: '/$id' - fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute - } - } -} interface LayoutLayout2RouteChildren { LayoutLayout2LayoutARoute: typeof LayoutLayout2LayoutARoute @@ -401,16 +382,16 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) -interface ApiUsersServerRouteChildren { - ApiUsersIdServerRoute: typeof ApiUsersIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersIdRoute: typeof ApiUsersIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersIdServerRoute: ApiUsersIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersIdRoute: ApiUsersIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { @@ -420,14 +401,17 @@ const rootRouteChildren: RootRouteChildren = { PostsRoute: PostsRouteWithChildren, RedirectRoute: RedirectRoute, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/react-start/basic-react-query/src/router.tsx b/e2e/react-start/basic-react-query/src/router.tsx index 6f934c47601..18b9e194ecf 100644 --- a/e2e/react-start/basic-react-query/src/router.tsx +++ b/e2e/react-start/basic-react-query/src/router.tsx @@ -1,17 +1,13 @@ import { QueryClient } from '@tanstack/react-query' -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -// NOTE: Most of the integration code found here is experimental and will -// definitely end up in a more streamlined API in the future. This is just -// to show what's possible with the current APIs. - -export function createRouter() { +export function getRouter() { const queryClient = new QueryClient() - const router = createTanStackRouter({ + const router = createRouter({ routeTree, context: { queryClient }, scrollRestoration: true, @@ -25,9 +21,3 @@ export function createRouter() { }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/basic-react-query/src/routes/api.users.ts b/e2e/react-start/basic-react-query/src/routes/api.users.ts index e3865404bbe..454aca50a06 100644 --- a/e2e/react-start/basic-react-query/src/routes/api.users.ts +++ b/e2e/react-start/basic-react-query/src/routes/api.users.ts @@ -1,4 +1,4 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { json } from '@tanstack/react-start' import axios from 'redaxios' import type { User } from '../utils/users' @@ -9,13 +9,17 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users').methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await axios.get>(`${queryURL}/users`) - - const list = res.data.slice(0, 10) - - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) +export const Route = createFileRoute('/api/users')({ + server: { + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>(`${queryURL}/users`) + const list = res.data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, }, }) diff --git a/e2e/react-start/basic-react-query/src/routes/api/users.$id.ts b/e2e/react-start/basic-react-query/src/routes/api/users.$id.ts index 88e8b71fe0f..70ad77d76b3 100644 --- a/e2e/react-start/basic-react-query/src/routes/api/users.$id.ts +++ b/e2e/react-start/basic-react-query/src/routes/api/users.$id.ts @@ -1,4 +1,4 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { json } from '@tanstack/react-start' import axios from 'redaxios' import type { User } from '../../utils/users' @@ -9,20 +9,23 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users/$id').methods({ - GET: async ({ request, params }) => { - console.info(`Fetching users by id=${params.id}... @`, request.url) - try { - const res = await axios.get(`${queryURL}/users/` + params.id) - - return json({ - id: res.data.id, - name: res.data.name, - email: res.data.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } +export const Route = createFileRoute('/api/users/$id')({ + server: { + handlers: { + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get(`${queryURL}/users/` + params.id) + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/e2e/react-start/basic-react-query/src/utils/posts.tsx b/e2e/react-start/basic-react-query/src/utils/posts.tsx index 3fd16db69a4..2574bf10d16 100644 --- a/e2e/react-start/basic-react-query/src/utils/posts.tsx +++ b/e2e/react-start/basic-react-query/src/utils/posts.tsx @@ -31,7 +31,7 @@ export const postsQueryOptions = () => }) export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/basic-react-query/vite.config.ts b/e2e/react-start/basic-react-query/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/basic-react-query/vite.config.ts +++ b/e2e/react-start/basic-react-query/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/basic-rsc/package.json b/e2e/react-start/basic-rsc/package.disabled.json similarity index 97% rename from e2e/react-start/basic-rsc/package.json rename to e2e/react-start/basic-rsc/package.disabled.json index 9c820c9ccd4..30ac070db2b 100644 --- a/e2e/react-start/basic-rsc/package.json +++ b/e2e/react-start/basic-rsc/package.disabled.json @@ -17,7 +17,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@types/react": "^19.0.8", diff --git a/e2e/react-start/basic-rsc/src/routeTree.gen.ts b/e2e/react-start/basic-rsc/src/routeTree.gen.ts index 39e61ac5f9d..500f71a9f73 100644 --- a/e2e/react-start/basic-rsc/src/routeTree.gen.ts +++ b/e2e/react-start/basic-rsc/src/routeTree.gen.ts @@ -244,3 +244,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/basic-rsc/src/router.tsx b/e2e/react-start/basic-rsc/src/router.tsx index 5a1c0ad410e..fef35c9e067 100644 --- a/e2e/react-start/basic-rsc/src/router.tsx +++ b/e2e/react-start/basic-rsc/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: 'intent', @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/basic-rsc/src/routes/posts.$postId.tsx b/e2e/react-start/basic-rsc/src/routes/posts.$postId.tsx index 7dd63a2cd0a..a7f2a551b2e 100644 --- a/e2e/react-start/basic-rsc/src/routes/posts.$postId.tsx +++ b/e2e/react-start/basic-rsc/src/routes/posts.$postId.tsx @@ -6,7 +6,7 @@ import { fetchPost } from '~/utils/posts' import { NotFound } from '~/components/NotFound' const renderPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { const post = await fetchPost(postId) diff --git a/e2e/react-start/basic-rsc/vite.config.ts b/e2e/react-start/basic-rsc/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/basic-rsc/vite.config.ts +++ b/e2e/react-start/basic-rsc/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/basic-tsr-config/package.json b/e2e/react-start/basic-tsr-config/package.json index 6e366951836..a41ed7d4675 100644 --- a/e2e/react-start/basic-tsr-config/package.json +++ b/e2e/react-start/basic-tsr-config/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "rimraf ./count.txt && vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -15,13 +15,14 @@ "@tanstack/react-start": "workspace:^", "react": "^19.0.0", "react-dom": "^19.0.0", - "vite": "^6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "srvx": "^0.8.6", "typescript": "^5.7.2" } } diff --git a/e2e/react-start/basic-tsr-config/src/app/router.tsx b/e2e/react-start/basic-tsr-config/src/app/router.tsx deleted file mode 100644 index 25729701a7b..00000000000 --- a/e2e/react-start/basic-tsr-config/src/app/router.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' - -export function createRouter() { - const router = createTanStackRouter({ - routeTree, - scrollRestoration: true, - }) - - return router -} - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/basic-tsr-config/src/app/routeTree.gen.ts b/e2e/react-start/basic-tsr-config/src/routeTree.gen.ts similarity index 87% rename from e2e/react-start/basic-tsr-config/src/app/routeTree.gen.ts rename to e2e/react-start/basic-tsr-config/src/routeTree.gen.ts index d204c269b33..d9eff184704 100644 --- a/e2e/react-start/basic-tsr-config/src/app/routeTree.gen.ts +++ b/e2e/react-start/basic-tsr-config/src/routeTree.gen.ts @@ -57,3 +57,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/basic-tsr-config/src/router.tsx b/e2e/react-start/basic-tsr-config/src/router.tsx new file mode 100644 index 00000000000..82a730704ad --- /dev/null +++ b/e2e/react-start/basic-tsr-config/src/router.tsx @@ -0,0 +1,11 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function getRouter() { + const router = createRouter({ + routeTree, + scrollRestoration: true, + }) + + return router +} diff --git a/e2e/react-start/basic-tsr-config/src/app/routes/__root.tsx b/e2e/react-start/basic-tsr-config/src/routes/__root.tsx similarity index 100% rename from e2e/react-start/basic-tsr-config/src/app/routes/__root.tsx rename to e2e/react-start/basic-tsr-config/src/routes/__root.tsx diff --git a/e2e/react-start/basic-tsr-config/src/app/routes/index.tsx b/e2e/react-start/basic-tsr-config/src/routes/index.tsx similarity index 90% rename from e2e/react-start/basic-tsr-config/src/app/routes/index.tsx rename to e2e/react-start/basic-tsr-config/src/routes/index.tsx index 5e2d540fbce..413617217d8 100644 --- a/e2e/react-start/basic-tsr-config/src/app/routes/index.tsx +++ b/e2e/react-start/basic-tsr-config/src/routes/index.tsx @@ -1,5 +1,5 @@ import fs from 'node:fs' -import { useRouter, createFileRoute } from '@tanstack/react-router' +import { createFileRoute, useRouter } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' const filePath = 'count.txt' @@ -12,7 +12,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await getCount() await fs.promises.writeFile(filePath, `${count + data}`) diff --git a/e2e/react-start/basic-tsr-config/tests/app.spec.ts b/e2e/react-start/basic-tsr-config/tests/app.spec.ts index e3488df952b..07a562fcb31 100644 --- a/e2e/react-start/basic-tsr-config/tests/app.spec.ts +++ b/e2e/react-start/basic-tsr-config/tests/app.spec.ts @@ -2,9 +2,14 @@ import { expect, test } from '@playwright/test' test('opening the app', async ({ page }) => { await page.goto('/') + await page.waitForLoadState('networkidle') + + const button = page.getByTestId('add-button') - await expect(page.getByTestId('add-button')).toContainText('Add 1 to 0?') - await page.getByTestId('add-button').click() + await expect(button).toContainText('Add 1 to 0?') + + await button.click() await page.waitForLoadState('networkidle') - await expect(page.getByTestId('add-button')).toContainText('Add 1 to 1?') + + await expect(button).toContainText('Add 1 to 1?') }) diff --git a/e2e/react-start/basic-tsr-config/vite.config.ts b/e2e/react-start/basic-tsr-config/vite.config.ts index 69045b5a44a..8bb2daf5054 100644 --- a/e2e/react-start/basic-tsr-config/vite.config.ts +++ b/e2e/react-start/basic-tsr-config/vite.config.ts @@ -7,8 +7,11 @@ export default defineConfig({ }, plugins: [ tanstackStart({ - tsr: { - srcDirectory: './src/app', + srcDirectory: './src/app', + router: { + entry: '../router.tsx', + routesDirectory: '../routes', + generatedRouteTree: '../routeTree.gen.ts', }, }), ], diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index 57309b87a44..b3f2fe4b233 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -17,9 +17,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", - "tailwind-merge": "^2.6.0", - "vite": "6.3.5", - "zod": "^3.24.2" + "tailwind-merge": "^2.6.0" }, "devDependencies": { "@playwright/test": "^1.50.1", @@ -31,8 +29,11 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.4" + "vite": "^7.1.1", + "vite-tsconfig-paths": "^5.1.4", + "zod": "^3.24.2" } } diff --git a/e2e/react-start/basic/src/client.tsx b/e2e/react-start/basic/src/client.tsx index 5fcd1c5f19d..fdfbde86770 100644 --- a/e2e/react-start/basic/src/client.tsx +++ b/e2e/react-start/basic/src/client.tsx @@ -2,18 +2,15 @@ // This file is a good smoke test to make sure the custom client entry is working import { StrictMode, startTransition } from 'react' import { hydrateRoot } from 'react-dom/client' -import { StartClient } from '@tanstack/react-start' -import { createRouter } from './router' +import { StartClient } from '@tanstack/react-start/client' console.log("[client-entry]: using custom client entry in 'src/client.tsx'") -const router = createRouter() - startTransition(() => { hydrateRoot( document, - + , ) }) diff --git a/e2e/react-start/basic/src/components/throwRedirect.ts b/e2e/react-start/basic/src/components/throwRedirect.ts index 6a7a0119126..0081a3c5602 100644 --- a/e2e/react-start/basic/src/components/throwRedirect.ts +++ b/e2e/react-start/basic/src/components/throwRedirect.ts @@ -2,7 +2,7 @@ import { redirect } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' export const throwRedirect = createServerFn() - .validator( + .inputValidator( (opts: { target: 'internal' | 'external' reloadDocument?: boolean diff --git a/e2e/react-start/basic/src/routeTree.gen.ts b/e2e/react-start/basic/src/routeTree.gen.ts index 1298d9b2113..c9a9b131a57 100644 --- a/e2e/react-start/basic/src/routeTree.gen.ts +++ b/e2e/react-start/basic/src/routeTree.gen.ts @@ -10,11 +10,6 @@ import { createFileRoute } from '@tanstack/react-router' import type { CreateFileRoute, FileRoutesByPath } from '@tanstack/react-router' -import type { - CreateServerFileRoute, - ServerFileRoutesByPath, -} from '@tanstack/react-start/server' -import { createServerRootRoute } from '@tanstack/react-start/server' import { Route as rootRouteImport } from './routes/__root' import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' @@ -41,11 +36,13 @@ import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' +import { Route as ApiUsersRouteImport } from './routes/api.users' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' import { Route as RedirectTargetServerFnIndexRouteImport } from './routes/redirect/$target/serverFn/index' @@ -54,11 +51,8 @@ import { Route as RedirectTargetServerFnViaLoaderRouteImport } from './routes/re import { Route as RedirectTargetServerFnViaBeforeLoadRouteImport } from './routes/redirect/$target/serverFn/via-beforeLoad' import { Route as FooBarQuxHereRouteImport } from './routes/foo/$bar/$qux/_here' import { Route as FooBarQuxHereIndexRouteImport } from './routes/foo/$bar/$qux/_here/index' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api.users' -import { ServerRoute as ApiUsersIdServerRouteImport } from './routes/api/users.$id' const FooBarQuxRouteImport = createFileRoute('/foo/$bar/$qux')() -const rootServerRouteImport = createServerRootRoute() const Char45824Char54620Char48124Char44397Route = Char45824Char54620Char48124Char44397RouteImport.update({ @@ -181,6 +175,11 @@ const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ path: '/via-beforeLoad', getParentRoute: () => NotFoundRouteRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -211,6 +210,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', @@ -254,16 +258,6 @@ const FooBarQuxHereIndexRoute = FooBarQuxHereIndexRouteImport.update({ path: '/', getParentRoute: () => FooBarQuxHereRoute, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersIdServerRoute = ApiUsersIdServerRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -277,6 +271,7 @@ export interface FileRoutesByFullPath { '/stream': typeof StreamRoute '/users': typeof UsersRouteWithChildren '/대한민국': typeof Char45824Char54620Char48124Char44397Route + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -291,6 +286,7 @@ export interface FileRoutesByFullPath { '/users/': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -310,6 +306,7 @@ export interface FileRoutesByTo { '/scripts': typeof ScriptsRoute '/stream': typeof StreamRoute '/대한민국': typeof Char45824Char54620Char48124Char44397Route + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -323,6 +320,7 @@ export interface FileRoutesByTo { '/users': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -348,6 +346,7 @@ export interface FileRoutesById { '/users': typeof UsersRouteWithChildren '/대한민국': typeof Char45824Char54620Char48124Char44397Route '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -362,6 +361,7 @@ export interface FileRoutesById { '/users/': typeof UsersIndexRoute '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -388,6 +388,7 @@ export interface FileRouteTypes { | '/stream' | '/users' | '/대한민국' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -402,6 +403,7 @@ export interface FileRouteTypes { | '/users/' | '/layout-a' | '/layout-b' + | '/api/users/$id' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -421,6 +423,7 @@ export interface FileRouteTypes { | '/scripts' | '/stream' | '/대한민국' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -434,6 +437,7 @@ export interface FileRouteTypes { | '/users' | '/layout-a' | '/layout-b' + | '/api/users/$id' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -458,6 +462,7 @@ export interface FileRouteTypes { | '/users' | '/대한민국' | '/_layout/_layout-2' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -472,6 +477,7 @@ export interface FileRouteTypes { | '/users/' | '/_layout/_layout-2/layout-a' | '/_layout/_layout-2/layout-b' + | '/api/users/$id' | '/posts_/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -498,71 +504,55 @@ export interface RootRouteChildren { StreamRoute: typeof StreamRoute UsersRoute: typeof UsersRouteWithChildren Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route + ApiUsersRoute: typeof ApiUsersRouteWithChildren RedirectTargetRoute: typeof RedirectTargetRouteWithChildren RedirectIndexRoute: typeof RedirectIndexRoute PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute FooBarQuxRoute: typeof FooBarQuxRouteWithChildren } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$id' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$id' - id: '__root__' | '/api/users' | '/api/users/$id' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport parentRoute: typeof rootRouteImport } - '/not-found': { - id: '/not-found' - path: '/not-found' - fullPath: '/not-found' - preLoaderRoute: typeof NotFoundRouteRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/search-params': { - id: '/search-params' - path: '/search-params' - fullPath: '/search-params' - preLoaderRoute: typeof SearchParamsRouteRouteImport + '/stream': { + id: '/stream' + path: '/stream' + fullPath: '/stream' + preLoaderRoute: typeof StreamRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutRouteImport + '/scripts': { + id: '/scripts' + path: '/scripts' + fullPath: '/scripts' + preLoaderRoute: typeof ScriptsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/inline-scripts': { + id: '/inline-scripts' + path: '/inline-scripts' + fullPath: '/inline-scripts' + preLoaderRoute: typeof InlineScriptsRouteImport parentRoute: typeof rootRouteImport } '/inline-scripts': { @@ -579,82 +569,89 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LinksRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/scripts': { - id: '/scripts' - path: '/scripts' - fullPath: '/scripts' - preLoaderRoute: typeof ScriptsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/stream': { - id: '/stream' - path: '/stream' - fullPath: '/stream' - preLoaderRoute: typeof StreamRouteImport + '/search-params': { + id: '/search-params' + path: '/search-params' + fullPath: '/search-params' + preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/not-found': { + id: '/not-found' + path: '/not-found' + fullPath: '/not-found' + preLoaderRoute: typeof NotFoundRouteRouteImport parentRoute: typeof rootRouteImport } - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users': { - id: '/api/users' - path: '' - fullPath: '/api/users' - preLoaderRoute: unknown + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute + } + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/not-found/via-beforeLoad': { - id: '/not-found/via-beforeLoad' - path: '/via-beforeLoad' - fullPath: '/not-found/via-beforeLoad' - preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport - parentRoute: typeof NotFoundRouteRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } - '/not-found/via-loader': { - id: '/not-found/via-loader' - path: '/via-loader' - fullPath: '/not-found/via-loader' - preLoaderRoute: typeof NotFoundViaLoaderRouteImport + '/not-found/': { + id: '/not-found/' + path: '/' + fullPath: '/not-found/' + preLoaderRoute: typeof NotFoundIndexRouteImport parentRoute: typeof NotFoundRouteRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsPostIdRouteImport - parentRoute: typeof PostsRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport - parentRoute: typeof rootRouteImport + '/search-params/loader-throws-redirect': { + id: '/search-params/loader-throws-redirect' + path: '/loader-throws-redirect' + fullPath: '/search-params/loader-throws-redirect' + preLoaderRoute: typeof SearchParamsLoaderThrowsRedirectRouteImport + parentRoute: typeof SearchParamsRouteRoute } '/search-params/default': { id: '/search-params/default' @@ -663,88 +660,60 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SearchParamsDefaultRouteImport parentRoute: typeof SearchParamsRouteRoute } - '/search-params/loader-throws-redirect': { - id: '/search-params/loader-throws-redirect' - path: '/loader-throws-redirect' - fullPath: '/search-params/loader-throws-redirect' - preLoaderRoute: typeof SearchParamsLoaderThrowsRedirectRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport + parentRoute: typeof rootRouteImport } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute } - '/not-found/': { - id: '/not-found/' - path: '/' - fullPath: '/not-found/' - preLoaderRoute: typeof NotFoundIndexRouteImport + '/not-found/via-loader': { + id: '/not-found/via-loader' + path: '/via-loader' + fullPath: '/not-found/via-loader' + preLoaderRoute: typeof NotFoundViaLoaderRouteImport parentRoute: typeof NotFoundRouteRoute } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/not-found/via-beforeLoad': { + id: '/not-found/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/not-found/via-beforeLoad' + preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/redirect/': { - id: '/redirect/' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectIndexRouteImport + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport parentRoute: typeof rootRouteImport } - '/search-params/': { - id: '/search-params/' - path: '/' - fullPath: '/search-params/' - preLoaderRoute: typeof SearchParamsIndexRouteImport - parentRoute: typeof SearchParamsRouteRoute - } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute - } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route - } - '/_layout/_layout-2/layout-b': { - id: '/_layout/_layout-2/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport - parentRoute: typeof LayoutLayout2Route - } - '/api/users/$id': { - id: '/api/users/$id' + '/_layout/_layout-2': { + id: '/_layout/_layout-2' path: '' - fullPath: '/api/users/$id' - preLoaderRoute: unknown - parentRoute: typeof rootRouteImport + fullPath: '' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport + '/foo/$bar/$qux': { + id: '/foo/$bar/$qux' + path: '/foo/$bar/$qux' + fullPath: '/foo/$bar/$qux' + preLoaderRoute: typeof FooBarQuxRouteImport parentRoute: typeof rootRouteImport } - '/redirect/$target/via-beforeLoad': { - id: '/redirect/$target/via-beforeLoad' - path: '/via-beforeLoad' - fullPath: '/redirect/$target/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + '/redirect/$target/': { + id: '/redirect/$target/' + path: '/' + fullPath: '/redirect/$target/' + preLoaderRoute: typeof RedirectTargetIndexRouteImport parentRoute: typeof RedirectTargetRoute } '/redirect/$target/via-loader': { @@ -754,32 +723,46 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport parentRoute: typeof RedirectTargetRoute } - '/redirect/$target/': { - id: '/redirect/$target/' - path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport + '/redirect/$target/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport parentRoute: typeof RedirectTargetRoute } - '/foo/$bar/$qux/_here': { - id: '/foo/$bar/$qux/_here' - path: '/foo/$bar/$qux' - fullPath: '/foo/$bar/$qux' - preLoaderRoute: typeof FooBarQuxHereRouteImport - parentRoute: typeof FooBarQuxRoute + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute } - '/redirect/$target/serverFn/via-beforeLoad': { - id: '/redirect/$target/serverFn/via-beforeLoad' - path: '/serverFn/via-beforeLoad' - fullPath: '/redirect/$target/serverFn/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetServerFnViaBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + '/_layout/_layout-2/layout-b': { + id: '/_layout/_layout-2/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport + parentRoute: typeof LayoutLayout2Route } - '/redirect/$target/serverFn/via-loader': { - id: '/redirect/$target/serverFn/via-loader' - path: '/serverFn/via-loader' - fullPath: '/redirect/$target/serverFn/via-loader' - preLoaderRoute: typeof RedirectTargetServerFnViaLoaderRouteImport + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route + } + '/redirect/$target/serverFn/': { + id: '/redirect/$target/serverFn/' + path: '/serverFn' + fullPath: '/redirect/$target/serverFn' + preLoaderRoute: typeof RedirectTargetServerFnIndexRouteImport parentRoute: typeof RedirectTargetRoute } '/redirect/$target/serverFn/via-useServerFn': { @@ -789,13 +772,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport parentRoute: typeof RedirectTargetRoute } - '/redirect/$target/serverFn/': { - id: '/redirect/$target/serverFn/' - path: '/serverFn' - fullPath: '/redirect/$target/serverFn' - preLoaderRoute: typeof RedirectTargetServerFnIndexRouteImport + '/redirect/$target/serverFn/via-loader': { + id: '/redirect/$target/serverFn/via-loader' + path: '/serverFn/via-loader' + fullPath: '/redirect/$target/serverFn/via-loader' + preLoaderRoute: typeof RedirectTargetServerFnViaLoaderRouteImport parentRoute: typeof RedirectTargetRoute } + '/redirect/$target/serverFn/via-beforeLoad': { + id: '/redirect/$target/serverFn/via-beforeLoad' + path: '/serverFn/via-beforeLoad' + fullPath: '/redirect/$target/serverFn/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetServerFnViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute '/foo/$bar/$qux/_here/': { id: '/foo/$bar/$qux/_here/' path: '/' @@ -1049,43 +1038,15 @@ declare module '@tanstack/react-start/server' { id: '/foo/$bar/$qux/_here' path: '/foo/$bar/$qux' fullPath: '/foo/$bar/$qux' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport - } - '/redirect/$target/serverFn/via-beforeLoad': { - id: '/redirect/$target/serverFn/via-beforeLoad' - path: '/serverFn/via-beforeLoad' - fullPath: '/redirect/$target/serverFn/via-beforeLoad' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport - } - '/redirect/$target/serverFn/via-loader': { - id: '/redirect/$target/serverFn/via-loader' - path: '/serverFn/via-loader' - fullPath: '/redirect/$target/serverFn/via-loader' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport - } - '/redirect/$target/serverFn/via-useServerFn': { - id: '/redirect/$target/serverFn/via-useServerFn' - path: '/serverFn/via-useServerFn' - fullPath: '/redirect/$target/serverFn/via-useServerFn' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport - } - '/redirect/$target/serverFn/': { - id: '/redirect/$target/serverFn/' - path: '/serverFn' - fullPath: '/redirect/$target/serverFn' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport + preLoaderRoute: typeof FooBarQuxHereRouteImport + parentRoute: typeof FooBarQuxRoute } '/foo/$bar/$qux/_here/': { id: '/foo/$bar/$qux/_here/' path: '/' fullPath: '/foo/$bar/$qux/' - preLoaderRoute: unknown - parentRoute: typeof rootServerRouteImport + preLoaderRoute: typeof FooBarQuxHereIndexRouteImport + parentRoute: typeof FooBarQuxHereRoute } } } @@ -1098,14 +1059,6 @@ declare module './routes/index' { FileRoutesByPath['/']['path'], FileRoutesByPath['/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/']['parentRoute'], - ServerFileRoutesByPath['/']['id'], - ServerFileRoutesByPath['/']['path'], - ServerFileRoutesByPath['/']['fullPath'], - unknown - > } declare module './routes/not-found/route' { const createFileRoute: CreateFileRoute< @@ -1115,14 +1068,6 @@ declare module './routes/not-found/route' { FileRoutesByPath['/not-found']['path'], FileRoutesByPath['/not-found']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/not-found']['parentRoute'], - ServerFileRoutesByPath['/not-found']['id'], - ServerFileRoutesByPath['/not-found']['path'], - ServerFileRoutesByPath['/not-found']['fullPath'], - unknown - > } declare module './routes/search-params/route' { const createFileRoute: CreateFileRoute< @@ -1132,14 +1077,6 @@ declare module './routes/search-params/route' { FileRoutesByPath['/search-params']['path'], FileRoutesByPath['/search-params']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/search-params']['parentRoute'], - ServerFileRoutesByPath['/search-params']['id'], - ServerFileRoutesByPath['/search-params']['path'], - ServerFileRoutesByPath['/search-params']['fullPath'], - unknown - > } declare module './routes/_layout' { const createFileRoute: CreateFileRoute< @@ -1149,14 +1086,6 @@ declare module './routes/_layout' { FileRoutesByPath['/_layout']['path'], FileRoutesByPath['/_layout']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/_layout']['parentRoute'], - ServerFileRoutesByPath['/_layout']['id'], - ServerFileRoutesByPath['/_layout']['path'], - ServerFileRoutesByPath['/_layout']['fullPath'], - unknown - > } declare module './routes/deferred' { const createFileRoute: CreateFileRoute< @@ -1200,14 +1129,6 @@ declare module './routes/links' { FileRoutesByPath['/links']['path'], FileRoutesByPath['/links']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/links']['parentRoute'], - ServerFileRoutesByPath['/links']['id'], - ServerFileRoutesByPath['/links']['path'], - ServerFileRoutesByPath['/links']['fullPath'], - unknown - > } declare module './routes/posts' { const createFileRoute: CreateFileRoute< @@ -1217,14 +1138,6 @@ declare module './routes/posts' { FileRoutesByPath['/posts']['path'], FileRoutesByPath['/posts']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/posts']['parentRoute'], - ServerFileRoutesByPath['/posts']['id'], - ServerFileRoutesByPath['/posts']['path'], - ServerFileRoutesByPath['/posts']['fullPath'], - unknown - > } declare module './routes/scripts' { const createFileRoute: CreateFileRoute< @@ -1234,14 +1147,6 @@ declare module './routes/scripts' { FileRoutesByPath['/scripts']['path'], FileRoutesByPath['/scripts']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/scripts']['parentRoute'], - ServerFileRoutesByPath['/scripts']['id'], - ServerFileRoutesByPath['/scripts']['path'], - ServerFileRoutesByPath['/scripts']['fullPath'], - unknown - > } declare module './routes/stream' { const createFileRoute: CreateFileRoute< @@ -1251,14 +1156,6 @@ declare module './routes/stream' { FileRoutesByPath['/stream']['path'], FileRoutesByPath['/stream']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/stream']['parentRoute'], - ServerFileRoutesByPath['/stream']['id'], - ServerFileRoutesByPath['/stream']['path'], - ServerFileRoutesByPath['/stream']['fullPath'], - unknown - > } declare module './routes/users' { const createFileRoute: CreateFileRoute< @@ -1268,14 +1165,6 @@ declare module './routes/users' { FileRoutesByPath['/users']['path'], FileRoutesByPath['/users']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/users']['parentRoute'], - ServerFileRoutesByPath['/users']['id'], - ServerFileRoutesByPath['/users']['path'], - ServerFileRoutesByPath['/users']['fullPath'], - unknown - > } declare module './routes/대한민국' { const createFileRoute: CreateFileRoute< @@ -1285,14 +1174,6 @@ declare module './routes/대한민국' { FileRoutesByPath['/대한민국']['path'], FileRoutesByPath['/대한민국']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/대한민국']['parentRoute'], - ServerFileRoutesByPath['/대한민국']['id'], - ServerFileRoutesByPath['/대한민국']['path'], - ServerFileRoutesByPath['/대한민국']['fullPath'], - unknown - > } declare module './routes/_layout/_layout-2' { const createFileRoute: CreateFileRoute< @@ -1302,14 +1183,6 @@ declare module './routes/_layout/_layout-2' { FileRoutesByPath['/_layout/_layout-2']['path'], FileRoutesByPath['/_layout/_layout-2']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/_layout/_layout-2']['parentRoute'], - ServerFileRoutesByPath['/_layout/_layout-2']['id'], - ServerFileRoutesByPath['/_layout/_layout-2']['path'], - ServerFileRoutesByPath['/_layout/_layout-2']['fullPath'], - unknown - > } declare module './routes/api.users' { const createFileRoute: CreateFileRoute< @@ -1319,14 +1192,6 @@ declare module './routes/api.users' { FileRoutesByPath['/api/users']['path'], FileRoutesByPath['/api/users']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/api/users']['parentRoute'], - ServerFileRoutesByPath['/api/users']['id'], - ServerFileRoutesByPath['/api/users']['path'], - ServerFileRoutesByPath['/api/users']['fullPath'], - unknown - > } declare module './routes/not-found/via-beforeLoad' { const createFileRoute: CreateFileRoute< @@ -1336,14 +1201,6 @@ declare module './routes/not-found/via-beforeLoad' { FileRoutesByPath['/not-found/via-beforeLoad']['path'], FileRoutesByPath['/not-found/via-beforeLoad']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/not-found/via-beforeLoad']['parentRoute'], - ServerFileRoutesByPath['/not-found/via-beforeLoad']['id'], - ServerFileRoutesByPath['/not-found/via-beforeLoad']['path'], - ServerFileRoutesByPath['/not-found/via-beforeLoad']['fullPath'], - unknown - > } declare module './routes/not-found/via-loader' { const createFileRoute: CreateFileRoute< @@ -1353,14 +1210,6 @@ declare module './routes/not-found/via-loader' { FileRoutesByPath['/not-found/via-loader']['path'], FileRoutesByPath['/not-found/via-loader']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/not-found/via-loader']['parentRoute'], - ServerFileRoutesByPath['/not-found/via-loader']['id'], - ServerFileRoutesByPath['/not-found/via-loader']['path'], - ServerFileRoutesByPath['/not-found/via-loader']['fullPath'], - unknown - > } declare module './routes/posts.$postId' { const createFileRoute: CreateFileRoute< @@ -1370,14 +1219,6 @@ declare module './routes/posts.$postId' { FileRoutesByPath['/posts/$postId']['path'], FileRoutesByPath['/posts/$postId']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/posts/$postId']['parentRoute'], - ServerFileRoutesByPath['/posts/$postId']['id'], - ServerFileRoutesByPath['/posts/$postId']['path'], - ServerFileRoutesByPath['/posts/$postId']['fullPath'], - unknown - > } declare module './routes/redirect/$target' { const createFileRoute: CreateFileRoute< @@ -1387,14 +1228,6 @@ declare module './routes/redirect/$target' { FileRoutesByPath['/redirect/$target']['path'], FileRoutesByPath['/redirect/$target']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target']['id'], - ServerFileRoutesByPath['/redirect/$target']['path'], - ServerFileRoutesByPath['/redirect/$target']['fullPath'], - unknown - > } declare module './routes/search-params/default' { const createFileRoute: CreateFileRoute< @@ -1404,14 +1237,6 @@ declare module './routes/search-params/default' { FileRoutesByPath['/search-params/default']['path'], FileRoutesByPath['/search-params/default']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/search-params/default']['parentRoute'], - ServerFileRoutesByPath['/search-params/default']['id'], - ServerFileRoutesByPath['/search-params/default']['path'], - ServerFileRoutesByPath['/search-params/default']['fullPath'], - unknown - > } declare module './routes/search-params/loader-throws-redirect' { const createFileRoute: CreateFileRoute< @@ -1421,14 +1246,6 @@ declare module './routes/search-params/loader-throws-redirect' { FileRoutesByPath['/search-params/loader-throws-redirect']['path'], FileRoutesByPath['/search-params/loader-throws-redirect']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/search-params/loader-throws-redirect']['parentRoute'], - ServerFileRoutesByPath['/search-params/loader-throws-redirect']['id'], - ServerFileRoutesByPath['/search-params/loader-throws-redirect']['path'], - ServerFileRoutesByPath['/search-params/loader-throws-redirect']['fullPath'], - unknown - > } declare module './routes/users.$userId' { const createFileRoute: CreateFileRoute< @@ -1438,14 +1255,6 @@ declare module './routes/users.$userId' { FileRoutesByPath['/users/$userId']['path'], FileRoutesByPath['/users/$userId']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/users/$userId']['parentRoute'], - ServerFileRoutesByPath['/users/$userId']['id'], - ServerFileRoutesByPath['/users/$userId']['path'], - ServerFileRoutesByPath['/users/$userId']['fullPath'], - unknown - > } declare module './routes/not-found/index' { const createFileRoute: CreateFileRoute< @@ -1455,14 +1264,6 @@ declare module './routes/not-found/index' { FileRoutesByPath['/not-found/']['path'], FileRoutesByPath['/not-found/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/not-found/']['parentRoute'], - ServerFileRoutesByPath['/not-found/']['id'], - ServerFileRoutesByPath['/not-found/']['path'], - ServerFileRoutesByPath['/not-found/']['fullPath'], - unknown - > } declare module './routes/posts.index' { const createFileRoute: CreateFileRoute< @@ -1472,14 +1273,6 @@ declare module './routes/posts.index' { FileRoutesByPath['/posts/']['path'], FileRoutesByPath['/posts/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/posts/']['parentRoute'], - ServerFileRoutesByPath['/posts/']['id'], - ServerFileRoutesByPath['/posts/']['path'], - ServerFileRoutesByPath['/posts/']['fullPath'], - unknown - > } declare module './routes/redirect/index' { const createFileRoute: CreateFileRoute< @@ -1489,14 +1282,6 @@ declare module './routes/redirect/index' { FileRoutesByPath['/redirect/']['path'], FileRoutesByPath['/redirect/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/']['parentRoute'], - ServerFileRoutesByPath['/redirect/']['id'], - ServerFileRoutesByPath['/redirect/']['path'], - ServerFileRoutesByPath['/redirect/']['fullPath'], - unknown - > } declare module './routes/search-params/index' { const createFileRoute: CreateFileRoute< @@ -1506,14 +1291,6 @@ declare module './routes/search-params/index' { FileRoutesByPath['/search-params/']['path'], FileRoutesByPath['/search-params/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/search-params/']['parentRoute'], - ServerFileRoutesByPath['/search-params/']['id'], - ServerFileRoutesByPath['/search-params/']['path'], - ServerFileRoutesByPath['/search-params/']['fullPath'], - unknown - > } declare module './routes/users.index' { const createFileRoute: CreateFileRoute< @@ -1523,14 +1300,6 @@ declare module './routes/users.index' { FileRoutesByPath['/users/']['path'], FileRoutesByPath['/users/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/users/']['parentRoute'], - ServerFileRoutesByPath['/users/']['id'], - ServerFileRoutesByPath['/users/']['path'], - ServerFileRoutesByPath['/users/']['fullPath'], - unknown - > } declare module './routes/_layout/_layout-2/layout-a' { const createFileRoute: CreateFileRoute< @@ -1540,14 +1309,6 @@ declare module './routes/_layout/_layout-2/layout-a' { FileRoutesByPath['/_layout/_layout-2/layout-a']['path'], FileRoutesByPath['/_layout/_layout-2/layout-a']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/_layout/_layout-2/layout-a']['parentRoute'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-a']['id'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-a']['path'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-a']['fullPath'], - unknown - > } declare module './routes/_layout/_layout-2/layout-b' { const createFileRoute: CreateFileRoute< @@ -1557,14 +1318,6 @@ declare module './routes/_layout/_layout-2/layout-b' { FileRoutesByPath['/_layout/_layout-2/layout-b']['path'], FileRoutesByPath['/_layout/_layout-2/layout-b']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/_layout/_layout-2/layout-b']['parentRoute'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-b']['id'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-b']['path'], - ServerFileRoutesByPath['/_layout/_layout-2/layout-b']['fullPath'], - unknown - > } declare module './routes/api/users.$id' { const createFileRoute: CreateFileRoute< @@ -1574,14 +1327,6 @@ declare module './routes/api/users.$id' { FileRoutesByPath['/api/users/$id']['path'], FileRoutesByPath['/api/users/$id']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/api/users/$id']['parentRoute'], - ServerFileRoutesByPath['/api/users/$id']['id'], - ServerFileRoutesByPath['/api/users/$id']['path'], - ServerFileRoutesByPath['/api/users/$id']['fullPath'], - unknown - > } declare module './routes/posts_.$postId.deep' { const createFileRoute: CreateFileRoute< @@ -1591,14 +1336,6 @@ declare module './routes/posts_.$postId.deep' { FileRoutesByPath['/posts_/$postId/deep']['path'], FileRoutesByPath['/posts_/$postId/deep']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/posts_/$postId/deep']['parentRoute'], - ServerFileRoutesByPath['/posts_/$postId/deep']['id'], - ServerFileRoutesByPath['/posts_/$postId/deep']['path'], - ServerFileRoutesByPath['/posts_/$postId/deep']['fullPath'], - unknown - > } declare module './routes/redirect/$target/via-beforeLoad' { const createFileRoute: CreateFileRoute< @@ -1608,14 +1345,6 @@ declare module './routes/redirect/$target/via-beforeLoad' { FileRoutesByPath['/redirect/$target/via-beforeLoad']['path'], FileRoutesByPath['/redirect/$target/via-beforeLoad']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/via-beforeLoad']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/via-beforeLoad']['id'], - ServerFileRoutesByPath['/redirect/$target/via-beforeLoad']['path'], - ServerFileRoutesByPath['/redirect/$target/via-beforeLoad']['fullPath'], - unknown - > } declare module './routes/redirect/$target/via-loader' { const createFileRoute: CreateFileRoute< @@ -1625,14 +1354,6 @@ declare module './routes/redirect/$target/via-loader' { FileRoutesByPath['/redirect/$target/via-loader']['path'], FileRoutesByPath['/redirect/$target/via-loader']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/via-loader']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/via-loader']['id'], - ServerFileRoutesByPath['/redirect/$target/via-loader']['path'], - ServerFileRoutesByPath['/redirect/$target/via-loader']['fullPath'], - unknown - > } declare module './routes/redirect/$target/index' { const createFileRoute: CreateFileRoute< @@ -1642,14 +1363,6 @@ declare module './routes/redirect/$target/index' { FileRoutesByPath['/redirect/$target/']['path'], FileRoutesByPath['/redirect/$target/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/']['id'], - ServerFileRoutesByPath['/redirect/$target/']['path'], - ServerFileRoutesByPath['/redirect/$target/']['fullPath'], - unknown - > } declare module './routes/foo/$bar/$qux/_here' { const createFileRoute: CreateFileRoute< @@ -1659,14 +1372,6 @@ declare module './routes/foo/$bar/$qux/_here' { FileRoutesByPath['/foo/$bar/$qux/_here']['path'], FileRoutesByPath['/foo/$bar/$qux/_here']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/foo/$bar/$qux/_here']['parentRoute'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here']['id'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here']['path'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here']['fullPath'], - unknown - > } declare module './routes/redirect/$target/serverFn/via-beforeLoad' { const createFileRoute: CreateFileRoute< @@ -1676,14 +1381,6 @@ declare module './routes/redirect/$target/serverFn/via-beforeLoad' { FileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['path'], FileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['id'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['path'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-beforeLoad']['fullPath'], - unknown - > } declare module './routes/redirect/$target/serverFn/via-loader' { const createFileRoute: CreateFileRoute< @@ -1693,14 +1390,6 @@ declare module './routes/redirect/$target/serverFn/via-loader' { FileRoutesByPath['/redirect/$target/serverFn/via-loader']['path'], FileRoutesByPath['/redirect/$target/serverFn/via-loader']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/serverFn/via-loader']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-loader']['id'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-loader']['path'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-loader']['fullPath'], - unknown - > } declare module './routes/redirect/$target/serverFn/via-useServerFn' { const createFileRoute: CreateFileRoute< @@ -1710,14 +1399,6 @@ declare module './routes/redirect/$target/serverFn/via-useServerFn' { FileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['path'], FileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['id'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['path'], - ServerFileRoutesByPath['/redirect/$target/serverFn/via-useServerFn']['fullPath'], - unknown - > } declare module './routes/redirect/$target/serverFn/index' { const createFileRoute: CreateFileRoute< @@ -1727,14 +1408,6 @@ declare module './routes/redirect/$target/serverFn/index' { FileRoutesByPath['/redirect/$target/serverFn/']['path'], FileRoutesByPath['/redirect/$target/serverFn/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/redirect/$target/serverFn/']['parentRoute'], - ServerFileRoutesByPath['/redirect/$target/serverFn/']['id'], - ServerFileRoutesByPath['/redirect/$target/serverFn/']['path'], - ServerFileRoutesByPath['/redirect/$target/serverFn/']['fullPath'], - unknown - > } declare module './routes/foo/$bar/$qux/_here/index' { const createFileRoute: CreateFileRoute< @@ -1744,14 +1417,6 @@ declare module './routes/foo/$bar/$qux/_here/index' { FileRoutesByPath['/foo/$bar/$qux/_here/']['path'], FileRoutesByPath['/foo/$bar/$qux/_here/']['fullPath'] > - - const createServerFileRoute: CreateServerFileRoute< - ServerFileRoutesByPath['/foo/$bar/$qux/_here/']['parentRoute'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here/']['id'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here/']['path'], - ServerFileRoutesByPath['/foo/$bar/$qux/_here/']['fullPath'], - unknown - > } interface NotFoundRouteRouteChildren { @@ -1834,6 +1499,18 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) +interface ApiUsersRouteChildren { + ApiUsersIdRoute: typeof ApiUsersIdRoute +} + +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersIdRoute: ApiUsersIdRoute, +} + +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, +) + interface RedirectTargetRouteChildren { RedirectTargetViaBeforeLoadRoute: typeof RedirectTargetViaBeforeLoadRoute RedirectTargetViaLoaderRoute: typeof RedirectTargetViaLoaderRoute @@ -1884,18 +1561,6 @@ const FooBarQuxRouteWithChildren = FooBarQuxRoute._addFileChildren( FooBarQuxRouteChildren, ) -interface ApiUsersServerRouteChildren { - ApiUsersIdServerRoute: typeof ApiUsersIdServerRoute -} - -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersIdServerRoute: ApiUsersIdServerRoute, -} - -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, -) - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, NotFoundRouteRoute: NotFoundRouteRouteWithChildren, @@ -1910,6 +1575,7 @@ const rootRouteChildren: RootRouteChildren = { UsersRoute: UsersRouteWithChildren, Char45824Char54620Char48124Char44397Route: Char45824Char54620Char48124Char44397Route, + ApiUsersRoute: ApiUsersRouteWithChildren, RedirectTargetRoute: RedirectTargetRouteWithChildren, RedirectIndexRoute: RedirectIndexRoute, PostsPostIdDeepRoute: PostsPostIdDeepRoute, @@ -1918,9 +1584,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/react-start/basic/src/router.tsx b/e2e/react-start/basic/src/router.tsx index c76eb0210cc..fef35c9e067 100644 --- a/e2e/react-start/basic/src/router.tsx +++ b/e2e/react-start/basic/src/router.tsx @@ -1,22 +1,16 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, + scrollRestoration: true, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, defaultNotFoundComponent: () => , - scrollRestoration: true, }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/basic/src/routes/api.users.ts b/e2e/react-start/basic/src/routes/api.users.ts index 6a27f556c62..a760bd201ee 100644 --- a/e2e/react-start/basic/src/routes/api.users.ts +++ b/e2e/react-start/basic/src/routes/api.users.ts @@ -9,13 +9,19 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await axios.get>(`${queryURL}/users`) +export const Route = createFileRoute({ + server: { + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>(`${queryURL}/users`) - const list = res.data.slice(0, 10) + const list = res.data.slice(0, 10) - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, }, }) diff --git a/e2e/react-start/basic/src/routes/api/users.$id.ts b/e2e/react-start/basic/src/routes/api/users.$id.ts index 4da7586562f..41338898447 100644 --- a/e2e/react-start/basic/src/routes/api/users.$id.ts +++ b/e2e/react-start/basic/src/routes/api/users.$id.ts @@ -8,20 +8,24 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute().methods({ - GET: async ({ request, params }) => { - console.info(`Fetching users by id=${params.id}... @`, request.url) - try { - const res = await axios.get(`${queryURL}/users/` + params.id) +export const Route = createFileRoute({ + server: { + handlers: { + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get(`${queryURL}/users/` + params.id) - return json({ - id: res.data.id, - name: res.data.name, - email: res.data.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/e2e/react-start/basic/src/routes/deferred.tsx b/e2e/react-start/basic/src/routes/deferred.tsx index 6b128efea0d..b8c050d0965 100644 --- a/e2e/react-start/basic/src/routes/deferred.tsx +++ b/e2e/react-start/basic/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/react-start' import { Suspense, useState } from 'react' const personServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(({ data }) => { return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(async ({ data }) => { await new Promise((r) => setTimeout(r, 1000)) return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/e2e/react-start/basic/src/server.ts b/e2e/react-start/basic/src/server.ts index 77cb4a13169..00d13b4d178 100644 --- a/e2e/react-start/basic/src/server.ts +++ b/e2e/react-start/basic/src/server.ts @@ -1,13 +1,11 @@ // DO NOT DELETE THIS FILE!!! // This file is a good smoke test to make sure the custom server entry is working -import { - createStartHandler, - defaultStreamHandler, -} from '@tanstack/react-start/server' -import { createRouter } from './router' +import handler from '@tanstack/react-start/server-entry' console.log("[server-entry]: using custom server entry in 'src/server.ts'") -export default createStartHandler({ - createRouter, -})(defaultStreamHandler) +export default { + fetch(request: Request) { + return handler.fetch(request) + }, +} diff --git a/e2e/react-start/basic/src/utils/posts.tsx b/e2e/react-start/basic/src/utils/posts.tsx index 4cc5dcc4c6a..b2d9f3edecf 100644 --- a/e2e/react-start/basic/src/utils/posts.tsx +++ b/e2e/react-start/basic/src/utils/posts.tsx @@ -15,7 +15,7 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/basic/tests/fixture.ts b/e2e/react-start/basic/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/basic/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/basic/tests/navigation.spec.ts b/e2e/react-start/basic/tests/navigation.spec.ts index 62433a0134d..c067581c0cc 100644 --- a/e2e/react-start/basic/tests/navigation.spec.ts +++ b/e2e/react-start/basic/tests/navigation.spec.ts @@ -1,5 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' + +test.use({ + whitelistErrors: [ + /Failed to load resource: the server responded with a status of 404/, + ], +}) test('Navigating to post', async ({ page }) => { await page.goto('/') diff --git a/e2e/react-start/basic/tests/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts index 0d83ab52802..ab1c94eee90 100644 --- a/e2e/react-start/basic/tests/not-found.spec.ts +++ b/e2e/react-start/basic/tests/not-found.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' import combinateImport from 'combinate' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport diff --git a/e2e/react-start/basic/tests/params.spec.ts b/e2e/react-start/basic/tests/params.spec.ts index b22c3e26554..737c82f35d2 100644 --- a/e2e/react-start/basic/tests/params.spec.ts +++ b/e2e/react-start/basic/tests/params.spec.ts @@ -1,9 +1,16 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' + +import { test } from '@tanstack/router-e2e-utils' test.beforeEach(async ({ page }) => { await page.goto('/') }) +test.use({ + whitelistErrors: [ + /Failed to load resource: the server responded with a status of 404/, + ], +}) test.describe('Unicode route rendering', () => { test('should render non-latin route correctly', async ({ page, baseURL }) => { await page.goto('/대한민국') diff --git a/e2e/react-start/basic/tests/redirect.spec.ts b/e2e/react-start/basic/tests/redirect.spec.ts index 87e93a42e6b..78c57d6a00b 100644 --- a/e2e/react-start/basic/tests/redirect.spec.ts +++ b/e2e/react-start/basic/tests/redirect.spec.ts @@ -4,9 +4,9 @@ import combinateImport from 'combinate' import { getDummyServerPort, getTestServerPort, + test, } from '@tanstack/router-e2e-utils' import packageJson from '../package.json' with { type: 'json' } -import { test } from './fixture' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport @@ -184,6 +184,8 @@ test.describe('redirects', () => { await page.goto(`/redirect/${target}/serverFn/via-useServerFn?${q}`) + await page.waitForLoadState('networkidle') + const button = page.getByTestId('redirect-on-click') let fullPageLoad = false diff --git a/e2e/react-start/basic/tests/search-params.spec.ts b/e2e/react-start/basic/tests/search-params.spec.ts index 23cd97517d6..d7ce6178900 100644 --- a/e2e/react-start/basic/tests/search-params.spec.ts +++ b/e2e/react-start/basic/tests/search-params.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' import type { Response } from '@playwright/test' function expectRedirect(response: Response | null, endsWith: string) { diff --git a/e2e/react-start/basic/tests/streaming.spec.ts b/e2e/react-start/basic/tests/streaming.spec.ts index 252bb192aaf..15f60b7deb9 100644 --- a/e2e/react-start/basic/tests/streaming.spec.ts +++ b/e2e/react-start/basic/tests/streaming.spec.ts @@ -1,4 +1,5 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' test('Navigating to deferred route', async ({ page }) => { await page.goto('/') diff --git a/e2e/react-start/basic/vite.config.ts b/e2e/react-start/basic/vite.config.ts index 54430698260..0a38b8adb01 100644 --- a/e2e/react-start/basic/vite.config.ts +++ b/e2e/react-start/basic/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -10,6 +11,8 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({ tsr: { verboseFileRoutes: false } }), + // @ts-ignore we want to keep one test with verboseFileRoutes off even though the option is hidden + tanstackStart({ router: { verboseFileRoutes: false } }), + viteReact(), ], }) diff --git a/e2e/react-start/clerk-basic/package.disabled.json b/e2e/react-start/clerk-basic/package.disabled.json index d819677f464..43d26930a6a 100644 --- a/e2e/react-start/clerk-basic/package.disabled.json +++ b/e2e/react-start/clerk-basic/package.disabled.json @@ -11,7 +11,7 @@ "test:e2e": "exit 0; rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { - "@clerk/tanstack-react-start": "^0.12.0", + "@clerk/tanstack-react-start": "^0.19.0", "@tanstack/react-router": "workspace:^", "@tanstack/react-router-devtools": "workspace:^", "@tanstack/react-start": "workspace:^", @@ -19,7 +19,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@playwright/test": "^1.50.1", diff --git a/e2e/react-start/clerk-basic/src/routeTree.gen.ts b/e2e/react-start/clerk-basic/src/routeTree.gen.ts index 00f53c86239..8ac0e6e8c32 100644 --- a/e2e/react-start/clerk-basic/src/routeTree.gen.ts +++ b/e2e/react-start/clerk-basic/src/routeTree.gen.ts @@ -8,11 +8,7 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import type { CreateFileRoute, FileRoutesByPath } from '@tanstack/react-router' - -// Import Routes - -import { Route as rootRoute } from './routes/__root' +import { Route as rootRouteImport } from './routes/__root' import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' @@ -20,151 +16,125 @@ import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.ind import { Route as AuthedProfileSplatRouteImport } from './routes/_authed/profile.$' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -// Create/Update Routes - const AuthedRoute = AuthedRouteImport.update({ id: '/_authed', - getParentRoute: () => rootRoute, + getParentRoute: () => rootRouteImport, } as any) - const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRoute, + getParentRoute: () => rootRouteImport, } as any) - const AuthedPostsRoute = AuthedPostsRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => AuthedRoute, } as any) - const AuthedPostsIndexRoute = AuthedPostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => AuthedPostsRoute, } as any) - const AuthedProfileSplatRoute = AuthedProfileSplatRouteImport.update({ id: '/profile/$', path: '/profile/$', getParentRoute: () => AuthedRoute, } as any) - const AuthedPostsPostIdRoute = AuthedPostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', getParentRoute: () => AuthedPostsRoute, } as any) -// Populate the FileRoutesByPath interface +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/posts': typeof AuthedPostsRouteWithChildren + '/posts/$postId': typeof AuthedPostsPostIdRoute + '/profile/$': typeof AuthedProfileSplatRoute + '/posts/': typeof AuthedPostsIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/posts/$postId': typeof AuthedPostsPostIdRoute + '/profile/$': typeof AuthedProfileSplatRoute + '/posts': typeof AuthedPostsIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/_authed': typeof AuthedRouteWithChildren + '/_authed/posts': typeof AuthedPostsRouteWithChildren + '/_authed/posts/$postId': typeof AuthedPostsPostIdRoute + '/_authed/profile/$': typeof AuthedProfileSplatRoute + '/_authed/posts/': typeof AuthedPostsIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/posts' | '/posts/$postId' | '/profile/$' | '/posts/' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/posts/$postId' | '/profile/$' | '/posts' + id: + | '__root__' + | '/' + | '/_authed' + | '/_authed/posts' + | '/_authed/posts/$postId' + | '/_authed/profile/$' + | '/_authed/posts/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AuthedRoute: typeof AuthedRouteWithChildren +} declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRoute - } '/_authed': { id: '/_authed' path: '' fullPath: '' preLoaderRoute: typeof AuthedRouteImport - parentRoute: typeof rootRoute + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport } '/_authed/posts': { id: '/_authed/posts' path: '/posts' fullPath: '/posts' preLoaderRoute: typeof AuthedPostsRouteImport - parentRoute: typeof AuthedRouteImport + parentRoute: typeof AuthedRoute } - '/_authed/posts/$postId': { - id: '/_authed/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof AuthedPostsPostIdRouteImport - parentRoute: typeof AuthedPostsRouteImport + '/_authed/posts/': { + id: '/_authed/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof AuthedPostsIndexRouteImport + parentRoute: typeof AuthedPostsRoute } '/_authed/profile/$': { id: '/_authed/profile/$' path: '/profile/$' fullPath: '/profile/$' preLoaderRoute: typeof AuthedProfileSplatRouteImport - parentRoute: typeof AuthedRouteImport + parentRoute: typeof AuthedRoute } - '/_authed/posts/': { - id: '/_authed/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof AuthedPostsIndexRouteImport - parentRoute: typeof AuthedPostsRouteImport + '/_authed/posts/$postId': { + id: '/_authed/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof AuthedPostsPostIdRouteImport + parentRoute: typeof AuthedPostsRoute } } } -// Add type-safety to the createFileRoute function across the route tree - -declare module './routes/index' { - const createFileRoute: CreateFileRoute< - '/', - FileRoutesByPath['/']['parentRoute'], - FileRoutesByPath['/']['id'], - FileRoutesByPath['/']['path'], - FileRoutesByPath['/']['fullPath'] - > -} -declare module './routes/_authed' { - const createFileRoute: CreateFileRoute< - '/_authed', - FileRoutesByPath['/_authed']['parentRoute'], - FileRoutesByPath['/_authed']['id'], - FileRoutesByPath['/_authed']['path'], - FileRoutesByPath['/_authed']['fullPath'] - > -} -declare module './routes/_authed/posts' { - const createFileRoute: CreateFileRoute< - '/_authed/posts', - FileRoutesByPath['/_authed/posts']['parentRoute'], - FileRoutesByPath['/_authed/posts']['id'], - FileRoutesByPath['/_authed/posts']['path'], - FileRoutesByPath['/_authed/posts']['fullPath'] - > -} -declare module './routes/_authed/posts.$postId' { - const createFileRoute: CreateFileRoute< - '/_authed/posts/$postId', - FileRoutesByPath['/_authed/posts/$postId']['parentRoute'], - FileRoutesByPath['/_authed/posts/$postId']['id'], - FileRoutesByPath['/_authed/posts/$postId']['path'], - FileRoutesByPath['/_authed/posts/$postId']['fullPath'] - > -} -declare module './routes/_authed/profile.$' { - const createFileRoute: CreateFileRoute< - '/_authed/profile/$', - FileRoutesByPath['/_authed/profile/$']['parentRoute'], - FileRoutesByPath['/_authed/profile/$']['id'], - FileRoutesByPath['/_authed/profile/$']['path'], - FileRoutesByPath['/_authed/profile/$']['fullPath'] - > -} -declare module './routes/_authed/posts.index' { - const createFileRoute: CreateFileRoute< - '/_authed/posts/', - FileRoutesByPath['/_authed/posts/']['parentRoute'], - FileRoutesByPath['/_authed/posts/']['id'], - FileRoutesByPath['/_authed/posts/']['path'], - FileRoutesByPath['/_authed/posts/']['fullPath'] - > -} - -// Create and export the route tree - interface AuthedPostsRouteChildren { AuthedPostsPostIdRoute: typeof AuthedPostsPostIdRoute AuthedPostsIndexRoute: typeof AuthedPostsIndexRoute @@ -192,103 +162,10 @@ const AuthedRouteChildren: AuthedRouteChildren = { const AuthedRouteWithChildren = AuthedRoute._addFileChildren(AuthedRouteChildren) -export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '': typeof AuthedRouteWithChildren - '/posts': typeof AuthedPostsRouteWithChildren - '/posts/$postId': typeof AuthedPostsPostIdRoute - '/profile/$': typeof AuthedProfileSplatRoute - '/posts/': typeof AuthedPostsIndexRoute -} - -export interface FileRoutesByTo { - '/': typeof IndexRoute - '': typeof AuthedRouteWithChildren - '/posts/$postId': typeof AuthedPostsPostIdRoute - '/profile/$': typeof AuthedProfileSplatRoute - '/posts': typeof AuthedPostsIndexRoute -} - -export interface FileRoutesById { - __root__: typeof rootRoute - '/': typeof IndexRoute - '/_authed': typeof AuthedRouteWithChildren - '/_authed/posts': typeof AuthedPostsRouteWithChildren - '/_authed/posts/$postId': typeof AuthedPostsPostIdRoute - '/_authed/profile/$': typeof AuthedProfileSplatRoute - '/_authed/posts/': typeof AuthedPostsIndexRoute -} - -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '' | '/posts' | '/posts/$postId' | '/profile/$' | '/posts/' - fileRoutesByTo: FileRoutesByTo - to: '/' | '' | '/posts/$postId' | '/profile/$' | '/posts' - id: - | '__root__' - | '/' - | '/_authed' - | '/_authed/posts' - | '/_authed/posts/$postId' - | '/_authed/profile/$' - | '/_authed/posts/' - fileRoutesById: FileRoutesById -} - -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - AuthedRoute: typeof AuthedRouteWithChildren -} - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AuthedRoute: AuthedRouteWithChildren, } - -export const routeTree = rootRoute +export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -/* ROUTE_MANIFEST_START -{ - "routes": { - "__root__": { - "filePath": "__root.tsx", - "children": [ - "/", - "/_authed" - ] - }, - "/": { - "filePath": "index.tsx" - }, - "/_authed": { - "filePath": "_authed.tsx", - "children": [ - "/_authed/posts", - "/_authed/profile/$" - ] - }, - "/_authed/posts": { - "filePath": "_authed/posts.tsx", - "parent": "/_authed", - "children": [ - "/_authed/posts/$postId", - "/_authed/posts/" - ] - }, - "/_authed/posts/$postId": { - "filePath": "_authed/posts.$postId.tsx", - "parent": "/_authed/posts" - }, - "/_authed/profile/$": { - "filePath": "_authed/profile.$.tsx", - "parent": "/_authed" - }, - "/_authed/posts/": { - "filePath": "_authed/posts.index.tsx", - "parent": "/_authed/posts" - } - } -} -ROUTE_MANIFEST_END */ diff --git a/e2e/react-start/clerk-basic/src/router.tsx b/e2e/react-start/clerk-basic/src/router.tsx index 5a1c0ad410e..1a1d8822d20 100644 --- a/e2e/react-start/clerk-basic/src/router.tsx +++ b/e2e/react-start/clerk-basic/src/router.tsx @@ -1,22 +1,16 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, - scrollRestoration: true, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, defaultNotFoundComponent: () => , + scrollRestoration: true, }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/clerk-basic/src/routes/__root.tsx b/e2e/react-start/clerk-basic/src/routes/__root.tsx index 51f8705b28a..f2a475c95f5 100644 --- a/e2e/react-start/clerk-basic/src/routes/__root.tsx +++ b/e2e/react-start/clerk-basic/src/routes/__root.tsx @@ -17,13 +17,13 @@ import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { createServerFn } from '@tanstack/react-start' import * as React from 'react' import { getAuth } from '@clerk/tanstack-react-start/server' -import { getWebRequest } from '@tanstack/react-start/server' +import { getRequest } from '@tanstack/react-start/server' import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary.js' import { NotFound } from '~/components/NotFound.js' import appCss from '~/styles/app.css?url' const fetchClerkAuth = createServerFn({ method: 'GET' }).handler(async () => { - const user = await getAuth(getWebRequest()!) + const user = await getAuth(getRequest()!) return { user, diff --git a/e2e/react-start/clerk-basic/src/routes/_authed.tsx b/e2e/react-start/clerk-basic/src/routes/_authed.tsx index 62429752724..226ae0829ff 100644 --- a/e2e/react-start/clerk-basic/src/routes/_authed.tsx +++ b/e2e/react-start/clerk-basic/src/routes/_authed.tsx @@ -1,6 +1,7 @@ +import { createFileRoute } from '@tanstack/react-router' import { SignIn } from '@clerk/tanstack-react-start' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_authed')({ beforeLoad: ({ context }) => { if (!context.user.userId) { throw new Error('Not authenticated') diff --git a/e2e/react-start/clerk-basic/src/routes/_authed/posts.$postId.tsx b/e2e/react-start/clerk-basic/src/routes/_authed/posts.$postId.tsx index 49cad9b3748..27296b96585 100644 --- a/e2e/react-start/clerk-basic/src/routes/_authed/posts.$postId.tsx +++ b/e2e/react-start/clerk-basic/src/routes/_authed/posts.$postId.tsx @@ -1,10 +1,10 @@ -import { ErrorComponent } from '@tanstack/react-router' +import { ErrorComponent, createFileRoute } from '@tanstack/react-router' import type { ErrorComponentProps } from '@tanstack/react-router' import { NotFound } from '~/components/NotFound.js' import { fetchPost } from '~/utils/posts.js' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_authed/posts/$postId')({ loader: ({ params: { postId } }) => fetchPost({ data: postId }), errorComponent: PostErrorComponent, component: PostComponent, diff --git a/e2e/react-start/clerk-basic/src/routes/_authed/posts.index.tsx b/e2e/react-start/clerk-basic/src/routes/_authed/posts.index.tsx index 13529228bb4..de01fe5ddef 100644 --- a/e2e/react-start/clerk-basic/src/routes/_authed/posts.index.tsx +++ b/e2e/react-start/clerk-basic/src/routes/_authed/posts.index.tsx @@ -1,4 +1,5 @@ -export const Route = createFileRoute({ +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/_authed/posts/')({ component: PostsIndexComponent, }) diff --git a/e2e/react-start/clerk-basic/src/routes/_authed/posts.tsx b/e2e/react-start/clerk-basic/src/routes/_authed/posts.tsx index 6c37957e8c2..c01f12c49ae 100644 --- a/e2e/react-start/clerk-basic/src/routes/_authed/posts.tsx +++ b/e2e/react-start/clerk-basic/src/routes/_authed/posts.tsx @@ -1,8 +1,8 @@ -import { Link, Outlet } from '@tanstack/react-router' +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' import { fetchPosts } from '~/utils/posts.js' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_authed/posts')({ loader: () => fetchPosts(), component: PostsComponent, }) diff --git a/e2e/react-start/clerk-basic/src/routes/_authed/profile.$.tsx b/e2e/react-start/clerk-basic/src/routes/_authed/profile.$.tsx index 6c37957e8c2..1e5a8b1429e 100644 --- a/e2e/react-start/clerk-basic/src/routes/_authed/profile.$.tsx +++ b/e2e/react-start/clerk-basic/src/routes/_authed/profile.$.tsx @@ -1,8 +1,8 @@ -import { Link, Outlet } from '@tanstack/react-router' +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' import { fetchPosts } from '~/utils/posts.js' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_authed/profile/$')({ loader: () => fetchPosts(), component: PostsComponent, }) diff --git a/e2e/react-start/clerk-basic/src/routes/index.tsx b/e2e/react-start/clerk-basic/src/routes/index.tsx index 66baf1e9f11..00ce18725b4 100644 --- a/e2e/react-start/clerk-basic/src/routes/index.tsx +++ b/e2e/react-start/clerk-basic/src/routes/index.tsx @@ -1,4 +1,5 @@ -export const Route = createFileRoute({ +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/')({ component: Home, }) diff --git a/e2e/react-start/clerk-basic/src/server.ts b/e2e/react-start/clerk-basic/src/server.ts new file mode 100644 index 00000000000..39afb5c85fe --- /dev/null +++ b/e2e/react-start/clerk-basic/src/server.ts @@ -0,0 +1,15 @@ +import { + createStartHandler, + defaultStreamHandler, +} from '@tanstack/react-start/server' +import { createClerkHandler } from '@clerk/tanstack-react-start/server' +import { createRouter } from './router' + +const startHandler = createStartHandler({ + createRouter, +}) +const fetch = createClerkHandler(startHandler)(defaultStreamHandler) + +export default { + fetch, +} diff --git a/e2e/react-start/clerk-basic/src/server.tsx b/e2e/react-start/clerk-basic/src/server.tsx deleted file mode 100644 index 2987216e24d..00000000000 --- a/e2e/react-start/clerk-basic/src/server.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { - createStartHandler, - defaultStreamHandler, - defineEventHandler, -} from '@tanstack/react-start/server' -import { createClerkHandler } from '@clerk/tanstack-react-start/server' -import { createRouter } from './router' - -export default defineEventHandler((event) => { - const startHandler = createStartHandler({ - createRouter, - }) - - const withClerkHandler = - createClerkHandler(startHandler)(defaultStreamHandler) - - return withClerkHandler(event) -}) diff --git a/e2e/react-start/clerk-basic/src/utils/posts.ts b/e2e/react-start/clerk-basic/src/utils/posts.ts index 532e883ac0c..b30810a5a3a 100644 --- a/e2e/react-start/clerk-basic/src/utils/posts.ts +++ b/e2e/react-start/clerk-basic/src/utils/posts.ts @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/clerk-basic/vite.config.ts b/e2e/react-start/clerk-basic/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/clerk-basic/vite.config.ts +++ b/e2e/react-start/clerk-basic/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/custom-basepath/express-server.ts b/e2e/react-start/custom-basepath/express-server.ts new file mode 100644 index 00000000000..6fa802e4476 --- /dev/null +++ b/e2e/react-start/custom-basepath/express-server.ts @@ -0,0 +1,44 @@ +import express from 'express' +import { toNodeHandler } from 'srvx/node' + +const DEVELOPMENT = process.env.NODE_ENV === 'development' +const PORT = Number.parseInt(process.env.PORT || '3000') + +const app = express() + +if (DEVELOPMENT) { + const viteDevServer = await import('vite').then((vite) => + vite.createServer({ + server: { middlewareMode: true }, + }), + ) + app.use(viteDevServer.middlewares) + app.use(async (req, res, next) => { + try { + const { default: serverEntry } = + await viteDevServer.ssrLoadModule('./src/server.ts') + const handler = toNodeHandler(serverEntry.fetch) + await handler(req, res) + } catch (error) { + if (typeof error === 'object' && error instanceof Error) { + viteDevServer.ssrFixStacktrace(error) + } + next(error) + } + }) +} else { + const { default: handler } = await import('./dist/server/server.js') + const nodeHandler = toNodeHandler(handler.fetch) + app.use('/custom/basepath', express.static('dist/client')) + app.use(async (req, res, next) => { + try { + await nodeHandler(req, res) + } catch (error) { + next(error) + } + }) +} + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`) +}) diff --git a/e2e/react-start/custom-basepath/package.json b/e2e/react-start/custom-basepath/package.json index ef1a13b2c68..9a89b00d26b 100644 --- a/e2e/react-start/custom-basepath/package.json +++ b/e2e/react-start/custom-basepath/package.json @@ -4,35 +4,36 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vite dev --port 3000", - "dev:e2e": "vite dev", + "dev": "cross-env NODE_ENV=development tsx express-server.ts", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "tsx express-server.ts", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { "@tanstack/react-router": "workspace:^", "@tanstack/react-router-devtools": "workspace:^", "@tanstack/react-start": "workspace:^", + "express": "^4.21.2", "react": "^19.0.0", "react-dom": "^19.0.0", - "redaxios": "^0.5.1", - "tailwind-merge": "^2.6.0", - "vite": "6.3.5", - "zod": "^3.24.2" + "redaxios": "^0.5.1" }, "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", + "@types/express": "^5.0.3", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", - "combinate": "^1.1.11", + "cross-env": "^10.0.0", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", + "tsx": "^4.20.3", "typescript": "^5.7.2", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/react-start/custom-basepath/src/routeTree.gen.ts b/e2e/react-start/custom-basepath/src/routeTree.gen.ts index 81100d418e9..0f66010092f 100644 --- a/e2e/react-start/custom-basepath/src/routeTree.gen.ts +++ b/e2e/react-start/custom-basepath/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as PostsRouteImport } from './routes/posts' @@ -20,11 +18,9 @@ import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api.users' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api.users' -import { ServerRoute as ApiUsersIdServerRouteImport } from './routes/api/users.$id' - -const rootServerRouteImport = createServerRootRoute() +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -71,20 +67,20 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersIdServerRoute = ApiUsersIdServerRouteImport.update({ +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ id: '/$id', path: '/$id', - getParentRoute: () => ApiUsersServerRoute, + getParentRoute: () => ApiUsersRoute, } as any) export interface FileRoutesByFullPath { @@ -93,20 +89,24 @@ export interface FileRoutesByFullPath { '/logout': typeof LogoutRoute '/posts': typeof PostsRouteWithChildren '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/deferred': typeof DeferredRoute '/logout': typeof LogoutRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { @@ -116,10 +116,12 @@ export interface FileRoutesById { '/logout': typeof LogoutRoute '/posts': typeof PostsRouteWithChildren '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { @@ -130,20 +132,24 @@ export interface FileRouteTypes { | '/logout' | '/posts' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' + | '/api/users/$id' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' | '/deferred' | '/logout' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' + | '/api/users/$id' | '/posts/$postId/deep' id: | '__root__' @@ -152,10 +158,12 @@ export interface FileRouteTypes { | '/logout' | '/posts' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' + | '/api/users/$id' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } @@ -165,32 +173,9 @@ export interface RootRouteChildren { LogoutRoute: typeof LogoutRoute PostsRoute: typeof PostsRouteWithChildren UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$id' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$id' - id: '__root__' | '/api/users' | '/api/users/$id' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -257,6 +242,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/posts_/$postId/deep': { id: '/posts_/$postId/deep' path: '/posts/$postId/deep' @@ -264,23 +256,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } - } -} -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } '/api/users/$id': { id: '/api/users/$id' path: '/$id' fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute } } } @@ -309,16 +290,16 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) -interface ApiUsersServerRouteChildren { - ApiUsersIdServerRoute: typeof ApiUsersIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersIdRoute: typeof ApiUsersIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersIdServerRoute: ApiUsersIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersIdRoute: ApiUsersIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { @@ -327,14 +308,17 @@ const rootRouteChildren: RootRouteChildren = { LogoutRoute: LogoutRoute, PostsRoute: PostsRouteWithChildren, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/react-start/custom-basepath/src/router.tsx b/e2e/react-start/custom-basepath/src/router.tsx index 418650e1124..81b4c31daa8 100644 --- a/e2e/react-start/custom-basepath/src/router.tsx +++ b/e2e/react-start/custom-basepath/src/router.tsx @@ -1,11 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' import { basepath } from './utils/basepath' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -16,9 +16,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/custom-basepath/src/routes/api.users.ts b/e2e/react-start/custom-basepath/src/routes/api.users.ts index fdb8d0067cb..a03076490b8 100644 --- a/e2e/react-start/custom-basepath/src/routes/api.users.ts +++ b/e2e/react-start/custom-basepath/src/routes/api.users.ts @@ -1,4 +1,4 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { json } from '@tanstack/react-start' import axios from 'redaxios' @@ -10,13 +10,19 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users').methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await axios.get>(`${queryURL}/users`) +export const Route = createFileRoute('/api/users')({ + server: { + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>(`${queryURL}/users`) - const list = res.data.slice(0, 10) + const list = res.data.slice(0, 10) - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, }, }) diff --git a/e2e/react-start/custom-basepath/src/routes/api/users.$id.ts b/e2e/react-start/custom-basepath/src/routes/api/users.$id.ts index e3f2a1c3884..5a2b9fe6216 100644 --- a/e2e/react-start/custom-basepath/src/routes/api/users.$id.ts +++ b/e2e/react-start/custom-basepath/src/routes/api/users.$id.ts @@ -1,7 +1,7 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' import { json } from '@tanstack/react-start' import axios from 'redaxios' import type { User } from '~/utils/users' +import { createFileRoute } from '@tanstack/react-router' let queryURL = 'https://jsonplaceholder.typicode.com' @@ -9,20 +9,24 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users/$id').methods({ - GET: async ({ request, params }) => { - console.info(`Fetching users by id=${params.id}... @`, request.url) - try { - const res = await axios.get(`${queryURL}/users/` + params.id) +export const Route = createFileRoute('/api/users/$id')({ + server: { + handlers: { + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get(`${queryURL}/users/` + params.id) - return json({ - id: res.data.id, - name: res.data.name, - email: res.data.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/e2e/react-start/custom-basepath/src/routes/deferred.tsx b/e2e/react-start/custom-basepath/src/routes/deferred.tsx index a6bd45f8c25..9c6e3064b88 100644 --- a/e2e/react-start/custom-basepath/src/routes/deferred.tsx +++ b/e2e/react-start/custom-basepath/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/react-start' import { Suspense, useState } from 'react' const personServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(({ data }) => { return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(async ({ data }) => { await new Promise((r) => setTimeout(r, 1000)) return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/e2e/react-start/custom-basepath/src/server.ts b/e2e/react-start/custom-basepath/src/server.ts new file mode 100644 index 00000000000..130fbb5a96e --- /dev/null +++ b/e2e/react-start/custom-basepath/src/server.ts @@ -0,0 +1,7 @@ +import handler from '@tanstack/react-start/server-entry' + +export default { + fetch(request: Request) { + return handler.fetch(request) + }, +} diff --git a/e2e/react-start/custom-basepath/src/utils/posts.tsx b/e2e/react-start/custom-basepath/src/utils/posts.tsx index 4cc5dcc4c6a..b2d9f3edecf 100644 --- a/e2e/react-start/custom-basepath/src/utils/posts.tsx +++ b/e2e/react-start/custom-basepath/src/utils/posts.tsx @@ -15,7 +15,7 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/custom-basepath/vite.config.ts b/e2e/react-start/custom-basepath/vite.config.ts index c304ad647b5..7e200dc0740 100644 --- a/e2e/react-start/custom-basepath/vite.config.ts +++ b/e2e/react-start/custom-basepath/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ base: '/custom/basepath', @@ -11,6 +12,9 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart(), + tanstackStart({ + vite: { installDevServerMiddleware: true }, + }), + viteReact(), ], }) diff --git a/e2e/react-start/query-integration/package.json b/e2e/react-start/query-integration/package.json index 7916c849f69..34080d78bfa 100644 --- a/e2e/react-start/query-integration/package.json +++ b/e2e/react-start/query-integration/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { diff --git a/e2e/react-start/query-integration/src/routeTree.gen.ts b/e2e/react-start/query-integration/src/routeTree.gen.ts index e98a780a1e6..434f4b1298d 100644 --- a/e2e/react-start/query-integration/src/routeTree.gen.ts +++ b/e2e/react-start/query-integration/src/routeTree.gen.ts @@ -120,3 +120,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/query-integration/src/router.tsx b/e2e/react-start/query-integration/src/router.tsx index e30458c549e..ca830a530b2 100644 --- a/e2e/react-start/query-integration/src/router.tsx +++ b/e2e/react-start/query-integration/src/router.tsx @@ -1,11 +1,11 @@ import { QueryClient } from '@tanstack/react-query' -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import { routeTree } from './routeTree.gen' -export function createRouter() { +export function getRouter() { const queryClient = new QueryClient() - const router = createTanStackRouter({ + const router = createRouter({ routeTree, context: { queryClient }, scrollRestoration: true, @@ -17,9 +17,3 @@ export function createRouter() { }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/query-integration/tests/app.spec.ts b/e2e/react-start/query-integration/tests/app.spec.ts index 5c78722d20f..b55f0db8012 100644 --- a/e2e/react-start/query-integration/tests/app.spec.ts +++ b/e2e/react-start/query-integration/tests/app.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' // if the query would not be streamed to the client, it would re-execute on the client // and thus cause a hydration mismatch since the query function returns 'client' when executed on the client diff --git a/e2e/react-start/query-integration/tests/fixture.ts b/e2e/react-start/query-integration/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/query-integration/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/scroll-restoration/package.json b/e2e/react-start/scroll-restoration/package.json index 6f9393c929d..f4b18512c1c 100644 --- a/e2e/react-start/scroll-restoration/package.json +++ b/e2e/react-start/scroll-restoration/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -19,7 +19,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -32,6 +32,7 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/react-start/scroll-restoration/src/routeTree.gen.ts b/e2e/react-start/scroll-restoration/src/routeTree.gen.ts index 3d33b466851..669a919a5af 100644 --- a/e2e/react-start/scroll-restoration/src/routeTree.gen.ts +++ b/e2e/react-start/scroll-restoration/src/routeTree.gen.ts @@ -116,3 +116,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/scroll-restoration/src/router.tsx b/e2e/react-start/scroll-restoration/src/router.tsx index 5a1c0ad410e..fef35c9e067 100644 --- a/e2e/react-start/scroll-restoration/src/router.tsx +++ b/e2e/react-start/scroll-restoration/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: 'intent', @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/scroll-restoration/vite.config.ts b/e2e/react-start/scroll-restoration/vite.config.ts index 5b91efb7515..33948491feb 100644 --- a/e2e/react-start/scroll-restoration/vite.config.ts +++ b/e2e/react-start/scroll-restoration/vite.config.ts @@ -1,7 +1,12 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ - plugins: [tsConfigPaths({ projects: ['./tsconfig.json'] }), tanstackStart()], + plugins: [ + tsConfigPaths({ projects: ['./tsconfig.json'] }), + tanstackStart(), + viteReact(), + ], }) diff --git a/e2e/react-start/selective-ssr/package.json b/e2e/react-start/selective-ssr/package.json index 995641c88f1..f7f29a329f4 100644 --- a/e2e/react-start/selective-ssr/package.json +++ b/e2e/react-start/selective-ssr/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -16,16 +16,18 @@ "@tanstack/react-start": "workspace:^", "react": "^19.0.0", "react-dom": "^19.0.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4", "zod": "^3.24.2" }, "devDependencies": { "@tanstack/router-e2e-utils": "workspace:^", + "@vitejs/plugin-react": "^4.3.4", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2" } diff --git a/e2e/react-start/selective-ssr/src/routeTree.gen.ts b/e2e/react-start/selective-ssr/src/routeTree.gen.ts index 675f91bd3f2..b2073da8ce1 100644 --- a/e2e/react-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/react-start/selective-ssr/src/routeTree.gen.ts @@ -101,3 +101,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/selective-ssr/src/router.tsx b/e2e/react-start/selective-ssr/src/router.tsx index 25729701a7b..82a730704ad 100644 --- a/e2e/react-start/selective-ssr/src/router.tsx +++ b/e2e/react-start/selective-ssr/src/router.tsx @@ -1,17 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/selective-ssr/tests/app.spec.ts b/e2e/react-start/selective-ssr/tests/app.spec.ts index b013dfb1694..aea216d5065 100644 --- a/e2e/react-start/selective-ssr/tests/app.spec.ts +++ b/e2e/react-start/selective-ssr/tests/app.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' const testCount = 7 diff --git a/e2e/react-start/selective-ssr/tests/fixture.ts b/e2e/react-start/selective-ssr/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/selective-ssr/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/selective-ssr/vite.config.ts b/e2e/react-start/selective-ssr/vite.config.ts index c3b722c89b2..04033010e8f 100644 --- a/e2e/react-start/selective-ssr/vite.config.ts +++ b/e2e/react-start/selective-ssr/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/serialization-adapters/.gitignore b/e2e/react-start/serialization-adapters/.gitignore new file mode 100644 index 00000000000..08eba9e7065 --- /dev/null +++ b/e2e/react-start/serialization-adapters/.gitignore @@ -0,0 +1,20 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +.output +/build/ +/api/ +/server/build +/public/build# Sentry Config File +.env.sentry-build-plugin +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + +count.txt diff --git a/e2e/react-start/serialization-adapters/.prettierignore b/e2e/react-start/serialization-adapters/.prettierignore new file mode 100644 index 00000000000..2be5eaa6ece --- /dev/null +++ b/e2e/react-start/serialization-adapters/.prettierignore @@ -0,0 +1,4 @@ +**/build +**/public +pnpm-lock.yaml +routeTree.gen.ts \ No newline at end of file diff --git a/e2e/react-start/serialization-adapters/package.json b/e2e/react-start/serialization-adapters/package.json new file mode 100644 index 00000000000..bb66301e8d5 --- /dev/null +++ b/e2e/react-start/serialization-adapters/package.json @@ -0,0 +1,34 @@ +{ + "name": "tanstack-react-start-e2e-serialization-adapters", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "dev:e2e": "vite dev", + "build": "vite build && tsc --noEmit", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", + "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/react-router-devtools": "workspace:^", + "@tanstack/react-start": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "vite": "^7.1.1", + "vite-tsconfig-paths": "^5.1.4", + "zod": "^3.24.2" + }, + "devDependencies": { + "@tanstack/router-e2e-utils": "workspace:^", + "@vitejs/plugin-react": "^4.3.4", + "@types/node": "^22.10.2", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "postcss": "^8.5.1", + "srvx": "^0.8.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2" + } +} diff --git a/e2e/react-start/serialization-adapters/playwright.config.ts b/e2e/react-start/serialization-adapters/playwright.config.ts new file mode 100644 index 00000000000..badd6db0eb3 --- /dev/null +++ b/e2e/react-start/serialization-adapters/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test' +import { getTestServerPort } from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +const PORT = await getTestServerPort(packageJson.name) +const baseURL = `http://localhost:${PORT}` +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + workers: 1, + + reporter: [['line']], + + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL, + }, + + webServer: { + command: `VITE_SERVER_PORT=${PORT} pnpm build && NODE_ENV=production PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/react-start/serialization-adapters/postcss.config.mjs b/e2e/react-start/serialization-adapters/postcss.config.mjs new file mode 100644 index 00000000000..2e7af2b7f1a --- /dev/null +++ b/e2e/react-start/serialization-adapters/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/e2e/react-start/serialization-adapters/src/CustomError.ts b/e2e/react-start/serialization-adapters/src/CustomError.ts new file mode 100644 index 00000000000..9fcf85b36fa --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/CustomError.ts @@ -0,0 +1,31 @@ +import { createSerializationAdapter } from '@tanstack/react-router' + +export class CustomError extends Error { + public foo: string + public bar: bigint + + constructor(message: string, options: { foo: string; bar: bigint }) { + super(message) + + Object.setPrototypeOf(this, new.target.prototype) + + this.name = this.constructor.name + this.foo = options.foo + this.bar = options.bar + } +} + +export const customErrorAdapter = createSerializationAdapter({ + key: 'custom-error', + test: (v) => v instanceof CustomError, + toSerializable: ({ message, foo, bar }) => { + return { + message, + foo, + bar, + } + }, + fromSerializable: ({ message, foo, bar }) => { + return new CustomError(message, { foo, bar }) + }, +}) diff --git a/e2e/react-start/serialization-adapters/src/data.tsx b/e2e/react-start/serialization-adapters/src/data.tsx new file mode 100644 index 00000000000..ea1922fedc8 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/data.tsx @@ -0,0 +1,127 @@ +import { createSerializationAdapter } from '@tanstack/react-router' + +export class Foo { + constructor(public value: string) {} +} + +export interface Car { + __type: 'car' + make: string + model: string + year: number + honk: () => { message: string; make: string; model: string; year: number } +} + +export function makeCar(opts: { + make: string + model: string + year: number +}): Car { + return { + ...opts, + __type: 'car', + honk: () => { + return { message: `Honk! Honk!`, ...opts } + }, + } +} + +export const fooAdapter = createSerializationAdapter({ + key: 'foo', + test: (value: any) => value instanceof Foo, + toSerializable: (foo) => foo.value, + fromSerializable: (value) => new Foo(value), +}) + +export const carAdapter = createSerializationAdapter({ + key: 'car', + test: (value: any): value is Car => + '__type' in (value as Car) && value.__type === 'car', + toSerializable: (car) => ({ + make: car.make, + model: car.model, + year: car.year, + }), + fromSerializable: (value: { make: string; model: string; year: number }) => + makeCar(value), +}) + +export function makeData() { + function makeFoo(suffix: string = '') { + return new Foo(typeof window === 'undefined' ? 'server' : 'client' + suffix) + } + return { + foo: { + singleInstance: makeFoo(), + array: [makeFoo('0'), makeFoo('1'), makeFoo('2')], + map: new Map([ + [0, makeFoo('0')], + [1, makeFoo('1')], + [2, makeFoo('2')], + ]), + mapOfArrays: new Map([ + [0, [makeFoo('0-a'), makeFoo('0-b')]], + [1, [makeFoo('1-a'), makeFoo('1-b')]], + [2, [makeFoo('2-a'), makeFoo('2-b')]], + ]), + }, + car: { + singleInstance: makeCar({ + make: 'Toyota', + model: 'Camry', + year: 2020, + }), + array: [ + makeCar({ make: 'Honda', model: 'Accord', year: 2019 }), + makeCar({ make: 'Ford', model: 'Mustang', year: 2021 }), + ], + map: new Map([ + [0, makeCar({ make: 'Chevrolet', model: 'Malibu', year: 2018 })], + [1, makeCar({ make: 'Nissan', model: 'Altima', year: 2020 })], + [2, makeCar({ make: 'Hyundai', model: 'Sonata', year: 2021 })], + ]), + mapOfArrays: new Map([ + [0, [makeCar({ make: 'Kia', model: 'Optima', year: 2019 })]], + [1, [makeCar({ make: 'Subaru', model: 'Legacy', year: 2020 })]], + [2, [makeCar({ make: 'Volkswagen', model: 'Passat', year: 2021 })]], + ]), + }, + } +} + +export function RenderData({ + id, + data, +}: { + id: string + data: ReturnType +}) { + const localData = makeData() + return ( +
+

Car

+

expected

+
+ {JSON.stringify({ + make: localData.car.singleInstance.make, + model: localData.car.singleInstance.model, + year: localData.car.singleInstance.year, + })} +
+

actual

+
+ {JSON.stringify({ + make: data.car.singleInstance.make, + model: data.car.singleInstance.model, + year: data.car.singleInstance.year, + })} +
+ Foo +
+ {JSON.stringify({ + value: data.foo.singleInstance.value, + })} +
+
+ ) +} diff --git a/e2e/react-start/serialization-adapters/src/routeTree.gen.ts b/e2e/react-start/serialization-adapters/src/routeTree.gen.ts new file mode 100644 index 00000000000..b48f3713bd5 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routeTree.gen.ts @@ -0,0 +1,132 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as SsrStreamRouteImport } from './routes/ssr/stream' +import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' +import { Route as ServerFunctionCustomErrorRouteImport } from './routes/server-function/custom-error' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const SsrStreamRoute = SsrStreamRouteImport.update({ + id: '/ssr/stream', + path: '/ssr/stream', + getParentRoute: () => rootRouteImport, +} as any) +const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ + id: '/ssr/data-only', + path: '/ssr/data-only', + getParentRoute: () => rootRouteImport, +} as any) +const ServerFunctionCustomErrorRoute = + ServerFunctionCustomErrorRouteImport.update({ + id: '/server-function/custom-error', + path: '/server-function/custom-error', + getParentRoute: () => rootRouteImport, + } as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/server-function/custom-error': typeof ServerFunctionCustomErrorRoute + '/ssr/data-only': typeof SsrDataOnlyRoute + '/ssr/stream': typeof SsrStreamRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/server-function/custom-error': typeof ServerFunctionCustomErrorRoute + '/ssr/data-only': typeof SsrDataOnlyRoute + '/ssr/stream': typeof SsrStreamRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/server-function/custom-error': typeof ServerFunctionCustomErrorRoute + '/ssr/data-only': typeof SsrDataOnlyRoute + '/ssr/stream': typeof SsrStreamRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/server-function/custom-error' + | '/ssr/data-only' + | '/ssr/stream' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/server-function/custom-error' | '/ssr/data-only' | '/ssr/stream' + id: + | '__root__' + | '/' + | '/server-function/custom-error' + | '/ssr/data-only' + | '/ssr/stream' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + ServerFunctionCustomErrorRoute: typeof ServerFunctionCustomErrorRoute + SsrDataOnlyRoute: typeof SsrDataOnlyRoute + SsrStreamRoute: typeof SsrStreamRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/ssr/stream': { + id: '/ssr/stream' + path: '/ssr/stream' + fullPath: '/ssr/stream' + preLoaderRoute: typeof SsrStreamRouteImport + parentRoute: typeof rootRouteImport + } + '/ssr/data-only': { + id: '/ssr/data-only' + path: '/ssr/data-only' + fullPath: '/ssr/data-only' + preLoaderRoute: typeof SsrDataOnlyRouteImport + parentRoute: typeof rootRouteImport + } + '/server-function/custom-error': { + id: '/server-function/custom-error' + path: '/server-function/custom-error' + fullPath: '/server-function/custom-error' + preLoaderRoute: typeof ServerFunctionCustomErrorRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + ServerFunctionCustomErrorRoute: ServerFunctionCustomErrorRoute, + SsrDataOnlyRoute: SsrDataOnlyRoute, + SsrStreamRoute: SsrStreamRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { startInstance } from './start.tsx' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + config: Awaited> + } +} diff --git a/e2e/react-start/serialization-adapters/src/router.tsx b/e2e/react-start/serialization-adapters/src/router.tsx new file mode 100644 index 00000000000..82a730704ad --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/router.tsx @@ -0,0 +1,11 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function getRouter() { + const router = createRouter({ + routeTree, + scrollRestoration: true, + }) + + return router +} diff --git a/e2e/react-start/serialization-adapters/src/routes/__root.tsx b/e2e/react-start/serialization-adapters/src/routes/__root.tsx new file mode 100644 index 00000000000..2f30dfa3f1f --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routes/__root.tsx @@ -0,0 +1,72 @@ +/// +import * as React from 'react' +import { + ClientOnly, + HeadContent, + Link, + Scripts, + createRootRoute, + useRouterState, +} from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +import appCss from '~/styles/app.css?url' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'Serialization Adapters E2E Test', + }, + ], + links: [{ rel: 'stylesheet', href: appCss }], + }), + shellComponent: RootDocument, +}) + +function RootDocument({ children }: { children: React.ReactNode }) { + const { isLoading, status } = useRouterState({ + select: (state) => ({ isLoading: state.isLoading, status: state.status }), + structuralSharing: true, + }) + return ( + + + + + +
+

Serialization Adapters E2E Test

+ + Home + +
+
+ +
+ router isLoading:{' '} + {isLoading ? 'true' : 'false'} +
+
+ router status: {status} +
+
+
+ {children} + + + + + ) +} diff --git a/e2e/react-start/serialization-adapters/src/routes/index.tsx b/e2e/react-start/serialization-adapters/src/routes/index.tsx new file mode 100644 index 00000000000..0c39c4d0204 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routes/index.tsx @@ -0,0 +1,40 @@ +import { Link, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( + <> +
+

SSR

+ + Data Only + +
+ + Stream + +
+
+

Server Functions

+ + Custom Error Serialization + +
+ + ) +} diff --git a/e2e/react-start/serialization-adapters/src/routes/server-function/custom-error.tsx b/e2e/react-start/serialization-adapters/src/routes/server-function/custom-error.tsx new file mode 100644 index 00000000000..4c2edc0b448 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routes/server-function/custom-error.tsx @@ -0,0 +1,69 @@ +import { createFileRoute } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' +import { setResponseStatus } from '@tanstack/react-start/server' +import { useState } from 'react' +import { z } from 'zod' +import { CustomError } from '~/CustomError' + +const schema = z.object({ hello: z.string() }) +const serverFnThrowing = createServerFn() + .inputValidator(schema) + .handler(async ({ data }) => { + if (data.hello === 'world') { + return 'Hello, world!' + } + setResponseStatus(499) + throw new CustomError('Invalid input', { foo: 'bar', bar: BigInt(123) }) + }) + +export const Route = createFileRoute('/server-function/custom-error')({ + component: RouteComponent, +}) + +function RouteComponent() { + const [validResponse, setValidResponse] = useState(null) + const [invalidResponse, setInvalidResponse] = useState( + null, + ) + + return ( +
+ +
+ {JSON.stringify(validResponse)} +
+ +
+ +
+ {invalidResponse + ? JSON.stringify({ + message: invalidResponse.message, + foo: invalidResponse.foo, + bar: invalidResponse.bar.toString(), + }) + : JSON.stringify(validResponse)} +
+
+ ) +} diff --git a/e2e/react-start/serialization-adapters/src/routes/ssr/data-only.tsx b/e2e/react-start/serialization-adapters/src/routes/ssr/data-only.tsx new file mode 100644 index 00000000000..c2626f42e23 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routes/ssr/data-only.tsx @@ -0,0 +1,51 @@ +import { Outlet, createFileRoute } from '@tanstack/react-router' +import { RenderData, makeData } from '~/data' + +export const Route = createFileRoute('/ssr/data-only')({ + ssr: 'data-only', + beforeLoad: () => { + return makeData() + }, + loader: ({ context }) => { + return context + }, + component: () => { + const context = Route.useRouteContext() + const loaderData = Route.useLoaderData() + + const localData = makeData() + const expectedHonkState = localData.car.singleInstance.honk() + + const honkState = loaderData.car.singleInstance.honk() + + return ( +
+

data-only

+
+ context: +
+
+ loader: +
+
+

honk

+
+ expected:{' '} +
+ {JSON.stringify(expectedHonkState)} +
+
+
+ actual:{' '} +
+ {JSON.stringify(honkState)} +
+
+
+
+ +
+ ) + }, + pendingComponent: () =>
posts Loading...
, +}) diff --git a/e2e/react-start/serialization-adapters/src/routes/ssr/stream.tsx b/e2e/react-start/serialization-adapters/src/routes/ssr/stream.tsx new file mode 100644 index 00000000000..75926f8342c --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/routes/ssr/stream.tsx @@ -0,0 +1,31 @@ +import { Await, createFileRoute } from '@tanstack/react-router' +import { Suspense } from 'react' +import { RenderData, makeData } from '~/data' + +export const Route = createFileRoute('/ssr/stream')({ + loader: () => { + const dataPromise = new Promise>((r) => + setTimeout(() => r(makeData()), 1000), + ) + return { + someString: 'hello world', + dataPromise, + } + }, + component: RouteComponent, +}) + +function RouteComponent() { + const loaderData = Route.useLoaderData() + return ( +
+

Stream

+
{loaderData.someString}
+ Loading...
}> + + {(data) => } + + + + ) +} diff --git a/e2e/react-start/serialization-adapters/src/start.tsx b/e2e/react-start/serialization-adapters/src/start.tsx new file mode 100644 index 00000000000..75363769634 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/start.tsx @@ -0,0 +1,10 @@ +import { createStart } from '@tanstack/react-start' +import { carAdapter, fooAdapter } from './data' +import { customErrorAdapter } from './CustomError' + +export const startInstance = createStart(() => { + return { + defaultSsr: true, + serializationAdapters: [fooAdapter, carAdapter, customErrorAdapter], + } +}) diff --git a/e2e/react-start/serialization-adapters/src/styles/app.css b/e2e/react-start/serialization-adapters/src/styles/app.css new file mode 100644 index 00000000000..c53c8706654 --- /dev/null +++ b/e2e/react-start/serialization-adapters/src/styles/app.css @@ -0,0 +1,22 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html { + color-scheme: light dark; + } + + * { + @apply border-gray-200 dark:border-gray-800; + } + + html, + body { + @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; + } + + .using-mouse * { + outline: none !important; + } +} diff --git a/e2e/react-start/serialization-adapters/tailwind.config.mjs b/e2e/react-start/serialization-adapters/tailwind.config.mjs new file mode 100644 index 00000000000..e49f4eb776e --- /dev/null +++ b/e2e/react-start/serialization-adapters/tailwind.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], +} diff --git a/e2e/react-start/serialization-adapters/tests/app.spec.ts b/e2e/react-start/serialization-adapters/tests/app.spec.ts new file mode 100644 index 00000000000..2cfaaf727d9 --- /dev/null +++ b/e2e/react-start/serialization-adapters/tests/app.spec.ts @@ -0,0 +1,76 @@ +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' +import type { Page } from '@playwright/test' + +async function awaitPageLoaded(page: Page) { + // wait for page to be loaded by waiting for the ClientOnly component to be rendered + + await expect(page.getByTestId('router-isLoading')).toContainText('false') + await expect(page.getByTestId('router-status')).toContainText('idle') +} +async function checkData(page: Page, id: string) { + const expectedData = await page + .getByTestId(`${id}-car-expected`) + .textContent() + expect(expectedData).not.toBeNull() + await expect(page.getByTestId(`${id}-car-actual`)).toContainText( + expectedData!, + ) + + await expect(page.getByTestId(`${id}-foo`)).toContainText( + '{"value":"server"}', + ) +} +test.use({ + whitelistErrors: [ + /Failed to load resource: the server responded with a status of 499/, + ], +}) +test.describe('SSR serialization adapters', () => { + test(`data-only`, async ({ page }) => { + await page.goto('/ssr/data-only') + await awaitPageLoaded(page) + + await Promise.all( + ['context', 'loader'].map(async (id) => checkData(page, id)), + ) + + const expectedHonkData = await page + .getByTestId('honk-expected-state') + .textContent() + expect(expectedHonkData).not.toBeNull() + await expect(page.getByTestId('honk-actual-state')).toContainText( + expectedHonkData!, + ) + }) + + test('stream', async ({ page }) => { + await page.goto('/ssr/stream') + await awaitPageLoaded(page) + await checkData(page, 'stream') + }) +}) + +test.describe('server functions serialization adapters', () => { + test('custom error', async ({ page }) => { + await page.goto('/server-function/custom-error') + await awaitPageLoaded(page) + + await expect( + page.getByTestId('server-function-valid-response'), + ).toContainText('null') + await expect( + page.getByTestId('server-function-invalid-response'), + ).toContainText('null') + + await page.getByTestId('server-function-valid-input').click() + await expect( + page.getByTestId('server-function-valid-response'), + ).toContainText('Hello, world!') + + await page.getByTestId('server-function-invalid-input').click() + await expect( + page.getByTestId('server-function-invalid-response'), + ).toContainText('{"message":"Invalid input","foo":"bar","bar":"123"}') + }) +}) diff --git a/e2e/react-start/serialization-adapters/tsconfig.json b/e2e/react-start/serialization-adapters/tsconfig.json new file mode 100644 index 00000000000..3a9fb7cd716 --- /dev/null +++ b/e2e/react-start/serialization-adapters/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +} diff --git a/e2e/react-start/serialization-adapters/vite.config.ts b/e2e/react-start/serialization-adapters/vite.config.ts new file mode 100644 index 00000000000..04033010e8f --- /dev/null +++ b/e2e/react-start/serialization-adapters/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' + +export default defineConfig({ + server: { + port: 3000, + }, + plugins: [ + tsConfigPaths({ + projects: ['./tsconfig.json'], + }), + tanstackStart(), + viteReact(), + ], +}) diff --git a/e2e/react-start/server-functions/package.json b/e2e/react-start/server-functions/package.json index bf735283d82..2a74346351f 100644 --- a/e2e/react-start/server-functions/package.json +++ b/e2e/react-start/server-functions/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -19,7 +19,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -33,6 +33,7 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/react-start/server-functions/src/routeTree.gen.ts b/e2e/react-start/server-functions/src/routeTree.gen.ts index 39bdf99d2de..f788702ef1c 100644 --- a/e2e/react-start/server-functions/src/routeTree.gen.ts +++ b/e2e/react-start/server-functions/src/routeTree.gen.ts @@ -24,6 +24,7 @@ import { Route as AbortSignalRouteImport } from './routes/abort-signal' import { Route as IndexRouteImport } from './routes/index' import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' +import { Route as FactoryIndexRouteImport } from './routes/factory/index' import { Route as CookiesIndexRouteImport } from './routes/cookies/index' import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' @@ -105,6 +106,11 @@ const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ path: '/formdata-redirect/', getParentRoute: () => rootRouteImport, } as any) +const FactoryIndexRoute = FactoryIndexRouteImport.update({ + id: '/factory/', + path: '/factory/', + getParentRoute: () => rootRouteImport, +} as any) const CookiesIndexRoute = CookiesIndexRouteImport.update({ id: '/cookies/', path: '/cookies/', @@ -151,6 +157,7 @@ export interface FileRoutesByFullPath { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute '/cookies': typeof CookiesIndexRoute + '/factory': typeof FactoryIndexRoute '/formdata-redirect': typeof FormdataRedirectIndexRoute '/middleware': typeof MiddlewareIndexRoute '/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute @@ -173,6 +180,7 @@ export interface FileRoutesByTo { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute '/cookies': typeof CookiesIndexRoute + '/factory': typeof FactoryIndexRoute '/formdata-redirect': typeof FormdataRedirectIndexRoute '/middleware': typeof MiddlewareIndexRoute '/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute @@ -196,6 +204,7 @@ export interface FileRoutesById { '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute '/cookies/': typeof CookiesIndexRoute + '/factory/': typeof FactoryIndexRoute '/formdata-redirect/': typeof FormdataRedirectIndexRoute '/middleware/': typeof MiddlewareIndexRoute '/formdata-redirect/target/$name': typeof FormdataRedirectTargetNameRoute @@ -220,6 +229,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/send-serverFn' | '/cookies' + | '/factory' | '/formdata-redirect' | '/middleware' | '/formdata-redirect/target/$name' @@ -242,6 +252,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/send-serverFn' | '/cookies' + | '/factory' | '/formdata-redirect' | '/middleware' | '/formdata-redirect/target/$name' @@ -264,6 +275,7 @@ export interface FileRouteTypes { | '/middleware/client-middleware-router' | '/middleware/send-serverFn' | '/cookies/' + | '/factory/' | '/formdata-redirect/' | '/middleware/' | '/formdata-redirect/target/$name' @@ -287,6 +299,7 @@ export interface RootRouteChildren { MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute MiddlewareSendServerFnRoute: typeof MiddlewareSendServerFnRoute CookiesIndexRoute: typeof CookiesIndexRoute + FactoryIndexRoute: typeof FactoryIndexRoute FormdataRedirectIndexRoute: typeof FormdataRedirectIndexRoute MiddlewareIndexRoute: typeof MiddlewareIndexRoute FormdataRedirectTargetNameRoute: typeof FormdataRedirectTargetNameRoute @@ -399,6 +412,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FormdataRedirectIndexRouteImport parentRoute: typeof rootRouteImport } + '/factory/': { + id: '/factory/' + path: '/factory' + fullPath: '/factory' + preLoaderRoute: typeof FactoryIndexRouteImport + parentRoute: typeof rootRouteImport + } '/cookies/': { id: '/cookies/' path: '/cookies' @@ -455,6 +475,7 @@ const rootRouteChildren: RootRouteChildren = { MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute, MiddlewareSendServerFnRoute: MiddlewareSendServerFnRoute, CookiesIndexRoute: CookiesIndexRoute, + FactoryIndexRoute: FactoryIndexRoute, FormdataRedirectIndexRoute: FormdataRedirectIndexRoute, MiddlewareIndexRoute: MiddlewareIndexRoute, FormdataRedirectTargetNameRoute: FormdataRedirectTargetNameRoute, @@ -462,3 +483,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/server-functions/src/router.tsx b/e2e/react-start/server-functions/src/router.tsx index bc1c386f89e..e2d1147335f 100644 --- a/e2e/react-start/server-functions/src/router.tsx +++ b/e2e/react-start/server-functions/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -19,9 +19,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/server-functions/src/routes/consistent.tsx b/e2e/react-start/server-functions/src/routes/consistent.tsx index 5908512fa2a..b0d91168056 100644 --- a/e2e/react-start/server-functions/src/routes/consistent.tsx +++ b/e2e/react-start/server-functions/src/routes/consistent.tsx @@ -20,25 +20,25 @@ export const Route = createFileRoute('/consistent')({ }) const cons_getFn1 = createServerFn() - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return { payload: data } }) const cons_serverGetFn1 = createServerFn() - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(async ({ data }) => { return cons_getFn1({ data }) }) const cons_postFn1 = createServerFn({ method: 'POST' }) - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return { payload: data } }) const cons_serverPostFn1 = createServerFn({ method: 'POST' }) - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return cons_postFn1({ data }) }) diff --git a/e2e/react-start/server-functions/src/routes/cookies/set.tsx b/e2e/react-start/server-functions/src/routes/cookies/set.tsx index fd9e979ab96..d2bb2723676 100644 --- a/e2e/react-start/server-functions/src/routes/cookies/set.tsx +++ b/e2e/react-start/server-functions/src/routes/cookies/set.tsx @@ -18,14 +18,14 @@ export const Route = createFileRoute('/cookies/set')({ }) export const setCookieServerFn1 = createServerFn() - .validator(cookieSchema) + .inputValidator(cookieSchema) .handler(({ data }) => { setCookie(`cookie-1-${data.value}`, data.value) setCookie(`cookie-2-${data.value}`, data.value) }) export const setCookieServerFn2 = createServerFn() - .validator(cookieSchema) + .inputValidator(cookieSchema) .handler(({ data }) => { setCookie(`cookie-3-${data.value}`, data.value) setCookie(`cookie-4-${data.value}`, data.value) diff --git a/e2e/react-start/server-functions/src/routes/env-only.tsx b/e2e/react-start/server-functions/src/routes/env-only.tsx index fe5ae8c1e81..2a938d692e4 100644 --- a/e2e/react-start/server-functions/src/routes/env-only.tsx +++ b/e2e/react-start/server-functions/src/routes/env-only.tsx @@ -1,9 +1,13 @@ import { createFileRoute } from '@tanstack/react-router' -import { clientOnly, createServerFn, serverOnly } from '@tanstack/react-start' +import { + createClientOnlyFn, + createServerFn, + createServerOnlyFn, +} from '@tanstack/react-start' import { useState } from 'react' -const serverEcho = serverOnly((input: string) => 'server got: ' + input) -const clientEcho = clientOnly((input: string) => 'client got: ' + input) +const serverEcho = createServerOnlyFn((input: string) => 'server got: ' + input) +const clientEcho = createClientOnlyFn((input: string) => 'client got: ' + input) const testOnServer = createServerFn().handler(() => { const serverOnServer = serverEcho('hello') diff --git a/e2e/react-start/server-functions/src/routes/factory/-functions/createBarServerFn.ts b/e2e/react-start/server-functions/src/routes/factory/-functions/createBarServerFn.ts new file mode 100644 index 00000000000..4d622fcf9b0 --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/factory/-functions/createBarServerFn.ts @@ -0,0 +1,22 @@ +import { createMiddleware } from '@tanstack/react-start' +import { createFooServerFn } from './createFooServerFn' + +const barMiddleware = createMiddleware({ type: 'function' }).server( + ({ next }) => { + console.log('Bar middleware triggered') + return next({ + context: { bar: 'bar' } as const, + }) + }, +) + +export const createBarServerFn = createFooServerFn().middleware([barMiddleware]) + +export const barFnInsideFactoryFile = createBarServerFn().handler( + ({ context }) => { + return { + name: 'barFnInsideFactoryFile', + context, + } + }, +) diff --git a/e2e/react-start/server-functions/src/routes/factory/-functions/createFakeFn.ts b/e2e/react-start/server-functions/src/routes/factory/-functions/createFakeFn.ts new file mode 100644 index 00000000000..1c727338850 --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/factory/-functions/createFakeFn.ts @@ -0,0 +1,5 @@ +export function createFakeFn() { + return { + handler: (cb: () => Promise) => cb, + } +} diff --git a/e2e/react-start/server-functions/src/routes/factory/-functions/createFooServerFn.ts b/e2e/react-start/server-functions/src/routes/factory/-functions/createFooServerFn.ts new file mode 100644 index 00000000000..af13270b627 --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/factory/-functions/createFooServerFn.ts @@ -0,0 +1,22 @@ +import { createMiddleware, createServerFn } from '@tanstack/react-start' + +const fooMiddleware = createMiddleware({ type: 'function' }).server( + ({ next }) => { + console.log('Foo middleware triggered') + return next({ + context: { foo: 'foo' } as const, + }) + }, +) + +export const createFooServerFn = createServerFn().middleware([fooMiddleware]) + +export const fooFnInsideFactoryFile = createFooServerFn().handler( + async ({ context, method }) => { + console.log('fooFnInsideFactoryFile handler triggered', method) + return { + name: 'fooFnInsideFactoryFile', + context, + } + }, +) diff --git a/e2e/react-start/server-functions/src/routes/factory/-functions/functions.ts b/e2e/react-start/server-functions/src/routes/factory/-functions/functions.ts new file mode 100644 index 00000000000..35be5f91f7b --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/factory/-functions/functions.ts @@ -0,0 +1,93 @@ +import { createMiddleware, createServerFn } from '@tanstack/react-start' +import { createBarServerFn } from './createBarServerFn' +import { createFooServerFn } from './createFooServerFn' +import { createFakeFn } from './createFakeFn' + +export const fooFn = createFooServerFn().handler(({ context }) => { + return { + name: 'fooFn', + context, + } +}) + +export const fooFnPOST = createFooServerFn({ method: 'POST' }).handler( + ({ context }) => { + return { + name: 'fooFnPOST', + context, + } + }, +) + +export const barFn = createBarServerFn().handler(({ context }) => { + return { + name: 'barFn', + context, + } +}) + +export const barFnPOST = createBarServerFn({ method: 'POST' }).handler( + ({ context }) => { + return { + name: 'barFnPOST', + context, + } + }, +) + +const localMiddleware = createMiddleware({ type: 'function' }).server( + ({ next }) => { + console.log('local middleware triggered') + return next({ + context: { local: 'local' } as const, + }) + }, +) + +const localFnFactory = createBarServerFn.middleware([localMiddleware]) + +const anotherMiddleware = createMiddleware({ type: 'function' }).server( + ({ next }) => { + console.log('another middleware triggered') + return next({ + context: { another: 'another' } as const, + }) + }, +) + +export const localFn = localFnFactory() + .middleware([anotherMiddleware]) + .handler(({ context }) => { + return { + name: 'localFn', + context, + } + }) + +export const localFnPOST = localFnFactory({ method: 'POST' }) + .middleware([anotherMiddleware]) + .handler(({ context }) => { + return { + name: 'localFnPOST', + context, + } + }) + +export const fakeFn = createFakeFn().handler(async () => { + return { + name: 'fakeFn', + window, + } +}) + +export const composeFactory = createServerFn({ method: 'GET' }).middleware([ + createBarServerFn, +]) +export const composedFn = composeFactory() + .middleware([anotherMiddleware, localFnFactory]) + .handler(({ context }) => { + return { + name: 'composedFn', + context, + } + }) diff --git a/e2e/react-start/server-functions/src/routes/factory/index.tsx b/e2e/react-start/server-functions/src/routes/factory/index.tsx new file mode 100644 index 00000000000..cf16c362b18 --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/factory/index.tsx @@ -0,0 +1,184 @@ +import { createFileRoute, deepEqual } from '@tanstack/react-router' + +import React from 'react' +import { createServerFn } from '@tanstack/react-start' +import { fooFnInsideFactoryFile } from './-functions/createFooServerFn' +import { + barFn, + barFnPOST, + composedFn, + fakeFn, + fooFn, + fooFnPOST, + localFn, + localFnPOST, +} from './-functions/functions' + +export const Route = createFileRoute('/factory/')({ + ssr: false, + component: RouteComponent, +}) + +const fnInsideRoute = createServerFn({ method: 'GET' }).handler(() => { + return { + name: 'fnInsideRoute', + } +}) + +const functions = { + fnInsideRoute: { + fn: fnInsideRoute, + type: 'serverFn', + expected: { + name: 'fnInsideRoute', + }, + }, + fooFnInsideFactoryFile: { + fn: fooFnInsideFactoryFile, + type: 'serverFn', + + expected: { + name: 'fooFnInsideFactoryFile', + context: { foo: 'foo' }, + }, + }, + fooFn: { + fn: fooFn, + type: 'serverFn', + + expected: { + name: 'fooFn', + context: { foo: 'foo' }, + }, + }, + fooFnPOST: { + fn: fooFnPOST, + type: 'serverFn', + + expected: { + name: 'fooFnPOST', + context: { foo: 'foo' }, + }, + }, + barFn: { + fn: barFn, + type: 'serverFn', + + expected: { + name: 'barFn', + context: { foo: 'foo', bar: 'bar' }, + }, + }, + barFnPOST: { + fn: barFnPOST, + type: 'serverFn', + + expected: { + name: 'barFnPOST', + context: { foo: 'foo', bar: 'bar' }, + }, + }, + localFn: { + fn: localFn, + type: 'serverFn', + + expected: { + name: 'localFn', + context: { foo: 'foo', bar: 'bar', local: 'local', another: 'another' }, + }, + }, + localFnPOST: { + fn: localFnPOST, + type: 'serverFn', + + expected: { + name: 'localFnPOST', + context: { foo: 'foo', bar: 'bar', local: 'local', another: 'another' }, + }, + }, + composedFn: { + fn: composedFn, + type: 'serverFn', + expected: { + name: 'composedFn', + context: { foo: 'foo', bar: 'bar', another: 'another', local: 'local' }, + }, + }, + fakeFn: { + fn: fakeFn, + type: 'localFn', + expected: { + name: 'fakeFn', + window, + }, + }, +} satisfies Record + +interface TestCase { + fn: () => Promise + expected: any + type: 'serverFn' | 'localFn' +} +function Test({ fn, type, expected }: TestCase) { + const [result, setResult] = React.useState(null) + function comparison() { + if (result) { + const isEqual = deepEqual(result, expected) + return isEqual ? 'equal' : 'not equal' + } + return 'Loading...' + } + + return ( +
+

+
+ It should return{' '} + +
+            {type === 'serverFn' ? JSON.stringify(expected) : 'localFn'}
+          
+
+
+

+ fn returns: +
+ + {result + ? type === 'serverFn' + ? JSON.stringify(result) + : 'localFn' + : 'Loading...'} + {' '} + + {comparison()} + +

+ +
+ ) +} +function RouteComponent() { + return ( +
+

+ Server functions middleware E2E tests +

+ {Object.entries(functions).map(([name, testCase]) => ( + + ))} +
+ ) +} diff --git a/e2e/react-start/server-functions/src/routes/formdata-redirect/index.tsx b/e2e/react-start/server-functions/src/routes/formdata-redirect/index.tsx index 67954ecf50a..8420025983b 100644 --- a/e2e/react-start/server-functions/src/routes/formdata-redirect/index.tsx +++ b/e2e/react-start/server-functions/src/routes/formdata-redirect/index.tsx @@ -14,7 +14,7 @@ const testValues = { } export const greetUser = createServerFn({ method: 'POST' }) - .validator((data: FormData) => { + .inputValidator((data: FormData) => { if (!(data instanceof FormData)) { throw new Error('Invalid! FormData is required') } diff --git a/e2e/react-start/server-functions/src/routes/headers.tsx b/e2e/react-start/server-functions/src/routes/headers.tsx index 77c576e40b8..79b05c0fcb3 100644 --- a/e2e/react-start/server-functions/src/routes/headers.tsx +++ b/e2e/react-start/server-functions/src/routes/headers.tsx @@ -1,8 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' import * as React from 'react' import { createServerFn } from '@tanstack/react-start' -import { getHeaders, setHeader } from '@tanstack/react-start/server' -import type { HTTPHeaderName } from '@tanstack/react-start/server' +import { + getRequestHeaders, + setResponseHeader, +} from '@tanstack/react-start/server' +import type { RequestHeaderName } from '@tanstack/react-start/server' export const Route = createFileRoute('/headers')({ loader: async () => { @@ -17,17 +20,18 @@ export const Route = createFileRoute('/headers')({ }) export const getTestHeaders = createServerFn().handler(() => { - setHeader('x-test-header', 'test-value') + setResponseHeader('x-test-header', 'test-value') + const reqHeaders = Object.fromEntries(getRequestHeaders().entries()) return { - serverHeaders: getHeaders(), - headers: getHeaders(), + serverHeaders: reqHeaders, + headers: reqHeaders, } }) type TestHeadersResult = { - headers?: Partial> - serverHeaders?: Partial> + headers?: Partial> + serverHeaders?: Partial> } function ResponseHeaders({ diff --git a/e2e/react-start/server-functions/src/routes/index.tsx b/e2e/react-start/server-functions/src/routes/index.tsx index 949cc08f582..596b855c099 100644 --- a/e2e/react-start/server-functions/src/routes/index.tsx +++ b/e2e/react-start/server-functions/src/routes/index.tsx @@ -83,7 +83,10 @@ function Home() {
  • - Server Functions Middlware E2E tests + Server Functions Middleware E2E tests +
  • +
  • + Server Functions Factory E2E tests
  • diff --git a/e2e/react-start/server-functions/src/routes/isomorphic-fns.tsx b/e2e/react-start/server-functions/src/routes/isomorphic-fns.tsx index 2c7bbc4a8fa..773a2e53298 100644 --- a/e2e/react-start/server-functions/src/routes/isomorphic-fns.tsx +++ b/e2e/react-start/server-functions/src/routes/isomorphic-fns.tsx @@ -13,7 +13,7 @@ const getEcho = createIsomorphicFn() .client((input) => 'client received ' + input) const getServerEcho = createServerFn() - .validator((input: string) => input) + .inputValidator((input: string) => input) .handler(({ data }) => getEcho(data)) export const Route = createFileRoute('/isomorphic-fns')({ diff --git a/e2e/react-start/server-functions/src/routes/middleware/client-middleware-router.tsx b/e2e/react-start/server-functions/src/routes/middleware/client-middleware-router.tsx index e71b34fd1e5..bfb5059da8a 100644 --- a/e2e/react-start/server-functions/src/routes/middleware/client-middleware-router.tsx +++ b/e2e/react-start/server-functions/src/routes/middleware/client-middleware-router.tsx @@ -1,9 +1,14 @@ import { createFileRoute, useRouter } from '@tanstack/react-router' -import { createMiddleware, createServerFn } from '@tanstack/react-start' +import { + createMiddleware, + createServerFn, + getRouterInstance, +} from '@tanstack/react-start' import React from 'react' const middleware = createMiddleware({ type: 'function' }).client( - async ({ router, next }) => { + async ({ next }) => { + const router = await getRouterInstance() return next({ sendContext: { routerContext: router.options.context, diff --git a/e2e/react-start/server-functions/src/routes/multipart.tsx b/e2e/react-start/server-functions/src/routes/multipart.tsx index a80ca0f1cd1..34312ca658c 100644 --- a/e2e/react-start/server-functions/src/routes/multipart.tsx +++ b/e2e/react-start/server-functions/src/routes/multipart.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute('/multipart')({ }) const multipartFormDataServerFn = createServerFn({ method: 'POST' }) - .validator((x: unknown) => { + .inputValidator((x: unknown) => { if (!(x instanceof FormData)) { throw new Error('Invalid form data') } diff --git a/e2e/react-start/server-functions/src/routes/raw-response.tsx b/e2e/react-start/server-functions/src/routes/raw-response.tsx index 8da5f8af7dc..f30ff6660e5 100644 --- a/e2e/react-start/server-functions/src/routes/raw-response.tsx +++ b/e2e/react-start/server-functions/src/routes/raw-response.tsx @@ -8,7 +8,7 @@ export const Route = createFileRoute('/raw-response')({ }) const expectedValue = 'Hello from a server function!' -export const rawResponseFn = createServerFn({ response: 'raw' }).handler(() => { +export const rawResponseFn = createServerFn().handler(() => { return new Response(expectedValue) }) diff --git a/e2e/react-start/server-functions/src/routes/serialize-form-data.tsx b/e2e/react-start/server-functions/src/routes/serialize-form-data.tsx index b1b9dc777a2..f5d7d75db6e 100644 --- a/e2e/react-start/server-functions/src/routes/serialize-form-data.tsx +++ b/e2e/react-start/server-functions/src/routes/serialize-form-data.tsx @@ -15,8 +15,8 @@ const testValues = { __adder: 1, } -export const greetUser = createServerFn() - .validator((data: FormData) => { +export const greetUser = createServerFn({ method: 'POST' }) + .inputValidator((data: FormData) => { if (!(data instanceof FormData)) { throw new Error('Invalid! FormData is required') } diff --git a/e2e/react-start/server-functions/src/routes/submit-post-formdata.tsx b/e2e/react-start/server-functions/src/routes/submit-post-formdata.tsx index 05df9228179..826ec255d38 100644 --- a/e2e/react-start/server-functions/src/routes/submit-post-formdata.tsx +++ b/e2e/react-start/server-functions/src/routes/submit-post-formdata.tsx @@ -9,8 +9,8 @@ const testValues = { name: 'Sean', } -export const greetUser = createServerFn({ method: 'POST', response: 'raw' }) - .validator((data: FormData) => { +export const greetUser = createServerFn({ method: 'POST' }) + .inputValidator((data: FormData) => { if (!(data instanceof FormData)) { throw new Error('Invalid! FormData is required') } diff --git a/e2e/react-start/server-functions/tests/fixture.ts b/e2e/react-start/server-functions/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/server-functions/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/server-functions/tests/server-functions.spec.ts b/e2e/react-start/server-functions/tests/server-functions.spec.ts index 86332e74918..990487289fd 100644 --- a/e2e/react-start/server-functions/tests/server-functions.spec.ts +++ b/e2e/react-start/server-functions/tests/server-functions.spec.ts @@ -1,8 +1,11 @@ import * as fs from 'node:fs' -import { expect, test } from '@playwright/test' -import { PORT } from '../playwright.config' +import { expect } from '@playwright/test' +import { getTestServerPort, test } from '@tanstack/router-e2e-utils' +import packageJson from '../package.json' with { type: 'json' } import type { Page } from '@playwright/test' +const PORT = await getTestServerPort(packageJson.name) + test('invoking a server function with custom response status code', async ({ page, }) => { @@ -11,16 +14,10 @@ test('invoking a server function with custom response status code', async ({ await page.waitForLoadState('networkidle') const requestPromise = new Promise((resolve) => { - page.on('response', async (response) => { + page.on('response', (response) => { expect(response.status()).toBe(225) expect(response.statusText()).toBe('hello') - expect(response.headers()['content-type']).toBe('application/json') - expect(await response.json()).toEqual( - expect.objectContaining({ - result: { hello: 'world' }, - context: {}, - }), - ) + expect(response.headers()['content-type']).toContain('application/json') resolve() }) }) @@ -122,11 +119,11 @@ test('env-only functions can only be called on the server or client respectively 'server got: hello', ) await expect(page.getByTestId('server-on-client')).toContainText( - 'serverEcho threw an error: serverOnly() functions can only be called on the server!', + 'serverEcho threw an error: createServerOnlyFn() functions can only be called on the server!', ) await expect(page.getByTestId('client-on-server')).toContainText( - 'clientEcho threw an error: clientOnly() functions can only be called on the client!', + 'clientEcho threw an error: createClientOnlyFn() functions can only be called on the client!', ) await expect(page.getByTestId('client-on-client')).toContainText( 'client got: hello', @@ -372,3 +369,37 @@ test.describe('middleware', () => { }) }) }) + +test('factory', async ({ page }) => { + await page.goto('/factory') + + await expect(page.getByTestId('factory-route-component')).toBeInViewport() + + const buttons = await page + .locator('[data-testid^="btn-fn-"]') + .elementHandles() + for (const button of buttons) { + const testId = await button.getAttribute('data-testid') + + if (!testId) { + throw new Error('Button is missing data-testid') + } + + const suffix = testId.replace('btn-fn-', '') + + const expected = + (await page.getByTestId(`expected-fn-result-${suffix}`).textContent()) || + '' + expect(expected).not.toBe('') + + await button.click() + + await expect(page.getByTestId(`fn-result-${suffix}`)).toContainText( + expected, + ) + + await expect(page.getByTestId(`fn-comparison-${suffix}`)).toContainText( + 'equal', + ) + } +}) diff --git a/e2e/react-start/server-functions/vite.config.ts b/e2e/react-start/server-functions/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/server-functions/vite.config.ts +++ b/e2e/react-start/server-functions/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/server-routes/package.json b/e2e/react-start/server-routes/package.json index e7352c35ced..614c5d1552b 100644 --- a/e2e/react-start/server-routes/package.json +++ b/e2e/react-start/server-routes/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "playwright test --project=chromium" }, "dependencies": { @@ -19,7 +19,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -33,6 +33,7 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/react-start/server-routes/src/routeTree.gen.ts b/e2e/react-start/server-routes/src/routeTree.gen.ts index 1d1d61ae496..c1f0cedfd3c 100644 --- a/e2e/react-start/server-routes/src/routeTree.gen.ts +++ b/e2e/react-start/server-routes/src/routeTree.gen.ts @@ -8,14 +8,10 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as MergeServerFnMiddlewareContextRouteImport } from './routes/merge-server-fn-middleware-context' import { Route as IndexRouteImport } from './routes/index' -import { ServerRoute as ApiMiddlewareContextServerRouteImport } from './routes/api/middleware-context' - -const rootServerRouteImport = createServerRootRoute() +import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' const MergeServerFnMiddlewareContextRoute = MergeServerFnMiddlewareContextRouteImport.update({ @@ -28,58 +24,47 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const ApiMiddlewareContextServerRoute = - ApiMiddlewareContextServerRouteImport.update({ - id: '/api/middleware-context', - path: '/api/middleware-context', - getParentRoute: () => rootServerRouteImport, - } as any) +const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ + id: '/api/middleware-context', + path: '/api/middleware-context', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/merge-server-fn-middleware-context' + fullPaths: + | '/' + | '/merge-server-fn-middleware-context' + | '/api/middleware-context' fileRoutesByTo: FileRoutesByTo - to: '/' | '/merge-server-fn-middleware-context' - id: '__root__' | '/' | '/merge-server-fn-middleware-context' + to: '/' | '/merge-server-fn-middleware-context' | '/api/middleware-context' + id: + | '__root__' + | '/' + | '/merge-server-fn-middleware-context' + | '/api/middleware-context' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute MergeServerFnMiddlewareContextRoute: typeof MergeServerFnMiddlewareContextRoute -} -export interface FileServerRoutesByFullPath { - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRoutesByTo { - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/middleware-context' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/middleware-context' - id: '__root__' | '/api/middleware-context' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiMiddlewareContextServerRoute: typeof ApiMiddlewareContextServerRoute + ApiMiddlewareContextRoute: typeof ApiMiddlewareContextRoute } declare module '@tanstack/react-router' { @@ -98,16 +83,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - } -} -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { '/api/middleware-context': { id: '/api/middleware-context' path: '/api/middleware-context' fullPath: '/api/middleware-context' - preLoaderRoute: typeof ApiMiddlewareContextServerRouteImport - parentRoute: typeof rootServerRouteImport + preLoaderRoute: typeof ApiMiddlewareContextRouteImport + parentRoute: typeof rootRouteImport } } } @@ -115,13 +96,16 @@ declare module '@tanstack/react-start/server' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, MergeServerFnMiddlewareContextRoute: MergeServerFnMiddlewareContextRoute, + ApiMiddlewareContextRoute: ApiMiddlewareContextRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiMiddlewareContextServerRoute: ApiMiddlewareContextServerRoute, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/react-start/server-routes/src/router.tsx b/e2e/react-start/server-routes/src/router.tsx index c76eb0210cc..1a1d8822d20 100644 --- a/e2e/react-start/server-routes/src/router.tsx +++ b/e2e/react-start/server-routes/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/server-routes/src/routes/api/middleware-context.ts b/e2e/react-start/server-routes/src/routes/api/middleware-context.ts index bc75e29126d..e897aa9a3a2 100644 --- a/e2e/react-start/server-routes/src/routes/api/middleware-context.ts +++ b/e2e/react-start/server-routes/src/routes/api/middleware-context.ts @@ -1,28 +1,29 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { createMiddleware, json } from '@tanstack/react-start' -const testParentMiddleware = createMiddleware({ type: 'request' }).server( - async ({ next }) => { - const result = await next({ context: { testParent: true } }) - return result - }, -) +const testParentMiddleware = createMiddleware().server(async ({ next }) => { + const result = await next({ context: { testParent: true } }) + return result +}) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next }) => { const result = await next({ context: { test: true } }) return result }) -export const ServerRoute = createServerFileRoute('/api/middleware-context') - .middleware([testMiddleware]) - .methods({ - GET: ({ request, context }) => { - return json({ - url: request.url, - context: context, - expectedContext: { testParent: true, test: true }, - }) +export const Route = createFileRoute('/api/middleware-context')({ + server: { + middleware: [testMiddleware], + handlers: { + GET: ({ request, context }) => { + return json({ + url: request.url, + context: context, + expectedContext: { testParent: true, test: true }, + }) + }, }, - }) + }, +}) diff --git a/e2e/react-start/server-routes/tests/fixture.ts b/e2e/react-start/server-routes/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/server-routes/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/server-routes/tests/server-routes.spec.ts b/e2e/react-start/server-routes/tests/server-routes.spec.ts index 098a40b2efe..33176a23fd2 100644 --- a/e2e/react-start/server-routes/tests/server-routes.spec.ts +++ b/e2e/react-start/server-routes/tests/server-routes.spec.ts @@ -1,4 +1,5 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' test('merge-server-fn-middleware-context', async ({ page }) => { await page.goto('/merge-server-fn-middleware-context') diff --git a/e2e/react-start/server-routes/vite.config.ts b/e2e/react-start/server-routes/vite.config.ts index 1df337cd40d..dc57f144e4f 100644 --- a/e2e/react-start/server-routes/vite.config.ts +++ b/e2e/react-start/server-routes/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ plugins: [ @@ -8,5 +9,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/react-start/spa-mode/package.json b/e2e/react-start/spa-mode/package.json index ec5aaa4d5d0..ae48f46c885 100644 --- a/e2e/react-start/spa-mode/package.json +++ b/e2e/react-start/spa-mode/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "npx serve .output/public", + "start": "npx serve dist/client", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -25,7 +25,7 @@ "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/react-start/spa-mode/src/routeTree.gen.ts b/e2e/react-start/spa-mode/src/routeTree.gen.ts index 675f91bd3f2..b2073da8ce1 100644 --- a/e2e/react-start/spa-mode/src/routeTree.gen.ts +++ b/e2e/react-start/spa-mode/src/routeTree.gen.ts @@ -101,3 +101,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/spa-mode/src/router.tsx b/e2e/react-start/spa-mode/src/router.tsx index 25729701a7b..82a730704ad 100644 --- a/e2e/react-start/spa-mode/src/router.tsx +++ b/e2e/react-start/spa-mode/src/router.tsx @@ -1,17 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/spa-mode/tests/app.spec.ts b/e2e/react-start/spa-mode/tests/app.spec.ts index 8970ec333c8..c18a0ff65d5 100644 --- a/e2e/react-start/spa-mode/tests/app.spec.ts +++ b/e2e/react-start/spa-mode/tests/app.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' import type { Page } from '@playwright/test' async function runTest( diff --git a/e2e/react-start/spa-mode/tests/fixture.ts b/e2e/react-start/spa-mode/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/react-start/spa-mode/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/react-start/virtual-routes/package.json b/e2e/react-start/virtual-routes/package.json index 3347f1e9957..b4f3b6b6edc 100644 --- a/e2e/react-start/virtual-routes/package.json +++ b/e2e/react-start/virtual-routes/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -19,7 +19,7 @@ "react-dom": "^19.0.0", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -32,6 +32,7 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/react-start/virtual-routes/src/routeTree.gen.ts b/e2e/react-start/virtual-routes/src/routeTree.gen.ts index a96d463d2d1..2c45b5cecdc 100644 --- a/e2e/react-start/virtual-routes/src/routeTree.gen.ts +++ b/e2e/react-start/virtual-routes/src/routeTree.gen.ts @@ -315,3 +315,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/react-start/virtual-routes/src/router.tsx b/e2e/react-start/virtual-routes/src/router.tsx index b8744cb41fb..6ee5705ca9b 100644 --- a/e2e/react-start/virtual-routes/src/router.tsx +++ b/e2e/react-start/virtual-routes/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', scrollRestoration: true, @@ -10,9 +10,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/virtual-routes/src/utils/posts.tsx b/e2e/react-start/virtual-routes/src/utils/posts.tsx index 4cc5dcc4c6a..b2d9f3edecf 100644 --- a/e2e/react-start/virtual-routes/src/utils/posts.tsx +++ b/e2e/react-start/virtual-routes/src/utils/posts.tsx @@ -15,7 +15,7 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/react-start/virtual-routes/vite.config.ts b/e2e/react-start/virtual-routes/vite.config.ts index 21ff4c2940c..110811e1985 100644 --- a/e2e/react-start/virtual-routes/vite.config.ts +++ b/e2e/react-start/virtual-routes/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart({ - tsr: { + router: { virtualRouteConfig: './routes.ts', }, }), diff --git a/e2e/react-start/website/package.json b/e2e/react-start/website/package.json index 2990a23c08e..5d54afc0569 100644 --- a/e2e/react-start/website/package.json +++ b/e2e/react-start/website/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -29,9 +29,10 @@ "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/react-start/website/src/routeTree.gen.ts b/e2e/react-start/website/src/routeTree.gen.ts index e8358b4aa3a..d8a3e0cd08d 100644 --- a/e2e/react-start/website/src/routeTree.gen.ts +++ b/e2e/react-start/website/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as LibraryRouteImport } from './routes/_library' import { Route as LibraryIndexRouteImport } from './routes/_library.index' @@ -19,11 +17,9 @@ import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_librar import { Route as ProjectVersionDocsIndexRouteImport } from './routes/$project.$version.docs.index' import { Route as ProjectVersionDocsFrameworkFrameworkRouteImport } from './routes/$project.$version.docs.framework.$framework' import { Route as ProjectVersionDocsFrameworkFrameworkIndexRouteImport } from './routes/$project.$version.docs.framework.$framework.index' +import { Route as ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport } from './routes/$project.$version.docs.framework.$framework.{$}[.]md' import { Route as ProjectVersionDocsFrameworkFrameworkSplatRouteImport } from './routes/$project.$version.docs.framework.$framework.$' import { Route as ProjectVersionDocsFrameworkFrameworkExamplesSplatRouteImport } from './routes/$project.$version.docs.framework.$framework.examples.$' -import { ServerRoute as ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRouteImport } from './routes/$project.$version.docs.framework.$framework.{$}[.]md' - -const rootServerRouteImport = createServerRootRoute() const LibraryRoute = LibraryRouteImport.update({ id: '/_library', @@ -67,6 +63,12 @@ const ProjectVersionDocsFrameworkFrameworkIndexRoute = path: '/', getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, } as any) +const ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute = + ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport.update({ + id: '/{$}.md', + path: '/{$}.md', + getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, + } as any) const ProjectVersionDocsFrameworkFrameworkSplatRoute = ProjectVersionDocsFrameworkFrameworkSplatRouteImport.update({ id: '/$', @@ -79,14 +81,6 @@ const ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute = path: '/examples/$', getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, } as any) -const ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute = - ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRouteImport.update( - { - id: '/$project/$version/docs/framework/$framework/{$}.md', - path: '/$project/$version/docs/framework/$framework/{$}.md', - getParentRoute: () => rootServerRouteImport, - } as any, - ) export interface FileRoutesByFullPath { '/$project': typeof ProjectIndexRoute @@ -95,6 +89,7 @@ export interface FileRoutesByFullPath { '/$project/$version': typeof LibraryProjectVersionIndexRoute '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute + '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute '/$project/$version/docs/framework/$framework/': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute } @@ -104,6 +99,7 @@ export interface FileRoutesByTo { '/$project/$version/docs': typeof ProjectVersionDocsIndexRoute '/$project/$version': typeof LibraryProjectVersionIndexRoute '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute + '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute } @@ -117,6 +113,7 @@ export interface FileRoutesById { '/_library/$project/$version/': typeof LibraryProjectVersionIndexRoute '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute + '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute '/$project/$version/docs/framework/$framework/': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute } @@ -129,6 +126,7 @@ export interface FileRouteTypes { | '/$project/$version' | '/$project/$version/docs/framework/$framework' | '/$project/$version/docs/framework/$framework/$' + | '/$project/$version/docs/framework/$framework/{$}.md' | '/$project/$version/docs/framework/$framework/' | '/$project/$version/docs/framework/$framework/examples/$' fileRoutesByTo: FileRoutesByTo @@ -138,6 +136,7 @@ export interface FileRouteTypes { | '/$project/$version/docs' | '/$project/$version' | '/$project/$version/docs/framework/$framework/$' + | '/$project/$version/docs/framework/$framework/{$}.md' | '/$project/$version/docs/framework/$framework' | '/$project/$version/docs/framework/$framework/examples/$' id: @@ -150,6 +149,7 @@ export interface FileRouteTypes { | '/_library/$project/$version/' | '/$project/$version/docs/framework/$framework' | '/$project/$version/docs/framework/$framework/$' + | '/$project/$version/docs/framework/$framework/{$}.md' | '/$project/$version/docs/framework/$framework/' | '/$project/$version/docs/framework/$framework/examples/$' fileRoutesById: FileRoutesById @@ -160,27 +160,6 @@ export interface RootRouteChildren { ProjectVersionDocsIndexRoute: typeof ProjectVersionDocsIndexRoute ProjectVersionDocsFrameworkFrameworkRoute: typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren } -export interface FileServerRoutesByFullPath { - '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute -} -export interface FileServerRoutesByTo { - '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/$project/$version/docs/framework/$framework/{$}.md': typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/$project/$version/docs/framework/$framework/{$}.md' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/$project/$version/docs/framework/$framework/{$}.md' - id: '__root__' | '/$project/$version/docs/framework/$framework/{$}.md' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute: typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute -} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -240,6 +219,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkIndexRouteImport parentRoute: typeof ProjectVersionDocsFrameworkFrameworkRoute } + '/$project/$version/docs/framework/$framework/{$}.md': { + id: '/$project/$version/docs/framework/$framework/{$}.md' + path: '/{$}.md' + fullPath: '/$project/$version/docs/framework/$framework/{$}.md' + preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport + parentRoute: typeof ProjectVersionDocsFrameworkFrameworkRoute + } '/$project/$version/docs/framework/$framework/$': { id: '/$project/$version/docs/framework/$framework/$' path: '/$' @@ -256,17 +242,6 @@ declare module '@tanstack/react-router' { } } } -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/$project/$version/docs/framework/$framework/{$}.md': { - id: '/$project/$version/docs/framework/$framework/{$}.md' - path: '/$project/$version/docs/framework/$framework/{$}.md' - fullPath: '/$project/$version/docs/framework/$framework/{$}.md' - preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRouteImport - parentRoute: typeof rootServerRouteImport - } - } -} interface LibraryProjectRouteChildren { LibraryProjectVersionIndexRoute: typeof LibraryProjectVersionIndexRoute @@ -295,6 +270,7 @@ const LibraryRouteWithChildren = interface ProjectVersionDocsFrameworkFrameworkRouteChildren { ProjectVersionDocsFrameworkFrameworkSplatRoute: typeof ProjectVersionDocsFrameworkFrameworkSplatRoute + ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute: typeof ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute ProjectVersionDocsFrameworkFrameworkIndexRoute: typeof ProjectVersionDocsFrameworkFrameworkIndexRoute ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute: typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute } @@ -303,6 +279,8 @@ const ProjectVersionDocsFrameworkFrameworkRouteChildren: ProjectVersionDocsFrame { ProjectVersionDocsFrameworkFrameworkSplatRoute: ProjectVersionDocsFrameworkFrameworkSplatRoute, + ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute: + ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute, ProjectVersionDocsFrameworkFrameworkIndexRoute: ProjectVersionDocsFrameworkFrameworkIndexRoute, ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute: @@ -324,10 +302,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute: - ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdServerRoute, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/react-start/website/src/router.tsx b/e2e/react-start/website/src/router.tsx index 28304cb690d..ef3744130e9 100644 --- a/e2e/react-start/website/src/router.tsx +++ b/e2e/react-start/website/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: 'intent', @@ -15,9 +15,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/react-start/website/src/routes/$project.$version.docs.framework.$framework.{$}[.]md.tsx b/e2e/react-start/website/src/routes/$project.$version.docs.framework.$framework.{$}[.]md.tsx index d1deb64810b..f9cdba1c171 100644 --- a/e2e/react-start/website/src/routes/$project.$version.docs.framework.$framework.{$}[.]md.tsx +++ b/e2e/react-start/website/src/routes/$project.$version.docs.framework.$framework.{$}[.]md.tsx @@ -1,25 +1,26 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { getDocument } from '~/server/document' -export const ServerRoute = createServerFileRoute( +export const Route = createFileRoute( '/$project/$version/docs/framework/$framework/{$}.md', -).methods({ - GET: async ({ params }) => { - const splat = params['_splat'] || '' - const docPath = splat.split('.md')[0] - - if (!docPath) { - return new Response('Document not found', { status: 404 }) - } - - const doc = await getDocument({ data: docPath }) - const markdown = `# ${doc.title}\n\n${doc.content}` - - return new Response(markdown, { - headers: { - 'Content-Type': 'text/markdown', - 'Content-Disposition': `inline; filename="${doc.title}.md"`, +)({ + server: { + handlers: { + GET: async ({ params }) => { + const splat = params['_splat'] || '' + const docPath = splat.split('.md')[0] + if (!docPath) { + return new Response('Document not found', { status: 404 }) + } + const doc = await getDocument({ data: docPath }) + const markdown = `# ${doc.title}\n\n${doc.content}` + return new Response(markdown, { + headers: { + 'Content-Type': 'text/markdown', + 'Content-Disposition': `inline; filename="${doc.title}.md"`, + }, + }) }, - }) + }, }, }) diff --git a/e2e/react-start/website/src/server/document.tsx b/e2e/react-start/website/src/server/document.tsx index 0f8372b2516..502040dd45f 100644 --- a/e2e/react-start/website/src/server/document.tsx +++ b/e2e/react-start/website/src/server/document.tsx @@ -41,7 +41,7 @@ export const getDocumentHeads = createServerFn({ method: 'GET' }).handler( ) export const getDocument = createServerFn({ method: 'GET' }) - .validator((id: string) => id) + .inputValidator((id: string) => id) .handler(async ({ data: id }) => { await new Promise((resolve) => setTimeout(resolve, 200)) diff --git a/e2e/react-start/website/src/server/projects.tsx b/e2e/react-start/website/src/server/projects.tsx index 11e75e2b22f..5409769237c 100644 --- a/e2e/react-start/website/src/server/projects.tsx +++ b/e2e/react-start/website/src/server/projects.tsx @@ -13,7 +13,7 @@ export const getProjects = createServerFn({ method: 'GET' }).handler( ) export const getProject = createServerFn({ method: 'GET' }) - .validator((project: string) => project) + .inputValidator((project: string) => project) .handler(async (ctx) => { await new Promise((resolve) => setTimeout(resolve, 200)) diff --git a/e2e/react-start/website/vite.config.ts b/e2e/react-start/website/vite.config.ts index a1b3d840f53..c2c28ae93b7 100644 --- a/e2e/react-start/website/vite.config.ts +++ b/e2e/react-start/website/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/e2e/solid-router/basic-file-based-code-splitting/package.json b/e2e/solid-router/basic-file-based-code-splitting/package.json index 6a929a45bce..05739aa9c5a 100644 --- a/e2e/solid-router/basic-file-based-code-splitting/package.json +++ b/e2e/solid-router/basic-file-based-code-splitting/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/e2e/solid-router/basic-file-based-code-splitting/src/routeTree.gen.ts b/e2e/solid-router/basic-file-based-code-splitting/src/routeTree.gen.ts index 0fedabf7621..f52487e717b 100644 --- a/e2e/solid-router/basic-file-based-code-splitting/src/routeTree.gen.ts +++ b/e2e/solid-router/basic-file-based-code-splitting/src/routeTree.gen.ts @@ -147,18 +147,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/without-loader': { + id: '/without-loader' + path: '/without-loader' + fullPath: '/without-loader' + preLoaderRoute: typeof WithoutLoaderRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutRouteImport + '/viewport-test': { + id: '/viewport-test' + path: '/viewport-test' + fullPath: '/viewport-test' + preLoaderRoute: typeof ViewportTestRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -168,26 +168,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/viewport-test': { - id: '/viewport-test' - path: '/viewport-test' - fullPath: '/viewport-test' - preLoaderRoute: typeof ViewportTestRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/without-loader': { - id: '/without-loader' - path: '/without-loader' - fullPath: '/without-loader' - preLoaderRoute: typeof WithoutLoaderRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } '/posts/$postId': { id: '/posts/$postId' @@ -196,19 +196,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute - } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } '/_layout/_layout-2/layout-b': { id: '/_layout/_layout-2/layout-b' @@ -217,6 +210,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport parentRoute: typeof LayoutLayout2Route } + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route + } } } diff --git a/e2e/solid-router/basic-file-based/package.json b/e2e/solid-router/basic-file-based/package.json index 50b911f89af..f3a3019b46c 100644 --- a/e2e/solid-router/basic-file-based/package.json +++ b/e2e/solid-router/basic-file-based/package.json @@ -25,8 +25,8 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", + "vite-plugin-solid": "^2.11.8", "combinate": "^1.1.11", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic-scroll-restoration/package.json b/e2e/solid-router/basic-scroll-restoration/package.disabled.json similarity index 93% rename from e2e/solid-router/basic-scroll-restoration/package.json rename to e2e/solid-router/basic-scroll-restoration/package.disabled.json index 99eadce4ed6..b1eef167025 100644 --- a/e2e/solid-router/basic-scroll-restoration/package.json +++ b/e2e/solid-router/basic-scroll-restoration/package.disabled.json @@ -23,7 +23,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic-scroll-restoration/src/main.tsx b/e2e/solid-router/basic-scroll-restoration/src/main.tsx index ea3d1f7726a..c9f4ca03eff 100644 --- a/e2e/solid-router/basic-scroll-restoration/src/main.tsx +++ b/e2e/solid-router/basic-scroll-restoration/src/main.tsx @@ -45,7 +45,7 @@ function RootComponent() { const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: IndexComponent, }) @@ -74,7 +74,7 @@ function IndexComponent() { const aboutRoute = createRoute({ getParentRoute: () => rootRoute, path: '/about', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: AboutComponent, }) @@ -99,7 +99,7 @@ function AboutComponent() { const byElementRoute = createRoute({ getParentRoute: () => rootRoute, path: '/by-element', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: ByElementComponent, }) @@ -114,7 +114,7 @@ function ByElementComponent() { }) // Let's use TanStack Virtual to virtualize some content! - let virtualizerParentRef: any = null + const virtualizerParentRef: any = null const virtualizer = createVirtualizer({ count: 10000, getScrollElement: () => virtualizerParentRef?.current, @@ -194,7 +194,7 @@ function ByElementComponent() { const fooRoute = createRoute({ getParentRoute: () => rootRoute, path: '/foo', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: FooComponent, }) @@ -219,7 +219,7 @@ function FooComponent() { const barRoute = createRoute({ getParentRoute: () => rootRoute, path: '/bar', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: BarComponent, }) diff --git a/e2e/solid-router/basic-solid-query-file-based/package.json b/e2e/solid-router/basic-solid-query-file-based/package.json index cff6d8c71ca..2d9ccafe208 100644 --- a/e2e/solid-router/basic-solid-query-file-based/package.json +++ b/e2e/solid-router/basic-solid-query-file-based/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic-solid-query/package.json b/e2e/solid-router/basic-solid-query/package.json index 61c7fa9fd36..4a1346f52ee 100644 --- a/e2e/solid-router/basic-solid-query/package.json +++ b/e2e/solid-router/basic-solid-query/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic-virtual-file-based/package.json b/e2e/solid-router/basic-virtual-file-based/package.json index 7e6f8ec3cda..3a19c7f6023 100644 --- a/e2e/solid-router/basic-virtual-file-based/package.json +++ b/e2e/solid-router/basic-virtual-file-based/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic-virtual-file-based/src/routeTree.gen.ts b/e2e/solid-router/basic-virtual-file-based/src/routeTree.gen.ts index abbf5c8056a..0ba11d91ea4 100644 --- a/e2e/solid-router/basic-virtual-file-based/src/routeTree.gen.ts +++ b/e2e/solid-router/basic-virtual-file-based/src/routeTree.gen.ts @@ -168,11 +168,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -182,25 +182,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport - parentRoute: typeof rootRouteImport - } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof postsPostsHomeRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport parentRoute: typeof postsPostsRoute } '/_first/_second': { @@ -210,32 +203,25 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/_first/_second/layout-a': { - id: '/_first/_second/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof aRouteImport - parentRoute: typeof layoutSecondLayoutRoute - } - '/_first/_second/layout-b': { - id: '/_first/_second/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport } - '/classic/hello/universe': { - id: '/classic/hello/universe' - path: '/universe' - fullPath: '/classic/hello/universe' - preLoaderRoute: typeof ClassicHelloUniverseRouteImport + '/classic/hello/': { + id: '/classic/hello/' + path: '/' + fullPath: '/classic/hello/' + preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } '/classic/hello/world': { @@ -245,13 +231,27 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloWorldRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/': { - id: '/classic/hello/' - path: '/' - fullPath: '/classic/hello/' - preLoaderRoute: typeof ClassicHelloIndexRouteImport + '/classic/hello/universe': { + id: '/classic/hello/universe' + path: '/universe' + fullPath: '/classic/hello/universe' + preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } + '/_first/_second/layout-b': { + id: '/_first/_second/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } + '/_first/_second/layout-a': { + id: '/_first/_second/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof aRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/e2e/solid-router/basic-virtual-named-export-config-file-based/package.json b/e2e/solid-router/basic-virtual-named-export-config-file-based/package.json index 212cb73be7e..6f57ade84c2 100644 --- a/e2e/solid-router/basic-virtual-named-export-config-file-based/package.json +++ b/e2e/solid-router/basic-virtual-named-export-config-file-based/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/basic/package.json b/e2e/solid-router/basic/package.json index 96341481850..930f065ce8d 100644 --- a/e2e/solid-router/basic/package.json +++ b/e2e/solid-router/basic/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-router/scroll-restoration-sandbox-vite/package.json b/e2e/solid-router/scroll-restoration-sandbox-vite/package.json index 8d41ebe795f..c39283cc584 100644 --- a/e2e/solid-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/solid-router/scroll-restoration-sandbox-vite/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", - "vite-plugin-solid": "^2.11.2", - "vite": "^6.3.5" + "vite-plugin-solid": "^2.11.8", + "vite": "^7.1.1" } } diff --git a/e2e/solid-start/basic-tsr-config/package.json b/e2e/solid-start/basic-tsr-config/package.json index 97d12fa5ba0..e94beef5ee2 100644 --- a/e2e/solid-start/basic-tsr-config/package.json +++ b/e2e/solid-start/basic-tsr-config/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "rimraf ./count.txt && vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -15,12 +15,14 @@ "@tanstack/solid-router-devtools": "workspace:^", "@tanstack/solid-start": "workspace:^", "solid-js": "^1.9.5", - "vite": "6.3.5" + "vite": "^7.1.1" }, "devDependencies": { "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", + "srvx": "^0.8.6", "typescript": "^5.7.2", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/basic-tsr-config/src/app/routeTree.gen.ts b/e2e/solid-start/basic-tsr-config/src/app/routeTree.gen.ts index 15d08c1472e..9d968f66df2 100644 --- a/e2e/solid-start/basic-tsr-config/src/app/routeTree.gen.ts +++ b/e2e/solid-start/basic-tsr-config/src/app/routeTree.gen.ts @@ -57,3 +57,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/basic-tsr-config/src/app/router.tsx b/e2e/solid-start/basic-tsr-config/src/app/router.tsx index e230377cb19..b8757ff85b5 100644 --- a/e2e/solid-start/basic-tsr-config/src/app/router.tsx +++ b/e2e/solid-start/basic-tsr-config/src/app/router.tsx @@ -1,17 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/solid-start/basic-tsr-config/src/app/routes/index.tsx b/e2e/solid-start/basic-tsr-config/src/app/routes/index.tsx index 5c96a118802..b4a514d42a1 100644 --- a/e2e/solid-start/basic-tsr-config/src/app/routes/index.tsx +++ b/e2e/solid-start/basic-tsr-config/src/app/routes/index.tsx @@ -12,7 +12,7 @@ const getCount = createServerFn({ }) const updateCount = createServerFn({ method: 'POST' }) - .validator((d: number) => d) + .inputValidator((d: number) => d) .handler(async ({ data }) => { const count = await getCount() await fs.promises.writeFile(filePath, `${count + data}`) diff --git a/e2e/solid-start/basic-tsr-config/tests/app.spec.ts b/e2e/solid-start/basic-tsr-config/tests/app.spec.ts index e3488df952b..782007684ab 100644 --- a/e2e/solid-start/basic-tsr-config/tests/app.spec.ts +++ b/e2e/solid-start/basic-tsr-config/tests/app.spec.ts @@ -3,6 +3,7 @@ import { expect, test } from '@playwright/test' test('opening the app', async ({ page }) => { await page.goto('/') + await page.waitForLoadState('networkidle') await expect(page.getByTestId('add-button')).toContainText('Add 1 to 0?') await page.getByTestId('add-button').click() await page.waitForLoadState('networkidle') diff --git a/e2e/solid-start/basic-tsr-config/vite.config.ts b/e2e/solid-start/basic-tsr-config/vite.config.ts index 347962b83da..fa5673a503c 100644 --- a/e2e/solid-start/basic-tsr-config/vite.config.ts +++ b/e2e/solid-start/basic-tsr-config/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -11,9 +12,8 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart({ - tsr: { - srcDirectory: './src/app', - }, + srcDirectory: './src/app', }), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json index c7b631c1d6c..d3515f9eaf1 100644 --- a/e2e/solid-start/basic/package.json +++ b/e2e/solid-start/basic/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -17,7 +17,7 @@ "redaxios": "^0.5.1", "solid-js": "^1.9.5", "tailwind-merge": "^2.6.0", - "vite": "6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -27,9 +27,10 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.2", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/basic/src/client.tsx b/e2e/solid-start/basic/src/client.tsx index b2fdcc95054..76f568709bf 100644 --- a/e2e/solid-start/basic/src/client.tsx +++ b/e2e/solid-start/basic/src/client.tsx @@ -1,11 +1,8 @@ // DO NOT DELETE THIS FILE!!! // This file is a good smoke test to make sure the custom client entry is working import { hydrate } from 'solid-js/web' -import { StartClient } from '@tanstack/solid-start' -import { createRouter } from './router' +import { StartClient } from '@tanstack/solid-start/client' console.log("[client-entry]: using custom client entry in 'src/client.tsx'") -const router = createRouter() - -hydrate(() => , document.body) +hydrate(() => , document.body) diff --git a/e2e/solid-start/basic/src/components/throwRedirect.ts b/e2e/solid-start/basic/src/components/throwRedirect.ts index fd4a056324b..3de2515f2b4 100644 --- a/e2e/solid-start/basic/src/components/throwRedirect.ts +++ b/e2e/solid-start/basic/src/components/throwRedirect.ts @@ -2,7 +2,7 @@ import { redirect } from '@tanstack/solid-router' import { createServerFn } from '@tanstack/solid-start' export const throwRedirect = createServerFn() - .validator( + .inputValidator( (opts: { target: 'internal' | 'external' reloadDocument?: boolean diff --git a/e2e/solid-start/basic/src/routeTree.gen.ts b/e2e/solid-start/basic/src/routeTree.gen.ts index cf6db920d4e..0e5dc131184 100644 --- a/e2e/solid-start/basic/src/routeTree.gen.ts +++ b/e2e/solid-start/basic/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/solid-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as StreamRouteImport } from './routes/stream' @@ -34,21 +32,19 @@ import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' import { Route as RedirectTargetServerFnIndexRouteImport } from './routes/redirect/$target/serverFn/index' import { Route as RedirectTargetServerFnViaUseServerFnRouteImport } from './routes/redirect/$target/serverFn/via-useServerFn' import { Route as RedirectTargetServerFnViaLoaderRouteImport } from './routes/redirect/$target/serverFn/via-loader' import { Route as RedirectTargetServerFnViaBeforeLoadRouteImport } from './routes/redirect/$target/serverFn/via-beforeLoad' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api/users' -import { ServerRoute as ApiUsersUserIdServerRouteImport } from './routes/api/users.$userId' - -const rootServerRouteImport = createServerRootRoute() const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -165,6 +161,11 @@ const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ path: '/via-beforeLoad', getParentRoute: () => NotFoundRouteRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -190,6 +191,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', @@ -224,16 +230,6 @@ const RedirectTargetServerFnViaBeforeLoadRoute = path: '/serverFn/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersUserIdServerRoute = ApiUsersUserIdServerRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -246,6 +242,7 @@ export interface FileRoutesByFullPath { '/scripts': typeof ScriptsRoute '/stream': typeof StreamRoute '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -260,6 +257,7 @@ export interface FileRoutesByFullPath { '/users/': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -276,6 +274,7 @@ export interface FileRoutesByTo { '/links': typeof LinksRoute '/scripts': typeof ScriptsRoute '/stream': typeof StreamRoute + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -289,6 +288,7 @@ export interface FileRoutesByTo { '/users': typeof UsersIndexRoute '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -312,6 +312,7 @@ export interface FileRoutesById { '/stream': typeof StreamRoute '/users': typeof UsersRouteWithChildren '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-loader': typeof NotFoundViaLoaderRoute '/posts/$postId': typeof PostsPostIdRoute @@ -326,6 +327,7 @@ export interface FileRoutesById { '/users/': typeof UsersIndexRoute '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute '/redirect/$target/via-loader': typeof RedirectTargetViaLoaderRoute @@ -348,6 +350,7 @@ export interface FileRouteTypes { | '/scripts' | '/stream' | '/users' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -362,6 +365,7 @@ export interface FileRouteTypes { | '/users/' | '/layout-a' | '/layout-b' + | '/api/users/$userId' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -378,6 +382,7 @@ export interface FileRouteTypes { | '/links' | '/scripts' | '/stream' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -391,6 +396,7 @@ export interface FileRouteTypes { | '/users' | '/layout-a' | '/layout-b' + | '/api/users/$userId' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -413,6 +419,7 @@ export interface FileRouteTypes { | '/stream' | '/users' | '/_layout/_layout-2' + | '/api/users' | '/not-found/via-beforeLoad' | '/not-found/via-loader' | '/posts/$postId' @@ -427,6 +434,7 @@ export interface FileRouteTypes { | '/users/' | '/_layout/_layout-2/layout-a' | '/_layout/_layout-2/layout-b' + | '/api/users/$userId' | '/posts_/$postId/deep' | '/redirect/$target/via-beforeLoad' | '/redirect/$target/via-loader' @@ -449,34 +457,11 @@ export interface RootRouteChildren { ScriptsRoute: typeof ScriptsRoute StreamRoute: typeof StreamRoute UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren RedirectTargetRoute: typeof RedirectTargetRouteWithChildren RedirectIndexRoute: typeof RedirectIndexRoute PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$userId' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$userId' - id: '__root__' | '/api/users' | '/api/users/$userId' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/solid-router' { interface FileRoutesByPath { @@ -641,6 +626,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport parentRoute: typeof NotFoundRouteRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_layout-2': { id: '/_layout/_layout-2' path: '' @@ -676,6 +668,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } '/_layout/_layout-2/layout-b': { id: '/_layout/_layout-2/layout-b' path: '/layout-b' @@ -720,24 +719,6 @@ declare module '@tanstack/solid-router' { } } } -declare module '@tanstack/solid-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users/$userId': { - id: '/api/users/$userId' - path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute - } - } -} interface NotFoundRouteRouteChildren { NotFoundViaBeforeLoadRoute: typeof NotFoundViaBeforeLoadRoute @@ -819,6 +800,18 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) +interface ApiUsersRouteChildren { + ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute +} + +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersUserIdRoute: ApiUsersUserIdRoute, +} + +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, +) + interface RedirectTargetRouteChildren { RedirectTargetViaBeforeLoadRoute: typeof RedirectTargetViaBeforeLoadRoute RedirectTargetViaLoaderRoute: typeof RedirectTargetViaLoaderRoute @@ -845,18 +838,6 @@ const RedirectTargetRouteWithChildren = RedirectTargetRoute._addFileChildren( RedirectTargetRouteChildren, ) -interface ApiUsersServerRouteChildren { - ApiUsersUserIdServerRoute: typeof ApiUsersUserIdServerRoute -} - -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersUserIdServerRoute: ApiUsersUserIdServerRoute, -} - -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, -) - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, NotFoundRouteRoute: NotFoundRouteRouteWithChildren, @@ -869,6 +850,7 @@ const rootRouteChildren: RootRouteChildren = { ScriptsRoute: ScriptsRoute, StreamRoute: StreamRoute, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, RedirectTargetRoute: RedirectTargetRouteWithChildren, RedirectIndexRoute: RedirectIndexRoute, PostsPostIdDeepRoute: PostsPostIdDeepRoute, @@ -876,9 +858,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/solid-start/basic/src/router.tsx b/e2e/solid-start/basic/src/router.tsx index c45bed4758c..5da353c1ce2 100644 --- a/e2e/solid-start/basic/src/router.tsx +++ b/e2e/solid-start/basic/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/solid-start/basic/src/routes/api/users.$userId.ts b/e2e/solid-start/basic/src/routes/api/users.$userId.ts index a653b39ee0c..7f637260f51 100644 --- a/e2e/solid-start/basic/src/routes/api/users.$userId.ts +++ b/e2e/solid-start/basic/src/routes/api/users.$userId.ts @@ -1,4 +1,4 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { json } from '@tanstack/solid-start' import type { User } from '~/utils/users' @@ -8,25 +8,27 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users/$userId').methods({ - GET: async ({ params, request }) => { - console.info(`Fetching users by id=${params.userId}... @`, request.url) - try { - const res = await fetch(`${queryURL}/users/${params.userId}`) - if (!res.ok) { - throw new Error('Failed to fetch user') - } - - const user = (await res.json()) as User - - return json({ - id: user.id, - name: user.name, - email: user.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } +export const Route = createFileRoute('/api/users/$userId')({ + server: { + handlers: { + GET: async ({ params, request }) => { + console.info(`Fetching users by id=${params.userId}... @`, request.url) + try { + const res = await fetch(`${queryURL}/users/${params.userId}`) + if (!res.ok) { + throw new Error('Failed to fetch user') + } + const user = (await res.json()) as User + return json({ + id: user.id, + name: user.name, + email: user.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/e2e/solid-start/basic/src/routes/api/users.ts b/e2e/solid-start/basic/src/routes/api/users.ts index c8d04ac2f2e..04aec562e14 100644 --- a/e2e/solid-start/basic/src/routes/api/users.ts +++ b/e2e/solid-start/basic/src/routes/api/users.ts @@ -1,8 +1,8 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { createMiddleware, json } from '@tanstack/solid-start' import type { User } from '~/utils/users' -const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( +const userLoggerMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: /users') const result = await next() @@ -12,7 +12,7 @@ const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testParentMiddleware = createMiddleware({ type: 'request' }).server( +const testParentMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: testParentMiddleware') const result = await next() @@ -22,20 +22,18 @@ const testParentMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next, request }) => { console.info('In: testMiddleware') const result = await next() result.response.headers.set('x-test', 'true') - // if (Math.random() > 0.5) { // throw new Response(null, { // status: 302, // headers: { Location: 'https://www.google.com' }, // }) // } - console.info('Out: testMiddleware') return result }) @@ -46,20 +44,22 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users') - .middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware]) - .methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await fetch(`${queryURL}/users`) - if (!res.ok) { - throw new Error('Failed to fetch users') - } - - const data = (await res.json()) as Array - - const list = data.slice(0, 10) - - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) +export const Route = createFileRoute('/api/users')({ + server: { + middleware: [testMiddleware, userLoggerMiddleware, testParentMiddleware], + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await fetch(`${queryURL}/users`) + if (!res.ok) { + throw new Error('Failed to fetch users') + } + const data = (await res.json()) as Array + const list = data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, }, - }) + }, +}) diff --git a/e2e/solid-start/basic/src/routes/deferred.tsx b/e2e/solid-start/basic/src/routes/deferred.tsx index 2a536434538..5e4e119b5f0 100644 --- a/e2e/solid-start/basic/src/routes/deferred.tsx +++ b/e2e/solid-start/basic/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/solid-start' import { Suspense, createSignal } from 'solid-js' const personServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(({ data }) => { return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(async ({ data }) => { await new Promise((r) => setTimeout(r, 1000)) return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/e2e/solid-start/basic/src/routes/posts.index.tsx b/e2e/solid-start/basic/src/routes/posts.index.tsx index ac7c378e605..c7d8cfe19c7 100644 --- a/e2e/solid-start/basic/src/routes/posts.index.tsx +++ b/e2e/solid-start/basic/src/routes/posts.index.tsx @@ -1,4 +1,5 @@ import { createFileRoute } from '@tanstack/solid-router' + export const Route = createFileRoute('/posts/')({ component: PostsIndexComponent, }) diff --git a/e2e/solid-start/basic/src/routes/search-params/route.tsx b/e2e/solid-start/basic/src/routes/search-params/route.tsx index c324b10d65d..5d9fc675158 100644 --- a/e2e/solid-start/basic/src/routes/search-params/route.tsx +++ b/e2e/solid-start/basic/src/routes/search-params/route.tsx @@ -1,4 +1,5 @@ import { createFileRoute } from '@tanstack/solid-router' + export const Route = createFileRoute('/search-params')({ beforeLoad: async () => { await new Promise((resolve) => setTimeout(resolve, 1000)) diff --git a/e2e/solid-start/basic/src/server.ts b/e2e/solid-start/basic/src/server.ts index f9fbb3a2d58..d48e6df3494 100644 --- a/e2e/solid-start/basic/src/server.ts +++ b/e2e/solid-start/basic/src/server.ts @@ -1,13 +1,11 @@ // DO NOT DELETE THIS FILE!!! // This file is a good smoke test to make sure the custom server entry is working -import { - createStartHandler, - defaultStreamHandler, -} from '@tanstack/solid-start/server' -import { createRouter } from './router' +import handler from '@tanstack/solid-start/server-entry' console.log("[server-entry]: using custom server entry in 'src/server.ts'") -export default createStartHandler({ - createRouter, -})(defaultStreamHandler) +export default { + fetch(request: Request) { + return handler.fetch(request) + }, +} diff --git a/e2e/solid-start/basic/src/utils/posts.tsx b/e2e/solid-start/basic/src/utils/posts.tsx index 05ca4651fb3..83a03ea163b 100644 --- a/e2e/solid-start/basic/src/utils/posts.tsx +++ b/e2e/solid-start/basic/src/utils/posts.tsx @@ -15,7 +15,7 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/solid-start/basic/tests/fixture.ts b/e2e/solid-start/basic/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/solid-start/basic/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/solid-start/basic/tests/navigation.spec.ts b/e2e/solid-start/basic/tests/navigation.spec.ts index 62433a0134d..6f90383afbc 100644 --- a/e2e/solid-start/basic/tests/navigation.spec.ts +++ b/e2e/solid-start/basic/tests/navigation.spec.ts @@ -1,5 +1,11 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' +test.use({ + whitelistErrors: [ + /Failed to load resource: the server responded with a status of 404/, + ], +}) test('Navigating to post', async ({ page }) => { await page.goto('/') diff --git a/e2e/solid-start/basic/tests/not-found.spec.ts b/e2e/solid-start/basic/tests/not-found.spec.ts index 0d83ab52802..ab1c94eee90 100644 --- a/e2e/solid-start/basic/tests/not-found.spec.ts +++ b/e2e/solid-start/basic/tests/not-found.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' import combinateImport from 'combinate' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport diff --git a/e2e/solid-start/basic/tests/redirect.spec.ts b/e2e/solid-start/basic/tests/redirect.spec.ts index 87e93a42e6b..78c57d6a00b 100644 --- a/e2e/solid-start/basic/tests/redirect.spec.ts +++ b/e2e/solid-start/basic/tests/redirect.spec.ts @@ -4,9 +4,9 @@ import combinateImport from 'combinate' import { getDummyServerPort, getTestServerPort, + test, } from '@tanstack/router-e2e-utils' import packageJson from '../package.json' with { type: 'json' } -import { test } from './fixture' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport @@ -184,6 +184,8 @@ test.describe('redirects', () => { await page.goto(`/redirect/${target}/serverFn/via-useServerFn?${q}`) + await page.waitForLoadState('networkidle') + const button = page.getByTestId('redirect-on-click') let fullPageLoad = false diff --git a/e2e/solid-start/basic/tests/search-params.spec.ts b/e2e/solid-start/basic/tests/search-params.spec.ts index 23cd97517d6..d7ce6178900 100644 --- a/e2e/solid-start/basic/tests/search-params.spec.ts +++ b/e2e/solid-start/basic/tests/search-params.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' import type { Response } from '@playwright/test' function expectRedirect(response: Response | null, endsWith: string) { diff --git a/e2e/solid-start/basic/tests/streaming.spec.ts b/e2e/solid-start/basic/tests/streaming.spec.ts index 252bb192aaf..15f60b7deb9 100644 --- a/e2e/solid-start/basic/tests/streaming.spec.ts +++ b/e2e/solid-start/basic/tests/streaming.spec.ts @@ -1,4 +1,5 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' test('Navigating to deferred route', async ({ page }) => { await page.goto('/') diff --git a/e2e/solid-start/basic/vite.config.ts b/e2e/solid-start/basic/vite.config.ts index 3af67d62ad2..1a2219f4435 100644 --- a/e2e/solid-start/basic/vite.config.ts +++ b/e2e/solid-start/basic/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -10,6 +11,7 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({}), + tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/custom-basepath/express-server.ts b/e2e/solid-start/custom-basepath/express-server.ts new file mode 100644 index 00000000000..6fa802e4476 --- /dev/null +++ b/e2e/solid-start/custom-basepath/express-server.ts @@ -0,0 +1,44 @@ +import express from 'express' +import { toNodeHandler } from 'srvx/node' + +const DEVELOPMENT = process.env.NODE_ENV === 'development' +const PORT = Number.parseInt(process.env.PORT || '3000') + +const app = express() + +if (DEVELOPMENT) { + const viteDevServer = await import('vite').then((vite) => + vite.createServer({ + server: { middlewareMode: true }, + }), + ) + app.use(viteDevServer.middlewares) + app.use(async (req, res, next) => { + try { + const { default: serverEntry } = + await viteDevServer.ssrLoadModule('./src/server.ts') + const handler = toNodeHandler(serverEntry.fetch) + await handler(req, res) + } catch (error) { + if (typeof error === 'object' && error instanceof Error) { + viteDevServer.ssrFixStacktrace(error) + } + next(error) + } + }) +} else { + const { default: handler } = await import('./dist/server/server.js') + const nodeHandler = toNodeHandler(handler.fetch) + app.use('/custom/basepath', express.static('dist/client')) + app.use(async (req, res, next) => { + try { + await nodeHandler(req, res) + } catch (error) { + next(error) + } + }) +} + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`) +}) diff --git a/e2e/solid-start/custom-basepath/package.json b/e2e/solid-start/custom-basepath/package.json index 1f668703a33..1b0755af310 100644 --- a/e2e/solid-start/custom-basepath/package.json +++ b/e2e/solid-start/custom-basepath/package.json @@ -4,32 +4,33 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vite dev --port 3000", - "dev:e2e": "vite dev", + "dev": "cross-env NODE_ENV=development tsx express-server.ts", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "tsx express-server.ts", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { "@tanstack/solid-router": "workspace:^", "@tanstack/solid-router-devtools": "workspace:^", "@tanstack/solid-start": "workspace:^", + "express": "^4.21.2", "redaxios": "^0.5.1", - "solid-js": "^1.9.5", - "tailwind-merge": "^2.6.0", - "vite": "6.3.5", - "zod": "^3.24.2" + "solid-js": "^1.9.5" }, "devDependencies": { "@playwright/test": "^1.50.1", "@tanstack/router-e2e-utils": "workspace:^", + "@types/express": "^5.0.3", "@types/node": "^22.10.2", "autoprefixer": "^10.4.20", - "combinate": "^1.1.11", + "cross-env": "^10.0.0", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", + "tsx": "^4.20.3", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.2", + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/custom-basepath/src/routeTree.gen.ts b/e2e/solid-start/custom-basepath/src/routeTree.gen.ts index 124ac4d29f0..507455c73b2 100644 --- a/e2e/solid-start/custom-basepath/src/routeTree.gen.ts +++ b/e2e/solid-start/custom-basepath/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/solid-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as PostsRouteImport } from './routes/posts' @@ -19,11 +17,9 @@ import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api/users' -import { ServerRoute as ApiUsersUserIdServerRouteImport } from './routes/api/users.$userId' - -const rootServerRouteImport = createServerRootRoute() +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -65,20 +61,20 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersUserIdServerRoute = ApiUsersUserIdServerRouteImport.update({ +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersServerRoute, + getParentRoute: () => ApiUsersRoute, } as any) export interface FileRoutesByFullPath { @@ -86,19 +82,23 @@ export interface FileRoutesByFullPath { '/deferred': typeof DeferredRoute '/posts': typeof PostsRouteWithChildren '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/deferred': typeof DeferredRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { @@ -107,10 +107,12 @@ export interface FileRoutesById { '/deferred': typeof DeferredRoute '/posts': typeof PostsRouteWithChildren '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { @@ -120,19 +122,23 @@ export interface FileRouteTypes { | '/deferred' | '/posts' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' + | '/api/users/$userId' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' | '/deferred' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' + | '/api/users/$userId' | '/posts/$postId/deep' id: | '__root__' @@ -140,10 +146,12 @@ export interface FileRouteTypes { | '/deferred' | '/posts' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' + | '/api/users/$userId' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } @@ -152,32 +160,9 @@ export interface RootRouteChildren { DeferredRoute: typeof DeferredRoute PostsRoute: typeof PostsRouteWithChildren UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$userId' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$userId' - id: '__root__' | '/api/users' | '/api/users/$userId' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/solid-router' { interface FileRoutesByPath { @@ -237,6 +222,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/posts_/$postId/deep': { id: '/posts_/$postId/deep' path: '/posts/$postId/deep' @@ -244,23 +236,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } - } -} -declare module '@tanstack/solid-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } '/api/users/$userId': { id: '/api/users/$userId' path: '/$userId' fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute } } } @@ -289,16 +270,16 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) -interface ApiUsersServerRouteChildren { - ApiUsersUserIdServerRoute: typeof ApiUsersUserIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersUserIdServerRoute: ApiUsersUserIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersUserIdRoute: ApiUsersUserIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { @@ -306,14 +287,17 @@ const rootRouteChildren: RootRouteChildren = { DeferredRoute: DeferredRoute, PostsRoute: PostsRouteWithChildren, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/solid-start/custom-basepath/src/router.tsx b/e2e/solid-start/custom-basepath/src/router.tsx index f2825f2bed1..b940fffea80 100644 --- a/e2e/solid-start/custom-basepath/src/router.tsx +++ b/e2e/solid-start/custom-basepath/src/router.tsx @@ -1,11 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' import { basepath } from './utils/basepath' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -16,9 +16,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/solid-start/custom-basepath/src/routes/api/users.$userId.ts b/e2e/solid-start/custom-basepath/src/routes/api/users.$userId.ts index a653b39ee0c..7f637260f51 100644 --- a/e2e/solid-start/custom-basepath/src/routes/api/users.$userId.ts +++ b/e2e/solid-start/custom-basepath/src/routes/api/users.$userId.ts @@ -1,4 +1,4 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { json } from '@tanstack/solid-start' import type { User } from '~/utils/users' @@ -8,25 +8,27 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users/$userId').methods({ - GET: async ({ params, request }) => { - console.info(`Fetching users by id=${params.userId}... @`, request.url) - try { - const res = await fetch(`${queryURL}/users/${params.userId}`) - if (!res.ok) { - throw new Error('Failed to fetch user') - } - - const user = (await res.json()) as User - - return json({ - id: user.id, - name: user.name, - email: user.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } +export const Route = createFileRoute('/api/users/$userId')({ + server: { + handlers: { + GET: async ({ params, request }) => { + console.info(`Fetching users by id=${params.userId}... @`, request.url) + try { + const res = await fetch(`${queryURL}/users/${params.userId}`) + if (!res.ok) { + throw new Error('Failed to fetch user') + } + const user = (await res.json()) as User + return json({ + id: user.id, + name: user.name, + email: user.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/e2e/solid-start/custom-basepath/src/routes/api/users.ts b/e2e/solid-start/custom-basepath/src/routes/api/users.ts index c8d04ac2f2e..04aec562e14 100644 --- a/e2e/solid-start/custom-basepath/src/routes/api/users.ts +++ b/e2e/solid-start/custom-basepath/src/routes/api/users.ts @@ -1,8 +1,8 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { createMiddleware, json } from '@tanstack/solid-start' import type { User } from '~/utils/users' -const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( +const userLoggerMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: /users') const result = await next() @@ -12,7 +12,7 @@ const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testParentMiddleware = createMiddleware({ type: 'request' }).server( +const testParentMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: testParentMiddleware') const result = await next() @@ -22,20 +22,18 @@ const testParentMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next, request }) => { console.info('In: testMiddleware') const result = await next() result.response.headers.set('x-test', 'true') - // if (Math.random() > 0.5) { // throw new Response(null, { // status: 302, // headers: { Location: 'https://www.google.com' }, // }) // } - console.info('Out: testMiddleware') return result }) @@ -46,20 +44,22 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` } -export const ServerRoute = createServerFileRoute('/api/users') - .middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware]) - .methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await fetch(`${queryURL}/users`) - if (!res.ok) { - throw new Error('Failed to fetch users') - } - - const data = (await res.json()) as Array - - const list = data.slice(0, 10) - - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) +export const Route = createFileRoute('/api/users')({ + server: { + middleware: [testMiddleware, userLoggerMiddleware, testParentMiddleware], + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await fetch(`${queryURL}/users`) + if (!res.ok) { + throw new Error('Failed to fetch users') + } + const data = (await res.json()) as Array + const list = data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, }, - }) + }, +}) diff --git a/e2e/solid-start/custom-basepath/src/routes/deferred.tsx b/e2e/solid-start/custom-basepath/src/routes/deferred.tsx index 2a536434538..5e4e119b5f0 100644 --- a/e2e/solid-start/custom-basepath/src/routes/deferred.tsx +++ b/e2e/solid-start/custom-basepath/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/solid-start' import { Suspense, createSignal } from 'solid-js' const personServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(({ data }) => { return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((data: { name: string }) => data) + .inputValidator((data: { name: string }) => data) .handler(async ({ data }) => { await new Promise((r) => setTimeout(r, 1000)) return { name: data.name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/e2e/solid-start/custom-basepath/src/server.ts b/e2e/solid-start/custom-basepath/src/server.ts new file mode 100644 index 00000000000..3682c04c200 --- /dev/null +++ b/e2e/solid-start/custom-basepath/src/server.ts @@ -0,0 +1,7 @@ +import handler from '@tanstack/solid-start/server-entry' + +export default { + fetch(request: Request) { + return handler.fetch(request) + }, +} diff --git a/e2e/solid-start/custom-basepath/src/utils/posts.tsx b/e2e/solid-start/custom-basepath/src/utils/posts.tsx index 05ca4651fb3..83a03ea163b 100644 --- a/e2e/solid-start/custom-basepath/src/utils/posts.tsx +++ b/e2e/solid-start/custom-basepath/src/utils/posts.tsx @@ -15,7 +15,7 @@ if (import.meta.env.VITE_NODE_ENV === 'test') { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/e2e/solid-start/custom-basepath/vite.config.ts b/e2e/solid-start/custom-basepath/vite.config.ts index d6784f657cf..4f0310eddf7 100644 --- a/e2e/solid-start/custom-basepath/vite.config.ts +++ b/e2e/solid-start/custom-basepath/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ base: '/custom/basepath', @@ -11,6 +12,7 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({}), + tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/scroll-restoration/package.json b/e2e/solid-start/scroll-restoration/package.json index 60f64e7d040..8527fa97e4b 100644 --- a/e2e/solid-start/scroll-restoration/package.json +++ b/e2e/solid-start/scroll-restoration/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -18,7 +18,7 @@ "redaxios": "^0.5.1", "solid-js": "^1.9.5", "tailwind-merge": "^2.6.0", - "vite": "6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -28,9 +28,10 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.6", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts b/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts index 05ad12de835..9d39430dd08 100644 --- a/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts +++ b/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts @@ -116,3 +116,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/scroll-restoration/src/router.tsx b/e2e/solid-start/scroll-restoration/src/router.tsx index b0449d7478d..6b397aa78d6 100644 --- a/e2e/solid-start/scroll-restoration/src/router.tsx +++ b/e2e/solid-start/scroll-restoration/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: 'intent', @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/solid-start/scroll-restoration/src/routes/index.tsx b/e2e/solid-start/scroll-restoration/src/routes/index.tsx index 0402a231551..b293629756b 100644 --- a/e2e/solid-start/scroll-restoration/src/routes/index.tsx +++ b/e2e/solid-start/scroll-restoration/src/routes/index.tsx @@ -1,5 +1,4 @@ -import * as Solid from 'solid-js' -import { Link, linkOptions, createFileRoute } from '@tanstack/solid-router' +import { Link, createFileRoute, linkOptions } from '@tanstack/solid-router' export const Route = createFileRoute('/')({ component: HomeComponent, diff --git a/e2e/solid-start/scroll-restoration/vite.config.ts b/e2e/solid-start/scroll-restoration/vite.config.ts index 3af67d62ad2..1a2219f4435 100644 --- a/e2e/solid-start/scroll-restoration/vite.config.ts +++ b/e2e/solid-start/scroll-restoration/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -10,6 +11,7 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({}), + tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/selective-ssr/package.json b/e2e/solid-start/selective-ssr/package.json index 3be0fee142f..67a902cfe23 100644 --- a/e2e/solid-start/selective-ssr/package.json +++ b/e2e/solid-start/selective-ssr/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -19,9 +19,11 @@ "devDependencies": { "@tanstack/router-e2e-utils": "workspace:^", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts index 070e249d585..96dd9ad640b 100644 --- a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts @@ -101,3 +101,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/selective-ssr/src/router.tsx b/e2e/solid-start/selective-ssr/src/router.tsx index e230377cb19..e924cb642f1 100644 --- a/e2e/solid-start/selective-ssr/src/router.tsx +++ b/e2e/solid-start/selective-ssr/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -12,6 +12,6 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/e2e/solid-start/selective-ssr/tests/app.spec.ts b/e2e/solid-start/selective-ssr/tests/app.spec.ts index b013dfb1694..aea216d5065 100644 --- a/e2e/solid-start/selective-ssr/tests/app.spec.ts +++ b/e2e/solid-start/selective-ssr/tests/app.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' const testCount = 7 diff --git a/e2e/solid-start/selective-ssr/tests/fixture.ts b/e2e/solid-start/selective-ssr/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/solid-start/selective-ssr/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/solid-start/selective-ssr/vite.config.ts b/e2e/solid-start/selective-ssr/vite.config.ts index 151f2248f87..20ffdb3d6e5 100644 --- a/e2e/solid-start/selective-ssr/vite.config.ts +++ b/e2e/solid-start/selective-ssr/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/server-functions/package.json b/e2e/solid-start/server-functions/package.json index 7a02e8266ea..fb3d6499347 100644 --- a/e2e/solid-start/server-functions/package.json +++ b/e2e/solid-start/server-functions/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -18,7 +18,7 @@ "redaxios": "^0.5.1", "solid-js": "^1.9.5", "tailwind-merge": "^2.6.0", - "vite": "6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -29,9 +29,10 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.6", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/server-functions/src/routeTree.gen.ts b/e2e/solid-start/server-functions/src/routeTree.gen.ts index a4d613214bc..25189685bb9 100644 --- a/e2e/solid-start/server-functions/src/routeTree.gen.ts +++ b/e2e/solid-start/server-functions/src/routeTree.gen.ts @@ -355,3 +355,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/server-functions/src/router.tsx b/e2e/solid-start/server-functions/src/router.tsx index c45bed4758c..da050c7db65 100644 --- a/e2e/solid-start/server-functions/src/router.tsx +++ b/e2e/solid-start/server-functions/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -17,6 +17,6 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/e2e/solid-start/server-functions/src/routes/consistent.tsx b/e2e/solid-start/server-functions/src/routes/consistent.tsx index 9e4c1b3b082..87fc44249cc 100644 --- a/e2e/solid-start/server-functions/src/routes/consistent.tsx +++ b/e2e/solid-start/server-functions/src/routes/consistent.tsx @@ -20,25 +20,25 @@ export const Route = createFileRoute('/consistent')({ }) const cons_getFn1 = createServerFn() - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return { payload: data } }) const cons_serverGetFn1 = createServerFn() - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(async ({ data }) => { return cons_getFn1({ data }) }) const cons_postFn1 = createServerFn({ method: 'POST' }) - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return { payload: data } }) const cons_serverPostFn1 = createServerFn({ method: 'POST' }) - .validator((d: { username: string }) => d) + .inputValidator((d: { username: string }) => d) .handler(({ data }) => { return cons_postFn1({ data }) }) diff --git a/e2e/solid-start/server-functions/src/routes/cookies/set.tsx b/e2e/solid-start/server-functions/src/routes/cookies/set.tsx index 32e06725cf1..f2d1c96f89c 100644 --- a/e2e/solid-start/server-functions/src/routes/cookies/set.tsx +++ b/e2e/solid-start/server-functions/src/routes/cookies/set.tsx @@ -18,14 +18,14 @@ export const Route = createFileRoute('/cookies/set')({ }) export const setCookieServerFn1 = createServerFn() - .validator(cookieSchema) + .inputValidator(cookieSchema) .handler(({ data }) => { setCookie(`cookie-1-${data.value}`, data.value) setCookie(`cookie-2-${data.value}`, data.value) }) export const setCookieServerFn2 = createServerFn() - .validator(cookieSchema) + .inputValidator(cookieSchema) .handler(({ data }) => { setCookie(`cookie-3-${data.value}`, data.value) setCookie(`cookie-4-${data.value}`, data.value) diff --git a/e2e/solid-start/server-functions/src/routes/env-only.tsx b/e2e/solid-start/server-functions/src/routes/env-only.tsx index df8f08eed20..85bcae0f60a 100644 --- a/e2e/solid-start/server-functions/src/routes/env-only.tsx +++ b/e2e/solid-start/server-functions/src/routes/env-only.tsx @@ -1,9 +1,13 @@ import { createFileRoute } from '@tanstack/solid-router' -import { clientOnly, createServerFn, serverOnly } from '@tanstack/solid-start' +import { + createClientOnlyFn, + createServerFn, + createServerOnlyFn, +} from '@tanstack/solid-start' import { createSignal } from 'solid-js' -const serverEcho = serverOnly((input: string) => 'server got: ' + input) -const clientEcho = clientOnly((input: string) => 'client got: ' + input) +const serverEcho = createServerOnlyFn((input: string) => 'server got: ' + input) +const clientEcho = createClientOnlyFn((input: string) => 'client got: ' + input) const testOnServer = createServerFn().handler(() => { const serverOnServer = serverEcho('hello') diff --git a/e2e/solid-start/server-functions/src/routes/headers.tsx b/e2e/solid-start/server-functions/src/routes/headers.tsx index 3b1ce8d499a..132f304fbe7 100644 --- a/e2e/solid-start/server-functions/src/routes/headers.tsx +++ b/e2e/solid-start/server-functions/src/routes/headers.tsx @@ -1,8 +1,11 @@ import { createFileRoute } from '@tanstack/solid-router' import * as Solid from 'solid-js' import { createServerFn } from '@tanstack/solid-start' -import { getHeaders, setHeader } from '@tanstack/solid-start/server' -import type { HTTPHeaderName } from '@tanstack/solid-start/server' +import { + getRequestHeaders, + setResponseHeader, +} from '@tanstack/solid-start/server' +import type { RequestHeaderName } from '@tanstack/solid-start/server' export const Route = createFileRoute('/headers')({ loader: async () => { @@ -17,17 +20,18 @@ export const Route = createFileRoute('/headers')({ }) export const getTestHeaders = createServerFn().handler(() => { - setHeader('x-test-header', 'test-value') + setResponseHeader('x-test-header', 'test-value') + const reqHeaders = Object.fromEntries(getRequestHeaders().entries()) return { - serverHeaders: getHeaders(), - headers: getHeaders(), + serverHeaders: reqHeaders, + headers: reqHeaders, } }) type TestHeadersResult = { - headers?: Partial> - serverHeaders?: Partial> + headers?: Partial> + serverHeaders?: Partial> } function ResponseHeaders({ diff --git a/e2e/solid-start/server-functions/src/routes/isomorphic-fns.tsx b/e2e/solid-start/server-functions/src/routes/isomorphic-fns.tsx index 3327b38af77..b46ec22eabb 100644 --- a/e2e/solid-start/server-functions/src/routes/isomorphic-fns.tsx +++ b/e2e/solid-start/server-functions/src/routes/isomorphic-fns.tsx @@ -13,7 +13,7 @@ const getEcho = createIsomorphicFn() .client((input) => 'client received ' + input) const getServerEcho = createServerFn() - .validator((input: string) => input) + .inputValidator((input: string) => input) .handler(({ data }) => getEcho(data)) export const Route = createFileRoute('/isomorphic-fns')({ diff --git a/e2e/solid-start/server-functions/src/routes/multipart.tsx b/e2e/solid-start/server-functions/src/routes/multipart.tsx index 5e959da7ae5..53da4a2e66c 100644 --- a/e2e/solid-start/server-functions/src/routes/multipart.tsx +++ b/e2e/solid-start/server-functions/src/routes/multipart.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute('/multipart')({ }) const multipartFormDataServerFn = createServerFn({ method: 'POST' }) - .validator((x: unknown) => { + .inputValidator((x: unknown) => { if (!(x instanceof FormData)) { throw new Error('Invalid form data') } diff --git a/e2e/solid-start/server-functions/src/routes/raw-response.tsx b/e2e/solid-start/server-functions/src/routes/raw-response.tsx index 24f95e2cd17..ad3cfdc4190 100644 --- a/e2e/solid-start/server-functions/src/routes/raw-response.tsx +++ b/e2e/solid-start/server-functions/src/routes/raw-response.tsx @@ -8,7 +8,7 @@ export const Route = createFileRoute('/raw-response')({ }) const expectedValue = 'Hello from a server function!' -export const rawResponseFn = createServerFn({ response: 'raw' }).handler(() => { +export const rawResponseFn = createServerFn().handler(() => { return new Response(expectedValue) }) diff --git a/e2e/solid-start/server-functions/src/routes/serialize-form-data.tsx b/e2e/solid-start/server-functions/src/routes/serialize-form-data.tsx index 1973e9f7b90..e6f1547ca75 100644 --- a/e2e/solid-start/server-functions/src/routes/serialize-form-data.tsx +++ b/e2e/solid-start/server-functions/src/routes/serialize-form-data.tsx @@ -11,8 +11,8 @@ const testValues = { __adder: 1, } -export const greetUser = createServerFn() - .validator((data: FormData) => { +export const greetUser = createServerFn({ method: 'POST' }) + .inputValidator((data: FormData) => { if (!(data instanceof FormData)) { throw new Error('Invalid! FormData is required') } diff --git a/e2e/solid-start/server-functions/src/routes/submit-post-formdata.tsx b/e2e/solid-start/server-functions/src/routes/submit-post-formdata.tsx index 3fb6ea356f7..8c10ba88749 100644 --- a/e2e/solid-start/server-functions/src/routes/submit-post-formdata.tsx +++ b/e2e/solid-start/server-functions/src/routes/submit-post-formdata.tsx @@ -9,8 +9,8 @@ const testValues = { name: 'Sean', } -export const greetUser = createServerFn({ method: 'POST', response: 'raw' }) - .validator((data: FormData) => { +export const greetUser = createServerFn({ method: 'POST' }) + .inputValidator((data: FormData) => { if (!(data instanceof FormData)) { throw new Error('Invalid! FormData is required') } diff --git a/e2e/solid-start/server-functions/tests/fixture.ts b/e2e/solid-start/server-functions/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/solid-start/server-functions/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/solid-start/server-functions/tests/server-functions.spec.ts b/e2e/solid-start/server-functions/tests/server-functions.spec.ts index 80c89004c57..2af3201cf48 100644 --- a/e2e/solid-start/server-functions/tests/server-functions.spec.ts +++ b/e2e/solid-start/server-functions/tests/server-functions.spec.ts @@ -1,5 +1,6 @@ import * as fs from 'node:fs' -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' import { PORT } from '../playwright.config' import type { Page } from '@playwright/test' @@ -11,16 +12,10 @@ test('invoking a server function with custom response status code', async ({ await page.waitForLoadState('networkidle') const requestPromise = new Promise((resolve) => { - page.on('response', async (response) => { + page.on('response', (response) => { expect(response.status()).toBe(225) expect(response.statusText()).toBe('hello') - expect(response.headers()['content-type']).toBe('application/json') - expect(await response.json()).toEqual( - expect.objectContaining({ - result: { hello: 'world' }, - context: {}, - }), - ) + expect(response.headers()['content-type']).toContain('application/json') resolve() }) }) @@ -122,11 +117,11 @@ test('env-only functions can only be called on the server or client respectively 'server got: hello', ) await expect(page.getByTestId('server-on-client')).toContainText( - 'serverEcho threw an error: serverOnly() functions can only be called on the server!', + 'serverEcho threw an error: createServerOnlyFn() functions can only be called on the server!', ) await expect(page.getByTestId('client-on-server')).toContainText( - 'clientEcho threw an error: clientOnly() functions can only be called on the client!', + 'clientEcho threw an error: createClientOnlyFn() functions can only be called on the client!', ) await expect(page.getByTestId('client-on-client')).toContainText( 'client got: hello', diff --git a/e2e/solid-start/server-functions/vite.config.ts b/e2e/solid-start/server-functions/vite.config.ts index bae1bfaad6e..1a2219f4435 100644 --- a/e2e/solid-start/server-functions/vite.config.ts +++ b/e2e/solid-start/server-functions/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/server-routes/package.json b/e2e/solid-start/server-routes/package.json index c2bec1aa27a..96a395f2862 100644 --- a/e2e/solid-start/server-routes/package.json +++ b/e2e/solid-start/server-routes/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "playwright test --project=chromium" }, "dependencies": { @@ -18,7 +18,7 @@ "redaxios": "^0.5.1", "solid-js": "^1.9.5", "tailwind-merge": "^2.6.0", - "vite": "6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -29,9 +29,10 @@ "autoprefixer": "^10.4.20", "combinate": "^1.1.11", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.6", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/server-routes/src/routeTree.gen.ts b/e2e/solid-start/server-routes/src/routeTree.gen.ts index 17ffa3450c0..e50fbe7ba1e 100644 --- a/e2e/solid-start/server-routes/src/routeTree.gen.ts +++ b/e2e/solid-start/server-routes/src/routeTree.gen.ts @@ -8,14 +8,10 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/solid-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as MergeServerFnMiddlewareContextRouteImport } from './routes/merge-server-fn-middleware-context' import { Route as IndexRouteImport } from './routes/index' -import { ServerRoute as ApiMiddlewareContextServerRouteImport } from './routes/api/middleware-context' - -const rootServerRouteImport = createServerRootRoute() +import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' const MergeServerFnMiddlewareContextRoute = MergeServerFnMiddlewareContextRouteImport.update({ @@ -28,58 +24,47 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const ApiMiddlewareContextServerRoute = - ApiMiddlewareContextServerRouteImport.update({ - id: '/api/middleware-context', - path: '/api/middleware-context', - getParentRoute: () => rootServerRouteImport, - } as any) +const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ + id: '/api/middleware-context', + path: '/api/middleware-context', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/merge-server-fn-middleware-context': typeof MergeServerFnMiddlewareContextRoute + '/api/middleware-context': typeof ApiMiddlewareContextRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/merge-server-fn-middleware-context' + fullPaths: + | '/' + | '/merge-server-fn-middleware-context' + | '/api/middleware-context' fileRoutesByTo: FileRoutesByTo - to: '/' | '/merge-server-fn-middleware-context' - id: '__root__' | '/' | '/merge-server-fn-middleware-context' + to: '/' | '/merge-server-fn-middleware-context' | '/api/middleware-context' + id: + | '__root__' + | '/' + | '/merge-server-fn-middleware-context' + | '/api/middleware-context' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute MergeServerFnMiddlewareContextRoute: typeof MergeServerFnMiddlewareContextRoute -} -export interface FileServerRoutesByFullPath { - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRoutesByTo { - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/middleware-context': typeof ApiMiddlewareContextServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/middleware-context' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/middleware-context' - id: '__root__' | '/api/middleware-context' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiMiddlewareContextServerRoute: typeof ApiMiddlewareContextServerRoute + ApiMiddlewareContextRoute: typeof ApiMiddlewareContextRoute } declare module '@tanstack/solid-router' { @@ -98,16 +83,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - } -} -declare module '@tanstack/solid-start/server' { - interface ServerFileRoutesByPath { '/api/middleware-context': { id: '/api/middleware-context' path: '/api/middleware-context' fullPath: '/api/middleware-context' - preLoaderRoute: typeof ApiMiddlewareContextServerRouteImport - parentRoute: typeof rootServerRouteImport + preLoaderRoute: typeof ApiMiddlewareContextRouteImport + parentRoute: typeof rootRouteImport } } } @@ -115,13 +96,16 @@ declare module '@tanstack/solid-start/server' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, MergeServerFnMiddlewareContextRoute: MergeServerFnMiddlewareContextRoute, + ApiMiddlewareContextRoute: ApiMiddlewareContextRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiMiddlewareContextServerRoute: ApiMiddlewareContextServerRoute, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/e2e/solid-start/server-routes/src/router.tsx b/e2e/solid-start/server-routes/src/router.tsx index c45bed4758c..5da353c1ce2 100644 --- a/e2e/solid-start/server-routes/src/router.tsx +++ b/e2e/solid-start/server-routes/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/e2e/solid-start/server-routes/src/routes/api/middleware-context.ts b/e2e/solid-start/server-routes/src/routes/api/middleware-context.ts index ef338345943..cd50ed71974 100644 --- a/e2e/solid-start/server-routes/src/routes/api/middleware-context.ts +++ b/e2e/solid-start/server-routes/src/routes/api/middleware-context.ts @@ -1,28 +1,29 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { createMiddleware, json } from '@tanstack/solid-start' -const testParentMiddleware = createMiddleware({ type: 'request' }).server( - async ({ next }) => { - const result = await next({ context: { testParent: true } }) - return result - }, -) +const testParentMiddleware = createMiddleware().server(async ({ next }) => { + const result = await next({ context: { testParent: true } }) + return result +}) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next }) => { const result = await next({ context: { test: true } }) return result }) -export const ServerRoute = createServerFileRoute('/api/middleware-context') - .middleware([testMiddleware]) - .methods({ - GET: ({ request, context }) => { - return json({ - url: request.url, - context: context, - expectedContext: { testParent: true, test: true }, - }) +export const Route = createFileRoute('/api/middleware-context')({ + server: { + middleware: [testMiddleware], + handlers: { + GET: ({ request, context }) => { + return json({ + url: request.url, + context: context, + expectedContext: { testParent: true, test: true }, + }) + }, }, - }) + }, +}) diff --git a/e2e/solid-start/server-routes/tests/fixture.ts b/e2e/solid-start/server-routes/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/solid-start/server-routes/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/solid-start/server-routes/tests/server-routes.spec.ts b/e2e/solid-start/server-routes/tests/server-routes.spec.ts index 098a40b2efe..33176a23fd2 100644 --- a/e2e/solid-start/server-routes/tests/server-routes.spec.ts +++ b/e2e/solid-start/server-routes/tests/server-routes.spec.ts @@ -1,4 +1,5 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' test('merge-server-fn-middleware-context', async ({ page }) => { await page.goto('/merge-server-fn-middleware-context') diff --git a/e2e/solid-start/server-routes/vite.config.ts b/e2e/solid-start/server-routes/vite.config.ts index bae1bfaad6e..1a2219f4435 100644 --- a/e2e/solid-start/server-routes/vite.config.ts +++ b/e2e/solid-start/server-routes/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/spa-mode/package.json b/e2e/solid-start/spa-mode/package.json index 74f862abcbc..a0ffac57a5c 100644 --- a/e2e/solid-start/spa-mode/package.json +++ b/e2e/solid-start/spa-mode/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "npx serve .output/public", + "start": "npx serve dist/client", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -22,7 +22,8 @@ "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-tsconfig-paths": "^5.1.4" + "vite": "^7.1.1", + "vite-tsconfig-paths": "^5.1.4", + "vite-plugin-solid": "^2.11.8" } } diff --git a/e2e/solid-start/spa-mode/src/routeTree.gen.ts b/e2e/solid-start/spa-mode/src/routeTree.gen.ts index 070e249d585..96dd9ad640b 100644 --- a/e2e/solid-start/spa-mode/src/routeTree.gen.ts +++ b/e2e/solid-start/spa-mode/src/routeTree.gen.ts @@ -101,3 +101,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/spa-mode/src/router.tsx b/e2e/solid-start/spa-mode/src/router.tsx index e230377cb19..e924cb642f1 100644 --- a/e2e/solid-start/spa-mode/src/router.tsx +++ b/e2e/solid-start/spa-mode/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -12,6 +12,6 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/e2e/solid-start/spa-mode/tests/app.spec.ts b/e2e/solid-start/spa-mode/tests/app.spec.ts index 8970ec333c8..c18a0ff65d5 100644 --- a/e2e/solid-start/spa-mode/tests/app.spec.ts +++ b/e2e/solid-start/spa-mode/tests/app.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test' -import { test } from './fixture' +import { test } from '@tanstack/router-e2e-utils' import type { Page } from '@playwright/test' async function runTest( diff --git a/e2e/solid-start/spa-mode/tests/fixture.ts b/e2e/solid-start/spa-mode/tests/fixture.ts deleted file mode 100644 index abb7b1d564d..00000000000 --- a/e2e/solid-start/spa-mode/tests/fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test as base, expect } from '@playwright/test' - -export interface TestFixtureOptions { - whitelistErrors: Array -} -export const test = base.extend({ - whitelistErrors: [[], { option: true }], - page: async ({ page, whitelistErrors }, use) => { - const errorMessages: Array = [] - page.on('console', (m) => { - if (m.type() === 'error') { - const text = m.text() - for (const whitelistError of whitelistErrors) { - if ( - (typeof whitelistError === 'string' && - text.includes(whitelistError)) || - (whitelistError instanceof RegExp && whitelistError.test(text)) - ) { - return - } - } - errorMessages.push(text) - } - }) - await use(page) - expect(errorMessages).toEqual([]) - }, -}) diff --git a/e2e/solid-start/spa-mode/vite.config.ts b/e2e/solid-start/spa-mode/vite.config.ts index f14e499525f..3bad2df77eb 100644 --- a/e2e/solid-start/spa-mode/vite.config.ts +++ b/e2e/solid-start/spa-mode/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -25,5 +26,6 @@ export default defineConfig({ }, ], }), + viteSolid({ ssr: true }), ], }) diff --git a/e2e/solid-start/website/package.json b/e2e/solid-start/website/package.json index 383f3305db0..0a60d4e5f6e 100644 --- a/e2e/solid-start/website/package.json +++ b/e2e/solid-start/website/package.json @@ -7,7 +7,7 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "start": "node .output/server/index.mjs", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" }, "dependencies": { @@ -17,7 +17,7 @@ "redaxios": "^0.5.1", "solid-js": "^1.9.5", "tailwind-merge": "^2.6.0", - "vite": "6.3.5", + "vite": "^7.1.1", "zod": "^3.24.2" }, "devDependencies": { @@ -26,9 +26,10 @@ "@types/node": "^22.10.2", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", + "srvx": "^0.8.6", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite-plugin-solid": "^2.11.6", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/e2e/solid-start/website/src/routeTree.gen.ts b/e2e/solid-start/website/src/routeTree.gen.ts index b50616a38be..91f0d2f7287 100644 --- a/e2e/solid-start/website/src/routeTree.gen.ts +++ b/e2e/solid-start/website/src/routeTree.gen.ts @@ -279,3 +279,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/e2e/solid-start/website/src/router.tsx b/e2e/solid-start/website/src/router.tsx index cf079db611c..bddd03bcd9a 100644 --- a/e2e/solid-start/website/src/router.tsx +++ b/e2e/solid-start/website/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: 'intent', @@ -18,6 +18,6 @@ export function createRouter() { declare module '@tanstack/solid-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/e2e/solid-start/website/src/routes/$project.$version.docs.framework.$framework.tsx b/e2e/solid-start/website/src/routes/$project.$version.docs.framework.$framework.tsx index 92a2284371a..62d59befae3 100644 --- a/e2e/solid-start/website/src/routes/$project.$version.docs.framework.$framework.tsx +++ b/e2e/solid-start/website/src/routes/$project.$version.docs.framework.$framework.tsx @@ -1,8 +1,8 @@ import { Link, Outlet, - useLocation, createFileRoute, + useLocation, } from '@tanstack/solid-router' import { getDocumentHeads } from '~/server/document' import { getProject } from '~/server/projects' diff --git a/e2e/solid-start/website/src/routes/_library.tsx b/e2e/solid-start/website/src/routes/_library.tsx index 60138279d32..c25ae5cfe1b 100644 --- a/e2e/solid-start/website/src/routes/_library.tsx +++ b/e2e/solid-start/website/src/routes/_library.tsx @@ -1,8 +1,8 @@ import { Link, Outlet, - useLocation, createFileRoute, + useLocation, } from '@tanstack/solid-router' import { getProjects } from '~/server/projects' diff --git a/e2e/solid-start/website/src/server/document.tsx b/e2e/solid-start/website/src/server/document.tsx index 0090c3b1b14..1e0d34b86c8 100644 --- a/e2e/solid-start/website/src/server/document.tsx +++ b/e2e/solid-start/website/src/server/document.tsx @@ -41,7 +41,7 @@ export const getDocumentHeads = createServerFn({ method: 'GET' }).handler( ) export const getDocument = createServerFn({ method: 'GET' }) - .validator((id: string) => id) + .inputValidator((id: string) => id) .handler(async ({ data: id }) => { await new Promise((resolve) => setTimeout(resolve, 200)) diff --git a/e2e/solid-start/website/src/server/projects.tsx b/e2e/solid-start/website/src/server/projects.tsx index a66de2b2ca3..c5cbb4c08de 100644 --- a/e2e/solid-start/website/src/server/projects.tsx +++ b/e2e/solid-start/website/src/server/projects.tsx @@ -13,7 +13,7 @@ export const getProjects = createServerFn({ method: 'GET' }).handler( ) export const getProject = createServerFn({ method: 'GET' }) - .validator((project: string) => project) + .inputValidator((project: string) => project) .handler(async (ctx) => { await new Promise((resolve) => setTimeout(resolve, 200)) diff --git a/e2e/solid-start/website/vite.config.ts b/e2e/solid-start/website/vite.config.ts index 3af67d62ad2..1a2219f4435 100644 --- a/e2e/solid-start/website/vite.config.ts +++ b/e2e/solid-start/website/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -10,6 +11,7 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({}), + tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/examples/react/authenticated-routes-firebase/package.json b/examples/react/authenticated-routes-firebase/package.json index f0824fc95b6..822fff3e5b5 100644 --- a/examples/react/authenticated-routes-firebase/package.json +++ b/examples/react/authenticated-routes-firebase/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "autoprefixer": "^10.4.20", "firebase": "^11.4.0", "postcss": "^8.5.1", @@ -27,6 +27,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index 62ac5adebd5..85571c610e8 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index 2fd6a0e89aa..50d3da5b933 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-devtools-panel/package.json b/examples/react/basic-devtools-panel/package.json index a2049d01bbf..2f824a930a5 100644 --- a/examples/react/basic-devtools-panel/package.json +++ b/examples/react/basic-devtools-panel/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "@tanstack/react-query-devtools": "^5.67.2", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -24,6 +24,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index 27795b932c0..1608ed5dd0e 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-non-nested-devtools/package.json b/examples/react/basic-non-nested-devtools/package.json index 736761a3d2a..2e96586ddea 100644 --- a/examples/react/basic-non-nested-devtools/package.json +++ b/examples/react/basic-non-nested-devtools/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,6 +23,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index 4f8f08f288d..0d078467520 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -11,9 +11,9 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -27,6 +27,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index 9b4ef746eda..4119b226831 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index b60315a0dde..1cfe4aee242 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -11,23 +11,22 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "compression": "^1.8.0", "express": "^4.21.2", "get-port": "^7.1.0", - "isbot": "^5.1.28", "node-fetch": "^3.3.2", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "@types/express": "^4.17.23", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", "@vitejs/plugin-react": "^4.5.2", "typescript": "^5.8.3", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index 77a3ada2a25..f14d14cc5ca 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -11,23 +11,22 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "compression": "^1.7.5", "express": "^4.21.2", "get-port": "^7.1.0", - "isbot": "^5.1.22", "node-fetch": "^3.3.2", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "@types/express": "^4.17.21", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index a3cb6bfc225..794c8cdd9af 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -9,10 +9,10 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", - "@tanstack/virtual-file-routes": "^1.131.2", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", + "@tanstack/virtual-file-routes": "^1.132.0-alpha.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -26,6 +26,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-virtual-file-based/src/routeTree.gen.ts b/examples/react/basic-virtual-file-based/src/routeTree.gen.ts index b016b0f33ff..dc243ae8572 100644 --- a/examples/react/basic-virtual-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-virtual-file-based/src/routeTree.gen.ts @@ -168,11 +168,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -182,25 +182,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport - parentRoute: typeof rootRouteImport - } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof postsPostsHomeRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport parentRoute: typeof postsPostsRoute } '/_first/_second-layout': { @@ -210,18 +203,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello/universe': { - id: '/classic/hello/universe' - path: '/universe' - fullPath: '/classic/hello/universe' - preLoaderRoute: typeof ClassicHelloUniverseRouteImport + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/classic/hello/': { + id: '/classic/hello/' + path: '/' + fullPath: '/classic/hello/' + preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } '/classic/hello/world': { @@ -231,20 +231,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloWorldRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/': { - id: '/classic/hello/' - path: '/' - fullPath: '/classic/hello/' - preLoaderRoute: typeof ClassicHelloIndexRouteImport + '/classic/hello/universe': { + id: '/classic/hello/universe' + path: '/universe' + fullPath: '/classic/hello/universe' + preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-a': { - id: '/_first/_second-layout/route-without-file/layout-a' - path: '/route-without-file/layout-a' - fullPath: '/route-without-file/layout-a' - preLoaderRoute: typeof aRouteImport - parentRoute: typeof layoutSecondLayoutRoute - } '/_first/_second-layout/route-without-file/layout-b': { id: '/_first/_second-layout/route-without-file/layout-b' path: '/route-without-file/layout-b' @@ -252,6 +245,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof bRouteImport parentRoute: typeof layoutSecondLayoutRoute } + '/_first/_second-layout/route-without-file/layout-a': { + id: '/_first/_second-layout/route-without-file/layout-a' + path: '/route-without-file/layout-a' + fullPath: '/route-without-file/layout-a' + preLoaderRoute: typeof aRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json index 686ef212026..8d094a6888a 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -9,10 +9,10 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", - "@tanstack/virtual-file-routes": "^1.131.2", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", + "@tanstack/virtual-file-routes": "^1.132.0-alpha.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -26,6 +26,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/basic-virtual-inside-file-based/src/routeTree.gen.ts b/examples/react/basic-virtual-inside-file-based/src/routeTree.gen.ts index 861887e7db4..2d3afb49836 100644 --- a/examples/react/basic-virtual-inside-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-virtual-inside-file-based/src/routeTree.gen.ts @@ -145,11 +145,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -159,18 +159,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof postsHomeRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsDetailsRouteImport parentRoute: typeof PostsRoute } '/_layout/_layout-2': { @@ -180,19 +180,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutLayout2RouteImport parentRoute: typeof LayoutRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsDetailsRouteImport + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof postsHomeRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/posts/inception/': { + id: '/posts/inception/' + path: '/inception' + fullPath: '/posts/inception' + preLoaderRoute: typeof postsLetsGoIndexRouteImport + parentRoute: typeof PostsRoute } '/_layout/_layout-2/layout-b': { id: '/_layout/_layout-2/layout-b' @@ -201,12 +201,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport parentRoute: typeof LayoutLayout2Route } - '/posts/inception/': { - id: '/posts/inception/' - path: '/inception' - fullPath: '/posts/inception' - preLoaderRoute: typeof postsLetsGoIndexRouteImport - parentRoute: typeof PostsRoute + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } '/posts/inception/deeper/': { id: '/posts/inception/deeper/' diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 0c8ee1e3287..20b33a4954e 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,6 +23,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index 52992fd098d..09d78316cd3 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -24,6 +24,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 3b8b544790b..4827556bd1e 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -26,6 +26,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json index ab16dd0b638..6c461b0b412 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -11,9 +11,9 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -28,6 +28,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index f867bb38d98..d76ba34ab94 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -27,6 +27,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index e0ff10a8659..953687d0145 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 7c1baf5afc9..cf659964cdf 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -28,6 +28,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index db73e620ad2..d8d98bec3d9 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -11,8 +11,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.6", "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index ddad4db5a16..0f1c1ece2b8 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -24,6 +24,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 10b52b7ded8..75d4183237d 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -9,9 +9,9 @@ "start": "dev" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json index 8899511ffb2..de37e5c894b 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index f4a32eb0058..078f6b31ffd 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -8,8 +8,8 @@ "preview": "rsbuild preview" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", @@ -19,7 +19,7 @@ "devDependencies": { "@rsbuild/core": "1.2.4", "@rsbuild/plugin-react": "1.1.0", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "typescript": "^5.6.2" diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index b736ea9c0eb..caaa88c9305 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -7,14 +7,14 @@ "build": "webpack build && tsc --noEmit" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@swc/core": "^1.10.15", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "html-webpack-plugin": "^5.6.3", diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index 1b03e0cc972..1a5a8d4980b 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", @@ -22,6 +22,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 992dd760f6a..e51220a8d1b 100644 --- a/examples/react/router-monorepo-react-query/package.json +++ b/examples/react/router-monorepo-react-query/package.json @@ -12,9 +12,9 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -25,7 +25,7 @@ "@types/react-dom": "^19.0.3", "typescript": "^5.7.2", "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" }, "keywords": [], diff --git a/examples/react/router-monorepo-react-query/packages/app/package.json b/examples/react/router-monorepo-react-query/packages/app/package.json index d635eccabed..a6119e0f8a6 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -20,11 +20,11 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" }, "nx": { diff --git a/examples/react/router-monorepo-react-query/packages/post-feature/package.json b/examples/react/router-monorepo-react-query/packages/post-feature/package.json index ef4700d04fc..fc6532deeb3 100644 --- a/examples/react/router-monorepo-react-query/packages/post-feature/package.json +++ b/examples/react/router-monorepo-react-query/packages/post-feature/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/router-monorepo-react-query/packages/post-query/package.json b/examples/react/router-monorepo-react-query/packages/post-query/package.json index 6f856efc890..89c813fc708 100644 --- a/examples/react/router-monorepo-react-query/packages/post-query/package.json +++ b/examples/react/router-monorepo-react-query/packages/post-query/package.json @@ -15,6 +15,6 @@ "devDependencies": { "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json index 35661ab8441..d14d1a5c966 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -8,10 +8,10 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { - "@tanstack/history": "^1.131.2", + "@tanstack/history": "^1.132.0-alpha.1", "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@router-mono-react-query/post-query": "workspace:*", "redaxios": "^0.5.1", "zod": "^3.24.2", @@ -23,7 +23,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/router-monorepo-simple-lazy/package.json b/examples/react/router-monorepo-simple-lazy/package.json index 1d28fcef6f1..e82e2f7415e 100644 --- a/examples/react/router-monorepo-simple-lazy/package.json +++ b/examples/react/router-monorepo-simple-lazy/package.json @@ -8,9 +8,9 @@ "dev": "pnpm router build && pnpm post-feature build && pnpm app dev" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -21,7 +21,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" }, "keywords": [], diff --git a/examples/react/router-monorepo-simple-lazy/packages/app/package.json b/examples/react/router-monorepo-simple-lazy/packages/app/package.json index 99b1dc73fea..c0fb712be3b 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/app/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/app/package.json @@ -19,11 +19,11 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" }, "nx": { diff --git a/examples/react/router-monorepo-simple-lazy/packages/post-feature/package.json b/examples/react/router-monorepo-simple-lazy/packages/post-feature/package.json index 324c49737ae..b241c9403eb 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/post-feature/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/post-feature/package.json @@ -26,7 +26,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/router-monorepo-simple-lazy/packages/router/package.json b/examples/react/router-monorepo-simple-lazy/packages/router/package.json index 67b53bfa0ba..436f1031b84 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/router/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/router/package.json @@ -8,9 +8,9 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { - "@tanstack/history": "^1.131.2", - "@tanstack/react-router": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/history": "^1.132.0-alpha.1", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "redaxios": "^0.5.1", "zod": "^3.24.2", "react": "^19.0.0", @@ -21,7 +21,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index cb2955350cc..905e2286040 100644 --- a/examples/react/router-monorepo-simple/package.json +++ b/examples/react/router-monorepo-simple/package.json @@ -8,9 +8,9 @@ "dev": "pnpm router build && pnpm post-feature build && pnpm app dev" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -21,7 +21,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" }, "keywords": [], diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index 77a4428296d..3c32c07b350 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -19,8 +19,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.131.50", - "vite": "^6.3.5", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "vite": "^7.1.1", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", diff --git a/examples/react/router-monorepo-simple/packages/post-feature/package.json b/examples/react/router-monorepo-simple/packages/post-feature/package.json index 2a5ff9288e5..eaeb4539ffc 100644 --- a/examples/react/router-monorepo-simple/packages/post-feature/package.json +++ b/examples/react/router-monorepo-simple/packages/post-feature/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index 32d6fdadb66..1846a817a5c 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -8,9 +8,9 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { - "@tanstack/history": "^1.131.2", - "@tanstack/react-router": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/history": "^1.132.0-alpha.1", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "redaxios": "^0.5.1", "zod": "^3.24.2", "react": "^19.0.0", @@ -21,7 +21,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-dts": "^4.5.0" } } diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index 50e18c92d04..c691a2ff91c 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", "@tanstack/react-virtual": "^3.13.0", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", @@ -23,6 +23,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/scroll-restoration/src/main.tsx b/examples/react/scroll-restoration/src/main.tsx index 6cb6011743c..330bdc10538 100644 --- a/examples/react/scroll-restoration/src/main.tsx +++ b/examples/react/scroll-restoration/src/main.tsx @@ -43,7 +43,7 @@ function RootComponent() { const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: IndexComponent, }) @@ -68,7 +68,7 @@ function IndexComponent() { const aboutRoute = createRoute({ getParentRoute: () => rootRoute, path: '/about', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: AboutComponent, }) @@ -93,7 +93,7 @@ function AboutComponent() { const byElementRoute = createRoute({ getParentRoute: () => rootRoute, path: '/by-element', - loader: () => new Promise((r) => setTimeout(r, 500)), + loader: () => new Promise((r) => setTimeout(r, 500)), component: ByElementComponent, }) diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index fbfe01706d1..9b2561513c0 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -10,13 +10,13 @@ "test:unit": "vitest" }, "dependencies": { - "@tanstack/arktype-adapter": "^1.131.50", + "@tanstack/arktype-adapter": "^1.132.0-alpha.25", "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", - "@tanstack/valibot-adapter": "^1.131.50", - "@tanstack/zod-adapter": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", + "@tanstack/valibot-adapter": "^1.132.0-alpha.25", + "@tanstack/zod-adapter": "^1.132.0-alpha.25", "arktype": "^2.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -33,6 +33,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/search-validator-adapters/src/routeTree.gen.ts b/examples/react/search-validator-adapters/src/routeTree.gen.ts index da0a7b2118f..3312abe25e8 100644 --- a/examples/react/search-validator-adapters/src/routeTree.gen.ts +++ b/examples/react/search-validator-adapters/src/routeTree.gen.ts @@ -80,11 +80,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/users/arktype/': { - id: '/users/arktype/' - path: '/users/arktype' - fullPath: '/users/arktype' - preLoaderRoute: typeof UsersArktypeIndexRouteImport + '/users/zod/': { + id: '/users/zod/' + path: '/users/zod' + fullPath: '/users/zod' + preLoaderRoute: typeof UsersZodIndexRouteImport parentRoute: typeof rootRouteImport } '/users/valibot/': { @@ -94,11 +94,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof UsersValibotIndexRouteImport parentRoute: typeof rootRouteImport } - '/users/zod/': { - id: '/users/zod/' - path: '/users/zod' - fullPath: '/users/zod' - preLoaderRoute: typeof UsersZodIndexRouteImport + '/users/arktype/': { + id: '/users/arktype/' + path: '/users/arktype' + fullPath: '/users/arktype' + preLoaderRoute: typeof UsersArktypeIndexRouteImport parentRoute: typeof rootRouteImport } } diff --git a/examples/react/start-bare/package.json b/examples/react/start-bare/package.json index 20725e50803..cbf3e6c488e 100644 --- a/examples/react/start-bare/package.json +++ b/examples/react/start-bare/package.json @@ -9,9 +9,9 @@ "start": "vite start" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.24.2" @@ -20,8 +20,9 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-bare/src/routeTree.gen.ts b/examples/react/start-bare/src/routeTree.gen.ts index 59499d9fbf3..bb937ce84a1 100644 --- a/examples/react/start-bare/src/routeTree.gen.ts +++ b/examples/react/start-bare/src/routeTree.gen.ts @@ -75,3 +75,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-bare/src/router.tsx b/examples/react/start-bare/src/router.tsx index e4232569c04..a464233af90 100644 --- a/examples/react/start-bare/src/router.tsx +++ b/examples/react/start-bare/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: (err) =>

    {err.error.stack}

    , @@ -15,6 +15,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-bare/vite.config.ts b/examples/react/start-bare/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-bare/vite.config.ts +++ b/examples/react/start-bare/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index 9faa4be95ee..15fe4b65fa3 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@prisma/client": "5.22.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "prisma": "^5.22.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -21,6 +21,7 @@ "tailwind-merge": "^2.6.0" }, "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", @@ -28,7 +29,7 @@ "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-basic-auth/src/routeTree.gen.ts b/examples/react/start-basic-auth/src/routeTree.gen.ts index 25bb969f23f..b6882e6c516 100644 --- a/examples/react/start-basic-auth/src/routeTree.gen.ts +++ b/examples/react/start-basic-auth/src/routeTree.gen.ts @@ -213,3 +213,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-basic-auth/src/router.tsx b/examples/react/start-basic-auth/src/router.tsx index c76eb0210cc..f648dfcf8c4 100644 --- a/examples/react/start-basic-auth/src/router.tsx +++ b/examples/react/start-basic-auth/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -17,6 +17,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-basic-auth/src/routes/_authed.tsx b/examples/react/start-basic-auth/src/routes/_authed.tsx index 3944e590f9d..230be64c625 100644 --- a/examples/react/start-basic-auth/src/routes/_authed.tsx +++ b/examples/react/start-basic-auth/src/routes/_authed.tsx @@ -5,7 +5,7 @@ import { Login } from '~/components/Login' import { useAppSession } from '~/utils/session' export const loginFn = createServerFn({ method: 'POST' }) - .validator((d: { email: string; password: string }) => d) + .inputValidator((d: { email: string; password: string }) => d) .handler(async ({ data }) => { // Find the user const user = await prismaClient.user.findUnique({ diff --git a/examples/react/start-basic-auth/src/routes/signup.tsx b/examples/react/start-basic-auth/src/routes/signup.tsx index 247a94c03c7..7b64265840b 100644 --- a/examples/react/start-basic-auth/src/routes/signup.tsx +++ b/examples/react/start-basic-auth/src/routes/signup.tsx @@ -6,7 +6,7 @@ import { Auth } from '~/components/Auth' import { useAppSession } from '~/utils/session' export const signupFn = createServerFn({ method: 'POST' }) - .validator( + .inputValidator( (d: { email: string; password: string; redirectUrl?: string }) => d, ) .handler(async ({ data }) => { diff --git a/examples/react/start-basic-auth/src/utils/posts.ts b/examples/react/start-basic-auth/src/utils/posts.ts index a7e0e99cfa2..fbcc43c34b0 100644 --- a/examples/react/start-basic-auth/src/utils/posts.ts +++ b/examples/react/start-basic-auth/src/utils/posts.ts @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios diff --git a/examples/react/start-basic-auth/vite.config.ts b/examples/react/start-basic-auth/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-basic-auth/vite.config.ts +++ b/examples/react/start-basic-auth/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index eec3f833041..858ec8199ed 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-ssr-query": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-ssr-query": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -24,11 +24,12 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-basic-react-query/src/routeTree.gen.ts b/examples/react/start-basic-react-query/src/routeTree.gen.ts index a16228badb2..7dea7d32fa2 100644 --- a/examples/react/start-basic-react-query/src/routeTree.gen.ts +++ b/examples/react/start-basic-react-query/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as RedirectRouteImport } from './routes/redirect' import { Route as DeferredRouteImport } from './routes/deferred' @@ -21,14 +19,12 @@ import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api.users' -import { ServerRoute as ApiUsersIdServerRouteImport } from './routes/api/users.$id' - -const rootServerRouteImport = createServerRootRoute() const RedirectRoute = RedirectRouteImport.update({ id: '/redirect', @@ -79,6 +75,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutNestedLayoutRoute = PathlessLayoutNestedLayoutRouteImport.update({ id: '/_nested-layout', @@ -89,6 +90,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', @@ -101,16 +107,6 @@ const PathlessLayoutNestedLayoutRouteARoute = path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersIdServerRoute = ApiUsersIdServerRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -118,24 +114,28 @@ export interface FileRoutesByFullPath { '/users': typeof UsersRouteRouteWithChildren '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { @@ -147,12 +147,14 @@ export interface FileRoutesById { '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute '/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$id': typeof ApiUsersIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { @@ -163,24 +165,28 @@ export interface FileRouteTypes { | '/users' | '/deferred' | '/redirect' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/route-a' | '/route-b' + | '/api/users/$id' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' | '/deferred' | '/redirect' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' | '/route-a' | '/route-b' + | '/api/users/$id' | '/posts/$postId/deep' id: | '__root__' @@ -191,12 +197,14 @@ export interface FileRouteTypes { | '/deferred' | '/redirect' | '/_pathlessLayout/_nested-layout' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/_pathlessLayout/_nested-layout/route-a' | '/_pathlessLayout/_nested-layout/route-b' + | '/api/users/$id' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } @@ -207,32 +215,9 @@ export interface RootRouteChildren { PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren DeferredRoute: typeof DeferredRoute RedirectRoute: typeof RedirectRoute + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$id': typeof ApiUsersIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$id' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$id' - id: '__root__' | '/api/users' | '/api/users/$id' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -306,6 +291,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout/_nested-layout': { id: '/_pathlessLayout/_nested-layout' path: '' @@ -320,6 +312,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' path: '/route-b' @@ -336,24 +335,6 @@ declare module '@tanstack/react-router' { } } } -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users/$id': { - id: '/api/users/$id' - path: '/$id' - fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute - } - } -} interface PostsRouteRouteChildren { PostsPostIdRoute: typeof PostsPostIdRoute @@ -413,16 +394,16 @@ const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren( PathlessLayoutRouteChildren, ) -interface ApiUsersServerRouteChildren { - ApiUsersIdServerRoute: typeof ApiUsersIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersIdRoute: typeof ApiUsersIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersIdServerRoute: ApiUsersIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersIdRoute: ApiUsersIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { @@ -432,14 +413,17 @@ const rootRouteChildren: RootRouteChildren = { PathlessLayoutRoute: PathlessLayoutRouteWithChildren, DeferredRoute: DeferredRoute, RedirectRoute: RedirectRoute, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/examples/react/start-basic-react-query/src/router.tsx b/examples/react/start-basic-react-query/src/router.tsx index fe06f5cd68a..314b29edd01 100644 --- a/examples/react/start-basic-react-query/src/router.tsx +++ b/examples/react/start-basic-react-query/src/router.tsx @@ -1,14 +1,14 @@ import { QueryClient } from '@tanstack/react-query' -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { +export function getRouter() { const queryClient = new QueryClient() - const router = createTanStackRouter({ + const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', @@ -25,6 +25,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-basic-react-query/src/routes/api.users.ts b/examples/react/start-basic-react-query/src/routes/api.users.ts deleted file mode 100644 index 8cdcaf76e1e..00000000000 --- a/examples/react/start-basic-react-query/src/routes/api.users.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { json } from '@tanstack/react-start' -import { createServerFileRoute } from '@tanstack/react-start/server' -import axios from 'redaxios' -import type { User } from '../utils/users' - -export const ServerRoute = createServerFileRoute('/api/users').methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await axios.get>( - 'https://jsonplaceholder.typicode.com/users', - ) - - const list = res.data.slice(0, 10) - - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) - }, -}) diff --git a/examples/react/start-basic-react-query/src/routes/api/users.$id.ts b/examples/react/start-basic-react-query/src/routes/api/users.$id.ts index eef27752df3..79c38d41a68 100644 --- a/examples/react/start-basic-react-query/src/routes/api/users.$id.ts +++ b/examples/react/start-basic-react-query/src/routes/api/users.$id.ts @@ -1,25 +1,27 @@ import { createFileRoute } from '@tanstack/react-router' import { json } from '@tanstack/react-start' -import { createServerFileRoute } from '@tanstack/react-start/server' import axios from 'redaxios' import type { User } from '../../utils/users' -export const ServerRoute = createServerFileRoute('/api/users/$id').methods({ - GET: async ({ request, params }) => { - console.info(`Fetching users by id=${params.id}... @`, request.url) - try { - const res = await axios.get( - 'https://jsonplaceholder.typicode.com/users/' + params.id, - ) - - return json({ - id: res.data.id, - name: res.data.name, - email: res.data.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } +export const Route = createFileRoute('/api/users/$id')({ + server: { + handlers: { + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get( + 'https://jsonplaceholder.typicode.com/users/' + params.id, + ) + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/examples/react/start-basic-react-query/src/routes/api/users.ts b/examples/react/start-basic-react-query/src/routes/api/users.ts new file mode 100644 index 00000000000..0e290833bb6 --- /dev/null +++ b/examples/react/start-basic-react-query/src/routes/api/users.ts @@ -0,0 +1,21 @@ +import { createFileRoute } from '@tanstack/react-router' +import { json } from '@tanstack/react-start' +import axios from 'redaxios' +import type { User } from '../../utils/users' + +export const Route = createFileRoute('/api/users')({ + server: { + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>( + 'https://jsonplaceholder.typicode.com/users', + ) + const list = res.data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, + }, +}) diff --git a/examples/react/start-basic-react-query/src/utils/posts.tsx b/examples/react/start-basic-react-query/src/utils/posts.tsx index dc6979005e0..a95e9dce271 100644 --- a/examples/react/start-basic-react-query/src/utils/posts.tsx +++ b/examples/react/start-basic-react-query/src/utils/posts.tsx @@ -25,7 +25,7 @@ export const postsQueryOptions = () => }) export const fetchPost = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios diff --git a/examples/react/start-basic-react-query/vite.config.ts b/examples/react/start-basic-react-query/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-basic-react-query/vite.config.ts +++ b/examples/react/start-basic-react-query/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.disabled.json similarity index 77% rename from examples/react/start-basic-rsc/package.json rename to examples/react/start-basic-rsc/package.disabled.json index 7e29de7e381..1839946b331 100644 --- a/examples/react/start-basic-rsc/package.json +++ b/examples/react/start-basic-rsc/package.disabled.json @@ -10,9 +10,9 @@ }, "dependencies": { "@babel/plugin-syntax-typescript": "^7.25.9", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -21,11 +21,12 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" }, "overrides": { diff --git a/examples/react/start-basic-rsc/src/routeTree.gen.ts b/examples/react/start-basic-rsc/src/routeTree.gen.ts index f3e51585ab4..2f5748f5783 100644 --- a/examples/react/start-basic-rsc/src/routeTree.gen.ts +++ b/examples/react/start-basic-rsc/src/routeTree.gen.ts @@ -252,3 +252,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-basic-rsc/src/router.tsx b/examples/react/start-basic-rsc/src/router.tsx index c76eb0210cc..f648dfcf8c4 100644 --- a/examples/react/start-basic-rsc/src/router.tsx +++ b/examples/react/start-basic-rsc/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -17,6 +17,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-basic-rsc/src/routes/posts.$postId.tsx b/examples/react/start-basic-rsc/src/routes/posts.$postId.tsx index 5404a690a06..f3dba9bfd3c 100644 --- a/examples/react/start-basic-rsc/src/routes/posts.$postId.tsx +++ b/examples/react/start-basic-rsc/src/routes/posts.$postId.tsx @@ -5,7 +5,7 @@ import type { ErrorComponentProps } from '@tanstack/react-router' import { NotFound } from '~/components/NotFound' const renderPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data }) => { const post = await fetchPost(data) diff --git a/examples/react/start-basic-rsc/vite.config.ts b/examples/react/start-basic-rsc/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-basic-rsc/vite.config.ts +++ b/examples/react/start-basic-rsc/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json index d3c4773990b..431c20c6602 100644 --- a/examples/react/start-basic-static/package.json +++ b/examples/react/start-basic-static/package.json @@ -9,9 +9,10 @@ "start": "vite start" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", + "@tanstack/start-static-server-functions": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,7 +26,7 @@ "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "typescript": "^5.6.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.3" } } diff --git a/examples/react/start-basic-static/src/routeTree.gen.ts b/examples/react/start-basic-static/src/routeTree.gen.ts index cef83cd0975..4116787031e 100644 --- a/examples/react/start-basic-static/src/routeTree.gen.ts +++ b/examples/react/start-basic-static/src/routeTree.gen.ts @@ -363,3 +363,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-basic-static/src/router.tsx b/examples/react/start-basic-static/src/router.tsx index c76eb0210cc..1a1d8822d20 100644 --- a/examples/react/start-basic-static/src/router.tsx +++ b/examples/react/start-basic-static/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/react/start-basic-static/src/routes/deferred.tsx b/examples/react/start-basic-static/src/routes/deferred.tsx index e59a871e7b9..f0d832b8055 100644 --- a/examples/react/start-basic-static/src/routes/deferred.tsx +++ b/examples/react/start-basic-static/src/routes/deferred.tsx @@ -1,15 +1,18 @@ import { Await, createFileRoute } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' import { Suspense, useState } from 'react' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const personServerFn = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const personServerFn = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(({ data: name }) => { return { name, randomNumber: Math.floor(Math.random() * 100) } }) -const slowServerFn = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const slowServerFn = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data: name }) => { await new Promise((r) => setTimeout(r, 1000)) return { name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/examples/react/start-basic-static/src/routes/users.$userId.tsx b/examples/react/start-basic-static/src/routes/users.$userId.tsx index d166885e29d..2e163f86ef4 100644 --- a/examples/react/start-basic-static/src/routes/users.$userId.tsx +++ b/examples/react/start-basic-static/src/routes/users.$userId.tsx @@ -4,9 +4,11 @@ import { createServerFn } from '@tanstack/react-start' import type { ErrorComponentProps } from '@tanstack/react-router' import type { User } from '~/utils/users' import { NotFound } from '~/components/NotFound' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const fetchUser = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const fetchUser = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data: userId }) => { return axios .get('https://jsonplaceholder.typicode.com/users/' + userId) diff --git a/examples/react/start-basic-static/src/routes/users.tsx b/examples/react/start-basic-static/src/routes/users.tsx index 95eeacaf638..98fb79518b3 100644 --- a/examples/react/start-basic-static/src/routes/users.tsx +++ b/examples/react/start-basic-static/src/routes/users.tsx @@ -2,9 +2,11 @@ import { Link, Outlet, createFileRoute } from '@tanstack/react-router' import axios from 'redaxios' import { createServerFn } from '@tanstack/react-start' import type { User } from '../utils/users' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const fetchUsers = createServerFn({ method: 'GET', type: 'static' }).handler( - async () => { +const fetchUsers = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .handler(async () => { console.info('Fetching users...') const res = await axios.get>( 'https://jsonplaceholder.typicode.com/users', @@ -13,8 +15,7 @@ const fetchUsers = createServerFn({ method: 'GET', type: 'static' }).handler( return res.data .slice(0, 10) .map((u) => ({ id: u.id, name: u.name, email: u.email })) - }, -) + }) export const Route = createFileRoute('/users')({ loader: async () => fetchUsers(), diff --git a/examples/react/start-basic-static/src/utils/posts.tsx b/examples/react/start-basic-static/src/utils/posts.tsx index 79f52eea476..5f01de769ae 100644 --- a/examples/react/start-basic-static/src/utils/posts.tsx +++ b/examples/react/start-basic-static/src/utils/posts.tsx @@ -2,6 +2,7 @@ import { createServerFn } from '@tanstack/react-start' import axios from 'redaxios' import { notFound } from '@tanstack/react-router' import { logMiddleware } from './loggingMiddleware' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' export type PostType = { id: string @@ -9,9 +10,9 @@ export type PostType = { body: string } -export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) - .middleware([logMiddleware]) - .validator((d: string) => d) +export const fetchPost = createServerFn({ method: 'GET' }) + .middleware([logMiddleware, staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios @@ -27,8 +28,8 @@ export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) return post }) -export const fetchPosts = createServerFn({ method: 'GET', type: 'static' }) - .middleware([logMiddleware]) +export const fetchPosts = createServerFn({ method: 'GET' }) + .middleware([logMiddleware, staticFunctionMiddleware]) .handler(async () => { console.info('Fetching posts...') return axios diff --git a/examples/react/start-basic-static/vite.config.ts b/examples/react/start-basic-static/vite.config.ts index 303ac171c95..07451c7d66f 100644 --- a/examples/react/start-basic-static/vite.config.ts +++ b/examples/react/start-basic-static/vite.config.ts @@ -20,6 +20,9 @@ export default defineConfig({ sitemap: { host: 'https://localhost:3000', }, + prerender: { + failOnError: false, + }, }), ], }) diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 2c5ff9cd655..aca69796fd9 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -9,9 +9,9 @@ "start": "node .output/server/index.mjs" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.6.0", @@ -26,7 +26,7 @@ "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-basic/src/Foo.ts b/examples/react/start-basic/src/Foo.ts new file mode 100644 index 00000000000..6a3a36a146d --- /dev/null +++ b/examples/react/start-basic/src/Foo.ts @@ -0,0 +1,6 @@ +export class Foo { + constructor(private value: string) {} + bar() { + return this.value + } +} diff --git a/examples/react/start-basic/src/routeTree.gen.ts b/examples/react/start-basic/src/routeTree.gen.ts index 9f7ccda0189..011ecf91f23 100644 --- a/examples/react/start-basic/src/routeTree.gen.ts +++ b/examples/react/start-basic/src/routeTree.gen.ts @@ -8,28 +8,24 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as RedirectRouteImport } from './routes/redirect' import { Route as PostsRouteImport } from './routes/posts' import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' -import { ServerRoute as CustomScriptDotjsServerRouteImport } from './routes/customScript[.]js' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api/users' -import { ServerRoute as ApiUsersUserIdServerRouteImport } from './routes/api/users.$userId' - -const rootServerRouteImport = createServerRootRoute() const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -51,6 +47,11 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -80,6 +81,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutNestedLayoutRoute = PathlessLayoutNestedLayoutRouteImport.update({ id: '/_nested-layout', @@ -90,6 +96,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', @@ -102,147 +113,124 @@ const PathlessLayoutNestedLayoutRouteARoute = path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const CustomScriptDotjsServerRoute = CustomScriptDotjsServerRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersUserIdServerRoute = ApiUsersUserIdServerRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/customScript.js': typeof CustomScriptDotjsRoute '/deferred': typeof DeferredRoute '/posts': typeof PostsRouteWithChildren '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/customScript.js': typeof CustomScriptDotjsRoute '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/_pathlessLayout': typeof PathlessLayoutRouteWithChildren + '/customScript.js': typeof CustomScriptDotjsRoute '/deferred': typeof DeferredRoute '/posts': typeof PostsRouteWithChildren '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren '/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/customScript.js' | '/deferred' | '/posts' | '/redirect' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/route-a' | '/route-b' + | '/api/users/$userId' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/customScript.js' | '/deferred' | '/redirect' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' | '/route-a' | '/route-b' + | '/api/users/$userId' | '/posts/$postId/deep' id: | '__root__' | '/' | '/_pathlessLayout' + | '/customScript.js' | '/deferred' | '/posts' | '/redirect' | '/users' | '/_pathlessLayout/_nested-layout' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/_pathlessLayout/_nested-layout/route-a' | '/_pathlessLayout/_nested-layout/route-b' + | '/api/users/$userId' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren + CustomScriptDotjsRoute: typeof CustomScriptDotjsRoute DeferredRoute: typeof DeferredRoute PostsRoute: typeof PostsRouteWithChildren RedirectRoute: typeof RedirectRoute UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/customScript.js': typeof CustomScriptDotjsServerRoute - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesByTo { - '/customScript.js': typeof CustomScriptDotjsServerRoute - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/customScript.js': typeof CustomScriptDotjsServerRoute - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/customScript.js' | '/api/users' | '/api/users/$userId' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/customScript.js' | '/api/users' | '/api/users/$userId' - id: '__root__' | '/customScript.js' | '/api/users' | '/api/users/$userId' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - CustomScriptDotjsServerRoute: typeof CustomScriptDotjsServerRoute - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -274,6 +262,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -316,6 +311,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout/_nested-layout': { id: '/_pathlessLayout/_nested-layout' path: '' @@ -330,6 +332,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' path: '/route-b' @@ -346,31 +355,6 @@ declare module '@tanstack/react-router' { } } } -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/customScript.js': { - id: '/customScript.js' - path: '/customScript.js' - fullPath: '/customScript.js' - preLoaderRoute: typeof CustomScriptDotjsServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users/$userId': { - id: '/api/users/$userId' - path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute - } - } -} interface PathlessLayoutNestedLayoutRouteChildren { PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute @@ -426,34 +410,38 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) -interface ApiUsersServerRouteChildren { - ApiUsersUserIdServerRoute: typeof ApiUsersUserIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersUserIdServerRoute: ApiUsersUserIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersUserIdRoute: ApiUsersUserIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, PathlessLayoutRoute: PathlessLayoutRouteWithChildren, + CustomScriptDotjsRoute: CustomScriptDotjsRoute, DeferredRoute: DeferredRoute, PostsRoute: PostsRouteWithChildren, RedirectRoute: RedirectRoute, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - CustomScriptDotjsServerRoute: CustomScriptDotjsServerRoute, - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { startInstance } from './start.tsx' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + config: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/examples/react/start-basic/src/router.tsx b/examples/react/start-basic/src/router.tsx index c76eb0210cc..6773db547f0 100644 --- a/examples/react/start-basic/src/router.tsx +++ b/examples/react/start-basic/src/router.tsx @@ -1,22 +1,19 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' +import { getGlobalStartContext } from '@tanstack/react-start' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, defaultNotFoundComponent: () => , scrollRestoration: true, + ssr: { + nonce: getGlobalStartContext()?.nonce, + }, }) - return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/react/start-basic/src/routes/__root.tsx b/examples/react/start-basic/src/routes/__root.tsx index 346409e9d91..e9c952a1f74 100644 --- a/examples/react/start-basic/src/routes/__root.tsx +++ b/examples/react/start-basic/src/routes/__root.tsx @@ -9,10 +9,140 @@ import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import * as React from 'react' import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary' import { NotFound } from '~/components/NotFound' +import { Test, startInstance } from '~/start' import appCss from '~/styles/app.css?url' import { seo } from '~/utils/seo' +export const testServerMw = startInstance + .createMiddleware() + .server(({ next, context }) => { + context.fromFetch + // ^? + context.fromServerMw + // ^? + + return next({ + context: { + fromIndexServerMw: true, + }, + }) + }) + +export const testFnMw = startInstance + .createMiddleware({ type: 'function' }) + .middleware([testServerMw]) + .server(({ next, context }) => { + context.fromFetch + // ^? + context.fromServerMw + // ^? + context.fromFnMw + // ^? + context.fromIndexServerMw + // ^? + + return next({ + context: { + fromIndexFnMw: true, + }, + }) + }) + +export const testGetMiddleware = startInstance + .createMiddleware() + .server(({ next, context }) => { + return next({ + context: { + fromGetMiddleware: true, + }, + }) + }) + export const Route = createRootRoute({ + server: { + middleware: [testServerMw], + handlers: { + GET: ({ context, next }) => { + context.fromFetch + // ^? + context.fromServerMw + // ^? + context.fromIndexServerMw + // ^? + return next({ + context: { + fromGet: true, + }, + }) + }, + POST: ({ context, next }) => { + context.fromFetch + context.fromServerMw + context.fromIndexServerMw + return next({ + context: { + fromPost: true, + }, + }) + }, + }, + // handlers: ({ createHandlers }) => + // createHandlers({ + // GET: { + // middleware: [testGetMiddleware], + // handler: ({ context, next }) => { + // context.fromFetch + // // ^? + // context.fromServerMw + // // ^? + // context.fromIndexServerMw + // // ^? + // context.fromGetMiddleware + // // ^? + // return next({ + // context: { + // fromGet: true, + // fromPost: false, + // }, + // }) + // }, + // }, + // POST: { + // handler: ({ next }) => { + // return next({ + // context: { + // fromGet: false, + // fromPost: true, + // }, + // }) + // }, + // }, + // }), + test: (test) => {}, + }, + beforeLoad: ({ serverContext }) => { + serverContext?.fromFetch + // ^? + serverContext?.fromServerMw + // ^? + serverContext?.fromIndexServerMw + // ^? + serverContext?.fromGet + // ^? + return serverContext + }, + // ssr: false, + loader: ({ context }) => { + context?.fromFetch + // ^? + context?.fromServerMw + // ^? + context?.fromIndexServerMw + // ^? + context?.fromPost + // ^? + return new Test('test') + }, head: () => ({ meta: [ { diff --git a/examples/react/start-basic/src/routes/api/users.$userId.ts b/examples/react/start-basic/src/routes/api/users.$userId.ts index c5c2539932d..2ae7eb9c044 100644 --- a/examples/react/start-basic/src/routes/api/users.$userId.ts +++ b/examples/react/start-basic/src/routes/api/users.$userId.ts @@ -1,28 +1,32 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { json } from '@tanstack/react-start' import type { User } from '~/utils/users' -export const ServerRoute = createServerFileRoute('/api/users/$userId').methods({ - GET: async ({ params, request }) => { - console.info(`Fetching users by id=${params.userId}... @`, request.url) - try { - const res = await fetch( - 'https://jsonplaceholder.typicode.com/users/' + params.userId, - ) - if (!res.ok) { - throw new Error('Failed to fetch user') - } +export const Route = createFileRoute('/api/users/$userId')({ + server: { + handlers: { + GET: async ({ params, request }) => { + console.info(`Fetching users by id=${params.userId}... @`, request.url) + try { + const res = await fetch( + 'https://jsonplaceholder.typicode.com/users/' + params.userId, + ) + if (!res.ok) { + throw new Error('Failed to fetch user') + } - const user = (await res.json()) as User + const user = (await res.json()) as User - return json({ - id: user.id, - name: user.name, - email: user.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } + return json({ + id: user.id, + name: user.name, + email: user.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/examples/react/start-basic/src/routes/api/users.ts b/examples/react/start-basic/src/routes/api/users.ts index cb7f9e97b07..473a42e2930 100644 --- a/examples/react/start-basic/src/routes/api/users.ts +++ b/examples/react/start-basic/src/routes/api/users.ts @@ -1,9 +1,9 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' +import { createFileRoute } from '@tanstack/react-router' import { getRequestHeaders } from '@tanstack/react-start/server' import { createMiddleware, json } from '@tanstack/react-start' import type { User } from '~/utils/users' -const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( +const userLoggerMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: /users') console.info('Request Headers:', getRequestHeaders()) @@ -14,7 +14,7 @@ const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testParentMiddleware = createMiddleware({ type: 'request' }).server( +const testParentMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: testParentMiddleware') const result = await next() @@ -24,7 +24,7 @@ const testParentMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next, request }) => { console.info('In: testMiddleware') @@ -42,21 +42,26 @@ const testMiddleware = createMiddleware({ type: 'request' }) return result }) -export const ServerRoute = createServerFileRoute('/api/users') - .middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware]) - .methods({ - GET: async ({ request }) => { - console.info('GET /api/users @', request.url) - console.info('Fetching users... @', request.url) - const res = await fetch('https://jsonplaceholder.typicode.com/users') - if (!res.ok) { - throw new Error('Failed to fetch users') - } +export const Route = createFileRoute('/api/users')({ + server: { + middleware: [testMiddleware, userLoggerMiddleware], + handlers: { + GET: async ({ request }) => { + console.info('GET /api/users @', request.url) + console.info('Fetching users... @', request.url) + const res = await fetch('https://jsonplaceholder.typicode.com/users') + if (!res.ok) { + throw new Error('Failed to fetch users') + } - const data = (await res.json()) as Array + const data = (await res.json()) as Array - const list = data.slice(0, 10) + const list = data.slice(0, 10) - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, }, - }) + }, +}) diff --git a/examples/react/start-basic/src/routes/customScript[.]js.ts b/examples/react/start-basic/src/routes/customScript[.]js.ts index 92cc40dad44..811471ac153 100644 --- a/examples/react/start-basic/src/routes/customScript[.]js.ts +++ b/examples/react/start-basic/src/routes/customScript[.]js.ts @@ -1,10 +1,15 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' -export const ServerRoute = createServerFileRoute('/customScript.js').methods({ - GET: async ({ request }) => { - return new Response('console.log("Hello from customScript.js!")', { - headers: { - 'Content-Type': 'application/javascript', +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/customScript.js')({ + server: { + handlers: { + GET: () => { + return new Response('console.log("Hello from customScript.js!")', { + headers: { + 'Content-Type': 'application/javascript', + }, + }) }, - }) + }, }, }) diff --git a/examples/react/start-basic/src/routes/deferred.tsx b/examples/react/start-basic/src/routes/deferred.tsx index f3e09d1d4e4..c38808243e1 100644 --- a/examples/react/start-basic/src/routes/deferred.tsx +++ b/examples/react/start-basic/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/react-start' import { Suspense, useState } from 'react' const personServerFn = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(({ data: name }) => { return { name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(async ({ data: name }) => { await new Promise((r) => setTimeout(r, 1000)) return { name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/examples/react/start-basic/src/routes/index.tsx b/examples/react/start-basic/src/routes/index.tsx index 37c8d237bd4..9e9601e86d4 100644 --- a/examples/react/start-basic/src/routes/index.tsx +++ b/examples/react/start-basic/src/routes/index.tsx @@ -1,5 +1,70 @@ import { createFileRoute } from '@tanstack/react-router' +import { Test } from '~/start' + export const Route = createFileRoute('/')({ + // server: { + // handlers: { + // GET: ({ context, next }) => { + // context.fromFetch + // // ^? + // context.fromServerMw + // // ^? + // context.fromIndexServerMw + // // ^? + // return next({ + // context: { + // fromGet: true, + // }, + // }) + // }, + // POST: ({ context, next }) => { + // context.fromFetch + // context.fromServerMw + // context.fromIndexServerMw + // return next({ + // context: { + // fromPost: true, + // }, + // }) + // }, + // }, + // // handlers: ({ createHandlers }) => + // // createHandlers({ + // // GET: { + // // middleware: [testGetMiddleware], + // // handler: ({ context, next }) => { + // // context.fromFetch + // // // ^? + // // context.fromServerMw + // // // ^? + // // context.fromIndexServerMw + // // // ^? + // // context.fromGetMiddleware + // // // ^? + // // return next({ + // // context: { + // // fromGet: true, + // // fromPost: false, + // // }, + // // }) + // // }, + // // }, + // // POST: { + // // handler: ({ next }) => { + // // return next({ + // // context: { + // // fromGet: false, + // // fromPost: true, + // // }, + // // }) + // // }, + // // }, + // // }), + // test: (test) => {}, + // }, + loader: () => { + return new Test('test') + }, component: Home, }) diff --git a/examples/react/start-basic/src/server.ts b/examples/react/start-basic/src/server.ts new file mode 100644 index 00000000000..49256ef1e35 --- /dev/null +++ b/examples/react/start-basic/src/server.ts @@ -0,0 +1,11 @@ +// DO NOT DELETE THIS FILE!!! +// This file is a good smoke test to make sure the custom server entry is working +import handler from '@tanstack/react-start/server-entry' + +console.log("[server-entry]: using custom server entry in 'src/server.ts'") + +export default { + fetch(request: Request) { + return handler.fetch(request, { context: { fromFetch: true } }) + }, +} diff --git a/examples/react/start-basic/src/start.tsx b/examples/react/start-basic/src/start.tsx new file mode 100644 index 00000000000..38f083f237a --- /dev/null +++ b/examples/react/start-basic/src/start.tsx @@ -0,0 +1,114 @@ +import { createMiddleware, createStart } from '@tanstack/react-start' +import { createSerializationAdapter } from '@tanstack/react-router' +import type { Register } from '@tanstack/react-router' + +declare module '@tanstack/react-start' { + interface Register { + server: { + requestContext: { + fromFetch: boolean + } + } + } +} + +// @manuel +export const serverMw = createMiddleware().server(({ next, context }) => { + context.fromFetch + // ^? + + const nonce = Math.random().toString(16).slice(2, 10) + console.log('nonce', nonce) + return next({ + context: { + fromServerMw: true, + nonce, + }, + }) +}) + +export const fnMw = createMiddleware({ type: 'function' }) + .middleware([serverMw]) + .server(({ next, context }) => { + context.fromFetch + // ^? + + return next({ + context: { + fromFnMw: true, + }, + }) + }) + +const serializeClass = createSerializationAdapter({ + key: 'Test', + test: (v) => v instanceof Test, + toSerializable: (v) => v.test, + fromSerializable: (v) => new Test(v), +}) + +export class Test { + constructor(public test: string) {} + init() { + return this.test + } +} + +export const startInstance = createStart(() => { + return { + defaultSsr: true, + serializationAdapters: [serializeClass], + requestMiddleware: [serverMw], + functionMiddleware: [fnMw], + } +}) + +// type configKey2 = Register['configKey'] + +// type configKey = GetRegisteredConfigKey + +// type test3 = Register[GetRegisteredConfigKey]['~types'] + +// type test = Register[GetRegisteredConfigKey] extends { +// '~types': infer TTypes +// } +// ? 'defaultSsr' extends keyof TTypes +// ? TTypes['defaultSsr'] +// : unknown +// : unknown + +// export type RegisteredConfigType = TRegister extends Register +// ? TRegister['config'] extends { +// '~types': infer TTypes +// } +// ? TKey extends keyof TTypes +// ? TTypes[TKey] +// : unknown +// : unknown +// : unknown + +// type test = RegisteredSSROption +// type test2 = RegisteredConfigType + +type test3 = Register extends { + config: infer TConfig +} + ? TConfig extends { + '~types': infer TTypes + } + ? TTypes + : unknown + : unknown + +startInstance.createMiddleware().server(({ next, context }) => { + context.fromFetch + // ^? + context.fromServerMw + // ^? + + return next({ + context: { + fromStartInstanceMw: true, + }, + }) +}) diff --git a/examples/react/start-basic/src/utils/posts.tsx b/examples/react/start-basic/src/utils/posts.tsx index 52877be68c9..e3fbd013ff7 100644 --- a/examples/react/start-basic/src/utils/posts.tsx +++ b/examples/react/start-basic/src/utils/posts.tsx @@ -7,9 +7,10 @@ export type PostType = { body: string } -export const fetchPost = createServerFn() - .validator((d: string) => d) - .handler(async ({ data }) => { +export const fetchPost = createServerFn({ method: 'POST' }) + .inputValidator((d: string) => d) + .handler(async ({ data, context }) => { + console.log('Request context:', context) console.info(`Fetching post with id ${data}...`) const res = await fetch( `https://jsonplaceholder.typicode.com/posts/${data}`, diff --git a/examples/react/start-basic/vite.config.ts b/examples/react/start-basic/vite.config.ts index 70067dfa49a..9291b5563d4 100644 --- a/examples/react/start-basic/vite.config.ts +++ b/examples/react/start-basic/vite.config.ts @@ -11,7 +11,11 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({ customViteReactPlugin: true }), + tanstackStart({ + srcDirectory: 'src', + start: { entry: './start.tsx' }, + server: { entry: './server.ts' }, + }), viteReact(), ], }) diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.disabled.json similarity index 70% rename from examples/react/start-clerk-basic/package.json rename to examples/react/start-clerk-basic/package.disabled.json index f6ce00f14c0..bd4aec4202a 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.disabled.json @@ -9,10 +9,11 @@ "start": "vite start" }, "dependencies": { - "@clerk/tanstack-react-start": "^0.23.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@clerk/tanstack-react-start": "^0.19.0", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", + "@vitejs/plugin-react": "^4.3.4", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -26,7 +27,7 @@ "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-clerk-basic/src/routeTree.gen.ts b/examples/react/start-clerk-basic/src/routeTree.gen.ts index 8ac0e6e8c32..411606473c5 100644 --- a/examples/react/start-clerk-basic/src/routeTree.gen.ts +++ b/examples/react/start-clerk-basic/src/routeTree.gen.ts @@ -169,3 +169,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-clerk-basic/src/router.tsx b/examples/react/start-clerk-basic/src/router.tsx index c76eb0210cc..1a1d8822d20 100644 --- a/examples/react/start-clerk-basic/src/router.tsx +++ b/examples/react/start-clerk-basic/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/react/start-clerk-basic/src/routes/__root.tsx b/examples/react/start-clerk-basic/src/routes/__root.tsx index 1842003cea7..80ce274116b 100644 --- a/examples/react/start-clerk-basic/src/routes/__root.tsx +++ b/examples/react/start-clerk-basic/src/routes/__root.tsx @@ -17,13 +17,13 @@ import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { createServerFn } from '@tanstack/react-start' import * as React from 'react' import { getAuth } from '@clerk/tanstack-react-start/server' -import { getWebRequest } from '@tanstack/react-start/server' +import { getRequest } from '@tanstack/react-start/server' import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary.js' import { NotFound } from '~/components/NotFound.js' import appCss from '~/styles/app.css?url' const fetchClerkAuth = createServerFn({ method: 'GET' }).handler(async () => { - const { userId } = await getAuth(getWebRequest()!) + const { userId } = await getAuth(getRequest()) return { userId, diff --git a/examples/react/start-clerk-basic/src/server.ts b/examples/react/start-clerk-basic/src/server.ts index 1ded20f60b8..74b9ed60042 100644 --- a/examples/react/start-clerk-basic/src/server.ts +++ b/examples/react/start-clerk-basic/src/server.ts @@ -1,18 +1,9 @@ -import { - createStartHandler, - defaultStreamHandler, - defineHandlerCallback, -} from '@tanstack/react-start/server' +import { defaultStreamHandler } from '@tanstack/react-start/server' import { createClerkHandler } from '@clerk/tanstack-react-start/server' -import { createRouter } from './router' -const handlerFactory = createClerkHandler( - createStartHandler({ - createRouter, - }), -) +// TODO fixme +const clerkHandler = createClerkHandler({} as any) -export default defineHandlerCallback(async (event) => { - const startHandler = await handlerFactory(defaultStreamHandler) - return startHandler(event) -}) +export default { + fetch: clerkHandler(defaultStreamHandler), +} diff --git a/examples/react/start-clerk-basic/src/utils/posts.ts b/examples/react/start-clerk-basic/src/utils/posts.ts index a7e0e99cfa2..fbcc43c34b0 100644 --- a/examples/react/start-clerk-basic/src/utils/posts.ts +++ b/examples/react/start-clerk-basic/src/utils/posts.ts @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios diff --git a/examples/react/start-clerk-basic/vite.config.ts b/examples/react/start-clerk-basic/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-clerk-basic/vite.config.ts +++ b/examples/react/start-clerk-basic/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index f284a097e2b..6169e9a898b 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -14,10 +14,10 @@ "@convex-dev/react-query": "0.0.0-alpha.8", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-ssr-query": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-ssr-query": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "concurrently": "^8.2.2", "convex": "^1.19.0", "ky": "^1.7.4", @@ -33,11 +33,12 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-convex-trellaux/src/routeTree.gen.ts b/examples/react/start-convex-trellaux/src/routeTree.gen.ts index a616fbaf3f2..7b846980985 100644 --- a/examples/react/start-convex-trellaux/src/routeTree.gen.ts +++ b/examples/react/start-convex-trellaux/src/routeTree.gen.ts @@ -75,3 +75,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-convex-trellaux/src/router.tsx b/examples/react/start-convex-trellaux/src/router.tsx index c289f8b75ce..025f3564b52 100644 --- a/examples/react/start-convex-trellaux/src/router.tsx +++ b/examples/react/start-convex-trellaux/src/router.tsx @@ -1,4 +1,4 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { MutationCache, QueryClient, @@ -12,7 +12,7 @@ import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { +export function getRouter() { if (typeof document !== 'undefined') { notifyManager.setScheduler(window.requestAnimationFrame) } @@ -38,7 +38,7 @@ export function createRouter() { }) convexQueryClient.connect(queryClient) - const router = createTanStackRouter({ + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -61,6 +61,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-convex-trellaux/src/utils/posts.tsx b/examples/react/start-convex-trellaux/src/utils/posts.tsx index e87764806d1..cf602f98270 100644 --- a/examples/react/start-convex-trellaux/src/utils/posts.tsx +++ b/examples/react/start-convex-trellaux/src/utils/posts.tsx @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios diff --git a/examples/react/start-convex-trellaux/vite.config.ts b/examples/react/start-convex-trellaux/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-convex-trellaux/vite.config.ts +++ b/examples/react/start-convex-trellaux/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index d3173347cc2..0c6c73ffdcb 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -9,9 +9,9 @@ "start": "vite start" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -19,7 +19,8 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/start-counter/src/routeTree.gen.ts b/examples/react/start-counter/src/routeTree.gen.ts index d204c269b33..d9eff184704 100644 --- a/examples/react/start-counter/src/routeTree.gen.ts +++ b/examples/react/start-counter/src/routeTree.gen.ts @@ -57,3 +57,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-counter/src/router.tsx b/examples/react/start-counter/src/router.tsx index 25729701a7b..24eb56ec3aa 100644 --- a/examples/react/start-counter/src/router.tsx +++ b/examples/react/start-counter/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -12,6 +12,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-counter/src/routes/index.tsx b/examples/react/start-counter/src/routes/index.tsx index b95cc76ec85..0faec87836d 100644 --- a/examples/react/start-counter/src/routes/index.tsx +++ b/examples/react/start-counter/src/routes/index.tsx @@ -15,7 +15,7 @@ const getCount = createServerFn({ method: 'GET' }).handler(() => { }) const updateCount = createServerFn({ method: 'POST' }) - .validator((addBy: number) => addBy) + .inputValidator((addBy: number) => addBy) .handler(async ({ data }) => { const count = await readCount() await fs.promises.writeFile(filePath, `${count + data}`) diff --git a/examples/react/start-counter/vite.config.ts b/examples/react/start-counter/vite.config.ts index 86790438cf0..bae3acaefe5 100644 --- a/examples/react/start-counter/vite.config.ts +++ b/examples/react/start-counter/vite.config.ts @@ -1,9 +1,10 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { port: 3000, }, - plugins: [tanstackStart()], + plugins: [tanstackStart(), viteReact()], }) diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index f11d57f21fe..c63ff23b976 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,11 +25,12 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" }, "keywords": [], diff --git a/examples/react/start-large/src/routeTree.gen.ts b/examples/react/start-large/src/routeTree.gen.ts index 11eb0131fd7..ac357b7aeac 100644 --- a/examples/react/start-large/src/routeTree.gen.ts +++ b/examples/react/start-large/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as RelativeRouteImport } from './routes/relative' import { Route as LinkPropsRouteImport } from './routes/linkProps' @@ -17,14 +15,9 @@ import { Route as AbsoluteRouteImport } from './routes/absolute' import { Route as SearchRouteRouteImport } from './routes/search/route' import { Route as ParamsRouteRouteImport } from './routes/params/route' import { Route as IndexRouteImport } from './routes/index' -import { - Route as SearchSearchPlaceholderRouteImport, - ServerRoute as SearchSearchPlaceholderServerRouteImport, -} from './routes/search/searchPlaceholder' +import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' -const rootServerRouteImport = createServerRootRoute() - const RelativeRoute = RelativeRouteImport.update({ id: '/relative', path: '/relative', @@ -65,12 +58,6 @@ const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderRouteImport.update({ path: '/$paramsPlaceholder', getParentRoute: () => ParamsRouteRoute, } as any) -const SearchSearchPlaceholderServerRoute = - SearchSearchPlaceholderServerRouteImport.update({ - id: '/searchPlaceholder', - path: '/searchPlaceholder', - getParentRoute: () => rootServerRouteImport, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -144,25 +131,6 @@ export interface RootRouteChildren { LinkPropsRoute: typeof LinkPropsRoute RelativeRoute: typeof RelativeRoute } -export interface FileServerRoutesByFullPath { - '/search/searchPlaceholder': typeof SearchSearchPlaceholderServerRoute -} -export interface FileServerRoutesByTo { - '/search/searchPlaceholder': typeof SearchSearchPlaceholderServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/search/searchPlaceholder': typeof SearchSearchPlaceholderServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/search/searchPlaceholder' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/search/searchPlaceholder' - id: '__root__' | '/search/searchPlaceholder' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren {} declare module '@tanstack/react-router' { interface FileRoutesByPath { @@ -224,17 +192,6 @@ declare module '@tanstack/react-router' { } } } -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/search/searchPlaceholder': { - id: '/search/searchPlaceholder' - path: '/searchPlaceholder' - fullPath: '/search/searchPlaceholder' - preLoaderRoute: typeof SearchSearchPlaceholderServerRouteImport - parentRoute: typeof rootServerRouteImport - } - } -} interface ParamsRouteRouteChildren { ParamsParamsPlaceholderRoute: typeof ParamsParamsPlaceholderRoute @@ -271,7 +228,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = {} -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-large/src/router.tsx b/examples/react/start-large/src/router.tsx index 7e1bd53e005..514a00e9ff8 100644 --- a/examples/react/start-large/src/router.tsx +++ b/examples/react/start-large/src/router.tsx @@ -1,11 +1,10 @@ -// src/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { QueryClient } from '@tanstack/react-query' import { routeTree } from './routeTree.gen' -export function createRouter() { +export function getRouter() { const queryClient = new QueryClient() - const router = createTanStackRouter({ + const router = createRouter({ routeTree, context: { queryClient: queryClient, @@ -14,9 +13,3 @@ export function createRouter() { return router } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/react/start-large/src/routes/params/$paramsPlaceholder.tsx b/examples/react/start-large/src/routes/params/$paramsPlaceholder.tsx index 7238dea3148..568adb11bea 100644 --- a/examples/react/start-large/src/routes/params/$paramsPlaceholder.tsx +++ b/examples/react/start-large/src/routes/params/$paramsPlaceholder.tsx @@ -15,7 +15,7 @@ const loaderResult = v.object({ }) const middleware = createMiddleware({ type: 'function' }) - .validator(params) + .inputValidator(params) .client(({ next }) => { const context = { client: { paramsPlaceholder: 'paramsPlaceholder' } } return next({ diff --git a/examples/react/start-large/src/routes/search/searchPlaceholder.tsx b/examples/react/start-large/src/routes/search/searchPlaceholder.tsx index eb7f95c39a7..bcf6bc777f8 100644 --- a/examples/react/start-large/src/routes/search/searchPlaceholder.tsx +++ b/examples/react/start-large/src/routes/search/searchPlaceholder.tsx @@ -1,6 +1,4 @@ -import { createServerFileRoute } from '@tanstack/react-start/server' -import { Link, createFileRoute } from '@tanstack/react-router' - +import { createFileRoute, Link } from '@tanstack/react-router' import * as v from 'valibot' import { queryOptions } from '@tanstack/react-query' import { createMiddleware, createServerFn } from '@tanstack/react-start' @@ -17,7 +15,7 @@ const loaderResult = v.object({ }) const middleware = createMiddleware({ type: 'function' }) - .validator(search) + .inputValidator(search) .client(({ next }) => { const context = { client: { searchPlaceholder: 'searchPlaceholder' } } return next({ @@ -39,19 +37,17 @@ const fn = createServerFn() const result = v.parse(loaderResult, { searchPlaceholder: 0, }) - return result }) -export const ServerRoute = createServerFileRoute( - '/search/searchPlaceholder', -).methods((api) => ({ - GET: api.handler(() => { - return 'searchPlaceholder' - }), -})) - export const Route = createFileRoute('/search/searchPlaceholder')({ + server: { + handlers: { + GET: () => { + return new Response('searchPlaceholder') + }, + }, + }, component: SearchComponent, validateSearch: search, loaderDeps: ({ search }) => ({ search }), @@ -68,7 +64,6 @@ export const Route = createFileRoute('/search/searchPlaceholder')({ return { search, - external, } }, }) diff --git a/examples/react/start-large/vite.config.ts b/examples/react/start-large/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-large/vite.config.ts +++ b/examples/react/start-large/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-material-ui/package.json b/examples/react/start-material-ui/package.json index 24d061ee572..d57e6c71dde 100644 --- a/examples/react/start-material-ui/package.json +++ b/examples/react/start-material-ui/package.json @@ -14,9 +14,9 @@ "@emotion/styled": "11.14.0", "@fontsource-variable/roboto": "5.2.5", "@mui/material": "6.4.7", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-start": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.24.2" @@ -25,8 +25,9 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-material-ui/src/routeTree.gen.ts b/examples/react/start-material-ui/src/routeTree.gen.ts index 59499d9fbf3..bb937ce84a1 100644 --- a/examples/react/start-material-ui/src/routeTree.gen.ts +++ b/examples/react/start-material-ui/src/routeTree.gen.ts @@ -75,3 +75,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-material-ui/src/router.tsx b/examples/react/start-material-ui/src/router.tsx index e4232569c04..a464233af90 100644 --- a/examples/react/start-material-ui/src/router.tsx +++ b/examples/react/start-material-ui/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: (err) =>

    {err.error.stack}

    , @@ -15,6 +15,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-material-ui/vite.config.ts b/examples/react/start-material-ui/vite.config.ts index 1c59bcc8f98..5bd46872f12 100644 --- a/examples/react/start-material-ui/vite.config.ts +++ b/examples/react/start-material-ui/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -14,5 +15,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index 80d3c4510af..a1460067b9e 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -15,9 +15,9 @@ "dependencies": { "@supabase/ssr": "^0.5.2", "@supabase/supabase-js": "^2.48.1", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -25,11 +25,12 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-supabase-basic/src/routeTree.gen.ts b/examples/react/start-supabase-basic/src/routeTree.gen.ts index 0d9f3c20242..19a8180edba 100644 --- a/examples/react/start-supabase-basic/src/routeTree.gen.ts +++ b/examples/react/start-supabase-basic/src/routeTree.gen.ts @@ -214,3 +214,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-supabase-basic/src/router.tsx b/examples/react/start-supabase-basic/src/router.tsx index 12377b7bae0..24eb56ec3aa 100644 --- a/examples/react/start-supabase-basic/src/router.tsx +++ b/examples/react/start-supabase-basic/src/router.tsx @@ -1,9 +1,8 @@ -// src/router.tsx -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }) @@ -13,6 +12,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-supabase-basic/src/routes/_authed.tsx b/examples/react/start-supabase-basic/src/routes/_authed.tsx index ed97848e6aa..f6edff63c18 100644 --- a/examples/react/start-supabase-basic/src/routes/_authed.tsx +++ b/examples/react/start-supabase-basic/src/routes/_authed.tsx @@ -4,7 +4,7 @@ import { Login } from '../components/Login' import { getSupabaseServerClient } from '../utils/supabase' export const loginFn = createServerFn({ method: 'POST' }) - .validator((d: { email: string; password: string }) => d) + .inputValidator((d: { email: string; password: string }) => d) .handler(async ({ data }) => { const supabase = getSupabaseServerClient() const { error } = await supabase.auth.signInWithPassword({ diff --git a/examples/react/start-supabase-basic/src/routes/signup.tsx b/examples/react/start-supabase-basic/src/routes/signup.tsx index d7c00600842..908d8ffcb3a 100644 --- a/examples/react/start-supabase-basic/src/routes/signup.tsx +++ b/examples/react/start-supabase-basic/src/routes/signup.tsx @@ -5,7 +5,7 @@ import { Auth } from '../components/Auth' import { getSupabaseServerClient } from '../utils/supabase' export const signupFn = createServerFn({ method: 'POST' }) - .validator( + .inputValidator( (d: { email: string; password: string; redirectUrl?: string }) => d, ) .handler(async ({ data }) => { diff --git a/examples/react/start-supabase-basic/src/utils/posts.ts b/examples/react/start-supabase-basic/src/utils/posts.ts index 5512187e92d..297beefeb7c 100644 --- a/examples/react/start-supabase-basic/src/utils/posts.ts +++ b/examples/react/start-supabase-basic/src/utils/posts.ts @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(async ({ data: postId }) => { console.info(`Fetching post with id ${postId}...`) const post = await axios diff --git a/examples/react/start-supabase-basic/src/utils/supabase.ts b/examples/react/start-supabase-basic/src/utils/supabase.ts index 39ad47a8fac..b06ffb81cd5 100644 --- a/examples/react/start-supabase-basic/src/utils/supabase.ts +++ b/examples/react/start-supabase-basic/src/utils/supabase.ts @@ -1,4 +1,4 @@ -import { parseCookies, setCookie } from '@tanstack/react-start/server' +import { getCookies, setCookie } from '@tanstack/react-start/server' import { createServerClient } from '@supabase/ssr' export function getSupabaseServerClient() { @@ -8,7 +8,7 @@ export function getSupabaseServerClient() { { cookies: { getAll() { - return Object.entries(parseCookies()).map(([name, value]) => ({ + return Object.entries(getCookies()).map(([name, value]) => ({ name, value, })) diff --git a/examples/react/start-supabase-basic/vite.config.ts b/examples/react/start-supabase-basic/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-supabase-basic/vite.config.ts +++ b/examples/react/start-supabase-basic/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-tailwind-v4/package.json b/examples/react/start-tailwind-v4/package.json index 8f4c4cd41bc..bc9bd7b0067 100644 --- a/examples/react/start-tailwind-v4/package.json +++ b/examples/react/start-tailwind-v4/package.json @@ -9,9 +9,9 @@ "start": "node .output/server/index.mjs" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.6.0", @@ -22,9 +22,10 @@ "@types/node": "^22.5.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "tailwindcss": "^4.1.6", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-tailwind-v4/src/routeTree.gen.ts b/examples/react/start-tailwind-v4/src/routeTree.gen.ts index d204c269b33..d9eff184704 100644 --- a/examples/react/start-tailwind-v4/src/routeTree.gen.ts +++ b/examples/react/start-tailwind-v4/src/routeTree.gen.ts @@ -57,3 +57,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-tailwind-v4/src/router.tsx b/examples/react/start-tailwind-v4/src/router.tsx index b8744cb41fb..6544aaf68b3 100644 --- a/examples/react/start-tailwind-v4/src/router.tsx +++ b/examples/react/start-tailwind-v4/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', scrollRestoration: true, @@ -13,6 +13,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-tailwind-v4/vite.config.ts b/examples/react/start-tailwind-v4/vite.config.ts index 6a8f14e7c01..360b4f7ddae 100644 --- a/examples/react/start-tailwind-v4/vite.config.ts +++ b/examples/react/start-tailwind-v4/vite.config.ts @@ -2,6 +2,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import tailwindcss from '@tailwindcss/vite' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -12,6 +13,7 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), tailwindcss(), ], }) diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index 662984bd514..c4fa5508a85 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-ssr-query": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-ssr-query": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "ky": "^1.7.4", "msw": "^2.7.0", "react": "^19.0.0", @@ -28,11 +28,12 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.6.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-trellaux/src/db/board.ts b/examples/react/start-trellaux/src/db/board.ts index 9c403e405ef..92f94967e9c 100644 --- a/examples/react/start-trellaux/src/db/board.ts +++ b/examples/react/start-trellaux/src/db/board.ts @@ -33,7 +33,7 @@ export const getBoards = createServerFn({ method: 'GET' }).handler(async () => { }) export const getBoard = createServerFn({ method: 'GET' }) - .validator(z.string()) + .inputValidator(z.string()) .handler(async ({ data }) => { await delay(DELAY) const board = boards.find((b) => b.id === data) @@ -42,7 +42,7 @@ export const getBoard = createServerFn({ method: 'GET' }) }) export const createColumn = createServerFn() - .validator(newColumnSchema) + .inputValidator(newColumnSchema) .handler(async ({ data }) => { await delay(DELAY) const newColumn = newColumnSchema.parse(data) @@ -62,7 +62,7 @@ export const createColumn = createServerFn() }) export const createItem = createServerFn() - .validator(itemSchema) + .inputValidator(itemSchema) .handler(async ({ data }) => { await delay(DELAY) const item = itemSchema.parse(data) @@ -75,7 +75,7 @@ export const createItem = createServerFn() }) export const deleteItem = createServerFn({ method: 'GET' }) - .validator(deleteItemSchema) + .inputValidator(deleteItemSchema) .handler(async ({ data }) => { await delay(DELAY) const { id } = deleteItemSchema.parse(data) @@ -85,7 +85,7 @@ export const deleteItem = createServerFn({ method: 'GET' }) }) export const updateItem = createServerFn() - .validator(itemSchema) + .inputValidator(itemSchema) .handler(async ({ data }) => { await delay(DELAY) const item = itemSchema.parse(data) @@ -97,7 +97,7 @@ export const updateItem = createServerFn() }) export const updateColumn = createServerFn() - .validator(updateColumnSchema) + .inputValidator(updateColumnSchema) .handler(async ({ data }) => { await delay(DELAY) const column = updateColumnSchema.parse(data) @@ -109,7 +109,7 @@ export const updateColumn = createServerFn() }) export const updateBoard = createServerFn() - .validator(updateBoardSchema) + .inputValidator(updateBoardSchema) .handler(async ({ data }) => { await delay(DELAY) const update = updateBoardSchema.parse(data) @@ -119,7 +119,7 @@ export const updateBoard = createServerFn() }) export const deleteColumn = createServerFn({ method: 'GET' }) - .validator(deleteColumnSchema) + .inputValidator(deleteColumnSchema) .handler(async ({ data }) => { await delay(DELAY) const { id } = deleteColumnSchema.parse(data) diff --git a/examples/react/start-trellaux/src/routeTree.gen.ts b/examples/react/start-trellaux/src/routeTree.gen.ts index a616fbaf3f2..7b846980985 100644 --- a/examples/react/start-trellaux/src/routeTree.gen.ts +++ b/examples/react/start-trellaux/src/routeTree.gen.ts @@ -75,3 +75,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/react/start-trellaux/src/router.tsx b/examples/react/start-trellaux/src/router.tsx index 1a6a98172d0..0aeae73df21 100644 --- a/examples/react/start-trellaux/src/router.tsx +++ b/examples/react/start-trellaux/src/router.tsx @@ -1,4 +1,3 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' import { MutationCache, QueryClient, @@ -6,11 +5,12 @@ import { } from '@tanstack/react-query' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import toast from 'react-hot-toast' +import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { +export function getRouter() { if (typeof document !== 'undefined') { notifyManager.setScheduler(window.requestAnimationFrame) } @@ -33,7 +33,7 @@ export function createRouter() { }), }) - const router = createTanStackRouter({ + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -53,6 +53,6 @@ export function createRouter() { declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType } } diff --git a/examples/react/start-trellaux/src/utils/posts.tsx b/examples/react/start-trellaux/src/utils/posts.tsx index e87764806d1..cf602f98270 100644 --- a/examples/react/start-trellaux/src/utils/posts.tsx +++ b/examples/react/start-trellaux/src/utils/posts.tsx @@ -9,7 +9,7 @@ export type PostType = { } export const fetchPost = createServerFn({ method: 'GET' }) - .validator((postId: string) => postId) + .inputValidator((postId: string) => postId) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios diff --git a/examples/react/start-trellaux/vite.config.ts b/examples/react/start-trellaux/vite.config.ts index 1f5fd1961a3..f10c86e79fc 100644 --- a/examples/react/start-trellaux/vite.config.ts +++ b/examples/react/start-trellaux/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteReact from '@vitejs/plugin-react' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }) diff --git a/examples/react/start-workos/package.json b/examples/react/start-workos/package.json index d1210999d4a..fc4bd17a3a6 100644 --- a/examples/react/start-workos/package.json +++ b/examples/react/start-workos/package.json @@ -14,9 +14,9 @@ "license": "MIT", "dependencies": { "@radix-ui/themes": "^3.2.1", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/react-start": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/react-start": "^1.132.0-alpha.25", "@workos-inc/node": "^7.45.0", "iron-session": "^8.0.4", "jose": "^6.0.10", @@ -26,11 +26,12 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/start-workos/src/authkit/serverFunctions.ts b/examples/react/start-workos/src/authkit/serverFunctions.ts index 8ce62c746b9..07d5909f8bd 100644 --- a/examples/react/start-workos/src/authkit/serverFunctions.ts +++ b/examples/react/start-workos/src/authkit/serverFunctions.ts @@ -6,7 +6,7 @@ import { getWorkOS } from './ssr/workos'; import type { GetAuthURLOptions, NoUserInfo, UserInfo } from './ssr/interfaces'; export const getAuthorizationUrl = createServerFn({ method: 'GET' }) - .validator((options?: GetAuthURLOptions) => options) + .inputValidator((options?: GetAuthURLOptions) => options) .handler(({ data: options = {} }) => { const { returnPathname, screenHint, redirectUri } = options; @@ -20,19 +20,19 @@ export const getAuthorizationUrl = createServerFn({ method: 'GET' }) }); export const getSignInUrl = createServerFn({ method: 'GET' }) - .validator((data?: string) => data) + .inputValidator((data?: string) => data) .handler(async ({ data: returnPathname }) => { return await getAuthorizationUrl({ data: { returnPathname, screenHint: 'sign-in' } }); }); export const getSignUpUrl = createServerFn({ method: 'GET' }) - .validator((data?: string) => data) + .inputValidator((data?: string) => data) .handler(async ({ data: returnPathname }) => { return getAuthorizationUrl({ data: { returnPathname, screenHint: 'sign-up' } }); }); -export const signOut = createServerFn({ method: 'POST', response: 'full' }) - .validator((data?: string) => data) +export const signOut = createServerFn({ method: 'POST' }) + .inputValidator((data?: string) => data) .handler(async ({ data: returnTo }) => { const cookieName = getConfig('cookieName') || 'wos_session'; deleteCookie(cookieName); diff --git a/examples/react/start-workos/src/routeTree.gen.ts b/examples/react/start-workos/src/routeTree.gen.ts index e7d11fd8942..ff9b7a5c87c 100644 --- a/examples/react/start-workos/src/routeTree.gen.ts +++ b/examples/react/start-workos/src/routeTree.gen.ts @@ -8,16 +8,12 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/react-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as LogoutRouteImport } from './routes/logout' import { Route as AuthenticatedRouteImport } from './routes/_authenticated' import { Route as IndexRouteImport } from './routes/index' import { Route as AuthenticatedAccountRouteImport } from './routes/_authenticated/account' -import { ServerRoute as ApiAuthCallbackServerRouteImport } from './routes/api/auth/callback' - -const rootServerRouteImport = createServerRootRoute() +import { Route as ApiAuthCallbackRouteImport } from './routes/api/auth/callback' const LogoutRoute = LogoutRouteImport.update({ id: '/logout', @@ -38,21 +34,23 @@ const AuthenticatedAccountRoute = AuthenticatedAccountRouteImport.update({ path: '/account', getParentRoute: () => AuthenticatedRoute, } as any) -const ApiAuthCallbackServerRoute = ApiAuthCallbackServerRouteImport.update({ +const ApiAuthCallbackRoute = ApiAuthCallbackRouteImport.update({ id: '/api/auth/callback', path: '/api/auth/callback', - getParentRoute: () => rootServerRouteImport, + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/logout': typeof LogoutRoute '/account': typeof AuthenticatedAccountRoute + '/api/auth/callback': typeof ApiAuthCallbackRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/logout': typeof LogoutRoute '/account': typeof AuthenticatedAccountRoute + '/api/auth/callback': typeof ApiAuthCallbackRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -60,45 +58,27 @@ export interface FileRoutesById { '/_authenticated': typeof AuthenticatedRouteWithChildren '/logout': typeof LogoutRoute '/_authenticated/account': typeof AuthenticatedAccountRoute + '/api/auth/callback': typeof ApiAuthCallbackRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/logout' | '/account' + fullPaths: '/' | '/logout' | '/account' | '/api/auth/callback' fileRoutesByTo: FileRoutesByTo - to: '/' | '/logout' | '/account' + to: '/' | '/logout' | '/account' | '/api/auth/callback' id: | '__root__' | '/' | '/_authenticated' | '/logout' | '/_authenticated/account' + | '/api/auth/callback' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute AuthenticatedRoute: typeof AuthenticatedRouteWithChildren LogoutRoute: typeof LogoutRoute -} -export interface FileServerRoutesByFullPath { - '/api/auth/callback': typeof ApiAuthCallbackServerRoute -} -export interface FileServerRoutesByTo { - '/api/auth/callback': typeof ApiAuthCallbackServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/auth/callback': typeof ApiAuthCallbackServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/auth/callback' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/auth/callback' - id: '__root__' | '/api/auth/callback' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiAuthCallbackServerRoute: typeof ApiAuthCallbackServerRoute + ApiAuthCallbackRoute: typeof ApiAuthCallbackRoute } declare module '@tanstack/react-router' { @@ -131,16 +111,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedAccountRouteImport parentRoute: typeof AuthenticatedRoute } - } -} -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { '/api/auth/callback': { id: '/api/auth/callback' path: '/api/auth/callback' fullPath: '/api/auth/callback' - preLoaderRoute: typeof ApiAuthCallbackServerRouteImport - parentRoute: typeof rootServerRouteImport + preLoaderRoute: typeof ApiAuthCallbackRouteImport + parentRoute: typeof rootRouteImport } } } @@ -161,13 +137,16 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AuthenticatedRoute: AuthenticatedRouteWithChildren, LogoutRoute: LogoutRoute, + ApiAuthCallbackRoute: ApiAuthCallbackRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiAuthCallbackServerRoute: ApiAuthCallbackServerRoute, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/examples/react/start-workos/src/router.tsx b/examples/react/start-workos/src/router.tsx index 9abe341f5d5..4a6b5e6885f 100644 --- a/examples/react/start-workos/src/router.tsx +++ b/examples/react/start-workos/src/router.tsx @@ -1,17 +1,11 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router'; +import { createRouter } from '@tanstack/react-router'; import { routeTree } from './routeTree.gen'; -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, scrollRestoration: true, }); return router; } - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType; - } -} diff --git a/examples/react/start-workos/src/routes/api/auth/callback.tsx b/examples/react/start-workos/src/routes/api/auth/callback.tsx index edc7d29c138..8ba493b7dc9 100644 --- a/examples/react/start-workos/src/routes/api/auth/callback.tsx +++ b/examples/react/start-workos/src/routes/api/auth/callback.tsx @@ -1,77 +1,81 @@ -import { createServerFileRoute } from '@tanstack/react-start/server'; +import { createFileRoute } from '@tanstack/react-router'; import { getConfig } from '../../../authkit/ssr/config'; import { saveSession } from '../../../authkit/ssr/session'; import { getWorkOS } from '../../../authkit/ssr/workos'; -export const ServerRoute = createServerFileRoute('/api/auth/callback').methods({ - GET: async ({ request }) => { - const url = new URL(request.url); - const code = url.searchParams.get('code'); - const state = url.searchParams.get('state'); - let returnPathname = state && state !== 'null' ? JSON.parse(atob(state)).returnPathname : null; - - if (code) { - try { - // Use the code returned to us by AuthKit and authenticate the user with WorkOS - const { accessToken, refreshToken, user, impersonator } = await getWorkOS().userManagement.authenticateWithCode( - { - clientId: getConfig('clientId'), - code, - }, - ); - - // If baseURL is provided, use it instead of request.nextUrl - // This is useful if the app is being run in a container like docker where - // the hostname can be different from the one in the request +export const Route = createFileRoute('/api/auth/callback')({ + server: { + handlers: { + GET: async ({ request }) => { const url = new URL(request.url); - - // Cleanup params - url.searchParams.delete('code'); - url.searchParams.delete('state'); - - // Redirect to the requested path and store the session - returnPathname = returnPathname ?? '/'; - - // Extract the search params if they are present - if (returnPathname.includes('?')) { - const newUrl = new URL(returnPathname, 'https://example.com'); - url.pathname = newUrl.pathname; - - for (const [key, value] of newUrl.searchParams) { - url.searchParams.append(key, value); + const code = url.searchParams.get('code'); + const state = url.searchParams.get('state'); + let returnPathname = state && state !== 'null' ? JSON.parse(atob(state)).returnPathname : null; + + if (code) { + try { + // Use the code returned to us by AuthKit and authenticate the user with WorkOS + const { accessToken, refreshToken, user, impersonator } = + await getWorkOS().userManagement.authenticateWithCode({ + clientId: getConfig('clientId'), + code, + }); + + // If baseURL is provided, use it instead of request.nextUrl + // This is useful if the app is being run in a container like docker where + // the hostname can be different from the one in the request + const url = new URL(request.url); + + // Cleanup params + url.searchParams.delete('code'); + url.searchParams.delete('state'); + + // Redirect to the requested path and store the session + returnPathname = returnPathname ?? '/'; + + // Extract the search params if they are present + if (returnPathname.includes('?')) { + const newUrl = new URL(returnPathname, 'https://example.com'); + url.pathname = newUrl.pathname; + + for (const [key, value] of newUrl.searchParams) { + url.searchParams.append(key, value); + } + } else { + url.pathname = returnPathname; + } + + const response = redirectWithFallback(url.toString()); + + if (!accessToken || !refreshToken) throw new Error('response is missing tokens'); + + await saveSession({ accessToken, refreshToken, user, impersonator }); + + return response; + } catch (error) { + const errorRes = { + error: error instanceof Error ? error.message : String(error), + }; + + console.error(errorRes); + + return errorResponse(); } - } else { - url.pathname = returnPathname; } - const response = redirectWithFallback(url.toString()); - - if (!accessToken || !refreshToken) throw new Error('response is missing tokens'); - - await saveSession({ accessToken, refreshToken, user, impersonator }); - - return response; - } catch (error) { - const errorRes = { - error: error instanceof Error ? error.message : String(error), - }; - - console.error(errorRes); - return errorResponse(); - } - } - - return errorResponse(); - - function errorResponse() { - return errorResponseWithFallback({ - error: { - message: 'Something went wrong', - description: "Couldn't sign in. If you are not sure what happened, please contact your organization admin.", - }, - }); - } + + function errorResponse() { + return errorResponseWithFallback({ + error: { + message: 'Something went wrong', + description: + "Couldn't sign in. If you are not sure what happened, please contact your organization admin.", + }, + }); + } + }, + }, }, }); diff --git a/examples/react/start-workos/src/routes/logout.tsx b/examples/react/start-workos/src/routes/logout.tsx index 41e8bc0b88e..401d6cbc995 100644 --- a/examples/react/start-workos/src/routes/logout.tsx +++ b/examples/react/start-workos/src/routes/logout.tsx @@ -3,5 +3,7 @@ import { signOut } from '../authkit/serverFunctions'; export const Route = createFileRoute('/logout')({ preload: false, - loader: () => signOut(), + loader: async () => { + await signOut(); + }, }); diff --git a/examples/react/start-workos/vite.config.ts b/examples/react/start-workos/vite.config.ts index cd888de96e5..fce3baa54da 100644 --- a/examples/react/start-workos/vite.config.ts +++ b/examples/react/start-workos/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite'; import tsConfigPaths from 'vite-tsconfig-paths'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; +import viteReact from '@vitejs/plugin-react'; export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteReact(), ], }); diff --git a/examples/react/view-transitions/package.json b/examples/react/view-transitions/package.json index 4d1ee88780c..858d3ab38c2 100644 --- a/examples/react/view-transitions/package.json +++ b/examples/react/view-transitions/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index a323942261c..4ef7b7dd8a2 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", "framer-motion": "^11.18.2", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -25,6 +25,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index f68cbf850f3..28bcc5e8e85 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -12,9 +12,9 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "@trpc/tanstack-react-query": "^11.4.3", @@ -33,6 +33,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "tsx": "^4.20.3", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/with-trpc-react-query/src/server/server.ts b/examples/react/with-trpc-react-query/src/server/server.ts index 1f8b4a3c47f..b2b3fd24855 100644 --- a/examples/react/with-trpc-react-query/src/server/server.ts +++ b/examples/react/with-trpc-react-query/src/server/server.ts @@ -24,7 +24,7 @@ export const createServer = async ( ) => { const app = express() - app.use('/trpc', trpcMiddleWare) + app.use('/trpc', trpcMiddleWare as any) if (!isProd) { const vite = await import('vite') diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index 0d48dd0ef2b..6cbbf6228af 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -10,9 +10,9 @@ "start": "NODE_ENV=production node dist/server/server.js" }, "dependencies": { - "@tanstack/react-router": "^1.131.50", - "@tanstack/react-router-devtools": "^1.131.50", - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/react-router": "^1.132.0-alpha.25", + "@tanstack/react-router-devtools": "^1.132.0-alpha.25", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "autoprefixer": "^10.4.20", @@ -30,6 +30,6 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "tsx": "^4.20.3", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/react/with-trpc/src/server/server.ts b/examples/react/with-trpc/src/server/server.ts index 1f8b4a3c47f..b2b3fd24855 100644 --- a/examples/react/with-trpc/src/server/server.ts +++ b/examples/react/with-trpc/src/server/server.ts @@ -24,7 +24,7 @@ export const createServer = async ( ) => { const app = express() - app.use('/trpc', trpcMiddleWare) + app.use('/trpc', trpcMiddleWare as any) if (!isProd) { const vite = await import('vite') diff --git a/examples/solid/basic-devtools-panel/package.json b/examples/solid/basic-devtools-panel/package.json index fc76e08283a..61b187cc2d6 100644 --- a/examples/solid/basic-devtools-panel/package.json +++ b/examples/solid/basic-devtools-panel/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", @@ -18,8 +18,8 @@ "tailwindcss": "^3.4.17" }, "devDependencies": { - "vite-plugin-solid": "^2.11.6", + "vite-plugin-solid": "^2.11.8", "typescript": "^5.7.2", - "vite": "^6.3.5" + "vite": "^7.1.1" } } diff --git a/examples/solid/basic-file-based/package.json b/examples/solid/basic-file-based/package.json index 42e6490238b..6b8e8a9cf5d 100644 --- a/examples/solid/basic-file-based/package.json +++ b/examples/solid/basic-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "redaxios": "^0.5.1", @@ -19,9 +19,9 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/basic-non-nested-devtools/package.json b/examples/solid/basic-non-nested-devtools/package.json index 305e19f9db1..1e6f1906ec7 100644 --- a/examples/solid/basic-non-nested-devtools/package.json +++ b/examples/solid/basic-non-nested-devtools/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "redaxios": "^0.5.1", "postcss": "^8.5.1", "solid-js": "^1.9.5", @@ -21,7 +21,7 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/basic-solid-query-file-based/package.json b/examples/solid/basic-solid-query-file-based/package.json index a55021a7a82..bdeaaa5fe93 100644 --- a/examples/solid/basic-solid-query-file-based/package.json +++ b/examples/solid/basic-solid-query-file-based/package.json @@ -12,8 +12,8 @@ "dependencies": { "@tanstack/solid-query": "^5.71.9", "@tanstack/solid-query-devtools": "^5.71.9", - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", @@ -22,9 +22,9 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/basic-solid-query/package.json b/examples/solid/basic-solid-query/package.json index 9b35a016ef8..b74e17e2610 100644 --- a/examples/solid/basic-solid-query/package.json +++ b/examples/solid/basic-solid-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/solid-query": "^5.71.9", "@tanstack/solid-query-devtools": "^5.71.9", - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", @@ -20,9 +20,9 @@ "tailwindcss": "^3.4.17" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/basic-ssr-streaming-file-based/package.json b/examples/solid/basic-ssr-streaming-file-based/package.json index 4779fa0e5d4..137b5e154f7 100644 --- a/examples/solid/basic-ssr-streaming-file-based/package.json +++ b/examples/solid/basic-ssr-streaming-file-based/package.json @@ -11,13 +11,12 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "autoprefixer": "^10.4.20", "compression": "^1.7.5", "express": "^4.21.2", "get-port": "^7.1.0", - "isbot": "^5.1.22", "node-fetch": "^3.3.2", "postcss": "^8.5.1", "redaxios": "^0.5.1", @@ -26,10 +25,10 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "@types/express": "^4.17.21", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json index c4dc8a03d08..4bb30a521c4 100644 --- a/examples/solid/basic/package.json +++ b/examples/solid/basic/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "redaxios": "^0.5.1", "postcss": "^8.5.1", "solid-js": "^1.9.5", @@ -21,7 +21,7 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/kitchen-sink-file-based/package.json b/examples/solid/kitchen-sink-file-based/package.json index 5c501c47baa..639dbbb3ffd 100644 --- a/examples/solid/kitchen-sink-file-based/package.json +++ b/examples/solid/kitchen-sink-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "immer": "^10.1.1", "solid-js": "^1.9.5", "redaxios": "^0.5.1", @@ -20,9 +20,9 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/quickstart-file-based/package.json b/examples/solid/quickstart-file-based/package.json index 03dd7c3ed55..2f8a95e5c1c 100644 --- a/examples/solid/quickstart-file-based/package.json +++ b/examples/solid/quickstart-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "redaxios": "^0.5.1", @@ -19,9 +19,9 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.131.50", + "@tanstack/router-plugin": "^1.132.0-alpha.25", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.2" + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/start-bare/package.json b/examples/solid/start-bare/package.json index 5da0b011ac3..4f1be3f422b 100644 --- a/examples/solid/start-bare/package.json +++ b/examples/solid/start-bare/package.json @@ -9,9 +9,9 @@ "start": "vite start" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", - "@tanstack/solid-start": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", + "@tanstack/solid-start": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", @@ -19,10 +19,10 @@ }, "devDependencies": { "@types/node": "^22.10.2", - "vite-plugin-solid": "^2.11.2", + "vite-plugin-solid": "^2.11.8", "combinate": "^1.1.11", "typescript": "^5.7.2", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/solid/start-bare/src/routeTree.gen.ts b/examples/solid/start-bare/src/routeTree.gen.ts index 333a815c38c..85bb67e7758 100644 --- a/examples/solid/start-bare/src/routeTree.gen.ts +++ b/examples/solid/start-bare/src/routeTree.gen.ts @@ -75,3 +75,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/solid/start-bare/src/router.tsx b/examples/solid/start-bare/src/router.tsx index a8d9cb45e7a..84312aad0d3 100644 --- a/examples/solid/start-bare/src/router.tsx +++ b/examples/solid/start-bare/src/router.tsx @@ -1,8 +1,8 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: (err) =>

    {err.error.stack}

    , @@ -12,9 +12,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/solid/start-bare/vite.config.ts b/examples/solid/start-bare/vite.config.ts index bae1bfaad6e..1a2219f4435 100644 --- a/examples/solid/start-bare/vite.config.ts +++ b/examples/solid/start-bare/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -11,5 +12,6 @@ export default defineConfig({ projects: ['./tsconfig.json'], }), tanstackStart(), + viteSolid({ ssr: true }), ], }) diff --git a/examples/solid/start-basic-static/package.json b/examples/solid/start-basic-static/package.json index 7188a4a3b58..a47de185541 100644 --- a/examples/solid/start-basic-static/package.json +++ b/examples/solid/start-basic-static/package.json @@ -9,9 +9,10 @@ "start": "vite start" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", - "@tanstack/solid-start": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", + "@tanstack/solid-start": "^1.132.0-alpha.25", + "@tanstack/start-static-server-functions": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "tailwind-merge": "^2.5.5" @@ -22,7 +23,8 @@ "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "typescript": "^5.6.2", - "vite": "^6.3.5", - "vite-tsconfig-paths": "^5.1.3" + "vite": "^7.1.1", + "vite-tsconfig-paths": "^5.1.3", + "vite-plugin-solid": "^2.11.8" } } diff --git a/examples/solid/start-basic-static/src/routeTree.gen.ts b/examples/solid/start-basic-static/src/routeTree.gen.ts index f2b1e5cfb4d..2c6990916d1 100644 --- a/examples/solid/start-basic-static/src/routeTree.gen.ts +++ b/examples/solid/start-basic-static/src/routeTree.gen.ts @@ -363,3 +363,11 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } +} diff --git a/examples/solid/start-basic-static/src/router.tsx b/examples/solid/start-basic-static/src/router.tsx index c45bed4758c..5da353c1ce2 100644 --- a/examples/solid/start-basic-static/src/router.tsx +++ b/examples/solid/start-basic-static/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/solid/start-basic-static/src/routes/__root.tsx b/examples/solid/start-basic-static/src/routes/__root.tsx index c439d9dfcda..6d60d836c7f 100644 --- a/examples/solid/start-basic-static/src/routes/__root.tsx +++ b/examples/solid/start-basic-static/src/routes/__root.tsx @@ -73,68 +73,63 @@ function RootComponent() { function RootLayout({ children }: { children: Solid.JSX.Element }) { return ( - - - - - -
    - - Home - {' '} - - Posts - {' '} - - Users - {' '} - - Pathless Layout - {' '} - - Deferred - {' '} - - This Route Does Not Exist - -
    -
    - {children} - - - - + <> +
    + + Home + {' '} + + Posts + {' '} + + Users + {' '} + + Pathless Layout + {' '} + + Deferred + {' '} + + This Route Does Not Exist + +
    +
    + {children} + + + ) } diff --git a/examples/solid/start-basic-static/src/routes/deferred.tsx b/examples/solid/start-basic-static/src/routes/deferred.tsx index ce617191eb7..11a5c62f8c2 100644 --- a/examples/solid/start-basic-static/src/routes/deferred.tsx +++ b/examples/solid/start-basic-static/src/routes/deferred.tsx @@ -1,15 +1,18 @@ import { Await, createFileRoute } from '@tanstack/solid-router' import { createServerFn } from '@tanstack/solid-start' import { Suspense, createSignal } from 'solid-js' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const personServerFn = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const personServerFn = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(({ data: name }) => { return { name, randomNumber: Math.floor(Math.random() * 100) } }) -const slowServerFn = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const slowServerFn = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data: name }) => { await new Promise((r) => setTimeout(r, 1000)) return { name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/examples/solid/start-basic-static/src/routes/posts.$postId.tsx b/examples/solid/start-basic-static/src/routes/posts.$postId.tsx index 73bbe725538..df0b03af9e6 100644 --- a/examples/solid/start-basic-static/src/routes/posts.$postId.tsx +++ b/examples/solid/start-basic-static/src/routes/posts.$postId.tsx @@ -12,7 +12,7 @@ export const Route = createFileRoute('/posts/$postId')({ }, }) -export function PostErrorComponent({ error }: ErrorComponentProps) { +function PostErrorComponent({ error }: ErrorComponentProps) { return } @@ -21,12 +21,12 @@ function PostComponent() { return (
    -

    {post.title}

    -
    {post.body}
    +

    {post().title}

    +
    {post().body}
    fetchPost({ data: postId, }), - errorComponent: PostErrorComponent, component: PostDeepComponent, }) diff --git a/examples/solid/start-basic-static/src/routes/users.$userId.tsx b/examples/solid/start-basic-static/src/routes/users.$userId.tsx index 928077c240b..1b0a2e84b85 100644 --- a/examples/solid/start-basic-static/src/routes/users.$userId.tsx +++ b/examples/solid/start-basic-static/src/routes/users.$userId.tsx @@ -4,9 +4,11 @@ import { createServerFn } from '@tanstack/solid-start' import type { ErrorComponentProps } from '@tanstack/solid-router' import type { User } from '~/utils/users' import { NotFound } from '~/components/NotFound' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const fetchUser = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +const fetchUser = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data: userId }) => { return axios .get('https://jsonplaceholder.typicode.com/users/' + userId) @@ -29,7 +31,7 @@ export const Route = createFileRoute('/users/$userId')({ }, }) -export function UserErrorComponent({ error }: ErrorComponentProps) { +function UserErrorComponent({ error }: ErrorComponentProps) { return } diff --git a/examples/solid/start-basic-static/src/routes/users.tsx b/examples/solid/start-basic-static/src/routes/users.tsx index 1b5e9e23a2f..19a36135ac8 100644 --- a/examples/solid/start-basic-static/src/routes/users.tsx +++ b/examples/solid/start-basic-static/src/routes/users.tsx @@ -2,9 +2,11 @@ import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' import axios from 'redaxios' import { createServerFn } from '@tanstack/solid-start' import type { User } from '../utils/users' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' -const fetchUsers = createServerFn({ method: 'GET', type: 'static' }).handler( - async () => { +const fetchUsers = createServerFn({ method: 'GET' }) + .middleware([staticFunctionMiddleware]) + .handler(async () => { console.info('Fetching users...') const res = await axios.get>( 'https://jsonplaceholder.typicode.com/users', @@ -13,8 +15,7 @@ const fetchUsers = createServerFn({ method: 'GET', type: 'static' }).handler( return res.data .slice(0, 10) .map((u) => ({ id: u.id, name: u.name, email: u.email })) - }, -) + }) export const Route = createFileRoute('/users')({ loader: async () => fetchUsers(), diff --git a/examples/solid/start-basic-static/src/utils/posts.tsx b/examples/solid/start-basic-static/src/utils/posts.tsx index 204230b7fea..166c1787c88 100644 --- a/examples/solid/start-basic-static/src/utils/posts.tsx +++ b/examples/solid/start-basic-static/src/utils/posts.tsx @@ -2,6 +2,7 @@ import { createServerFn } from '@tanstack/solid-start' import axios from 'redaxios' import { notFound } from '@tanstack/solid-router' import { logMiddleware } from './loggingMiddleware' +import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions' export type PostType = { id: string @@ -9,9 +10,9 @@ export type PostType = { body: string } -export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) - .middleware([logMiddleware]) - .validator((d: string) => d) +export const fetchPost = createServerFn({ method: 'GET' }) + .middleware([logMiddleware, staticFunctionMiddleware]) + .inputValidator((d: string) => d) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios @@ -27,8 +28,8 @@ export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) return post }) -export const fetchPosts = createServerFn({ method: 'GET', type: 'static' }) - .middleware([logMiddleware]) +export const fetchPosts = createServerFn({ method: 'GET' }) + .middleware([logMiddleware, staticFunctionMiddleware]) .handler(async () => { console.info('Fetching posts...') return axios diff --git a/examples/solid/start-basic-static/vite.config.ts b/examples/solid/start-basic-static/vite.config.ts index 213bd9189eb..97fcbafb577 100644 --- a/examples/solid/start-basic-static/vite.config.ts +++ b/examples/solid/start-basic-static/vite.config.ts @@ -1,6 +1,7 @@ import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import { defineConfig } from 'vite' import tsConfigPaths from 'vite-tsconfig-paths' +import viteSolid from 'vite-plugin-solid' export default defineConfig({ server: { @@ -13,7 +14,20 @@ export default defineConfig({ tanstackStart({ spa: { enabled: true, + prerender: { + crawlLinks: true, + }, + }, + sitemap: { + host: 'https://localhost:3000', + }, + prerender: { + failOnError: true, + filter: (page) => { + return !page.path.includes('exist') + }, }, }), + viteSolid({ ssr: true }), ], }) diff --git a/examples/solid/start-basic/package.json b/examples/solid/start-basic/package.json index 6e96c21b769..7576a83fae8 100644 --- a/examples/solid/start-basic/package.json +++ b/examples/solid/start-basic/package.json @@ -9,9 +9,9 @@ "start": "vite start" }, "dependencies": { - "@tanstack/solid-router": "^1.131.50", - "@tanstack/solid-router-devtools": "^1.131.50", - "@tanstack/solid-start": "^1.131.50", + "@tanstack/solid-router": "^1.132.0-alpha.25", + "@tanstack/solid-router-devtools": "^1.132.0-alpha.25", + "@tanstack/solid-start": "^1.132.0-alpha.25", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0" @@ -22,8 +22,8 @@ "postcss": "^8.5.1", "tailwindcss": "^3.4.17", "typescript": "^5.7.2", - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.7", + "vite": "^7.1.1", + "vite-plugin-solid": "^2.11.8", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/solid/start-basic/src/routeTree.gen.ts b/examples/solid/start-basic/src/routeTree.gen.ts index 16b7ef22db7..c9e9fbeceb3 100644 --- a/examples/solid/start-basic/src/routeTree.gen.ts +++ b/examples/solid/start-basic/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createServerRootRoute } from '@tanstack/solid-start/server' - import { Route as rootRouteImport } from './routes/__root' import { Route as UsersRouteImport } from './routes/users' import { Route as RedirectRouteImport } from './routes/redirect' @@ -21,14 +19,12 @@ import { Route as UsersIndexRouteImport } from './routes/users.index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' -import { ServerRoute as ApiUsersServerRouteImport } from './routes/api/users' -import { ServerRoute as ApiUsersUserIdServerRouteImport } from './routes/api/users.$userId' - -const rootServerRouteImport = createServerRootRoute() const UsersRoute = UsersRouteImport.update({ id: '/users', @@ -79,6 +75,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutNestedLayoutRoute = PathlessLayoutNestedLayoutRouteImport.update({ id: '/_nested-layout', @@ -89,6 +90,11 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', @@ -101,16 +107,6 @@ const PathlessLayoutNestedLayoutRouteARoute = path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const ApiUsersServerRoute = ApiUsersServerRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiUsersUserIdServerRoute = ApiUsersUserIdServerRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersServerRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -118,24 +114,28 @@ export interface FileRoutesByFullPath { '/posts': typeof PostsRouteWithChildren '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/deferred': typeof DeferredRoute '/redirect': typeof RedirectRoute + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts': typeof PostsIndexRoute '/users': typeof UsersIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRoutesById { @@ -147,12 +147,14 @@ export interface FileRoutesById { '/redirect': typeof RedirectRoute '/users': typeof UsersRouteWithChildren '/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute '/users/$userId': typeof UsersUserIdRoute '/posts/': typeof PostsIndexRoute '/users/': typeof UsersIndexRoute '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/api/users/$userId': typeof ApiUsersUserIdRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute } export interface FileRouteTypes { @@ -163,24 +165,28 @@ export interface FileRouteTypes { | '/posts' | '/redirect' | '/users' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/route-a' | '/route-b' + | '/api/users/$userId' | '/posts/$postId/deep' fileRoutesByTo: FileRoutesByTo to: | '/' | '/deferred' | '/redirect' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts' | '/users' | '/route-a' | '/route-b' + | '/api/users/$userId' | '/posts/$postId/deep' id: | '__root__' @@ -191,12 +197,14 @@ export interface FileRouteTypes { | '/redirect' | '/users' | '/_pathlessLayout/_nested-layout' + | '/api/users' | '/posts/$postId' | '/users/$userId' | '/posts/' | '/users/' | '/_pathlessLayout/_nested-layout/route-a' | '/_pathlessLayout/_nested-layout/route-b' + | '/api/users/$userId' | '/posts_/$postId/deep' fileRoutesById: FileRoutesById } @@ -207,32 +215,9 @@ export interface RootRouteChildren { PostsRoute: typeof PostsRouteWithChildren RedirectRoute: typeof RedirectRoute UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute } -export interface FileServerRoutesByFullPath { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesByTo { - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/users': typeof ApiUsersServerRouteWithChildren - '/api/users/$userId': typeof ApiUsersUserIdServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/users' | '/api/users/$userId' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/users' | '/api/users/$userId' - id: '__root__' | '/api/users' | '/api/users/$userId' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiUsersServerRoute: typeof ApiUsersServerRouteWithChildren -} declare module '@tanstack/solid-router' { interface FileRoutesByPath { @@ -306,6 +291,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout/_nested-layout': { id: '/_pathlessLayout/_nested-layout' path: '' @@ -320,6 +312,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' path: '/route-b' @@ -336,24 +335,6 @@ declare module '@tanstack/solid-router' { } } } -declare module '@tanstack/solid-start/server' { - interface ServerFileRoutesByPath { - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/users/$userId': { - id: '/api/users/$userId' - path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdServerRouteImport - parentRoute: typeof ApiUsersServerRoute - } - } -} interface PathlessLayoutNestedLayoutRouteChildren { PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute @@ -409,16 +390,16 @@ const UsersRouteChildren: UsersRouteChildren = { const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) -interface ApiUsersServerRouteChildren { - ApiUsersUserIdServerRoute: typeof ApiUsersUserIdServerRoute +interface ApiUsersRouteChildren { + ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute } -const ApiUsersServerRouteChildren: ApiUsersServerRouteChildren = { - ApiUsersUserIdServerRoute: ApiUsersUserIdServerRoute, +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersUserIdRoute: ApiUsersUserIdRoute, } -const ApiUsersServerRouteWithChildren = ApiUsersServerRoute._addFileChildren( - ApiUsersServerRouteChildren, +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, ) const rootRouteChildren: RootRouteChildren = { @@ -428,14 +409,17 @@ const rootRouteChildren: RootRouteChildren = { PostsRoute: PostsRouteWithChildren, RedirectRoute: RedirectRoute, UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, PostsPostIdDeepRoute: PostsPostIdDeepRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiUsersServerRoute: ApiUsersServerRouteWithChildren, + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + router: Awaited> + } } -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/examples/solid/start-basic/src/router.tsx b/examples/solid/start-basic/src/router.tsx index c45bed4758c..5da353c1ce2 100644 --- a/examples/solid/start-basic/src/router.tsx +++ b/examples/solid/start-basic/src/router.tsx @@ -1,10 +1,10 @@ -import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { createRouter } from '@tanstack/solid-router' import { routeTree } from './routeTree.gen' import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' import { NotFound } from './components/NotFound' -export function createRouter() { - const router = createTanStackRouter({ +export function getRouter() { + const router = createRouter({ routeTree, defaultPreload: 'intent', defaultErrorComponent: DefaultCatchBoundary, @@ -14,9 +14,3 @@ export function createRouter() { return router } - -declare module '@tanstack/solid-router' { - interface Register { - router: ReturnType - } -} diff --git a/examples/solid/start-basic/src/routes/api/users.$userId.ts b/examples/solid/start-basic/src/routes/api/users.$userId.ts index cf539e0c0cf..65e5da00fc7 100644 --- a/examples/solid/start-basic/src/routes/api/users.$userId.ts +++ b/examples/solid/start-basic/src/routes/api/users.$userId.ts @@ -1,28 +1,30 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { json } from '@tanstack/solid-start' import type { User } from '~/utils/users' -export const ServerRoute = createServerFileRoute('/api/users/$userId').methods({ - GET: async ({ params, request }) => { - console.info(`Fetching users by id=${params.userId}... @`, request.url) - try { - const res = await fetch( - 'https://jsonplaceholder.typicode.com/users/' + params.userId, - ) - if (!res.ok) { - throw new Error('Failed to fetch user') - } - - const user = (await res.json()) as User - - return json({ - id: user.id, - name: user.name, - email: user.email, - }) - } catch (e) { - console.error(e) - return json({ error: 'User not found' }, { status: 404 }) - } +export const Route = createFileRoute('/api/users/$userId')({ + server: { + handlers: { + GET: async ({ params, request }) => { + console.info(`Fetching users by id=${params.userId}... @`, request.url) + try { + const res = await fetch( + 'https://jsonplaceholder.typicode.com/users/' + params.userId, + ) + if (!res.ok) { + throw new Error('Failed to fetch user') + } + const user = (await res.json()) as User + return json({ + id: user.id, + name: user.name, + email: user.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, }, }) diff --git a/examples/solid/start-basic/src/routes/api/users.ts b/examples/solid/start-basic/src/routes/api/users.ts index ec59c81bc0f..755805280b2 100644 --- a/examples/solid/start-basic/src/routes/api/users.ts +++ b/examples/solid/start-basic/src/routes/api/users.ts @@ -1,8 +1,8 @@ -import { createServerFileRoute } from '@tanstack/solid-start/server' +import { createFileRoute } from '@tanstack/solid-router' import { createMiddleware, json } from '@tanstack/solid-start' import type { User } from '~/utils/users' -const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( +const userLoggerMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: /users') const result = await next() @@ -12,7 +12,7 @@ const userLoggerMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testParentMiddleware = createMiddleware({ type: 'request' }).server( +const testParentMiddleware = createMiddleware().server( async ({ next, request }) => { console.info('In: testParentMiddleware') const result = await next() @@ -22,38 +22,38 @@ const testParentMiddleware = createMiddleware({ type: 'request' }).server( }, ) -const testMiddleware = createMiddleware({ type: 'request' }) +const testMiddleware = createMiddleware() .middleware([testParentMiddleware]) .server(async ({ next, request }) => { console.info('In: testMiddleware') const result = await next() result.response.headers.set('x-test', 'true') - // if (Math.random() > 0.5) { // throw new Response(null, { // status: 302, // headers: { Location: 'https://www.google.com' }, // }) // } - console.info('Out: testMiddleware') return result }) -export const ServerRoute = createServerFileRoute('/api/users') - .middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware]) - .methods({ - GET: async ({ request }) => { - console.info('Fetching users... @', request.url) - const res = await fetch('https://jsonplaceholder.typicode.com/users') - if (!res.ok) { - throw new Error('Failed to fetch users') - } - - const data = (await res.json()) as Array - - const list = data.slice(0, 10) - - return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) +export const Route = createFileRoute('/api/users')({ + server: { + middleware: [testMiddleware, userLoggerMiddleware, testParentMiddleware], + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await fetch('https://jsonplaceholder.typicode.com/users') + if (!res.ok) { + throw new Error('Failed to fetch users') + } + const data = (await res.json()) as Array + const list = data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, }, - }) + }, +}) diff --git a/examples/solid/start-basic/src/routes/deferred.tsx b/examples/solid/start-basic/src/routes/deferred.tsx index 1633369b200..97c018ab033 100644 --- a/examples/solid/start-basic/src/routes/deferred.tsx +++ b/examples/solid/start-basic/src/routes/deferred.tsx @@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/solid-start' import { Suspense, createSignal } from 'solid-js' const personServerFn = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(({ data: name }) => { return { name, randomNumber: Math.floor(Math.random() * 100) } }) const slowServerFn = createServerFn({ method: 'GET' }) - .validator((d: string) => d) + .inputValidator((d: string) => d) .handler(async ({ data: name }) => { await new Promise((r) => setTimeout(r, 1000)) return { name, randomNumber: Math.floor(Math.random() * 100) } diff --git a/examples/solid/start-basic/src/utils/posts.tsx b/examples/solid/start-basic/src/utils/posts.tsx index e29706d38cd..78c7f7d44a7 100644 --- a/examples/solid/start-basic/src/utils/posts.tsx +++ b/examples/solid/start-basic/src/utils/posts.tsx @@ -8,8 +8,8 @@ export type PostType = { body: string } -export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) - .validator((d: string) => d) +export const fetchPost = createServerFn({ method: 'GET' }) + .inputValidator((d: string) => d) .handler(async ({ data }) => { console.info(`Fetching post with id ${data}...`) const post = await axios @@ -28,7 +28,6 @@ export const fetchPost = createServerFn({ method: 'GET', type: 'static' }) export const fetchPosts = createServerFn({ method: 'GET', - type: 'static', }).handler(async () => { console.info('Fetching posts...') return axios diff --git a/examples/solid/start-basic/vite.config.ts b/examples/solid/start-basic/vite.config.ts index 93d987e62af..1a2219f4435 100644 --- a/examples/solid/start-basic/vite.config.ts +++ b/examples/solid/start-basic/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ tsConfigPaths({ projects: ['./tsconfig.json'], }), - tanstackStart({ customViteSolidPlugin: true }), + tanstackStart(), viteSolid({ ssr: true }), ], }) diff --git a/labeler-config.yml b/labeler-config.yml index 015ae4ec3af..3fd030023c3 100644 --- a/labeler-config.yml +++ b/labeler-config.yml @@ -10,6 +10,9 @@ 'package: history': - changed-files: - any-glob-to-any-file: 'packages/history/**/*' +'package: nitro-v2-vite-plugin': + - changed-files: + - any-glob-to-any-file: 'packages/nitro-v2-vite-plugin/**/*' 'package: react-router': - changed-files: - any-glob-to-any-file: 'packages/react-router/**/*' @@ -25,9 +28,6 @@ 'package: react-start-client': - changed-files: - any-glob-to-any-file: 'packages/react-start-client/**/*' -'package: react-start-plugin': - - changed-files: - - any-glob-to-any-file: 'packages/react-start-plugin/**/*' 'package: react-start-server': - changed-files: - any-glob-to-any-file: 'packages/react-start-server/**/*' @@ -73,9 +73,6 @@ 'package: solid-start-client': - changed-files: - any-glob-to-any-file: 'packages/solid-start-client/**/*' -'package: solid-start-plugin': - - changed-files: - - any-glob-to-any-file: 'packages/solid-start-plugin/**/*' 'package: solid-start-server': - changed-files: - any-glob-to-any-file: 'packages/solid-start-server/**/*' @@ -88,15 +85,9 @@ 'package: start-server-core': - changed-files: - any-glob-to-any-file: 'packages/start-server-core/**/*' -'package: start-server-functions-client': - - changed-files: - - any-glob-to-any-file: 'packages/start-server-functions-client/**/*' -'package: start-server-functions-fetcher': - - changed-files: - - any-glob-to-any-file: 'packages/start-server-functions-fetcher/**/*' -'package: start-server-functions-server': +'package: start-static-server-functions': - changed-files: - - any-glob-to-any-file: 'packages/start-server-functions-server/**/*' + - any-glob-to-any-file: 'packages/start-static-server-functions/**/*' 'package: start-storage-context': - changed-files: - any-glob-to-any-file: 'packages/start-storage-context/**/*' diff --git a/package.json b/package.json index f4d91cf9ba1..a6af63a493c 100644 --- a/package.json +++ b/package.json @@ -61,13 +61,13 @@ "rimraf": "^6.0.1", "tinyglobby": "^0.2.12", "typescript": "^5.9.0", - "vite": "6.3.5", - "vitest": "^3.0.6", "typescript54": "npm:typescript@5.4", "typescript55": "npm:typescript@5.5", "typescript56": "npm:typescript@5.6", "typescript57": "npm:typescript@5.7", - "typescript58": "npm:typescript@5.8" + "typescript58": "npm:typescript@5.8", + "vite": "^7.1.1", + "vitest": "^3.2.4" }, "pnpm": { "overrides": { @@ -99,15 +99,10 @@ "@tanstack/arktype-adapter": "workspace:*", "@tanstack/react-start": "workspace:*", "@tanstack/react-start-client": "workspace:*", - "@tanstack/react-start-plugin": "workspace:*", "@tanstack/react-start-server": "workspace:*", "@tanstack/solid-start": "workspace:*", "@tanstack/solid-start-client": "workspace:*", - "@tanstack/solid-start-plugin": "workspace:*", "@tanstack/solid-start-server": "workspace:*", - "@tanstack/start-server-functions-fetcher": "workspace:*", - "@tanstack/start-server-functions-client": "workspace:*", - "@tanstack/start-server-functions-server": "workspace:*", "@tanstack/start-plugin-core": "workspace:*", "@tanstack/start-client-core": "workspace:*", "@tanstack/start-server-core": "workspace:*", @@ -115,7 +110,9 @@ "@tanstack/eslint-plugin-router": "workspace:*", "@tanstack/server-functions-plugin": "workspace:*", "@tanstack/directive-functions-plugin": "workspace:*", - "@tanstack/router-utils": "workspace:*" + "@tanstack/router-utils": "workspace:*", + "@tanstack/start-static-server-functions": "workspace:*", + "@tanstack/nitro-v2-vite-plugin": "workspace:*" } } } diff --git a/packages/arktype-adapter/package.json b/packages/arktype-adapter/package.json index 3b09de8d65c..77b03c5bb95 100644 --- a/packages/arktype-adapter/package.json +++ b/packages/arktype-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/arktype-adapter", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/directive-functions-plugin/package.json b/packages/directive-functions-plugin/package.json index 62c5b30da84..d98148407cb 100644 --- a/packages/directive-functions-plugin/package.json +++ b/packages/directive-functions-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/directive-functions-plugin", - "version": "1.131.2", + "version": "1.132.0-alpha.9", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -77,9 +77,10 @@ "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", "@types/babel__traverse": "^7.20.7", - "vite": "^6.0.0" + "dedent": "^1.6.0", + "vite": "^7.1.1" }, "peerDependencies": { - "vite": ">=6.0.0" + "vite": ">=6.0.0 || >=7.0.0" } } diff --git a/packages/directive-functions-plugin/tests/compiler.test.ts b/packages/directive-functions-plugin/tests/compiler.test.ts index e405a7411e7..a943f7f0803 100644 --- a/packages/directive-functions-plugin/tests/compiler.test.ts +++ b/packages/directive-functions-plugin/tests/compiler.test.ts @@ -825,12 +825,14 @@ describe('server function compilation', () => { expect(client.compiledResult.code).toMatchInlineSnapshot(` "import { createClientRpc } from "my-rpc-lib-client"; const generator_1 = createClientRpc("test_ts--generator_1"); - const generator = generator_1;"`) + const generator = generator_1;" + `) expect(ssr.compiledResult.code).toMatchInlineSnapshot(` "import { createSsrRpc } from "my-rpc-lib-server"; const generator_1 = createSsrRpc("test_ts--generator_1"); - const generator = generator_1;"`) + const generator = generator_1;" + `) expect(server.compiledResult.code).toMatchInlineSnapshot(` "import { createServerRpc } from "my-rpc-lib-server"; @@ -839,7 +841,8 @@ describe('server function compilation', () => { return 'hello world'; }); const generator = generator_1; - export { generator_1 };"`) + export { generator_1 };" + `) }) test('async generator function', () => { const code = ` @@ -864,12 +867,14 @@ describe('server function compilation', () => { expect(client.compiledResult.code).toMatchInlineSnapshot(` "import { createClientRpc } from "my-rpc-lib-client"; const asyncGenerator_1 = createClientRpc("test_ts--asyncGenerator_1"); - const asyncGenerator = asyncGenerator_1;"`) + const asyncGenerator = asyncGenerator_1;" + `) expect(ssr.compiledResult.code).toMatchInlineSnapshot(` "import { createSsrRpc } from "my-rpc-lib-server"; const asyncGenerator_1 = createSsrRpc("test_ts--asyncGenerator_1"); - const asyncGenerator = asyncGenerator_1;"`) + const asyncGenerator = asyncGenerator_1;" + `) expect(server.compiledResult.code).toMatchInlineSnapshot(` "import { createServerRpc } from "my-rpc-lib-server"; @@ -878,6 +883,7 @@ describe('server function compilation', () => { return 'hello world'; }); const asyncGenerator = asyncGenerator_1; - export { asyncGenerator_1 };"`) + export { asyncGenerator_1 };" + `) }) }) diff --git a/packages/eslint-plugin-router/package.json b/packages/eslint-plugin-router/package.json index f52e05f35fb..c28a6273304 100644 --- a/packages/eslint-plugin-router/package.json +++ b/packages/eslint-plugin-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/eslint-plugin-router", - "version": "1.131.2", + "version": "1.132.0-alpha.1", "description": "ESLint plugin for TanStack Router", "author": "Manuel Schiller", "license": "MIT", diff --git a/packages/history/package.json b/packages/history/package.json index 022d1a175df..f6a63175a93 100644 --- a/packages/history/package.json +++ b/packages/history/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/history", - "version": "1.131.2", + "version": "1.132.0-alpha.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/nitro-v2-vite-plugin/README.md b/packages/nitro-v2-vite-plugin/README.md new file mode 100644 index 00000000000..5882e611206 --- /dev/null +++ b/packages/nitro-v2-vite-plugin/README.md @@ -0,0 +1 @@ +# Experimental Nitro v2 Vite Plugin diff --git a/packages/react-start-plugin/eslint.config.js b/packages/nitro-v2-vite-plugin/eslint.config.js similarity index 100% rename from packages/react-start-plugin/eslint.config.js rename to packages/nitro-v2-vite-plugin/eslint.config.js diff --git a/packages/react-start-plugin/package.json b/packages/nitro-v2-vite-plugin/package.json similarity index 76% rename from packages/react-start-plugin/package.json rename to packages/nitro-v2-vite-plugin/package.json index dbbb29ac544..8a0e66c4248 100644 --- a/packages/react-start-plugin/package.json +++ b/packages/nitro-v2-vite-plugin/package.json @@ -1,15 +1,15 @@ { - "name": "@tanstack/react-start-plugin", - "version": "1.131.50", + "name": "@tanstack/nitro-v2-vite-plugin", + "version": "1.132.0-alpha.23", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/TanStack/router.git", - "directory": "packages/react-start-plugin" + "directory": "packages/nitro-v2-vite-plugin" }, - "homepage": "https://tanstack.com/start", + "homepage": "https://tanstack.com/router", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -25,9 +25,8 @@ ], "scripts": { "clean": "rimraf ./dist && rimraf ./coverage", - "clean:snapshots": "rimraf **/*snapshot* --glob", "test": "pnpm test:eslint && pnpm test:types && pnpm test:build && pnpm test:unit", - "test:unit": "exit 0; vitest", + "test:unit": "echo 'No unit tests are needed here since we do them in @tanstack/router-plugin!'", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js", @@ -41,6 +40,7 @@ }, "type": "module", "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", "module": "dist/esm/index.js", "exports": { ".": { @@ -57,19 +57,13 @@ "src" ], "engines": { - "node": ">=12" + "node": ">=22.12" }, "dependencies": { - "@tanstack/start-plugin-core": "workspace:*", - "pathe": "^2.0.3", - "zod": "^3.24.2" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.3.4", - "vite": "^6.0.0" + "nitropack": "^2.12.6", + "pathe": "^2.0.3" }, "peerDependencies": { - "@vitejs/plugin-react": ">=4.3.4", - "vite": ">=6.0.0" + "vite": ">=7.0.0" } } diff --git a/packages/nitro-v2-vite-plugin/src/index.ts b/packages/nitro-v2-vite-plugin/src/index.ts new file mode 100644 index 00000000000..541fb868e54 --- /dev/null +++ b/packages/nitro-v2-vite-plugin/src/index.ts @@ -0,0 +1,174 @@ +import { build, copyPublicAssets, createNitro, prepare } from 'nitropack' +import { dirname, resolve } from 'pathe' + +import type { PluginOption, Rollup } from 'vite' +import type { NitroConfig } from 'nitropack' + +let ssrBundle: Rollup.OutputBundle +let ssrEntryFile: string + +export function nitroV2Plugin(nitroConfig?: NitroConfig): Array { + return [ + { + name: 'tanstack-nitro-v2-vite-plugin', + + generateBundle: { + handler(_options, bundle) { + if (this.environment.name !== 'ssr') { + return + } + + // find entry point of ssr bundle + let entryFile: string | undefined + for (const [_name, file] of Object.entries(bundle)) { + if (file.type === 'chunk') { + if (file.isEntry) { + if (entryFile !== undefined) { + this.error( + `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.`, + ) + } + entryFile = file.fileName + } + } + } + if (entryFile === undefined) { + this.error( + `No entry point found for service "${this.environment.name}".`, + ) + } + ssrEntryFile = entryFile! + ssrBundle = bundle + }, + }, + + async config(_, env) { + if (env.command !== 'build') { + return + } + + return { + environments: { + ssr: { + consumer: 'server', + build: { + ssr: true, + // we don't write to the file system as the below 'capture-output' plugin will + // capture the output and write it to the virtual file system + write: false, + copyPublicDir: false, + commonjsOptions: { + include: [/node_modules/], + }, + }, + }, + }, + builder: { + sharedPlugins: true, + async buildApp(builder) { + const client = builder.environments.client + const server = builder.environments.ssr + + if (!client) { + throw new Error('Client environment not found') + } + + if (!server) { + throw new Error('SSR environment not found') + } + + await builder.build(client) + await builder.build(server) + + const virtualEntry = '#tanstack/start/entry' + const config: NitroConfig = { + ...nitroConfig, + publicAssets: [ + { + dir: client.config.build.outDir, + baseURL: '/', + maxAge: 31536000, // 1 year + }, + ], + renderer: virtualEntry, + rollupConfig: { + ...nitroConfig?.rollupConfig, + plugins: [virtualBundlePlugin(ssrBundle) as any], + }, + virtual: { + ...nitroConfig?.virtual, + [virtualEntry]: `import { fromWebHandler } from 'h3' + import handler from '${ssrEntryFile}' + export default fromWebHandler(handler.fetch)`, + }, + } + + const nitro = await createNitro(config) + + await prepare(nitro) + await copyPublicAssets(nitro) + await build(nitro) + + await nitro.close() + }, + }, + } + }, + }, + ] +} + +function virtualBundlePlugin(bundle: Rollup.OutputBundle): Rollup.Plugin { + type VirtualModule = { code: string; map: string | null } + let _modules: Map | null = null + + // lazy initialize _modules since at the time of plugin creation, the bundles are not available yet + const getModules = () => { + if (_modules) { + return _modules + } + _modules = new Map() + // group chunks and source maps + for (const [fileName, content] of Object.entries(bundle)) { + if (content.type === 'chunk') { + const virtualModule: VirtualModule = { + code: content.code, + map: null, + } + const maybeMap = bundle[`${fileName}.map`] + if (maybeMap && maybeMap.type === 'asset') { + virtualModule.map = maybeMap.source as string + } + _modules.set(fileName, virtualModule) + _modules.set(resolve(fileName), virtualModule) + } + } + return _modules + } + + return { + name: 'virtual-bundle', + resolveId(id, importer) { + const modules = getModules() + if (modules.has(id)) { + return resolve(id) + } + + if (importer) { + const resolved = resolve(dirname(importer), id) + if (modules.has(resolved)) { + return resolved + } + } + return null + }, + load(id) { + const modules = getModules() + const m = modules.get(id) + if (!m) { + return null + } + return m + }, + } +} diff --git a/packages/nitro-v2-vite-plugin/tsconfig.json b/packages/nitro-v2-vite-plugin/tsconfig.json new file mode 100644 index 00000000000..bd398a87282 --- /dev/null +++ b/packages/nitro-v2-vite-plugin/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "vite.config.ts", "tests"], + "exclude": ["tests/test-files/**", "tests/snapshots/**"], + "compilerOptions": { + "jsx": "react-jsx" + } +} diff --git a/packages/solid-start-plugin/vite.config.ts b/packages/nitro-v2-vite-plugin/vite.config.ts similarity index 53% rename from packages/solid-start-plugin/vite.config.ts rename to packages/nitro-v2-vite-plugin/vite.config.ts index 2c711fd1810..75bb7599983 100644 --- a/packages/solid-start-plugin/vite.config.ts +++ b/packages/nitro-v2-vite-plugin/vite.config.ts @@ -1,21 +1,13 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackViteConfig } from '@tanstack/config/vite' -import packageJson from './package.json' -const config = defineConfig({ - test: { - name: packageJson.name, - dir: './tests', - watch: false, - typecheck: { enabled: true }, - }, -}) +const config = defineConfig({}) export default mergeConfig( config, tanstackViteConfig({ entry: './src/index.ts', srcDir: './src', - outDir: './dist', + exclude: ['./src/tests/'], }), ) diff --git a/packages/react-router-devtools/package.json b/packages/react-router-devtools/package.json index a122352cc97..20d4e45ad8b 100644 --- a/packages/react-router-devtools/package.json +++ b/packages/react-router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-devtools", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -62,7 +62,8 @@ "node": ">=12" }, "dependencies": { - "@tanstack/router-devtools-core": "workspace:*" + "@tanstack/router-devtools-core": "workspace:*", + "vite": "^7.1.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", diff --git a/packages/react-router-ssr-query/package.json b/packages/react-router-ssr-query/package.json index 337267b35bc..e88af14fd0d 100644 --- a/packages/react-router-ssr-query/package.json +++ b/packages/react-router-ssr-query/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-ssr-query", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 570407905c5..dc324cdc089 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router/src/ScriptOnce.tsx b/packages/react-router/src/ScriptOnce.tsx index 5cae90d3c7d..abf5d6bf92b 100644 --- a/packages/react-router/src/ScriptOnce.tsx +++ b/packages/react-router/src/ScriptOnce.tsx @@ -1,19 +1,17 @@ -export function ScriptOnce({ - children, -}: { - children: string - log?: boolean - sync?: boolean -}) { - if (typeof document !== 'undefined') { +import { useRouter } from './useRouter' + +export function ScriptOnce({ children }: { children: string }) { + const router = useRouter() + if (!router.isServer) { return null } return ( ` + return `` }) }, dehydrate: async () => { @@ -106,12 +108,21 @@ export function attachRouterServerSsrUtils( _dehydrated = true const p = createControlledPromise() + const trackPlugins = { didRun: false } + const plugins = + ( + router.options.serializationAdapters as + | Array + | undefined + )?.map((t) => makeSsrSerovalPlugin(t, trackPlugins)) ?? [] crossSerializeStream(dehydratedRouter, { - refs: serializationRefs, - // TODO make plugins configurable - plugins: [ReadableStreamPlugin, ShallowErrorPlugin], + refs: new Map(), + plugins: [...plugins, ...defaultSerovalPlugins], onSerialize: (data, initial) => { - const serialized = initial ? `${GLOBAL_TSR}["router"]=` + data : data + let serialized = initial ? GLOBAL_TSR + '.router=' + data : data + if (trackPlugins.didRun) { + serialized = GLOBAL_TSR + '.p(()=>' + serialized + ')' + } router.serverSsr!.injectScript(() => serialized) }, scopeId: SCOPE_ID, diff --git a/packages/router-core/src/ssr/tsrScript.ts b/packages/router-core/src/ssr/tsrScript.ts index bd8c2a0e0fa..c399733fd82 100644 --- a/packages/router-core/src/ssr/tsrScript.ts +++ b/packages/router-core/src/ssr/tsrScript.ts @@ -1,7 +1,11 @@ self.$_TSR = { - c: () => { + c() { document.querySelectorAll('.\\$tsr').forEach((o) => { o.remove() }) }, + p(script) { + !this.initialized ? this.buffer.push(script) : script() + }, + buffer: [], } diff --git a/packages/router-core/src/utils.ts b/packages/router-core/src/utils.ts index fae3abd183c..df54c8905ca 100644 --- a/packages/router-core/src/utils.ts +++ b/packages/router-core/src/utils.ts @@ -433,47 +433,6 @@ export function createControlledPromise(onResolve?: (value: T) => void) { return controlledPromise } -/** - * - * @deprecated use `jsesc` instead - */ -export function escapeJSON(jsonString: string) { - return jsonString - .replace(/\\/g, '\\\\') // Escape backslashes - .replace(/'/g, "\\'") // Escape single quotes - .replace(/"/g, '\\"') // Escape double quotes -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } - - for (const item of keysA) { - if ( - !hasOwn.call(objB, item) || - !Object.is(objA[item as keyof T], objB[item as keyof T]) - ) { - return false - } - } - return true -} - export function isModuleNotFoundError(error: any): boolean { // chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split" // firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split" diff --git a/packages/router-core/tests/load.test.ts b/packages/router-core/tests/load.test.ts index 5633c64abb9..17f4743687d 100644 --- a/packages/router-core/tests/load.test.ts +++ b/packages/router-core/tests/load.test.ts @@ -7,9 +7,9 @@ import { notFound, redirect, } from '../src' -import type { RouteOptions } from '../src' +import type { RootRouteOptions } from '../src' -type AnyRouteOptions = RouteOptions +type AnyRouteOptions = RootRouteOptions type BeforeLoad = NonNullable type Loader = NonNullable @@ -461,5 +461,5 @@ test('exec on stay (beforeLoad & loader)', async () => { }) function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)) } diff --git a/packages/router-core/tests/match-by-path.test.ts b/packages/router-core/tests/match-by-path.test.ts index 4de3b8d6c76..024db05a247 100644 --- a/packages/router-core/tests/match-by-path.test.ts +++ b/packages/router-core/tests/match-by-path.test.ts @@ -3,20 +3,20 @@ import { matchByPath } from '../src' describe('default path matching', () => { it.each([ - ['', '', '', {}], - ['', '/', '', {}], - ['', '', '/', {}], - ['', '/', '/', {}], - ['/', '/', '/', {}], - ['/', '/a', '/a', {}], - ['/', '/a/b', '/a/b', {}], - ['/', '/a', '/a/', {}], - ['/', '/a/', '/a/', {}], - ['/', '/a/', '/a', undefined], - ['/', '/b', '/a', undefined], - ])('static %s %s => %s', (base, from, to, result) => { + ['', '', {}], + ['/', '', {}], + ['', '/', {}], + ['/', '/', {}], + ['/', '/', {}], + ['/a', '/a', {}], + ['/a/b', '/a/b', {}], + ['/a', '/a/', {}], + ['/a/', '/a/', {}], + ['/a/', '/a', undefined], + ['/b', '/a', undefined], + ])('static %s %s => %s', (from, to, result) => { expect( - matchByPath(base, from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -28,7 +28,7 @@ describe('default path matching', () => { ['/a/1/b/2', '/a/$id/b/$id', { id: '2' }], ])('params %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -36,14 +36,13 @@ describe('default path matching', () => { // in the value: basically everything except / and % expect( matchByPath( - '/', '/a/@&é"\'(§è!çà)-_°^¨$*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{', { to: '/a/$id' }, ), ).toEqual({ id: '@&é"\'(§è!çà)-_°^¨$*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{' }) // in the key: basically everything except / and % and $ expect( - matchByPath('/', '/a/1', { + matchByPath('/a/1', { to: '/a/$@&é"\'(§è!çà)-_°^¨*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{', }), ).toEqual({ '@&é"\'(§è!çà)-_°^¨*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{': '1' }) @@ -61,7 +60,7 @@ describe('default path matching', () => { ['/a/1/b/2', '/a/{-$id}/b/{-$id}', { id: '2' }], ])('optional %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -72,7 +71,7 @@ describe('default path matching', () => { ['/a/b/c', '/a/$/foo', { _splat: 'b/c', '*': 'b/c' }], ])('wildcard %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) }) @@ -92,7 +91,7 @@ describe('case insensitive path matching', () => { ['/', '/b', '/A', undefined], ])('static %s %s => %s', (base, from, to, result) => { expect( - matchByPath(base, from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -103,7 +102,7 @@ describe('case insensitive path matching', () => { ['/a/1/b/2', '/A/$id/B/$id', { id: '2' }], ])('params %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -121,7 +120,7 @@ describe('case insensitive path matching', () => { ['/a/1/b/2_', '/A/{-$id}/B/{-$id}', { id: '2_' }], ])('optional %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -132,7 +131,7 @@ describe('case insensitive path matching', () => { ['/a/b/c', '/A/$/foo', { _splat: 'b/c', '*': 'b/c' }], ])('wildcard %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) }) @@ -156,9 +155,9 @@ describe('fuzzy path matching', () => { ['/', '/b', '/a', undefined], ['/', '/a', '/b', undefined], ])('static %s %s => %s', (base, from, to, result) => { - expect( - matchByPath(base, from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) + expect(matchByPath(from, { to, fuzzy: true, caseSensitive: true })).toEqual( + result, + ) }) it.each([ @@ -168,9 +167,9 @@ describe('fuzzy path matching', () => { ['/a/1/b/2', '/a/$id/b/$other', { id: '1', other: '2' }], ['/a/1/b/2/c', '/a/$id/b/$other', { id: '1', other: '2', '**': 'c' }], ])('params %s => %s', (from, to, result) => { - expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) + expect(matchByPath(from, { to, fuzzy: true, caseSensitive: true })).toEqual( + result, + ) }) it.each([ @@ -184,9 +183,9 @@ describe('fuzzy path matching', () => { ['/a/b/2/d', '/a/{-$id}/b/{-$other}', { other: '2', '**': 'd' }], ['/a/1/b/2/c', '/a/{-$id}/b/{-$other}', { id: '1', other: '2', '**': 'c' }], ])('optional %s => %s', (from, to, result) => { - expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) + expect(matchByPath(from, { to, fuzzy: true, caseSensitive: true })).toEqual( + result, + ) }) it.each([ @@ -195,24 +194,24 @@ describe('fuzzy path matching', () => { ['/a', '/a/$', { _splat: '', '*': '' }], ['/a/b/c/d', '/a/$/foo', { _splat: 'b/c/d', '*': 'b/c/d' }], ])('wildcard %s => %s', (from, to, result) => { - expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) + expect(matchByPath(from, { to, fuzzy: true, caseSensitive: true })).toEqual( + result, + ) }) }) describe('non-nested paths', () => { describe('default path matching', () => { it.each([ - ['/', '/a', '/a_', {}], - ['/', '/a/b', '/a_/b_', {}], - ['/', '/a', '/a_/', {}], - ['/', '/a/', '/a_/', {}], - ['/', '/a/', '/a_', undefined], - ['/', '/b', '/a_', undefined], - ])('static %s %s => %s', (base, from, to, result) => { + ['/a', '/a_', {}], + ['/a/b', '/a_/b_', {}], + ['/a', '/a_/', {}], + ['/a/', '/a_/', {}], + ['/a/', '/a_', undefined], + ['/b', '/a_', undefined], + ])('static %s %s => %s', (from, to, result) => { expect( - matchByPath(base, from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -224,7 +223,7 @@ describe('non-nested paths', () => { ['/a/1/b/2', '/a_/$id_/b_/$id_', { id: '2' }], ])('params %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -232,7 +231,6 @@ describe('non-nested paths', () => { // in the value: basically everything except / and % expect( matchByPath( - '/', '/a/@&é"\'(§è!çà)-_°^¨$*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{', { to: '/a_/$id_' }, ), @@ -241,7 +239,7 @@ describe('non-nested paths', () => { }) // in the key: basically everything except / and % and $ expect( - matchByPath('/', '/a/1', { + matchByPath('/a/1', { to: '/a_/$@&é"\'(§è!çà)-_°^¨*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{_', }), ).toEqual({ @@ -261,7 +259,7 @@ describe('non-nested paths', () => { ['/a/1/b/2', '/a_/{-$id}_/b_/{-$id}_', { id: '2' }], ])('optional %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) @@ -272,22 +270,22 @@ describe('non-nested paths', () => { ['/a/b/c', '/a_/$_/foo_', { _splat: 'b/c', '*': 'b/c' }], ])('wildcard %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: true, fuzzy: false }), + matchByPath(from, { to, caseSensitive: true, fuzzy: false }), ).toEqual(result) }) }) describe('case insensitive path matching', () => { it.each([ - ['/', '/a', '/A_', {}], - ['/', '/a/b', '/A_/B_', {}], - ['/', '/a', '/A_/', {}], - ['/', '/a/', '/A_/', {}], - ['/', '/a/', '/A_', undefined], - ['/', '/b', '/A_', undefined], - ])('static %s %s => %s', (base, from, to, result) => { + ['/a', '/A_', {}], + ['/a/b', '/A_/B_', {}], + ['/a', '/A_/', {}], + ['/a/', '/A_/', {}], + ['/a/', '/A_', undefined], + ['/b', '/A_', undefined], + ])('static %s %s => %s', (from, to, result) => { expect( - matchByPath(base, from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -298,7 +296,7 @@ describe('non-nested paths', () => { ['/a/1/b/2', '/A_/$id_/B_/$id_', { id: '2' }], ])('params %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -311,7 +309,7 @@ describe('non-nested paths', () => { ['/a/1/b/2', '/A_/{-$id}_/B/{-$id}_', { id: '2' }], ])('optional %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) @@ -322,27 +320,27 @@ describe('non-nested paths', () => { ['/a/b/c', '/A_/$_/foo_', { _splat: 'b/c', '*': 'b/c' }], ])('wildcard %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, caseSensitive: false, fuzzy: false }), + matchByPath(from, { to, caseSensitive: false, fuzzy: false }), ).toEqual(result) }) }) describe('fuzzy path matching', () => { it.each([ - ['/', '/a', '/a_', {}], - ['/', '/a', '/a_/', {}], - ['/', '/a/', '/a_/', {}], - ['/', '/a/', '/a_', { '**': '/' }], - ['/', '/a/b', '/a_/b_', {}], - ['/', '/a/b', '/a_', { '**': 'b' }], - ['/', '/a/b/', '/a_', { '**': 'b/' }], - ['/', '/a/b/c', '/a_', { '**': 'b/c' }], - ['/', '/a', '/a_/b_', undefined], - ['/', '/b', '/a_', undefined], - ['/', '/a', '/b_', undefined], - ])('static %s %s => %s', (base, from, to, result) => { + ['/a', '/a_', {}], + ['/a', '/a_/', {}], + ['/a/', '/a_/', {}], + ['/a/', '/a_', { '**': '/' }], + ['/a/b', '/a_/b_', {}], + ['/a/b', '/a_', { '**': 'b' }], + ['/a/b/', '/a_', { '**': 'b/' }], + ['/a/b/c', '/a_', { '**': 'b/c' }], + ['/a', '/a_/b_', undefined], + ['/b', '/a_', undefined], + ['/a', '/b_', undefined], + ])('static %s %s => %s', (from, to, result) => { expect( - matchByPath(base, from, { to, fuzzy: true, caseSensitive: true }), + matchByPath(from, { to, fuzzy: true, caseSensitive: true }), ).toEqual(result) }) @@ -354,7 +352,7 @@ describe('non-nested paths', () => { ['/a/1/b/2/c', '/a_/$id_/b_/$other_', { id: '1', other: '2', '**': 'c' }], ])('params %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), + matchByPath(from, { to, fuzzy: true, caseSensitive: true }), ).toEqual(result) }) @@ -374,7 +372,7 @@ describe('non-nested paths', () => { ], ])('optional %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), + matchByPath(from, { to, fuzzy: true, caseSensitive: true }), ).toEqual(result) }) @@ -385,7 +383,7 @@ describe('non-nested paths', () => { ['/a/b/c/d', '/a_/$_/foo_', { _splat: 'b/c/d', '*': 'b/c/d' }], ])('wildcard %s => %s', (from, to, result) => { expect( - matchByPath('/', from, { to, fuzzy: true, caseSensitive: true }), + matchByPath(from, { to, fuzzy: true, caseSensitive: true }), ).toEqual(result) }) }) diff --git a/packages/router-core/tests/optional-path-params-clean.test.ts b/packages/router-core/tests/optional-path-params-clean.test.ts index 57f7d50f2ae..f9cbd0c4fd9 100644 --- a/packages/router-core/tests/optional-path-params-clean.test.ts +++ b/packages/router-core/tests/optional-path-params-clean.test.ts @@ -99,43 +99,43 @@ describe('Optional Path Parameters - Clean Comprehensive Tests', () => { describe('matchPathname', () => { it('should match optional dynamic params when present', () => { - const result = matchPathname('/', '/posts/tech', { + const result = matchPathname('/posts/tech', { to: '/posts/{-$category}', }) expect(result).toEqual({ category: 'tech' }) }) it('should match optional dynamic params when absent', () => { - const result = matchPathname('/', '/posts', { + const result = matchPathname('/posts', { to: '/posts/{-$category}', }) expect(result).toEqual({}) }) it('should handle multiple optional dynamic params', () => { - const result1 = matchPathname('/', '/posts/tech/hello', { + const result1 = matchPathname('/posts/tech/hello', { to: '/posts/{-$category}/{-$slug}', }) expect(result1).toEqual({ category: 'tech', slug: 'hello' }) - const result2 = matchPathname('/', '/posts/tech', { + const result2 = matchPathname('/posts/tech', { to: '/posts/{-$category}/{-$slug}', }) expect(result2).toEqual({ category: 'tech' }) - const result3 = matchPathname('/', '/posts', { + const result3 = matchPathname('/posts', { to: '/posts/{-$category}/{-$slug}', }) expect(result3).toEqual({}) }) it('should handle mixed required and optional dynamic params', () => { - const result1 = matchPathname('/', '/posts/tech/user/123', { + const result1 = matchPathname('/posts/tech/user/123', { to: '/posts/{-$category}/user/$id', }) expect(result1).toEqual({ category: 'tech', id: '123' }) - const result2 = matchPathname('/', '/posts/user/123', { + const result2 = matchPathname('/posts/user/123', { to: '/posts/{-$category}/user/$id', }) expect(result2).toEqual({ id: '123' }) diff --git a/packages/router-core/tests/optional-path-params.test.ts b/packages/router-core/tests/optional-path-params.test.ts index a34c54bb2fe..bf54607300c 100644 --- a/packages/router-core/tests/optional-path-params.test.ts +++ b/packages/router-core/tests/optional-path-params.test.ts @@ -389,7 +389,7 @@ describe('Optional Path Parameters', () => { expectedMatchedParams: {}, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -422,16 +422,16 @@ describe('Optional Path Parameters', () => { ] tests.forEach(({ input, pattern, expected }) => { - expect(matchPathname('/', input, { to: pattern })).toEqual(expected) + expect(matchPathname(input, { to: pattern })).toEqual(expected) }) }) it('should prioritize more specific routes over optional param routes', () => { // Test that /posts/featured matches a static route, not optional param route - const staticMatch = matchPathname('/', '/posts/featured', { + const staticMatch = matchPathname('/posts/featured', { to: '/posts/featured', }) - const optionalMatch = matchPathname('/', '/posts/featured', { + const optionalMatch = matchPathname('/posts/featured', { to: '/posts/{-$category}', }) @@ -448,7 +448,7 @@ describe('Optional Path Parameters', () => { _splat: 'extra/path', } - expect(matchPathname('/', input, { to: pattern })).toEqual(expected) + expect(matchPathname(input, { to: pattern })).toEqual(expected) }) }) }) diff --git a/packages/router-core/tests/path.test.ts b/packages/router-core/tests/path.test.ts index 7f77809c54f..c84ca14fcee 100644 --- a/packages/router-core/tests/path.test.ts +++ b/packages/router-core/tests/path.test.ts @@ -7,96 +7,12 @@ import { interpolatePath, matchPathname, parsePathname, - removeBasepath, removeTrailingSlash, resolvePath, trimPathLeft, } from '../src/path' import type { Segment as PathSegment } from '../src/path' -describe('removeBasepath', () => { - it.each([ - { - name: '`/` should leave pathname as-is', - basepath: '/', - pathname: '/path', - expected: '/path', - }, - { - name: 'should return empty string if basepath is the same as pathname', - basepath: '/path', - pathname: '/path', - expected: '', - }, - { - name: 'should remove basepath from the beginning of the pathname', - basepath: '/app', - pathname: '/app/path/app', - expected: '/path/app', - }, - { - name: 'should remove multisegment basepath from the beginning of the pathname', - basepath: '/app/new', - pathname: '/app/new/path/app/new', - expected: '/path/app/new', - }, - { - name: 'should remove basepath only in case it matches segments completely', - basepath: '/app', - pathname: '/application', - expected: '/application', - }, - { - name: 'should remove multisegment basepath only in case it matches segments completely', - basepath: '/app/new', - pathname: '/app/new-application', - expected: '/app/new-application', - }, - ])('$name', ({ basepath, pathname, expected }) => { - expect(removeBasepath(basepath, pathname)).toBe(expected) - }) - - describe('case sensitivity', () => { - describe('caseSensitive = true', () => { - it.each([ - { - name: 'should not remove basepath from the beginning of the pathname', - basepath: '/app', - pathname: '/App/path/App', - expected: '/App/path/App', - }, - { - name: 'should not remove basepath from the beginning of the pathname with multiple segments', - basepath: '/app/New', - pathname: '/App/New/path/App', - expected: '/App/New/path/App', - }, - ])('$name', ({ basepath, pathname, expected }) => { - expect(removeBasepath(basepath, pathname, true)).toBe(expected) - }) - }) - - describe('caseSensitive = false', () => { - it.each([ - { - name: 'should remove basepath from the beginning of the pathname', - basepath: '/App', - pathname: '/app/path/app', - expected: '/path/app', - }, - { - name: 'should remove multisegment basepath from the beginning of the pathname', - basepath: '/App/New', - pathname: '/app/new/path/app', - expected: '/path/app', - }, - ])('$name', ({ basepath, pathname, expected }) => { - expect(removeBasepath(basepath, pathname, false)).toBe(expected) - }) - }) - }) -}) - describe.each([{ basepath: '/' }, { basepath: '/app' }, { basepath: '/app/' }])( 'removeTrailingSlash with basepath $basepath', ({ basepath }) => { @@ -168,47 +84,40 @@ describe.each([{ basepath: '/' }, { basepath: '/app' }, { basepath: '/app/' }])( describe('resolvePath', () => { describe.each([ - ['/', '/', '/', '/'], - ['/', '/', '/a', '/a'], - ['/', '/', 'a/', '/a'], - ['/', '/', '/a/b', '/a/b'], - ['/', 'a', 'b', '/a/b'], - ['/a/b', 'c', '/a/b/c', '/a/b/c'], - ['/a/b', '/', 'c', '/a/b/c'], - ['/a/b', '/', './c', '/a/b/c'], - ['/', '/', 'a/b', '/a/b'], - ['/', '/', './a/b', '/a/b'], - ['/', '/a/b/c', 'd', '/a/b/c/d'], - ['/', '/a/b/c', './d', '/a/b/c/d'], - ['/', '/a/b/c', './../d', '/a/b/d'], - ['/', '/a/b/c/d', './../d', '/a/b/c/d'], - ['/', '/a/b/c', '../../d', '/a/d'], - ['/', '/a/b/c', '../d', '/a/b/d'], - ['/', '/a/b/c', '..', '/a/b'], - ['/', '/a/b/c', '../..', '/a'], - ['/', '/a/b/c', '../../..', '/'], - ['/', '/a/b/c/', '../../..', '/'], - ['/products', '/', '/products-list', '/products/products-list'], - ['/basepath', '/products', '.', '/basepath/products'], - ])('resolves correctly', (base, a, b, eq) => { - it(`Base: ${base} - ${a} to ${b} === ${eq}`, () => { - expect(resolvePath({ basepath: base, base: a, to: b })).toEqual(eq) + ['/', '/', '/'], + ['/', '/a', '/a'], + ['/', 'a/', '/a'], + ['/', '/a/b', '/a/b'], + ['/a', 'b', '/a/b'], + ['/', 'a/b', '/a/b'], + ['/', './a/b', '/a/b'], + ['/a/b/c', 'd', '/a/b/c/d'], + ['/a/b/c', './d', '/a/b/c/d'], + ['/a/b/c', './../d', '/a/b/d'], + ['/a/b/c/d', './../d', '/a/b/c/d'], + ['/a/b/c', '../../d', '/a/d'], + ['/a/b/c', '../d', '/a/b/d'], + ['/a/b/c', '..', '/a/b'], + ['/a/b/c', '../..', '/a'], + ['/a/b/c', '../../..', '/'], + ['/a/b/c/', '../../..', '/'], + ])('resolves correctly', (a, b, eq) => { + it(`${a} to ${b} === ${eq}`, () => { + expect(resolvePath({ base: a, to: b })).toEqual(eq) }) - it(`Base: ${base} - ${a}/ to ${b} === ${eq} (trailing slash)`, () => { - expect(resolvePath({ basepath: base, base: a + '/', to: b })).toEqual(eq) + it(`${a}/ to ${b} === ${eq} (trailing slash)`, () => { + expect(resolvePath({ base: a + '/', to: b })).toEqual(eq) }) - it(`Base: ${base} - ${a}/ to ${b}/ === ${eq} (trailing slash + trailing slash)`, () => { - expect( - resolvePath({ basepath: base, base: a + '/', to: b + '/' }), - ).toEqual(eq) + it(`${a}/ to ${b}/ === ${eq} (trailing slash + trailing slash)`, () => { + expect(resolvePath({ base: a + '/', to: b + '/' })).toEqual(eq) }) }) + describe('trailingSlash', () => { describe(`'always'`, () => { it('keeps trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd/', trailingSlash: 'always', @@ -218,7 +127,6 @@ describe('resolvePath', () => { it('adds trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd', trailingSlash: 'always', @@ -230,7 +138,6 @@ describe('resolvePath', () => { it('removes trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd/', trailingSlash: 'never', @@ -240,7 +147,6 @@ describe('resolvePath', () => { it('does not add trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd', trailingSlash: 'never', @@ -252,7 +158,6 @@ describe('resolvePath', () => { it('keeps trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd/', trailingSlash: 'preserve', @@ -262,7 +167,6 @@ describe('resolvePath', () => { it('does not add trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd', trailingSlash: 'preserve', @@ -295,11 +199,9 @@ describe('resolvePath', () => { const candidate = base + trimPathLeft(to) expect( resolvePath({ - basepath: '/', base, to: candidate, trailingSlash: 'never', - caseSensitive: false, }), ).toEqual(candidate) }) @@ -325,11 +227,9 @@ describe('resolvePath', () => { const candidate = base + trimPathLeft(to) expect( resolvePath({ - basepath: '/', base, to: candidate, trailingSlash: 'never', - caseSensitive: false, }), ).toEqual(candidate) }) @@ -584,63 +484,6 @@ describe('interpolatePath', () => { }) describe('matchPathname', () => { - describe('basepath matching', () => { - it.each([ - { - name: 'should match when the input is the same as the basepath', - basepath: '/basepath', - input: '/basepath', - matchingOptions: { - to: '/', - }, - expectedMatchedParams: {}, - }, - { - name: 'should match when the input starts with the basepath and `to` is set to the remaining', - basepath: '/basepath', - input: '/basepath/abc', - matchingOptions: { - to: '/abc', - }, - expectedMatchedParams: {}, - }, - { - name: 'should not match when the input is `/` and does not start with the basepath', - basepath: '/basepath', - input: '/', - matchingOptions: { - to: '/', - }, - expectedMatchedParams: undefined, - }, - { - name: 'should not match when the input completely does not start with the basepath', - basepath: '/basepath', - input: '/abc', - matchingOptions: { - to: '/abc', - }, - expectedMatchedParams: undefined, - }, - { - name: 'should not match when the input only partially matches the basepath', - basepath: '/base', - input: '/basepath/abc', - matchingOptions: { - to: '/abc', - }, - expectedMatchedParams: undefined, - }, - ])( - '$name', - ({ basepath, input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname(basepath, input, matchingOptions)).toStrictEqual( - expectedMatchedParams, - ) - }, - ) - }) - describe('path param(s) matching', () => { it.each([ { @@ -716,7 +559,7 @@ describe('matchPathname', () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -780,7 +623,7 @@ describe('matchPathname', () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -859,7 +702,7 @@ describe('matchPathname', () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -1167,35 +1010,31 @@ describe('parsePathname', () => { describe('non-nested paths', async () => { describe('resolvePath', () => { describe.each([ - ['/', '/', '/a_', '/a'], - ['/', '/', 'a_/', '/a'], - ['/', '/', '/a_/b_', '/a/b'], - ['/', 'a', 'b_', '/a/b'], - ['/a/b', 'c', '/a/b/c_', '/a/b/c'], - ['/a/b', '/', 'c_', '/a/b/c'], - ['/a/b', '/', './c_', '/a/b/c'], - ['/', '/', 'a_/b_', '/a/b'], - ['/', '/', './a_/b_', '/a/b'], - ['/', '/a/b/c', 'd_', '/a/b/c/d'], - ['/', '/a/b/c', './d_', '/a/b/c/d'], - ['/', '/a/b/c', './../d_', '/a/b/d'], - ['/', '/a/b/c/d', './../d_', '/a/b/c/d'], - ['/', '/a/b/c', '../../d_', '/a/d'], - ['/', '/a/b/c', '../d_', '/a/b/d'], - ['/products', '/', '/products-list_', '/products/products-list'], - ])('resolves correctly', (base, a, b, eq) => { - it(`Base: ${base} - ${a} to ${b} === ${eq}`, () => { - expect(resolvePath({ basepath: base, base: a, to: b })).toEqual(eq) + ['/', '/a_', '/a'], + ['/', 'a_/', '/a'], + ['/', '/a_/b_', '/a/b'], + ['/a', 'b_', '/a/b'], + ['/c', '/a/b/c_', '/a/b/c'], + ['/', 'c_', '/c'], + ['/', './c_', '/c'], + ['/', 'a_/b_', '/a/b'], + ['/', './a_/b_', '/a/b'], + ['/a/b/c', 'd_', '/a/b/c/d'], + ['/a/b/c', './d_', '/a/b/c/d'], + ['/a/b/c', './../d_', '/a/b/d'], + ['/a/b/c/d', './../d_', '/a/b/c/d'], + ['/a/b/c', '../../d_', '/a/d'], + ['/a/b/c', '../d_', '/a/b/d'], + ['/', '/products-list_', '/products-list'], + ])('resolves correctly', (a, b, eq) => { + it(`${a} to ${b} === ${eq}`, () => { + expect(resolvePath({ base: a, to: b })).toEqual(eq) }) - it(`Base: ${base} - ${a}/ to ${b} === ${eq} (trailing slash)`, () => { - expect(resolvePath({ basepath: base, base: a + '/', to: b })).toEqual( - eq, - ) + it(`${a}/ to ${b} === ${eq} (trailing slash)`, () => { + expect(resolvePath({ base: a + '/', to: b })).toEqual(eq) }) - it(`Base: ${base} - ${a}/ to ${b}/ === ${eq} (trailing slash + trailing slash)`, () => { - expect( - resolvePath({ basepath: base, base: a + '/', to: b + '/' }), - ).toEqual(eq) + it(`${a}/ to ${b}/ === ${eq} (trailing slash + trailing slash)`, () => { + expect(resolvePath({ base: a + '/', to: b + '/' })).toEqual(eq) }) }) @@ -1204,7 +1043,6 @@ describe('non-nested paths', async () => { it('keeps trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_/', trailingSlash: 'always', @@ -1214,7 +1052,6 @@ describe('non-nested paths', async () => { it('adds trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_', trailingSlash: 'always', @@ -1227,7 +1064,6 @@ describe('non-nested paths', async () => { it('removes trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_/', trailingSlash: 'never', @@ -1237,7 +1073,6 @@ describe('non-nested paths', async () => { it('does not add trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_', trailingSlash: 'never', @@ -1250,7 +1085,6 @@ describe('non-nested paths', async () => { it('keeps trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_/', trailingSlash: 'preserve', @@ -1260,7 +1094,6 @@ describe('non-nested paths', async () => { it('does not add trailing slash', () => { expect( resolvePath({ - basepath: '/', base: '/a/b/c', to: 'd_', trailingSlash: 'preserve', @@ -1316,11 +1149,9 @@ describe('non-nested paths', async () => { const result = base + trimPathLeft(expected) expect( resolvePath({ - basepath: '/', base, to: candidate, trailingSlash: 'never', - caseSensitive: false, }), ).toEqual(result) }) @@ -1369,11 +1200,9 @@ describe('non-nested paths', async () => { const result = base + trimPathLeft(expected) expect( resolvePath({ - basepath: '/', base, to: candidate, trailingSlash: 'never', - caseSensitive: false, }), ).toEqual(result) }) @@ -1610,54 +1439,6 @@ describe('non-nested paths', async () => { }) describe('matchPathname', () => { - describe('basepath matching', () => { - it.each([ - { - name: 'should match when the input starts with the basepath and `to` is set to the remaining', - basepath: '/basepath', - input: '/basepath/abc', - matchingOptions: { - to: '/abc_', - }, - expectedMatchedParams: {}, - }, - { - name: 'should not match when the input is `/` and does not start with the basepath', - basepath: '/basepath', - input: '/', - matchingOptions: { - to: '/', - }, - expectedMatchedParams: undefined, - }, - { - name: 'should not match when the input completely does not start with the basepath', - basepath: '/basepath', - input: '/abc', - matchingOptions: { - to: '/abc_', - }, - expectedMatchedParams: undefined, - }, - { - name: 'should not match when the input only partially matches the basepath', - basepath: '/base', - input: '/basepath/abc', - matchingOptions: { - to: '/abc_', - }, - expectedMatchedParams: undefined, - }, - ])( - '$name', - ({ basepath, input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname(basepath, input, matchingOptions)).toStrictEqual( - expectedMatchedParams, - ) - }, - ) - }) - describe('path param(s) matching', () => { it.each([ { @@ -1733,7 +1514,7 @@ describe('non-nested paths', async () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -1797,7 +1578,7 @@ describe('non-nested paths', async () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) @@ -1856,7 +1637,7 @@ describe('non-nested paths', async () => { }, }, ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname('/', input, matchingOptions)).toStrictEqual( + expect(matchPathname(input, matchingOptions)).toStrictEqual( expectedMatchedParams, ) }) diff --git a/packages/router-core/tests/processRouteTree.test.ts b/packages/router-core/tests/processRouteTree.test.ts index e2fdfca35a3..3a509a3376f 100644 --- a/packages/router-core/tests/processRouteTree.test.ts +++ b/packages/router-core/tests/processRouteTree.test.ts @@ -291,7 +291,6 @@ describe('processRouteTree', () => { // Test actual path matching for `/foo` const matchResult = getMatchedRoutes({ pathname: '/foo', - basepath: '/', caseSensitive: false, routesByPath: result.routesByPath, routesById: result.routesById, diff --git a/packages/router-devtools-core/package.json b/packages/router-devtools-core/package.json index 25e034b3f6f..f80f9d470dd 100644 --- a/packages/router-devtools-core/package.json +++ b/packages/router-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools-core", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for Web applications", "author": "Tanner Linsley", "license": "MIT", @@ -64,16 +64,17 @@ "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", - "solid-js": "^1.9.5" + "solid-js": "^1.9.5", + "vite": "^7.1.1" }, "devDependencies": { - "vite-plugin-solid": "^2.11.6" + "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { - "tiny-invariant": "^1.3.3", "@tanstack/router-core": "workspace:^", "csstype": "^3.0.10", - "solid-js": ">=1.9.5" + "solid-js": ">=1.9.5", + "tiny-invariant": "^1.3.3" }, "peerDependenciesMeta": { "csstype": { diff --git a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx index f7c1b91ac15..a0c288704e0 100644 --- a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx +++ b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx @@ -98,6 +98,8 @@ function RouteComp({ routerState: Accessor< RouterState< Route< + any, + any, any, '/', '/', @@ -111,7 +113,9 @@ function RouteComp({ {}, undefined, any, - FileRouteTypes + FileRouteTypes, + unknown, + undefined >, MakeRouteMatchUnion > diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index aab06bcc6b4..41d9e34a1ec 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -62,9 +62,10 @@ "node": ">=12" }, "dependencies": { + "@tanstack/react-router-devtools": "workspace:*", "clsx": "^2.1.1", "goober": "^2.1.16", - "@tanstack/react-router-devtools": "workspace:*" + "vite": "^7.1.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json index da53dc3b3aa..0a0c582de22 100644 --- a/packages/router-generator/package.json +++ b/packages/router-generator/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-generator", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index 366f2f1d4a7..332549594eb 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -37,7 +37,12 @@ export const configSchema = baseConfigSchema.extend({ verboseFileRoutes: z.boolean().optional(), addExtensions: z.boolean().optional().default(false), enableRouteTreeFormatting: z.boolean().optional().default(true), - routeTreeFileFooter: z.array(z.string()).optional().default([]), + routeTreeFileFooter: z + .union([ + z.array(z.string()).optional().default([]), + z.function().returns(z.array(z.string())), + ]) + .optional(), autoCodeSplitting: z.boolean().optional(), customScaffolding: z .object({ @@ -53,6 +58,7 @@ export const configSchema = baseConfigSchema.extend({ .optional(), plugins: z.array(z.custom()).optional(), tmpDir: z.string().optional().default(''), + importRoutesUsingAbsolutePaths: z.boolean().optional().default(false), }) export type Config = z.infer diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 122976d417a..956b1f7a4ec 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -1,8 +1,8 @@ import path from 'node:path' import * as fsp from 'node:fs/promises' -import { mkdirSync } from 'node:fs' +import { existsSync, mkdirSync } from 'node:fs' import crypto from 'node:crypto' -import { deepEqual, rootRouteId } from '@tanstack/router-core' +import { rootRouteId } from '@tanstack/router-core' import { logging } from './logger' import { isVirtualConfigFile, @@ -15,16 +15,18 @@ import { buildImportString, buildRouteTreeConfig, checkFileExists, + checkRouteFullPathUniqueness, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, determineNodePath, findParent, format, + getImportForRouteNode, + getImportPath, getResolvedRouteNodeVariableName, hasParentRoute, isRouteNodeValidForAugmentation, - lowerCaseFirstChar, mergeImportDeclarations, multiSortBy, removeExt, @@ -39,11 +41,7 @@ import { } from './utils' import { fillTemplate, getTargetTemplate } from './template' import { transform } from './transform/transform' -import { defaultGeneratorPlugin } from './plugin/default-generator-plugin' -import type { - GeneratorPlugin, - GeneratorPluginWithTransform, -} from './plugin/types' +import type { GeneratorPlugin } from './plugin/types' import type { TargetTemplate } from './template' import type { FsRouteType, @@ -55,7 +53,6 @@ import type { } from './types' import type { Config } from './config' import type { Logger } from './logger' -import type { TransformPlugin } from './transform/types' interface fs { stat: ( @@ -149,12 +146,18 @@ interface GeneratorCacheEntry { } interface RouteNodeCacheEntry extends GeneratorCacheEntry { - exports: Array routeId: string + node: RouteNode } type GeneratorRouteNodeCache = Map +interface CrawlingResult { + rootRouteNode: RouteNode + routeFileResult: Array + acc: HandleNodeAccumulator +} + export class Generator { /** * why do we have two caches for the route files? @@ -171,6 +174,7 @@ export class Generator { private routeTreeFileCache: GeneratorCacheEntry | undefined + private crawlingResult: CrawlingResult | undefined public config: Config public targetTemplate: TargetTemplate @@ -182,11 +186,8 @@ export class Generator { private generatedRouteTreePath: string private runPromise: Promise | undefined private fileEventQueue: Array = [] - private plugins: Array = [defaultGeneratorPlugin()] - private pluginsWithTransform: Array = [] - // this is just a cache for the transform plugins since we need them for each route file that is to be processed - private transformPlugins: Array = [] - private routeGroupPatternRegex = /\(.+\)/g + private plugins: Array = [] + private static routeGroupPatternRegex = /\(.+\)/g private physicalDirectories: Array = [] constructor(opts: { config: Config; root: string; fs?: fs }) { @@ -194,22 +195,31 @@ export class Generator { this.logger = logging({ disabled: this.config.disableLogging }) this.root = opts.root this.fs = opts.fs || DefaultFileSystem - this.generatedRouteTreePath = path.resolve(this.config.generatedRouteTree) + this.generatedRouteTreePath = this.getGeneratedRouteTreePath() this.targetTemplate = getTargetTemplate(this.config) this.routesDirectoryPath = this.getRoutesDirectoryPath() this.plugins.push(...(opts.config.plugins || [])) - this.plugins.forEach((plugin) => { - if ('transformPlugin' in plugin) { - if (this.pluginsWithTransform.find((p) => p.name === plugin.name)) { - throw new Error( - `Plugin with name "${plugin.name}" is already registered for export ${plugin.transformPlugin.exportName}!`, - ) - } - this.pluginsWithTransform.push(plugin) - this.transformPlugins.push(plugin.transformPlugin) - } - }) + + for (const plugin of this.plugins) { + plugin.init?.({ generator: this }) + } + } + + private getGeneratedRouteTreePath() { + const generatedRouteTreePath = path.isAbsolute( + this.config.generatedRouteTree, + ) + ? this.config.generatedRouteTree + : path.resolve(this.root, this.config.generatedRouteTree) + + const generatedRouteTreeDir = path.dirname(generatedRouteTreePath) + + if (!existsSync(generatedRouteTreeDir)) { + mkdirSync(generatedRouteTreeDir, { recursive: true }) + } + + return generatedRouteTreePath } private getRoutesDirectoryPath() { @@ -279,12 +289,7 @@ export class Generator { } try { - const start = performance.now() await this.generatorInternal() - const end = performance.now() - this.logger.info( - `Generated route tree in ${Math.round(end - start)}ms`, - ) } catch (err) { const errArray = !Array.isArray(err) ? [err] : err @@ -335,7 +340,7 @@ export class Generator { } this.physicalDirectories = physicalDirectories - writeRouteTreeFile = await this.handleRootNode(rootRouteNode) + await this.handleRootNode(rootRouteNode) const preRouteNodes = multiSortBy(beforeRouteNodes, [ (d) => (d.routePath === '/' ? -1 : 1), @@ -374,22 +379,29 @@ export class Generator { const routeFileResult = routeFileAllResult.flatMap((result) => { if (result.status === 'fulfilled' && result.value !== null) { - return result.value + if (result.value.shouldWriteTree) { + writeRouteTreeFile = true + } + return result.value.node } return [] }) - routeFileResult.forEach((result) => { - if (!result.node.exports?.length) { - this.logger.warn( - `Route file "${result.cacheEntry.fileContent}" does not export any route piece. This is likely a mistake.`, - ) - } - }) - if (routeFileResult.find((r) => r.shouldWriteTree)) { - writeRouteTreeFile = true + // reset children in case we re-use a node from the cache + routeFileResult.forEach((r) => (r.children = undefined)) + + const acc: HandleNodeAccumulator = { + routeTree: [], + routeNodes: [], + routePiecesByPath: {}, + } + + for (const node of routeFileResult) { + Generator.handleNode(node, acc) } + this.crawlingResult = { rootRouteNode, routeFileResult, acc } + // this is the first time the generator runs, so read in the route tree file if it exists yet if (!this.routeTreeFileCache) { const routeTreeFile = await this.fs.readFile(this.generatedRouteTreePath) @@ -424,10 +436,14 @@ export class Generator { if (!writeRouteTreeFile) { // only needs to be done if no other changes have been detected yet // compare shadowCache and cache to identify deleted routes - for (const fullPath of this.routeNodeCache.keys()) { - if (!this.routeNodeShadowCache.has(fullPath)) { - writeRouteTreeFile = true - break + if (this.routeNodeCache.size !== this.routeNodeShadowCache.size) { + writeRouteTreeFile = true + } else { + for (const fullPath of this.routeNodeCache.keys()) { + if (!this.routeNodeShadowCache.has(fullPath)) { + writeRouteTreeFile = true + break + } } } } @@ -437,11 +453,13 @@ export class Generator { return } - let routeTreeContent = this.buildRouteTreeFileContent( + const buildResult = this.buildRouteTree({ rootRouteNode, - preRouteNodes, + acc, routeFileResult, - ) + }) + let routeTreeContent = buildResult.routeTreeContent + routeTreeContent = this.config.enableRouteTreeFormatting ? await format(routeTreeContent, this.config) : routeTreeContent @@ -483,6 +501,14 @@ export class Generator { } } + this.plugins.map((plugin) => { + return plugin.onRouteTreeChanged?.({ + routeTree: buildResult.routeTree, + routeNodes: buildResult.routeNodes, + acc, + rootRouteNode, + }) + }) this.swapCaches() } @@ -491,127 +517,117 @@ export class Generator { this.routeNodeShadowCache = new Map() } - private buildRouteTreeFileContent( - rootRouteNode: RouteNode, - preRouteNodes: Array, - routeFileResult: Array<{ - cacheEntry: RouteNodeCacheEntry - node: RouteNode - }>, - ) { - const getImportForRouteNode = (node: RouteNode, exportName: string) => { - if (node.exports?.includes(exportName)) { - return { - source: `./${this.getImportPath(node)}`, - specifiers: [ - { - imported: exportName, - local: `${node.variableName}${exportName}Import`, - }, - ], - } satisfies ImportDeclaration - } - return undefined - } + public buildRouteTree(opts: { + rootRouteNode: RouteNode + acc: HandleNodeAccumulator + routeFileResult: Array + config?: Partial + }) { + const config = { ...this.config, ...(opts.config || {}) } - const buildRouteTreeForExport = (plugin: GeneratorPluginWithTransform) => { - const exportName = plugin.transformPlugin.exportName - const acc: HandleNodeAccumulator = { - routeTree: [], - routeNodes: [], - routePiecesByPath: {}, - } - for (const node of preRouteNodes) { - if (node.exports?.includes(plugin.transformPlugin.exportName)) { - this.handleNode(node, acc) - } - } + const { rootRouteNode, acc } = opts - const sortedRouteNodes = multiSortBy(acc.routeNodes, [ - (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1), - (d) => d.routePath?.split('/').length, - (d) => (d.routePath?.endsWith(this.config.indexToken) ? -1 : 1), - (d) => d, - ]) + const sortedRouteNodes = multiSortBy(acc.routeNodes, [ + (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1), + (d) => d.routePath?.split('/').length, + (d) => (d.routePath?.endsWith(config.indexToken) ? -1 : 1), + (d) => d, + ]) + + const routeImports = sortedRouteNodes + .filter((d) => !d.isVirtual) + .flatMap((node) => + getImportForRouteNode( + node, + config, + this.generatedRouteTreePath, + this.root, + ), + ) - const pluginConfig = plugin.config({ - generator: this, - rootRouteNode, - sortedRouteNodes, + const virtualRouteNodes = sortedRouteNodes + .filter((d) => d.isVirtual) + .map((node) => { + return `const ${ + node.variableName + }RouteImport = createFileRoute('${node.routePath}')()` }) - const routeImports = sortedRouteNodes - .filter((d) => !d.isVirtual) - .flatMap((node) => getImportForRouteNode(node, exportName) ?? []) - - const hasMatchingRouteFiles = - acc.routeNodes.length > 0 || rootRouteNode.exports?.includes(exportName) - - const virtualRouteNodes = sortedRouteNodes - .filter((d) => d.isVirtual) - .map((node) => { - return `const ${ - node.variableName - }${exportName}Import = ${plugin.createVirtualRouteCode({ node })}` - }) + const imports: Array = [] + if (acc.routeNodes.some((n) => n.isVirtual)) { + imports.push({ + specifiers: [{ imported: 'createFileRoute' }], + source: this.targetTemplate.fullPkg, + }) + } + if (config.verboseFileRoutes === false) { + const typeImport: ImportDeclaration = { + specifiers: [], + source: this.targetTemplate.fullPkg, + importKind: 'type', + } if ( - !rootRouteNode.exports?.includes(exportName) && - pluginConfig.virtualRootRoute + sortedRouteNodes.some( + (d) => + isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy', + ) ) { - virtualRouteNodes.unshift( - `const ${rootRouteNode.variableName}${exportName}Import = ${plugin.createRootRouteCode()}`, + typeImport.specifiers.push({ imported: 'CreateFileRoute' }) + } + if ( + sortedRouteNodes.some( + (node) => + acc.routePiecesByPath[node.routePath!]?.lazy && + isRouteNodeValidForAugmentation(node), ) + ) { + typeImport.specifiers.push({ imported: 'CreateLazyFileRoute' }) } - const imports = plugin.imports({ - sortedRouteNodes, - acc, - generator: this, - rootRouteNode, - }) + if (typeImport.specifiers.length > 0) { + typeImport.specifiers.push({ imported: 'FileRoutesByPath' }) + imports.push(typeImport) + } + } - const routeTreeConfig = buildRouteTreeConfig( - acc.routeTree, - exportName, - this.config.disableTypes, - ) + const routeTreeConfig = buildRouteTreeConfig( + acc.routeTree, + config.disableTypes, + ) - const createUpdateRoutes = sortedRouteNodes.map((node) => { - const loaderNode = acc.routePiecesByPath[node.routePath!]?.loader - const componentNode = acc.routePiecesByPath[node.routePath!]?.component - const errorComponentNode = - acc.routePiecesByPath[node.routePath!]?.errorComponent - const pendingComponentNode = - acc.routePiecesByPath[node.routePath!]?.pendingComponent - const lazyComponentNode = acc.routePiecesByPath[node.routePath!]?.lazy + const createUpdateRoutes = sortedRouteNodes.map((node) => { + const loaderNode = acc.routePiecesByPath[node.routePath!]?.loader + const componentNode = acc.routePiecesByPath[node.routePath!]?.component + const errorComponentNode = + acc.routePiecesByPath[node.routePath!]?.errorComponent + const pendingComponentNode = + acc.routePiecesByPath[node.routePath!]?.pendingComponent + const lazyComponentNode = acc.routePiecesByPath[node.routePath!]?.lazy - return [ - [ - `const ${node.variableName}${exportName} = ${node.variableName}${exportName}Import.update({ + return [ + [ + `const ${node.variableName}Route = ${node.variableName}RouteImport.update({ ${[ `id: '${node.path}'`, !node.isNonPath ? `path: '${node.cleanedPath}'` : undefined, - `getParentRoute: () => ${findParent(node, exportName)}`, + `getParentRoute: () => ${findParent(node)}`, ] .filter(Boolean) .join(',')} - }${this.config.disableTypes ? '' : 'as any'})`, - loaderNode - ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash( - removeExt( - path.relative( - path.dirname(this.config.generatedRouteTree), - path.resolve( - this.config.routesDirectory, - loaderNode.filePath, - ), - ), - this.config.addExtensions, + }${config.disableTypes ? '' : 'as any'})`, + loaderNode + ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash( + removeExt( + path.relative( + path.dirname(config.generatedRouteTree), + path.resolve(config.routesDirectory, loaderNode.filePath), ), - )}'), 'loader') })` - : '', - componentNode || errorComponentNode || pendingComponentNode - ? `.update({ + config.addExtensions, + ), + )}'), 'loader') })` + : '', + componentNode || errorComponentNode || pendingComponentNode + ? `.update({ ${( [ ['component', componentNode], @@ -626,171 +642,141 @@ export class Generator { }: lazyRouteComponent(() => import('./${replaceBackslash( removeExt( path.relative( - path.dirname(this.config.generatedRouteTree), - path.resolve( - this.config.routesDirectory, - d[1]!.filePath, - ), + path.dirname(config.generatedRouteTree), + path.resolve(config.routesDirectory, d[1]!.filePath), ), - this.config.addExtensions, + config.addExtensions, ), )}'), '${d[0]}')` }) .join('\n,')} })` - : '', - lazyComponentNode - ? `.lazy(() => import('./${replaceBackslash( - removeExt( - path.relative( - path.dirname(this.config.generatedRouteTree), - path.resolve( - this.config.routesDirectory, - lazyComponentNode.filePath, - ), + : '', + lazyComponentNode + ? `.lazy(() => import('./${replaceBackslash( + removeExt( + path.relative( + path.dirname(config.generatedRouteTree), + path.resolve( + config.routesDirectory, + lazyComponentNode.filePath, ), - this.config.addExtensions, ), - )}').then((d) => d.${exportName}))` - : '', - ].join(''), - ].join('\n\n') - }) + config.addExtensions, + ), + )}').then((d) => d.Route))` + : '', + ].join(''), + ].join('\n\n') + }) - let fileRoutesByPathInterfacePerPlugin = '' - let fileRoutesByFullPathPerPlugin = '' + let fileRoutesByPathInterface = '' + let fileRoutesByFullPath = '' - if (!this.config.disableTypes && hasMatchingRouteFiles) { - fileRoutesByFullPathPerPlugin = [ - `export interface File${exportName}sByFullPath { + if (!config.disableTypes) { + fileRoutesByFullPath = [ + `export interface FileRoutesByFullPath { ${[...createRouteNodesByFullPath(acc.routeNodes).entries()] .filter(([fullPath]) => fullPath) .map(([fullPath, routeNode]) => { - return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}` + return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` })} }`, - `export interface File${exportName}sByTo { + `export interface FileRoutesByTo { ${[...createRouteNodesByTo(acc.routeNodes).entries()] .filter(([to]) => to) .map(([to, routeNode]) => { - return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}` + return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` })} }`, - `export interface File${exportName}sById { -'${rootRouteId}': typeof root${exportName}Import, + `export interface FileRoutesById { +'${rootRouteId}': typeof rootRouteImport, ${[...createRouteNodesById(acc.routeNodes).entries()].map(([id, routeNode]) => { - return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode, exportName)}` + return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` })} }`, - `export interface File${exportName}Types { -file${exportName}sByFullPath: File${exportName}sByFullPath + `export interface FileRouteTypes { +fileRoutesByFullPath: FileRoutesByFullPath fullPaths: ${ - acc.routeNodes.length > 0 - ? [...createRouteNodesByFullPath(acc.routeNodes).keys()] - .filter((fullPath) => fullPath) - .map((fullPath) => `'${fullPath}'`) - .join('|') - : 'never' - } -file${exportName}sByTo: File${exportName}sByTo + acc.routeNodes.length > 0 + ? [...createRouteNodesByFullPath(acc.routeNodes).keys()] + .filter((fullPath) => fullPath) + .map((fullPath) => `'${fullPath}'`) + .join('|') + : 'never' + } +fileRoutesByTo: FileRoutesByTo to: ${ - acc.routeNodes.length > 0 - ? [...createRouteNodesByTo(acc.routeNodes).keys()] - .filter((to) => to) - .map((to) => `'${to}'`) - .join('|') - : 'never' - } + acc.routeNodes.length > 0 + ? [...createRouteNodesByTo(acc.routeNodes).keys()] + .filter((to) => to) + .map((to) => `'${to}'`) + .join('|') + : 'never' + } id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(acc.routeNodes).keys()].map((id) => `'${id}'`)].join('|')} -file${exportName}sById: File${exportName}sById +fileRoutesById: FileRoutesById }`, - `export interface Root${exportName}Children { -${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${getResolvedRouteNodeVariableName(child, exportName)}`).join(',')} + `export interface RootRouteChildren { +${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`).join(',')} }`, - ].join('\n') - - fileRoutesByPathInterfacePerPlugin = buildFileRoutesByPathInterface({ - ...plugin.moduleAugmentation({ generator: this }), - routeNodes: - this.config.verboseFileRoutes !== false - ? sortedRouteNodes - : [ - ...routeFileResult.map(({ node }) => node), - ...sortedRouteNodes.filter((d) => d.isVirtual), - ], - exportName, - }) - } + ].join('\n') - let routeTree = '' - if (hasMatchingRouteFiles) { - routeTree = [ - `const root${exportName}Children${this.config.disableTypes ? '' : `: Root${exportName}Children`} = { + fileRoutesByPathInterface = buildFileRoutesByPathInterface({ + module: this.targetTemplate.fullPkg, + interfaceName: 'FileRoutesByPath', + routeNodes: sortedRouteNodes, + }) + } + + const routeTree = [ + `const rootRouteChildren${config.disableTypes ? '' : `: RootRouteChildren`} = { ${acc.routeTree .map( (child) => - `${child.variableName}${exportName}: ${getResolvedRouteNodeVariableName(child, exportName)}`, + `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`, ) .join(',')} }`, - `export const ${lowerCaseFirstChar(exportName)}Tree = root${exportName}Import._addFileChildren(root${exportName}Children)${this.config.disableTypes ? '' : `._addFileTypes()`}`, - ].join('\n') - } - - return { - routeImports, - sortedRouteNodes, - acc, - virtualRouteNodes, - routeTreeConfig, - routeTree, - imports, - createUpdateRoutes, - fileRoutesByFullPathPerPlugin, - fileRoutesByPathInterfacePerPlugin, - } - } - - const routeTrees = this.pluginsWithTransform.map((plugin) => ({ - exportName: plugin.transformPlugin.exportName, - ...buildRouteTreeForExport(plugin), - })) + `export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)${config.disableTypes ? '' : `._addFileTypes()`}`, + ].join('\n') - this.plugins.map((plugin) => { - return plugin.onRouteTreesChanged?.({ - routeTrees, - rootRouteNode, - generator: this, - }) - }) - - let mergedImports = mergeImportDeclarations( - routeTrees.flatMap((d) => d.imports), + checkRouteFullPathUniqueness( + sortedRouteNodes.filter( + (d) => d.children === undefined && 'lazy' !== d._fsRouteType, + ), + config, ) - if (this.config.disableTypes) { + + let mergedImports = mergeImportDeclarations(imports) + if (config.disableTypes) { mergedImports = mergedImports.filter((d) => d.importKind !== 'type') } const importStatements = mergedImports.map(buildImportString) let moduleAugmentation = '' - if (this.config.verboseFileRoutes === false && !this.config.disableTypes) { - moduleAugmentation = routeFileResult - .map(({ node }) => { + if (config.verboseFileRoutes === false && !config.disableTypes) { + moduleAugmentation = opts.routeFileResult + .map((node) => { const getModuleDeclaration = (routeNode?: RouteNode) => { if (!isRouteNodeValidForAugmentation(routeNode)) { return '' } - const moduleAugmentation = this.pluginsWithTransform - .map((plugin) => { - return plugin.routeModuleAugmentation({ - routeNode, - }) - }) - .filter(Boolean) - .join('\n') + let moduleAugmentation = '' + if (routeNode._fsRouteType === 'lazy') { + moduleAugmentation = `const createLazyFileRoute: CreateLazyFileRoute` + } else { + moduleAugmentation = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}', + FileRoutesByPath['${routeNode.routePath}']['parentRoute'], + FileRoutesByPath['${routeNode.routePath}']['id'], + FileRoutesByPath['${routeNode.routePath}']['path'], + FileRoutesByPath['${routeNode.routePath}']['fullPath'] + > + ` + } - return `declare module './${this.getImportPath(routeNode)}' { + return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' { ${moduleAugmentation} }` } @@ -799,47 +785,45 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get .join('\n') } - const routeImports = routeTrees.flatMap((t) => t.routeImports) - const rootRouteImports = this.pluginsWithTransform.flatMap( - (p) => - getImportForRouteNode(rootRouteNode, p.transformPlugin.exportName) ?? - [], + const rootRouteImport = getImportForRouteNode( + rootRouteNode, + config, + this.generatedRouteTreePath, + this.root, ) - if (rootRouteImports.length > 0) { - routeImports.unshift(...rootRouteImports) + routeImports.unshift(rootRouteImport) + + let footer: Array = [] + if (config.routeTreeFileFooter) { + if (Array.isArray(config.routeTreeFileFooter)) { + footer = config.routeTreeFileFooter + } else { + footer = config.routeTreeFileFooter() + } } const routeTreeContent = [ - ...this.config.routeTreeFileHeader, + ...config.routeTreeFileHeader, `// This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`, [...importStatements].join('\n'), mergeImportDeclarations(routeImports).map(buildImportString).join('\n'), - routeTrees.flatMap((t) => t.virtualRouteNodes).join('\n'), - routeTrees.flatMap((t) => t.createUpdateRoutes).join('\n'), - - routeTrees.map((t) => t.fileRoutesByFullPathPerPlugin).join('\n'), - routeTrees.map((t) => t.fileRoutesByPathInterfacePerPlugin).join('\n'), + virtualRouteNodes.join('\n'), + createUpdateRoutes.join('\n'), + fileRoutesByFullPath, + fileRoutesByPathInterface, moduleAugmentation, - routeTrees.flatMap((t) => t.routeTreeConfig).join('\n'), - routeTrees.map((t) => t.routeTree).join('\n'), - ...this.config.routeTreeFileFooter, + routeTreeConfig.join('\n'), + routeTree, + ...footer, ] .filter(Boolean) .join('\n\n') - return routeTreeContent - } - - private getImportPath(node: RouteNode) { - return replaceBackslash( - removeExt( - path.relative( - path.dirname(this.config.generatedRouteTree), - path.resolve(this.config.routesDirectory, node.filePath), - ), - this.config.addExtensions, - ), - ) + return { + routeTreeContent, + routeTree: acc.routeTree, + routeNodes: acc.routeNodes, + } } private async processRouteNodeFile(node: RouteNode): Promise<{ @@ -850,14 +834,15 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get const result = await this.isRouteFileCacheFresh(node) if (result.status === 'fresh') { - node.exports = result.cacheEntry.exports return { - node, - shouldWriteTree: result.exportsChanged, + node: result.cacheEntry.node, + shouldWriteTree: false, cacheEntry: result.cacheEntry, } } + const previousCacheEntry = result.cacheEntry + const existingRouteFile = await this.fs.readFile(node.fullPath) if (existingRouteFile === 'file-not-existing') { throw new Error(`⚠️ File ${node.fullPath} does not exist`) @@ -866,16 +851,18 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get const updatedCacheEntry: RouteNodeCacheEntry = { fileContent: existingRouteFile.fileContent, mtimeMs: existingRouteFile.stat.mtimeMs, - exports: [], routeId: node.routePath ?? '$$TSR_NO_ROUTE_PATH_ASSIGNED$$', + node, } const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? '' let shouldWriteRouteFile = false + let shouldWriteTree = false // now we need to either scaffold the file or transform it if (!existingRouteFile.fileContent) { shouldWriteRouteFile = true + shouldWriteTree = true // Creating a new lazy route file if (node._fsRouteType === 'lazy') { const tLazyRouteTemplate = this.targetTemplate.lazyRoute @@ -894,7 +881,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd(), }, ) - updatedCacheEntry.exports = ['Route'] } else if ( // Creating a new normal route file (['layout', 'static'] satisfies Array).some( @@ -922,33 +908,40 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get tsrExportEnd: tRouteTemplate.imports.tsrExportEnd(), }, ) - updatedCacheEntry.exports = ['Route'] } else { return null } - } else { - // transform the file - const transformResult = await transform({ - source: updatedCacheEntry.fileContent, - ctx: { - target: this.config.target, - routeId: escapedRoutePath, - lazy: node._fsRouteType === 'lazy', - verboseFileRoutes: !(this.config.verboseFileRoutes === false), - }, - plugins: this.transformPlugins, - }) + } + // transform the file + const transformResult = await transform({ + source: updatedCacheEntry.fileContent, + ctx: { + target: this.config.target, + routeId: escapedRoutePath, + lazy: node._fsRouteType === 'lazy', + verboseFileRoutes: !(this.config.verboseFileRoutes === false), + }, + node, + }) - if (transformResult.result === 'error') { - throw new Error( - `Error transforming route file ${node.fullPath}: ${transformResult.error}`, - ) - } - updatedCacheEntry.exports = transformResult.exports - if (transformResult.result === 'modified') { - updatedCacheEntry.fileContent = transformResult.output - shouldWriteRouteFile = true - } + if (transformResult.result === 'no-route-export') { + this.logger.warn( + `Route file "${node.fullPath}" does not contain any route piece. This is likely a mistake.`, + ) + return null + } + if (transformResult.result === 'error') { + throw new Error( + `Error transforming route file ${node.fullPath}: ${transformResult.error}`, + ) + } + if (transformResult.result === 'modified') { + updatedCacheEntry.fileContent = transformResult.output + shouldWriteRouteFile = true + } + + for (const plugin of this.plugins) { + plugin.afterTransform?.({ node, prevNode: previousCacheEntry?.node }) } // file was changed @@ -965,11 +958,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get } this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry) - node.exports = updatedCacheEntry.exports - const shouldWriteTree = !deepEqual( - result.cacheEntry?.exports, - updatedCacheEntry.exports, - ) return { node, shouldWriteTree, @@ -1097,7 +1085,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get | { status: 'fresh' cacheEntry: RouteNodeCacheEntry - exportsChanged: boolean } | { status: 'stale'; cacheEntry?: RouteNodeCacheEntry } > { @@ -1109,7 +1096,6 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get this.routeNodeShadowCache.set(node.fullPath, fileChangedCache.cacheEntry) return { status: 'fresh', - exportsChanged: false, cacheEntry: fileChangedCache.cacheEntry, } } @@ -1130,24 +1116,9 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get if (shadowCacheFileChange.result === false) { // shadow cache has latest file state already - // compare shadowCache against cache to determine whether exports changed - // if they didn't, cache is fresh if (fileChangedCache.result === true) { - if ( - deepEqual( - fileChangedCache.cacheEntry.exports, - shadowCacheFileChange.cacheEntry.exports, - ) - ) { - return { - status: 'fresh', - exportsChanged: false, - cacheEntry: shadowCacheFileChange.cacheEntry, - } - } return { status: 'fresh', - exportsChanged: true, cacheEntry: shadowCacheFileChange.cacheEntry, } } @@ -1165,9 +1136,7 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get const result = await this.isRouteFileCacheFresh(node) if (result.status === 'fresh') { - node.exports = result.cacheEntry.exports this.routeNodeShadowCache.set(node.fullPath, result.cacheEntry) - return result.exportsChanged } const rootNodeFile = await this.fs.readFile(node.fullPath) if (rootNodeFile === 'file-not-existing') { @@ -1177,8 +1146,8 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get const updatedCacheEntry: RouteNodeCacheEntry = { fileContent: rootNodeFile.fileContent, mtimeMs: rootNodeFile.stat.mtimeMs, - exports: [], routeId: node.routePath ?? '$$TSR_NO_ROOT_ROUTE_PATH_ASSIGNED$$', + node, } // scaffold the root route @@ -1208,28 +1177,15 @@ ${acc.routeTree.map((child) => `${child.variableName}${exportName}: typeof ${get updatedCacheEntry.mtimeMs = stats.mtimeMs } - const rootRouteExports: Array = [] - for (const plugin of this.pluginsWithTransform) { - const exportName = plugin.transformPlugin.exportName - // TODO we need to parse instead of just string match - // otherwise a commented out export will still be detected - if (rootNodeFile.fileContent.includes(`export const ${exportName}`)) { - rootRouteExports.push(exportName) - } - } - - updatedCacheEntry.exports = rootRouteExports - node.exports = rootRouteExports this.routeNodeShadowCache.set(node.fullPath, updatedCacheEntry) + } - const shouldWriteTree = !deepEqual( - result.cacheEntry?.exports, - rootRouteExports, - ) - return shouldWriteTree + public async getCrawlingResult(): Promise { + await this.runPromise + return this.crawlingResult } - private handleNode(node: RouteNode, acc: HandleNodeAccumulator) { + private static handleNode(node: RouteNode, acc: HandleNodeAccumulator) { // Do not remove this as we need to set the lastIndex to 0 as it // is necessary to reset the regex's index when using the global flag // otherwise it might not match the next time it's used diff --git a/packages/router-generator/src/index.ts b/packages/router-generator/src/index.ts index 58ddef589c6..835d8f5301d 100644 --- a/packages/router-generator/src/index.ts +++ b/packages/router-generator/src/index.ts @@ -9,11 +9,7 @@ export type { Config, BaseConfig } from './config' export { Generator } from './generator' export type { FileEventType, FileEvent, GeneratorEvent } from './generator' -export type { - GeneratorPluginBase, - GeneratorPlugin, - GeneratorPluginWithTransform, -} from './plugin/types' +export type { GeneratorPlugin } from './plugin/types' export { capitalize, @@ -31,7 +27,6 @@ export { format, removeExt, checkRouteFullPathUniqueness, - hasChildWithExport, } from './utils' export type { @@ -41,6 +36,7 @@ export type { GetRoutesByFileMapResultValue, ImportDeclaration, ImportSpecifier, + HandleNodeAccumulator, } from './types' export { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes' @@ -54,5 +50,4 @@ export type { TransformImportsConfig, TransformContext, TransformOptions, - TransformPlugin, } from './transform/types' diff --git a/packages/router-generator/src/plugin/default-generator-plugin.ts b/packages/router-generator/src/plugin/default-generator-plugin.ts deleted file mode 100644 index 6ede2177ab1..00000000000 --- a/packages/router-generator/src/plugin/default-generator-plugin.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { defaultTransformPlugin } from '../transform/default-transform-plugin' -import { - checkRouteFullPathUniqueness, - isRouteNodeValidForAugmentation, -} from '../utils' -import type { ImportDeclaration } from '../types' -import type { GeneratorPluginWithTransform } from './types' - -const EXPORT_NAME = 'Route' -export function defaultGeneratorPlugin(): GeneratorPluginWithTransform { - return { - name: 'default', - transformPlugin: defaultTransformPlugin, - imports: (opts) => { - const imports: Array = [] - if (opts.acc.routeNodes.some((n) => n.isVirtual)) { - imports.push({ - specifiers: [{ imported: 'createFileRoute' }], - source: opts.generator.targetTemplate.fullPkg, - }) - } - if (opts.generator.config.verboseFileRoutes === false) { - const typeImport: ImportDeclaration = { - specifiers: [], - source: opts.generator.targetTemplate.fullPkg, - importKind: 'type', - } - if ( - opts.sortedRouteNodes.some( - (d) => - isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy', - ) - ) { - typeImport.specifiers.push({ imported: 'CreateFileRoute' }) - } - if ( - opts.sortedRouteNodes.some( - (node) => - opts.acc.routePiecesByPath[node.routePath!]?.lazy && - isRouteNodeValidForAugmentation(node), - ) - ) { - typeImport.specifiers.push({ imported: 'CreateLazyFileRoute' }) - } - - if (typeImport.specifiers.length > 0) { - typeImport.specifiers.push({ imported: 'FileRoutesByPath' }) - imports.push(typeImport) - } - } - const hasMatchingRouteFiles = opts.acc.routeNodes.length > 0 - if (hasMatchingRouteFiles) { - // needs a virtual root route - if (!opts.rootRouteNode.exports?.includes(EXPORT_NAME)) { - imports.push({ - specifiers: [{ imported: 'createRootRoute' }], - source: opts.generator.targetTemplate.fullPkg, - }) - } - } - return imports - }, - moduleAugmentation: ({ generator }) => ({ - module: generator.targetTemplate.fullPkg, - interfaceName: 'FileRoutesByPath', - }), - onRouteTreesChanged: ({ routeTrees, generator }) => { - const routeTree = routeTrees.find( - (tree) => tree.exportName === EXPORT_NAME, - ) - if (!routeTree) { - throw new Error( - 'No route tree found with export name "Route". Please ensure your routes are correctly defined.', - ) - } - checkRouteFullPathUniqueness( - routeTree.sortedRouteNodes.filter( - (d) => - d.children === undefined && - 'lazy' !== d._fsRouteType && - d.exports?.includes(EXPORT_NAME), - ), - generator.config, - ) - }, - routeModuleAugmentation: ({ routeNode }) => { - if (routeNode._fsRouteType === 'lazy') { - return `const createLazyFileRoute: CreateLazyFileRoute` - } else { - return `const createFileRoute: CreateFileRoute<'${routeNode.routePath}', - FileRoutesByPath['${routeNode.routePath}']['parentRoute'], - FileRoutesByPath['${routeNode.routePath}']['id'], - FileRoutesByPath['${routeNode.routePath}']['path'], - FileRoutesByPath['${routeNode.routePath}']['fullPath'] - > - ` - } - }, - createRootRouteCode: () => `createRootRoute()`, - createVirtualRouteCode: ({ node }) => - `createFileRoute('${node.routePath}')()`, - config: ({ sortedRouteNodes }) => { - const hasMatchingRouteFiles = sortedRouteNodes.length > 0 - return { - virtualRootRoute: hasMatchingRouteFiles, - } - }, - } -} diff --git a/packages/router-generator/src/plugin/types.ts b/packages/router-generator/src/plugin/types.ts index 6c04a8db105..57d210b40ba 100644 --- a/packages/router-generator/src/plugin/types.ts +++ b/packages/router-generator/src/plugin/types.ts @@ -1,51 +1,18 @@ -import type { TransformPlugin } from '../transform/types' -import type { - HandleNodeAccumulator, - ImportDeclaration, - RouteNode, -} from '../types' - +import type { HandleNodeAccumulator, RouteNode } from '../types' import type { Generator } from '../generator' -export type GeneratorPlugin = GeneratorPluginBase | GeneratorPluginWithTransform - -export interface GeneratorPluginBase { +export interface GeneratorPlugin { name: string - onRouteTreesChanged?: (opts: { - routeTrees: Array<{ - sortedRouteNodes: Array - acc: HandleNodeAccumulator - exportName: string - }> + init?: (opts: { generator: Generator }) => void + onRouteTreeChanged?: (opts: { + routeTree: Array + routeNodes: Array rootRouteNode: RouteNode - generator: Generator + acc: HandleNodeAccumulator }) => void -} -export interface GeneratorPluginWithTransform extends GeneratorPluginBase { - transformPlugin: TransformPlugin - moduleAugmentation: (opts: { generator: Generator }) => { - module: string - interfaceName: string - } - imports: (opts: { - rootRouteNode: RouteNode - sortedRouteNodes: Array - acc: HandleNodeAccumulator - generator: Generator - }) => Array - routeModuleAugmentation: (opts: { - routeNode: RouteNode - }) => string | undefined - createRootRouteCode: () => string - createVirtualRouteCode: (opts: { node: RouteNode }) => string - config: (opts: { - generator: Generator - rootRouteNode: RouteNode - sortedRouteNodes: Array - }) => { - virtualRootRoute?: boolean - } + afterTransform?: (opts: { + node: RouteNode + prevNode: RouteNode | undefined + }) => void } - -export {} diff --git a/packages/router-generator/src/transform/default-transform-plugin.ts b/packages/router-generator/src/transform/default-transform-plugin.ts deleted file mode 100644 index 5848ebe6ec5..00000000000 --- a/packages/router-generator/src/transform/default-transform-plugin.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { types } from 'recast' -import { ensureStringArgument } from './utils' -import type { TransformImportsConfig, TransformPlugin } from './types' - -const b = types.builders - -const EXPORT_NAME = 'Route' -export const defaultTransformPlugin: TransformPlugin = { - name: 'default-transform', - exportName: EXPORT_NAME, - imports: (ctx) => { - const imports: TransformImportsConfig = {} - const targetModule = `@tanstack/${ctx.target}-router` - if (ctx.verboseFileRoutes === false) { - imports.banned = [ - { - source: targetModule, - specifiers: [ - { imported: 'createLazyFileRoute' }, - { imported: 'createFileRoute' }, - ], - }, - ] - } else { - if (ctx.lazy) { - imports.required = [ - { - source: targetModule, - specifiers: [{ imported: 'createLazyFileRoute' }], - }, - ] - imports.banned = [ - { - source: targetModule, - specifiers: [{ imported: 'createFileRoute' }], - }, - ] - } else { - imports.required = [ - { - source: targetModule, - specifiers: [{ imported: 'createFileRoute' }], - }, - ] - imports.banned = [ - { - source: targetModule, - specifiers: [{ imported: 'createLazyFileRoute' }], - }, - ] - } - } - return imports - }, - onExportFound: ({ decl, ctx }) => { - let appliedChanges = false - if (decl.init?.type === 'CallExpression') { - const callExpression = decl.init - let identifier: types.namedTypes.Identifier | undefined - // `const Route = createFileRoute({ ... })` - if (callExpression.callee.type === 'Identifier') { - identifier = callExpression.callee - if (ctx.verboseFileRoutes) { - // we need to add the string literal via another CallExpression - callExpression.callee = b.callExpression(identifier, [ - b.stringLiteral(ctx.routeId), - ]) - appliedChanges = true - } - } - // `const Route = createFileRoute('/path')({ ... })` - else if ( - callExpression.callee.type === 'CallExpression' && - callExpression.callee.callee.type === 'Identifier' - ) { - identifier = callExpression.callee.callee - if (!ctx.verboseFileRoutes) { - // we need to remove the route id - callExpression.callee = identifier - appliedChanges = true - } else { - // check if the route id is correct - appliedChanges = ensureStringArgument( - callExpression.callee, - ctx.routeId, - ctx.preferredQuote, - ) - } - } - if (identifier === undefined) { - throw new Error( - `expected identifier to be present in ${ctx.routeId} for export ${EXPORT_NAME}`, - ) - } - if (identifier.name === 'createFileRoute' && ctx.lazy) { - identifier.name = 'createLazyFileRoute' - appliedChanges = true - } else if (identifier.name === 'createLazyFileRoute' && !ctx.lazy) { - identifier.name = 'createFileRoute' - appliedChanges = true - } - } - - return appliedChanges - }, -} diff --git a/packages/router-generator/src/transform/transform.ts b/packages/router-generator/src/transform/transform.ts index 162acfb2817..7b3fddb9f21 100644 --- a/packages/router-generator/src/transform/transform.ts +++ b/packages/router-generator/src/transform/transform.ts @@ -2,24 +2,20 @@ import { parseAst } from '@tanstack/router-utils' import { parse, print, types, visit } from 'recast' import { SourceMapConsumer } from 'source-map' import { mergeImportDeclarations } from '../utils' +import { ensureStringArgument } from './utils' import type { ImportDeclaration } from '../types' import type { RawSourceMap } from 'source-map' -import type { - TransformOptions, - TransformPlugin, - TransformResult, -} from './types' +import type { TransformOptions, TransformResult } from './types' const b = types.builders export async function transform({ ctx, source, - plugins, + node, }: TransformOptions): Promise { let appliedChanges = false as boolean let ast: types.namedTypes.File - const foundExports: Array = [] try { ast = parse(source, { sourceFileName: 'output.ts', @@ -44,41 +40,77 @@ export async function transform({ const preferredQuote = detectPreferredQuoteStyle(ast) - const registeredExports = new Map() - - for (const plugin of plugins ?? []) { - const exportName = plugin.exportName - if (registeredExports.has(exportName)) { + let routeExportHandled = false as boolean + function onExportFound(decl: types.namedTypes.VariableDeclarator) { + if (decl.init?.type === 'CallExpression') { + const callExpression = decl.init + const firstArgument = callExpression.arguments[0] + if (firstArgument) { + if (firstArgument.type === 'ObjectExpression') { + const staticProperties = firstArgument.properties.flatMap((p) => { + if (p.type === 'ObjectProperty' && p.key.type === 'Identifier') { + return p.key.name + } + return [] + }) + node.createFileRouteProps = new Set(staticProperties) + } + } + let identifier: types.namedTypes.Identifier | undefined + // `const Route = createFileRoute({ ... })` + if (callExpression.callee.type === 'Identifier') { + identifier = callExpression.callee + if (ctx.verboseFileRoutes) { + // we need to add the string literal via another CallExpression + callExpression.callee = b.callExpression(identifier, [ + b.stringLiteral(ctx.routeId), + ]) + appliedChanges = true + } + } + // `const Route = createFileRoute('/path')({ ... })` + else if ( + callExpression.callee.type === 'CallExpression' && + callExpression.callee.callee.type === 'Identifier' + ) { + identifier = callExpression.callee.callee + if (!ctx.verboseFileRoutes) { + // we need to remove the route id + callExpression.callee = identifier + appliedChanges = true + } else { + // check if the route id is correct + appliedChanges = ensureStringArgument( + callExpression.callee, + ctx.routeId, + ctx.preferredQuote, + ) + } + } + if (identifier === undefined) { + throw new Error( + `expected identifier to be present in ${ctx.routeId} for export "Route"`, + ) + } + if (identifier.name === 'createFileRoute' && ctx.lazy) { + identifier.name = 'createLazyFileRoute' + appliedChanges = true + } else if (identifier.name === 'createLazyFileRoute' && !ctx.lazy) { + identifier.name = 'createFileRoute' + appliedChanges = true + } + } else { throw new Error( - `Export ${exportName} is already registered by plugin ${registeredExports.get(exportName)?.name}`, + `expected "Route" export to be initialized by a CallExpression`, ) } - registeredExports.set(exportName, plugin) - } - - function onExportFound( - decl: types.namedTypes.VariableDeclarator, - exportName: string, - plugin: TransformPlugin, - ) { - const pluginAppliedChanges = plugin.onExportFound({ - decl, - ctx: { ...ctx, preferredQuote }, - }) - if (pluginAppliedChanges) { - appliedChanges = true - } - - // export is handled, remove it from the registered exports - registeredExports.delete(exportName) - // store the export so we can later return it once the file is transformed - foundExports.push(exportName) + routeExportHandled = true } const program: types.namedTypes.Program = ast.program - // first pass: find registered exports + // first pass: find Route export for (const n of program.body) { - if (registeredExports.size > 0 && n.type === 'ExportNamedDeclaration') { + if (n.type === 'ExportNamedDeclaration') { // direct export of a variable declaration, e.g. `export const Route = createFileRoute('/path')` if (n.declaration?.type === 'VariableDeclaration') { const decl = n.declaration.declarations[0] @@ -87,9 +119,8 @@ export async function transform({ decl.type === 'VariableDeclarator' && decl.id.type === 'Identifier' ) { - const plugin = registeredExports.get(decl.id.name) - if (plugin) { - onExportFound(decl, decl.id.name, plugin) + if (decl.id.name === 'Route') { + onExportFound(decl) } } } @@ -97,8 +128,7 @@ export async function transform({ else if (n.declaration === null && n.specifiers) { for (const spec of n.specifiers) { if (typeof spec.exported.name === 'string') { - const plugin = registeredExports.get(spec.exported.name) - if (plugin) { + if (spec.exported.name === 'Route') { const variableName = spec.local?.name || spec.exported.name // find the matching variable declaration by iterating over the top-level declarations for (const decl of program.body) { @@ -112,7 +142,7 @@ export async function transform({ variable.id.type === 'Identifier' && variable.id.name === variableName ) { - onExportFound(variable, spec.exported.name, plugin) + onExportFound(variable) break } } @@ -122,6 +152,15 @@ export async function transform({ } } } + if (routeExportHandled) { + break + } + } + + if (!routeExportHandled) { + return { + result: 'no-route-export', + } } const imports: { @@ -132,16 +171,44 @@ export async function transform({ banned: [], } - for (const plugin of plugins ?? []) { - const exportName = plugin.exportName - if (foundExports.includes(exportName)) { - const pluginImports = plugin.imports(ctx) - if (pluginImports.required) { - imports.required.push(...pluginImports.required) - } - if (pluginImports.banned) { - imports.banned.push(...pluginImports.banned) - } + const targetModule = `@tanstack/${ctx.target}-router` + if (ctx.verboseFileRoutes === false) { + imports.banned = [ + { + source: targetModule, + specifiers: [ + { imported: 'createLazyFileRoute' }, + { imported: 'createFileRoute' }, + ], + }, + ] + } else { + if (ctx.lazy) { + imports.required = [ + { + source: targetModule, + specifiers: [{ imported: 'createLazyFileRoute' }], + }, + ] + imports.banned = [ + { + source: targetModule, + specifiers: [{ imported: 'createFileRoute' }], + }, + ] + } else { + imports.required = [ + { + source: targetModule, + specifiers: [{ imported: 'createFileRoute' }], + }, + ] + imports.banned = [ + { + source: targetModule, + specifiers: [{ imported: 'createLazyFileRoute' }], + }, + ] } } @@ -289,7 +356,6 @@ export async function transform({ if (!appliedChanges) { return { - exports: foundExports, result: 'not-modified', } } @@ -310,7 +376,6 @@ export async function transform({ } return { result: 'modified', - exports: foundExports, output: transformedCode, } } diff --git a/packages/router-generator/src/transform/types.ts b/packages/router-generator/src/transform/types.ts index 102accc2c08..f8b75f6003d 100644 --- a/packages/router-generator/src/transform/types.ts +++ b/packages/router-generator/src/transform/types.ts @@ -1,22 +1,22 @@ -import type { ImportDeclaration } from '../types' -import type { types } from 'recast' +import type { ImportDeclaration, RouteNode } from '../types' import type { Config } from '../config' export interface TransformOptions { source: string ctx: TransformContext - plugins?: Array + node: RouteNode } export type TransformResult = + | { + result: 'no-route-export' + } | { result: 'not-modified' - exports: Array } | { result: 'modified' output: string - exports: Array } | { result: 'error' @@ -27,19 +27,6 @@ export interface TransformImportsConfig { banned?: Array required?: Array } -export interface TransformPlugin { - name: string - exportName: string - imports: (ctx: TransformContext) => TransformImportsConfig - /** - * Called after the export is found in the AST. - * @returns true if the plugin modified the AST, false otherwise - */ - onExportFound: (opts: { - decl: types.namedTypes.VariableDeclarator - ctx: TransformContext - }) => boolean -} export interface TransformContext { target: Config['target'] diff --git a/packages/router-generator/src/types.ts b/packages/router-generator/src/types.ts index b64674066d2..e29832f2c56 100644 --- a/packages/router-generator/src/types.ts +++ b/packages/router-generator/src/types.ts @@ -12,7 +12,7 @@ export type RouteNode = { isVirtual?: boolean children?: Array parent?: RouteNode - exports?: Array + createFileRouteProps?: Set } export interface GetRouteNodesResult { diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 58971c5cca4..3754f3288b4 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -320,11 +320,10 @@ export function hasParentRoute( */ export const getResolvedRouteNodeVariableName = ( routeNode: RouteNode, - variableNameSuffix: string, ): string => { return routeNode.children?.length - ? `${routeNode.variableName}${variableNameSuffix}WithChildren` - : `${routeNode.variableName}${variableNameSuffix}` + ? `${routeNode.variableName}RouteWithChildren` + : `${routeNode.variableName}Route` } /** @@ -463,65 +462,59 @@ Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDi export function buildRouteTreeConfig( nodes: Array, - exportName: string, disableTypes: boolean, depth = 1, ): Array { - const children = nodes - .filter((n) => n.exports?.includes(exportName)) - .map((node) => { - if (node._fsRouteType === '__root') { - return - } + const children = nodes.map((node) => { + if (node._fsRouteType === '__root') { + return + } - if (node._fsRouteType === 'pathless_layout' && !node.children?.length) { - return - } + if (node._fsRouteType === 'pathless_layout' && !node.children?.length) { + return + } - const route = `${node.variableName}` + const route = `${node.variableName}` - if (node.children?.length) { - const childConfigs = buildRouteTreeConfig( - node.children, - exportName, - disableTypes, - depth + 1, - ) + if (node.children?.length) { + const childConfigs = buildRouteTreeConfig( + node.children, + disableTypes, + depth + 1, + ) - const childrenDeclaration = disableTypes - ? '' - : `interface ${route}${exportName}Children { + const childrenDeclaration = disableTypes + ? '' + : `interface ${route}RouteChildren { ${node.children - .filter((n) => n.exports?.includes(exportName)) .map( (child) => - `${child.variableName}${exportName}: typeof ${getResolvedRouteNodeVariableName(child, exportName)}`, + `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`, ) .join(',')} }` - const children = `const ${route}${exportName}Children${disableTypes ? '' : `: ${route}${exportName}Children`} = { + const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = { ${node.children - .filter((n) => n.exports?.includes(exportName)) .map( (child) => - `${child.variableName}${exportName}: ${getResolvedRouteNodeVariableName(child, exportName)}`, + `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`, ) .join(',')} }` - const routeWithChildren = `const ${route}${exportName}WithChildren = ${route}${exportName}._addFileChildren(${route}${exportName}Children)` + const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)` - return [ - childConfigs.join('\n'), - childrenDeclaration, - children, - routeWithChildren, - ].join('\n\n') - } + return [ + childConfigs.join('\n'), + childrenDeclaration, + children, + routeWithChildren, + ].join('\n\n') + } - return undefined - }) + return undefined + }) return children.filter((x) => x !== undefined) } @@ -570,55 +563,33 @@ export function mergeImportDeclarations( return Object.values(merged) } -export function hasChildWithExport( - node: RouteNode, - exportName: string, -): boolean { - return ( - node.children?.some((child) => hasChildWithExport(child, exportName)) ?? - false - ) -} - -export const findParent = ( - node: RouteNode | undefined, - exportName: string, -): string => { +export const findParent = (node: RouteNode | undefined): string => { if (!node) { - return `root${exportName}Import` + return `rootRouteImport` } if (node.parent) { - if (node.parent.exports?.includes(exportName)) { - if (node.isVirtualParentRequired) { - return `${node.parent.variableName}${exportName}` - } else { - return `${node.parent.variableName}${exportName}` - } + if (node.isVirtualParentRequired) { + return `${node.parent.variableName}Route` + } else { + return `${node.parent.variableName}Route` } } - return findParent(node.parent, exportName) + return findParent(node.parent) } export function buildFileRoutesByPathInterface(opts: { routeNodes: Array module: string interfaceName: string - exportName: string }): string { return `declare module '${opts.module}' { interface ${opts.interfaceName} { ${opts.routeNodes .map((routeNode) => { const filePathId = routeNode.routePath - let preloaderRoute = '' - - if (routeNode.exports?.includes(opts.exportName)) { - preloaderRoute = `typeof ${routeNode.variableName}${opts.exportName}Import` - } else { - preloaderRoute = 'unknown' - } + const preloaderRoute = `typeof ${routeNode.variableName}RouteImport` - const parent = findParent(routeNode, opts.exportName) + const parent = findParent(routeNode) return `'${filePathId}': { id: '${filePathId}' @@ -632,3 +603,47 @@ export function buildFileRoutesByPathInterface(opts: { } }` } + +export function getImportPath( + node: RouteNode, + config: Config, + generatedRouteTreePath: string, +): string { + return replaceBackslash( + removeExt( + path.relative( + path.dirname(generatedRouteTreePath), + path.resolve(config.routesDirectory, node.filePath), + ), + config.addExtensions, + ), + ) +} + +export function getImportForRouteNode( + node: RouteNode, + config: Config, + generatedRouteTreePath: string, + root: string, +): ImportDeclaration { + let source = '' + if (config.importRoutesUsingAbsolutePaths) { + source = replaceBackslash( + removeExt( + path.resolve(root, config.routesDirectory, node.filePath), + config.addExtensions, + ), + ) + } else { + source = `./${getImportPath(node, config, generatedRouteTreePath)}` + } + return { + source, + specifiers: [ + { + imported: 'Route', + local: `${node.variableName}RouteImport`, + }, + ], + } satisfies ImportDeclaration +} diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index dbd5e495402..1c4d921f145 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -270,4 +270,56 @@ describe('generator works', async () => { await postprocess(folderName) }, ) + + it('should create directory for routeTree if it does not exist', async () => { + const folderName = 'only-root' + const folderRoot = makeFolderDir(folderName) + let pathCreated = false + + const config = await setupConfig(folderName) + + rewriteConfigByFolderName(folderName, config) + + await preprocess(folderName) + config.generatedRouteTree = join( + folderRoot, + 'generated', + '/routeTree.gen.ts', + ) + const generator = new Generator({ config, root: folderRoot }) + const error = shouldThrow(folderName) + if (error) { + try { + await generator.run() + } catch (e) { + expect(e).toBeInstanceOf(Error) + expect((e as Error).message.startsWith(error)).toBeTruthy() + } + } else { + await generator.run() + + const generatedRouteTree = await getRouteTreeFileText(config) + + await expect(generatedRouteTree).toMatchFileSnapshot( + join( + 'generator', + folderName, + `routeTree.generated.snapshot.${config.disableTypes ? 'js' : 'ts'}`, + ), + ) + + pathCreated = await fs.access(dirname(config.generatedRouteTree)).then( + () => true, + () => false, + ) + + await expect(pathCreated).toBe(true) + } + + await postprocess(folderName) + + if (pathCreated) { + await fs.rm(dirname(config.generatedRouteTree), { recursive: true }) + } + }) }) diff --git a/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts b/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts index 5fce56585f6..e80d1e7e73c 100644 --- a/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts @@ -8,12 +8,12 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createFileRoute, createRootRoute } from '@tanstack/react-router' +import { createFileRoute } from '@tanstack/react-router' +import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiBarRouteImport } from './routes/api/bar' -const rootRouteImport = createRootRoute() const FooLazyRouteImport = createFileRoute('/foo')() const FooLazyRoute = FooLazyRouteImport.update({ diff --git a/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts b/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts index d67fc9b91af..2ccc3a0a381 100644 --- a/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts @@ -8,15 +8,12 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createRootRoute } from '@tanstack/react-router' - +import { Route as rootRouteImport } from './routes/__root' import { Route as ScriptDotjsRouteImport } from './routes/script[.]js' import { Route as NestedDotjsRouteImport } from './routes/nested[.]js' import { Route as NestedDotjsScriptDotjsRouteImport } from './routes/nested[.]js.script[.]js' import { Route as NestedDotjsDoubleDotextDotjsRouteImport } from './routes/nested[.]js.double[.]ext[.]js' -const rootRouteImport = createRootRoute() - const ScriptDotjsRoute = ScriptDotjsRouteImport.update({ id: '/script.js', path: '/script.js', diff --git a/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.snapshot.ts b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.snapshot.ts index eeb9f088e6d..6cd0f9c68ec 100644 --- a/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.snapshot.ts @@ -100,18 +100,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/(test)/foo': { - id: '/(test)/foo' - path: '/foo' - fullPath: '/foo' - preLoaderRoute: typeof testFooRouteImport - parentRoute: typeof rootRouteImport - } - '/(test)/initiallyEmpty': { - id: '/(test)/initiallyEmpty' - path: '/initiallyEmpty' - fullPath: '/initiallyEmpty' - preLoaderRoute: typeof testInitiallyEmptyRouteImport + '/(test)/bar': { + id: '/(test)/bar' + path: '/bar' + fullPath: '/bar' + preLoaderRoute: typeof testBarLazyRouteImport parentRoute: typeof rootRouteImport } '/(test)/initiallyLazy': { @@ -121,18 +114,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testInitiallyLazyRouteImport parentRoute: typeof rootRouteImport } - '/(test)/bar': { - id: '/(test)/bar' - path: '/bar' - fullPath: '/bar' - preLoaderRoute: typeof testBarLazyRouteImport - parentRoute: typeof rootRouteImport - } '/(test)/initiallyEmpty': { id: '/(test)/initiallyEmpty' path: '/initiallyEmpty' fullPath: '/initiallyEmpty' - preLoaderRoute: typeof testInitiallyEmptyLazyRouteImport + preLoaderRoute: typeof testInitiallyEmptyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/foo': { + id: '/(test)/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof testFooRouteImport parentRoute: typeof rootRouteImport } '/(test)/foo/bar': { @@ -142,13 +135,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testFooBarRouteImport parentRoute: typeof testFooRoute } - '/(test)/bar': { - id: '/(test)/bar' - path: '/bar' - fullPath: '/bar' - preLoaderRoute: typeof testBarLazyRouteImport - parentRoute: typeof rootRouteImport - } } } diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.snapshot.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.snapshot.ts index af8164964a5..cfd2094de3d 100644 --- a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.snapshot.ts @@ -142,11 +142,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } '/blog': { @@ -156,18 +156,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogRouteRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/blog/$slug': { - id: '/blog/$slug' - path: '/$slug' - fullPath: '/blog/$slug' - preLoaderRoute: typeof BlogSlugRouteImport + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog/': { + id: '/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof BlogIndexRouteImport parentRoute: typeof BlogRouteRoute } '/blog_/stats': { @@ -177,18 +184,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogStatsRouteImport parentRoute: typeof rootRouteImport } - '/blog/': { - id: '/blog/' - path: '/' - fullPath: '/blog/' - preLoaderRoute: typeof BlogIndexRouteImport + '/blog/$slug': { + id: '/blog/$slug' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugRouteImport parentRoute: typeof BlogRouteRoute } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdIndexRouteImport parentRoute: typeof PostsRouteRoute } '/posts/$postId/deep': { @@ -198,13 +205,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof PostsRouteRoute } - '/posts/$postId/': { - id: '/posts/$postId/' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsPostIdIndexRouteImport - parentRoute: typeof PostsRouteRoute - } } } diff --git a/packages/router-generator/tests/generator/only-root/routeTree.generated.snapshot.ts b/packages/router-generator/tests/generator/only-root/routeTree.generated.snapshot.ts new file mode 100644 index 00000000000..7766df98323 --- /dev/null +++ b/packages/router-generator/tests/generator/only-root/routeTree.generated.snapshot.ts @@ -0,0 +1,35 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './../routes/__root' + +export interface FileRoutesByFullPath {} +export interface FileRoutesByTo {} +export interface FileRoutesById { + __root__: typeof rootRouteImport +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: never + fileRoutesByTo: FileRoutesByTo + to: never + id: '__root__' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren {} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath {} +} + +const rootRouteChildren: RootRouteChildren = {} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts b/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts index a5bc003d1f0..bf4addbd35f 100644 --- a/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts @@ -8,8 +8,9 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createFileRoute, createRootRoute } from '@tanstack/react-router' +import { createFileRoute } from '@tanstack/react-router' +import { Route as rootRouteImport } from './routes/__root' import { Route as barBarRouteImport } from './routes/(bar)/_bar' import { Route as fooAsdfLayoutRouteImport } from './routes/(foo)/asdf/_layout' import { Route as barBarHelloRouteImport } from './routes/(bar)/_bar.hello' @@ -19,7 +20,6 @@ import { Route as fooAsdfanotherGroupLayoutRouteImport } from './routes/(foo)/as import { Route as fooAsdfbarLayoutAboutRouteImport } from './routes/(foo)/asdf/(bar)/_layout.about' import { Route as fooAsdfanotherGroupLayoutBazRouteImport } from './routes/(foo)/asdf/(another-group)/_layout.baz' -const rootRouteImport = createRootRoute() const barRouteImport = createFileRoute('/(bar)')() const fooAsdfRouteImport = createFileRoute('/(foo)/asdf')() const fooAsdfanotherGroupRouteImport = createFileRoute( diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index 943acbb3b9b..65ca225e541 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-plugin", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -127,8 +127,8 @@ "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "workspace:^", - "vite": ">=5.0.0 || >=6.0.0", - "vite-plugin-solid": "^2.11.2", + "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", + "vite-plugin-solid": "^2.11.8", "webpack": ">=5.92.0" }, "peerDependenciesMeta": { diff --git a/packages/router-plugin/src/core/config.ts b/packages/router-plugin/src/core/config.ts index e2999ebe5c8..d776c7cd5b2 100644 --- a/packages/router-plugin/src/core/config.ts +++ b/packages/router-plugin/src/core/config.ts @@ -3,7 +3,11 @@ import { configSchema as generatorConfigSchema, getConfig as getGeneratorConfig, } from '@tanstack/router-generator' -import type { RegisteredRouter, RouteIds } from '@tanstack/router-core' +import type { + CreateFileRoute, + RegisteredRouter, + RouteIds, +} from '@tanstack/router-core' import type { CodeSplitGroupings } from './constants' export const splitGroupingsSchema = z @@ -68,15 +72,17 @@ export type CodeSplittingOptions = { addHmr?: boolean } -const DELETABLE_NODES = ['ssr'] as const -export const deletableNodesSchema = z.enum(DELETABLE_NODES) const codeSplittingOptionsSchema = z.object({ splitBehavior: z.function().optional(), defaultBehavior: splitGroupingsSchema.optional(), - deleteNodes: z.array(deletableNodesSchema).optional(), + deleteNodes: z.array(z.string()).optional(), addHmr: z.boolean().optional().default(true), }) -export type DeletableNodes = (typeof DELETABLE_NODES)[number] + +type FileRouteKeys = keyof (Parameters< + CreateFileRoute +>[0] & {}) +export type DeletableNodes = FileRouteKeys | (string & {}) export const configSchema = generatorConfigSchema.extend({ enableRouteGeneration: z.boolean().optional(), diff --git a/packages/router-plugin/src/core/route-autoimport-plugin.ts b/packages/router-plugin/src/core/route-autoimport-plugin.ts index dbb60be8d22..75b2b5f562a 100644 --- a/packages/router-plugin/src/core/route-autoimport-plugin.ts +++ b/packages/router-plugin/src/core/route-autoimport-plugin.ts @@ -10,11 +10,18 @@ import type { UnpluginFactory } from 'unplugin' * This plugin adds imports for createFileRoute and createLazyFileRoute to the file route. */ export const unpluginRouteAutoImportFactory: UnpluginFactory< - Partial | undefined + Partial Config)> | undefined > = (options = {}) => { let ROOT: string = process.cwd() - let userConfig = options as Config + let userConfig: Config + function initUserConfig() { + if (typeof options === 'function') { + userConfig = options() + } else { + userConfig = getConfig(options, ROOT) + } + } return { name: 'tanstack-router:autoimport', enforce: 'pre', @@ -91,18 +98,22 @@ export const unpluginRouteAutoImportFactory: UnpluginFactory< vite: { configResolved(config) { ROOT = config.root - userConfig = getConfig(options, ROOT) + initUserConfig() + }, + // this check may only happen after config is resolved, so we use applyToEnvironment (apply is too early) + applyToEnvironment() { + return userConfig.verboseFileRoutes === false }, }, rspack() { ROOT = process.cwd() - userConfig = getConfig(options, ROOT) + initUserConfig() }, webpack() { ROOT = process.cwd() - userConfig = getConfig(options, ROOT) + initUserConfig() }, } } diff --git a/packages/router-plugin/src/core/router-code-splitter-plugin.ts b/packages/router-plugin/src/core/router-code-splitter-plugin.ts index 8b86964912a..a79985d63d0 100644 --- a/packages/router-plugin/src/core/router-code-splitter-plugin.ts +++ b/packages/router-plugin/src/core/router-code-splitter-plugin.ts @@ -61,11 +61,18 @@ plugins: [ const PLUGIN_NAME = 'unplugin:router-code-splitter' export const unpluginRouterCodeSplitterFactory: UnpluginFactory< - Partial | undefined + Partial Config)> | undefined > = (options = {}, { framework }) => { let ROOT: string = process.cwd() - let userConfig = options as Config + let userConfig: Config + function initUserConfig() { + if (typeof options === 'function') { + userConfig = options() + } else { + userConfig = getConfig(options, ROOT) + } + } const isProduction = process.env.NODE_ENV === 'production' const getGlobalCodeSplitGroupings = () => { @@ -125,7 +132,8 @@ export const unpluginRouterCodeSplitterFactory: UnpluginFactory< filename: id, id, deleteNodes: new Set(userConfig.codeSplittingOptions?.deleteNodes), - addHmr: (options.codeSplittingOptions?.addHmr ?? true) && !isProduction, + addHmr: + (userConfig.codeSplittingOptions?.addHmr ?? true) && !isProduction, }) if (debug) { @@ -219,7 +227,7 @@ export const unpluginRouterCodeSplitterFactory: UnpluginFactory< vite: { configResolved(config) { ROOT = config.root - userConfig = getConfig(options, ROOT) + initUserConfig() }, applyToEnvironment(environment) { if (userConfig.plugin?.vite?.environmentName) { @@ -231,12 +239,12 @@ export const unpluginRouterCodeSplitterFactory: UnpluginFactory< rspack() { ROOT = process.cwd() - userConfig = getConfig(options, ROOT) + initUserConfig() }, webpack(compiler) { ROOT = process.cwd() - userConfig = getConfig(options, ROOT) + initUserConfig() if (compiler.options.mode === 'production') { compiler.hooks.done.tap(PLUGIN_NAME, () => { diff --git a/packages/router-plugin/src/core/router-generator-plugin.ts b/packages/router-plugin/src/core/router-generator-plugin.ts index b283348d576..055a1bc3c5b 100644 --- a/packages/router-plugin/src/core/router-generator-plugin.ts +++ b/packages/router-plugin/src/core/router-generator-plugin.ts @@ -10,10 +10,10 @@ import type { Config } from './config' const PLUGIN_NAME = 'unplugin:router-generator' export const unpluginRouterGeneratorFactory: UnpluginFactory< - Partial | undefined + Partial Config)> | undefined > = (options = {}) => { - const ROOT: string = process.cwd() - let userConfig = options as Config + let ROOT: string = process.cwd() + let userConfig: Config let generator: Generator const routeGenerationDisabled = () => @@ -24,8 +24,15 @@ export const unpluginRouterGeneratorFactory: UnpluginFactory< : join(ROOT, userConfig.routesDirectory) } - const initConfigAndGenerator = () => { - userConfig = getConfig(options, ROOT) + const initConfigAndGenerator = (opts?: { root?: string }) => { + if (opts?.root) { + ROOT = opts.root + } + if (typeof options === 'function') { + userConfig = options() + } else { + userConfig = getConfig(options, ROOT) + } generator = new Generator({ config: userConfig, root: ROOT, @@ -66,23 +73,11 @@ export const unpluginRouterGeneratorFactory: UnpluginFactory< event, }) }, - async buildStart() { - await generate() - }, vite: { - configResolved() { - initConfigAndGenerator() - }, - applyToEnvironment(environment) { - if (userConfig.plugin?.vite?.environmentName) { - return userConfig.plugin.vite.environmentName === environment.name - } - return true - }, - async buildStart() { + async configResolved(config) { + initConfigAndGenerator({ root: config.root }) await generate() }, - sharedDuringBuild: true, }, rspack(compiler) { initConfigAndGenerator() diff --git a/packages/router-plugin/src/esbuild.ts b/packages/router-plugin/src/esbuild.ts index c29c0e4d85a..c2caaf2cefa 100644 --- a/packages/router-plugin/src/esbuild.ts +++ b/packages/router-plugin/src/esbuild.ts @@ -5,7 +5,7 @@ import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-p import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' -import type { Config } from './core/config' +import type { CodeSplittingOptions, Config } from './core/config' /** * @example @@ -54,4 +54,4 @@ export { tanstackRouter, } -export type { Config } +export type { Config, CodeSplittingOptions } diff --git a/packages/router-plugin/src/index.ts b/packages/router-plugin/src/index.ts index 5e6eef7d189..a89dbf5656a 100644 --- a/packages/router-plugin/src/index.ts +++ b/packages/router-plugin/src/index.ts @@ -1,7 +1,13 @@ export { configSchema, getConfig } from './core/config' export { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' export { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' -export type { Config, ConfigInput, ConfigOutput } from './core/config' +export type { + Config, + ConfigInput, + ConfigOutput, + CodeSplittingOptions, + DeletableNodes, +} from './core/config' export { tsrSplit, splitRouteIdentNodes, diff --git a/packages/router-plugin/src/rspack.ts b/packages/router-plugin/src/rspack.ts index 4f82e245600..9eef9f2221c 100644 --- a/packages/router-plugin/src/rspack.ts +++ b/packages/router-plugin/src/rspack.ts @@ -4,7 +4,7 @@ import { configSchema } from './core/config' import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' -import type { Config } from './core/config' +import type { CodeSplittingOptions, Config } from './core/config' /** * @example @@ -65,4 +65,4 @@ export { TanStackRouterCodeSplitterRspack, tanstackRouter, } -export type { Config } +export type { Config, CodeSplittingOptions } diff --git a/packages/router-plugin/src/vite.ts b/packages/router-plugin/src/vite.ts index 155f8ad159c..75b66f31aa2 100644 --- a/packages/router-plugin/src/vite.ts +++ b/packages/router-plugin/src/vite.ts @@ -5,7 +5,7 @@ import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-p import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' import { unpluginRouteAutoImportFactory } from './core/route-autoimport-plugin' -import type { Config } from './core/config' +import type { CodeSplittingOptions, Config, getConfig } from './core/config' const tanstackRouterAutoImport = createVitePlugin( unpluginRouteAutoImportFactory, @@ -54,6 +54,7 @@ const TanStackRouterVite = tanstackRouter export default tanstackRouter export { configSchema, + getConfig, tanstackRouterAutoImport, tanStackRouterCodeSplitter, tanstackRouterGenerator, @@ -61,4 +62,4 @@ export { tanstackRouter, } -export type { Config } +export type { Config, CodeSplittingOptions } diff --git a/packages/router-plugin/src/webpack.ts b/packages/router-plugin/src/webpack.ts index 3b44448aa5d..a718e6056af 100644 --- a/packages/router-plugin/src/webpack.ts +++ b/packages/router-plugin/src/webpack.ts @@ -4,7 +4,7 @@ import { configSchema } from './core/config' import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' import { unpluginRouterComposedFactory } from './core/router-composed-plugin' -import type { Config } from './core/config' +import type { CodeSplittingOptions, Config } from './core/config' /** * @example @@ -54,4 +54,4 @@ export { TanStackRouterCodeSplitterWebpack, tanstackRouter, } -export type { Config } +export type { Config, CodeSplittingOptions } diff --git a/packages/router-ssr-query-core/package.json b/packages/router-ssr-query-core/package.json index 9001f690f73..803b2dcf2f1 100644 --- a/packages/router-ssr-query-core/package.json +++ b/packages/router-ssr-query-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-ssr-query-core", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-utils/package.json b/packages/router-utils/package.json index f1e872dc4ea..f874a9ea446 100644 --- a/packages/router-utils/package.json +++ b/packages/router-utils/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-utils", - "version": "1.131.2", + "version": "1.132.0-alpha.9", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -68,7 +68,9 @@ "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", - "diff": "^8.0.2" + "diff": "^8.0.2", + "fast-glob": "^3.3.3", + "pathe": "^2.0.3" }, "devDependencies": { "@babel/types": "^7.27.6", diff --git a/packages/router-utils/src/ast.ts b/packages/router-utils/src/ast.ts index 829471ad97d..812824a1217 100644 --- a/packages/router-utils/src/ast.ts +++ b/packages/router-utils/src/ast.ts @@ -8,10 +8,8 @@ export type ParseAstOptions = ParserOptions & { code: string } -export function parseAst({ - code, - ...opts -}: ParseAstOptions): ParseResult<_babel_types.File> { +export type ParseAstResult = ParseResult<_babel_types.File> +export function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult { return parse(code, { plugins: ['jsx', 'typescript', 'explicitResourceManagement'], sourceType: 'module', diff --git a/packages/router-utils/src/copy-files-plugin.ts b/packages/router-utils/src/copy-files-plugin.ts new file mode 100644 index 00000000000..13cfd31ec45 --- /dev/null +++ b/packages/router-utils/src/copy-files-plugin.ts @@ -0,0 +1,34 @@ +import { copyFile, mkdir } from 'node:fs/promises' +import { dirname, join } from 'pathe' +import fg from 'fast-glob' +import type { Plugin } from 'vite' + +export function copyFilesPlugin({ + fromDir, + toDir, + pattern = '**', +}: { + pattern?: string | Array + fromDir: string + toDir: string +}): Plugin { + return { + name: 'copy-files', + async writeBundle() { + const entries = await fg(pattern, { cwd: fromDir }) + if (entries.length === 0) { + throw new Error( + `No files found matching pattern "${pattern}" in directory "${fromDir}"`, + ) + } + + for (const entry of entries) { + const srcPath = join(fromDir, entry) + const destPath = join(toDir, entry) + // Ensure the destination directory exists + await mkdir(dirname(destPath), { recursive: true }) + await copyFile(srcPath, destPath) + } + }, + } +} diff --git a/packages/router-utils/src/index.ts b/packages/router-utils/src/index.ts index f238e8c695b..85c3f38590f 100644 --- a/packages/router-utils/src/index.ts +++ b/packages/router-utils/src/index.ts @@ -1,3 +1,5 @@ export { parseAst, generateFromAst } from './ast' -export type { ParseAstOptions, GeneratorResult } from './ast' +export type { ParseAstOptions, ParseAstResult, GeneratorResult } from './ast' export { logDiff } from './logger' + +export { copyFilesPlugin } from './copy-files-plugin' diff --git a/packages/router-vite-plugin/package.json b/packages/router-vite-plugin/package.json index bfb57181962..60310f842b9 100644 --- a/packages/router-vite-plugin/package.json +++ b/packages/router-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-vite-plugin", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/server-functions-plugin/package.json b/packages/server-functions-plugin/package.json index dc5876d26f7..9502c6d66c3 100644 --- a/packages/server-functions-plugin/package.json +++ b/packages/server-functions-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/server-functions-plugin", - "version": "1.131.2", + "version": "1.132.0-alpha.9", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/server-functions-plugin/src/index.ts b/packages/server-functions-plugin/src/index.ts index 8a396252edc..710fe0b2ba2 100644 --- a/packages/server-functions-plugin/src/index.ts +++ b/packages/server-functions-plugin/src/index.ts @@ -8,11 +8,7 @@ import type { ReplacerFn, } from '@tanstack/directive-functions-plugin' -export type CreateRpcFn = ( - functionId: string, - serverBase: string, - splitImportFn?: string, -) => any +export type CreateRpcFn = (functionId: string, splitImportFn?: string) => any export type ServerFnPluginOpts = { /** diff --git a/packages/solid-router-devtools/package.json b/packages/solid-router-devtools/package.json index dab7d89ac1b..3edb8c140f7 100644 --- a/packages/solid-router-devtools/package.json +++ b/packages/solid-router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-router-devtools", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for Solid applications", "author": "Tanner Linsley", "license": "MIT", @@ -66,14 +66,15 @@ "node": ">=12" }, "dependencies": { - "@tanstack/router-devtools-core": "workspace:*" + "@tanstack/router-devtools-core": "workspace:*", + "vite": "^7.1.1" }, "devDependencies": { "solid-js": "^1.9.5", - "vite-plugin-solid": "^2.11.6" + "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { - "solid-js": "^1.9.5", - "@tanstack/solid-router": "workspace:^" + "@tanstack/solid-router": "workspace:^", + "solid-js": "^1.9.5" } } diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json index fdfbf7ff8ec..3868d5f4bd8 100644 --- a/packages/solid-router/package.json +++ b/packages/solid-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-router", - "version": "1.131.50", + "version": "1.132.0-alpha.25", "description": "Modern and scalable routing for Solid applications", "author": "Tanner Linsley", "license": "MIT", @@ -114,7 +114,7 @@ "combinate": "^1.1.11", "eslint-plugin-solid": "^0.14.5", "solid-js": "^1.9.5", - "vite-plugin-solid": "^2.11.2", + "vite-plugin-solid": "^2.11.8", "zod": "^3.23.8" }, "peerDependencies": { diff --git a/packages/solid-router/src/ScriptOnce.tsx b/packages/solid-router/src/ScriptOnce.tsx index 7799f56a8c0..9527c65cbd8 100644 --- a/packages/solid-router/src/ScriptOnce.tsx +++ b/packages/solid-router/src/ScriptOnce.tsx @@ -1,3 +1,5 @@ +import { useRouter } from './useRouter' + export function ScriptOnce({ children, }: { @@ -5,14 +7,15 @@ export function ScriptOnce({ log?: boolean sync?: boolean }) { - if (typeof document !== 'undefined') { + const router = useRouter() + if (!router.isServer) { return null } - return (