diff --git a/.nx/workflows/dynamic-changesets.yaml b/.nx/workflows/dynamic-changesets.yaml index 786111053d..39d858d5b2 100644 --- a/.nx/workflows/dynamic-changesets.yaml +++ b/.nx/workflows/dynamic-changesets.yaml @@ -1,4 +1,4 @@ distribute-on: small-changeset: 4 linux-medium-js medium-changeset: 8 linux-medium-js - large-changeset: 14 linux-medium-js + large-changeset: 16 linux-medium-js diff --git a/.nx/workflows/sandboxing-config.yaml b/.nx/workflows/sandboxing-config.yaml new file mode 100644 index 0000000000..4fa2634bc3 --- /dev/null +++ b/.nx/workflows/sandboxing-config.yaml @@ -0,0 +1,9 @@ +exclude-reads: + - e2e/.gitignore + - '**/src/routeTree.gen.ts' +exclude-writes: + - '**/src/routeTree.gen.ts' +task-exclusions: + - target: test:build + exclude-reads: + - LICENSE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c28d01241b..e9e793926f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,30 +1,85 @@ -# Contributing - -- Clone the repo - - `gh repo clone TanStack/router` -- Ensure `node` is installed - - https://nodejs.org/en/ -- Ensure `pnpm` is installed - - https://pnpm.io/installation - - Why? We use `pnpm` to manage workspace dependencies. It's easily the best monorepo/workspace experience available as of when this was written. -- Install dependencies - - `pnpm install` - - This installs dependencies for all of the packages in the monorepo, even examples! - - Dependencies inside of the packages and examples are automatically linked together as local/dynamic dependencies. -- Install test dependencies - - `pnpm exec playwright install` (required for e2e tests) -- Run the build or dev watcher - - `pnpm build:all` (build all packages) or - - `pnpm build` (cached build with [nx affected](https://nx.dev/nx-api/nx/documents/affected)) or - - `pnpm dev` -- Navigate to an example - - `cd examples/react/basic` -- Run the example - - `pnpm dev` -- Make changes to the code - - If you ran `pnpm dev` the dev watcher will automatically rebuild the code that has changed -- Editing the docs locally and previewing the changes - - The documentations for all the TanStack projects are hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/router` docs. +# Contributing to TanStack Router + +TanStack Router is a community project. Contributions to both TanStack Router and TanStack Start are welcome. + +## Setup + +1. Clone the repository: + + ```sh + gh repo clone TanStack/router + ``` + +2. Install [Node.js](https://nodejs.org/en/) and [pnpm](https://pnpm.io/installation). This repository requires pnpm `11.x.x` or newer. +3. Install dependencies: + + ```sh + pnpm install + ``` + + This installs dependencies for the entire monorepo, including examples. Workspace dependencies are linked automatically. + +4. Install the Playwright browsers if you will run end-to-end tests: + + ```sh + pnpm exec playwright install + ``` + +5. Create a branch for your changes: + + ```sh + git checkout -b my-new-branch + ``` + +## Before you start + +PRs that require API changes must first be proposed as an issue and receive maintainer sign-off before implementation begins. + +## Development + +- `pnpm build:all` builds all packages. +- `pnpm build` runs the cached Nx build for affected packages. +- `pnpm dev` builds the packages and starts the development watcher. + +To run an example application, move into its directory and start its development server. For example: + +```sh +cd examples/react/basic +pnpm dev +``` + +## Testing and linting + +Run the checks relevant to your changes while developing: + +- `pnpm test:eslint` checks linting for affected packages. +- `pnpm test:types` checks types for affected packages. +- `pnpm test:unit` runs unit tests for affected packages. +- `pnpm test:build` checks builds for affected packages. +- `pnpm test:e2e` runs end-to-end tests and requires the Playwright browsers. + +Before opening a pull request, run at least: + +```sh +pnpm test:eslint +pnpm test:types +pnpm test:unit +``` + +To run the full CI test suite, use `pnpm test:ci`. For faster feedback, use an Nx target for a specific package or pass a test file or test name after `--`. For example: + +```sh +pnpm nx run @tanstack/react-router:test:unit -- tests/link.test.tsx +pnpm nx run @tanstack/react-router:test:unit -- tests/link.test.tsx -t "preloading" +``` + +Add appropriate unit tests for isolated behavior and end-to-end tests for browser or application workflows. Test relevant example applications when the change affects them. + +## Documentation + +Update the relevant documentation for user-facing changes. Documentation lives in the `docs/` directory, with separate sections for Router and Start. Use relative links within the `docs/` directory, such as `./guide/data-loading`. + +The documentation sites are hosted on [tanstack.com](https://tanstack.com), which is a [TanStack Start application](https://github.com/TanStack/tanstack.com). To preview documentation changes locally, clone both repositories as siblings: > [!NOTE] > The website fetches the doc pages from GitHub in production, and searches for them at `../router/docs` in development. Your local clone of `TanStack/router` needs to be in the same directory as the local clone of `TanStack/tanstack.com`. @@ -53,7 +108,7 @@ git clone git@github.com:TanStack/tanstack.com.git --depth=1 --single-branch --b > [!NOTE] > Your `tanstack` directory should look like this: > -> ``` +> ```text > tanstack/ > | > +-- router/ (<-- this directory cannot be called anything else!) @@ -75,6 +130,12 @@ pnpm dev > [!WARNING] > You will need to update the `docs/(router or start)config.json` file (in `TanStack/router`) if you add a new documentation page! -You can see the whole process in the screen capture below: +## AI-assisted contributions + +AI tools can be useful for exploring the codebase, drafting changes, and writing tests. They do not replace the contributor's responsibility for the result. + +1. **Keep the contribution in your own voice**. Review and rewrite generated code comments, issue reports, and pull request descriptions so they reflect what you actually mean. +2. **Keep ownership of technical decisions**. Understand every change, verify its behavior with the appropriate tests and documentation, and be ready to explain it during review. +3. **Do not paste LLM output verbatim into the codebase or a contribution**. Treat generated text and code as a draft: check its claims, adapt it to the repository, and remove anything you cannot justify. Issues or pull requests that appear to contain unreviewed LLM output may be closed without further review. -https://github.com/fulopkovacs/form/assets/43729152/9d35a3c3-8153-4e74-9cb2-af275f7a269b +AI-assisted contributions follow the same standards for correctness, testing, documentation, and review as every other contribution. diff --git a/README.md b/README.md index 8dd7deef41..736715b12a 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,20 @@ - + + + + TanStack Router + ## TanStack Router @@ -23,10 +33,20 @@ A modern router designed for type safety, data‑driven navigation, and seamless - + + + + TanStack Start + ## TanStack Start diff --git a/_artifacts/skill_tree.yaml b/_artifacts/skill_tree.yaml index b5ce76f547..ec38a04bdd 100644 --- a/_artifacts/skill_tree.yaml +++ b/_artifacts/skill_tree.yaml @@ -135,7 +135,6 @@ skills: for auth state. requires: - 'router-core' - - 'router-core/data-loading' sources: - 'TanStack/router:docs/router/guide/authenticated-routes.md' - 'TanStack/router:docs/router/how-to/setup-authentication.md' diff --git a/benchmarks/bundle-size/package.json b/benchmarks/bundle-size/package.json index 6708334f0e..bad72f4d42 100644 --- a/benchmarks/bundle-size/package.json +++ b/benchmarks/bundle-size/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "build": "node ../../scripts/benchmarks/bundle-size/measure.mjs --skip-package-builds" + "build": "node ../../scripts/benchmarks/bundle-size/measure.mjs --skip-package-builds", + "test:unit": "node --test ../../scripts/benchmarks/bundle-size/pr-report.test.mjs" }, "nx": { "targets": { @@ -23,6 +24,14 @@ "target": "build" } ] + }, + "test:unit": { + "inputs": [ + "default", + "^production", + "{workspaceRoot}/scripts/benchmarks/bundle-size/pr-report.mjs", + "{workspaceRoot}/scripts/benchmarks/bundle-size/pr-report.test.mjs" + ] } } }, @@ -40,7 +49,7 @@ "vue": "^3.5.16" }, "devDependencies": { - "@rsbuild/core": "^2.0.11", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/router-plugin": "workspace:^", "@types/react": "^19.0.8", @@ -48,7 +57,8 @@ "@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/benchmarks/client-nav/package.json b/benchmarks/client-nav/package.json index 9ed0149509..1cec164888 100644 --- a/benchmarks/client-nav/package.json +++ b/benchmarks/client-nav/package.json @@ -38,7 +38,8 @@ "@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vitest": "^4.1.4" diff --git a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts index 2b3336859a..9c79bab826 100644 --- a/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/async-pipeline/react/src/routeTree.gen.ts @@ -9,25 +9,25 @@ // 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 NestedRouteImport } from './routes/nested' import { Route as IndexRouteImport } from './routes/index' -import { Route as SlowIdRouteImport } from './routes/slow.$id' -import { Route as NestedIdRouteImport } from './routes/nested.$id' +import { Route as NestedRouteImport } from './routes/nested' import { Route as CtxIdRouteImport } from './routes/ctx.$id' +import { Route as NestedIdRouteImport } from './routes/nested.$id' +import { Route as SlowIdRouteImport } from './routes/slow.$id' -const NestedRoute = NestedRouteImport.update({ - id: '/nested', - path: '/nested', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const SlowIdRoute = SlowIdRouteImport.update({ - id: '/slow/$id', - path: '/slow/$id', +const NestedRoute = NestedRouteImport.update({ + id: '/nested', + path: '/nested', + getParentRoute: () => rootRouteImport, +} as any) +const CtxIdRoute = CtxIdRouteImport.update({ + id: '/ctx/$id', + path: '/ctx/$id', getParentRoute: () => rootRouteImport, } as any) const NestedIdRoute = NestedIdRouteImport.update({ @@ -35,9 +35,9 @@ const NestedIdRoute = NestedIdRouteImport.update({ path: '/$id', getParentRoute: () => NestedRoute, } as any) -const CtxIdRoute = CtxIdRouteImport.update({ - id: '/ctx/$id', - path: '/ctx/$id', +const SlowIdRoute = SlowIdRouteImport.update({ + id: '/slow/$id', + path: '/slow/$id', getParentRoute: () => rootRouteImport, } as any) @@ -80,13 +80,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/nested': { - id: '/nested' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -94,11 +87,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/slow/$id': { - id: '/slow/$id' - path: '/slow/$id' - fullPath: '/slow/$id' - preLoaderRoute: typeof SlowIdRouteImport + '/nested': { + id: '/nested' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedRouteImport + parentRoute: typeof rootRouteImport + } + '/ctx/$id': { + id: '/ctx/$id' + path: '/ctx/$id' + fullPath: '/ctx/$id' + preLoaderRoute: typeof CtxIdRouteImport parentRoute: typeof rootRouteImport } '/nested/$id': { @@ -108,11 +108,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedIdRouteImport parentRoute: typeof NestedRoute } - '/ctx/$id': { - id: '/ctx/$id' - path: '/ctx/$id' - fullPath: '/ctx/$id' - preLoaderRoute: typeof CtxIdRouteImport + '/slow/$id': { + id: '/slow/$id' + path: '/slow/$id' + fullPath: '/slow/$id' + preLoaderRoute: typeof SlowIdRouteImport parentRoute: typeof rootRouteImport } } diff --git a/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts index 86edcc8472..7b6db9b237 100644 --- a/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/control-flow/react/src/routeTree.gen.ts @@ -9,21 +9,21 @@ // 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 TargetRouteImport } from './routes/target' -import { Route as Hop2RouteImport } from './routes/hop2' -import { Route as Hop1RouteImport } from './routes/hop1' -import { Route as BrokenRouteImport } from './routes/broken' import { Route as IndexRouteImport } from './routes/index' +import { Route as BrokenRouteImport } from './routes/broken' +import { Route as Hop1RouteImport } from './routes/hop1' +import { Route as Hop2RouteImport } from './routes/hop2' +import { Route as TargetRouteImport } from './routes/target' import { Route as MissingIdRouteImport } from './routes/missing.$id' -const TargetRoute = TargetRouteImport.update({ - id: '/target', - path: '/target', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const Hop2Route = Hop2RouteImport.update({ - id: '/hop2', - path: '/hop2', +const BrokenRoute = BrokenRouteImport.update({ + id: '/broken', + path: '/broken', getParentRoute: () => rootRouteImport, } as any) const Hop1Route = Hop1RouteImport.update({ @@ -31,14 +31,14 @@ const Hop1Route = Hop1RouteImport.update({ path: '/hop1', getParentRoute: () => rootRouteImport, } as any) -const BrokenRoute = BrokenRouteImport.update({ - id: '/broken', - path: '/broken', +const Hop2Route = Hop2RouteImport.update({ + id: '/hop2', + path: '/hop2', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const TargetRoute = TargetRouteImport.update({ + id: '/target', + path: '/target', getParentRoute: () => rootRouteImport, } as any) const MissingIdRoute = MissingIdRouteImport.update({ @@ -98,18 +98,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/target': { - id: '/target' - path: '/target' - fullPath: '/target' - preLoaderRoute: typeof TargetRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/hop2': { - id: '/hop2' - path: '/hop2' - fullPath: '/hop2' - preLoaderRoute: typeof Hop2RouteImport + '/broken': { + id: '/broken' + path: '/broken' + fullPath: '/broken' + preLoaderRoute: typeof BrokenRouteImport parentRoute: typeof rootRouteImport } '/hop1': { @@ -119,18 +119,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Hop1RouteImport parentRoute: typeof rootRouteImport } - '/broken': { - id: '/broken' - path: '/broken' - fullPath: '/broken' - preLoaderRoute: typeof BrokenRouteImport + '/hop2': { + id: '/hop2' + path: '/hop2' + fullPath: '/hop2' + preLoaderRoute: typeof Hop2RouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/target': { + id: '/target' + path: '/target' + fullPath: '/target' + preLoaderRoute: typeof TargetRouteImport parentRoute: typeof rootRouteImport } '/missing/$id': { diff --git a/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts index 5c80a3104e..88867d9190 100644 --- a/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/head/react/src/routeTree.gen.ts @@ -9,15 +9,15 @@ // 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 SettingsRouteImport } from './routes/settings' -import { Route as DocsRouteImport } from './routes/docs' import { Route as IndexRouteImport } from './routes/index' -import { Route as DocsSectionRouteImport } from './routes/docs.$section' +import { Route as DocsRouteImport } from './routes/docs' +import { Route as SettingsRouteImport } from './routes/settings' import { Route as ArticlesIdRouteImport } from './routes/articles.$id' +import { Route as DocsSectionRouteImport } from './routes/docs.$section' -const SettingsRoute = SettingsRouteImport.update({ - id: '/settings', - path: '/settings', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const DocsRoute = DocsRouteImport.update({ @@ -25,21 +25,21 @@ const DocsRoute = DocsRouteImport.update({ path: '/docs', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SettingsRoute = SettingsRouteImport.update({ + id: '/settings', + path: '/settings', getParentRoute: () => rootRouteImport, } as any) -const DocsSectionRoute = DocsSectionRouteImport.update({ - id: '/$section', - path: '/$section', - getParentRoute: () => DocsRoute, -} as any) const ArticlesIdRoute = ArticlesIdRouteImport.update({ id: '/articles/$id', path: '/articles/$id', getParentRoute: () => rootRouteImport, } as any) +const DocsSectionRoute = DocsSectionRouteImport.update({ + id: '/$section', + path: '/$section', + getParentRoute: () => DocsRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -86,11 +86,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/settings': { - id: '/settings' - path: '/settings' - fullPath: '/settings' - preLoaderRoute: typeof SettingsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/docs': { @@ -100,20 +100,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/settings': { + id: '/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof SettingsRouteImport parentRoute: typeof rootRouteImport } - '/docs/$section': { - id: '/docs/$section' - path: '/$section' - fullPath: '/docs/$section' - preLoaderRoute: typeof DocsSectionRouteImport - parentRoute: typeof DocsRoute - } '/articles/$id': { id: '/articles/$id' path: '/articles/$id' @@ -121,6 +114,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ArticlesIdRouteImport parentRoute: typeof rootRouteImport } + '/docs/$section': { + id: '/docs/$section' + path: '/$section' + fullPath: '/docs/$section' + preLoaderRoute: typeof DocsSectionRouteImport + parentRoute: typeof DocsRoute + } } } diff --git a/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts index 61cc83c4c8..21bb12ba35 100644 --- a/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/history/react/src/routeTree.gen.ts @@ -9,24 +9,19 @@ // 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 GalleryRouteImport } from './routes/gallery' import { Route as IndexRouteImport } from './routes/index' -import { Route as PhotosPhotoIdRouteImport } from './routes/photos.$photoId' +import { Route as GalleryRouteImport } from './routes/gallery' import { Route as PagesNRouteImport } from './routes/pages.$n' +import { Route as PhotosPhotoIdRouteImport } from './routes/photos.$photoId' -const GalleryRoute = GalleryRouteImport.update({ - id: '/gallery', - path: '/gallery', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const PhotosPhotoIdRoute = PhotosPhotoIdRouteImport.update({ - id: '/photos/$photoId', - path: '/photos/$photoId', +const GalleryRoute = GalleryRouteImport.update({ + id: '/gallery', + path: '/gallery', getParentRoute: () => rootRouteImport, } as any) const PagesNRoute = PagesNRouteImport.update({ @@ -34,6 +29,11 @@ const PagesNRoute = PagesNRouteImport.update({ path: '/pages/$n', getParentRoute: () => rootRouteImport, } as any) +const PhotosPhotoIdRoute = PhotosPhotoIdRouteImport.update({ + id: '/photos/$photoId', + path: '/photos/$photoId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -71,13 +71,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/gallery': { - id: '/gallery' - path: '/gallery' - fullPath: '/gallery' - preLoaderRoute: typeof GalleryRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -85,11 +78,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/photos/$photoId': { - id: '/photos/$photoId' - path: '/photos/$photoId' - fullPath: '/photos/$photoId' - preLoaderRoute: typeof PhotosPhotoIdRouteImport + '/gallery': { + id: '/gallery' + path: '/gallery' + fullPath: '/gallery' + preLoaderRoute: typeof GalleryRouteImport parentRoute: typeof rootRouteImport } '/pages/$n': { @@ -99,6 +92,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PagesNRouteImport parentRoute: typeof rootRouteImport } + '/photos/$photoId': { + id: '/photos/$photoId' + path: '/photos/$photoId' + fullPath: '/photos/$photoId' + preLoaderRoute: typeof PhotosPhotoIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts index 97ec042c1c..6e0c29ca88 100644 --- a/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/links/react/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' import { Route as ItemsIdRouteImport } from './routes/items.$id' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) const ItemsIdRoute = ItemsIdRouteImport.update({ id: '/items/$id', path: '/items/$id', @@ -61,13 +61,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -75,6 +68,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } '/items/$id': { id: '/items/$id' path: '/items/$id' diff --git a/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts index 50f0a8863b..35908a225f 100644 --- a/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/loaders/react/src/routeTree.gen.ts @@ -9,24 +9,19 @@ // 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 DepsRouteImport } from './routes/deps' import { Route as IndexRouteImport } from './routes/index' -import { Route as FreshIdRouteImport } from './routes/fresh.$id' +import { Route as DepsRouteImport } from './routes/deps' import { Route as CachedIdRouteImport } from './routes/cached.$id' +import { Route as FreshIdRouteImport } from './routes/fresh.$id' -const DepsRoute = DepsRouteImport.update({ - id: '/deps', - path: '/deps', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const FreshIdRoute = FreshIdRouteImport.update({ - id: '/fresh/$id', - path: '/fresh/$id', +const DepsRoute = DepsRouteImport.update({ + id: '/deps', + path: '/deps', getParentRoute: () => rootRouteImport, } as any) const CachedIdRoute = CachedIdRouteImport.update({ @@ -34,6 +29,11 @@ const CachedIdRoute = CachedIdRouteImport.update({ path: '/cached/$id', getParentRoute: () => rootRouteImport, } as any) +const FreshIdRoute = FreshIdRouteImport.update({ + id: '/fresh/$id', + path: '/fresh/$id', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -71,13 +71,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/deps': { - id: '/deps' - path: '/deps' - fullPath: '/deps' - preLoaderRoute: typeof DepsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -85,11 +78,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/fresh/$id': { - id: '/fresh/$id' - path: '/fresh/$id' - fullPath: '/fresh/$id' - preLoaderRoute: typeof FreshIdRouteImport + '/deps': { + id: '/deps' + path: '/deps' + fullPath: '/deps' + preLoaderRoute: typeof DepsRouteImport parentRoute: typeof rootRouteImport } '/cached/$id': { @@ -99,6 +92,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CachedIdRouteImport parentRoute: typeof rootRouteImport } + '/fresh/$id': { + id: '/fresh/$id' + path: '/fresh/$id' + fullPath: '/fresh/$id' + preLoaderRoute: typeof FreshIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts index a796abe0ec..f9b7e5c268 100644 --- a/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/mount/react/src/routeTree.gen.ts @@ -9,25 +9,30 @@ // 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 ShopRouteImport } from './routes/shop' -import { Route as SettingsRouteImport } from './routes/settings' -import { Route as SearchRouteImport } from './routes/search' -import { Route as BlogRouteImport } from './routes/blog' -import { Route as AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' -import { Route as ShopIndexRouteImport } from './routes/shop.index' +import { Route as AboutRouteImport } from './routes/about' +import { Route as BlogRouteImport } from './routes/blog' +import { Route as SearchRouteImport } from './routes/search' +import { Route as SettingsRouteImport } from './routes/settings' +import { Route as ShopRouteImport } from './routes/shop' import { Route as BlogIndexRouteImport } from './routes/blog.index' -import { Route as ShopProductIdRouteImport } from './routes/shop.$productId' import { Route as BlogSlugRouteImport } from './routes/blog.$slug' +import { Route as ShopIndexRouteImport } from './routes/shop.index' +import { Route as ShopProductIdRouteImport } from './routes/shop.$productId' -const ShopRoute = ShopRouteImport.update({ - id: '/shop', - path: '/shop', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SettingsRoute = SettingsRouteImport.update({ - id: '/settings', - path: '/settings', +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) +const BlogRoute = BlogRouteImport.update({ + id: '/blog', + path: '/blog', getParentRoute: () => rootRouteImport, } as any) const SearchRoute = SearchRouteImport.update({ @@ -35,41 +40,36 @@ const SearchRoute = SearchRouteImport.update({ path: '/search', getParentRoute: () => rootRouteImport, } as any) -const BlogRoute = BlogRouteImport.update({ - id: '/blog', - path: '/blog', +const SettingsRoute = SettingsRouteImport.update({ + id: '/settings', + path: '/settings', getParentRoute: () => rootRouteImport, } as any) -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const ShopRoute = ShopRouteImport.update({ + id: '/shop', + path: '/shop', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const BlogIndexRoute = BlogIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => BlogRoute, +} as any) +const BlogSlugRoute = BlogSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogRoute, } as any) const ShopIndexRoute = ShopIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ShopRoute, } as any) -const BlogIndexRoute = BlogIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => BlogRoute, -} as any) const ShopProductIdRoute = ShopProductIdRouteImport.update({ id: '/$productId', path: '/$productId', getParentRoute: () => ShopRoute, } as any) -const BlogSlugRoute = BlogSlugRouteImport.update({ - id: '/$slug', - path: '/$slug', - getParentRoute: () => BlogRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -154,18 +154,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/shop': { - id: '/shop' - path: '/shop' - fullPath: '/shop' - preLoaderRoute: typeof ShopRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/settings': { - id: '/settings' - path: '/settings' - fullPath: '/settings' - preLoaderRoute: typeof SettingsRouteImport + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteImport parentRoute: typeof rootRouteImport } '/search': { @@ -175,26 +182,33 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SearchRouteImport parentRoute: typeof rootRouteImport } - '/blog': { - id: '/blog' - path: '/blog' - fullPath: '/blog' - preLoaderRoute: typeof BlogRouteImport + '/settings': { + id: '/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof SettingsRouteImport parentRoute: typeof rootRouteImport } - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/shop': { + id: '/shop' + path: '/shop' + fullPath: '/shop' + preLoaderRoute: typeof ShopRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' + '/blog/': { + id: '/blog/' path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + fullPath: '/blog/' + preLoaderRoute: typeof BlogIndexRouteImport + parentRoute: typeof BlogRoute + } + '/blog/$slug': { + id: '/blog/$slug' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugRouteImport + parentRoute: typeof BlogRoute } '/shop/': { id: '/shop/' @@ -203,13 +217,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ShopIndexRouteImport parentRoute: typeof ShopRoute } - '/blog/': { - id: '/blog/' - path: '/' - fullPath: '/blog/' - preLoaderRoute: typeof BlogIndexRouteImport - parentRoute: typeof BlogRoute - } '/shop/$productId': { id: '/shop/$productId' path: '/$productId' @@ -217,13 +224,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ShopProductIdRouteImport parentRoute: typeof ShopRoute } - '/blog/$slug': { - id: '/blog/$slug' - path: '/$slug' - fullPath: '/blog/$slug' - preLoaderRoute: typeof BlogSlugRouteImport - parentRoute: typeof BlogRoute - } } } diff --git a/benchmarks/client-nav/scenarios/preload/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/preload/react/src/routeTree.gen.ts index 0c76e85974..20619341c0 100644 --- a/benchmarks/client-nav/scenarios/preload/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/preload/react/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 DocsRouteImport } from './routes/docs' import { Route as IndexRouteImport } from './routes/index' +import { Route as DocsRouteImport } from './routes/docs' import { Route as SectionsSectionRouteImport } from './routes/sections.$section' -const DocsRoute = DocsRouteImport.update({ - id: '/docs', - path: '/docs', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DocsRoute = DocsRouteImport.update({ + id: '/docs', + path: '/docs', + getParentRoute: () => rootRouteImport, +} as any) const SectionsSectionRoute = SectionsSectionRouteImport.update({ id: '/sections/$section', path: '/sections/$section', @@ -61,13 +61,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/docs': { - id: '/docs' - path: '/docs' - fullPath: '/docs' - preLoaderRoute: typeof DocsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -75,6 +68,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/docs': { + id: '/docs' + path: '/docs' + fullPath: '/docs' + preLoaderRoute: typeof DocsRouteImport + parentRoute: typeof rootRouteImport + } '/sections/$section': { id: '/sections/$section' path: '/sections/$section' diff --git a/benchmarks/client-nav/scenarios/route-tree-scale/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/route-tree-scale/react/src/routeTree.gen.ts index b58b75180f..f8eeb73dda 100644 --- a/benchmarks/client-nav/scenarios/route-tree-scale/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/route-tree-scale/react/src/routeTree.gen.ts @@ -9,58 +9,62 @@ // 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 SecFRouteImport } from './routes/sec-f' -import { Route as SecERouteImport } from './routes/sec-e' -import { Route as SecDRouteImport } from './routes/sec-d' -import { Route as SecCRouteImport } from './routes/sec-c' -import { Route as SecBRouteImport } from './routes/sec-b' -import { Route as SecARouteImport } from './routes/sec-a' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as SecFIndexRouteImport } from './routes/sec-f.index' -import { Route as SecEIndexRouteImport } from './routes/sec-e.index' -import { Route as SecDIndexRouteImport } from './routes/sec-d.index' -import { Route as SecCIndexRouteImport } from './routes/sec-c.index' -import { Route as SecBIndexRouteImport } from './routes/sec-b.index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as SecARouteImport } from './routes/sec-a' +import { Route as SecBRouteImport } from './routes/sec-b' +import { Route as SecCRouteImport } from './routes/sec-c' +import { Route as SecDRouteImport } from './routes/sec-d' +import { Route as SecERouteImport } from './routes/sec-e' +import { Route as SecFRouteImport } from './routes/sec-f' +import { Route as marketingPricingRouteImport } from './routes/(marketing)/pricing' +import { Route as marketingPromoRouteImport } from './routes/(marketing)/promo' +import { Route as LayoutAlphaRouteImport } from './routes/_layout.alpha' +import { Route as LayoutBetaRouteImport } from './routes/_layout.beta' +import { Route as FilesSplatRouteImport } from './routes/files.$' +import { Route as ReleaseVChar123versionChar125RouteImport } from './routes/release.v{$version}' import { Route as SecAIndexRouteImport } from './routes/sec-a.index' -import { Route as SecFSettingsRouteImport } from './routes/sec-f.settings' -import { Route as SecFAboutRouteImport } from './routes/sec-f.about' -import { Route as SecFIdRouteImport } from './routes/sec-f.$id' -import { Route as SecESettingsRouteImport } from './routes/sec-e.settings' -import { Route as SecEAboutRouteImport } from './routes/sec-e.about' -import { Route as SecEIdRouteImport } from './routes/sec-e.$id' -import { Route as SecDSettingsRouteImport } from './routes/sec-d.settings' -import { Route as SecDAboutRouteImport } from './routes/sec-d.about' -import { Route as SecDIdRouteImport } from './routes/sec-d.$id' -import { Route as SecCSettingsRouteImport } from './routes/sec-c.settings' -import { Route as SecCAboutRouteImport } from './routes/sec-c.about' -import { Route as SecCIdRouteImport } from './routes/sec-c.$id' -import { Route as SecBSettingsRouteImport } from './routes/sec-b.settings' -import { Route as SecBAboutRouteImport } from './routes/sec-b.about' -import { Route as SecBIdRouteImport } from './routes/sec-b.$id' -import { Route as SecASettingsRouteImport } from './routes/sec-a.settings' -import { Route as SecAAboutRouteImport } from './routes/sec-a.about' import { Route as SecAIdRouteImport } from './routes/sec-a.$id' -import { Route as ReleaseVChar123versionChar125RouteImport } from './routes/release.v{$version}' -import { Route as FilesSplatRouteImport } from './routes/files.$' -import { Route as LayoutBetaRouteImport } from './routes/_layout.beta' -import { Route as LayoutAlphaRouteImport } from './routes/_layout.alpha' -import { Route as marketingPromoRouteImport } from './routes/(marketing)/promo' -import { Route as marketingPricingRouteImport } from './routes/(marketing)/pricing' +import { Route as SecAAboutRouteImport } from './routes/sec-a.about' +import { Route as SecASettingsRouteImport } from './routes/sec-a.settings' +import { Route as SecBIndexRouteImport } from './routes/sec-b.index' +import { Route as SecBIdRouteImport } from './routes/sec-b.$id' +import { Route as SecBAboutRouteImport } from './routes/sec-b.about' +import { Route as SecBSettingsRouteImport } from './routes/sec-b.settings' +import { Route as SecCIndexRouteImport } from './routes/sec-c.index' +import { Route as SecCIdRouteImport } from './routes/sec-c.$id' +import { Route as SecCAboutRouteImport } from './routes/sec-c.about' +import { Route as SecCSettingsRouteImport } from './routes/sec-c.settings' +import { Route as SecDIndexRouteImport } from './routes/sec-d.index' +import { Route as SecDIdRouteImport } from './routes/sec-d.$id' +import { Route as SecDAboutRouteImport } from './routes/sec-d.about' +import { Route as SecDSettingsRouteImport } from './routes/sec-d.settings' +import { Route as SecEIndexRouteImport } from './routes/sec-e.index' +import { Route as SecEIdRouteImport } from './routes/sec-e.$id' +import { Route as SecEAboutRouteImport } from './routes/sec-e.about' +import { Route as SecESettingsRouteImport } from './routes/sec-e.settings' +import { Route as SecFIndexRouteImport } from './routes/sec-f.index' +import { Route as SecFIdRouteImport } from './routes/sec-f.$id' +import { Route as SecFAboutRouteImport } from './routes/sec-f.about' +import { Route as SecFSettingsRouteImport } from './routes/sec-f.settings' -const SecFRoute = SecFRouteImport.update({ - id: '/sec-f', - path: '/sec-f', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SecERoute = SecERouteImport.update({ - id: '/sec-e', - path: '/sec-e', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const SecDRoute = SecDRouteImport.update({ - id: '/sec-d', - path: '/sec-d', +const SecARoute = SecARouteImport.update({ + id: '/sec-a', + path: '/sec-a', + getParentRoute: () => rootRouteImport, +} as any) +const SecBRoute = SecBRouteImport.update({ + id: '/sec-b', + path: '/sec-b', getParentRoute: () => rootRouteImport, } as any) const SecCRoute = SecCRouteImport.update({ @@ -68,108 +72,95 @@ const SecCRoute = SecCRouteImport.update({ path: '/sec-c', getParentRoute: () => rootRouteImport, } as any) -const SecBRoute = SecBRouteImport.update({ - id: '/sec-b', - path: '/sec-b', +const SecDRoute = SecDRouteImport.update({ + id: '/sec-d', + path: '/sec-d', getParentRoute: () => rootRouteImport, } as any) -const SecARoute = SecARouteImport.update({ - id: '/sec-a', - path: '/sec-a', +const SecERoute = SecERouteImport.update({ + id: '/sec-e', + path: '/sec-e', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const SecFRoute = SecFRouteImport.update({ + id: '/sec-f', + path: '/sec-f', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const marketingPricingRoute = marketingPricingRouteImport.update({ + id: '/(marketing)/pricing', + path: '/pricing', getParentRoute: () => rootRouteImport, } as any) -const SecFIndexRoute = SecFIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SecFRoute, -} as any) -const SecEIndexRoute = SecEIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SecERoute, +const marketingPromoRoute = marketingPromoRouteImport.update({ + id: '/(marketing)/promo', + path: '/promo', + getParentRoute: () => rootRouteImport, } as any) -const SecDIndexRoute = SecDIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SecDRoute, +const LayoutAlphaRoute = LayoutAlphaRouteImport.update({ + id: '/alpha', + path: '/alpha', + getParentRoute: () => LayoutRoute, } as any) -const SecCIndexRoute = SecCIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SecCRoute, +const LayoutBetaRoute = LayoutBetaRouteImport.update({ + id: '/beta', + path: '/beta', + getParentRoute: () => LayoutRoute, } as any) -const SecBIndexRoute = SecBIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SecBRoute, +const FilesSplatRoute = FilesSplatRouteImport.update({ + id: '/files/$', + path: '/files/$', + getParentRoute: () => rootRouteImport, } as any) +const ReleaseVChar123versionChar125Route = + ReleaseVChar123versionChar125RouteImport.update({ + id: '/release/v{$version}', + path: '/release/v{$version}', + getParentRoute: () => rootRouteImport, + } as any) const SecAIndexRoute = SecAIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => SecARoute, } as any) -const SecFSettingsRoute = SecFSettingsRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => SecFRoute, +const SecAIdRoute = SecAIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => SecARoute, } as any) -const SecFAboutRoute = SecFAboutRouteImport.update({ +const SecAAboutRoute = SecAAboutRouteImport.update({ id: '/about', path: '/about', - getParentRoute: () => SecFRoute, -} as any) -const SecFIdRoute = SecFIdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => SecFRoute, + getParentRoute: () => SecARoute, } as any) -const SecESettingsRoute = SecESettingsRouteImport.update({ +const SecASettingsRoute = SecASettingsRouteImport.update({ id: '/settings', path: '/settings', - getParentRoute: () => SecERoute, + getParentRoute: () => SecARoute, } as any) -const SecEAboutRoute = SecEAboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => SecERoute, +const SecBIndexRoute = SecBIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SecBRoute, } as any) -const SecEIdRoute = SecEIdRouteImport.update({ +const SecBIdRoute = SecBIdRouteImport.update({ id: '/$id', path: '/$id', - getParentRoute: () => SecERoute, -} as any) -const SecDSettingsRoute = SecDSettingsRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => SecDRoute, + getParentRoute: () => SecBRoute, } as any) -const SecDAboutRoute = SecDAboutRouteImport.update({ +const SecBAboutRoute = SecBAboutRouteImport.update({ id: '/about', path: '/about', - getParentRoute: () => SecDRoute, -} as any) -const SecDIdRoute = SecDIdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => SecDRoute, + getParentRoute: () => SecBRoute, } as any) -const SecCSettingsRoute = SecCSettingsRouteImport.update({ +const SecBSettingsRoute = SecBSettingsRouteImport.update({ id: '/settings', path: '/settings', - getParentRoute: () => SecCRoute, + getParentRoute: () => SecBRoute, } as any) -const SecCAboutRoute = SecCAboutRouteImport.update({ - id: '/about', - path: '/about', +const SecCIndexRoute = SecCIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => SecCRoute, } as any) const SecCIdRoute = SecCIdRouteImport.update({ @@ -177,66 +168,75 @@ const SecCIdRoute = SecCIdRouteImport.update({ path: '/$id', getParentRoute: () => SecCRoute, } as any) -const SecBSettingsRoute = SecBSettingsRouteImport.update({ +const SecCAboutRoute = SecCAboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => SecCRoute, +} as any) +const SecCSettingsRoute = SecCSettingsRouteImport.update({ id: '/settings', path: '/settings', - getParentRoute: () => SecBRoute, + getParentRoute: () => SecCRoute, } as any) -const SecBAboutRoute = SecBAboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => SecBRoute, +const SecDIndexRoute = SecDIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SecDRoute, } as any) -const SecBIdRoute = SecBIdRouteImport.update({ +const SecDIdRoute = SecDIdRouteImport.update({ id: '/$id', path: '/$id', - getParentRoute: () => SecBRoute, + getParentRoute: () => SecDRoute, } as any) -const SecASettingsRoute = SecASettingsRouteImport.update({ +const SecDAboutRoute = SecDAboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => SecDRoute, +} as any) +const SecDSettingsRoute = SecDSettingsRouteImport.update({ id: '/settings', path: '/settings', - getParentRoute: () => SecARoute, + getParentRoute: () => SecDRoute, } as any) -const SecAAboutRoute = SecAAboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => SecARoute, +const SecEIndexRoute = SecEIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SecERoute, } as any) -const SecAIdRoute = SecAIdRouteImport.update({ +const SecEIdRoute = SecEIdRouteImport.update({ id: '/$id', path: '/$id', - getParentRoute: () => SecARoute, + getParentRoute: () => SecERoute, } as any) -const ReleaseVChar123versionChar125Route = - ReleaseVChar123versionChar125RouteImport.update({ - id: '/release/v{$version}', - path: '/release/v{$version}', - getParentRoute: () => rootRouteImport, - } as any) -const FilesSplatRoute = FilesSplatRouteImport.update({ - id: '/files/$', - path: '/files/$', - getParentRoute: () => rootRouteImport, +const SecEAboutRoute = SecEAboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => SecERoute, } as any) -const LayoutBetaRoute = LayoutBetaRouteImport.update({ - id: '/beta', - path: '/beta', - getParentRoute: () => LayoutRoute, +const SecESettingsRoute = SecESettingsRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => SecERoute, } as any) -const LayoutAlphaRoute = LayoutAlphaRouteImport.update({ - id: '/alpha', - path: '/alpha', - getParentRoute: () => LayoutRoute, +const SecFIndexRoute = SecFIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SecFRoute, } as any) -const marketingPromoRoute = marketingPromoRouteImport.update({ - id: '/(marketing)/promo', - path: '/promo', - getParentRoute: () => rootRouteImport, +const SecFIdRoute = SecFIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => SecFRoute, } as any) -const marketingPricingRoute = marketingPricingRouteImport.update({ - id: '/(marketing)/pricing', - path: '/pricing', - getParentRoute: () => rootRouteImport, +const SecFAboutRoute = SecFAboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => SecFRoute, +} as any) +const SecFSettingsRoute = SecFSettingsRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => SecFRoute, } as any) export interface FileRoutesByFullPath { @@ -484,25 +484,32 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/sec-f': { - id: '/sec-f' - path: '/sec-f' - fullPath: '/sec-f' - preLoaderRoute: typeof SecFRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/sec-e': { - id: '/sec-e' - path: '/sec-e' - fullPath: '/sec-e' - preLoaderRoute: typeof SecERouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/sec-d': { - id: '/sec-d' - path: '/sec-d' - fullPath: '/sec-d' - preLoaderRoute: typeof SecDRouteImport + '/sec-a': { + id: '/sec-a' + path: '/sec-a' + fullPath: '/sec-a' + preLoaderRoute: typeof SecARouteImport + parentRoute: typeof rootRouteImport + } + '/sec-b': { + id: '/sec-b' + path: '/sec-b' + fullPath: '/sec-b' + preLoaderRoute: typeof SecBRouteImport parentRoute: typeof rootRouteImport } '/sec-c': { @@ -512,68 +519,68 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SecCRouteImport parentRoute: typeof rootRouteImport } - '/sec-b': { - id: '/sec-b' - path: '/sec-b' - fullPath: '/sec-b' - preLoaderRoute: typeof SecBRouteImport + '/sec-d': { + id: '/sec-d' + path: '/sec-d' + fullPath: '/sec-d' + preLoaderRoute: typeof SecDRouteImport parentRoute: typeof rootRouteImport } - '/sec-a': { - id: '/sec-a' - path: '/sec-a' - fullPath: '/sec-a' - preLoaderRoute: typeof SecARouteImport + '/sec-e': { + id: '/sec-e' + path: '/sec-e' + fullPath: '/sec-e' + preLoaderRoute: typeof SecERouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/sec-f': { + id: '/sec-f' + path: '/sec-f' + fullPath: '/sec-f' + preLoaderRoute: typeof SecFRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(marketing)/pricing': { + id: '/(marketing)/pricing' + path: '/pricing' + fullPath: '/pricing' + preLoaderRoute: typeof marketingPricingRouteImport parentRoute: typeof rootRouteImport } - '/sec-f/': { - id: '/sec-f/' - path: '/' - fullPath: '/sec-f/' - preLoaderRoute: typeof SecFIndexRouteImport - parentRoute: typeof SecFRoute + '/(marketing)/promo': { + id: '/(marketing)/promo' + path: '/promo' + fullPath: '/promo' + preLoaderRoute: typeof marketingPromoRouteImport + parentRoute: typeof rootRouteImport } - '/sec-e/': { - id: '/sec-e/' - path: '/' - fullPath: '/sec-e/' - preLoaderRoute: typeof SecEIndexRouteImport - parentRoute: typeof SecERoute + '/_layout/alpha': { + id: '/_layout/alpha' + path: '/alpha' + fullPath: '/alpha' + preLoaderRoute: typeof LayoutAlphaRouteImport + parentRoute: typeof LayoutRoute } - '/sec-d/': { - id: '/sec-d/' - path: '/' - fullPath: '/sec-d/' - preLoaderRoute: typeof SecDIndexRouteImport - parentRoute: typeof SecDRoute + '/_layout/beta': { + id: '/_layout/beta' + path: '/beta' + fullPath: '/beta' + preLoaderRoute: typeof LayoutBetaRouteImport + parentRoute: typeof LayoutRoute } - '/sec-c/': { - id: '/sec-c/' - path: '/' - fullPath: '/sec-c/' - preLoaderRoute: typeof SecCIndexRouteImport - parentRoute: typeof SecCRoute + '/files/$': { + id: '/files/$' + path: '/files/$' + fullPath: '/files/$' + preLoaderRoute: typeof FilesSplatRouteImport + parentRoute: typeof rootRouteImport } - '/sec-b/': { - id: '/sec-b/' - path: '/' - fullPath: '/sec-b/' - preLoaderRoute: typeof SecBIndexRouteImport - parentRoute: typeof SecBRoute + '/release/v{$version}': { + id: '/release/v{$version}' + path: '/release/v{$version}' + fullPath: '/release/v{$version}' + preLoaderRoute: typeof ReleaseVChar123versionChar125RouteImport + parentRoute: typeof rootRouteImport } '/sec-a/': { id: '/sec-a/' @@ -582,81 +589,60 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SecAIndexRouteImport parentRoute: typeof SecARoute } - '/sec-f/settings': { - id: '/sec-f/settings' - path: '/settings' - fullPath: '/sec-f/settings' - preLoaderRoute: typeof SecFSettingsRouteImport - parentRoute: typeof SecFRoute + '/sec-a/$id': { + id: '/sec-a/$id' + path: '/$id' + fullPath: '/sec-a/$id' + preLoaderRoute: typeof SecAIdRouteImport + parentRoute: typeof SecARoute } - '/sec-f/about': { - id: '/sec-f/about' + '/sec-a/about': { + id: '/sec-a/about' path: '/about' - fullPath: '/sec-f/about' - preLoaderRoute: typeof SecFAboutRouteImport - parentRoute: typeof SecFRoute - } - '/sec-f/$id': { - id: '/sec-f/$id' - path: '/$id' - fullPath: '/sec-f/$id' - preLoaderRoute: typeof SecFIdRouteImport - parentRoute: typeof SecFRoute + fullPath: '/sec-a/about' + preLoaderRoute: typeof SecAAboutRouteImport + parentRoute: typeof SecARoute } - '/sec-e/settings': { - id: '/sec-e/settings' + '/sec-a/settings': { + id: '/sec-a/settings' path: '/settings' - fullPath: '/sec-e/settings' - preLoaderRoute: typeof SecESettingsRouteImport - parentRoute: typeof SecERoute + fullPath: '/sec-a/settings' + preLoaderRoute: typeof SecASettingsRouteImport + parentRoute: typeof SecARoute } - '/sec-e/about': { - id: '/sec-e/about' - path: '/about' - fullPath: '/sec-e/about' - preLoaderRoute: typeof SecEAboutRouteImport - parentRoute: typeof SecERoute + '/sec-b/': { + id: '/sec-b/' + path: '/' + fullPath: '/sec-b/' + preLoaderRoute: typeof SecBIndexRouteImport + parentRoute: typeof SecBRoute } - '/sec-e/$id': { - id: '/sec-e/$id' + '/sec-b/$id': { + id: '/sec-b/$id' path: '/$id' - fullPath: '/sec-e/$id' - preLoaderRoute: typeof SecEIdRouteImport - parentRoute: typeof SecERoute - } - '/sec-d/settings': { - id: '/sec-d/settings' - path: '/settings' - fullPath: '/sec-d/settings' - preLoaderRoute: typeof SecDSettingsRouteImport - parentRoute: typeof SecDRoute + fullPath: '/sec-b/$id' + preLoaderRoute: typeof SecBIdRouteImport + parentRoute: typeof SecBRoute } - '/sec-d/about': { - id: '/sec-d/about' + '/sec-b/about': { + id: '/sec-b/about' path: '/about' - fullPath: '/sec-d/about' - preLoaderRoute: typeof SecDAboutRouteImport - parentRoute: typeof SecDRoute - } - '/sec-d/$id': { - id: '/sec-d/$id' - path: '/$id' - fullPath: '/sec-d/$id' - preLoaderRoute: typeof SecDIdRouteImport - parentRoute: typeof SecDRoute + fullPath: '/sec-b/about' + preLoaderRoute: typeof SecBAboutRouteImport + parentRoute: typeof SecBRoute } - '/sec-c/settings': { - id: '/sec-c/settings' + '/sec-b/settings': { + id: '/sec-b/settings' path: '/settings' - fullPath: '/sec-c/settings' - preLoaderRoute: typeof SecCSettingsRouteImport - parentRoute: typeof SecCRoute + fullPath: '/sec-b/settings' + preLoaderRoute: typeof SecBSettingsRouteImport + parentRoute: typeof SecBRoute } - '/sec-c/about': { - id: '/sec-c/about' - path: '/about' - fullPath: '/sec-c/about' - preLoaderRoute: typeof SecCAboutRouteImport + '/sec-c/': { + id: '/sec-c/' + path: '/' + fullPath: '/sec-c/' + preLoaderRoute: typeof SecCIndexRouteImport parentRoute: typeof SecCRoute } '/sec-c/$id': { @@ -666,89 +652,103 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SecCIdRouteImport parentRoute: typeof SecCRoute } - '/sec-b/settings': { - id: '/sec-b/settings' + '/sec-c/about': { + id: '/sec-c/about' + path: '/about' + fullPath: '/sec-c/about' + preLoaderRoute: typeof SecCAboutRouteImport + parentRoute: typeof SecCRoute + } + '/sec-c/settings': { + id: '/sec-c/settings' path: '/settings' - fullPath: '/sec-b/settings' - preLoaderRoute: typeof SecBSettingsRouteImport - parentRoute: typeof SecBRoute + fullPath: '/sec-c/settings' + preLoaderRoute: typeof SecCSettingsRouteImport + parentRoute: typeof SecCRoute } - '/sec-b/about': { - id: '/sec-b/about' - path: '/about' - fullPath: '/sec-b/about' - preLoaderRoute: typeof SecBAboutRouteImport - parentRoute: typeof SecBRoute + '/sec-d/': { + id: '/sec-d/' + path: '/' + fullPath: '/sec-d/' + preLoaderRoute: typeof SecDIndexRouteImport + parentRoute: typeof SecDRoute } - '/sec-b/$id': { - id: '/sec-b/$id' + '/sec-d/$id': { + id: '/sec-d/$id' path: '/$id' - fullPath: '/sec-b/$id' - preLoaderRoute: typeof SecBIdRouteImport - parentRoute: typeof SecBRoute + fullPath: '/sec-d/$id' + preLoaderRoute: typeof SecDIdRouteImport + parentRoute: typeof SecDRoute } - '/sec-a/settings': { - id: '/sec-a/settings' + '/sec-d/about': { + id: '/sec-d/about' + path: '/about' + fullPath: '/sec-d/about' + preLoaderRoute: typeof SecDAboutRouteImport + parentRoute: typeof SecDRoute + } + '/sec-d/settings': { + id: '/sec-d/settings' path: '/settings' - fullPath: '/sec-a/settings' - preLoaderRoute: typeof SecASettingsRouteImport - parentRoute: typeof SecARoute + fullPath: '/sec-d/settings' + preLoaderRoute: typeof SecDSettingsRouteImport + parentRoute: typeof SecDRoute } - '/sec-a/about': { - id: '/sec-a/about' - path: '/about' - fullPath: '/sec-a/about' - preLoaderRoute: typeof SecAAboutRouteImport - parentRoute: typeof SecARoute + '/sec-e/': { + id: '/sec-e/' + path: '/' + fullPath: '/sec-e/' + preLoaderRoute: typeof SecEIndexRouteImport + parentRoute: typeof SecERoute } - '/sec-a/$id': { - id: '/sec-a/$id' + '/sec-e/$id': { + id: '/sec-e/$id' path: '/$id' - fullPath: '/sec-a/$id' - preLoaderRoute: typeof SecAIdRouteImport - parentRoute: typeof SecARoute + fullPath: '/sec-e/$id' + preLoaderRoute: typeof SecEIdRouteImport + parentRoute: typeof SecERoute } - '/release/v{$version}': { - id: '/release/v{$version}' - path: '/release/v{$version}' - fullPath: '/release/v{$version}' - preLoaderRoute: typeof ReleaseVChar123versionChar125RouteImport - parentRoute: typeof rootRouteImport + '/sec-e/about': { + id: '/sec-e/about' + path: '/about' + fullPath: '/sec-e/about' + preLoaderRoute: typeof SecEAboutRouteImport + parentRoute: typeof SecERoute } - '/files/$': { - id: '/files/$' - path: '/files/$' - fullPath: '/files/$' - preLoaderRoute: typeof FilesSplatRouteImport - parentRoute: typeof rootRouteImport + '/sec-e/settings': { + id: '/sec-e/settings' + path: '/settings' + fullPath: '/sec-e/settings' + preLoaderRoute: typeof SecESettingsRouteImport + parentRoute: typeof SecERoute } - '/_layout/beta': { - id: '/_layout/beta' - path: '/beta' - fullPath: '/beta' - preLoaderRoute: typeof LayoutBetaRouteImport - parentRoute: typeof LayoutRoute + '/sec-f/': { + id: '/sec-f/' + path: '/' + fullPath: '/sec-f/' + preLoaderRoute: typeof SecFIndexRouteImport + parentRoute: typeof SecFRoute } - '/_layout/alpha': { - id: '/_layout/alpha' - path: '/alpha' - fullPath: '/alpha' - preLoaderRoute: typeof LayoutAlphaRouteImport - parentRoute: typeof LayoutRoute + '/sec-f/$id': { + id: '/sec-f/$id' + path: '/$id' + fullPath: '/sec-f/$id' + preLoaderRoute: typeof SecFIdRouteImport + parentRoute: typeof SecFRoute } - '/(marketing)/promo': { - id: '/(marketing)/promo' - path: '/promo' - fullPath: '/promo' - preLoaderRoute: typeof marketingPromoRouteImport - parentRoute: typeof rootRouteImport + '/sec-f/about': { + id: '/sec-f/about' + path: '/about' + fullPath: '/sec-f/about' + preLoaderRoute: typeof SecFAboutRouteImport + parentRoute: typeof SecFRoute } - '/(marketing)/pricing': { - id: '/(marketing)/pricing' - path: '/pricing' - fullPath: '/pricing' - preLoaderRoute: typeof marketingPricingRouteImport - parentRoute: typeof rootRouteImport + '/sec-f/settings': { + id: '/sec-f/settings' + path: '/settings' + fullPath: '/sec-f/settings' + preLoaderRoute: typeof SecFSettingsRouteImport + parentRoute: typeof SecFRoute } } } diff --git a/benchmarks/client-nav/scenarios/search-params/react/src/routeTree.gen.ts b/benchmarks/client-nav/scenarios/search-params/react/src/routeTree.gen.ts index 7380b704e1..cff9dca167 100644 --- a/benchmarks/client-nav/scenarios/search-params/react/src/routeTree.gen.ts +++ b/benchmarks/client-nav/scenarios/search-params/react/src/routeTree.gen.ts @@ -9,13 +9,13 @@ // 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 ProductsRouteImport } from './routes/products' -import { Route as CatalogRouteImport } from './routes/catalog' import { Route as IndexRouteImport } from './routes/index' +import { Route as CatalogRouteImport } from './routes/catalog' +import { Route as ProductsRouteImport } from './routes/products' -const ProductsRoute = ProductsRouteImport.update({ - id: '/products', - path: '/products', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const CatalogRoute = CatalogRouteImport.update({ @@ -23,9 +23,9 @@ const CatalogRoute = CatalogRouteImport.update({ path: '/catalog', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ProductsRoute = ProductsRouteImport.update({ + id: '/products', + path: '/products', getParentRoute: () => rootRouteImport, } as any) @@ -61,11 +61,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/products': { - id: '/products' - path: '/products' - fullPath: '/products' - preLoaderRoute: typeof ProductsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/catalog': { @@ -75,11 +75,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CatalogRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/products': { + id: '/products' + path: '/products' + fullPath: '/products' + preLoaderRoute: typeof ProductsRouteImport parentRoute: typeof rootRouteImport } } diff --git a/benchmarks/memory/client/package.json b/benchmarks/memory/client/package.json index 27f4ab551f..d2f9d73df1 100644 --- a/benchmarks/memory/client/package.json +++ b/benchmarks/memory/client/package.json @@ -32,7 +32,8 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "@types/jsdom": "28.0.0", "jsdom": "29.1.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vitest": "^4.1.4" diff --git a/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts index f7f3a7cd8b..80f234f0c8 100644 --- a/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts +++ b/benchmarks/memory/client/scenarios/interrupted-navigations/shared.ts @@ -35,7 +35,6 @@ type RenderedEvent = { } type InterruptedNavigationRouter = { - latestLoadPromise: Promise | undefined load: () => Promise navigate: (options: { to: '/fast/$id' | '/slow/$id' @@ -104,18 +103,6 @@ function assertSlowNavigationSettlement(settlement: NavigationSettlement) { ) } -async function awaitExpectedLoadSettlement(loadPromise: Promise) { - try { - await loadPromise - } catch (reason) { - if (reasonHasAbortShape(reason) || reasonHasCancellationShape(reason)) { - return - } - - throw reason - } -} - function reasonHasAbortShape(reason: unknown) { return reason instanceof DOMException && reason.name === 'AbortError' } @@ -144,7 +131,6 @@ export function createWorkload( let navigateFast: (id: string) => Promise = uninitialized let startSlowNavigation: (id: string) => Promise = uninitializedSettlement - let getLatestLoadPromise: () => Promise | undefined = () => undefined function assertRenderedPage(page: 'shell' | 'fast', id?: string) { const element = container?.querySelector('[data-bench-page]') @@ -203,7 +189,6 @@ export function createWorkload( const mounted = mountTestApp(container) const router = mounted.router as InterruptedNavigationRouter unmount = mounted.unmount - getLatestLoadPromise = () => router.latestLoadPromise unsub = router.subscribe('onRendered', (event) => { if ( @@ -265,7 +250,6 @@ export function createWorkload( expectedRenderedPath = undefined navigateFast = uninitialized startSlowNavigation = uninitializedSettlement - getLatestLoadPromise = () => undefined } async function interrupt( @@ -276,12 +260,6 @@ export function createWorkload( const slowNavigation = startSlowNavigation(slowId) await waitForSlowLoader(slowId) - const slowLoadPromise = getLatestLoadPromise() - - if (!slowLoadPromise) { - throw new Error(`Slow navigation did not start a load for id: ${slowId}`) - } - await navigateFast(fastId) resolveSlowLoader(slowId) @@ -291,7 +269,6 @@ export function createWorkload( assertSlowNavigationSettlement(settlement) } - await awaitExpectedLoadSettlement(slowLoadPromise) await drainMicrotasks() } diff --git a/benchmarks/memory/server/package.json b/benchmarks/memory/server/package.json index 19e16ad830..bdd0a2040a 100644 --- a/benchmarks/memory/server/package.json +++ b/benchmarks/memory/server/package.json @@ -29,7 +29,8 @@ "@platformatic/flame": "^1.6.0", "@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-vue-jsx": "^5.1.5", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vitest": "^4.1.4" diff --git a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts index f7f63c17e6..c98f462538 100644 --- a/benchmarks/memory/server/scenarios/aborted-requests/shared.ts +++ b/benchmarks/memory/server/scenarios/aborted-requests/shared.ts @@ -27,11 +27,11 @@ const textDecoder = new TextDecoder() const abortedRequestModes: Record = { react: { readMode: 'first-chunk', - cancelMode: 'plain', + cancelMode: 'swallow-abort-error', }, solid: { readMode: 'first-chunk', - cancelMode: 'plain', + cancelMode: 'swallow-abort-error', }, vue: { readMode: 'shell-before-deferred', diff --git a/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts index fd373be6b4..4e8e4ce0e4 100644 --- a/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts +++ b/benchmarks/memory/server/scenarios/error-paths/react/src/routeTree.gen.ts @@ -10,24 +10,19 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as TargetIdRouteImport } from './routes/target.$id' -import { Route as MissingIdRouteImport } from './routes/missing.$id' -import { Route as FromIdRouteImport } from './routes/from.$id' import { Route as BoomIdRouteImport } from './routes/boom.$id' +import { Route as FromIdRouteImport } from './routes/from.$id' +import { Route as MissingIdRouteImport } from './routes/missing.$id' +import { Route as TargetIdRouteImport } from './routes/target.$id' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const TargetIdRoute = TargetIdRouteImport.update({ - id: '/target/$id', - path: '/target/$id', - getParentRoute: () => rootRouteImport, -} as any) -const MissingIdRoute = MissingIdRouteImport.update({ - id: '/missing/$id', - path: '/missing/$id', +const BoomIdRoute = BoomIdRouteImport.update({ + id: '/boom/$id', + path: '/boom/$id', getParentRoute: () => rootRouteImport, } as any) const FromIdRoute = FromIdRouteImport.update({ @@ -35,9 +30,14 @@ const FromIdRoute = FromIdRouteImport.update({ path: '/from/$id', getParentRoute: () => rootRouteImport, } as any) -const BoomIdRoute = BoomIdRouteImport.update({ - id: '/boom/$id', - path: '/boom/$id', +const MissingIdRoute = MissingIdRouteImport.update({ + id: '/missing/$id', + path: '/missing/$id', + getParentRoute: () => rootRouteImport, +} as any) +const TargetIdRoute = TargetIdRouteImport.update({ + id: '/target/$id', + path: '/target/$id', getParentRoute: () => rootRouteImport, } as any) @@ -94,18 +94,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/target/$id': { - id: '/target/$id' - path: '/target/$id' - fullPath: '/target/$id' - preLoaderRoute: typeof TargetIdRouteImport - parentRoute: typeof rootRouteImport - } - '/missing/$id': { - id: '/missing/$id' - path: '/missing/$id' - fullPath: '/missing/$id' - preLoaderRoute: typeof MissingIdRouteImport + '/boom/$id': { + id: '/boom/$id' + path: '/boom/$id' + fullPath: '/boom/$id' + preLoaderRoute: typeof BoomIdRouteImport parentRoute: typeof rootRouteImport } '/from/$id': { @@ -115,11 +108,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FromIdRouteImport parentRoute: typeof rootRouteImport } - '/boom/$id': { - id: '/boom/$id' - path: '/boom/$id' - fullPath: '/boom/$id' - preLoaderRoute: typeof BoomIdRouteImport + '/missing/$id': { + id: '/missing/$id' + path: '/missing/$id' + fullPath: '/missing/$id' + preLoaderRoute: typeof MissingIdRouteImport + parentRoute: typeof rootRouteImport + } + '/target/$id': { + id: '/target/$id' + path: '/target/$id' + fullPath: '/target/$id' + preLoaderRoute: typeof TargetIdRouteImport parentRoute: typeof rootRouteImport } } diff --git a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts index 60a1bd1fbc..6004590f9b 100644 --- a/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts +++ b/benchmarks/memory/server/scenarios/peak-large-page/react/src/routeTree.gen.ts @@ -9,8 +9,8 @@ // 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 L1RouteImport } from './routes/l1' import { Route as IndexRouteImport } from './routes/index' +import { Route as L1RouteImport } from './routes/l1' import { Route as L1L2RouteImport } from './routes/l1.l2' import { Route as L1L2L3RouteImport } from './routes/l1.l2.l3' import { Route as L1L2L3L4RouteImport } from './routes/l1.l2.l3.l4' @@ -19,16 +19,16 @@ import { Route as L1L2L3L4L5L6RouteImport } from './routes/l1.l2.l3.l4.l5.l6' import { Route as L1L2L3L4L5L6L7RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7' import { Route as L1L2L3L4L5L6L7L8RouteImport } from './routes/l1.l2.l3.l4.l5.l6.l7.l8' -const L1Route = L1RouteImport.update({ - id: '/l1', - path: '/l1', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const L1Route = L1RouteImport.update({ + id: '/l1', + path: '/l1', + getParentRoute: () => rootRouteImport, +} as any) const L1L2Route = L1L2RouteImport.update({ id: '/l2', path: '/l2', @@ -142,13 +142,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/l1': { - id: '/l1' - path: '/l1' - fullPath: '/l1' - preLoaderRoute: typeof L1RouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -156,6 +149,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/l1': { + id: '/l1' + path: '/l1' + fullPath: '/l1' + preLoaderRoute: typeof L1RouteImport + parentRoute: typeof rootRouteImport + } '/l1/l2': { id: '/l1/l2' path: '/l2' diff --git a/benchmarks/ssr/package.json b/benchmarks/ssr/package.json index fbddd65f79..77abbb0752 100644 --- a/benchmarks/ssr/package.json +++ b/benchmarks/ssr/package.json @@ -20,7 +20,8 @@ "@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-vue-jsx": "^5.1.5", "seroval": "^1.5.4", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vitest": "^4.1.4" diff --git a/benchmarks/ssr/scenarios/control-flow/react/src/routeTree.gen.ts b/benchmarks/ssr/scenarios/control-flow/react/src/routeTree.gen.ts index ed682bf62f..4d4424c222 100644 --- a/benchmarks/ssr/scenarios/control-flow/react/src/routeTree.gen.ts +++ b/benchmarks/ssr/scenarios/control-flow/react/src/routeTree.gen.ts @@ -10,25 +10,25 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as TargetIdRouteImport } from './routes/target.$id' -import { Route as MissingIdRouteImport } from './routes/missing.$id' -import { Route as HeadersIdRouteImport } from './routes/headers.$id' -import { Route as FromIdRouteImport } from './routes/from.$id' import { Route as BoomIdRouteImport } from './routes/boom.$id' +import { Route as FromIdRouteImport } from './routes/from.$id' +import { Route as HeadersIdRouteImport } from './routes/headers.$id' +import { Route as MissingIdRouteImport } from './routes/missing.$id' +import { Route as TargetIdRouteImport } from './routes/target.$id' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const TargetIdRoute = TargetIdRouteImport.update({ - id: '/target/$id', - path: '/target/$id', +const BoomIdRoute = BoomIdRouteImport.update({ + id: '/boom/$id', + path: '/boom/$id', getParentRoute: () => rootRouteImport, } as any) -const MissingIdRoute = MissingIdRouteImport.update({ - id: '/missing/$id', - path: '/missing/$id', +const FromIdRoute = FromIdRouteImport.update({ + id: '/from/$id', + path: '/from/$id', getParentRoute: () => rootRouteImport, } as any) const HeadersIdRoute = HeadersIdRouteImport.update({ @@ -36,14 +36,14 @@ const HeadersIdRoute = HeadersIdRouteImport.update({ path: '/headers/$id', getParentRoute: () => rootRouteImport, } as any) -const FromIdRoute = FromIdRouteImport.update({ - id: '/from/$id', - path: '/from/$id', +const MissingIdRoute = MissingIdRouteImport.update({ + id: '/missing/$id', + path: '/missing/$id', getParentRoute: () => rootRouteImport, } as any) -const BoomIdRoute = BoomIdRouteImport.update({ - id: '/boom/$id', - path: '/boom/$id', +const TargetIdRoute = TargetIdRouteImport.update({ + id: '/target/$id', + path: '/target/$id', getParentRoute: () => rootRouteImport, } as any) @@ -117,18 +117,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/target/$id': { - id: '/target/$id' - path: '/target/$id' - fullPath: '/target/$id' - preLoaderRoute: typeof TargetIdRouteImport + '/boom/$id': { + id: '/boom/$id' + path: '/boom/$id' + fullPath: '/boom/$id' + preLoaderRoute: typeof BoomIdRouteImport parentRoute: typeof rootRouteImport } - '/missing/$id': { - id: '/missing/$id' - path: '/missing/$id' - fullPath: '/missing/$id' - preLoaderRoute: typeof MissingIdRouteImport + '/from/$id': { + id: '/from/$id' + path: '/from/$id' + fullPath: '/from/$id' + preLoaderRoute: typeof FromIdRouteImport parentRoute: typeof rootRouteImport } '/headers/$id': { @@ -138,18 +138,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HeadersIdRouteImport parentRoute: typeof rootRouteImport } - '/from/$id': { - id: '/from/$id' - path: '/from/$id' - fullPath: '/from/$id' - preLoaderRoute: typeof FromIdRouteImport + '/missing/$id': { + id: '/missing/$id' + path: '/missing/$id' + fullPath: '/missing/$id' + preLoaderRoute: typeof MissingIdRouteImport parentRoute: typeof rootRouteImport } - '/boom/$id': { - id: '/boom/$id' - path: '/boom/$id' - fullPath: '/boom/$id' - preLoaderRoute: typeof BoomIdRouteImport + '/target/$id': { + id: '/target/$id' + path: '/target/$id' + fullPath: '/target/$id' + preLoaderRoute: typeof TargetIdRouteImport parentRoute: typeof rootRouteImport } } diff --git a/benchmarks/ssr/scenarios/global-middleware/react/src/routeTree.gen.ts b/benchmarks/ssr/scenarios/global-middleware/react/src/routeTree.gen.ts index aa916f0800..8dfaa80562 100644 --- a/benchmarks/ssr/scenarios/global-middleware/react/src/routeTree.gen.ts +++ b/benchmarks/ssr/scenarios/global-middleware/react/src/routeTree.gen.ts @@ -10,8 +10,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as PageIdRouteImport } from './routes/page.$id' import { Route as ApiFnUrlsRouteImport } from './routes/api.fn-urls' +import { Route as PageIdRouteImport } from './routes/page.$id' import { Route as ApiPingIdRouteImport } from './routes/api.ping.$id' const IndexRoute = IndexRouteImport.update({ @@ -19,16 +19,16 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const PageIdRoute = PageIdRouteImport.update({ - id: '/page/$id', - path: '/page/$id', - getParentRoute: () => rootRouteImport, -} as any) const ApiFnUrlsRoute = ApiFnUrlsRouteImport.update({ id: '/api/fn-urls', path: '/api/fn-urls', getParentRoute: () => rootRouteImport, } as any) +const PageIdRoute = PageIdRouteImport.update({ + id: '/page/$id', + path: '/page/$id', + getParentRoute: () => rootRouteImport, +} as any) const ApiPingIdRoute = ApiPingIdRouteImport.update({ id: '/api/ping/$id', path: '/api/ping/$id', @@ -78,13 +78,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/page/$id': { - id: '/page/$id' - path: '/page/$id' - fullPath: '/page/$id' - preLoaderRoute: typeof PageIdRouteImport - parentRoute: typeof rootRouteImport - } '/api/fn-urls': { id: '/api/fn-urls' path: '/api/fn-urls' @@ -92,6 +85,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiFnUrlsRouteImport parentRoute: typeof rootRouteImport } + '/page/$id': { + id: '/page/$id' + path: '/page/$id' + fullPath: '/page/$id' + preLoaderRoute: typeof PageIdRouteImport + parentRoute: typeof rootRouteImport + } '/api/ping/$id': { id: '/api/ping/$id' path: '/api/ping/$id' diff --git a/benchmarks/ssr/scenarios/serialization/react/src/routeTree.gen.ts b/benchmarks/ssr/scenarios/serialization/react/src/routeTree.gen.ts index 92bf0bb732..49bdbd036b 100644 --- a/benchmarks/ssr/scenarios/serialization/react/src/routeTree.gen.ts +++ b/benchmarks/ssr/scenarios/serialization/react/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 RichIdRouteImport } from './routes/rich.$id' import { Route as PlainIdRouteImport } from './routes/plain.$id' +import { Route as RichIdRouteImport } from './routes/rich.$id' -const RichIdRoute = RichIdRouteImport.update({ - id: '/rich/$id', - path: '/rich/$id', - getParentRoute: () => rootRouteImport, -} as any) const PlainIdRoute = PlainIdRouteImport.update({ id: '/plain/$id', path: '/plain/$id', getParentRoute: () => rootRouteImport, } as any) +const RichIdRoute = RichIdRouteImport.update({ + id: '/rich/$id', + path: '/rich/$id', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/plain/$id': typeof PlainIdRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/rich/$id': { - id: '/rich/$id' - path: '/rich/$id' - fullPath: '/rich/$id' - preLoaderRoute: typeof RichIdRouteImport - parentRoute: typeof rootRouteImport - } '/plain/$id': { id: '/plain/$id' path: '/plain/$id' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PlainIdRouteImport parentRoute: typeof rootRouteImport } + '/rich/$id': { + id: '/rich/$id' + path: '/rich/$id' + fullPath: '/rich/$id' + preLoaderRoute: typeof RichIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/benchmarks/ssr/scenarios/server-fns/react/src/routeTree.gen.ts b/benchmarks/ssr/scenarios/server-fns/react/src/routeTree.gen.ts index 1abf49d56b..4daa5f03b0 100644 --- a/benchmarks/ssr/scenarios/server-fns/react/src/routeTree.gen.ts +++ b/benchmarks/ssr/scenarios/server-fns/react/src/routeTree.gen.ts @@ -10,24 +10,24 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as SsrCallIdRouteImport } from './routes/ssr-call.$id' import { Route as ApiFnUrlsRouteImport } from './routes/api.fn-urls' +import { Route as SsrCallIdRouteImport } from './routes/ssr-call.$id' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const SsrCallIdRoute = SsrCallIdRouteImport.update({ - id: '/ssr-call/$id', - path: '/ssr-call/$id', - getParentRoute: () => rootRouteImport, -} as any) const ApiFnUrlsRoute = ApiFnUrlsRouteImport.update({ id: '/api/fn-urls', path: '/api/fn-urls', getParentRoute: () => rootRouteImport, } as any) +const SsrCallIdRoute = SsrCallIdRouteImport.update({ + id: '/ssr-call/$id', + path: '/ssr-call/$id', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -68,13 +68,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/ssr-call/$id': { - id: '/ssr-call/$id' - path: '/ssr-call/$id' - fullPath: '/ssr-call/$id' - preLoaderRoute: typeof SsrCallIdRouteImport - parentRoute: typeof rootRouteImport - } '/api/fn-urls': { id: '/api/fn-urls' path: '/api/fn-urls' @@ -82,6 +75,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiFnUrlsRouteImport parentRoute: typeof rootRouteImport } + '/ssr-call/$id': { + id: '/ssr-call/$id' + path: '/ssr-call/$id' + fullPath: '/ssr-call/$id' + preLoaderRoute: typeof SsrCallIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/docs/router/api/router/LinkOptionsType.md b/docs/router/api/router/LinkOptionsType.md index 481a5b4a3d..cf20e0ff19 100644 --- a/docs/router/api/router/LinkOptionsType.md +++ b/docs/router/api/router/LinkOptionsType.md @@ -9,7 +9,7 @@ The `LinkOptions` type extends the [`NavigateOptions`](./NavigateOptionsType.md) type LinkOptions = NavigateOptions & { target?: HTMLAnchorElement['target'] activeOptions?: ActiveOptions - preload?: false | 'intent' + preload?: false | 'intent' | 'viewport' | 'render' preloadDelay?: number disabled?: boolean } @@ -42,7 +42,7 @@ The `LinkOptions` object accepts/contains the following properties: - Type: `number` - Optional -- Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled. +- Delay focus and hover intent preloading by this many milliseconds. Touch intent preloads immediately. If focus or hover exits before the delay, the preload will be cancelled. ### `disabled` diff --git a/docs/router/api/router/RouteMatchType.md b/docs/router/api/router/RouteMatchType.md index 251c2c031e..b5009ebe68 100644 --- a/docs/router/api/router/RouteMatchType.md +++ b/docs/router/api/router/RouteMatchType.md @@ -11,9 +11,8 @@ interface RouteMatch { routeId: string pathname: string params: Route['allParams'] - status: 'pending' | 'success' | 'error' | 'redirected' | 'notFound' + status: 'pending' | 'success' | 'error' | 'notFound' isFetching: false | 'beforeLoad' | 'loader' - showPending: boolean error: unknown paramsError: unknown searchError: unknown @@ -21,9 +20,17 @@ interface RouteMatch { loaderData?: Route['loaderData'] context: Route['allContext'] search: Route['fullSearchSchema'] - fetchedAt: number abortController: AbortController - cause: 'enter' | 'stay' + cause: 'preload' | 'enter' | 'stay' ssr?: boolean | 'data-only' } ``` + +`status` describes the match's render state. `isFetching` independently exposes +active `beforeLoad` or loader work. In particular, a successful match can report +`isFetching: 'loader'` while stale data remains visible during a background +reload. + +The router state can contain matches below the pending, error, or not-found +boundary. Those matches remain observable as part of the structurally matched +lane even though the route renderer stops at the boundary. diff --git a/docs/router/api/router/RouteOptionsType.md b/docs/router/api/router/RouteOptionsType.md index 1fb4c4eb30..ac35cdc845 100644 --- a/docs/router/api/router/RouteOptionsType.md +++ b/docs/router/api/router/RouteOptionsType.md @@ -56,6 +56,7 @@ The `RouteOptions` type accepts an object with the following properties: - Type: `(rawSearchParams: unknown) => TSearchSchema` - Optional - A function that will be called when this route is matched and passed the raw search params from the current location and return valid parsed search params. If this function throws, the route will be put into an error state and the error will be thrown during render. If this function does not throw, its return value will be used as the route's search params and the return type will be inferred into the rest of the router. +- This is a planning callback. It must be deterministic and side-effect-free for the same input; do not navigate or mutate application/router state from it. - Optionally, the parameter type can be tagged with the `SearchSchemaInput` type like this: `(searchParams: TSearchSchemaInput & SearchSchemaInput) => TSearchSchema`. If this tag is present, `TSearchSchemaInput` will be used to type the `search` property of `` and `navigate()` **instead of** `TSearchSchema`. The difference between `TSearchSchemaInput` and `TSearchSchema` can be useful, for example, to express optional search parameters. ### `search.middlewares` property @@ -70,6 +71,7 @@ The `RouteOptions` type accepts an object with the following properties: - Type: `(rawParams: Record) => TParams` - Optional - A function that will be called when this route is matched and passed the raw params from the current location and return valid parsed params. If this function throws, the route will be put into an error state and the error will be thrown during render. If this function does not throw, its return value will be used as the route's params and the return type will be inferred into the rest of the router. +- This is a planning callback. It must be deterministic and side-effect-free for the same input. ### `stringifyParams` method (⚠️ deprecated, use `params.stringify` instead) @@ -82,6 +84,7 @@ The `RouteOptions` type accepts an object with the following properties: - Type: `(rawParams: Record) => TParams | false` - Optional - A function that will be called when this route is matched and passed the raw params from the current location and return valid parsed params. If this function throws, the route will be put into an error state and the error will be thrown during render. If this function returns parsed params, its return value will be used as the route's params and the return type will be inferred into the rest of the router. +- This is a planning callback. It must be deterministic and side-effect-free for the same input. - Experimental: returning `false` during incoming route matching skips this route and allows matching to continue to another candidate route. ### `params.priority` property @@ -112,14 +115,14 @@ type beforeLoad = ( location: ParsedLocation navigate: NavigateFn // @deprecated buildLocation: BuildLocationFn - cause: 'enter' | 'stay' + cause: 'preload' | 'enter' | 'stay' }, ) => Promise | TRouteContext | void ``` - Optional - [`ParsedLocation`](./ParsedLocationType.md) -- This async function is called before a route is loaded. If an error is thrown here, the route's loader will not be called and the route will not render. If thrown during a navigation, the navigation will be canceled and the error will be passed to the `onError` function. If thrown during a preload event, the error will be logged to the console and the preload will fail. +- This async function is called before a route is loaded. If it fails, the route's loader and its descendants will not run. During navigation, ordinary errors become the match's error state and are passed to the `onError` function. During a preload, ordinary errors and not-found results are represented in the returned speculative match lane instead of rejecting the `preloadRoute` promise. - If this function returns a promise, the route will be put into a pending state and cause rendering to suspend until the promise resolves. If this route's pendingMs threshold is reached, the `pendingComponent` will be shown until it resolves. If the promise rejects, the route will be put into an error state and the error will be thrown during render. - If this function returns a `TRouteContext` object, that object will be merged into the route's context and be made available in the `loader` and other related route components/methods. - It's common to use this function to check if a user is authenticated and redirect them to a login page if they are not. To do this, you can either return or throw a `redirect` object from this function. @@ -156,9 +159,9 @@ type loader = - Optional - [`ParsedLocation`](./ParsedLocationType.md) -- This async function is called when a route is matched and passed the route's match object. If an error is thrown here, the route will be put into an error state and the error will be thrown during render. If thrown during a navigation, the navigation will be canceled and the error will be passed to the `onError` function. If thrown during a preload event, the error will be logged to the console and the preload will fail. +- This async function is called when a route is matched and passed the route's match object. During navigation, ordinary errors become the match's error state and are passed to the `onError` function. During a preload, ordinary errors and not-found results are represented in the returned speculative match lane instead of rejecting the `preloadRoute` promise. - If this function returns a promise, the route will be put into a pending state and cause rendering to suspend until the promise resolves. If this route's pendingMs threshold is reached, the `pendingComponent` will be shown until it resolves. If the promise rejects, the route will be put into an error state and the error will be thrown during render. -- If this function returns a `TLoaderData` object, that object will be stored on the route match until the route match is no longer active. It can be accessed using the `useLoaderData` hook in any component that is a child of the route match before another `` is rendered. +- If this function returns a `TLoaderData` object, that object will be stored on the route match and can remain available in the in-memory cache after the match becomes inactive. Navigation-owned data uses `gcTime` for retention, while preload-owned data uses `preloadGcTime`. It can be accessed using the `useLoaderData` hook in any component that is a child of the route match before another `` is rendered. - Deps must be returned by your `loaderDeps` function in order to appear. - Use the object form to configure loader-specific behavior like `staleReloadMode`. - `staleReloadMode: 'background'` preserves stale-while-revalidate behavior for stale successful matches. @@ -176,6 +179,7 @@ type loaderDeps = (opts: { search: TFullSearchSchema }) => Record - Optional - A function that will be called before this route is matched to provide additional unique identification to the route match and serve as a dependency tracker for when the match should be reloaded. It should return any serializable value that can uniquely identify the route match from navigation to navigation. +- This is a planning callback and cache-key function. It must be deterministic and side-effect-free for the same validated search input. The returned value and any serialization methods on it, such as `toJSON`, must also be side-effect-free. - By default, path params are already used to uniquely identify a route match, so it's unnecessary to return these here. - If your route match relies on search params for unique identification, it's required that you return them here so they can be made available in the `loader`'s `deps` argument. @@ -186,19 +190,26 @@ type loaderDeps = (opts: { search: TFullSearchSchema }) => Record - Defaults to `routerOptions.defaultStaleTime`, which defaults to `0` - The amount of time in milliseconds that a route match's loader data will be considered fresh. If a route match is matched again within this time frame, its loader data will not be reloaded. +### `preload` property + +- Type: `boolean` +- Optional +- Defaults to `true` +- If `false`, speculative preloads still run this route's `beforeLoad` function but skip its `loader`. A navigation runs both `beforeLoad` and the skipped `loader` normally. + ### `preloadStaleTime` property - Type: `number` - Optional - Defaults to `routerOptions.defaultPreloadStaleTime`, which defaults to `30_000` ms (30 seconds) -- The amount of time in milliseconds that a route match's loader data will be considered fresh when preloading. If a route match is preloaded again within this time frame, its loader data will not be reloaded. If a route match is loaded (for navigation) within this time frame, the normal `staleTime` is used instead. +- The amount of time in milliseconds that loader data produced by a preload is considered fresh. Another preload or the first navigation can reuse it within this interval. After navigation accepts the generation, subsequent freshness uses `staleTime`. ### `gcTime` property - Type: `number` - Optional -- Defaults to `routerOptions.defaultGcTime`, which defaults to 30 minutes. -- The amount of time in milliseconds that a route match's loader data will be kept in memory after a preload or it is no longer in use. +- Defaults to `routerOptions.defaultGcTime`, which defaults to 5 minutes. +- The retention window in milliseconds for unused loader data from an ordinary load. Once the data is older than this value, it is eligible for pruning during a later cache reconciliation. ### `shouldReload` property @@ -234,12 +245,12 @@ type loaderDeps = (opts: { search: TFullSearchSchema }) => Record - Defaults to `routerOptions.defaultPendingMinMs` which defaults to `500` - The minimum amount of time in milliseconds that the pending component will be shown for if it is shown. This is useful to prevent the pending component from flashing on the screen for a split second. -### `preloadMaxAge` property +### `preloadGcTime` property - Type: `number` - Optional -- Defaults to `30_000` ms (30 seconds) -- The maximum amount of time in milliseconds that a route's preloaded route data will be cached for. If a route is not matched within this time frame, its loader data will be discarded. +- Defaults to `routerOptions.defaultPreloadGcTime`, which defaults to 5 minutes. +- The retention window in milliseconds for unused loader data produced by a preload. Once the data is older than this value, it is eligible for pruning during a later cache reconciliation. Use `preloadStaleTime` to control whether retained data is fresh enough to reuse without reloading. ### `preSearchFilters` property (⚠️ deprecated, use `search.middlewares` instead) @@ -262,7 +273,7 @@ type loaderDeps = (opts: { search: TFullSearchSchema }) => Record - Type: `(error: any) => void` - Optional - A function that will be called when an error is thrown during a navigation or preload event. -- If this function throws a [`redirect`](./redirectFunction.md), then the router will process and apply the redirect immediately. +- If this function throws a [`redirect`](./redirectFunction.md), the redirect replaces the original error and becomes control flow for the current navigation or preload operation. If it throws a not-found result, that result replaces the original error in the current match lane. ### `onEnter` property diff --git a/docs/router/api/router/RouterOptionsType.md b/docs/router/api/router/RouterOptionsType.md index 7127e1191e..91f13e0e50 100644 --- a/docs/router/api/router/RouterOptionsType.md +++ b/docs/router/api/router/RouterOptionsType.md @@ -129,14 +129,14 @@ The `RouterOptions` type accepts an object with the following properties and met - Type: `number` - Optional -- Defaults to `routerOptions.defaultGcTime`, which defaults to 30 minutes. +- Defaults to 5 minutes. - The default `preloadGcTime` a route should use if no preloadGcTime is provided. ### `defaultGcTime` property - Type: `number` - Optional -- Defaults to 30 minutes. +- Defaults to 5 minutes. - The default `gcTime` a route should use if no gcTime is provided. ### `defaultOnCatch` property diff --git a/docs/router/api/router/RouterStateType.md b/docs/router/api/router/RouterStateType.md index a77f4e775f..4da9cd9bb6 100644 --- a/docs/router/api/router/RouterStateType.md +++ b/docs/router/api/router/RouterStateType.md @@ -3,16 +3,17 @@ id: RouterStateType title: RouterState type --- -The `RouterState` type represents shape of the internal state of the router. The Router's internal state is useful, if you need to access certain internals of the router, such as any pending matches, is the router in its loading state, etc. +The `RouterState` type represents the observable state of the router, including +the requested and resolved locations, match presentation, and foreground loading +status. ```tsx type RouterState = { status: 'pending' | 'idle' isLoading: boolean - isTransitioning: boolean matches: Array location: ParsedLocation - resolvedLocation: ParsedLocation + resolvedLocation?: ParsedLocation } ``` @@ -23,22 +24,24 @@ The `RouterState` type contains all of the properties that are available on the ### `status` property - Type: `'pending' | 'idle'` -- The current status of the router. If the router is pending, it means that it is currently loading a route or the router is still transitioning to the new route. +- The current foreground status of the router. `pending` means the requested route is still loading or waiting for its framework transition to settle. ### `isLoading` property - Type: `boolean` -- `true` if the router is currently loading a route or waiting for a route to finish loading. - -### `isTransitioning` property - -- Type: `boolean` -- `true` if the router is currently transitioning to a new route. +- `true` when `status` is `pending`. +- Background loader refreshes do not change this value. Inspect each match's `isFetching` field to observe foreground `beforeLoad`/loader work and background loader work. ### `matches` property - Type: [`Array`](./RouteMatchType.md) -- An array of all of the route matches that have been resolved and are currently active. +- The match presentation currently exposed to the application. +- A navigation can keep the previous presentation visible until pending UI is + published. Once the destination is presented, this contains its complete + structurally matched lane, including descendants that are still loading. +- Error and not-found results also preserve the complete structurally matched + lane. Rendering stops at the selected pending or terminal boundary; it does + not truncate this array. ### `location` property @@ -47,5 +50,5 @@ The `RouterState` type contains all of the properties that are available on the ### `resolvedLocation` property -- Type: [`ParsedLocation`](./ParsedLocationType.md) -- The location that the router has resolved and loaded. +- Type: [`ParsedLocation`](./ParsedLocationType.md) | `undefined` +- The location whose load and framework transition have settled. It is `undefined` before the initial location resolves. diff --git a/docs/router/api/router/RouterType.md b/docs/router/api/router/RouterType.md index 757a6098ca..cf8fdebeae 100644 --- a/docs/router/api/router/RouterType.md +++ b/docs/router/api/router/RouterType.md @@ -35,16 +35,6 @@ An instance of the `Router` has the following properties and methods: - Matches a pathname and search params against the router's route tree and returns an array of route matches. - If `opts.throwOnError` is `true`, any errors that occur during the matching process will be thrown (in addition to being returned in the route match's `error` property). -### `.cancelMatch` method - -- Type: `(matchId: string) => void` -- Cancels a route match that is currently pending by calling `match.abortController.abort()`. - -### `.cancelMatches` method - -- Type: `() => void` -- Cancels all route matches that are currently pending by calling `match.abortController.abort()` on each one. - ### `.buildLocation` method Builds a new parsed location object that can be used later to navigate to a new location. @@ -138,23 +128,27 @@ Navigates to a new location. ### `.invalidate` method -Invalidates route matches by forcing their `beforeLoad` and `load` functions to be called again. +Invalidates selected route-match generations, reruns their loading lifecycle, +reruns `beforeLoad`, and reloads their loaders through the normal loading +protocol. - Type: `(opts?: {filter?: (d: MakeRouteMatchUnion) => boolean, sync?: boolean, forcePending?: boolean }) => Promise` - This is useful any time your loader data might be out of date or stale. For example, if you have a route that displays a list of posts, and you have a loader function that fetches the list of posts from an API, you might want to invalidate the route matches for that route any time a new post is created so that the list of posts is always up-to-date. -- if `filter` is not supplied, all matches will be invalidated -- if `filter` is supplied, only matches for which `filter` returns `true` will be invalidated. -- if `sync` is true, the promise returned by this function will only resolve once all loaders have finished. -- if `forcePending` is true, the invalidated matches will be put into `'pending'` state regardless whether they are in `'error'` state or not. +- If `filter` is not supplied, all committed, cached, and in-flight match generations are invalidated. +- If `filter` is supplied, it is evaluated against committed, cached, and in-flight matches. Selecting one generation invalidates every committed, cached, or in-flight generation with the same match ID. +- Invalidation reruns `beforeLoad`; reusable loader data is marked stale and reloads through the normal loading protocol. Route-level `context` remains reusable while the match ID is unchanged. +- If `sync` is `true`, stale loader work is blocking and the returned promise resolves after it finishes instead of leaving a background refresh detached. +- If `forcePending` is `true`, selected routes that need loading enter the normal pending protocol even when successful data was already available. - You might also want to invalidate the Router if you imperatively `reset` the router's `CatchBoundary` to trigger loaders again. ### `.clearCache` method -Remove cached route matches. +Remove cached route matches and matching active preloads. - Type: `(opts?: {filter?: (d: MakeRouteMatchUnion) => boolean}) => void` -- if `filter` is not supplied, all cached matches will be removed -- if `filter` is supplied, only matches for which `filter` returns `true` will be removed. +- If `filter` is not supplied, all cached matches and active preload lanes are removed. +- If `filter` is supplied, matching cached matches are removed. An active preload lane is canceled when any match in that lane passes the filter. +- Current committed and presented matches are not removed. ### `.load` method @@ -170,16 +164,28 @@ Loads all of the currently matched route matches and resolves when they are all Preloads all of the matches that match the provided `NavigateOptions`. -> ⚠️⚠️⚠️ **Preloaded route matches are not stored long-term in the router state. They are only stored until the next attempted navigation action.** +An active preload is speculative and is not published as the current match +presentation. Successful loader data can enter the normal in-memory route +cache. Its freshness follows `preloadStaleTime`; once unused and older than +`preloadGcTime`, it is eligible for pruning during a later cache +reconciliation. + +Every preload and navigation runs its own `beforeLoad` chain. A later lane can +reuse successful settled loader data or join loader work that is still in +flight, but it never reuses `beforeLoad` context or an already-settled +redirect, error, or not-found result. If joined loader work later produces a +terminal outcome, all current consumers of that flight observe it. -- Type: `(opts?: NavigateOptions) => Promise` +- Type: `(opts: NavigateOptions) => Promise` - Properties - `opts` - Type: `NavigateOptions` - - Optional, defaults to the current location. + - Required. - The options that will be used to determine which route matches to preload. - Returns - - A promise that resolves with an array of all of the route matches that were preloaded. + - A promise that resolves with the speculative route-match lane. An ordinary error or not-found is represented by a terminal match array rather than a rejected promise. + - It resolves with `undefined` when cancellation or control flow does not produce a reusable lane. + - The method is also available on server router instances. It remains speculative and does not change the request's current location or presented matches. ### `.loadRouteChunk` method diff --git a/docs/router/api/router/useChildMatchesHook.md b/docs/router/api/router/useChildMatchesHook.md index d6de892192..13212e61e0 100644 --- a/docs/router/api/router/useChildMatchesHook.md +++ b/docs/router/api/router/useChildMatchesHook.md @@ -5,8 +5,9 @@ title: useChildMatches hook The `useChildMatches` hook returns all of the child [`RouteMatch`](./RouteMatchType.md) objects from the closest match down to the leaf-most match. **It does not include the current match, which can be obtained using the `useMatch` hook.** -> [!IMPORTANT] -> If the router has pending matches and they are showing their pending component fallbacks, pending matches are used instead of active matches. +The result is derived from the router's current match presentation. It can +include still-loading descendants and descendants below the current render +boundary. ## useChildMatches options diff --git a/docs/router/api/router/useMatchesHook.md b/docs/router/api/router/useMatchesHook.md index d7a1be8dc3..e57c6c2c7e 100644 --- a/docs/router/api/router/useMatchesHook.md +++ b/docs/router/api/router/useMatchesHook.md @@ -3,7 +3,7 @@ id: useMatchesHook title: useMatches hook --- -The `useMatches` hook returns all of the [`RouteMatch`](./RouteMatchType.md) objects from the router **regardless of its callers position in the React component tree**. +The `useMatches` hook returns the router's complete presented array of [`RouteMatch`](./RouteMatchType.md) objects **regardless of its caller's position in the component tree**. The array can include still-loading descendants or matches below a pending/error/not-found render boundary. > [!TIP] > If you only want the parent or child matches, then you can use the [`useParentMatches`](./useParentMatchesHook.md) or the [`useChildMatches`](./useChildMatchesHook.md) based on the selection you need. diff --git a/docs/router/api/router/useParentMatchesHook.md b/docs/router/api/router/useParentMatchesHook.md index 7afc5585a6..a0ce985411 100644 --- a/docs/router/api/router/useParentMatchesHook.md +++ b/docs/router/api/router/useParentMatchesHook.md @@ -5,8 +5,9 @@ title: useParentMatches hook The `useParentMatches` hook returns all of the parent [`RouteMatch`](./RouteMatchType.md) objects from the root down to the immediate parent of the current match in context. **It does not include the current match, which can be obtained using the `useMatch` hook.** -> [!IMPORTANT] -> If the router has pending matches and they are showing their pending component fallbacks, pending matches are used instead of active matches. +The result is derived from the router's current match presentation. During a +navigation that may still be the previous presentation; after pending UI is +published it is the destination's complete structurally matched lane. ## useParentMatches options diff --git a/docs/router/config.json b/docs/router/config.json index bb30fabc4a..1258ddd817 100644 --- a/docs/router/config.json +++ b/docs/router/config.json @@ -94,7 +94,7 @@ ] }, { - "label": "Routing", + "label": "Core Routing", "children": [ { "label": "Routing Concepts", @@ -123,6 +123,10 @@ { "label": "File Naming Conventions", "to": "routing/file-naming-conventions" + }, + { + "label": "URL Rewrites", + "to": "guide/url-rewrites" } ], "frameworks": [ @@ -137,47 +141,76 @@ ] }, { - "label": "Guides", + "label": "Navigation & URL State", + "collapsible": true, "children": [ { - "label": "Code Splitting", - "to": "guide/code-splitting" + "label": "Navigation", + "to": "guide/navigation" }, { - "label": "Automatic Code Splitting", - "to": "guide/automatic-code-splitting" + "label": "Link Options", + "to": "guide/link-options" }, { - "label": "Creating a Router", - "to": "guide/creating-a-router" + "label": "Custom Links", + "to": "guide/custom-link" }, { - "label": "Outlets", - "to": "guide/outlets" + "label": "Path Params", + "to": "guide/path-params" }, { - "label": "Navigation", - "to": "guide/navigation" + "label": "Search Params", + "to": "guide/search-params" }, { - "label": "Router Events", - "to": "guide/router-events" + "label": "Custom Search Param Serialization", + "to": "guide/custom-search-param-serialization" }, { - "label": "Path Params", - "to": "guide/path-params" + "label": "Route Masking", + "to": "guide/route-masking" }, { - "label": "Search Params", - "to": "guide/search-params" + "label": "Navigation Blocking", + "to": "guide/navigation-blocking" }, { - "label": "Link Options", - "to": "guide/link-options" + "label": "History Types", + "to": "guide/history-types" }, { - "label": "Custom Links", - "to": "guide/custom-link" + "label": "Scroll Restoration", + "to": "guide/scroll-restoration" + }, + { + "label": "Internationalization", + "to": "guide/internationalization-i18n" + } + ], + "frameworks": [ + { + "label": "react", + "children": [] + }, + { + "label": "solid", + "children": [] + } + ] + }, + { + "label": "Data & Rendering", + "collapsible": true, + "children": [ + { + "label": "Code Splitting", + "to": "guide/code-splitting" + }, + { + "label": "Automatic Code Splitting", + "to": "guide/automatic-code-splitting" }, { "label": "Data Loading", @@ -195,14 +228,6 @@ "label": "Data Mutations", "to": "guide/data-mutations" }, - { - "label": "Type Safety", - "to": "guide/type-safety" - }, - { - "label": "Type Utilities", - "to": "guide/type-utilities" - }, { "label": "Preloading", "to": "guide/preloading" @@ -212,20 +237,48 @@ "to": "guide/document-head-management" }, { - "label": "Route Masking", - "to": "guide/route-masking" + "label": "SSR", + "to": "guide/ssr" }, { - "label": "Navigation Blocking", - "to": "guide/navigation-blocking" + "label": "Render Optimizations", + "to": "guide/render-optimizations" + } + ], + "frameworks": [ + { + "label": "react", + "children": [] }, { - "label": "Custom Search Param Serialization", - "to": "guide/custom-search-param-serialization" + "label": "solid", + "children": [] + } + ] + }, + { + "label": "Router Configuration", + "collapsible": true, + "children": [ + { + "label": "Creating a Router", + "to": "guide/creating-a-router" }, { - "label": "History Types", - "to": "guide/history-types" + "label": "Outlets", + "to": "guide/outlets" + }, + { + "label": "Router Events", + "to": "guide/router-events" + }, + { + "label": "Type Safety", + "to": "guide/type-safety" + }, + { + "label": "Type Utilities", + "to": "guide/type-utilities" }, { "label": "Router Context", @@ -239,21 +292,9 @@ "label": "Authenticated Routes", "to": "guide/authenticated-routes" }, - { - "label": "Scroll Restoration", - "to": "guide/scroll-restoration" - }, { "label": "Static Route Data", "to": "guide/static-route-data" - }, - { - "label": "SSR", - "to": "guide/ssr" - }, - { - "label": "Render Optimizations", - "to": "guide/render-optimizations" } ], "frameworks": [ diff --git a/docs/router/decisions-on-dx.md b/docs/router/decisions-on-dx.md index 26c366a9da..a06ec4b331 100644 --- a/docs/router/decisions-on-dx.md +++ b/docs/router/decisions-on-dx.md @@ -39,7 +39,7 @@ But to achieve this, we had to make some decisions that deviate from the norms i ## Why is the Router's configuration done this way? -When you want to leverage the TypeScript's inference features to its fullest, you'll quickly realize that _Generics_ are your best friend. And so, TanStack Router uses Generics everywhere to ensure that the types of your routes are inferred as much as possible. +When you want to leverage TypeScript's inference features to its fullest, you'll quickly realize that _Generics_ are your best friend. And so, TanStack Router uses Generics everywhere to ensure that the types of your routes are inferred as much as possible. This means that you have to define your routes in a way that allows TypeScript to infer the types of your routes as much as possible. @@ -219,7 +219,7 @@ TanStack Router's file-based routing is designed to solve all of these issues. I The file-based routing approach is powered by the TanStack Router Bundler Plugin. It performs 3 essential tasks that solve the pain points in route configuration when using code-based routing: 1. **Route configuration boilerplate**: It generates the boilerplate for your route configurations. -2. **Route tree stitching**: It stitches together your route configurations into a single cohesive route-tree. Also in the background, it correctly updates the route configurations to define the `getParentRoute` function match the routes with their parent routes. +2. **Route tree stitching**: It stitches together your route configurations into a single cohesive route-tree. Also in the background, it correctly updates the route configurations to define the `getParentRoute` function and so matches the routes with their parent routes. 3. **Code-splitting**: It automatically code-splits your route content components and updates the route configurations with the correct component. Additionally, at runtime, it ensures that the correct component is loaded when the route is visited. Let's take a look at how the route configuration for the previous example would look like with file-based routing. diff --git a/docs/router/guide/automatic-code-splitting.md b/docs/router/guide/automatic-code-splitting.md index 4612c298c6..be599845bc 100644 --- a/docs/router/guide/automatic-code-splitting.md +++ b/docs/router/guide/automatic-code-splitting.md @@ -35,7 +35,7 @@ This happens seamlessly, without requiring you to manually split your code or ma ### The transformation process -When you enable automatic code splitting, the bundler plugin does this by using static code analysis look at your the code in your route files to transform them into optimized outputs. +When you enable automatic code splitting, the bundler plugin does this by using static code analysis to look at the code in your route files and to transform them into optimized outputs. This transformation process produces two key outputs when each of your route files are processed: @@ -48,7 +48,7 @@ This process ensures that your original code remains clean and readable, while t The decision of what to split into separate chunks is crucial for optimizing your application's performance. TanStack Router uses a concept called "**Split Groupings**" to determine how different parts of your route should be bundled together. -Split groupings are arrays of properties that tell TanStack Router how to bundle different parts of your route together. Each grouping is an list of property names that you want to bundle together into a single lazy-loaded chunk. +Split groupings are arrays of properties that tell TanStack Router how to bundle different parts of your route together. Each grouping is a list of property names that you want to bundle together into a single lazy-loaded chunk. The available properties to split are: @@ -76,7 +76,7 @@ This means that it creates three separate lazy-loaded chunks for each route. Res ### Rules of Splitting -For automatic code splitting to work, there are some rules in-place to make sure that this process can reliably and predictably happen. +For automatic code splitting to work, there are some rules in place to make sure that this process can happen reliably and predictably. #### Do not export route properties diff --git a/docs/router/guide/code-splitting.md b/docs/router/guide/code-splitting.md index 698bf262b5..b1423253be 100644 --- a/docs/router/guide/code-splitting.md +++ b/docs/router/guide/code-splitting.md @@ -115,7 +115,7 @@ These are the only options that `createLazyFileRoute` supports: ### Example code splitting with `.lazy.tsx` -When you are using `.lazy.tsx` you can split your route into two files to enable code splitting: +When you are using `.lazy.tsx`, you can split your route into two files to enable code splitting: **Before (Single File)** diff --git a/docs/router/guide/data-loading.md b/docs/router/guide/data-loading.md index 2ecdf443ad..26a82eaad6 100644 --- a/docs/router/guide/data-loading.md +++ b/docs/router/guide/data-loading.md @@ -81,7 +81,7 @@ Use the object form when you want to configure loader-specific behavior such as The `loader` function receives a single object with the following properties: -- `abortController` - The route's abortController. Its signal is cancelled when the route is unloaded or when the Route is no longer relevant and the current invocation of the `loader` function becomes outdated. +- `abortController` - The controller for this shareable loader invocation. A preload and navigation can share the same in-flight loader work. Its signal is cancelled after the invocation becomes outdated and no consumer still needs it. - `cause` - The cause of the current route match. Can be either one of the following: - `enter` - When the route is matched and loaded after not being matched in the previous location. - `preload` - When the route is being preloaded. @@ -150,7 +150,7 @@ Using these dependencies as keys, TanStack Router will cache the data returned f To control router dependencies and "freshness", TanStack Router provides a plethora of options to control the keying and caching behavior of your route loaders. Let's take a look at them in the order that you are most likely to use them: - `routeOptions.loaderDeps` - - A function that supplies you the search params for a router and returns an object of dependencies for use in your `loader` function. When these deps changed from navigation to navigation, it will cause the route to reload regardless of `staleTime`s. The deps are compared using a deep equality check. + - A deterministic, side-effect-free function that supplies you the validated search params for a router and returns a serializable object of dependencies for use in your `loader` function. When these deps change from navigation to navigation, it will cause the route to reload regardless of `staleTime`s. The deps are compared using a deep equality check. - `routeOptions.staleTime` - `routerOptions.defaultStaleTime` - The number of milliseconds that a route's data should be considered fresh when attempting to load. @@ -168,11 +168,11 @@ To control router dependencies and "freshness", TanStack Router provides a pleth ### ⚠️ Some Important Defaults -- By default, the `staleTime` is set to `0`, meaning that the route's data is immediately considered stale. Stale matches are reloaded in the background when the route is entered again, when its loader key changes (path params used by the route or `loaderDeps`), or when `router.load()` is called explicitly. -- By default, a previously preloaded route is considered fresh for **30 seconds**. This means if a route is preloaded, then preloaded again within 30 seconds, the second preload will be ignored. This prevents unnecessary preloads from happening too frequently. **When a route is loaded normally, the standard `staleTime` is used.** -- By default, the `gcTime` is set to **30 minutes**, meaning that any route data that has not been accessed in 30 minutes will be garbage collected and removed from the cache. +- By default, `staleTime` is set to `0`, so reusable successful data is immediately considered stale. When the same loader key is entered again or `router.load()` is called explicitly, stale data revalidates in the background by default. A different loader key identifies a separate cache entry and must load if it has no reusable data. +- By default, loader data produced by a preload is considered fresh for **30 seconds**. Every preload and navigation still runs its own `beforeLoad` chain, but a later preload and the first navigation can reuse the preload's loader data or in-flight loader work during that interval. After navigation accepts that loader generation, subsequent freshness checks use the standard `staleTime`. +- By default, `gcTime` and `preloadGcTime` define **5-minute** retention windows. Once unused data is older than its applicable window, it is eligible for pruning during a later cache reconciliation. The two windows can be configured independently. - By default, `staleReloadMode` is `'background'`, so stale successful matches keep rendering with their existing `loaderData` while the loader revalidates in the background. -- `router.invalidate()` will force all active routes to reload their loaders immediately and mark every cached route's data as stale. +- `router.invalidate()` selects matching committed, cached, and in-flight loader generations for invalidation and retires matching active preload lanes. Current active routes reload through the normal loading protocol; cached inactive data remains marked stale and reloads when it is reused. By default, stale successful loader data revalidates in the background unless `sync: true` is requested. ### Using `loaderDeps` to access search params @@ -180,6 +180,11 @@ Imagine a `/posts` route supports some pagination via search params `offset` and Once we have these deps in place, the route will always reload when the deps change. +`loaderDeps` defines a cache key during route planning. For the same validated +search input it must return the same value without navigating or mutating state. +The returned value and custom serialization methods such as `toJSON` must also +be side-effect-free. + ```tsx // /routes/posts.tsx export const Route = createFileRoute('/posts')({ @@ -216,7 +221,13 @@ export const Route = createFileRoute('/posts')({ ### Using `staleTime` to control how long data is considered fresh -By default, `staleTime` for navigations is set to `0`ms (and 30 seconds for preloads) which means that the route's data will always be considered stale. When a stale route is entered again, its loader key changes, or `router.load()` is called explicitly, the route will reload in the background. +By default, `staleTime` for accepted navigation data is `0`ms, while +`preloadStaleTime` is 30 seconds. A successful preload can therefore provide +loader data to the first navigation during that interval. Once navigation +accepts that loader generation, normal navigation freshness applies. With the +default `staleTime`, the data is immediately stale for a later use of the same +loader key and revalidates in the background while cached data remains visible. +A different loader key identifies a separate cache entry. **This is a good default for most use cases, but you may find that some route data is more static or potentially expensive to load.** In these cases, you can use the `staleTime` option to control how long the route's data is considered fresh for navigations. Let's take a look at an example: @@ -302,9 +313,16 @@ export const Route = createFileRoute('/posts')({ ### Opting out of caching while still preloading -Even though you may opt-out of short-term caching for your route data, you can still get the benefits of preloading! With the above configuration, preloading will still "just work" with the default `preloadGcTime`. This means that if a route is preloaded, then navigated to, the route's data will be considered fresh and will not be reloaded. +Even though you may opt out of retaining ordinary route data, you can still get +the benefits of preloading. Preloaded results use `preloadGcTime` for retention +and `preloadStaleTime` for freshness, so the default settings keep a recent +preload in memory and let the first navigation reuse it without another loader +call. -To opt out of preloading, don't turn it on via the `routerOptions.defaultPreload` or `routeOptions.preload` options. +Use `routerOptions.defaultPreload` to control automatic link preloading. Setting +`routeOptions.preload` to `false` has a narrower effect: a speculative lane +still runs that route's `beforeLoad`, but skips its `loader`. Navigation runs +both normally. ## Passing all loader events to an external cache @@ -317,7 +335,11 @@ const router = createRouter({ }) ``` -This will ensure that every preload, load, and reload event will trigger your `loader` functions, which can then be handled and deduped by your external cache. +This makes settled preload data immediately stale in the Router, allowing your +external cache to decide whether to fetch. Retention still follows +`preloadGcTime`, and overlapping preload or navigation consumers can still +share in-flight loader work. A route's `shouldReload` option can also suppress +a loader call. ## Using Router Context @@ -472,7 +494,7 @@ export const Route = createFileRoute('/posts')({ ## Using the Abort Signal -The `abortController` property of the `loader` function is an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). Its signal is cancelled when the route is unloaded or when the `loader` call becomes outdated. This is useful for cancelling network requests when the route is unloaded or when the route's params change. Here is an example using it with a fetch call: +The `abortController` property of the `loader` function is an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for that loader invocation. A preload and navigation can share an in-flight invocation, so its signal remains active while any consumer still needs the work. The signal is cancelled after the invocation becomes outdated and has no remaining consumers. Here is an example using it with a fetch call: ```tsx // src/routes/posts.tsx diff --git a/docs/router/guide/data-mutations.md b/docs/router/guide/data-mutations.md index 7ffb403bfb..5fddfffdea 100644 --- a/docs/router/guide/data-mutations.md +++ b/docs/router/guide/data-mutations.md @@ -2,7 +2,11 @@ title: Data Mutations --- -Since TanStack router does not store or cache data, it's role in data mutation is slim to none outside of reacting to potential URL side-effects from external mutation events. That said, we've compiled a list of mutation-related features you might find useful and libraries that implement them. +TanStack Router caches route loader data, but it does not manage mutation or +submission state. Its role in mutation workflows is primarily invalidating +loader data and reacting to potential URL side effects from external mutation +events. That said, we've compiled a list of mutation-related features you might +find useful and libraries that implement them. Look for and use mutation utilities that support: @@ -35,9 +39,14 @@ Similar to data fetching, mutation state isn't a one-size-fits-all solution, so ## Invalidating TanStack Router after a mutation -TanStack Router comes with short-term caching built-in. So even though we're not storing any data after a route match is unmounted, there is a high probability that if any mutations are made related to the data stored in the Router, the current route matches' data could become stale. +TanStack Router comes with short-term caching built in. Loader data can remain +cached after a route match is unmounted, so a mutation can make both active and +cached route data stale. -When mutations related to loader data are made, we can use `router.invalidate` to force the router to reload all of the current route matches: +When mutations related to loader data are made, we can use `router.invalidate` +to invalidate committed, cached, and in-flight loader generations. Matching +active preload lanes are retired, and selected current active matches reload +through the normal loading protocol: ```tsx const router = useRouter() @@ -52,7 +61,9 @@ const addTodo = async (todo: Todo) => { } ``` -Invalidating all of the current route matches happens in the background, so existing data will continue to be served until the new data is ready, just as if you were navigating to a new route. +By default, stale successful loader data revalidates in the background, so +existing data remains visible until the new data is ready. Cached inactive +matches remain marked stale and reload when they are reused. If you want to await the invalidation until all loaders have finished, pass `{sync: true}` into `router.invalidate`: diff --git a/docs/router/guide/navigation.md b/docs/router/guide/navigation.md index 3dd09cba47..b2bb03ad0e 100644 --- a/docs/router/guide/navigation.md +++ b/docs/router/guide/navigation.md @@ -11,7 +11,7 @@ TanStack Router keeps this constant concept of relative navigation in mind for e - `from` - The origin route path - `to` - The destination route path -> ⚠️ If a `from` route path isn't provided the router will assume you are navigating from the root `/` route and only auto-complete absolute paths. After all, you need to know where you are from in order to know where you're going 😉. +> ⚠️ If a `from` route path isn't provided, the router will assume you are navigating from the root `/` route and only autocomplete absolute paths. After all, you need to know where you are from in order to know where you're going 😉. ## Shared Navigation API @@ -127,9 +127,10 @@ export type LinkOptions< includeSearch?: boolean explicitUndefined?: boolean } - // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there. - preload?: false | 'intent' - // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled. + // Choose the preload strategy for this link. `false` disables preloading; + // `'intent'`, `'viewport'`, and `'render'` select when it begins. + preload?: false | 'intent' | 'viewport' | 'render' + // Delay focus/hover intent by this many milliseconds. Touch intent preloads immediately. preloadDelay?: number // If true, will render the link without the href attribute disabled?: boolean @@ -143,7 +144,7 @@ Since `LinkOptions` extends `NavigateOptions`, it also supports `mask`. With relative navigation and all of the interfaces in mind now, let's talk about the different flavors of navigation API at your disposal: - The `` component - - Generates an actual `` tag with a valid `href` which can be click or even cmd/ctrl + clicked to open in a new tab + - Generates an actual `` tag with a valid `href` which can be clicked or even cmd/ctrl + clicked to open in a new tab. - The `useNavigate()` hook - When possible, `Link` component should be used for navigation, but sometimes you need to navigate imperatively as a result of a side-effect. `useNavigate` returns a function that can be called to perform an immediate client-side navigation. - The `` component @@ -309,7 +310,7 @@ const link = ( ### Search Param Type Safety -Search params are a highly dynamic state management mechanism, so it's important to ensure that you are passing the correct types to your search params. We'll see in a later section in detail how to validate and ensure search params typesafety, among other great features! +Search params are a highly dynamic state management mechanism, so it's important to ensure that you are passing the correct types to your search params. We'll see in a later section in detail how to validate and ensure search params type safety, among other great features! ### Hash Links @@ -707,7 +708,14 @@ const link = ( ### Link Preloading -The `Link` component supports automatically preloading routes on intent (hovering or touchstart for now). This can be configured as a default in the router options (which we'll talk more about soon) or by passing a `preload='intent'` prop to the `Link` component. Here's an example: +The `Link` component supports four `preload` values: + +- `false` disables automatic preloading. +- `'intent'` preloads when the link receives focus, is hovered, or is touched. +- `'viewport'` preloads when the link enters the viewport. +- `'render'` preloads as soon as the link renders. + +This can be configured as a default in the router options (which we'll talk more about soon) or by passing the `preload` prop to the `Link` component. Here's an intent-preloading example: ```tsx const link = ( @@ -723,7 +731,7 @@ What's even better is that by using a cache-first library like `@tanstack/query` ### Link Preloading Delay -Along with preloading is a configurable delay which determines how long a user must hover over a link to trigger the intent-based preloading. The default delay is 50 milliseconds, but you can change this by passing a `preloadDelay` prop to the `Link` component with the number of milliseconds you'd like to wait: +For `'intent'` preloading, a configurable delay determines how long a link must remain focused or hovered before preloading begins. If focus or hover ends before the delay, the queued preload is cancelled. Touch intent preloads immediately without waiting for the delay. The default delay is 50 milliseconds, but you can change it by passing a `preloadDelay` prop to the `Link` component: ```tsx const link = ( diff --git a/docs/router/guide/path-params.md b/docs/router/guide/path-params.md index b1ae4f5d2f..7ebaaad19e 100644 --- a/docs/router/guide/path-params.md +++ b/docs/router/guide/path-params.md @@ -137,6 +137,9 @@ When multiple dynamic, optional, or wildcard routes can match the same URL, rout Higher `params.priority` values are tried first. The default priority is `0`, and if a higher-priority route's `params.parse` returns `false`, matching continues to the next candidate route. +`params.parse` runs during route planning and may be evaluated more than once. +It must be deterministic and side-effect-free for the same raw params. + ```tsx title="src/routes/posts.$postId.tsx" export const Route = createFileRoute('/posts/$postId')({ params: { @@ -226,7 +229,7 @@ function PostComponent() { -You can even combines prefixes with wildcard routes to create more complex patterns: +You can even combine prefixes with wildcard routes to create more complex patterns: @@ -582,10 +585,10 @@ Optional parameters can be combined with wildcards for complex routing patterns: # React -```tsx title="src/routes/docs/{-$version}/$.tsx" -// Route: /docs/{-$version}/$ +```tsx title="src/routes/docs/v{-$version}/$.tsx" +// Route: /docs/v{-$version}/$ // Matches: /docs/extra/path, /docs/v2/extra/path -export const Route = createFileRoute('/docs/{-$version}/$')({ +export const Route = createFileRoute('/docs/v{-$version}/$')({ component: DocsComponent, }) @@ -595,7 +598,7 @@ function DocsComponent() { return (
- Version: {version || 'latest'} + Version: {version ? `v${version}` : 'latest'} Path: {_splat}
) @@ -604,10 +607,10 @@ function DocsComponent() { # Solid -```tsx title="src/routes/docs/{-$version}/$.tsx" -// Route: /docs/{-$version}/$ +```tsx title="src/routes/docs/v{-$version}/$.tsx" +// Route: /docs/v{-$version}/$ // Matches: /docs/extra/path, /docs/v2/extra/path -export const Route = createFileRoute('/docs/{-$version}/$')({ +export const Route = createFileRoute('/docs/v{-$version}/$')({ component: DocsComponent, }) @@ -616,7 +619,7 @@ function DocsComponent() { return (
- Version: {params().version || 'latest'} + Version: {params().version ? `v${params().version}` : 'latest'} Path: {params()._splat}
) diff --git a/docs/router/guide/preloading.md b/docs/router/guide/preloading.md index 453294ab6e..6ae9563f2f 100644 --- a/docs/router/guide/preloading.md +++ b/docs/router/guide/preloading.md @@ -18,10 +18,17 @@ Preloading in TanStack Router is a way to load a route before the user actually ## How long does preloaded data stay in memory? -Preloaded route matches are temporarily cached in memory with a few important caveats: - -- **Unused preloaded data is removed after 30 seconds by default.** This can be configured by setting the `defaultPreloadMaxAge` option on your router. -- **Obviously, when a route is loaded, its preloaded version is promoted to the router's normal pending matches state.** +Successful preloaded loader results can enter the router's in-memory cache with +two independent policies: + +- **Freshness defaults to 30 seconds.** Configure it with + `defaultPreloadStaleTime` or a route's `preloadStaleTime`. +- **The unused retention window defaults to 5 minutes.** Configure it with + `defaultPreloadGcTime` or a route's `preloadGcTime`. Older unused entries + are eligible for pruning during a later cache reconciliation. +- **The speculative lane is never promoted into router state.** Navigation + creates its own presentation and runs its own `beforeLoad` chain. It can reuse + cached loader data or join a loader that is still in flight. If you need more control over preloading, caching and/or garbage collection of preloaded data, you should use an external caching library like [TanStack Query](https://tanstack.com/query). @@ -87,9 +94,16 @@ const router = createRouter({ You can also set the `preloadDelay` prop on individual `` components to override the default behavior on a per-link basis. -## Built-in Preloading & `preloadStaleTime` +## Built-in Preloading, Freshness, and Retention + +If you're using the built-in loaders, you can control how long preloaded data is considered fresh by setting either `routerOptions.defaultPreloadStaleTime` or `routeOptions.preloadStaleTime` to a number of milliseconds. **By default, preloaded data is considered fresh for 30 seconds.** -If you're using the built-in loaders, you can control how long preloaded data is considered fresh until another preload is triggered by setting either `routerOptions.defaultPreloadStaleTime` or `routeOptions.preloadStaleTime` to a number of milliseconds. **By default, preloaded data is considered fresh for 30 seconds.**. +Freshness and retention are separate. `preloadStaleTime` controls whether the +retained loader result can be reused without another loader call. +`preloadGcTime` (or `defaultPreloadGcTime`) controls when an unused preload +result becomes eligible for pruning during a later cache reconciliation; it +does not schedule a timer to evict the result at that exact moment. Both preload +GC options default to 5 minutes. To change this, you can set the `defaultPreloadStaleTime` option on your router: @@ -125,16 +139,34 @@ Or, you can use the `routeOptions.preloadStaleTime` option on individual routes: // src/routes/posts.$postId.tsx export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => fetchPost(params.postId), - // Preload the route again if the preload cache is older than 10 seconds + // Reload preloaded data when it is more than 10 seconds old preloadStaleTime: 10_000, }) ``` +Client-side preloading runs each route's `beforeLoad` with `preload: true`. +Every later preload or navigation runs its own `beforeLoad` chain, so a +navigation observes `preload: false` even when an identical preload is still +active. A later lane can reuse successful settled loader data or join loader +work that is still in flight, but it never reuses `beforeLoad` context or an +already-settled redirect, error, or not-found result. If joined loader work +later produces a terminal outcome, all current consumers of that flight observe +it. The `shouldReload` option remains loader-only. + +If a route has `preload: false`, its speculative lane still runs `beforeLoad`, +but skips that route's loader. Navigation runs `beforeLoad` again and performs +the skipped loader work. + ## Preloading with External Libraries When integrating external caching libraries like React Query, which have their own mechanisms for determining stale data, you may want to override the default preloading and stale-while-revalidate logic of TanStack Router. These libraries often use options like staleTime to control the freshness of data. -To customize the preloading behavior in TanStack Router and fully leverage your external library's caching strategy, you can bypass the built-in caching by setting routerOptions.defaultPreloadStaleTime or routeOptions.preloadStaleTime to 0. This ensures that all preloads are marked as stale internally, and loaders are always invoked, allowing your external library, such as React Query, to manage data loading and caching. +To let an external cache make the freshness decision, set +`routerOptions.defaultPreloadStaleTime` or `routeOptions.preloadStaleTime` to +`0`. Settled preload data then becomes immediately stale in the Router, while +retention still follows `preloadGcTime`. Overlapping preload or navigation +consumers can still share in-flight loader work, and `shouldReload` can still +suppress a loader call. For example: @@ -168,25 +200,38 @@ This would then allow you, for instance, to use an option like React Query's `st ## Preloading Manually -If you need to manually preload a route, you can use the router's `preloadRoute` method. It accepts a standard TanStack `NavigateOptions` object and returns a promise that resolves when the route is preloaded. +If you need to manually preload a route, use the router's `preloadRoute` method. +It accepts a standard TanStack `NavigateOptions` object and returns the +speculative match lane. An ordinary error or not-found thrown while loading is +represented in that returned lane; cancellation or control flow that produces +no reusable lane can return `undefined`. # React ```tsx +import { isNotFound } from '@tanstack/react-router' + function Component() { const router = useRouter() useEffect(() => { async function preload() { - try { - const matches = await router.preloadRoute({ - to: postRoute, - params: { id: 1 }, - }) - } catch (err) { - // Failed to preload route + const matches = await router.preloadRoute({ + to: postRoute, + params: { id: 1 }, + }) + + const routeFailure = matches?.find( + (match) => + match.status === 'error' || + match.status === 'notFound' || + isNotFound(match.error), + ) + + if (routeFailure) { + // Inspect routeFailure.error } } @@ -200,18 +245,27 @@ function Component() { # Solid ```tsx +import { isNotFound } from '@tanstack/solid-router' + function Component() { const router = useRouter() createEffect(() => { async function preload() { - try { - const matches = await router.preloadRoute({ - to: postRoute, - params: { id: 1 }, - }) - } catch (err) { - // Failed to preload route + const matches = await router.preloadRoute({ + to: postRoute, + params: { id: 1 }, + }) + + const routeFailure = matches?.find( + (match) => + match.status === 'error' || + match.status === 'notFound' || + isNotFound(match.error), + ) + + if (routeFailure) { + // Inspect routeFailure.error } } diff --git a/docs/router/how-to/debug-router-issues.md b/docs/router/how-to/debug-router-issues.md index b4d20ec72f..27dadd361f 100644 --- a/docs/router/how-to/debug-router-issues.md +++ b/docs/router/how-to/debug-router-issues.md @@ -322,11 +322,16 @@ const route = createRoute({ ```tsx function DataLoadingDebug() { - const location = useLocation() + const state = useRouterState() console.log('Route status:', { - isLoading: location.isLoading, - isTransitioning: location.isTransitioning, + status: state.status, + isLoading: state.isLoading, + matches: state.matches.map((match) => ({ + routeId: match.routeId, + status: match.status, + isFetching: match.isFetching, + })), }) return null diff --git a/docs/router/installation/manual.md b/docs/router/installation/manual.md index e2cfcabcf3..be2a8e4df9 100644 --- a/docs/router/installation/manual.md +++ b/docs/router/installation/manual.md @@ -232,7 +232,7 @@ render(() => , rootElement) Regardless of whether you are using the `@tanstack/router-plugin` package and running the `npm run dev`/`npm run build` scripts, or manually running the `tsr watch`/`tsr generate` commands from your package scripts, the route tree file will be generated at `src/routeTree.gen.ts`. -If you are working with this pattern you should change the `id` of the root `
` on your `index.html` file to `
` +If you are working with this pattern, you should change the `id` of the root `
` on your `index.html` file to `
` ## Using Code-Based Route Configuration diff --git a/docs/router/routing/code-based-routing.md b/docs/router/routing/code-based-routing.md index d3102cafd6..b1457c6fbe 100644 --- a/docs/router/routing/code-based-routing.md +++ b/docs/router/routing/code-based-routing.md @@ -14,7 +14,7 @@ title: Code-Based Routing Code-based routing is no different from file-based routing in that it uses the same route tree concept to organize, match and compose matching routes into a component tree. The only difference is that instead of using the filesystem to organize your routes, you use code. -Let's consider the same route tree from the [Route Trees & Nesting](./route-trees.md#route-trees) guide, and convert it to code-based routing: +Let's consider the same route tree from the [Route Trees](./route-trees.md#route-trees) guide, and convert it to code-based routing: Here is the file-based version: diff --git a/docs/router/routing/file-based-routing.md b/docs/router/routing/file-based-routing.md index 6be11e55d6..2eea38bba2 100644 --- a/docs/router/routing/file-based-routing.md +++ b/docs/router/routing/file-based-routing.md @@ -12,7 +12,7 @@ File-based routing is a way to configure your routes using the filesystem. Inste - **Organization**: Routes are organized in a way that mirrors the URL structure of your application. - **Scalability**: As your application grows, file-based routing makes it easy to add new routes and maintain existing ones. - **Code-Splitting**: File-based routing allows TanStack Router to automatically code-split your routes for better performance. -- **Type-Safety**: File-based routing raises the ceiling on type-safety by generating managing type linkages for your routes, which can otherwise be a tedious process via code-based routing. +- **Type-Safety**: File-based routing raises the ceiling on type-safety by generating and managing type linkages for your routes, which can otherwise be a tedious process via code-based routing. - **Consistency**: File-based routing enforces a consistent structure for your routes, making it easier to maintain and update your application and move from one project to another. ## `/`s or `.`s? @@ -80,7 +80,7 @@ See the example below: ## Mixed Flat and Directory Routes -It's extremely likely that a 100% directory or flat route structure won't be the best fit for your project, which is why TanStack Router allows you to mix both flat and directory routes together to create a route tree that uses the best of both worlds where it makes sense: +It's extremely likely that a 100% directory or flat route structure won't be the best fit for your project, which is why TanStack Router allows you to mix both flat and directory routes together to create a route tree that uses the best of both worlds where it makes sense. See the example below: diff --git a/docs/router/routing/file-naming-conventions.md b/docs/router/routing/file-naming-conventions.md index 115310f5b3..896e7d3d9b 100644 --- a/docs/router/routing/file-naming-conventions.md +++ b/docs/router/routing/file-naming-conventions.md @@ -2,7 +2,7 @@ title: File Naming Conventions --- -File-based routing requires that you follow a few simple file naming conventions to ensure that your routes are generated correctly. The concepts these conventions enable are covered in detail in the [Route Trees & Nesting](./route-trees.md) guide. +File-based routing requires that you follow a few simple file naming conventions to ensure that your routes are generated correctly. The concepts these conventions enable are covered in detail in the [Route Trees](./route-trees.md) guide. | Feature | Description | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -40,4 +40,4 @@ Pathless routes wrap child routes with either logic or a component without requi | ʦ `_app.a.tsx` | /a | `
` | | ʦ `_app.b.tsx` | /b | `` | -To learn more about pathless routes, see the [Routing Concepts - Pathless Routes](./routing-concepts.md#pathless-layout-routes) guide. +To learn more about pathless routes, see the [Routing Concepts - Pathless Layout Routes](./routing-concepts.md#pathless-layout-routes) guide. diff --git a/docs/router/routing/routing-concepts.md b/docs/router/routing/routing-concepts.md index 88eebbd5f0..11674e69c2 100644 --- a/docs/router/routing/routing-concepts.md +++ b/docs/router/routing/routing-concepts.md @@ -564,7 +564,7 @@ routes/ │ ├── ... ``` -We can import from the excluded files into our posts route +We can import from the excluded files into our posts route: diff --git a/docs/start/config.json b/docs/start/config.json index e93688eb91..57703aad89 100644 --- a/docs/start/config.json +++ b/docs/start/config.json @@ -59,8 +59,9 @@ ] }, { - "label": "Guides", + "label": "Server & Execution", "children": [], + "collapsible": true, "frameworks": [ { "label": "react", @@ -81,6 +82,10 @@ "label": "Import Protection", "to": "framework/react/guide/import-protection" }, + { + "label": "Path Aliases", + "to": "framework/react/guide/path-aliases" + }, { "label": "Environment Variables", "to": "framework/react/guide/environment-variables" @@ -89,6 +94,10 @@ "label": "Server Functions", "to": "framework/react/guide/server-functions" }, + { + "label": "Streaming Data from Server Functions", + "to": "framework/react/guide/streaming-data-from-server-functions" + }, { "label": "Server Components", "to": "framework/react/guide/server-components" @@ -112,94 +121,6 @@ { "label": "Server Routes", "to": "framework/react/guide/server-routes" - }, - { - "label": "Hydration Errors", - "to": "framework/react/guide/hydration-errors" - }, - { - "label": "Deferred Hydration", - "to": "framework/react/guide/deferred-hydration" - }, - { - "label": "Selective SSR", - "to": "framework/react/guide/selective-ssr" - }, - { - "label": "SPA Mode", - "to": "framework/react/guide/spa-mode" - }, - { - "label": "Static Prerendering", - "to": "framework/react/guide/static-prerendering" - }, - { - "label": "Incremental Static Regeneration (ISR)", - "to": "framework/react/guide/isr" - }, - { - "label": "Server Entry Point", - "to": "framework/react/guide/server-entry-point" - }, - { - "label": "Early Hints", - "to": "framework/react/guide/early-hints" - }, - { - "label": "CDN Asset URLs", - "to": "framework/react/guide/cdn-asset-urls" - }, - { - "label": "CSS Styling", - "to": "framework/react/guide/css-styling" - }, - { - "label": "Client Entry Point", - "to": "framework/react/guide/client-entry-point" - }, - { - "label": "Hosting", - "to": "framework/react/guide/hosting" - }, - { - "label": "Authentication Overview", - "to": "framework/react/guide/authentication-overview" - }, - { - "label": "Authentication Server Primitives", - "to": "framework/react/guide/authentication-server-primitives" - }, - { - "label": "Authentication", - "to": "framework/react/guide/authentication" - }, - { - "label": "Databases", - "to": "framework/react/guide/databases" - }, - { - "label": "Observability", - "to": "framework/react/guide/observability" - }, - { - "label": "Path Aliases", - "to": "framework/react/guide/path-aliases" - }, - { - "label": "Tailwind CSS Integration", - "to": "framework/react/guide/tailwind-integration" - }, - { - "label": "Rendering Markdown", - "to": "framework/react/guide/rendering-markdown" - }, - { - "label": "SEO", - "to": "framework/react/guide/seo" - }, - { - "label": "Generative Engine Optimization (GEO)", - "to": "framework/react/guide/geo" } ] }, @@ -222,6 +143,10 @@ "label": "Import Protection", "to": "framework/solid/guide/import-protection" }, + { + "label": "Path Aliases", + "to": "framework/solid/guide/path-aliases" + }, { "label": "Environment Variables", "to": "framework/solid/guide/environment-variables" @@ -249,7 +174,64 @@ { "label": "Server Routes", "to": "framework/solid/guide/server-routes" + } + ] + } + ] + }, + { + "label": "Rendering", + "children": [], + "collapsible": true, + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "Hydration Errors", + "to": "framework/react/guide/hydration-errors" + }, + { + "label": "Deferred Hydration", + "to": "framework/react/guide/deferred-hydration" + }, + { + "label": "Selective SSR", + "to": "framework/react/guide/selective-ssr" + }, + { + "label": "SPA Mode", + "to": "framework/react/guide/spa-mode" + }, + { + "label": "Static Prerendering", + "to": "framework/react/guide/static-prerendering" + }, + { + "label": "Incremental Static Regeneration (ISR)", + "to": "framework/react/guide/isr" }, + { + "label": "Server Entry Point", + "to": "framework/react/guide/server-entry-point" + }, + { + "label": "Client Entry Point", + "to": "framework/react/guide/client-entry-point" + }, + { + "label": "Early Hints", + "to": "framework/react/guide/early-hints" + }, + { + "label": "CDN Asset URLs", + "to": "framework/react/guide/cdn-asset-urls" + } + ] + }, + { + "label": "solid", + "children": [ { "label": "Hydration Errors", "to": "framework/solid/guide/hydration-errors" @@ -274,22 +256,80 @@ "label": "Server Entry Point", "to": "framework/solid/guide/server-entry-point" }, + { + "label": "Client Entry Point", + "to": "framework/solid/guide/client-entry-point" + }, { "label": "Early Hints", "to": "framework/solid/guide/early-hints" - }, + } + ] + } + ] + }, + { + "label": "Deployment & Operations", + "children": [], + "collapsible": true, + "frameworks": [ + { + "label": "react", + "children": [ { - "label": "CSS Styling", - "to": "framework/solid/guide/css-styling" + "label": "Hosting", + "to": "framework/react/guide/hosting" }, { - "label": "Client Entry Point", - "to": "framework/solid/guide/client-entry-point" - }, + "label": "Observability", + "to": "framework/react/guide/observability" + } + ] + }, + { + "label": "solid", + "children": [ { "label": "Hosting", "to": "framework/solid/guide/hosting" }, + { + "label": "Observability", + "to": "framework/solid/guide/observability" + } + ] + } + ] + }, + { + "label": "Authentication & Data", + "children": [], + "collapsible": true, + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "Authentication Overview", + "to": "framework/react/guide/authentication-overview" + }, + { + "label": "Authentication Server Primitives", + "to": "framework/react/guide/authentication-server-primitives" + }, + { + "label": "Authentication", + "to": "framework/react/guide/authentication" + }, + { + "label": "Databases", + "to": "framework/react/guide/databases" + } + ] + }, + { + "label": "solid", + "children": [ { "label": "Authentication Overview", "to": "framework/solid/guide/authentication-overview" @@ -305,14 +345,47 @@ { "label": "Databases", "to": "framework/solid/guide/databases" + } + ] + } + ] + }, + { + "label": "Styling & Metadata", + "children": [], + "collapsible": true, + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "CSS Styling", + "to": "framework/react/guide/css-styling" }, { - "label": "Observability", - "to": "framework/solid/guide/observability" + "label": "Tailwind CSS Integration", + "to": "framework/react/guide/tailwind-integration" }, { - "label": "Path Aliases", - "to": "framework/solid/guide/path-aliases" + "label": "Rendering Markdown", + "to": "framework/react/guide/rendering-markdown" + }, + { + "label": "SEO", + "to": "framework/react/guide/seo" + }, + { + "label": "Generative Engine Optimization (GEO)", + "to": "framework/react/guide/geo" + } + ] + }, + { + "label": "solid", + "children": [ + { + "label": "CSS Styling", + "to": "framework/solid/guide/css-styling" }, { "label": "Tailwind CSS Integration", diff --git a/docs/start/framework/react/comparison.md b/docs/start/framework/react/comparison.md index e734263d9d..5c39e0f602 100644 --- a/docs/start/framework/react/comparison.md +++ b/docs/start/framework/react/comparison.md @@ -243,7 +243,7 @@ const authMiddleware = createMiddleware({ type: 'function' }) }) ``` -**Next.js** has a single middleware.ts file that runs on the Edge Runtime for all requests. It cannot access server-only resources like databases and has limitations compared to the Node.js runtime. +**Next.js** has a single proxy.ts file that runs on the Node.js runtime. Unlike the deprecated middleware.ts (which ran on Edge Runtime), proxy.ts has full access to server-side resources like databases. ### Deployment Flexibility diff --git a/docs/start/framework/react/getting-started.md b/docs/start/framework/react/getting-started.md index 565dc4b4cf..0320fdab4e 100644 --- a/docs/start/framework/react/getting-started.md +++ b/docs/start/framework/react/getting-started.md @@ -39,6 +39,7 @@ To use a different example, replace `start-basic` with one of the slugs below. #### Examples - [Basic](https://github.com/TanStack/router/tree/main/examples/react/start-basic) (`start-basic`) +- [Basic + Rsbuild](https://github.com/TanStack/router/tree/main/examples/react/start-basic-rsbuild) (`start-basic-rsbuild`) - [Basic + Auth](https://github.com/TanStack/router/tree/main/examples/react/start-basic-auth) (`start-basic-auth`) - [Counter](https://github.com/TanStack/router/tree/main/examples/react/start-counter) (`start-counter`) - [Basic + React Query](https://github.com/TanStack/router/tree/main/examples/react/start-basic-react-query) (`start-basic-react-query`) diff --git a/docs/start/framework/react/guide/environment-variables.md b/docs/start/framework/react/guide/environment-variables.md index 8953c0a099..f1f6851602 100644 --- a/docs/start/framework/react/guide/environment-variables.md +++ b/docs/start/framework/react/guide/environment-variables.md @@ -394,14 +394,14 @@ Use Zod for runtime validation of environment variables: import { z } from 'zod' const envSchema = z.object({ - DATABASE_URL: z.string().url(), + DATABASE_URL: z.url(), JWT_SECRET: z.string().min(32), NODE_ENV: z.enum(['development', 'production', 'test']), }) const clientEnvSchema = z.object({ VITE_APP_NAME: z.string(), - VITE_API_URL: z.string().url(), + VITE_API_URL: z.url(), VITE_AUTH0_DOMAIN: z.string(), VITE_AUTH0_CLIENT_ID: z.string(), }) diff --git a/docs/start/framework/react/guide/hosting.md b/docs/start/framework/react/guide/hosting.md index 23fdd63226..aa1806c6bf 100644 --- a/docs/start/framework/react/guide/hosting.md +++ b/docs/start/framework/react/guide/hosting.md @@ -298,7 +298,7 @@ npm install srvx ```json "build": "rsbuild build", - "start": "srvx --prod -s dist/client dist/server/index.js" + "start": "srvx --prod -s ../client dist/server/index.js" ``` Express or any other custom Node.js server works too, as long as it serves the client assets and calls the server entry's `fetch` handler for dynamic requests. diff --git a/e2e/react-router/basepath-file-based/package.json b/e2e/react-router/basepath-file-based/package.json index dd0956e96f..a155cc2173 100644 --- a/e2e/react-router/basepath-file-based/package.json +++ b/e2e/react-router/basepath-file-based/package.json @@ -18,7 +18,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basepath-file-based/src/routeTree.gen.ts b/e2e/react-router/basepath-file-based/src/routeTree.gen.ts index 2989a394d3..3badc3f843 100644 --- a/e2e/react-router/basepath-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basepath-file-based/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 ScrollErrorRouteImport } from './routes/scroll-error' -import { Route as RedirectReloadRouteImport } from './routes/redirectReload' -import { Route as RedirectRouteImport } from './routes/redirect' -import { Route as AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as RedirectReloadRouteImport } from './routes/redirectReload' +import { Route as ScrollErrorRouteImport } from './routes/scroll-error' -const ScrollErrorRoute = ScrollErrorRouteImport.update({ - id: '/scroll-error', - path: '/scroll-error', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectReloadRoute = RedirectReloadRouteImport.update({ - id: '/redirectReload', - path: '/redirectReload', +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', getParentRoute: () => rootRouteImport, } as any) const RedirectRoute = RedirectRouteImport.update({ @@ -30,14 +30,14 @@ const RedirectRoute = RedirectRouteImport.update({ path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const RedirectReloadRoute = RedirectReloadRouteImport.update({ + id: '/redirectReload', + path: '/redirectReload', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ScrollErrorRoute = ScrollErrorRouteImport.update({ + id: '/scroll-error', + path: '/scroll-error', getParentRoute: () => rootRouteImport, } as any) @@ -87,18 +87,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/scroll-error': { - id: '/scroll-error' - path: '/scroll-error' - fullPath: '/scroll-error' - preLoaderRoute: typeof ScrollErrorRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirectReload': { - id: '/redirectReload' - path: '/redirectReload' - fullPath: '/redirectReload' - preLoaderRoute: typeof RedirectReloadRouteImport + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport parentRoute: typeof rootRouteImport } '/redirect': { @@ -108,18 +108,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/redirectReload': { + id: '/redirectReload' + path: '/redirectReload' + fullPath: '/redirectReload' + preLoaderRoute: typeof RedirectReloadRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/scroll-error': { + id: '/scroll-error' + path: '/scroll-error' + fullPath: '/scroll-error' + preLoaderRoute: typeof ScrollErrorRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-router/basic-esbuild-file-based/package.json b/e2e/react-router/basic-esbuild-file-based/package.json index 93e1263d11..2ff977a4f4 100644 --- a/e2e/react-router/basic-esbuild-file-based/package.json +++ b/e2e/react-router/basic-esbuild-file-based/package.json @@ -19,7 +19,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", 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 14a8f0373f..f3654137be 100644 --- a/e2e/react-router/basic-file-based-code-splitting/package.json +++ b/e2e/react-router/basic-file-based-code-splitting/package.json @@ -21,7 +21,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", 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 618ad52e34..c7a15cd84e 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 @@ -9,26 +9,30 @@ // 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 WithoutLoaderRouteImport } from './routes/without-loader' -import { Route as ViewportTestRouteImport } from './routes/viewport-test' -import { Route as SharedSingletonRouteImport } from './routes/shared-singleton' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as SharedSingletonRouteImport } from './routes/shared-singleton' +import { Route as ViewportTestRouteImport } from './routes/viewport-test' +import { Route as WithoutLoaderRouteImport } from './routes/without-loader' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -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 LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' -const WithoutLoaderRoute = WithoutLoaderRouteImport.update({ - id: '/without-loader', - path: '/without-loader', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const ViewportTestRoute = ViewportTestRouteImport.update({ - id: '/viewport-test', - path: '/viewport-test', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) const SharedSingletonRoute = SharedSingletonRouteImport.update({ @@ -36,19 +40,19 @@ const SharedSingletonRoute = SharedSingletonRouteImport.update({ path: '/shared-singleton', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const ViewportTestRoute = ViewportTestRouteImport.update({ + id: '/viewport-test', + path: '/viewport-test', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const WithoutLoaderRoute = WithoutLoaderRouteImport.update({ + id: '/without-loader', + path: '/without-loader', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', @@ -60,20 +64,16 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -158,18 +158,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/without-loader': { - id: '/without-loader' - path: '/without-loader' - fullPath: '/without-loader' - preLoaderRoute: typeof WithoutLoaderRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport 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 + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } '/shared-singleton': { @@ -179,26 +186,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SharedSingletonRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/viewport-test': { + id: '/viewport-test' + path: '/viewport-test' + fullPath: '/viewport-test' + preLoaderRoute: typeof ViewportTestRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/without-loader': { + id: '/without-loader' + path: '/without-loader' + fullPath: '/without-loader' + preLoaderRoute: typeof WithoutLoaderRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } '/posts/': { id: '/posts/' @@ -214,12 +221,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/_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' @@ -228,13 +235,6 @@ 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 da2df215af..be3d8add30 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 7ee0051549..2493a8c675 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -9,155 +9,149 @@ // 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 RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as MasksRouteImport } from './routes/masks' -import { Route as LazyErrorRouteImport } from './routes/lazy-error' -import { Route as HoverPreloadHashRouteImport } from './routes/hover-preload-hash' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as ComponentTypesTestRouteImport } from './routes/component-types-test' -import { Route as AnchorRouteImport } from './routes/anchor' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as Char45824Char54620Char48124Char44397RouteRouteImport } from './routes/대한민국/route' -import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' -import { Route as PathlessLayoutRouteRouteImport } from './routes/pathless-layout/route' -import { Route as NonNestedRouteRouteImport } from './routes/non-nested/route' +import { Route as AnchorRouteImport } from './routes/anchor' +import { Route as ComponentTypesTestRouteImport } from './routes/component-types-test' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' import { Route as FullpathTestRouteRouteImport } from './routes/fullpath-test/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' -import { Route as RelativeIndexRouteImport } from './routes/relative/index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as HoverPreloadHashRouteImport } from './routes/hover-preload-hash' +import { Route as LazyErrorRouteImport } from './routes/lazy-error' +import { Route as MasksRouteImport } from './routes/masks' +import { Route as NonNestedRouteRouteImport } from './routes/non-nested/route' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PathlessLayoutRouteRouteImport } from './routes/pathless-layout/route' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as Char45824Char54620Char48124Char44397RouteRouteImport } from './routes/대한민국/route' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as FullpathTestLayoutRouteRouteImport } from './routes/fullpath-test/_layout/route' +import { Route as NonNestedDeepRouteRouteImport } from './routes/non-nested/deep/route' +import { Route as NonNestedNamedRouteRouteImport } from './routes/non-nested/named/route' +import { Route as NonNestedPathRouteRouteImport } from './routes/non-nested/path/route' +import { Route as NonNestedPrefixRouteRouteImport } from './routes/non-nested/prefix/route' +import { Route as NonNestedSuffixRouteRouteImport } from './routes/non-nested/suffix/route' import { Route as ParamsPsIndexRouteImport } from './routes/params-ps/index' -import { Route as StructuralSharingEnabledRouteImport } from './routes/structural-sharing.$enabled' -import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' -import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as ParamsPsNonNestedRouteRouteImport } from './routes/params-ps/non-nested/route' +import { Route as ParamsPsStrictFalseRouteRouteImport } from './routes/params-ps/strict-false/route' +import { Route as PathlessLayoutLayoutRouteRouteImport } from './routes/pathless-layout/_layout/route' +import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as RelativeUseNavigateRouteRouteImport } from './routes/relative/useNavigate/route' +import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as RelativeIndexRouteImport } from './routes/relative/index' import { Route as RelativeLinkRouteRouteImport } from './routes/relative/link/route' -import { Route as PathlessLayoutLayoutRouteRouteImport } from './routes/pathless-layout/_layout/route' -import { Route as ParamsPsStrictFalseRouteRouteImport } from './routes/params-ps/strict-false/route' -import { Route as ParamsPsNonNestedRouteRouteImport } from './routes/params-ps/non-nested/route' -import { Route as NonNestedSuffixRouteRouteImport } from './routes/non-nested/suffix/route' -import { Route as NonNestedPrefixRouteRouteImport } from './routes/non-nested/prefix/route' -import { Route as NonNestedPathRouteRouteImport } from './routes/non-nested/path/route' -import { Route as NonNestedNamedRouteRouteImport } from './routes/non-nested/named/route' -import { Route as NonNestedDeepRouteRouteImport } from './routes/non-nested/deep/route' -import { Route as FullpathTestLayoutRouteRouteImport } from './routes/fullpath-test/_layout/route' -import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' -import { Route as PathlessLayoutLayoutIndexRouteImport } from './routes/pathless-layout/_layout/index' -import { Route as ParamsPsWildcardIndexRouteImport } from './routes/params-ps/wildcard/index' -import { Route as ParamsPsNamedIndexRouteImport } from './routes/params-ps/named/index' +import { Route as RelativeUseNavigateRouteRouteImport } from './routes/relative/useNavigate/route' +import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' +import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' +import { Route as StructuralSharingEnabledRouteImport } from './routes/structural-sharing.$enabled' +import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' import { Route as FullpathTestLayoutIndexRouteImport } from './routes/fullpath-test/_layout/index' -import { Route as Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport } from './routes/대한민국/🚀.$id' -import { Route as Char45824Char54620Char48124Char44397WildcardSplatRouteImport } from './routes/대한민국/wildcard.$' -import { Route as RelativeUseNavigateRelativeUseNavigateBRouteImport } from './routes/relative/useNavigate/relative-useNavigate-b' -import { Route as RelativeUseNavigateRelativeUseNavigateARouteImport } from './routes/relative/useNavigate/relative-useNavigate-a' -import { Route as RelativeLinkRelativeLinkBRouteImport } from './routes/relative/link/relative-link-b' -import { Route as RelativeLinkRelativeLinkARouteImport } from './routes/relative/link/relative-link-a' -import { Route as RedirectPreloadThirdRouteImport } from './routes/redirect/preload/third' -import { Route as RedirectPreloadSecondRouteImport } from './routes/redirect/preload/second' -import { Route as RedirectPreloadFirstRouteImport } from './routes/redirect/preload/first' -import { Route as RedirectTargetViaRouteApiRedirectLoaderRouteImport } from './routes/redirect/$target/via-routeApi-redirect-loader' -import { Route as RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport } from './routes/redirect/$target/via-routeApi-redirect-beforeLoad' -import { Route as RedirectTargetViaRouteRedirectLoaderRouteImport } from './routes/redirect/$target/via-route-redirect-loader' -import { Route as RedirectTargetViaRouteRedirectBeforeLoadRouteImport } from './routes/redirect/$target/via-route-redirect-beforeLoad' -import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' -import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' -import { Route as RedirectTargetDestinationRouteImport } from './routes/redirect/$target/destination' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -import { Route as PathlessLayoutLayoutChildRouteImport } from './routes/pathless-layout/_layout/child' -import { Route as ParamsSingleValueRouteImport } from './routes/params.single.$value' -import { Route as ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport } from './routes/params-ps/wildcard/{$}suffix@대' -import { Route as ParamsPsWildcardChar123Char125suffixRouteImport } from './routes/params-ps/wildcard/{$}suffix' -import { Route as ParamsPsWildcardPrefixChar123Char125RouteImport } from './routes/params-ps/wildcard/prefix{$}' -import { Route as ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport } from './routes/params-ps/wildcard/prefix@대{$}' -import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$' -import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix' -import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}' -import { Route as MasksPublicUsernameRouteImport } from './routes/masks.public.$username' -import { Route as MasksAdminUserIdRouteImport } from './routes/masks.admin.$userId' import { Route as FullpathTestLayoutIdRouteImport } from './routes/fullpath-test/_layout/$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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' -import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' -import { Route as ParamsPsStrictFalseVersionRouteRouteImport } from './routes/params-ps/strict-false/$version.route' -import { Route as ParamsPsNonNestedFooRouteRouteImport } from './routes/params-ps/non-nested/$foo_/route' -import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route' -import { Route as NonNestedSuffixChar123bazChar125suffixRouteRouteImport } from './routes/non-nested/suffix/{$baz}suffix.route' -import { Route as NonNestedPrefixPrefixChar123bazChar125RouteRouteImport } from './routes/non-nested/prefix/prefix{$baz}.route' -import { Route as NonNestedPathBazRouteRouteImport } from './routes/non-nested/path/baz.route' -import { Route as NonNestedNamedBazRouteRouteImport } from './routes/non-nested/named/$baz.route' +import { Route as MasksAdminUserIdRouteImport } from './routes/masks.admin.$userId' +import { Route as MasksPublicUsernameRouteImport } from './routes/masks.public.$username' import { Route as NonNestedDeepBazRouteRouteImport } from './routes/non-nested/deep/$baz.route' -import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index' -import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index' -import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index' -import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index' -import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index' -import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index' -import { Route as NonNestedSuffixChar123bazChar125suffixIndexRouteImport } from './routes/non-nested/suffix/{$baz}suffix.index' -import { Route as NonNestedPrefixPrefixChar123bazChar125IndexRouteImport } from './routes/non-nested/prefix/prefix{$baz}.index' -import { Route as NonNestedPathBazIndexRouteImport } from './routes/non-nested/path/baz.index' -import { Route as NonNestedNamedBazIndexRouteImport } from './routes/non-nested/named/$baz.index' +import { Route as NonNestedNamedBazRouteRouteImport } from './routes/non-nested/named/$baz.route' +import { Route as NonNestedPathBazRouteRouteImport } from './routes/non-nested/path/baz.route' +import { Route as NonNestedPrefixPrefixChar123bazChar125RouteRouteImport } from './routes/non-nested/prefix/prefix{$baz}.route' +import { Route as NonNestedSuffixChar123bazChar125suffixRouteRouteImport } from './routes/non-nested/suffix/{$baz}suffix.route' +import { Route as ParamsPsNamedIndexRouteImport } from './routes/params-ps/named/index' +import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route' +import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}' +import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix' +import { Route as ParamsPsNonNestedFooRouteRouteImport } from './routes/params-ps/non-nested/$foo_/route' +import { Route as ParamsPsStrictFalseVersionRouteRouteImport } from './routes/params-ps/strict-false/$version.route' +import { Route as ParamsPsWildcardIndexRouteImport } from './routes/params-ps/wildcard/index' +import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$' +import { Route as ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport } from './routes/params-ps/wildcard/prefix@대{$}' +import { Route as ParamsPsWildcardPrefixChar123Char125RouteImport } from './routes/params-ps/wildcard/prefix{$}' +import { Route as ParamsPsWildcardChar123Char125suffixRouteImport } from './routes/params-ps/wildcard/{$}suffix' +import { Route as ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport } from './routes/params-ps/wildcard/{$}suffix@대' +import { Route as ParamsSingleValueRouteImport } from './routes/params.single.$value' +import { Route as PathlessLayoutLayoutIndexRouteImport } from './routes/pathless-layout/_layout/index' +import { Route as PathlessLayoutLayoutChildRouteImport } from './routes/pathless-layout/_layout/child' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' +import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' +import { Route as RedirectTargetDestinationRouteImport } from './routes/redirect/$target/destination' +import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' +import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' +import { Route as RedirectTargetViaRouteRedirectBeforeLoadRouteImport } from './routes/redirect/$target/via-route-redirect-beforeLoad' +import { Route as RedirectTargetViaRouteRedirectLoaderRouteImport } from './routes/redirect/$target/via-route-redirect-loader' +import { Route as RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport } from './routes/redirect/$target/via-routeApi-redirect-beforeLoad' +import { Route as RedirectTargetViaRouteApiRedirectLoaderRouteImport } from './routes/redirect/$target/via-routeApi-redirect-loader' +import { Route as RedirectPreloadFirstRouteImport } from './routes/redirect/preload/first' +import { Route as RedirectPreloadSecondRouteImport } from './routes/redirect/preload/second' +import { Route as RedirectPreloadThirdRouteImport } from './routes/redirect/preload/third' +import { Route as RelativeLinkRelativeLinkARouteImport } from './routes/relative/link/relative-link-a' +import { Route as RelativeLinkRelativeLinkBRouteImport } from './routes/relative/link/relative-link-b' +import { Route as RelativeUseNavigateRelativeUseNavigateARouteImport } from './routes/relative/useNavigate/relative-useNavigate-a' +import { Route as RelativeUseNavigateRelativeUseNavigateBRouteImport } from './routes/relative/useNavigate/relative-useNavigate-b' +import { Route as Char45824Char54620Char48124Char44397WildcardSplatRouteImport } from './routes/대한민국/wildcard.$' +import { Route as Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport } from './routes/대한민국/🚀.$id' import { Route as NonNestedDeepBazIndexRouteImport } from './routes/non-nested/deep/$baz.index' -import { Route as ParamsPsNonNestedFooBarRouteImport } from './routes/params-ps/non-nested/$foo_/$bar' -import { Route as NonNestedSuffixChar123bazChar125suffixBarRouteImport } from './routes/non-nested/suffix/{$baz}suffix_.bar' -import { Route as NonNestedSuffixChar123bazChar125suffixFooRouteImport } from './routes/non-nested/suffix/{$baz}suffix.foo' -import { Route as NonNestedPrefixPrefixChar123bazChar125BarRouteImport } from './routes/non-nested/prefix/prefix{$baz}_.bar' -import { Route as NonNestedPrefixPrefixChar123bazChar125FooRouteImport } from './routes/non-nested/prefix/prefix{$baz}.foo' -import { Route as NonNestedPathBazBarRouteImport } from './routes/non-nested/path/baz_.bar' -import { Route as NonNestedPathBazFooRouteImport } from './routes/non-nested/path/baz.foo' -import { Route as NonNestedNamedBazBarRouteImport } from './routes/non-nested/named/$baz_.bar' +import { Route as NonNestedDeepBazBarRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.route' +import { Route as NonNestedNamedBazIndexRouteImport } from './routes/non-nested/named/$baz.index' import { Route as NonNestedNamedBazFooRouteImport } from './routes/non-nested/named/$baz.foo' +import { Route as NonNestedNamedBazBarRouteImport } from './routes/non-nested/named/$baz_.bar' +import { Route as NonNestedPathBazIndexRouteImport } from './routes/non-nested/path/baz.index' +import { Route as NonNestedPathBazFooRouteImport } from './routes/non-nested/path/baz.foo' +import { Route as NonNestedPathBazBarRouteImport } from './routes/non-nested/path/baz_.bar' +import { Route as NonNestedPrefixPrefixChar123bazChar125IndexRouteImport } from './routes/non-nested/prefix/prefix{$baz}.index' +import { Route as NonNestedPrefixPrefixChar123bazChar125FooRouteImport } from './routes/non-nested/prefix/prefix{$baz}.foo' +import { Route as NonNestedPrefixPrefixChar123bazChar125BarRouteImport } from './routes/non-nested/prefix/prefix{$baz}_.bar' +import { Route as NonNestedSuffixChar123bazChar125suffixIndexRouteImport } from './routes/non-nested/suffix/{$baz}suffix.index' +import { Route as NonNestedSuffixChar123bazChar125suffixFooRouteImport } from './routes/non-nested/suffix/{$baz}suffix.foo' +import { Route as NonNestedSuffixChar123bazChar125suffixBarRouteImport } from './routes/non-nested/suffix/{$baz}suffix_.bar' import { Route as ParamsPsNamedFooBarRouteRouteImport } from './routes/params-ps/named/$foo/$bar.route' -import { Route as NonNestedDeepBazBarRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.route' -import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index' -import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index' -import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index' -import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' +import { Route as ParamsPsNonNestedFooBarRouteImport } from './routes/params-ps/non-nested/$foo_/$bar' +import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index' +import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index' +import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index' +import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index' +import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index' +import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index' import { Route as NonNestedDeepBazBarIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.index' -import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' -import { Route as NonNestedDeepBazBarQuxRouteImport } from './routes/non-nested/deep/$baz_.bar_.qux' import { Route as NonNestedDeepBazBarFooRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.route' +import { Route as NonNestedDeepBazBarQuxRouteImport } from './routes/non-nested/deep/$baz_.bar_.qux' +import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' +import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' +import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index' +import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index' +import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index' import { Route as NonNestedDeepBazBarFooIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.index' import { Route as NonNestedDeepBazBarFooQuxRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo_.qux' -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ - id: '/notRemountDeps', - path: '/notRemountDeps', +const AnchorRoute = AnchorRouteImport.update({ + id: '/anchor', + path: '/anchor', getParentRoute: () => rootRouteImport, } as any) -const MasksRoute = MasksRouteImport.update({ - id: '/masks', - path: '/masks', +const ComponentTypesTestRoute = ComponentTypesTestRouteImport.update({ + id: '/component-types-test', + path: '/component-types-test', getParentRoute: () => rootRouteImport, } as any) -const LazyErrorRoute = LazyErrorRouteImport.update({ - id: '/lazy-error', - path: '/lazy-error', - getParentRoute: () => rootRouteImport, -} as any).lazy(() => import('./routes/lazy-error.lazy').then((d) => d.Route)) -const HoverPreloadHashRoute = HoverPreloadHashRouteImport.update({ - id: '/hover-preload-hash', - path: '/hover-preload-hash', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', getParentRoute: () => rootRouteImport, } as any) const EditingBRoute = EditingBRouteImport.update({ @@ -165,39 +159,24 @@ const EditingBRoute = EditingBRouteImport.update({ path: '/editing-b', getParentRoute: () => rootRouteImport, } as any) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const FullpathTestRouteRoute = FullpathTestRouteRouteImport.update({ + id: '/fullpath-test', + path: '/fullpath-test', getParentRoute: () => rootRouteImport, } as any) -const ComponentTypesTestRoute = ComponentTypesTestRouteImport.update({ - id: '/component-types-test', - path: '/component-types-test', +const HoverPreloadHashRoute = HoverPreloadHashRouteImport.update({ + id: '/hover-preload-hash', + path: '/hover-preload-hash', getParentRoute: () => rootRouteImport, } as any) -const AnchorRoute = AnchorRouteImport.update({ - id: '/anchor', - path: '/anchor', - getParentRoute: () => rootRouteImport, -} as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) -const Char45824Char54620Char48124Char44397RouteRoute = - Char45824Char54620Char48124Char44397RouteRouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any) -const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ - id: '/search-params', - path: '/search-params', +const LazyErrorRoute = LazyErrorRouteImport.update({ + id: '/lazy-error', + path: '/lazy-error', getParentRoute: () => rootRouteImport, -} as any) -const PathlessLayoutRouteRoute = PathlessLayoutRouteRouteImport.update({ - id: '/pathless-layout', - path: '/pathless-layout', +} as any).lazy(() => import('./routes/lazy-error.lazy').then((d) => d.Route)) +const MasksRoute = MasksRouteImport.update({ + id: '/masks', + path: '/masks', getParentRoute: () => rootRouteImport, } as any) const NonNestedRouteRoute = NonNestedRouteRouteImport.update({ @@ -205,65 +184,51 @@ const NonNestedRouteRoute = NonNestedRouteRouteImport.update({ path: '/non-nested', getParentRoute: () => rootRouteImport, } as any) -const FullpathTestRouteRoute = FullpathTestRouteRouteImport.update({ - id: '/fullpath-test', - path: '/fullpath-test', +const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ + id: '/notRemountDeps', + path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PathlessLayoutRouteRoute = PathlessLayoutRouteRouteImport.update({ + id: '/pathless-layout', + path: '/pathless-layout', getParentRoute: () => rootRouteImport, } as any) -const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RelativeIndexRoute = RelativeIndexRouteImport.update({ - id: '/relative/', - path: '/relative/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const RedirectIndexRoute = RedirectIndexRouteImport.update({ - id: '/redirect/', - path: '/redirect/', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, -} as any) -const ParamsPsIndexRoute = ParamsPsIndexRouteImport.update({ - id: '/params-ps/', - path: '/params-ps/', +const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ + id: '/search-params', + path: '/search-params', getParentRoute: () => rootRouteImport, } as any) -const StructuralSharingEnabledRoute = - StructuralSharingEnabledRouteImport.update({ - id: '/structural-sharing/$enabled', - path: '/structural-sharing/$enabled', +const Char45824Char54620Char48124Char44397RouteRoute = + Char45824Char54620Char48124Char44397RouteRouteImport.update({ + id: '/대한민국', + path: '/대한민국', getParentRoute: () => rootRouteImport, } as any) -const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ - id: '/default', - path: '/default', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RedirectTargetRoute = RedirectTargetRouteImport.update({ - id: '/redirect/$target', - path: '/redirect/$target', +const anotherGroupOnlyrouteinsideRoute = + anotherGroupOnlyrouteinsideRouteImport.update({ + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', + getParentRoute: () => rootRouteImport, + } as any) +const groupLayoutRoute = groupLayoutRouteImport.update({ + id: '/(group)/_layout', getParentRoute: () => rootRouteImport, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const groupInsideRoute = groupInsideRouteImport.update({ + id: '/(group)/inside', + path: '/inside', + getParentRoute: () => rootRouteImport, } as any) const groupLazyinsideRoute = groupLazyinsideRouteImport .update({ @@ -272,56 +237,22 @@ const groupLazyinsideRoute = groupLazyinsideRouteImport getParentRoute: () => rootRouteImport, } as any) .lazy(() => import('./routes/(group)/lazyinside.lazy').then((d) => d.Route)) -const groupInsideRoute = groupInsideRouteImport.update({ - id: '/(group)/inside', - path: '/inside', - getParentRoute: () => rootRouteImport, -} as any) -const groupLayoutRoute = groupLayoutRouteImport.update({ - id: '/(group)/_layout', - getParentRoute: () => rootRouteImport, -} as any) -const anotherGroupOnlyrouteinsideRoute = - anotherGroupOnlyrouteinsideRouteImport.update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', - getParentRoute: () => rootRouteImport, - } as any) -const RelativeUseNavigateRouteRoute = - RelativeUseNavigateRouteRouteImport.update({ - id: '/relative/useNavigate', - path: '/relative/useNavigate', - getParentRoute: () => rootRouteImport, - } as any) -const RelativeLinkRouteRoute = RelativeLinkRouteRouteImport.update({ - id: '/relative/link', - path: '/relative/link', - getParentRoute: () => rootRouteImport, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const PathlessLayoutLayoutRouteRoute = - PathlessLayoutLayoutRouteRouteImport.update({ - id: '/_layout', - getParentRoute: () => PathlessLayoutRouteRoute, - } as any) -const ParamsPsStrictFalseRouteRoute = - ParamsPsStrictFalseRouteRouteImport.update({ - id: '/params-ps/strict-false', - path: '/params-ps/strict-false', - getParentRoute: () => rootRouteImport, - } as any) -const ParamsPsNonNestedRouteRoute = ParamsPsNonNestedRouteRouteImport.update({ - id: '/params-ps/non-nested', - path: '/params-ps/non-nested', - getParentRoute: () => rootRouteImport, +const FullpathTestLayoutRouteRoute = FullpathTestLayoutRouteRouteImport.update({ + id: '/_layout', + getParentRoute: () => FullpathTestRouteRoute, } as any) -const NonNestedSuffixRouteRoute = NonNestedSuffixRouteRouteImport.update({ - id: '/suffix', - path: '/suffix', +const NonNestedDeepRouteRoute = NonNestedDeepRouteRouteImport.update({ + id: '/deep', + path: '/deep', getParentRoute: () => NonNestedRouteRoute, } as any) -const NonNestedPrefixRouteRoute = NonNestedPrefixRouteRouteImport.update({ - id: '/prefix', - path: '/prefix', +const NonNestedNamedRouteRoute = NonNestedNamedRouteRouteImport.update({ + id: '/named', + path: '/named', getParentRoute: () => NonNestedRouteRoute, } as any) const NonNestedPathRouteRoute = NonNestedPathRouteRouteImport.update({ @@ -329,164 +260,204 @@ const NonNestedPathRouteRoute = NonNestedPathRouteRouteImport.update({ path: '/path', getParentRoute: () => NonNestedRouteRoute, } as any) -const NonNestedNamedRouteRoute = NonNestedNamedRouteRouteImport.update({ - id: '/named', - path: '/named', +const NonNestedPrefixRouteRoute = NonNestedPrefixRouteRouteImport.update({ + id: '/prefix', + path: '/prefix', getParentRoute: () => NonNestedRouteRoute, } as any) -const NonNestedDeepRouteRoute = NonNestedDeepRouteRouteImport.update({ - id: '/deep', - path: '/deep', +const NonNestedSuffixRouteRoute = NonNestedSuffixRouteRouteImport.update({ + id: '/suffix', + path: '/suffix', getParentRoute: () => NonNestedRouteRoute, } as any) -const FullpathTestLayoutRouteRoute = FullpathTestLayoutRouteRouteImport.update({ - id: '/_layout', - getParentRoute: () => FullpathTestRouteRoute, +const ParamsPsIndexRoute = ParamsPsIndexRouteImport.update({ + id: '/params-ps/', + path: '/params-ps/', + getParentRoute: () => rootRouteImport, } as any) -const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ +const ParamsPsNonNestedRouteRoute = ParamsPsNonNestedRouteRouteImport.update({ + id: '/params-ps/non-nested', + path: '/params-ps/non-nested', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsStrictFalseRouteRoute = + ParamsPsStrictFalseRouteRouteImport.update({ + id: '/params-ps/strict-false', + path: '/params-ps/strict-false', + getParentRoute: () => rootRouteImport, + } as any) +const PathlessLayoutLayoutRouteRoute = + PathlessLayoutLayoutRouteRouteImport.update({ + id: '/_layout', + getParentRoute: () => PathlessLayoutRouteRoute, + } as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => PostsRoute, } as any) -const PathlessLayoutLayoutIndexRoute = - PathlessLayoutLayoutIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PathlessLayoutLayoutRouteRoute, - } as any) -const ParamsPsWildcardIndexRoute = ParamsPsWildcardIndexRouteImport.update({ - id: '/params-ps/wildcard/', - path: '/params-ps/wildcard/', +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const RedirectIndexRoute = RedirectIndexRouteImport.update({ + id: '/redirect/', + path: '/redirect/', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsNamedIndexRoute = ParamsPsNamedIndexRouteImport.update({ - id: '/params-ps/named/', - path: '/params-ps/named/', +const RedirectTargetRoute = RedirectTargetRouteImport.update({ + id: '/redirect/$target', + path: '/redirect/$target', getParentRoute: () => rootRouteImport, } as any) -const FullpathTestLayoutIndexRoute = FullpathTestLayoutIndexRouteImport.update({ +const RelativeIndexRoute = RelativeIndexRouteImport.update({ + id: '/relative/', + path: '/relative/', + getParentRoute: () => rootRouteImport, +} as any) +const RelativeLinkRouteRoute = RelativeLinkRouteRouteImport.update({ + id: '/relative/link', + path: '/relative/link', + getParentRoute: () => rootRouteImport, +} as any) +const RelativeUseNavigateRouteRoute = + RelativeUseNavigateRouteRouteImport.update({ + id: '/relative/useNavigate', + path: '/relative/useNavigate', + getParentRoute: () => rootRouteImport, + } as any) +const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => FullpathTestLayoutRouteRoute, + getParentRoute: () => SearchParamsRouteRoute, } as any) -const Char45824Char54620Char48124Char44397Char55357Char56960IdRoute = - Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport.update({ - id: '/🚀/$id', - path: '/🚀/$id', - getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, - } as any) -const Char45824Char54620Char48124Char44397WildcardSplatRoute = - Char45824Char54620Char48124Char44397WildcardSplatRouteImport.update({ - id: '/wildcard/$', - path: '/wildcard/$', - getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, - } as any) -const RelativeUseNavigateRelativeUseNavigateBRoute = - RelativeUseNavigateRelativeUseNavigateBRouteImport.update({ - id: '/relative-useNavigate-b', - path: '/relative-useNavigate-b', - getParentRoute: () => RelativeUseNavigateRouteRoute, - } as any) -const RelativeUseNavigateRelativeUseNavigateARoute = - RelativeUseNavigateRelativeUseNavigateARouteImport.update({ - id: '/relative-useNavigate-a', - path: '/relative-useNavigate-a', - getParentRoute: () => RelativeUseNavigateRouteRoute, - } as any) -const RelativeLinkRelativeLinkBRoute = - RelativeLinkRelativeLinkBRouteImport.update({ - id: '/relative-link-b', - path: '/relative-link-b', - getParentRoute: () => RelativeLinkRouteRoute, - } as any) -const RelativeLinkRelativeLinkARoute = - RelativeLinkRelativeLinkARouteImport.update({ - id: '/relative-link-a', - path: '/relative-link-a', - getParentRoute: () => RelativeLinkRouteRoute, +const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ + id: '/default', + path: '/default', + getParentRoute: () => SearchParamsRouteRoute, +} as any) +const StructuralSharingEnabledRoute = + StructuralSharingEnabledRouteImport.update({ + id: '/structural-sharing/$enabled', + path: '/structural-sharing/$enabled', + getParentRoute: () => rootRouteImport, } as any) -const RedirectPreloadThirdRoute = RedirectPreloadThirdRouteImport.update({ - id: '/redirect/preload/third', - path: '/redirect/preload/third', - getParentRoute: () => rootRouteImport, +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, } as any) -const RedirectPreloadSecondRoute = RedirectPreloadSecondRouteImport.update({ - id: '/redirect/preload/second', - path: '/redirect/preload/second', +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', getParentRoute: () => rootRouteImport, } as any) -const RedirectPreloadFirstRoute = RedirectPreloadFirstRouteImport.update({ - id: '/redirect/preload/first', - path: '/redirect/preload/first', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) -const RedirectTargetViaRouteApiRedirectLoaderRoute = - RedirectTargetViaRouteApiRedirectLoaderRouteImport.update({ - id: '/via-routeApi-redirect-loader', - path: '/via-routeApi-redirect-loader', - getParentRoute: () => RedirectTargetRoute, +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) +const FullpathTestLayoutIndexRoute = FullpathTestLayoutIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => FullpathTestLayoutRouteRoute, +} as any) +const FullpathTestLayoutIdRoute = FullpathTestLayoutIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => FullpathTestLayoutRouteRoute, +} as any) +const MasksAdminUserIdRoute = MasksAdminUserIdRouteImport.update({ + id: '/admin/$userId', + path: '/admin/$userId', + getParentRoute: () => MasksRoute, +} as any) +const MasksPublicUsernameRoute = MasksPublicUsernameRouteImport.update({ + id: '/public/$username', + path: '/public/$username', + getParentRoute: () => MasksRoute, +} as any) +const NonNestedDeepBazRouteRoute = NonNestedDeepBazRouteRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => NonNestedDeepRouteRoute, +} as any) +const NonNestedNamedBazRouteRoute = NonNestedNamedBazRouteRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => NonNestedNamedRouteRoute, +} as any) +const NonNestedPathBazRouteRoute = NonNestedPathBazRouteRouteImport.update({ + id: '/baz', + path: '/baz', + getParentRoute: () => NonNestedPathRouteRoute, +} as any) +const NonNestedPrefixPrefixChar123bazChar125RouteRoute = + NonNestedPrefixPrefixChar123bazChar125RouteRouteImport.update({ + id: '/prefix{$baz}', + path: '/prefix{$baz}', + getParentRoute: () => NonNestedPrefixRouteRoute, } as any) -const RedirectTargetViaRouteApiRedirectBeforeLoadRoute = - RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport.update({ - id: '/via-routeApi-redirect-beforeLoad', - path: '/via-routeApi-redirect-beforeLoad', - getParentRoute: () => RedirectTargetRoute, +const NonNestedSuffixChar123bazChar125suffixRouteRoute = + NonNestedSuffixChar123bazChar125suffixRouteRouteImport.update({ + id: '/{$baz}suffix', + path: '/{$baz}suffix', + getParentRoute: () => NonNestedSuffixRouteRoute, } as any) -const RedirectTargetViaRouteRedirectLoaderRoute = - RedirectTargetViaRouteRedirectLoaderRouteImport.update({ - id: '/via-route-redirect-loader', - path: '/via-route-redirect-loader', - getParentRoute: () => RedirectTargetRoute, +const ParamsPsNamedIndexRoute = ParamsPsNamedIndexRouteImport.update({ + id: '/params-ps/named/', + path: '/params-ps/named/', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({ + id: '/params-ps/named/$foo', + path: '/params-ps/named/$foo', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsNamedPrefixChar123fooChar125Route = + ParamsPsNamedPrefixChar123fooChar125RouteImport.update({ + id: '/params-ps/named/prefix{$foo}', + path: '/params-ps/named/prefix{$foo}', + getParentRoute: () => rootRouteImport, } as any) -const RedirectTargetViaRouteRedirectBeforeLoadRoute = - RedirectTargetViaRouteRedirectBeforeLoadRouteImport.update({ - id: '/via-route-redirect-beforeLoad', - path: '/via-route-redirect-beforeLoad', - getParentRoute: () => RedirectTargetRoute, +const ParamsPsNamedChar123fooChar125suffixRoute = + ParamsPsNamedChar123fooChar125suffixRouteImport.update({ + id: '/params-ps/named/{$foo}suffix', + path: '/params-ps/named/{$foo}suffix', + getParentRoute: () => rootRouteImport, } as any) -const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => RedirectTargetRoute, -} as any) -const RedirectTargetViaBeforeLoadRoute = - RedirectTargetViaBeforeLoadRouteImport.update({ - id: '/via-beforeLoad', - path: '/via-beforeLoad', - getParentRoute: () => RedirectTargetRoute, +const ParamsPsNonNestedFooRouteRoute = + ParamsPsNonNestedFooRouteRouteImport.update({ + id: '/$foo_', + path: '/$foo', + getParentRoute: () => ParamsPsNonNestedRouteRoute, } as any) -const RedirectTargetDestinationRoute = - RedirectTargetDestinationRouteImport.update({ - id: '/destination', - path: '/destination', - getParentRoute: () => RedirectTargetRoute, +const ParamsPsStrictFalseVersionRouteRoute = + ParamsPsStrictFalseVersionRouteRouteImport.update({ + id: '/$version', + path: '/$version', + getParentRoute: () => ParamsPsStrictFalseRouteRoute, } as any) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', +const ParamsPsWildcardIndexRoute = ParamsPsWildcardIndexRouteImport.update({ + id: '/params-ps/wildcard/', + path: '/params-ps/wildcard/', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutLayoutChildRoute = - PathlessLayoutLayoutChildRouteImport.update({ - id: '/child', - path: '/child', - getParentRoute: () => PathlessLayoutLayoutRouteRoute, - } as any) -const ParamsSingleValueRoute = ParamsSingleValueRouteImport.update({ - id: '/params/single/$value', - path: '/params/single/$value', +const ParamsPsWildcardSplatRoute = ParamsPsWildcardSplatRouteImport.update({ + id: '/params-ps/wildcard/$', + path: '/params-ps/wildcard/$', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsWildcardChar123Char125suffixAtChar45824Route = - ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport.update({ - id: '/params-ps/wildcard/{$}suffix@대', - path: '/params-ps/wildcard/{$}suffix@대', - getParentRoute: () => rootRouteImport, - } as any) -const ParamsPsWildcardChar123Char125suffixRoute = - ParamsPsWildcardChar123Char125suffixRouteImport.update({ - id: '/params-ps/wildcard/{$}suffix', - path: '/params-ps/wildcard/{$}suffix', +const ParamsPsWildcardPrefixAtChar45824Char123Char125Route = + ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport.update({ + id: '/params-ps/wildcard/prefix@대{$}', + path: '/params-ps/wildcard/prefix@대{$}', getParentRoute: () => rootRouteImport, } as any) const ParamsPsWildcardPrefixChar123Char125Route = @@ -495,185 +466,189 @@ const ParamsPsWildcardPrefixChar123Char125Route = path: '/params-ps/wildcard/prefix{$}', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsWildcardPrefixAtChar45824Char123Char125Route = - ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport.update({ - id: '/params-ps/wildcard/prefix@대{$}', - path: '/params-ps/wildcard/prefix@대{$}', +const ParamsPsWildcardChar123Char125suffixRoute = + ParamsPsWildcardChar123Char125suffixRouteImport.update({ + id: '/params-ps/wildcard/{$}suffix', + path: '/params-ps/wildcard/{$}suffix', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsWildcardSplatRoute = ParamsPsWildcardSplatRouteImport.update({ - id: '/params-ps/wildcard/$', - path: '/params-ps/wildcard/$', +const ParamsPsWildcardChar123Char125suffixAtChar45824Route = + ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport.update({ + id: '/params-ps/wildcard/{$}suffix@대', + path: '/params-ps/wildcard/{$}suffix@대', + getParentRoute: () => rootRouteImport, + } as any) +const ParamsSingleValueRoute = ParamsSingleValueRouteImport.update({ + id: '/params/single/$value', + path: '/params/single/$value', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsNamedChar123fooChar125suffixRoute = - ParamsPsNamedChar123fooChar125suffixRouteImport.update({ - id: '/params-ps/named/{$foo}suffix', - path: '/params-ps/named/{$foo}suffix', - getParentRoute: () => rootRouteImport, +const PathlessLayoutLayoutIndexRoute = + PathlessLayoutLayoutIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PathlessLayoutLayoutRouteRoute, } as any) -const ParamsPsNamedPrefixChar123fooChar125Route = - ParamsPsNamedPrefixChar123fooChar125RouteImport.update({ - id: '/params-ps/named/prefix{$foo}', - path: '/params-ps/named/prefix{$foo}', - getParentRoute: () => rootRouteImport, +const PathlessLayoutLayoutChildRoute = + PathlessLayoutLayoutChildRouteImport.update({ + id: '/child', + path: '/child', + getParentRoute: () => PathlessLayoutLayoutRouteRoute, } as any) -const MasksPublicUsernameRoute = MasksPublicUsernameRouteImport.update({ - id: '/public/$username', - path: '/public/$username', - getParentRoute: () => MasksRoute, -} as any) -const MasksAdminUserIdRoute = MasksAdminUserIdRouteImport.update({ - id: '/admin/$userId', - path: '/admin/$userId', - getParentRoute: () => MasksRoute, +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', + getParentRoute: () => rootRouteImport, } as any) -const FullpathTestLayoutIdRoute = FullpathTestLayoutIdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => FullpathTestLayoutRouteRoute, -} as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, -} as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', - getParentRoute: () => rootRouteImport, -} as any) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, +const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => RedirectTargetRoute, } as any) -const ParamsPsStrictFalseVersionRouteRoute = - ParamsPsStrictFalseVersionRouteRouteImport.update({ - id: '/$version', - path: '/$version', - getParentRoute: () => ParamsPsStrictFalseRouteRoute, +const RedirectTargetDestinationRoute = + RedirectTargetDestinationRouteImport.update({ + id: '/destination', + path: '/destination', + getParentRoute: () => RedirectTargetRoute, } as any) -const ParamsPsNonNestedFooRouteRoute = - ParamsPsNonNestedFooRouteRouteImport.update({ - id: '/$foo_', - path: '/$foo', - getParentRoute: () => ParamsPsNonNestedRouteRoute, +const RedirectTargetViaBeforeLoadRoute = + RedirectTargetViaBeforeLoadRouteImport.update({ + id: '/via-beforeLoad', + path: '/via-beforeLoad', + getParentRoute: () => RedirectTargetRoute, } as any) -const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({ - id: '/params-ps/named/$foo', - path: '/params-ps/named/$foo', - getParentRoute: () => rootRouteImport, +const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => RedirectTargetRoute, } as any) -const NonNestedSuffixChar123bazChar125suffixRouteRoute = - NonNestedSuffixChar123bazChar125suffixRouteRouteImport.update({ - id: '/{$baz}suffix', - path: '/{$baz}suffix', - getParentRoute: () => NonNestedSuffixRouteRoute, +const RedirectTargetViaRouteRedirectBeforeLoadRoute = + RedirectTargetViaRouteRedirectBeforeLoadRouteImport.update({ + id: '/via-route-redirect-beforeLoad', + path: '/via-route-redirect-beforeLoad', + getParentRoute: () => RedirectTargetRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125RouteRoute = - NonNestedPrefixPrefixChar123bazChar125RouteRouteImport.update({ - id: '/prefix{$baz}', - path: '/prefix{$baz}', - getParentRoute: () => NonNestedPrefixRouteRoute, +const RedirectTargetViaRouteRedirectLoaderRoute = + RedirectTargetViaRouteRedirectLoaderRouteImport.update({ + id: '/via-route-redirect-loader', + path: '/via-route-redirect-loader', + getParentRoute: () => RedirectTargetRoute, } as any) -const NonNestedPathBazRouteRoute = NonNestedPathBazRouteRouteImport.update({ - id: '/baz', - path: '/baz', - getParentRoute: () => NonNestedPathRouteRoute, +const RedirectTargetViaRouteApiRedirectBeforeLoadRoute = + RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport.update({ + id: '/via-routeApi-redirect-beforeLoad', + path: '/via-routeApi-redirect-beforeLoad', + getParentRoute: () => RedirectTargetRoute, + } as any) +const RedirectTargetViaRouteApiRedirectLoaderRoute = + RedirectTargetViaRouteApiRedirectLoaderRouteImport.update({ + id: '/via-routeApi-redirect-loader', + path: '/via-routeApi-redirect-loader', + getParentRoute: () => RedirectTargetRoute, + } as any) +const RedirectPreloadFirstRoute = RedirectPreloadFirstRouteImport.update({ + id: '/redirect/preload/first', + path: '/redirect/preload/first', + getParentRoute: () => rootRouteImport, } as any) -const NonNestedNamedBazRouteRoute = NonNestedNamedBazRouteRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => NonNestedNamedRouteRoute, +const RedirectPreloadSecondRoute = RedirectPreloadSecondRouteImport.update({ + id: '/redirect/preload/second', + path: '/redirect/preload/second', + getParentRoute: () => rootRouteImport, } as any) -const NonNestedDeepBazRouteRoute = NonNestedDeepBazRouteRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => NonNestedDeepRouteRoute, +const RedirectPreloadThirdRoute = RedirectPreloadThirdRouteImport.update({ + id: '/redirect/preload/third', + path: '/redirect/preload/third', + getParentRoute: () => rootRouteImport, } as any) -const RelativeUseNavigateWithSearchIndexRoute = - RelativeUseNavigateWithSearchIndexRouteImport.update({ - id: '/with-search/', - path: '/with-search/', - getParentRoute: () => RelativeUseNavigateRouteRoute, +const RelativeLinkRelativeLinkARoute = + RelativeLinkRelativeLinkARouteImport.update({ + id: '/relative-link-a', + path: '/relative-link-a', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const RelativeUseNavigatePathIndexRoute = - RelativeUseNavigatePathIndexRouteImport.update({ - id: '/path/', - path: '/path/', - getParentRoute: () => RelativeUseNavigateRouteRoute, +const RelativeLinkRelativeLinkBRoute = + RelativeLinkRelativeLinkBRouteImport.update({ + id: '/relative-link-b', + path: '/relative-link-b', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const RelativeUseNavigateNestedIndexRoute = - RelativeUseNavigateNestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', +const RelativeUseNavigateRelativeUseNavigateARoute = + RelativeUseNavigateRelativeUseNavigateARouteImport.update({ + id: '/relative-useNavigate-a', + path: '/relative-useNavigate-a', getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeLinkWithSearchIndexRoute = - RelativeLinkWithSearchIndexRouteImport.update({ - id: '/with-search/', - path: '/with-search/', - getParentRoute: () => RelativeLinkRouteRoute, +const RelativeUseNavigateRelativeUseNavigateBRoute = + RelativeUseNavigateRelativeUseNavigateBRouteImport.update({ + id: '/relative-useNavigate-b', + path: '/relative-useNavigate-b', + getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeLinkPathIndexRoute = RelativeLinkPathIndexRouteImport.update({ - id: '/path/', - path: '/path/', - getParentRoute: () => RelativeLinkRouteRoute, -} as any) -const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => RelativeLinkRouteRoute, -} as any) -const NonNestedSuffixChar123bazChar125suffixIndexRoute = - NonNestedSuffixChar123bazChar125suffixIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, +const Char45824Char54620Char48124Char44397WildcardSplatRoute = + Char45824Char54620Char48124Char44397WildcardSplatRouteImport.update({ + id: '/wildcard/$', + path: '/wildcard/$', + getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125IndexRoute = - NonNestedPrefixPrefixChar123bazChar125IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, +const Char45824Char54620Char48124Char44397Char55357Char56960IdRoute = + Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport.update({ + id: '/🚀/$id', + path: '/🚀/$id', + getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, } as any) -const NonNestedPathBazIndexRoute = NonNestedPathBazIndexRouteImport.update({ +const NonNestedDeepBazIndexRoute = NonNestedDeepBazIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NonNestedPathBazRouteRoute, + getParentRoute: () => NonNestedDeepBazRouteRoute, } as any) +const NonNestedDeepBazBarRouteRoute = + NonNestedDeepBazBarRouteRouteImport.update({ + id: '/$baz_/bar', + path: '/$baz/bar', + getParentRoute: () => NonNestedDeepRouteRoute, + } as any) const NonNestedNamedBazIndexRoute = NonNestedNamedBazIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => NonNestedNamedBazRouteRoute, } as any) -const NonNestedDeepBazIndexRoute = NonNestedDeepBazIndexRouteImport.update({ +const NonNestedNamedBazFooRoute = NonNestedNamedBazFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NonNestedNamedBazRouteRoute, +} as any) +const NonNestedNamedBazBarRoute = NonNestedNamedBazBarRouteImport.update({ + id: '/$baz_/bar', + path: '/$baz/bar', + getParentRoute: () => NonNestedNamedRouteRoute, +} as any) +const NonNestedPathBazIndexRoute = NonNestedPathBazIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NonNestedDeepBazRouteRoute, + getParentRoute: () => NonNestedPathBazRouteRoute, } as any) -const ParamsPsNonNestedFooBarRoute = ParamsPsNonNestedFooBarRouteImport.update({ - id: '/$bar', - path: '/$bar', - getParentRoute: () => ParamsPsNonNestedFooRouteRoute, +const NonNestedPathBazFooRoute = NonNestedPathBazFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NonNestedPathBazRouteRoute, } as any) -const NonNestedSuffixChar123bazChar125suffixBarRoute = - NonNestedSuffixChar123bazChar125suffixBarRouteImport.update({ - id: '/{$baz}suffix_/bar', - path: '/{$baz}suffix/bar', - getParentRoute: () => NonNestedSuffixRouteRoute, +const NonNestedPathBazBarRoute = NonNestedPathBazBarRouteImport.update({ + id: '/baz_/bar', + path: '/baz/bar', + getParentRoute: () => NonNestedPathRouteRoute, +} as any) +const NonNestedPrefixPrefixChar123bazChar125IndexRoute = + NonNestedPrefixPrefixChar123bazChar125IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, } as any) -const NonNestedSuffixChar123bazChar125suffixFooRoute = - NonNestedSuffixChar123bazChar125suffixFooRouteImport.update({ +const NonNestedPrefixPrefixChar123bazChar125FooRoute = + NonNestedPrefixPrefixChar123bazChar125FooRouteImport.update({ id: '/foo', path: '/foo', - getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, + getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, } as any) const NonNestedPrefixPrefixChar123bazChar125BarRoute = NonNestedPrefixPrefixChar123bazChar125BarRouteImport.update({ @@ -681,67 +656,68 @@ const NonNestedPrefixPrefixChar123bazChar125BarRoute = path: '/prefix{$baz}/bar', getParentRoute: () => NonNestedPrefixRouteRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125FooRoute = - NonNestedPrefixPrefixChar123bazChar125FooRouteImport.update({ +const NonNestedSuffixChar123bazChar125suffixIndexRoute = + NonNestedSuffixChar123bazChar125suffixIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, + } as any) +const NonNestedSuffixChar123bazChar125suffixFooRoute = + NonNestedSuffixChar123bazChar125suffixFooRouteImport.update({ id: '/foo', path: '/foo', - getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, + getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, + } as any) +const NonNestedSuffixChar123bazChar125suffixBarRoute = + NonNestedSuffixChar123bazChar125suffixBarRouteImport.update({ + id: '/{$baz}suffix_/bar', + path: '/{$baz}suffix/bar', + getParentRoute: () => NonNestedSuffixRouteRoute, } as any) -const NonNestedPathBazBarRoute = NonNestedPathBazBarRouteImport.update({ - id: '/baz_/bar', - path: '/baz/bar', - getParentRoute: () => NonNestedPathRouteRoute, -} as any) -const NonNestedPathBazFooRoute = NonNestedPathBazFooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => NonNestedPathBazRouteRoute, -} as any) -const NonNestedNamedBazBarRoute = NonNestedNamedBazBarRouteImport.update({ - id: '/$baz_/bar', - path: '/$baz/bar', - getParentRoute: () => NonNestedNamedRouteRoute, -} as any) -const NonNestedNamedBazFooRoute = NonNestedNamedBazFooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => NonNestedNamedBazRouteRoute, -} as any) const ParamsPsNamedFooBarRouteRoute = ParamsPsNamedFooBarRouteRouteImport.update({ id: '/$bar', path: '/$bar', getParentRoute: () => ParamsPsNamedFooRouteRoute, } as any) -const NonNestedDeepBazBarRouteRoute = - NonNestedDeepBazBarRouteRouteImport.update({ - id: '/$baz_/bar', - path: '/$baz/bar', - getParentRoute: () => NonNestedDeepRouteRoute, +const ParamsPsNonNestedFooBarRoute = ParamsPsNonNestedFooBarRouteImport.update({ + id: '/$bar', + path: '/$bar', + getParentRoute: () => ParamsPsNonNestedFooRouteRoute, +} as any) +const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({ + id: '/nested/', + path: '/nested/', + getParentRoute: () => RelativeLinkRouteRoute, +} as any) +const RelativeLinkPathIndexRoute = RelativeLinkPathIndexRouteImport.update({ + id: '/path/', + path: '/path/', + getParentRoute: () => RelativeLinkRouteRoute, +} as any) +const RelativeLinkWithSearchIndexRoute = + RelativeLinkWithSearchIndexRouteImport.update({ + id: '/with-search/', + path: '/with-search/', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const RelativeUseNavigatePathPathIndexRoute = - RelativeUseNavigatePathPathIndexRouteImport.update({ - id: '/path/$path/', - path: '/path/$path/', +const RelativeUseNavigateNestedIndexRoute = + RelativeUseNavigateNestedIndexRouteImport.update({ + id: '/nested/', + path: '/nested/', getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeUseNavigateNestedDeepIndexRoute = - RelativeUseNavigateNestedDeepIndexRouteImport.update({ - id: '/nested/deep/', - path: '/nested/deep/', +const RelativeUseNavigatePathIndexRoute = + RelativeUseNavigatePathIndexRouteImport.update({ + id: '/path/', + path: '/path/', getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeLinkPathPathIndexRoute = - RelativeLinkPathPathIndexRouteImport.update({ - id: '/path/$path/', - path: '/path/$path/', - getParentRoute: () => RelativeLinkRouteRoute, - } as any) -const RelativeLinkNestedDeepIndexRoute = - RelativeLinkNestedDeepIndexRouteImport.update({ - id: '/nested/deep/', - path: '/nested/deep/', - getParentRoute: () => RelativeLinkRouteRoute, +const RelativeUseNavigateWithSearchIndexRoute = + RelativeUseNavigateWithSearchIndexRouteImport.update({ + id: '/with-search/', + path: '/with-search/', + getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) const NonNestedDeepBazBarIndexRoute = NonNestedDeepBazBarIndexRouteImport.update({ @@ -749,22 +725,46 @@ const NonNestedDeepBazBarIndexRoute = path: '/', getParentRoute: () => NonNestedDeepBazBarRouteRoute, } as any) -const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => ParamsPsNamedFooBarRouteRoute, -} as any) -const NonNestedDeepBazBarQuxRoute = NonNestedDeepBazBarQuxRouteImport.update({ - id: '/$baz_/bar_/qux', - path: '/$baz/bar/qux', - getParentRoute: () => NonNestedDeepRouteRoute, -} as any) const NonNestedDeepBazBarFooRouteRoute = NonNestedDeepBazBarFooRouteRouteImport.update({ id: '/$foo', path: '/$foo', getParentRoute: () => NonNestedDeepBazBarRouteRoute, } as any) +const NonNestedDeepBazBarQuxRoute = NonNestedDeepBazBarQuxRouteImport.update({ + id: '/$baz_/bar_/qux', + path: '/$baz/bar/qux', + getParentRoute: () => NonNestedDeepRouteRoute, +} as any) +const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => ParamsPsNamedFooBarRouteRoute, +} as any) +const RelativeLinkNestedDeepIndexRoute = + RelativeLinkNestedDeepIndexRouteImport.update({ + id: '/nested/deep/', + path: '/nested/deep/', + getParentRoute: () => RelativeLinkRouteRoute, + } as any) +const RelativeLinkPathPathIndexRoute = + RelativeLinkPathPathIndexRouteImport.update({ + id: '/path/$path/', + path: '/path/$path/', + getParentRoute: () => RelativeLinkRouteRoute, + } as any) +const RelativeUseNavigateNestedDeepIndexRoute = + RelativeUseNavigateNestedDeepIndexRouteImport.update({ + id: '/nested/deep/', + path: '/nested/deep/', + getParentRoute: () => RelativeUseNavigateRouteRoute, + } as any) +const RelativeUseNavigatePathPathIndexRoute = + RelativeUseNavigatePathPathIndexRouteImport.update({ + id: '/path/$path/', + path: '/path/$path/', + getParentRoute: () => RelativeUseNavigateRouteRoute, + } as any) const NonNestedDeepBazBarFooIndexRoute = NonNestedDeepBazBarFooIndexRouteImport.update({ id: '/', @@ -1521,46 +1521,39 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/notRemountDeps': { - id: '/notRemountDeps' - path: '/notRemountDeps' - fullPath: '/notRemountDeps' - preLoaderRoute: typeof NotRemountDepsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/masks': { - id: '/masks' - path: '/masks' - fullPath: '/masks' - preLoaderRoute: typeof MasksRouteImport + '/anchor': { + id: '/anchor' + path: '/anchor' + fullPath: '/anchor' + preLoaderRoute: typeof AnchorRouteImport parentRoute: typeof rootRouteImport } - '/lazy-error': { - id: '/lazy-error' - path: '/lazy-error' - fullPath: '/lazy-error' - preLoaderRoute: typeof LazyErrorRouteImport + '/component-types-test': { + id: '/component-types-test' + path: '/component-types-test' + fullPath: '/component-types-test' + preLoaderRoute: typeof ComponentTypesTestRouteImport parentRoute: typeof rootRouteImport } - '/hover-preload-hash': { - id: '/hover-preload-hash' - path: '/hover-preload-hash' - fullPath: '/hover-preload-hash' - preLoaderRoute: typeof HoverPreloadHashRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport parentRoute: typeof rootRouteImport } '/editing-b': { @@ -1570,46 +1563,46 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/fullpath-test': { + id: '/fullpath-test' + path: '/fullpath-test' + fullPath: '/fullpath-test' + preLoaderRoute: typeof FullpathTestRouteRouteImport parentRoute: typeof rootRouteImport } - '/component-types-test': { - id: '/component-types-test' - path: '/component-types-test' - fullPath: '/component-types-test' - preLoaderRoute: typeof ComponentTypesTestRouteImport + '/hover-preload-hash': { + id: '/hover-preload-hash' + path: '/hover-preload-hash' + fullPath: '/hover-preload-hash' + preLoaderRoute: typeof HoverPreloadHashRouteImport parentRoute: typeof rootRouteImport } - '/anchor': { - id: '/anchor' - path: '/anchor' - fullPath: '/anchor' - preLoaderRoute: typeof AnchorRouteImport + '/lazy-error': { + id: '/lazy-error' + path: '/lazy-error' + fullPath: '/lazy-error' + preLoaderRoute: typeof LazyErrorRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/masks': { + id: '/masks' + path: '/masks' + fullPath: '/masks' + preLoaderRoute: typeof MasksRouteImport parentRoute: typeof rootRouteImport } - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteRouteImport + '/non-nested': { + id: '/non-nested' + path: '/non-nested' + fullPath: '/non-nested' + preLoaderRoute: typeof NonNestedRouteRouteImport parentRoute: typeof rootRouteImport } - '/search-params': { - id: '/search-params' - path: '/search-params' - fullPath: '/search-params' - preLoaderRoute: typeof SearchParamsRouteRouteImport + '/notRemountDeps': { + id: '/notRemountDeps' + path: '/notRemountDeps' + fullPath: '/notRemountDeps' + preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } '/pathless-layout': { @@ -1619,54 +1612,110 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteRouteImport parentRoute: typeof rootRouteImport } - '/non-nested': { - id: '/non-nested' - path: '/non-nested' - fullPath: '/non-nested' - preLoaderRoute: typeof NonNestedRouteRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/fullpath-test': { - id: '/fullpath-test' - path: '/fullpath-test' - fullPath: '/fullpath-test' - preLoaderRoute: typeof FullpathTestRouteRouteImport + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/search-params': { + id: '/search-params' + path: '/search-params' + fullPath: '/search-params' + preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/search-params/': { - id: '/search-params/' - path: '/' - fullPath: '/search-params/' - preLoaderRoute: typeof SearchParamsIndexRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteRouteImport + parentRoute: typeof rootRouteImport } - '/relative/': { - id: '/relative/' - path: '/relative' - fullPath: '/relative/' - preLoaderRoute: typeof RelativeIndexRouteImport + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport parentRoute: typeof rootRouteImport } - '/redirect/': { - id: '/redirect/' - path: '/redirect' - fullPath: '/redirect/' - preLoaderRoute: typeof RedirectIndexRouteImport + '/(group)/_layout': { + id: '/(group)/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport + parentRoute: typeof rootRouteImport + } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } + '/fullpath-test/_layout': { + id: '/fullpath-test/_layout' + path: '' + fullPath: '/fullpath-test' + preLoaderRoute: typeof FullpathTestLayoutRouteRouteImport + parentRoute: typeof FullpathTestRouteRoute + } + '/non-nested/deep': { + id: '/non-nested/deep' + path: '/deep' + fullPath: '/non-nested/deep' + preLoaderRoute: typeof NonNestedDeepRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/named': { + id: '/non-nested/named' + path: '/named' + fullPath: '/non-nested/named' + preLoaderRoute: typeof NonNestedNamedRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/path': { + id: '/non-nested/path' + path: '/path' + fullPath: '/non-nested/path' + preLoaderRoute: typeof NonNestedPathRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/prefix': { + id: '/non-nested/prefix' + path: '/prefix' + fullPath: '/non-nested/prefix' + preLoaderRoute: typeof NonNestedPrefixRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/suffix': { + id: '/non-nested/suffix' + path: '/suffix' + fullPath: '/non-nested/suffix' + preLoaderRoute: typeof NonNestedSuffixRouteRouteImport + parentRoute: typeof NonNestedRouteRoute } '/params-ps/': { id: '/params-ps/' @@ -1675,27 +1724,34 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsPsIndexRouteImport parentRoute: typeof rootRouteImport } - '/structural-sharing/$enabled': { - id: '/structural-sharing/$enabled' - path: '/structural-sharing/$enabled' - fullPath: '/structural-sharing/$enabled' - preLoaderRoute: typeof StructuralSharingEnabledRouteImport + '/params-ps/non-nested': { + id: '/params-ps/non-nested' + path: '/params-ps/non-nested' + fullPath: '/params-ps/non-nested' + preLoaderRoute: typeof ParamsPsNonNestedRouteRouteImport parentRoute: typeof rootRouteImport } - '/search-params/default': { - id: '/search-params/default' - path: '/default' - fullPath: '/search-params/default' - preLoaderRoute: typeof SearchParamsDefaultRouteImport - parentRoute: typeof SearchParamsRouteRoute - } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport + '/params-ps/strict-false': { + id: '/params-ps/strict-false' + path: '/params-ps/strict-false' + fullPath: '/params-ps/strict-false' + preLoaderRoute: typeof ParamsPsStrictFalseRouteRouteImport parentRoute: typeof rootRouteImport } + '/pathless-layout/_layout': { + id: '/pathless-layout/_layout' + path: '' + fullPath: '/pathless-layout' + preLoaderRoute: typeof PathlessLayoutLayoutRouteRouteImport + parentRoute: typeof PathlessLayoutRouteRoute + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -1703,39 +1759,32 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport + '/relative/': { + id: '/relative/' + path: '/relative' + fullPath: '/relative/' + preLoaderRoute: typeof RelativeIndexRouteImport parentRoute: typeof rootRouteImport } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/relative/link': { + id: '/relative/link' + path: '/relative/link' + fullPath: '/relative/link' + preLoaderRoute: typeof RelativeLinkRouteRouteImport parentRoute: typeof rootRouteImport } '/relative/useNavigate': { @@ -1745,96 +1794,117 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RelativeUseNavigateRouteRouteImport parentRoute: typeof rootRouteImport } - '/relative/link': { - id: '/relative/link' - path: '/relative/link' - fullPath: '/relative/link' - preLoaderRoute: typeof RelativeLinkRouteRouteImport - parentRoute: typeof rootRouteImport + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/pathless-layout/_layout': { - id: '/pathless-layout/_layout' - path: '' - fullPath: '/pathless-layout' - preLoaderRoute: typeof PathlessLayoutLayoutRouteRouteImport - parentRoute: typeof PathlessLayoutRouteRoute + '/search-params/default': { + id: '/search-params/default' + path: '/default' + fullPath: '/search-params/default' + preLoaderRoute: typeof SearchParamsDefaultRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/params-ps/strict-false': { - id: '/params-ps/strict-false' - path: '/params-ps/strict-false' - fullPath: '/params-ps/strict-false' - preLoaderRoute: typeof ParamsPsStrictFalseRouteRouteImport + '/structural-sharing/$enabled': { + id: '/structural-sharing/$enabled' + path: '/structural-sharing/$enabled' + fullPath: '/structural-sharing/$enabled' + preLoaderRoute: typeof StructuralSharingEnabledRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/non-nested': { - id: '/params-ps/non-nested' - path: '/params-ps/non-nested' - fullPath: '/params-ps/non-nested' - preLoaderRoute: typeof ParamsPsNonNestedRouteRouteImport - parentRoute: typeof rootRouteImport + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/non-nested/suffix': { - id: '/non-nested/suffix' - path: '/suffix' - fullPath: '/non-nested/suffix' - preLoaderRoute: typeof NonNestedSuffixRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport + parentRoute: typeof rootRouteImport } - '/non-nested/prefix': { - id: '/non-nested/prefix' - path: '/prefix' - fullPath: '/non-nested/prefix' - preLoaderRoute: typeof NonNestedPrefixRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/non-nested/path': { - id: '/non-nested/path' - path: '/path' - fullPath: '/non-nested/path' - preLoaderRoute: typeof NonNestedPathRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/_layout/_layout-2/layout-b': { + id: '/_layout/_layout-2/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport + parentRoute: typeof LayoutLayout2Route } - '/non-nested/named': { - id: '/non-nested/named' - path: '/named' - fullPath: '/non-nested/named' - preLoaderRoute: typeof NonNestedNamedRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/fullpath-test/_layout/': { + id: '/fullpath-test/_layout/' + path: '/' + fullPath: '/fullpath-test/' + preLoaderRoute: typeof FullpathTestLayoutIndexRouteImport + parentRoute: typeof FullpathTestLayoutRouteRoute } - '/non-nested/deep': { - id: '/non-nested/deep' - path: '/deep' - fullPath: '/non-nested/deep' - preLoaderRoute: typeof NonNestedDeepRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/fullpath-test/_layout/$id': { + id: '/fullpath-test/_layout/$id' + path: '/$id' + fullPath: '/fullpath-test/$id' + preLoaderRoute: typeof FullpathTestLayoutIdRouteImport + parentRoute: typeof FullpathTestLayoutRouteRoute } - '/fullpath-test/_layout': { - id: '/fullpath-test/_layout' - path: '' - fullPath: '/fullpath-test' - preLoaderRoute: typeof FullpathTestLayoutRouteRouteImport - parentRoute: typeof FullpathTestRouteRoute + '/masks/admin/$userId': { + id: '/masks/admin/$userId' + path: '/admin/$userId' + fullPath: '/masks/admin/$userId' + preLoaderRoute: typeof MasksAdminUserIdRouteImport + parentRoute: typeof MasksRoute } - '/redirect/$target/': { - id: '/redirect/$target/' - path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport - parentRoute: typeof RedirectTargetRoute + '/masks/public/$username': { + id: '/masks/public/$username' + path: '/public/$username' + fullPath: '/masks/public/$username' + preLoaderRoute: typeof MasksPublicUsernameRouteImport + parentRoute: typeof MasksRoute } - '/pathless-layout/_layout/': { - id: '/pathless-layout/_layout/' - path: '/' - fullPath: '/pathless-layout/' - preLoaderRoute: typeof PathlessLayoutLayoutIndexRouteImport - parentRoute: typeof PathlessLayoutLayoutRouteRoute + '/non-nested/deep/$baz': { + id: '/non-nested/deep/$baz' + path: '/$baz' + fullPath: '/non-nested/deep/$baz' + preLoaderRoute: typeof NonNestedDeepBazRouteRouteImport + parentRoute: typeof NonNestedDeepRouteRoute } - '/params-ps/wildcard/': { - id: '/params-ps/wildcard/' - path: '/params-ps/wildcard' - fullPath: '/params-ps/wildcard/' - preLoaderRoute: typeof ParamsPsWildcardIndexRouteImport - parentRoute: typeof rootRouteImport + '/non-nested/named/$baz': { + id: '/non-nested/named/$baz' + path: '/$baz' + fullPath: '/non-nested/named/$baz' + preLoaderRoute: typeof NonNestedNamedBazRouteRouteImport + parentRoute: typeof NonNestedNamedRouteRoute + } + '/non-nested/path/baz': { + id: '/non-nested/path/baz' + path: '/baz' + fullPath: '/non-nested/path/baz' + preLoaderRoute: typeof NonNestedPathBazRouteRouteImport + parentRoute: typeof NonNestedPathRouteRoute + } + '/non-nested/prefix/prefix{$baz}': { + id: '/non-nested/prefix/prefix{$baz}' + path: '/prefix{$baz}' + fullPath: '/non-nested/prefix/prefix{$baz}' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRouteImport + parentRoute: typeof NonNestedPrefixRouteRoute + } + '/non-nested/suffix/{$baz}suffix': { + id: '/non-nested/suffix/{$baz}suffix' + path: '/{$baz}suffix' + fullPath: '/non-nested/suffix/{$baz}suffix' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRouteImport + parentRoute: typeof NonNestedSuffixRouteRoute } '/params-ps/named/': { id: '/params-ps/named/' @@ -1843,116 +1913,116 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsPsNamedIndexRouteImport parentRoute: typeof rootRouteImport } - '/fullpath-test/_layout/': { - id: '/fullpath-test/_layout/' - path: '/' - fullPath: '/fullpath-test/' - preLoaderRoute: typeof FullpathTestLayoutIndexRouteImport - parentRoute: typeof FullpathTestLayoutRouteRoute + '/params-ps/named/$foo': { + id: '/params-ps/named/$foo' + path: '/params-ps/named/$foo' + fullPath: '/params-ps/named/$foo' + preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport + parentRoute: typeof rootRouteImport } - '/대한민국/🚀/$id': { - id: '/대한민국/🚀/$id' - path: '/🚀/$id' - fullPath: '/대한민국/🚀/$id' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport - parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute + '/params-ps/named/prefix{$foo}': { + id: '/params-ps/named/prefix{$foo}' + path: '/params-ps/named/prefix{$foo}' + fullPath: '/params-ps/named/prefix{$foo}' + preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport + parentRoute: typeof rootRouteImport } - '/대한민국/wildcard/$': { - id: '/대한민국/wildcard/$' - path: '/wildcard/$' - fullPath: '/대한민국/wildcard/$' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397WildcardSplatRouteImport - parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute + '/params-ps/named/{$foo}suffix': { + id: '/params-ps/named/{$foo}suffix' + path: '/params-ps/named/{$foo}suffix' + fullPath: '/params-ps/named/{$foo}suffix' + preLoaderRoute: typeof ParamsPsNamedChar123fooChar125suffixRouteImport + parentRoute: typeof rootRouteImport } - '/relative/useNavigate/relative-useNavigate-b': { - id: '/relative/useNavigate/relative-useNavigate-b' - path: '/relative-useNavigate-b' - fullPath: '/relative/useNavigate/relative-useNavigate-b' - preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateBRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/params-ps/non-nested/$foo_': { + id: '/params-ps/non-nested/$foo_' + path: '/$foo' + fullPath: '/params-ps/non-nested/$foo' + preLoaderRoute: typeof ParamsPsNonNestedFooRouteRouteImport + parentRoute: typeof ParamsPsNonNestedRouteRoute } - '/relative/useNavigate/relative-useNavigate-a': { - id: '/relative/useNavigate/relative-useNavigate-a' - path: '/relative-useNavigate-a' - fullPath: '/relative/useNavigate/relative-useNavigate-a' - preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateARouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/params-ps/strict-false/$version': { + id: '/params-ps/strict-false/$version' + path: '/$version' + fullPath: '/params-ps/strict-false/$version' + preLoaderRoute: typeof ParamsPsStrictFalseVersionRouteRouteImport + parentRoute: typeof ParamsPsStrictFalseRouteRoute } - '/relative/link/relative-link-b': { - id: '/relative/link/relative-link-b' - path: '/relative-link-b' - fullPath: '/relative/link/relative-link-b' - preLoaderRoute: typeof RelativeLinkRelativeLinkBRouteImport - parentRoute: typeof RelativeLinkRouteRoute + '/params-ps/wildcard/': { + id: '/params-ps/wildcard/' + path: '/params-ps/wildcard' + fullPath: '/params-ps/wildcard/' + preLoaderRoute: typeof ParamsPsWildcardIndexRouteImport + parentRoute: typeof rootRouteImport } - '/relative/link/relative-link-a': { - id: '/relative/link/relative-link-a' - path: '/relative-link-a' - fullPath: '/relative/link/relative-link-a' - preLoaderRoute: typeof RelativeLinkRelativeLinkARouteImport - parentRoute: typeof RelativeLinkRouteRoute + '/params-ps/wildcard/$': { + id: '/params-ps/wildcard/$' + path: '/params-ps/wildcard/$' + fullPath: '/params-ps/wildcard/$' + preLoaderRoute: typeof ParamsPsWildcardSplatRouteImport + parentRoute: typeof rootRouteImport } - '/redirect/preload/third': { - id: '/redirect/preload/third' - path: '/redirect/preload/third' - fullPath: '/redirect/preload/third' - preLoaderRoute: typeof RedirectPreloadThirdRouteImport + '/params-ps/wildcard/prefix@대{$}': { + id: '/params-ps/wildcard/prefix@대{$}' + path: '/params-ps/wildcard/prefix@대{$}' + fullPath: '/params-ps/wildcard/prefix@대{$}' + preLoaderRoute: typeof ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport parentRoute: typeof rootRouteImport } - '/redirect/preload/second': { - id: '/redirect/preload/second' - path: '/redirect/preload/second' - fullPath: '/redirect/preload/second' - preLoaderRoute: typeof RedirectPreloadSecondRouteImport + '/params-ps/wildcard/prefix{$}': { + id: '/params-ps/wildcard/prefix{$}' + path: '/params-ps/wildcard/prefix{$}' + fullPath: '/params-ps/wildcard/prefix{$}' + preLoaderRoute: typeof ParamsPsWildcardPrefixChar123Char125RouteImport parentRoute: typeof rootRouteImport } - '/redirect/preload/first': { - id: '/redirect/preload/first' - path: '/redirect/preload/first' - fullPath: '/redirect/preload/first' - preLoaderRoute: typeof RedirectPreloadFirstRouteImport + '/params-ps/wildcard/{$}suffix': { + id: '/params-ps/wildcard/{$}suffix' + path: '/params-ps/wildcard/{$}suffix' + fullPath: '/params-ps/wildcard/{$}suffix' + preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixRouteImport parentRoute: typeof rootRouteImport } - '/redirect/$target/via-routeApi-redirect-loader': { - id: '/redirect/$target/via-routeApi-redirect-loader' - path: '/via-routeApi-redirect-loader' - fullPath: '/redirect/$target/via-routeApi-redirect-loader' - preLoaderRoute: typeof RedirectTargetViaRouteApiRedirectLoaderRouteImport - parentRoute: typeof RedirectTargetRoute + '/params-ps/wildcard/{$}suffix@대': { + id: '/params-ps/wildcard/{$}suffix@대' + path: '/params-ps/wildcard/{$}suffix@대' + fullPath: '/params-ps/wildcard/{$}suffix@대' + preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport + parentRoute: typeof rootRouteImport } - '/redirect/$target/via-routeApi-redirect-beforeLoad': { - id: '/redirect/$target/via-routeApi-redirect-beforeLoad' - path: '/via-routeApi-redirect-beforeLoad' - fullPath: '/redirect/$target/via-routeApi-redirect-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + '/params/single/$value': { + id: '/params/single/$value' + path: '/params/single/$value' + fullPath: '/params/single/$value' + preLoaderRoute: typeof ParamsSingleValueRouteImport + parentRoute: typeof rootRouteImport } - '/redirect/$target/via-route-redirect-loader': { - id: '/redirect/$target/via-route-redirect-loader' - path: '/via-route-redirect-loader' - fullPath: '/redirect/$target/via-route-redirect-loader' - preLoaderRoute: typeof RedirectTargetViaRouteRedirectLoaderRouteImport - parentRoute: typeof RedirectTargetRoute + '/pathless-layout/_layout/': { + id: '/pathless-layout/_layout/' + path: '/' + fullPath: '/pathless-layout/' + preLoaderRoute: typeof PathlessLayoutLayoutIndexRouteImport + parentRoute: typeof PathlessLayoutLayoutRouteRoute } - '/redirect/$target/via-route-redirect-beforeLoad': { - id: '/redirect/$target/via-route-redirect-beforeLoad' - path: '/via-route-redirect-beforeLoad' - fullPath: '/redirect/$target/via-route-redirect-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaRouteRedirectBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + '/pathless-layout/_layout/child': { + id: '/pathless-layout/_layout/child' + path: '/child' + fullPath: '/pathless-layout/child' + preLoaderRoute: typeof PathlessLayoutLayoutChildRouteImport + parentRoute: typeof PathlessLayoutLayoutRouteRoute } - '/redirect/$target/via-loader': { - id: '/redirect/$target/via-loader' - path: '/via-loader' - fullPath: '/redirect/$target/via-loader' - preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport - parentRoute: typeof RedirectTargetRoute + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport + 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/destination': { @@ -1962,250 +2032,110 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RedirectTargetDestinationRouteImport parentRoute: typeof RedirectTargetRoute } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport - parentRoute: typeof rootRouteImport - } - '/pathless-layout/_layout/child': { - id: '/pathless-layout/_layout/child' - path: '/child' - fullPath: '/pathless-layout/child' - preLoaderRoute: typeof PathlessLayoutLayoutChildRouteImport - parentRoute: typeof PathlessLayoutLayoutRouteRoute - } - '/params/single/$value': { - id: '/params/single/$value' - path: '/params/single/$value' - fullPath: '/params/single/$value' - preLoaderRoute: typeof ParamsSingleValueRouteImport - parentRoute: typeof rootRouteImport - } - '/params-ps/wildcard/{$}suffix@대': { - id: '/params-ps/wildcard/{$}suffix@대' - path: '/params-ps/wildcard/{$}suffix@대' - fullPath: '/params-ps/wildcard/{$}suffix@대' - preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/wildcard/{$}suffix': { - id: '/params-ps/wildcard/{$}suffix' - path: '/params-ps/wildcard/{$}suffix' - fullPath: '/params-ps/wildcard/{$}suffix' - preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixRouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-loader': { + id: '/redirect/$target/via-loader' + path: '/via-loader' + fullPath: '/redirect/$target/via-loader' + preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/wildcard/prefix{$}': { - id: '/params-ps/wildcard/prefix{$}' - path: '/params-ps/wildcard/prefix{$}' - fullPath: '/params-ps/wildcard/prefix{$}' - preLoaderRoute: typeof ParamsPsWildcardPrefixChar123Char125RouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-route-redirect-beforeLoad': { + id: '/redirect/$target/via-route-redirect-beforeLoad' + path: '/via-route-redirect-beforeLoad' + fullPath: '/redirect/$target/via-route-redirect-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaRouteRedirectBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/wildcard/prefix@대{$}': { - id: '/params-ps/wildcard/prefix@대{$}' - path: '/params-ps/wildcard/prefix@대{$}' - fullPath: '/params-ps/wildcard/prefix@대{$}' - preLoaderRoute: typeof ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-route-redirect-loader': { + id: '/redirect/$target/via-route-redirect-loader' + path: '/via-route-redirect-loader' + fullPath: '/redirect/$target/via-route-redirect-loader' + preLoaderRoute: typeof RedirectTargetViaRouteRedirectLoaderRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/wildcard/$': { - id: '/params-ps/wildcard/$' - path: '/params-ps/wildcard/$' - fullPath: '/params-ps/wildcard/$' - preLoaderRoute: typeof ParamsPsWildcardSplatRouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-routeApi-redirect-beforeLoad': { + id: '/redirect/$target/via-routeApi-redirect-beforeLoad' + path: '/via-routeApi-redirect-beforeLoad' + fullPath: '/redirect/$target/via-routeApi-redirect-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaRouteApiRedirectBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/named/{$foo}suffix': { - id: '/params-ps/named/{$foo}suffix' - path: '/params-ps/named/{$foo}suffix' - fullPath: '/params-ps/named/{$foo}suffix' - preLoaderRoute: typeof ParamsPsNamedChar123fooChar125suffixRouteImport - parentRoute: typeof rootRouteImport + '/redirect/$target/via-routeApi-redirect-loader': { + id: '/redirect/$target/via-routeApi-redirect-loader' + path: '/via-routeApi-redirect-loader' + fullPath: '/redirect/$target/via-routeApi-redirect-loader' + preLoaderRoute: typeof RedirectTargetViaRouteApiRedirectLoaderRouteImport + parentRoute: typeof RedirectTargetRoute } - '/params-ps/named/prefix{$foo}': { - id: '/params-ps/named/prefix{$foo}' - path: '/params-ps/named/prefix{$foo}' - fullPath: '/params-ps/named/prefix{$foo}' - preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport + '/redirect/preload/first': { + id: '/redirect/preload/first' + path: '/redirect/preload/first' + fullPath: '/redirect/preload/first' + preLoaderRoute: typeof RedirectPreloadFirstRouteImport parentRoute: typeof rootRouteImport } - '/masks/public/$username': { - id: '/masks/public/$username' - path: '/public/$username' - fullPath: '/masks/public/$username' - preLoaderRoute: typeof MasksPublicUsernameRouteImport - parentRoute: typeof MasksRoute - } - '/masks/admin/$userId': { - id: '/masks/admin/$userId' - path: '/admin/$userId' - fullPath: '/masks/admin/$userId' - preLoaderRoute: typeof MasksAdminUserIdRouteImport - parentRoute: typeof MasksRoute - } - '/fullpath-test/_layout/$id': { - id: '/fullpath-test/_layout/$id' - path: '/$id' - fullPath: '/fullpath-test/$id' - preLoaderRoute: typeof FullpathTestLayoutIdRouteImport - parentRoute: typeof FullpathTestLayoutRouteRoute - } - '/_layout/_layout-2/layout-b': { - id: '/_layout/_layout-2/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - 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 - } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/redirect/preload/second': { + id: '/redirect/preload/second' + path: '/redirect/preload/second' + fullPath: '/redirect/preload/second' + preLoaderRoute: typeof RedirectPreloadSecondRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } - '/params-ps/strict-false/$version': { - id: '/params-ps/strict-false/$version' - path: '/$version' - fullPath: '/params-ps/strict-false/$version' - preLoaderRoute: typeof ParamsPsStrictFalseVersionRouteRouteImport - parentRoute: typeof ParamsPsStrictFalseRouteRoute - } - '/params-ps/non-nested/$foo_': { - id: '/params-ps/non-nested/$foo_' - path: '/$foo' - fullPath: '/params-ps/non-nested/$foo' - preLoaderRoute: typeof ParamsPsNonNestedFooRouteRouteImport - parentRoute: typeof ParamsPsNonNestedRouteRoute - } - '/params-ps/named/$foo': { - id: '/params-ps/named/$foo' - path: '/params-ps/named/$foo' - fullPath: '/params-ps/named/$foo' - preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport + '/redirect/preload/third': { + id: '/redirect/preload/third' + path: '/redirect/preload/third' + fullPath: '/redirect/preload/third' + preLoaderRoute: typeof RedirectPreloadThirdRouteImport parentRoute: typeof rootRouteImport } - '/non-nested/suffix/{$baz}suffix': { - id: '/non-nested/suffix/{$baz}suffix' - path: '/{$baz}suffix' - fullPath: '/non-nested/suffix/{$baz}suffix' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRouteImport - parentRoute: typeof NonNestedSuffixRouteRoute - } - '/non-nested/prefix/prefix{$baz}': { - id: '/non-nested/prefix/prefix{$baz}' - path: '/prefix{$baz}' - fullPath: '/non-nested/prefix/prefix{$baz}' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRouteImport - parentRoute: typeof NonNestedPrefixRouteRoute - } - '/non-nested/path/baz': { - id: '/non-nested/path/baz' - path: '/baz' - fullPath: '/non-nested/path/baz' - preLoaderRoute: typeof NonNestedPathBazRouteRouteImport - parentRoute: typeof NonNestedPathRouteRoute - } - '/non-nested/named/$baz': { - id: '/non-nested/named/$baz' - path: '/$baz' - fullPath: '/non-nested/named/$baz' - preLoaderRoute: typeof NonNestedNamedBazRouteRouteImport - parentRoute: typeof NonNestedNamedRouteRoute - } - '/non-nested/deep/$baz': { - id: '/non-nested/deep/$baz' - path: '/$baz' - fullPath: '/non-nested/deep/$baz' - preLoaderRoute: typeof NonNestedDeepBazRouteRouteImport - parentRoute: typeof NonNestedDeepRouteRoute - } - '/relative/useNavigate/with-search/': { - id: '/relative/useNavigate/with-search/' - path: '/with-search' - fullPath: '/relative/useNavigate/with-search/' - preLoaderRoute: typeof RelativeUseNavigateWithSearchIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute - } - '/relative/useNavigate/path/': { - id: '/relative/useNavigate/path/' - path: '/path' - fullPath: '/relative/useNavigate/path/' - preLoaderRoute: typeof RelativeUseNavigatePathIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute - } - '/relative/useNavigate/nested/': { - id: '/relative/useNavigate/nested/' - path: '/nested' - fullPath: '/relative/useNavigate/nested/' - preLoaderRoute: typeof RelativeUseNavigateNestedIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute - } - '/relative/link/with-search/': { - id: '/relative/link/with-search/' - path: '/with-search' - fullPath: '/relative/link/with-search/' - preLoaderRoute: typeof RelativeLinkWithSearchIndexRouteImport - parentRoute: typeof RelativeLinkRouteRoute - } - '/relative/link/path/': { - id: '/relative/link/path/' - path: '/path' - fullPath: '/relative/link/path/' - preLoaderRoute: typeof RelativeLinkPathIndexRouteImport + '/relative/link/relative-link-a': { + id: '/relative/link/relative-link-a' + path: '/relative-link-a' + fullPath: '/relative/link/relative-link-a' + preLoaderRoute: typeof RelativeLinkRelativeLinkARouteImport parentRoute: typeof RelativeLinkRouteRoute } - '/relative/link/nested/': { - id: '/relative/link/nested/' - path: '/nested' - fullPath: '/relative/link/nested/' - preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport + '/relative/link/relative-link-b': { + id: '/relative/link/relative-link-b' + path: '/relative-link-b' + fullPath: '/relative/link/relative-link-b' + preLoaderRoute: typeof RelativeLinkRelativeLinkBRouteImport parentRoute: typeof RelativeLinkRouteRoute } - '/non-nested/suffix/{$baz}suffix/': { - id: '/non-nested/suffix/{$baz}suffix/' - path: '/' - fullPath: '/non-nested/suffix/{$baz}suffix/' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixIndexRouteImport - parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + '/relative/useNavigate/relative-useNavigate-a': { + id: '/relative/useNavigate/relative-useNavigate-a' + path: '/relative-useNavigate-a' + fullPath: '/relative/useNavigate/relative-useNavigate-a' + preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateARouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute } - '/non-nested/prefix/prefix{$baz}/': { - id: '/non-nested/prefix/prefix{$baz}/' - path: '/' - fullPath: '/non-nested/prefix/prefix{$baz}/' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125IndexRouteImport - parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + '/relative/useNavigate/relative-useNavigate-b': { + id: '/relative/useNavigate/relative-useNavigate-b' + path: '/relative-useNavigate-b' + fullPath: '/relative/useNavigate/relative-useNavigate-b' + preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateBRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute } - '/non-nested/path/baz/': { - id: '/non-nested/path/baz/' - path: '/' - fullPath: '/non-nested/path/baz/' - preLoaderRoute: typeof NonNestedPathBazIndexRouteImport - parentRoute: typeof NonNestedPathBazRouteRoute + '/대한민국/wildcard/$': { + id: '/대한민국/wildcard/$' + path: '/wildcard/$' + fullPath: '/대한민국/wildcard/$' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397WildcardSplatRouteImport + parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute } - '/non-nested/named/$baz/': { - id: '/non-nested/named/$baz/' - path: '/' - fullPath: '/non-nested/named/$baz/' - preLoaderRoute: typeof NonNestedNamedBazIndexRouteImport - parentRoute: typeof NonNestedNamedBazRouteRoute + '/대한민국/🚀/$id': { + id: '/대한민국/🚀/$id' + path: '/🚀/$id' + fullPath: '/대한민국/🚀/$id' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport + parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute } '/non-nested/deep/$baz/': { id: '/non-nested/deep/$baz/' @@ -2214,40 +2144,47 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NonNestedDeepBazIndexRouteImport parentRoute: typeof NonNestedDeepBazRouteRoute } - '/params-ps/non-nested/$foo_/$bar': { - id: '/params-ps/non-nested/$foo_/$bar' - path: '/$bar' - fullPath: '/params-ps/non-nested/$foo/$bar' - preLoaderRoute: typeof ParamsPsNonNestedFooBarRouteImport - parentRoute: typeof ParamsPsNonNestedFooRouteRoute + '/non-nested/deep/$baz_/bar': { + id: '/non-nested/deep/$baz_/bar' + path: '/$baz/bar' + fullPath: '/non-nested/deep/$baz/bar' + preLoaderRoute: typeof NonNestedDeepBazBarRouteRouteImport + parentRoute: typeof NonNestedDeepRouteRoute } - '/non-nested/suffix/{$baz}suffix_/bar': { - id: '/non-nested/suffix/{$baz}suffix_/bar' - path: '/{$baz}suffix/bar' - fullPath: '/non-nested/suffix/{$baz}suffix/bar' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixBarRouteImport - parentRoute: typeof NonNestedSuffixRouteRoute + '/non-nested/named/$baz/': { + id: '/non-nested/named/$baz/' + path: '/' + fullPath: '/non-nested/named/$baz/' + preLoaderRoute: typeof NonNestedNamedBazIndexRouteImport + parentRoute: typeof NonNestedNamedBazRouteRoute } - '/non-nested/suffix/{$baz}suffix/foo': { - id: '/non-nested/suffix/{$baz}suffix/foo' + '/non-nested/named/$baz/foo': { + id: '/non-nested/named/$baz/foo' path: '/foo' - fullPath: '/non-nested/suffix/{$baz}suffix/foo' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixFooRouteImport - parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + fullPath: '/non-nested/named/$baz/foo' + preLoaderRoute: typeof NonNestedNamedBazFooRouteImport + parentRoute: typeof NonNestedNamedBazRouteRoute } - '/non-nested/prefix/prefix{$baz}_/bar': { - id: '/non-nested/prefix/prefix{$baz}_/bar' - path: '/prefix{$baz}/bar' - fullPath: '/non-nested/prefix/prefix{$baz}/bar' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125BarRouteImport - parentRoute: typeof NonNestedPrefixRouteRoute + '/non-nested/named/$baz_/bar': { + id: '/non-nested/named/$baz_/bar' + path: '/$baz/bar' + fullPath: '/non-nested/named/$baz/bar' + preLoaderRoute: typeof NonNestedNamedBazBarRouteImport + parentRoute: typeof NonNestedNamedRouteRoute } - '/non-nested/prefix/prefix{$baz}/foo': { - id: '/non-nested/prefix/prefix{$baz}/foo' + '/non-nested/path/baz/': { + id: '/non-nested/path/baz/' + path: '/' + fullPath: '/non-nested/path/baz/' + preLoaderRoute: typeof NonNestedPathBazIndexRouteImport + parentRoute: typeof NonNestedPathBazRouteRoute + } + '/non-nested/path/baz/foo': { + id: '/non-nested/path/baz/foo' path: '/foo' - fullPath: '/non-nested/prefix/prefix{$baz}/foo' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125FooRouteImport - parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + fullPath: '/non-nested/path/baz/foo' + preLoaderRoute: typeof NonNestedPathBazFooRouteImport + parentRoute: typeof NonNestedPathBazRouteRoute } '/non-nested/path/baz_/bar': { id: '/non-nested/path/baz_/bar' @@ -2256,26 +2193,47 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NonNestedPathBazBarRouteImport parentRoute: typeof NonNestedPathRouteRoute } - '/non-nested/path/baz/foo': { - id: '/non-nested/path/baz/foo' + '/non-nested/prefix/prefix{$baz}/': { + id: '/non-nested/prefix/prefix{$baz}/' + path: '/' + fullPath: '/non-nested/prefix/prefix{$baz}/' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125IndexRouteImport + parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + } + '/non-nested/prefix/prefix{$baz}/foo': { + id: '/non-nested/prefix/prefix{$baz}/foo' path: '/foo' - fullPath: '/non-nested/path/baz/foo' - preLoaderRoute: typeof NonNestedPathBazFooRouteImport - parentRoute: typeof NonNestedPathBazRouteRoute + fullPath: '/non-nested/prefix/prefix{$baz}/foo' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125FooRouteImport + parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute } - '/non-nested/named/$baz_/bar': { - id: '/non-nested/named/$baz_/bar' - path: '/$baz/bar' - fullPath: '/non-nested/named/$baz/bar' - preLoaderRoute: typeof NonNestedNamedBazBarRouteImport - parentRoute: typeof NonNestedNamedRouteRoute + '/non-nested/prefix/prefix{$baz}_/bar': { + id: '/non-nested/prefix/prefix{$baz}_/bar' + path: '/prefix{$baz}/bar' + fullPath: '/non-nested/prefix/prefix{$baz}/bar' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125BarRouteImport + parentRoute: typeof NonNestedPrefixRouteRoute } - '/non-nested/named/$baz/foo': { - id: '/non-nested/named/$baz/foo' + '/non-nested/suffix/{$baz}suffix/': { + id: '/non-nested/suffix/{$baz}suffix/' + path: '/' + fullPath: '/non-nested/suffix/{$baz}suffix/' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixIndexRouteImport + parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + } + '/non-nested/suffix/{$baz}suffix/foo': { + id: '/non-nested/suffix/{$baz}suffix/foo' path: '/foo' - fullPath: '/non-nested/named/$baz/foo' - preLoaderRoute: typeof NonNestedNamedBazFooRouteImport - parentRoute: typeof NonNestedNamedBazRouteRoute + fullPath: '/non-nested/suffix/{$baz}suffix/foo' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixFooRouteImport + parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + } + '/non-nested/suffix/{$baz}suffix_/bar': { + id: '/non-nested/suffix/{$baz}suffix_/bar' + path: '/{$baz}suffix/bar' + fullPath: '/non-nested/suffix/{$baz}suffix/bar' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixBarRouteImport + parentRoute: typeof NonNestedSuffixRouteRoute } '/params-ps/named/$foo/$bar': { id: '/params-ps/named/$foo/$bar' @@ -2284,41 +2242,55 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsPsNamedFooBarRouteRouteImport parentRoute: typeof ParamsPsNamedFooRouteRoute } - '/non-nested/deep/$baz_/bar': { - id: '/non-nested/deep/$baz_/bar' - path: '/$baz/bar' - fullPath: '/non-nested/deep/$baz/bar' - preLoaderRoute: typeof NonNestedDeepBazBarRouteRouteImport - parentRoute: typeof NonNestedDeepRouteRoute - } - '/relative/useNavigate/path/$path/': { - id: '/relative/useNavigate/path/$path/' - path: '/path/$path' - fullPath: '/relative/useNavigate/path/$path/' - preLoaderRoute: typeof RelativeUseNavigatePathPathIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/params-ps/non-nested/$foo_/$bar': { + id: '/params-ps/non-nested/$foo_/$bar' + path: '/$bar' + fullPath: '/params-ps/non-nested/$foo/$bar' + preLoaderRoute: typeof ParamsPsNonNestedFooBarRouteImport + parentRoute: typeof ParamsPsNonNestedFooRouteRoute } - '/relative/useNavigate/nested/deep/': { - id: '/relative/useNavigate/nested/deep/' - path: '/nested/deep' - fullPath: '/relative/useNavigate/nested/deep/' - preLoaderRoute: typeof RelativeUseNavigateNestedDeepIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/relative/link/nested/': { + id: '/relative/link/nested/' + path: '/nested' + fullPath: '/relative/link/nested/' + preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute } - '/relative/link/path/$path/': { - id: '/relative/link/path/$path/' - path: '/path/$path' - fullPath: '/relative/link/path/$path/' - preLoaderRoute: typeof RelativeLinkPathPathIndexRouteImport + '/relative/link/path/': { + id: '/relative/link/path/' + path: '/path' + fullPath: '/relative/link/path/' + preLoaderRoute: typeof RelativeLinkPathIndexRouteImport parentRoute: typeof RelativeLinkRouteRoute } - '/relative/link/nested/deep/': { - id: '/relative/link/nested/deep/' - path: '/nested/deep' - fullPath: '/relative/link/nested/deep/' - preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport + '/relative/link/with-search/': { + id: '/relative/link/with-search/' + path: '/with-search' + fullPath: '/relative/link/with-search/' + preLoaderRoute: typeof RelativeLinkWithSearchIndexRouteImport parentRoute: typeof RelativeLinkRouteRoute } + '/relative/useNavigate/nested/': { + id: '/relative/useNavigate/nested/' + path: '/nested' + fullPath: '/relative/useNavigate/nested/' + preLoaderRoute: typeof RelativeUseNavigateNestedIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/path/': { + id: '/relative/useNavigate/path/' + path: '/path' + fullPath: '/relative/useNavigate/path/' + preLoaderRoute: typeof RelativeUseNavigatePathIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/with-search/': { + id: '/relative/useNavigate/with-search/' + path: '/with-search' + fullPath: '/relative/useNavigate/with-search/' + preLoaderRoute: typeof RelativeUseNavigateWithSearchIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } '/non-nested/deep/$baz_/bar/': { id: '/non-nested/deep/$baz_/bar/' path: '/' @@ -2326,12 +2298,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NonNestedDeepBazBarIndexRouteImport parentRoute: typeof NonNestedDeepBazBarRouteRoute } - '/params-ps/named/$foo/$bar/$baz': { - id: '/params-ps/named/$foo/$bar/$baz' - path: '/$baz' - fullPath: '/params-ps/named/$foo/$bar/$baz' - preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport - parentRoute: typeof ParamsPsNamedFooBarRouteRoute + '/non-nested/deep/$baz_/bar/$foo': { + id: '/non-nested/deep/$baz_/bar/$foo' + path: '/$foo' + fullPath: '/non-nested/deep/$baz/bar/$foo' + preLoaderRoute: typeof NonNestedDeepBazBarFooRouteRouteImport + parentRoute: typeof NonNestedDeepBazBarRouteRoute } '/non-nested/deep/$baz_/bar_/qux': { id: '/non-nested/deep/$baz_/bar_/qux' @@ -2340,12 +2312,40 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NonNestedDeepBazBarQuxRouteImport parentRoute: typeof NonNestedDeepRouteRoute } - '/non-nested/deep/$baz_/bar/$foo': { - id: '/non-nested/deep/$baz_/bar/$foo' - path: '/$foo' - fullPath: '/non-nested/deep/$baz/bar/$foo' - preLoaderRoute: typeof NonNestedDeepBazBarFooRouteRouteImport - parentRoute: typeof NonNestedDeepBazBarRouteRoute + '/params-ps/named/$foo/$bar/$baz': { + id: '/params-ps/named/$foo/$bar/$baz' + path: '/$baz' + fullPath: '/params-ps/named/$foo/$bar/$baz' + preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport + parentRoute: typeof ParamsPsNamedFooBarRouteRoute + } + '/relative/link/nested/deep/': { + id: '/relative/link/nested/deep/' + path: '/nested/deep' + fullPath: '/relative/link/nested/deep/' + preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute + } + '/relative/link/path/$path/': { + id: '/relative/link/path/$path/' + path: '/path/$path' + fullPath: '/relative/link/path/$path/' + preLoaderRoute: typeof RelativeLinkPathPathIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute + } + '/relative/useNavigate/nested/deep/': { + id: '/relative/useNavigate/nested/deep/' + path: '/nested/deep' + fullPath: '/relative/useNavigate/nested/deep/' + preLoaderRoute: typeof RelativeUseNavigateNestedDeepIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/path/$path/': { + id: '/relative/useNavigate/path/$path/' + path: '/path/$path' + fullPath: '/relative/useNavigate/path/$path/' + preLoaderRoute: typeof RelativeUseNavigatePathPathIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute } '/non-nested/deep/$baz_/bar/$foo/': { id: '/non-nested/deep/$baz_/bar/$foo/' 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 dd6c71afdd..6548381772 100644 --- a/e2e/react-router/basic-react-query-file-based/package.json +++ b/e2e/react-router/basic-react-query-file-based/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basic-react-query-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-react-query-file-based/src/routeTree.gen.ts index 62ce94f2c8..115e4bce47 100644 --- a/e2e/react-router/basic-react-query-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-react-query-file-based/src/routeTree.gen.ts @@ -9,29 +9,33 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -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 LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,20 +46,16 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -114,11 +114,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -128,13 +128,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -149,12 +156,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/_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' @@ -163,13 +170,6 @@ 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-react-query/package.json b/e2e/react-router/basic-react-query/package.json index b66abda5fb..81eb9eb67b 100644 --- a/e2e/react-router/basic-react-query/package.json +++ b/e2e/react-router/basic-react-query/package.json @@ -22,7 +22,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basic-scroll-restoration/package.json b/e2e/react-router/basic-scroll-restoration/package.json index ab00360d48..d8ebcb414b 100644 --- a/e2e/react-router/basic-scroll-restoration/package.json +++ b/e2e/react-router/basic-scroll-restoration/package.json @@ -21,7 +21,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basic-virtual-file-based/package.json b/e2e/react-router/basic-virtual-file-based/package.json index 7b9c5945ee..891f1d6328 100644 --- a/e2e/react-router/basic-virtual-file-based/package.json +++ b/e2e/react-router/basic-virtual-file-based/package.json @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", 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 72e6928d43..eb98ba28c5 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 @@ -9,76 +9,76 @@ // 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 postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' -import { Route as aRouteImport } from './routes/a' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, -} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, +} as any) +const aRoute = aRouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) +const bRoute = bRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, -} as any) -const aRoute = aRouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) export interface FileRoutesByFullPath { @@ -166,11 +166,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -180,20 +180,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute - } '/_first/_second': { id: '/_first/_second' path: '' @@ -201,6 +194,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -208,12 +208,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + 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/' @@ -222,13 +236,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -236,19 +243,12 @@ declare module '@tanstack/react-router' { 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 + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } } } 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 f3dbf81ce0..d3872b01e6 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 @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/basic/package.json b/e2e/react-router/basic/package.json index c7c1661933..a8de9acc3f 100644 --- a/e2e/react-router/basic/package.json +++ b/e2e/react-router/basic/package.json @@ -20,7 +20,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/escaped-special-strings/package.json b/e2e/react-router/escaped-special-strings/package.json index 961d2423cd..71a3cb08ca 100644 --- a/e2e/react-router/escaped-special-strings/package.json +++ b/e2e/react-router/escaped-special-strings/package.json @@ -18,7 +18,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/escaped-special-strings/src/routeTree.gen.ts b/e2e/react-router/escaped-special-strings/src/routeTree.gen.ts index b0e04cd8e8..50a5f8b305 100644 --- a/e2e/react-router/escaped-special-strings/src/routeTree.gen.ts +++ b/e2e/react-router/escaped-special-strings/src/routeTree.gen.ts @@ -10,25 +10,25 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as Char91indexChar93RouteImport } from './routes/[index]' -import { Route as Char91routeChar93RouteImport } from './routes/[route]' -import { Route as Char91lazyChar93RouteImport } from './routes/[lazy]' -import { Route as BlogRouteImport } from './routes/blog[_]' -import { Route as LayoutRouteImport } from './routes/[_]layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/[_]layout' +import { Route as BlogRouteImport } from './routes/blog[_]' +import { Route as Char91lazyChar93RouteImport } from './routes/[lazy]' +import { Route as Char91routeChar93RouteImport } from './routes/[route]' const Char91indexChar93Route = Char91indexChar93RouteImport.update({ id: '/index', path: '/index', getParentRoute: () => rootRouteImport, } as any) -const Char91routeChar93Route = Char91routeChar93RouteImport.update({ - id: '/route', - path: '/route', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const Char91lazyChar93Route = Char91lazyChar93RouteImport.update({ - id: '/lazy', - path: '/lazy', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', + path: '/_layout', getParentRoute: () => rootRouteImport, } as any) const BlogRoute = BlogRouteImport.update({ @@ -36,14 +36,14 @@ const BlogRoute = BlogRouteImport.update({ path: '/blog_', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', - path: '/_layout', +const Char91lazyChar93Route = Char91lazyChar93RouteImport.update({ + id: '/lazy', + path: '/lazy', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const Char91routeChar93Route = Char91routeChar93RouteImport.update({ + id: '/route', + path: '/route', getParentRoute: () => rootRouteImport, } as any) @@ -98,18 +98,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char91indexChar93RouteImport parentRoute: typeof rootRouteImport } - '/route': { - id: '/route' - path: '/route' - fullPath: '/route' - preLoaderRoute: typeof Char91routeChar93RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/lazy': { - id: '/lazy' - path: '/lazy' - fullPath: '/lazy' - preLoaderRoute: typeof Char91lazyChar93RouteImport + '/_layout': { + id: '/_layout' + path: '/_layout' + fullPath: '/_layout' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } '/blog_': { @@ -119,18 +119,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '/_layout' - fullPath: '/_layout' - preLoaderRoute: typeof LayoutRouteImport + '/lazy': { + id: '/lazy' + path: '/lazy' + fullPath: '/lazy' + preLoaderRoute: typeof Char91lazyChar93RouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/route': { + id: '/route' + path: '/route' + fullPath: '/route' + preLoaderRoute: typeof Char91routeChar93RouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-router/generator-cli-only/package.json b/e2e/react-router/generator-cli-only/package.json index 9fbe253126..6bbcccf4bd 100644 --- a/e2e/react-router/generator-cli-only/package.json +++ b/e2e/react-router/generator-cli-only/package.json @@ -21,7 +21,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/generator-cli-only/src/routeTree.gen.ts b/e2e/react-router/generator-cli-only/src/routeTree.gen.ts index 28cfbec8ed..9676858c7c 100644 --- a/e2e/react-router/generator-cli-only/src/routeTree.gen.ts +++ b/e2e/react-router/generator-cli-only/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,6 +117,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -131,12 +138,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -152,12 +159,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/e2e/react-router/i18n-paraglide/package.json b/e2e/react-router/i18n-paraglide/package.json index 94a36498ed..a100d58e85 100644 --- a/e2e/react-router/i18n-paraglide/package.json +++ b/e2e/react-router/i18n-paraglide/package.json @@ -19,14 +19,15 @@ }, "devDependencies": { "@inlang/paraglide-js": "^2.4.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@tanstack/router-plugin": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-router/issue-7120/index.html b/e2e/react-router/issue-7120/index.html new file mode 100644 index 0000000000..04a3530c9d --- /dev/null +++ b/e2e/react-router/issue-7120/index.html @@ -0,0 +1,10 @@ + + + + + + Issues 7120 and 7367 + + + + diff --git a/e2e/react-router/issue-7120/package.json b/e2e/react-router/issue-7120/package.json new file mode 100644 index 0000000000..a16155eb61 --- /dev/null +++ b/e2e/react-router/issue-7120/package.json @@ -0,0 +1,27 @@ +{ + "name": "tanstack-router-e2e-react-issue-7120", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "dev:e2e": "vite", + "build": "vite build && tsc --noEmit", + "preview": "vite preview", + "start": "vite", + "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/router-plugin": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.61.0", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.14" + } +} diff --git a/e2e/react-router/issue-7120/playwright.config.ts b/e2e/react-router/issue-7120/playwright.config.ts new file mode 100644 index 0000000000..61a995a815 --- /dev/null +++ b/e2e/react-router/issue-7120/playwright.config.ts @@ -0,0 +1,25 @@ +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}` + +export default defineConfig({ + testDir: './tests', + workers: 1, + reporter: [['line']], + use: { baseURL }, + webServer: { + command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} pnpm dev:e2e --port ${PORT}`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/react-router/issue-7120/src/main.tsx b/e2e/react-router/issue-7120/src/main.tsx new file mode 100644 index 0000000000..0c983251af --- /dev/null +++ b/e2e/react-router/issue-7120/src/main.tsx @@ -0,0 +1,21 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ + routeTree, + defaultViewTransition: true, +}) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/e2e/react-router/issue-7120/src/redirectGate.ts b/e2e/react-router/issue-7120/src/redirectGate.ts new file mode 100644 index 0000000000..399d303aac --- /dev/null +++ b/e2e/react-router/issue-7120/src/redirectGate.ts @@ -0,0 +1,35 @@ +type RedirectGate = { + waiting: boolean + released: boolean + release: () => void +} + +declare global { + interface Window { + redirectGate: RedirectGate + } +} + +let releasePromise!: () => void +const promise = new Promise((resolve) => { + releasePromise = resolve +}) + +const redirectGate: RedirectGate = { + waiting: false, + released: false, + release: () => { + if (!redirectGate.released) { + redirectGate.released = true + releasePromise() + } + }, +} + +window.redirectGate = redirectGate + +export async function waitForRedirectGate() { + redirectGate.waiting = true + await promise + redirectGate.waiting = false +} diff --git a/e2e/react-router/issue-7120/src/routeTree.gen.ts b/e2e/react-router/issue-7120/src/routeTree.gen.ts new file mode 100644 index 0000000000..fc671589c7 --- /dev/null +++ b/e2e/react-router/issue-7120/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* 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 PostsRouteImport } from './routes/posts' +import { Route as IndexRouteImport } from './routes/index' + +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any).lazy(() => import('./routes/posts.lazy').then((d) => d.Route)) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/posts' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/posts' + id: '__root__' | '/' | '/posts' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PostsRoute: typeof PostsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PostsRoute: PostsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/e2e/react-router/issue-7120/src/routes/__root.tsx b/e2e/react-router/issue-7120/src/routes/__root.tsx new file mode 100644 index 0000000000..2f01516a55 --- /dev/null +++ b/e2e/react-router/issue-7120/src/routes/__root.tsx @@ -0,0 +1,33 @@ +import { Outlet, createRootRoute, redirect } from '@tanstack/react-router' +import { waitForRedirectGate } from '../redirectGate' + +let shouldRedirect = true + +export const Route = createRootRoute({ + pendingMs: 0, + pendingMinMs: 500, + pendingComponent: RootPendingComponent, + beforeLoad: async ({ location }) => { + if (location.pathname !== '/' || !shouldRedirect) { + return + } + + await waitForRedirectGate() + + if (!shouldRedirect) { + return + } + + shouldRedirect = false + throw redirect({ to: '/posts', replace: true }) + }, + component: () => , +}) + +function RootPendingComponent() { + return ( +
+ Loading route +
+ ) +} diff --git a/e2e/react-router/issue-7120/src/routes/index.tsx b/e2e/react-router/issue-7120/src/routes/index.tsx new file mode 100644 index 0000000000..1626ce706b --- /dev/null +++ b/e2e/react-router/issue-7120/src/routes/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () =>
Home
, +}) diff --git a/e2e/react-router/issue-7120/src/routes/posts.lazy.tsx b/e2e/react-router/issue-7120/src/routes/posts.lazy.tsx new file mode 100644 index 0000000000..dc0b0cfd7f --- /dev/null +++ b/e2e/react-router/issue-7120/src/routes/posts.lazy.tsx @@ -0,0 +1,14 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/posts')({ + component: PostsComponent, +}) + +function PostsComponent() { + return ( +
+

Posts loaded

+

The redirected lazy route rendered.

+
+ ) +} diff --git a/e2e/react-router/issue-7120/src/routes/posts.tsx b/e2e/react-router/issue-7120/src/routes/posts.tsx new file mode 100644 index 0000000000..cc0519b74e --- /dev/null +++ b/e2e/react-router/issue-7120/src/routes/posts.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({}) diff --git a/e2e/react-router/issue-7120/src/vite-env.d.ts b/e2e/react-router/issue-7120/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/e2e/react-router/issue-7120/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/e2e/react-router/issue-7120/tests/issue-7120.repro.spec.ts b/e2e/react-router/issue-7120/tests/issue-7120.repro.spec.ts new file mode 100644 index 0000000000..081ae73691 --- /dev/null +++ b/e2e/react-router/issue-7120/tests/issue-7120.repro.spec.ts @@ -0,0 +1,33 @@ +import { expect, test } from '@playwright/test' + +test('#7120 / #7367: a root redirect cannot commit a stale match during a view transition', async ({ + page, +}) => { + const pageErrors: Array = [] + const consoleErrors: Array = [] + + page.on('pageerror', (error) => { + pageErrors.push(error.message || String(error)) + }) + page.on('console', (message) => { + if (message.type() === 'error') { + consoleErrors.push(message.text()) + } + }) + + await page.goto('/') + + const pending = page.getByTestId('root-pending') + await expect(pending).toBeVisible() + expect(await page.evaluate(() => window.redirectGate.waiting)).toBe(true) + + await page.evaluate(() => window.redirectGate.release()) + + await expect(page).toHaveURL(/\/posts$/) + await expect( + page.getByRole('heading', { name: 'Posts loaded' }), + ).toBeVisible() + await expect(pending).not.toBeVisible() + expect(pageErrors).toEqual([]) + expect(consoleErrors).toEqual([]) +}) diff --git a/e2e/react-router/issue-7120/tsconfig.json b/e2e/react-router/issue-7120/tsconfig.json new file mode 100644 index 0000000000..4f6089bc08 --- /dev/null +++ b/e2e/react-router/issue-7120/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "target": "ESNext", + "moduleResolution": "Bundler", + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "exclude": ["node_modules", "dist"] +} diff --git a/e2e/react-router/issue-7120/vite.config.ts b/e2e/react-router/issue-7120/vite.config.ts new file mode 100644 index 0000000000..5b1c613906 --- /dev/null +++ b/e2e/react-router/issue-7120/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +export default defineConfig({ + plugins: [tanstackRouter({ target: 'react' }), react()], +}) diff --git a/e2e/react-router/issue-7457/index.html b/e2e/react-router/issue-7457/index.html new file mode 100644 index 0000000000..76369b5957 --- /dev/null +++ b/e2e/react-router/issue-7457/index.html @@ -0,0 +1,10 @@ + + + + + + Issue 7457 + + + + diff --git a/e2e/react-router/issue-7457/package.json b/e2e/react-router/issue-7457/package.json new file mode 100644 index 0000000000..3220a4dbb4 --- /dev/null +++ b/e2e/react-router/issue-7457/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-router-e2e-react-issue-7457", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "dev:e2e": "vite", + "build": "vite build && tsc --noEmit", + "preview": "vite preview", + "start": "vite", + "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/react-query": "^5.99.0", + "@tanstack/react-router": "workspace:^", + "@tanstack/router-plugin": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.61.0", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.14" + } +} diff --git a/e2e/react-router/issue-7457/playwright.config.ts b/e2e/react-router/issue-7457/playwright.config.ts new file mode 100644 index 0000000000..61a995a815 --- /dev/null +++ b/e2e/react-router/issue-7457/playwright.config.ts @@ -0,0 +1,25 @@ +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}` + +export default defineConfig({ + testDir: './tests', + workers: 1, + reporter: [['line']], + use: { baseURL }, + webServer: { + command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} pnpm dev:e2e --port ${PORT}`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/react-router/issue-7457/src/main.tsx b/e2e/react-router/issue-7457/src/main.tsx new file mode 100644 index 0000000000..1b6805edc0 --- /dev/null +++ b/e2e/react-router/issue-7457/src/main.tsx @@ -0,0 +1,32 @@ +import { StrictMode, useEffect } from 'react' +import { createRoot } from 'react-dom/client' +import { QueryClient } from '@tanstack/react-query' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const queryClient = new QueryClient() +const router = createRouter({ + routeTree, + context: { queryClient }, + defaultPendingComponent: DefaultPendingComponent, + defaultPreloadStaleTime: 0, +}) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +function DefaultPendingComponent() { + useEffect(() => { + ;(globalThis as any).__pendingSeen = true + }, []) + return
loading
+} + +createRoot(document.body).render( + + + , +) diff --git a/e2e/react-router/issue-7457/src/routeTree.gen.ts b/e2e/react-router/issue-7457/src/routeTree.gen.ts new file mode 100644 index 0000000000..72a8f23384 --- /dev/null +++ b/e2e/react-router/issue-7457/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* 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 AnotherRouteImport } from './routes/another' +import { Route as IndexRouteImport } from './routes/index' + +const AnotherRoute = AnotherRouteImport.update({ + id: '/another', + path: '/another', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/another': typeof AnotherRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/another': typeof AnotherRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/another': typeof AnotherRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/another' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/another' + id: '__root__' | '/' | '/another' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AnotherRoute: typeof AnotherRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/another': { + id: '/another' + path: '/another' + fullPath: '/another' + preLoaderRoute: typeof AnotherRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AnotherRoute: AnotherRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/e2e/react-router/issue-7457/src/routes/__root.tsx b/e2e/react-router/issue-7457/src/routes/__root.tsx new file mode 100644 index 0000000000..988ba76263 --- /dev/null +++ b/e2e/react-router/issue-7457/src/routes/__root.tsx @@ -0,0 +1,25 @@ +import { Outlet, createRootRouteWithContext } from '@tanstack/react-router' +import type { QueryClient } from '@tanstack/react-query' + +export const Route = createRootRouteWithContext<{ + queryClient: QueryClient +}>()({ + beforeLoad: async ({ context }) => { + await context.queryClient.ensureQueryData({ + queryKey: ['issue-7457-root'], + queryFn: async () => { + await new Promise((resolve) => setTimeout(resolve, 1_500)) + return true + }, + }) + }, + component: RootComponent, +}) + +function RootComponent() { + return ( +
+ +
+ ) +} diff --git a/e2e/react-router/issue-7457/src/routes/another.tsx b/e2e/react-router/issue-7457/src/routes/another.tsx new file mode 100644 index 0000000000..6b6caa55b3 --- /dev/null +++ b/e2e/react-router/issue-7457/src/routes/another.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/another')({ + component: () =>
Hello "/another"!
, +}) diff --git a/e2e/react-router/issue-7457/src/routes/index.tsx b/e2e/react-router/issue-7457/src/routes/index.tsx new file mode 100644 index 0000000000..daaae2aeed --- /dev/null +++ b/e2e/react-router/issue-7457/src/routes/index.tsx @@ -0,0 +1,8 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + beforeLoad: () => { + throw redirect({ to: '/another', replace: true }) + }, + component: () =>
You should never see this!
, +}) diff --git a/e2e/react-router/issue-7457/src/vite-env.d.ts b/e2e/react-router/issue-7457/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/e2e/react-router/issue-7457/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/e2e/react-router/issue-7457/tests/issue-7457.repro.spec.ts b/e2e/react-router/issue-7457/tests/issue-7457.repro.spec.ts new file mode 100644 index 0000000000..8b20fac1eb --- /dev/null +++ b/e2e/react-router/issue-7457/tests/issue-7457.repro.spec.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test' + +test('#7457: initial child redirect after async root beforeLoad does not blank', async ({ + page, +}) => { + const pageErrors: Array = [] + const consoleErrors: Array = [] + page.on('pageerror', (error) => + pageErrors.push(error.message || String(error)), + ) + page.on('console', (message) => { + if (message.type() === 'error') { + consoleErrors.push(message.text()) + } + }) + + await page.goto('/') + + await expect(page).toHaveURL(/\/another$/) + const target = page.getByText('Hello "/another"!') + await expect + .poll(async () => ({ + targetCount: await target.count(), + pageErrors: [...pageErrors], + consoleErrors: [...consoleErrors], + })) + .toEqual({ targetCount: 1, pageErrors: [], consoleErrors: [] }) + await expect(target).toBeVisible() + await expect + .poll(() => page.evaluate(() => (globalThis as any).__pendingSeen)) + .toBe(true) + await expect(page.getByTestId('app-pending')).not.toBeVisible() + expect(pageErrors).toEqual([]) + expect(consoleErrors).toEqual([]) +}) diff --git a/e2e/react-router/issue-7457/tsconfig.json b/e2e/react-router/issue-7457/tsconfig.json new file mode 100644 index 0000000000..4f6089bc08 --- /dev/null +++ b/e2e/react-router/issue-7457/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "target": "ESNext", + "moduleResolution": "Bundler", + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "exclude": ["node_modules", "dist"] +} diff --git a/e2e/react-router/issue-7457/vite.config.js b/e2e/react-router/issue-7457/vite.config.js new file mode 100644 index 0000000000..9e39ea83d9 --- /dev/null +++ b/e2e/react-router/issue-7457/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackRouter({ target: 'react', autoCodeSplitting: true }), + react(), + ], +}) diff --git a/e2e/react-router/js-only-file-based/package.json b/e2e/react-router/js-only-file-based/package.json index 6505c4a1e4..d18d4da143 100644 --- a/e2e/react-router/js-only-file-based/package.json +++ b/e2e/react-router/js-only-file-based/package.json @@ -20,7 +20,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@tanstack/router-plugin": "workspace:^", "@types/react": "^19.0.8", diff --git a/e2e/react-router/js-only-file-based/src/routeTree.gen.js b/e2e/react-router/js-only-file-based/src/routeTree.gen.js index f2d42e5a6c..80683abb30 100644 --- a/e2e/react-router/js-only-file-based/src/routeTree.gen.js +++ b/e2e/react-router/js-only-file-based/src/routeTree.gen.js @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +}) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, }) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -}) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + }) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, }) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, }) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, }) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - }) const PostsRouteRouteChildren = { PostsPostIdRoute: PostsPostIdRoute, diff --git a/e2e/react-router/match-params/package.json b/e2e/react-router/match-params/package.json index 033a1bb5fe..3282e5e5c2 100644 --- a/e2e/react-router/match-params/package.json +++ b/e2e/react-router/match-params/package.json @@ -14,7 +14,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/rspack-basic-file-based/package.json b/e2e/react-router/rspack-basic-file-based/package.json index 74659b51e1..455ff94779 100644 --- a/e2e/react-router/rspack-basic-file-based/package.json +++ b/e2e/react-router/rspack-basic-file-based/package.json @@ -17,8 +17,8 @@ "redaxios": "^0.5.1" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", @@ -27,6 +27,7 @@ "@types/react-dom": "^19.0.3", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json index a7b9eb58d4..28d3e9b302 100644 --- a/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json +++ b/e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json @@ -17,8 +17,8 @@ "redaxios": "^0.5.1" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", @@ -28,6 +28,7 @@ "@types/react-dom": "^19.0.3", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/package.json b/e2e/react-router/scroll-restoration-sandbox-vite/package.json index 215de36a70..a7fc908019 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/react-router/scroll-restoration-sandbox-vite/package.json @@ -25,7 +25,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts b/e2e/react-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts index a4dc8750b3..0c6823c368 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts +++ b/e2e/react-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts @@ -12,12 +12,12 @@ import { createFileRoute } from '@tanstack/react-router' import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' -import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' -import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' -import { Route as testsLazyPageRouteImport } from './routes/(tests)/lazy-page' -import { Route as testsIssue7040TargetRouteImport } from './routes/(tests)/issue-7040-target' import { Route as testsIssue7040SourceRouteImport } from './routes/(tests)/issue-7040-source' +import { Route as testsIssue7040TargetRouteImport } from './routes/(tests)/issue-7040-target' +import { Route as testsLazyPageRouteImport } from './routes/(tests)/lazy-page' +import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' +import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' const testsVirtualPageLazyRouteImport = createFileRoute( '/(tests)/virtual-page', @@ -28,23 +28,23 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport - .update({ - id: '/(tests)/virtual-page', - path: '/virtual-page', - getParentRoute: () => rootRouteImport, - } as any) - .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) -const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ - id: '/(tests)/page-with-search', - path: '/page-with-search', +const testsIssue7040SourceRoute = testsIssue7040SourceRouteImport.update({ + id: '/(tests)/issue-7040-source', + path: '/issue-7040-source', getParentRoute: () => rootRouteImport, } as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', +const testsIssue7040TargetRoute = testsIssue7040TargetRouteImport.update({ + id: '/(tests)/issue-7040-target', + path: '/issue-7040-target', getParentRoute: () => rootRouteImport, } as any) +const testsLazyPageRoute = testsLazyPageRouteImport + .update({ + id: '/(tests)/lazy-page', + path: '/lazy-page', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .update({ id: '/(tests)/lazy-with-loader-page', @@ -54,23 +54,23 @@ const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .lazy(() => import('./routes/(tests)/lazy-with-loader-page.lazy').then((d) => d.Route), ) -const testsLazyPageRoute = testsLazyPageRouteImport - .update({ - id: '/(tests)/lazy-page', - path: '/lazy-page', - getParentRoute: () => rootRouteImport, - } as any) - .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) -const testsIssue7040TargetRoute = testsIssue7040TargetRouteImport.update({ - id: '/(tests)/issue-7040-target', - path: '/issue-7040-target', +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', getParentRoute: () => rootRouteImport, } as any) -const testsIssue7040SourceRoute = testsIssue7040SourceRouteImport.update({ - id: '/(tests)/issue-7040-source', - path: '/issue-7040-source', +const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ + id: '/(tests)/page-with-search', + path: '/page-with-search', getParentRoute: () => rootRouteImport, } as any) +const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport + .update({ + id: '/(tests)/virtual-page', + path: '/virtual-page', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -156,25 +156,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/virtual-page': { - id: '/(tests)/virtual-page' - path: '/virtual-page' - fullPath: '/virtual-page' - preLoaderRoute: typeof testsVirtualPageLazyRouteImport + '/(tests)/issue-7040-source': { + id: '/(tests)/issue-7040-source' + path: '/issue-7040-source' + fullPath: '/issue-7040-source' + preLoaderRoute: typeof testsIssue7040SourceRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/page-with-search': { - id: '/(tests)/page-with-search' - path: '/page-with-search' - fullPath: '/page-with-search' - preLoaderRoute: typeof testsPageWithSearchRouteImport + '/(tests)/issue-7040-target': { + id: '/(tests)/issue-7040-target' + path: '/issue-7040-target' + fullPath: '/issue-7040-target' + preLoaderRoute: typeof testsIssue7040TargetRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/normal-page': { - id: '/(tests)/normal-page' - path: '/normal-page' - fullPath: '/normal-page' - preLoaderRoute: typeof testsNormalPageRouteImport + '/(tests)/lazy-page': { + id: '/(tests)/lazy-page' + path: '/lazy-page' + fullPath: '/lazy-page' + preLoaderRoute: typeof testsLazyPageRouteImport parentRoute: typeof rootRouteImport } '/(tests)/lazy-with-loader-page': { @@ -184,25 +184,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testsLazyWithLoaderPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/lazy-page': { - id: '/(tests)/lazy-page' - path: '/lazy-page' - fullPath: '/lazy-page' - preLoaderRoute: typeof testsLazyPageRouteImport + '/(tests)/normal-page': { + id: '/(tests)/normal-page' + path: '/normal-page' + fullPath: '/normal-page' + preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/issue-7040-target': { - id: '/(tests)/issue-7040-target' - path: '/issue-7040-target' - fullPath: '/issue-7040-target' - preLoaderRoute: typeof testsIssue7040TargetRouteImport + '/(tests)/page-with-search': { + id: '/(tests)/page-with-search' + path: '/page-with-search' + fullPath: '/page-with-search' + preLoaderRoute: typeof testsPageWithSearchRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/issue-7040-source': { - id: '/(tests)/issue-7040-source' - path: '/issue-7040-source' - fullPath: '/issue-7040-source' - preLoaderRoute: typeof testsIssue7040SourceRouteImport + '/(tests)/virtual-page': { + id: '/(tests)/virtual-page' + path: '/virtual-page' + fullPath: '/virtual-page' + preLoaderRoute: typeof testsVirtualPageLazyRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts b/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts index f72d2d06ab..9c2d944dd5 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts +++ b/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts @@ -2,6 +2,21 @@ import { expect, test } from '@playwright/test' import { linkOptions } from '@tanstack/react-router' import { toRuntimePath } from '@tanstack/router-e2e-utils' +type ScrollPaintTestState = { + sourceFrameScrollYs: Array + sourceWasReset: boolean + destinationMounted: boolean + destinationCommitScrollY: number | null + destinationFrameScrollYs: Array + cleanup: () => void +} + +declare global { + interface Window { + __scrollPaintTestState?: ScrollPaintTestState + } +} + test('Smoke - Renders home', async ({ page }) => { await page.goto(toRuntimePath('/')) await expect( @@ -9,6 +24,175 @@ test('Smoke - Renders home', async ({ page }) => { ).toBeVisible() }) +test('PUSH resets scroll before the destination can render a frame (#7815)', async ({ + page, +}) => { + await page.goto(toRuntimePath('/issue-7040-source')) + await expect(page.getByTestId('issue-7040-source-top')).toBeVisible() + + const sourceScrollY = await page.evaluate(async () => { + window.scrollTo(0, 500) + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + return window.scrollY + }) + + expect(sourceScrollY).toBeGreaterThan(0) + + await page.evaluate((expectedSourceScrollY) => { + const root = document.querySelector('#app') + if (!root) { + throw new Error('App root not found') + } + + const geometry = document.createElement('div') + geometry.setAttribute('aria-hidden', 'true') + geometry.style.height = `${window.innerHeight + expectedSourceScrollY + 100}px` + geometry.style.pointerEvents = 'none' + geometry.style.width = '1px' + document.body.append(geometry) + + const state: ScrollPaintTestState = { + sourceFrameScrollYs: [], + sourceWasReset: false, + destinationMounted: false, + destinationCommitScrollY: null, + destinationFrameScrollYs: [], + cleanup: () => {}, + } + + const getDestinationHeading = () => + Array.from(document.querySelectorAll('h3')).find( + (heading) => heading.textContent === 'normal-page', + ) + + const commitObserver = new MutationObserver(() => { + const destinationHeading = getDestinationHeading() + + if (!destinationHeading) { + return + } + + commitObserver.disconnect() + state.destinationMounted = true + state.destinationCommitScrollY = window.scrollY + }) + + commitObserver.observe(root, { childList: true, subtree: true }) + + const handleScroll = () => { + if ( + document.querySelector('[data-testid="issue-7040-source-top"]') && + Math.abs(window.scrollY - expectedSourceScrollY) > 1 + ) { + state.sourceWasReset = true + } + } + window.addEventListener('scroll', handleScroll, { passive: true }) + + const originalScrollTo = window.scrollTo + window.scrollTo = ((...args: Array) => { + const requestedTop = + typeof args[0] === 'number' + ? args[1] + : typeof args[0] === 'object' && args[0] !== null + ? (args[0] as ScrollToOptions).top + : undefined + if ( + typeof requestedTop === 'number' && + Math.abs(requestedTop - expectedSourceScrollY) > 1 && + document.querySelector('[data-testid="issue-7040-source-top"]') + ) { + state.sourceWasReset = true + } + Reflect.apply(originalScrollTo, window, args) + }) as typeof window.scrollTo + + let animationFrameId = 0 + const recordFrame = () => { + if (getDestinationHeading()) { + state.destinationFrameScrollYs.push(window.scrollY) + } else if ( + document.querySelector('[data-testid="issue-7040-source-top"]') + ) { + state.sourceFrameScrollYs.push(window.scrollY) + } + animationFrameId = requestAnimationFrame(recordFrame) + } + animationFrameId = requestAnimationFrame(recordFrame) + + state.cleanup = () => { + commitObserver.disconnect() + cancelAnimationFrame(animationFrameId) + window.removeEventListener('scroll', handleScroll) + window.scrollTo = originalScrollTo + geometry.remove() + delete window.__scrollPaintTestState + } + window.__scrollPaintTestState = state + }, sourceScrollY) + + await page.evaluate( + () => + new Promise((resolve) => { + requestAnimationFrame(() => resolve()) + }), + ) + expect(await page.evaluate(() => window.scrollY)).toBe(sourceScrollY) + + await page + .getByRole('link', { name: 'Head-/normal-page' }) + .dispatchEvent('click') + + await page.waitForFunction(() => { + const state = window.__scrollPaintTestState + if (!state?.destinationMounted) { + return false + } + + const lastFrames = state.destinationFrameScrollYs.slice(-2) + return ( + lastFrames.length === 2 && + lastFrames.every((scrollY) => Math.abs(scrollY) <= 1) + ) + }) + + const observation = await page.evaluate(() => { + const state = window.__scrollPaintTestState + if (!state) { + throw new Error('Scroll paint observer not found') + } + + return { + sourceFrameScrollYs: state.sourceFrameScrollYs, + sourceWasReset: state.sourceWasReset, + destinationCommitScrollY: state.destinationCommitScrollY, + destinationFrameScrollYs: state.destinationFrameScrollYs, + destinationMaxScrollY: + document.documentElement.scrollHeight - window.innerHeight, + finalScrollY: window.scrollY, + } + }) + + expect(observation.sourceFrameScrollYs.length).toBeGreaterThan(0) + expect( + observation.sourceFrameScrollYs.every( + (scrollY) => Math.abs(scrollY - sourceScrollY) <= 1, + ), + ).toBe(true) + expect(observation.sourceWasReset).toBe(false) + expect(observation.destinationMaxScrollY).toBeGreaterThanOrEqual( + sourceScrollY, + ) + expect(observation.finalScrollY).toBe(0) + expect(observation.destinationFrameScrollYs.length).toBeGreaterThanOrEqual(2) + expect( + observation.destinationFrameScrollYs.every( + (scrollY) => Math.abs(scrollY) <= 1, + ), + `Destination paint opportunities: ${observation.destinationFrameScrollYs.join(', ')}, commit: ${observation.destinationCommitScrollY}, final: ${observation.finalScrollY}`, + ).toBe(true) +}) + test('restores the prior scroll position after browser back then forward', async ({ page, }) => { diff --git a/e2e/react-router/sentry-integration/package.json b/e2e/react-router/sentry-integration/package.json index d97446da9f..e275758b83 100644 --- a/e2e/react-router/sentry-integration/package.json +++ b/e2e/react-router/sentry-integration/package.json @@ -23,7 +23,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", diff --git a/e2e/react-router/view-transitions/package.json b/e2e/react-router/view-transitions/package.json index 1c675b7258..ac949c2d7c 100644 --- a/e2e/react-router/view-transitions/package.json +++ b/e2e/react-router/view-transitions/package.json @@ -22,12 +22,13 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-router/view-transitions/src/routeTree.gen.ts b/e2e/react-router/view-transitions/src/routeTree.gen.ts index 20ee76a1a5..8982fca68f 100644 --- a/e2e/react-router/view-transitions/src/routeTree.gen.ts +++ b/e2e/react-router/view-transitions/src/routeTree.gen.ts @@ -9,16 +9,16 @@ // 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 HowItWorksRouteImport } from './routes/how-it-works' +import { Route as IndexRouteImport } from './routes/index' import { Route as ExploreRouteImport } from './routes/explore' +import { Route as HowItWorksRouteImport } from './routes/how-it-works' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const HowItWorksRoute = HowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ExploreRoute = ExploreRouteImport.update({ @@ -26,16 +26,16 @@ const ExploreRoute = ExploreRouteImport.update({ path: '/explore', getParentRoute: () => rootRouteImport, } as any) +const HowItWorksRoute = HowItWorksRouteImport.update({ + id: '/how-it-works', + path: '/how-it-works', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -101,11 +101,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/how-it-works': { - id: '/how-it-works' - path: '/how-it-works' - fullPath: '/how-it-works' - preLoaderRoute: typeof HowItWorksRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/explore': { @@ -115,6 +115,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ExploreRouteImport parentRoute: typeof rootRouteImport } + '/how-it-works': { + id: '/how-it-works' + path: '/how-it-works' + fullPath: '/how-it-works' + preLoaderRoute: typeof HowItWorksRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -122,13 +129,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/e2e/react-start/basic-auth/package.json b/e2e/react-start/basic-auth/package.json index 4475cf5adf..52a5c12d50 100644 --- a/e2e/react-start/basic-auth/package.json +++ b/e2e/react-start/basic-auth/package.json @@ -25,7 +25,7 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -35,7 +35,8 @@ "dotenv": "^17.2.3", "prisma": "^7.0.0", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/basic-auth/src/routeTree.gen.ts b/e2e/react-start/basic-auth/src/routeTree.gen.ts index 6be84444eb..998d34a709 100644 --- a/e2e/react-start/basic-auth/src/routeTree.gen.ts +++ b/e2e/react-start/basic-auth/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/e2e/react-start/basic-cloudflare/package.json b/e2e/react-start/basic-cloudflare/package.json index 4d90b4124e..7baa24073b 100644 --- a/e2e/react-start/basic-cloudflare/package.json +++ b/e2e/react-start/basic-cloudflare/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.29.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -29,7 +29,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "wrangler": "^4.74.0" } diff --git a/e2e/react-start/basic-cloudflare/src/routeTree.gen.ts b/e2e/react-start/basic-cloudflare/src/routeTree.gen.ts index 4219501f5e..b29f0d8d68 100644 --- a/e2e/react-start/basic-cloudflare/src/routeTree.gen.ts +++ b/e2e/react-start/basic-cloudflare/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 StaticRouteImport } from './routes/static' import { Route as IndexRouteImport } from './routes/index' +import { Route as StaticRouteImport } from './routes/static' -const StaticRoute = StaticRouteImport.update({ - id: '/static', - path: '/static', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const StaticRoute = StaticRouteImport.update({ + id: '/static', + path: '/static', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/static': { - id: '/static' - path: '/static' - fullPath: '/static' - preLoaderRoute: typeof StaticRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/static': { + id: '/static' + path: '/static' + fullPath: '/static' + preLoaderRoute: typeof StaticRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/basic-react-query/package.json b/e2e/react-start/basic-react-query/package.json index 242636c63f..ed7328f336 100644 --- a/e2e/react-start/basic-react-query/package.json +++ b/e2e/react-start/basic-react-query/package.json @@ -25,7 +25,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -34,7 +34,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } 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 a1bed86dfa..72bd9579f6 100644 --- a/e2e/react-start/basic-react-query/src/routeTree.gen.ts +++ b/e2e/react-start/basic-react-query/src/routeTree.gen.ts @@ -9,38 +9,37 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' -import { Route as RedirectRouteImport } from './routes/redirect' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as DeferredRouteImport } from './routes/deferred' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api.users' 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 TransitionCountQueryRouteImport } from './routes/transition/count/query' -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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as TransitionCountQueryRouteImport } from './routes/transition/count/query' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ - id: '/suspense-transition', - path: '/suspense-transition', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -48,73 +47,74 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ + id: '/suspense-transition', + path: '/suspense-transition', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, } as any) -const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ - id: '/transition/count/query', - path: '/transition/count/query', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, } as any) const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ id: '/$id', path: '/$id', getParentRoute: () => ApiUsersRoute, } as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ + id: '/transition/count/query', + path: '/transition/count/query', + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { @@ -244,25 +244,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/suspense-transition': { - id: '/suspense-transition' - path: '/suspense-transition' - fullPath: '/suspense-transition' - preLoaderRoute: typeof SuspenseTransitionRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -272,33 +272,40 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/suspense-transition': { + id: '/suspense-transition' + path: '/suspense-transition' + fullPath: '/suspense-transition' + preLoaderRoute: typeof SuspenseTransitionRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -307,13 +314,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -321,33 +321,33 @@ 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 + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/transition/count/query': { - id: '/transition/count/query' - path: '/transition/count/query' - fullPath: '/transition/count/query' - preLoaderRoute: typeof TransitionCountQueryRouteImport - parentRoute: typeof rootRouteImport + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -356,19 +356,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiUsersIdRouteImport parentRoute: typeof ApiUsersRoute } - '/_layout/_layout-2/layout-b': { - id: '/_layout/_layout-2/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport - parentRoute: typeof LayoutLayout2Route + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/transition/count/query': { + id: '/transition/count/query' + path: '/transition/count/query' + fullPath: '/transition/count/query' + preLoaderRoute: typeof TransitionCountQueryRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/e2e/react-start/basic-tsr-config/package.json b/e2e/react-start/basic-tsr-config/package.json index e52f908a6e..61f11c2372 100644 --- a/e2e/react-start/basic-tsr-config/package.json +++ b/e2e/react-start/basic-tsr-config/package.json @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index 37a3f4a0e2..9bd494b835 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -31,8 +31,8 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", @@ -45,7 +45,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "zod": "^4.4.3" }, diff --git a/e2e/react-start/basic/src/routeTree.gen.ts b/e2e/react-start/basic/src/routeTree.gen.ts index 103853f3b8..ea09864d8e 100644 --- a/e2e/react-start/basic/src/routeTree.gen.ts +++ b/e2e/react-start/basic/src/routeTree.gen.ts @@ -9,116 +9,106 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as TypeOnlyReexportRouteImport } from './routes/type-only-reexport' -import { Route as StreamRouteImport } from './routes/stream' -import { Route as ScriptsRouteImport } from './routes/scripts' -import { Route as RawStreamRouteImport } from './routes/raw-stream' -import { Route as PrimitiveBeforeloadErrorRouteImport } from './routes/primitive-beforeload-error' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as PlainTsTypeAssertionRouteImport } from './routes/plain-ts-type-assertion' -import { Route as LinksRouteImport } from './routes/links' -import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' -import { Route as DeferredRouteImport } from './routes/deferred' -import { Route as ClientOnlyRouteImport } from './routes/client-only' -import { Route as AsyncScriptsRouteImport } from './routes/async-scripts' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' -import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as AsyncScriptsRouteImport } from './routes/async-scripts' +import { Route as ClientOnlyRouteImport } from './routes/client-only' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' +import { Route as LinksRouteImport } from './routes/links' import { Route as NotFoundRouteRouteImport } from './routes/not-found/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' -import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' +import { Route as PlainTsTypeAssertionRouteImport } from './routes/plain-ts-type-assertion' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as PrimitiveBeforeloadErrorRouteImport } from './routes/primitive-beforeload-error' +import { Route as RawStreamRouteImport } from './routes/raw-stream' +import { Route as ScriptsRouteImport } from './routes/scripts' +import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' +import { Route as StreamRouteImport } from './routes/stream' +import { Route as TypeOnlyReexportRouteImport } from './routes/type-only-reexport' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api.users' +import { Route as Issue6221DashboardRouteImport } from './routes/issue-6221.dashboard' import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' -import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' -import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' -import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' -import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' -import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' -import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' -import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' -import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' -import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' -import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' -import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' -import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' -import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' -import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' -import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target' -import { Route as ApiUsersRouteImport } from './routes/api.users' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' -import { Route as NotFoundParentBoundaryRouteRouteImport } from './routes/not-found/parent-boundary/route' +import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' import { Route as NotFoundDeepRouteRouteImport } from './routes/not-found/deep/route' -import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' -import { Route as NotFoundParentBoundaryIndexRouteImport } from './routes/not-found/parent-boundary/index' -import { Route as NotFoundDeepIndexRouteImport } from './routes/not-found/deep/index' -import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' -import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' -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 NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' -import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' -import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as NotFoundParentBoundaryRouteRouteImport } from './routes/not-found/parent-boundary/route' +import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' +import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' +import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' +import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' +import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' +import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' +import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' +import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' +import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' +import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' +import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' +import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' +import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' +import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' +import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' +import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' +import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as Issue6221ArticleIdRouteImport } from './routes/issue-6221.article.$id' +import { Route as NotFoundDeepIndexRouteImport } from './routes/not-found/deep/index' import { Route as NotFoundDeepBRouteRouteImport } from './routes/not-found/deep/b/route' -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 { Route as NotFoundParentBoundaryIndexRouteImport } from './routes/not-found/parent-boundary/index' +import { Route as NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' +import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' +import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' +import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' +import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' import { Route as FooBarQuxHereRouteImport } from './routes/foo/$bar/$qux/_here' import { Route as NotFoundDeepBCRouteRouteImport } from './routes/not-found/deep/b/c/route' +import { Route as RedirectTargetServerFnIndexRouteImport } from './routes/redirect/$target/serverFn/index' +import { Route as RedirectTargetServerFnViaBeforeLoadRouteImport } from './routes/redirect/$target/serverFn/via-beforeLoad' +import { Route as RedirectTargetServerFnViaLoaderRouteImport } from './routes/redirect/$target/serverFn/via-loader' +import { Route as RedirectTargetServerFnViaUseServerFnRouteImport } from './routes/redirect/$target/serverFn/via-useServerFn' import { Route as FooBarQuxHereIndexRouteImport } from './routes/foo/$bar/$qux/_here/index' import { Route as NotFoundDeepBCDRouteImport } from './routes/not-found/deep/b/c/d' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', - getParentRoute: () => rootRouteImport, -} as any) -const TypeOnlyReexportRoute = TypeOnlyReexportRouteImport.update({ - id: '/type-only-reexport', - path: '/type-only-reexport', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const StreamRoute = StreamRouteImport.update({ - id: '/stream', - path: '/stream', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const ScriptsRoute = ScriptsRouteImport.update({ - id: '/scripts', - path: '/scripts', +const AsyncScriptsRoute = AsyncScriptsRouteImport.update({ + id: '/async-scripts', + path: '/async-scripts', getParentRoute: () => rootRouteImport, } as any) -const RawStreamRoute = RawStreamRouteImport.update({ - id: '/raw-stream', - path: '/raw-stream', +const ClientOnlyRoute = ClientOnlyRouteImport.update({ + id: '/client-only', + path: '/client-only', getParentRoute: () => rootRouteImport, } as any) -const PrimitiveBeforeloadErrorRoute = - PrimitiveBeforeloadErrorRouteImport.update({ - id: '/primitive-beforeload-error', - path: '/primitive-beforeload-error', - getParentRoute: () => rootRouteImport, - } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const PlainTsTypeAssertionRoute = PlainTsTypeAssertionRouteImport.update({ - id: '/plain-ts-type-assertion', - path: '/plain-ts-type-assertion', +const InlineScriptsRoute = InlineScriptsRouteImport.update({ + id: '/inline-scripts', + path: '/inline-scripts', getParentRoute: () => rootRouteImport, } as any) const LinksRoute = LinksRouteImport.update({ @@ -126,33 +116,35 @@ const LinksRoute = LinksRouteImport.update({ path: '/links', getParentRoute: () => rootRouteImport, } as any) -const InlineScriptsRoute = InlineScriptsRouteImport.update({ - id: '/inline-scripts', - path: '/inline-scripts', - getParentRoute: () => rootRouteImport, -} as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ + id: '/not-found', + path: '/not-found', getParentRoute: () => rootRouteImport, } as any) -const ClientOnlyRoute = ClientOnlyRouteImport.update({ - id: '/client-only', - path: '/client-only', +const PlainTsTypeAssertionRoute = PlainTsTypeAssertionRouteImport.update({ + id: '/plain-ts-type-assertion', + path: '/plain-ts-type-assertion', getParentRoute: () => rootRouteImport, } as any) -const AsyncScriptsRoute = AsyncScriptsRouteImport.update({ - id: '/async-scripts', - path: '/async-scripts', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const PrimitiveBeforeloadErrorRoute = + PrimitiveBeforeloadErrorRouteImport.update({ + id: '/primitive-beforeload-error', + path: '/primitive-beforeload-error', + getParentRoute: () => rootRouteImport, + } as any) +const RawStreamRoute = RawStreamRouteImport.update({ + id: '/raw-stream', + path: '/raw-stream', getParentRoute: () => rootRouteImport, } as any) -const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ - id: '/specialChars', - path: '/specialChars', +const ScriptsRoute = ScriptsRouteImport.update({ + id: '/scripts', + path: '/scripts', getParentRoute: () => rootRouteImport, } as any) const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ @@ -160,45 +152,39 @@ const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ path: '/search-params', getParentRoute: () => rootRouteImport, } as any) -const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ - id: '/not-found', - path: '/not-found', +const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ + id: '/specialChars', + path: '/specialChars', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const StreamRoute = StreamRouteImport.update({ + id: '/stream', + path: '/stream', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, -} as any) -const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SearchParamsRouteRoute, +const TypeOnlyReexportRoute = TypeOnlyReexportRouteImport.update({ + id: '/type-only-reexport', + path: '/type-only-reexport', + getParentRoute: () => rootRouteImport, } as any) -const RedirectIndexRoute = RedirectIndexRouteImport.update({ - id: '/redirect/', - path: '/redirect/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => RawStreamRoute, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) -const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NotFoundRouteRoute, +const Issue6221DashboardRoute = Issue6221DashboardRouteImport.update({ + id: '/issue-6221/dashboard', + path: '/issue-6221/dashboard', + getParentRoute: () => rootRouteImport, } as any) const MultiCookieRedirectIndexRoute = MultiCookieRedirectIndexRouteImport.update({ @@ -206,61 +192,67 @@ const MultiCookieRedirectIndexRoute = path: '/multi-cookie-redirect/', getParentRoute: () => rootRouteImport, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) -const SpecialCharsChar45824Char54620Char48124Char44397Route = - SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => SpecialCharsRouteRoute, +const MultiCookieRedirectTargetRoute = + MultiCookieRedirectTargetRouteImport.update({ + id: '/multi-cookie-redirect/target', + path: '/multi-cookie-redirect/target', + getParentRoute: () => rootRouteImport, } as any) -const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsRouteRoute, +const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NotFoundRouteRoute, } as any) -const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ - id: '/hash', - path: '/hash', - getParentRoute: () => SpecialCharsRouteRoute, +const NotFoundDeepRouteRoute = NotFoundDeepRouteRouteImport.update({ + id: '/deep', + path: '/deep', + getParentRoute: () => NotFoundRouteRoute, } as any) -const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsRouteRoute, +const NotFoundParentBoundaryRouteRoute = + NotFoundParentBoundaryRouteRouteImport.update({ + id: '/parent-boundary', + path: '/parent-boundary', + getParentRoute: () => NotFoundRouteRoute, + } as any) +const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ + id: '/via-beforeLoad', + path: '/via-beforeLoad', + getParentRoute: () => NotFoundRouteRoute, } as any) -const SearchParamsLoaderThrowsRedirectRoute = - SearchParamsLoaderThrowsRedirectRouteImport.update({ - id: '/loader-throws-redirect', - path: '/loader-throws-redirect', - getParentRoute: () => SearchParamsRouteRoute, +const NotFoundViaBeforeLoadTargetRootRoute = + NotFoundViaBeforeLoadTargetRootRouteImport.update({ + id: '/via-beforeLoad-target-root', + path: '/via-beforeLoad-target-root', + getParentRoute: () => NotFoundRouteRoute, } as any) -const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ - id: '/default', - path: '/default', - getParentRoute: () => SearchParamsRouteRoute, +const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => NotFoundRouteRoute, } as any) -const RedirectTargetRoute = RedirectTargetRouteImport.update({ - id: '/redirect/$target', - path: '/redirect/$target', - getParentRoute: () => rootRouteImport, +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, } as any) -const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ - id: '/ssr-text-hint', - path: '/ssr-text-hint', +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ - id: '/ssr-single', - path: '/ssr-single', +const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ + id: '/client-call', + path: '/client-call', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ - id: '/ssr-multiple', - path: '/ssr-multiple', +const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ + id: '/ssr-binary-hint', + path: '/ssr-binary-hint', getParentRoute: () => RawStreamRoute, } as any) const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ @@ -268,51 +260,56 @@ const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ path: '/ssr-mixed', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ - id: '/ssr-binary-hint', - path: '/ssr-binary-hint', +const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ + id: '/ssr-multiple', + path: '/ssr-multiple', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ - id: '/client-call', - path: '/client-call', +const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ + id: '/ssr-single', + path: '/ssr-single', getParentRoute: () => RawStreamRoute, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, +const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ + id: '/ssr-text-hint', + path: '/ssr-text-hint', + getParentRoute: () => RawStreamRoute, } as any) -const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => NotFoundRouteRoute, +const RedirectIndexRoute = RedirectIndexRouteImport.update({ + id: '/redirect/', + path: '/redirect/', + getParentRoute: () => rootRouteImport, } as any) -const NotFoundViaBeforeLoadTargetRootRoute = - NotFoundViaBeforeLoadTargetRootRouteImport.update({ - id: '/via-beforeLoad-target-root', - path: '/via-beforeLoad-target-root', - getParentRoute: () => NotFoundRouteRoute, - } as any) -const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ - id: '/via-beforeLoad', - path: '/via-beforeLoad', - getParentRoute: () => NotFoundRouteRoute, +const RedirectTargetRoute = RedirectTargetRouteImport.update({ + id: '/redirect/$target', + path: '/redirect/$target', + getParentRoute: () => rootRouteImport, } as any) -const MultiCookieRedirectTargetRoute = - MultiCookieRedirectTargetRouteImport.update({ - id: '/multi-cookie-redirect/target', - path: '/multi-cookie-redirect/target', - getParentRoute: () => rootRouteImport, +const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SearchParamsRouteRoute, +} as any) +const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ + id: '/default', + path: '/default', + getParentRoute: () => SearchParamsRouteRoute, +} as any) +const SearchParamsLoaderThrowsRedirectRoute = + SearchParamsLoaderThrowsRedirectRouteImport.update({ + id: '/loader-throws-redirect', + path: '/loader-throws-redirect', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ + id: '/hash', + path: '/hash', + getParentRoute: () => SpecialCharsRouteRoute, } as any) const SpecialCharsMalformedRouteRoute = SpecialCharsMalformedRouteRouteImport.update({ @@ -320,21 +317,56 @@ const SpecialCharsMalformedRouteRoute = path: '/malformed', getParentRoute: () => SpecialCharsRouteRoute, } as any) -const NotFoundParentBoundaryRouteRoute = - NotFoundParentBoundaryRouteRouteImport.update({ - id: '/parent-boundary', - path: '/parent-boundary', - getParentRoute: () => NotFoundRouteRoute, +const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsRouteRoute, +} as any) +const SpecialCharsChar45824Char54620Char48124Char44397Route = + SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const NotFoundDeepRouteRoute = NotFoundDeepRouteRouteImport.update({ - id: '/deep', - path: '/deep', - getParentRoute: () => NotFoundRouteRoute, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, +} as any) +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) +const Issue6221ArticleIdRoute = Issue6221ArticleIdRouteImport.update({ + id: '/issue-6221/article/$id', + path: '/issue-6221/article/$id', + getParentRoute: () => rootRouteImport, +} as any) +const NotFoundDeepIndexRoute = NotFoundDeepIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => NotFoundDeepRouteRoute, +} as any) +const NotFoundDeepBRouteRoute = NotFoundDeepBRouteRouteImport.update({ + id: '/b', + path: '/b', + getParentRoute: () => NotFoundDeepRouteRoute, } as any) const NotFoundParentBoundaryIndexRoute = NotFoundParentBoundaryIndexRouteImport.update({ @@ -342,26 +374,20 @@ const NotFoundParentBoundaryIndexRoute = path: '/', getParentRoute: () => NotFoundParentBoundaryRouteRoute, } as any) -const NotFoundDeepIndexRoute = NotFoundDeepIndexRouteImport.update({ +const NotFoundParentBoundaryViaBeforeLoadRoute = + NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ + id: '/via-beforeLoad', + path: '/via-beforeLoad', + getParentRoute: () => NotFoundParentBoundaryRouteRoute, + } as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NotFoundDeepRouteRoute, -} as any) -const SpecialCharsMalformedSearchRoute = - SpecialCharsMalformedSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const SpecialCharsMalformedParamRoute = - SpecialCharsMalformedParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', getParentRoute: () => RedirectTargetRoute, } as any) const RedirectTargetViaBeforeLoadRoute = @@ -370,36 +396,32 @@ const RedirectTargetViaBeforeLoadRoute = path: '/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => RedirectTargetRoute, } as any) -const NotFoundParentBoundaryViaBeforeLoadRoute = - NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ - id: '/via-beforeLoad', - path: '/via-beforeLoad', - getParentRoute: () => NotFoundParentBoundaryRouteRoute, +const SpecialCharsMalformedParamRoute = + SpecialCharsMalformedParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsMalformedRouteRoute, } as any) -const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => ApiUsersRoute, -} as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, -} as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const SpecialCharsMalformedSearchRoute = + SpecialCharsMalformedSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsMalformedRouteRoute, + } as any) +const FooBarQuxHereRoute = FooBarQuxHereRouteImport.update({ + id: '/foo/$bar/$qux/_here', + path: '/foo/$bar/$qux', + getParentRoute: () => rootRouteImport, } as any) -const NotFoundDeepBRouteRoute = NotFoundDeepBRouteRouteImport.update({ - id: '/b', - path: '/b', - getParentRoute: () => NotFoundDeepRouteRoute, +const NotFoundDeepBCRouteRoute = NotFoundDeepBCRouteRouteImport.update({ + id: '/c', + path: '/c', + getParentRoute: () => NotFoundDeepBRouteRoute, } as any) const RedirectTargetServerFnIndexRoute = RedirectTargetServerFnIndexRouteImport.update({ @@ -407,10 +429,10 @@ const RedirectTargetServerFnIndexRoute = path: '/serverFn/', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaUseServerFnRoute = - RedirectTargetServerFnViaUseServerFnRouteImport.update({ - id: '/serverFn/via-useServerFn', - path: '/serverFn/via-useServerFn', +const RedirectTargetServerFnViaBeforeLoadRoute = + RedirectTargetServerFnViaBeforeLoadRouteImport.update({ + id: '/serverFn/via-beforeLoad', + path: '/serverFn/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) const RedirectTargetServerFnViaLoaderRoute = @@ -419,22 +441,12 @@ const RedirectTargetServerFnViaLoaderRoute = path: '/serverFn/via-loader', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaBeforeLoadRoute = - RedirectTargetServerFnViaBeforeLoadRouteImport.update({ - id: '/serverFn/via-beforeLoad', - path: '/serverFn/via-beforeLoad', +const RedirectTargetServerFnViaUseServerFnRoute = + RedirectTargetServerFnViaUseServerFnRouteImport.update({ + id: '/serverFn/via-useServerFn', + path: '/serverFn/via-useServerFn', getParentRoute: () => RedirectTargetRoute, } as any) -const FooBarQuxHereRoute = FooBarQuxHereRouteImport.update({ - id: '/foo/$bar/$qux/_here', - path: '/foo/$bar/$qux', - getParentRoute: () => rootRouteImport, -} as any) -const NotFoundDeepBCRouteRoute = NotFoundDeepBCRouteRouteImport.update({ - id: '/c', - path: '/c', - getParentRoute: () => NotFoundDeepBRouteRoute, -} as any) const FooBarQuxHereIndexRoute = FooBarQuxHereIndexRouteImport.update({ id: '/', path: '/', @@ -468,6 +480,7 @@ export interface FileRoutesByFullPath { '/not-found/parent-boundary': typeof NotFoundParentBoundaryRouteRouteWithChildren '/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren '/api/users': typeof ApiUsersRouteWithChildren + '/issue-6221/dashboard': typeof Issue6221DashboardRoute '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute @@ -498,6 +511,7 @@ export interface FileRoutesByFullPath { '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute '/api/users/$id': typeof ApiUsersIdRoute + '/issue-6221/article/$id': typeof Issue6221ArticleIdRoute '/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute @@ -531,6 +545,7 @@ export interface FileRoutesByTo { '/type-only-reexport': typeof TypeOnlyReexportRoute '/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren '/api/users': typeof ApiUsersRouteWithChildren + '/issue-6221/dashboard': typeof Issue6221DashboardRoute '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute @@ -560,6 +575,7 @@ export interface FileRoutesByTo { '/layout-a': typeof LayoutLayout2LayoutARoute '/layout-b': typeof LayoutLayout2LayoutBRoute '/api/users/$id': typeof ApiUsersIdRoute + '/issue-6221/article/$id': typeof Issue6221ArticleIdRoute '/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute '/posts/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute @@ -602,6 +618,7 @@ export interface FileRoutesById { '/specialChars/malformed': typeof SpecialCharsMalformedRouteRouteWithChildren '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren '/api/users': typeof ApiUsersRouteWithChildren + '/issue-6221/dashboard': typeof Issue6221DashboardRoute '/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute '/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute '/not-found/via-beforeLoad-target-root': typeof NotFoundViaBeforeLoadTargetRootRoute @@ -632,6 +649,7 @@ export interface FileRoutesById { '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute '/api/users/$id': typeof ApiUsersIdRoute + '/issue-6221/article/$id': typeof Issue6221ArticleIdRoute '/not-found/parent-boundary/via-beforeLoad': typeof NotFoundParentBoundaryViaBeforeLoadRoute '/posts_/$postId/deep': typeof PostsPostIdDeepRoute '/redirect/$target/via-beforeLoad': typeof RedirectTargetViaBeforeLoadRoute @@ -674,6 +692,7 @@ export interface FileRouteTypes { | '/not-found/parent-boundary' | '/specialChars/malformed' | '/api/users' + | '/issue-6221/dashboard' | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-beforeLoad-target-root' @@ -704,6 +723,7 @@ export interface FileRouteTypes { | '/layout-a' | '/layout-b' | '/api/users/$id' + | '/issue-6221/article/$id' | '/not-found/parent-boundary/via-beforeLoad' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' @@ -737,6 +757,7 @@ export interface FileRouteTypes { | '/type-only-reexport' | '/specialChars/malformed' | '/api/users' + | '/issue-6221/dashboard' | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-beforeLoad-target-root' @@ -766,6 +787,7 @@ export interface FileRouteTypes { | '/layout-a' | '/layout-b' | '/api/users/$id' + | '/issue-6221/article/$id' | '/not-found/parent-boundary/via-beforeLoad' | '/posts/$postId/deep' | '/redirect/$target/via-beforeLoad' @@ -807,6 +829,7 @@ export interface FileRouteTypes { | '/specialChars/malformed' | '/_layout/_layout-2' | '/api/users' + | '/issue-6221/dashboard' | '/multi-cookie-redirect/target' | '/not-found/via-beforeLoad' | '/not-found/via-beforeLoad-target-root' @@ -837,6 +860,7 @@ export interface FileRouteTypes { | '/_layout/_layout-2/layout-a' | '/_layout/_layout-2/layout-b' | '/api/users/$id' + | '/issue-6221/article/$id' | '/not-found/parent-boundary/via-beforeLoad' | '/posts_/$postId/deep' | '/redirect/$target/via-beforeLoad' @@ -876,63 +900,72 @@ export interface RootRouteChildren { TypeOnlyReexportRoute: typeof TypeOnlyReexportRoute UsersRoute: typeof UsersRouteWithChildren ApiUsersRoute: typeof ApiUsersRouteWithChildren + Issue6221DashboardRoute: typeof Issue6221DashboardRoute MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute RedirectTargetRoute: typeof RedirectTargetRouteWithChildren MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute RedirectIndexRoute: typeof RedirectIndexRoute + Issue6221ArticleIdRoute: typeof Issue6221ArticleIdRoute PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute FooBarQuxHereRoute: typeof FooBarQuxHereRouteWithChildren } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/type-only-reexport': { - id: '/type-only-reexport' - path: '/type-only-reexport' - fullPath: '/type-only-reexport' - preLoaderRoute: typeof TypeOnlyReexportRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/stream': { - id: '/stream' - path: '/stream' - fullPath: '/stream' - preLoaderRoute: typeof StreamRouteImport + '/async-scripts': { + id: '/async-scripts' + path: '/async-scripts' + fullPath: '/async-scripts' + preLoaderRoute: typeof AsyncScriptsRouteImport parentRoute: typeof rootRouteImport } - '/scripts': { - id: '/scripts' - path: '/scripts' - fullPath: '/scripts' - preLoaderRoute: typeof ScriptsRouteImport + '/client-only': { + id: '/client-only' + path: '/client-only' + fullPath: '/client-only' + preLoaderRoute: typeof ClientOnlyRouteImport parentRoute: typeof rootRouteImport } - '/raw-stream': { - id: '/raw-stream' - path: '/raw-stream' - fullPath: '/raw-stream' - preLoaderRoute: typeof RawStreamRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/primitive-beforeload-error': { - id: '/primitive-beforeload-error' - path: '/primitive-beforeload-error' - fullPath: '/primitive-beforeload-error' - preLoaderRoute: typeof PrimitiveBeforeloadErrorRouteImport + '/inline-scripts': { + id: '/inline-scripts' + path: '/inline-scripts' + fullPath: '/inline-scripts' + preLoaderRoute: typeof InlineScriptsRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/links': { + id: '/links' + path: '/links' + fullPath: '/links' + preLoaderRoute: typeof LinksRouteImport + parentRoute: typeof rootRouteImport + } + '/not-found': { + id: '/not-found' + path: '/not-found' + fullPath: '/not-found' + preLoaderRoute: typeof NotFoundRouteRouteImport parentRoute: typeof rootRouteImport } '/plain-ts-type-assertion': { @@ -942,46 +975,39 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PlainTsTypeAssertionRouteImport parentRoute: typeof rootRouteImport } - '/links': { - id: '/links' - path: '/links' - fullPath: '/links' - preLoaderRoute: typeof LinksRouteImport - parentRoute: typeof rootRouteImport - } - '/inline-scripts': { - id: '/inline-scripts' - path: '/inline-scripts' - fullPath: '/inline-scripts' - preLoaderRoute: typeof InlineScriptsRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/primitive-beforeload-error': { + id: '/primitive-beforeload-error' + path: '/primitive-beforeload-error' + fullPath: '/primitive-beforeload-error' + preLoaderRoute: typeof PrimitiveBeforeloadErrorRouteImport parentRoute: typeof rootRouteImport } - '/client-only': { - id: '/client-only' - path: '/client-only' - fullPath: '/client-only' - preLoaderRoute: typeof ClientOnlyRouteImport + '/raw-stream': { + id: '/raw-stream' + path: '/raw-stream' + fullPath: '/raw-stream' + preLoaderRoute: typeof RawStreamRouteImport parentRoute: typeof rootRouteImport } - '/async-scripts': { - id: '/async-scripts' - path: '/async-scripts' - fullPath: '/async-scripts' - preLoaderRoute: typeof AsyncScriptsRouteImport + '/scripts': { + id: '/scripts' + path: '/scripts' + fullPath: '/scripts' + preLoaderRoute: typeof ScriptsRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/search-params': { + id: '/search-params' + path: '/search-params' + fullPath: '/search-params' + preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } '/specialChars': { @@ -991,61 +1017,61 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SpecialCharsRouteRouteImport 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 } - '/not-found': { - id: '/not-found' - path: '/not-found' - fullPath: '/not-found' - preLoaderRoute: typeof NotFoundRouteRouteImport + '/type-only-reexport': { + id: '/type-only-reexport' + path: '/type-only-reexport' + fullPath: '/type-only-reexport' + preLoaderRoute: typeof TypeOnlyReexportRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/search-params/': { - id: '/search-params/' - path: '/' - fullPath: '/search-params/' - preLoaderRoute: typeof SearchParamsIndexRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } - '/redirect/': { - id: '/redirect/' - path: '/redirect' - fullPath: '/redirect/' - preLoaderRoute: typeof RedirectIndexRouteImport + '/issue-6221/dashboard': { + id: '/issue-6221/dashboard' + path: '/issue-6221/dashboard' + fullPath: '/issue-6221/dashboard' + preLoaderRoute: typeof Issue6221DashboardRouteImport parentRoute: typeof rootRouteImport } - '/raw-stream/': { - id: '/raw-stream/' - path: '/' - fullPath: '/raw-stream/' - preLoaderRoute: typeof RawStreamIndexRouteImport - parentRoute: typeof RawStreamRoute + '/multi-cookie-redirect/': { + id: '/multi-cookie-redirect/' + path: '/multi-cookie-redirect' + fullPath: '/multi-cookie-redirect/' + preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport + parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/multi-cookie-redirect/target': { + id: '/multi-cookie-redirect/target' + path: '/multi-cookie-redirect/target' + fullPath: '/multi-cookie-redirect/target' + preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport + parentRoute: typeof rootRouteImport } '/not-found/': { id: '/not-found/' @@ -1054,88 +1080,74 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NotFoundIndexRouteImport parentRoute: typeof NotFoundRouteRoute } - '/multi-cookie-redirect/': { - id: '/multi-cookie-redirect/' - path: '/multi-cookie-redirect' - fullPath: '/multi-cookie-redirect/' - preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } - '/specialChars/대한민국': { - id: '/specialChars/대한민국' - path: '/대한민국' - fullPath: '/specialChars/대한민국' - preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/deep': { + id: '/not-found/deep' + path: '/deep' + fullPath: '/not-found/deep' + preLoaderRoute: typeof NotFoundDeepRouteRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/search': { - id: '/specialChars/search' - path: '/search' - fullPath: '/specialChars/search' - preLoaderRoute: typeof SpecialCharsSearchRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/parent-boundary': { + id: '/not-found/parent-boundary' + path: '/parent-boundary' + fullPath: '/not-found/parent-boundary' + preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/hash': { - id: '/specialChars/hash' - path: '/hash' - fullPath: '/specialChars/hash' - preLoaderRoute: typeof SpecialCharsHashRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-beforeLoad': { + id: '/not-found/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/not-found/via-beforeLoad' + preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/$param': { - id: '/specialChars/$param' - path: '/$param' - fullPath: '/specialChars/$param' - preLoaderRoute: typeof SpecialCharsParamRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-beforeLoad-target-root': { + id: '/not-found/via-beforeLoad-target-root' + path: '/via-beforeLoad-target-root' + fullPath: '/not-found/via-beforeLoad-target-root' + preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/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 + '/not-found/via-loader': { + id: '/not-found/via-loader' + path: '/via-loader' + fullPath: '/not-found/via-loader' + preLoaderRoute: typeof NotFoundViaLoaderRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/search-params/default': { - id: '/search-params/default' - path: '/default' - fullPath: '/search-params/default' - preLoaderRoute: typeof SearchParamsDefaultRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute } - '/raw-stream/ssr-text-hint': { - id: '/raw-stream/ssr-text-hint' - path: '/ssr-text-hint' - fullPath: '/raw-stream/ssr-text-hint' - preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + '/raw-stream/': { + id: '/raw-stream/' + path: '/' + fullPath: '/raw-stream/' + preLoaderRoute: typeof RawStreamIndexRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-single': { - id: '/raw-stream/ssr-single' - path: '/ssr-single' - fullPath: '/raw-stream/ssr-single' - preLoaderRoute: typeof RawStreamSsrSingleRouteImport + '/raw-stream/client-call': { + id: '/raw-stream/client-call' + path: '/client-call' + fullPath: '/raw-stream/client-call' + preLoaderRoute: typeof RawStreamClientCallRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-multiple': { - id: '/raw-stream/ssr-multiple' - path: '/ssr-multiple' - fullPath: '/raw-stream/ssr-multiple' - preLoaderRoute: typeof RawStreamSsrMultipleRouteImport + '/raw-stream/ssr-binary-hint': { + id: '/raw-stream/ssr-binary-hint' + path: '/ssr-binary-hint' + fullPath: '/raw-stream/ssr-binary-hint' + preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport parentRoute: typeof RawStreamRoute } '/raw-stream/ssr-mixed': { @@ -1145,68 +1157,75 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RawStreamSsrMixedRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-binary-hint': { - id: '/raw-stream/ssr-binary-hint' - path: '/ssr-binary-hint' - fullPath: '/raw-stream/ssr-binary-hint' - preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport + '/raw-stream/ssr-multiple': { + id: '/raw-stream/ssr-multiple' + path: '/ssr-multiple' + fullPath: '/raw-stream/ssr-multiple' + preLoaderRoute: typeof RawStreamSsrMultipleRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/client-call': { - id: '/raw-stream/client-call' - path: '/client-call' - fullPath: '/raw-stream/client-call' - preLoaderRoute: typeof RawStreamClientCallRouteImport + '/raw-stream/ssr-single': { + id: '/raw-stream/ssr-single' + path: '/ssr-single' + fullPath: '/raw-stream/ssr-single' + preLoaderRoute: typeof RawStreamSsrSingleRouteImport parentRoute: typeof RawStreamRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsPostIdRouteImport - parentRoute: typeof PostsRoute + '/raw-stream/ssr-text-hint': { + id: '/raw-stream/ssr-text-hint' + path: '/ssr-text-hint' + fullPath: '/raw-stream/ssr-text-hint' + preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + parentRoute: typeof RawStreamRoute } - '/not-found/via-loader': { - id: '/not-found/via-loader' - path: '/via-loader' - fullPath: '/not-found/via-loader' - preLoaderRoute: typeof NotFoundViaLoaderRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport + parentRoute: typeof rootRouteImport } - '/not-found/via-beforeLoad-target-root': { - id: '/not-found/via-beforeLoad-target-root' - path: '/via-beforeLoad-target-root' - fullPath: '/not-found/via-beforeLoad-target-root' - preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport + 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 + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute + } + '/search-params/default': { + id: '/search-params/default' + path: '/default' + fullPath: '/search-params/default' + preLoaderRoute: typeof SearchParamsDefaultRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/multi-cookie-redirect/target': { - id: '/multi-cookie-redirect/target' - path: '/multi-cookie-redirect/target' - fullPath: '/multi-cookie-redirect/target' - preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport - 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 } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersRouteImport - parentRoute: typeof rootRouteImport + '/specialChars/$param': { + id: '/specialChars/$param' + path: '/$param' + fullPath: '/specialChars/$param' + preLoaderRoute: typeof SpecialCharsParamRouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/specialChars/hash': { + id: '/specialChars/hash' + path: '/hash' + fullPath: '/specialChars/hash' + preLoaderRoute: typeof SpecialCharsHashRouteImport + parentRoute: typeof SpecialCharsRouteRoute } '/specialChars/malformed': { id: '/specialChars/malformed' @@ -1215,33 +1234,61 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SpecialCharsMalformedRouteRouteImport parentRoute: typeof SpecialCharsRouteRoute } - '/not-found/parent-boundary': { - id: '/not-found/parent-boundary' - path: '/parent-boundary' - fullPath: '/not-found/parent-boundary' - preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport - parentRoute: typeof NotFoundRouteRoute + '/specialChars/search': { + id: '/specialChars/search' + path: '/search' + fullPath: '/specialChars/search' + preLoaderRoute: typeof SpecialCharsSearchRouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/not-found/deep': { - id: '/not-found/deep' - path: '/deep' - fullPath: '/not-found/deep' - preLoaderRoute: typeof NotFoundDeepRouteRouteImport - parentRoute: typeof NotFoundRouteRoute + '/specialChars/대한민국': { + id: '/specialChars/대한민국' + path: '/대한민국' + fullPath: '/specialChars/대한민국' + preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/redirect/$target/': { - id: '/redirect/$target/' + '/users/': { + id: '/users/' path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport - parentRoute: typeof RedirectTargetRoute + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/not-found/parent-boundary/': { - id: '/not-found/parent-boundary/' - path: '/' - fullPath: '/not-found/parent-boundary/' - preLoaderRoute: typeof NotFoundParentBoundaryIndexRouteImport - parentRoute: typeof NotFoundParentBoundaryRouteRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + 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' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/issue-6221/article/$id': { + id: '/issue-6221/article/$id' + path: '/issue-6221/article/$id' + fullPath: '/issue-6221/article/$id' + preLoaderRoute: typeof Issue6221ArticleIdRouteImport + parentRoute: typeof rootRouteImport } '/not-found/deep/': { id: '/not-found/deep/' @@ -1250,33 +1297,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NotFoundDeepIndexRouteImport parentRoute: typeof NotFoundDeepRouteRoute } - '/specialChars/malformed/search': { - id: '/specialChars/malformed/search' - path: '/search' - fullPath: '/specialChars/malformed/search' - preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute - } - '/specialChars/malformed/$param': { - id: '/specialChars/malformed/$param' - path: '/$param' - fullPath: '/specialChars/malformed/$param' - preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute + '/not-found/deep/b': { + id: '/not-found/deep/b' + path: '/b' + fullPath: '/not-found/deep/b' + preLoaderRoute: typeof NotFoundDeepBRouteRouteImport + parentRoute: typeof NotFoundDeepRouteRoute } - '/redirect/$target/via-loader': { - id: '/redirect/$target/via-loader' - path: '/via-loader' - fullPath: '/redirect/$target/via-loader' - preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport - parentRoute: typeof RedirectTargetRoute + '/not-found/parent-boundary/': { + id: '/not-found/parent-boundary/' + path: '/' + fullPath: '/not-found/parent-boundary/' + preLoaderRoute: typeof NotFoundParentBoundaryIndexRouteImport + parentRoute: typeof NotFoundParentBoundaryRouteRoute } - '/redirect/$target/via-beforeLoad': { - id: '/redirect/$target/via-beforeLoad' + '/not-found/parent-boundary/via-beforeLoad': { + id: '/not-found/parent-boundary/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/redirect/$target/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + fullPath: '/not-found/parent-boundary/via-beforeLoad' + preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport + parentRoute: typeof NotFoundParentBoundaryRouteRoute } '/posts_/$postId/deep': { id: '/posts_/$postId/deep' @@ -1285,40 +1325,54 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } - '/not-found/parent-boundary/via-beforeLoad': { - id: '/not-found/parent-boundary/via-beforeLoad' + '/redirect/$target/': { + id: '/redirect/$target/' + path: '/' + fullPath: '/redirect/$target/' + preLoaderRoute: typeof RedirectTargetIndexRouteImport + parentRoute: typeof RedirectTargetRoute + } + '/redirect/$target/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/not-found/parent-boundary/via-beforeLoad' - preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport - parentRoute: typeof NotFoundParentBoundaryRouteRoute + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/api/users/$id': { - id: '/api/users/$id' - path: '/$id' - fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdRouteImport - parentRoute: typeof ApiUsersRoute + '/redirect/$target/via-loader': { + id: '/redirect/$target/via-loader' + path: '/via-loader' + fullPath: '/redirect/$target/via-loader' + preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport + 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 + '/specialChars/malformed/$param': { + id: '/specialChars/malformed/$param' + path: '/$param' + fullPath: '/specialChars/malformed/$param' + preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/specialChars/malformed/search': { + id: '/specialChars/malformed/search' + path: '/search' + fullPath: '/specialChars/malformed/search' + preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute } - '/not-found/deep/b': { - id: '/not-found/deep/b' - path: '/b' - fullPath: '/not-found/deep/b' - preLoaderRoute: typeof NotFoundDeepBRouteRouteImport - parentRoute: typeof NotFoundDeepRouteRoute + '/foo/$bar/$qux/_here': { + id: '/foo/$bar/$qux/_here' + path: '/foo/$bar/$qux' + fullPath: '/foo/$bar/$qux' + preLoaderRoute: typeof FooBarQuxHereRouteImport + parentRoute: typeof rootRouteImport + } + '/not-found/deep/b/c': { + id: '/not-found/deep/b/c' + path: '/c' + fullPath: '/not-found/deep/b/c' + preLoaderRoute: typeof NotFoundDeepBCRouteRouteImport + parentRoute: typeof NotFoundDeepBRouteRoute } '/redirect/$target/serverFn/': { id: '/redirect/$target/serverFn/' @@ -1327,11 +1381,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RedirectTargetServerFnIndexRouteImport parentRoute: typeof RedirectTargetRoute } - '/redirect/$target/serverFn/via-useServerFn': { - id: '/redirect/$target/serverFn/via-useServerFn' - path: '/serverFn/via-useServerFn' - fullPath: '/redirect/$target/serverFn/via-useServerFn' - preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport + '/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 } '/redirect/$target/serverFn/via-loader': { @@ -1341,27 +1395,13 @@ declare module '@tanstack/react-router' { 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 + '/redirect/$target/serverFn/via-useServerFn': { + id: '/redirect/$target/serverFn/via-useServerFn' + path: '/serverFn/via-useServerFn' + fullPath: '/redirect/$target/serverFn/via-useServerFn' + preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport 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 rootRouteImport - } - '/not-found/deep/b/c': { - id: '/not-found/deep/b/c' - path: '/c' - fullPath: '/not-found/deep/b/c' - preLoaderRoute: typeof NotFoundDeepBCRouteRouteImport - parentRoute: typeof NotFoundDeepBRouteRoute - } '/foo/$bar/$qux/_here/': { id: '/foo/$bar/$qux/_here/' path: '/' @@ -1648,10 +1688,12 @@ const rootRouteChildren: RootRouteChildren = { TypeOnlyReexportRoute: TypeOnlyReexportRoute, UsersRoute: UsersRouteWithChildren, ApiUsersRoute: ApiUsersRouteWithChildren, + Issue6221DashboardRoute: Issue6221DashboardRoute, MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute, RedirectTargetRoute: RedirectTargetRouteWithChildren, MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute, RedirectIndexRoute: RedirectIndexRoute, + Issue6221ArticleIdRoute: Issue6221ArticleIdRoute, PostsPostIdDeepRoute: PostsPostIdDeepRoute, FooBarQuxHereRoute: FooBarQuxHereRouteWithChildren, } diff --git a/e2e/react-start/basic/src/routes/issue-6221.article.$id.tsx b/e2e/react-start/basic/src/routes/issue-6221.article.$id.tsx new file mode 100644 index 0000000000..c923a5f1cb --- /dev/null +++ b/e2e/react-start/basic/src/routes/issue-6221.article.$id.tsx @@ -0,0 +1,42 @@ +import { Link, createFileRoute } from '@tanstack/react-router' +import { createClientOnlyFn } from '@tanstack/react-start' + +const isAuthed = createClientOnlyFn( + () => localStorage.getItem('issue-6221-auth') === 'good', +) + +const fetchArticle = async (id: string) => { + await new Promise((resolve) => setTimeout(resolve, 1000)) + return isAuthed() + ? { + title: `Article Title for ${id}`, + content: `Article content for ${id}`, + } + : null +} + +export const Route = createFileRoute('/issue-6221/article/$id')({ + ssr: false, + loader: ({ params }) => fetchArticle(params.id), + head: ({ loaderData }) => ({ + meta: [{ title: loaderData?.title ?? 'title n/a' }], + }), + component: Article, +}) + +function Article() { + const article = Route.useLoaderData() + + return ( +
+ + Dashboard + + {article ? ( +
{article.content}
+ ) : ( +
Article Not Accessible.
+ )} +
+ ) +} diff --git a/e2e/react-start/basic/src/routes/issue-6221.dashboard.tsx b/e2e/react-start/basic/src/routes/issue-6221.dashboard.tsx new file mode 100644 index 0000000000..8c93da669c --- /dev/null +++ b/e2e/react-start/basic/src/routes/issue-6221.dashboard.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/issue-6221/dashboard')({ + head: () => ({ meta: [{ title: 'Dashboard' }] }), + component: () =>
Dashboard
, +}) diff --git a/e2e/react-start/basic/src/routes/posts.tsx b/e2e/react-start/basic/src/routes/posts.tsx index 0f69c18341..22fb7e60a8 100644 --- a/e2e/react-start/basic/src/routes/posts.tsx +++ b/e2e/react-start/basic/src/routes/posts.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { Link, Outlet, createFileRoute } from '@tanstack/react-router' import { fetchPosts } from '~/utils/posts' @@ -16,9 +17,16 @@ export const Route = createFileRoute('/posts')({ function PostsComponent() { const posts = Route.useLoaderData() + const [hydrationCount, setHydrationCount] = useState(0) return (
+
    {[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map( (post) => { diff --git a/e2e/react-start/basic/src/routes/specialChars/search.tsx b/e2e/react-start/basic/src/routes/specialChars/search.tsx index 9e4b42e888..5840d425ab 100644 --- a/e2e/react-start/basic/src/routes/specialChars/search.tsx +++ b/e2e/react-start/basic/src/routes/specialChars/search.tsx @@ -7,6 +7,9 @@ export const Route = createFileRoute('/specialChars/search')({ validateSearch: z.object({ searchParam: z.string(), }), + beforeLoad: () => ({ + beforeLoadOn: isBrowser ? 'client' : 'server', + }), loader: async () => { console.log(`[loader] Running on ${isBrowser ? 'client' : 'server'}`) return { @@ -18,11 +21,15 @@ export const Route = createFileRoute('/specialChars/search')({ function RouteComponent() { const search = Route.useSearch() + const { beforeLoadOn } = Route.useRouteContext() const { loadedOn } = Route.useLoaderData() return (
    Hello "/specialChars/search"! +
    + Before load on: {beforeLoadOn} +
    Loaded on: {loadedOn}
    {search.searchParam}
    diff --git a/e2e/react-start/basic/tests/issue-6221-head.spec.ts b/e2e/react-start/basic/tests/issue-6221-head.spec.ts new file mode 100644 index 0000000000..96274ce0f9 --- /dev/null +++ b/e2e/react-start/basic/tests/issue-6221-head.spec.ts @@ -0,0 +1,41 @@ +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' +import { isPreview } from './utils/isPreview' +import { isSpaMode } from './utils/isSpaMode' + +// Ported to React from the regression app in +// https://github.com/TanStack/router/pull/6222 +test.skip( + isSpaMode || isPreview, + 'head() coverage for an ssr:false route requires the SSR test mode', +) + +test('browser back uses fresh loaderData for both the body and head (#6221)', async ({ + page, +}) => { + await page.goto('/') + await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good')) + + await page.goto('/issue-6221/article/1') + + await expect(page.getByTestId('issue-6221-article')).toHaveText( + 'Article content for 1', + ) + await expect(page).toHaveTitle('Article Title for 1') + + await page.evaluate(() => localStorage.removeItem('issue-6221-auth')) + await page.reload() + await expect(page.getByText('Article Not Accessible.')).toBeVisible() + await expect(page).toHaveTitle('title n/a') + + await page.evaluate(() => localStorage.setItem('issue-6221-auth', 'good')) + await page.getByTestId('issue-6221-dashboard-link').click() + await expect(page.getByTestId('issue-6221-dashboard')).toBeVisible() + await expect(page).toHaveTitle('Dashboard') + + await page.goBack() + await expect(page.getByTestId('issue-6221-article')).toHaveText( + 'Article content for 1', + ) + await expect(page).toHaveTitle('Article Title for 1') +}) diff --git a/e2e/react-start/basic/tests/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts index d6e91696e3..339f841b21 100644 --- a/e2e/react-start/basic/tests/not-found.spec.ts +++ b/e2e/react-start/basic/tests/not-found.spec.ts @@ -12,6 +12,24 @@ test.use({ ], }) test.describe('not-found', () => { + test('direct nested not-found hydrates its already-rendered parent route', async ({ + page, + }) => { + test.skip(isSpaMode, 'issue #5106 requires a server-rendered direct visit') + + const response = await page.goto('/posts/i-do-not-exist') + await page.waitForLoadState('networkidle') + + const serverHtml = await response?.text() + expect(serverHtml).toContain('posts-parent-hydration-counter') + expect(serverHtml).toContain('Post not found') + await expect(page.getByText('Post not found')).toBeInViewport() + const counter = page.getByTestId('posts-parent-hydration-counter') + await expect(counter).toHaveText('Parent hydration count: 0') + await counter.click() + await expect(counter).toHaveText('Parent hydration count: 1') + }) + test(`global not found`, async ({ page }) => { const response = await page.goto(`/this-page-does-not-exist/foo/bar`) diff --git a/e2e/react-start/basic/tests/special-characters.spec.ts b/e2e/react-start/basic/tests/special-characters.spec.ts index 7cf50c39c8..188d1f10a6 100644 --- a/e2e/react-start/basic/tests/special-characters.spec.ts +++ b/e2e/react-start/basic/tests/special-characters.spec.ts @@ -104,11 +104,16 @@ test.describe('Unicode route rendering', () => { const loadedOn = await page .getByTestId('special-search-loaded-info') .textContent() + const beforeLoadOn = await page + .getByTestId('special-search-before-load-info') + .textContent() if (isSpaMode) { expect(loadedOn).toBe('Loaded on: client') + expect(beforeLoadOn).toBe('Before load on: client') } else { expect(loadedOn).toBe('Loaded on: server') + expect(beforeLoadOn).toBe('Before load on: server') } }) diff --git a/e2e/react-start/clerk-basic/package.json b/e2e/react-start/clerk-basic/package.json index 2ff6a85a3a..1fb9714180 100644 --- a/e2e/react-start/clerk-basic/package.json +++ b/e2e/react-start/clerk-basic/package.json @@ -21,7 +21,7 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -29,7 +29,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/clerk-basic/src/routeTree.gen.ts b/e2e/react-start/clerk-basic/src/routeTree.gen.ts index 5b9f75e27a..d8111b637a 100644 --- a/e2e/react-start/clerk-basic/src/routeTree.gen.ts +++ b/e2e/react-start/clerk-basic/src/routeTree.gen.ts @@ -9,22 +9,22 @@ // 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 AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' -import { Route as AuthedProfileSplatRouteImport } from './routes/_authed/profile.$' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' +import { Route as AuthedProfileSplatRouteImport } from './routes/_authed/profile.$' -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', + getParentRoute: () => rootRouteImport, +} as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ id: '/posts', path: '/posts', @@ -35,16 +35,16 @@ const AuthedPostsIndexRoute = AuthedPostsIndexRouteImport.update({ 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) +const AuthedProfileSplatRoute = AuthedProfileSplatRouteImport.update({ + id: '/profile/$', + path: '/profile/$', + getParentRoute: () => AuthedRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -90,13 +90,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -104,6 +97,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport + parentRoute: typeof rootRouteImport + } '/_authed/posts': { id: '/_authed/posts' path: '/posts' @@ -118,13 +118,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedPostsIndexRouteImport parentRoute: typeof AuthedPostsRoute } - '/_authed/profile/$': { - id: '/_authed/profile/$' - path: '/profile/$' - fullPath: '/profile/$' - preLoaderRoute: typeof AuthedProfileSplatRouteImport - parentRoute: typeof AuthedRoute - } '/_authed/posts/$postId': { id: '/_authed/posts/$postId' path: '/$postId' @@ -132,6 +125,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedPostsPostIdRouteImport parentRoute: typeof AuthedPostsRoute } + '/_authed/profile/$': { + id: '/_authed/profile/$' + path: '/profile/$' + fullPath: '/profile/$' + preLoaderRoute: typeof AuthedProfileSplatRouteImport + parentRoute: typeof AuthedRoute + } } } diff --git a/e2e/react-start/csp/package.json b/e2e/react-start/csp/package.json index 4d3c4fd264..bbe4df9bbc 100644 --- a/e2e/react-start/csp/package.json +++ b/e2e/react-start/csp/package.json @@ -17,13 +17,14 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/css-inline/package.json b/e2e/react-start/css-inline/package.json index a7bc5d023f..7d9c0520fc 100644 --- a/e2e/react-start/css-inline/package.json +++ b/e2e/react-start/css-inline/package.json @@ -23,15 +23,16 @@ "srvx": "^0.11.9" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/react-start/css-inline/src/routeTree.gen.ts b/e2e/react-start/css-inline/src/routeTree.gen.ts index f663ab1b4e..1b5e5886b1 100644 --- a/e2e/react-start/css-inline/src/routeTree.gen.ts +++ b/e2e/react-start/css-inline/src/routeTree.gen.ts @@ -9,22 +9,22 @@ // 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 AppRouteRouteImport } from './routes/app/route' import { Route as IndexRouteImport } from './routes/index' +import { Route as AppRouteRouteImport } from './routes/app/route' import { Route as AppDashboardRouteRouteImport } from './routes/app/dashboard/route' import { Route as AppDashboardIndexRouteImport } from './routes/app/dashboard/index' import { Route as AppDashboardDetailsRouteImport } from './routes/app/dashboard/details' -const AppRouteRoute = AppRouteRouteImport.update({ - id: '/app', - path: '/app', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AppRouteRoute = AppRouteRouteImport.update({ + id: '/app', + path: '/app', + getParentRoute: () => rootRouteImport, +} as any) const AppDashboardRouteRoute = AppDashboardRouteRouteImport.update({ id: '/dashboard', path: '/dashboard', @@ -88,13 +88,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/app': { - id: '/app' - path: '/app' - fullPath: '/app' - preLoaderRoute: typeof AppRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -102,6 +95,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/app': { + id: '/app' + path: '/app' + fullPath: '/app' + preLoaderRoute: typeof AppRouteRouteImport + parentRoute: typeof rootRouteImport + } '/app/dashboard': { id: '/app/dashboard' path: '/dashboard' diff --git a/e2e/react-start/css-modules/package.json b/e2e/react-start/css-modules/package.json index 1610f62a61..2591161d66 100644 --- a/e2e/react-start/css-modules/package.json +++ b/e2e/react-start/css-modules/package.json @@ -35,7 +35,7 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.29.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:*", "@types/node": "^22.10.2", "@types/react": "^19.0.8", @@ -44,7 +44,8 @@ "nitro": "^3.0.260311-beta", "sass": "^1.97.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "wrangler": "^4.74.0" } diff --git a/e2e/react-start/css-modules/src/routeTree.gen.ts b/e2e/react-start/css-modules/src/routeTree.gen.ts index d19ae80e5a..822118990d 100644 --- a/e2e/react-start/css-modules/src/routeTree.gen.ts +++ b/e2e/react-start/css-modules/src/routeTree.gen.ts @@ -9,19 +9,14 @@ // 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 SassMixinRouteImport } from './routes/sass-mixin' -import { Route as QuotesRouteImport } from './routes/quotes' -import { Route as ModulesRouteImport } from './routes/modules' import { Route as IndexRouteImport } from './routes/index' +import { Route as ModulesRouteImport } from './routes/modules' +import { Route as QuotesRouteImport } from './routes/quotes' +import { Route as SassMixinRouteImport } from './routes/sass-mixin' -const SassMixinRoute = SassMixinRouteImport.update({ - id: '/sass-mixin', - path: '/sass-mixin', - getParentRoute: () => rootRouteImport, -} as any) -const QuotesRoute = QuotesRouteImport.update({ - id: '/quotes', - path: '/quotes', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ModulesRoute = ModulesRouteImport.update({ @@ -29,9 +24,14 @@ const ModulesRoute = ModulesRouteImport.update({ path: '/modules', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const QuotesRoute = QuotesRouteImport.update({ + id: '/quotes', + path: '/quotes', + getParentRoute: () => rootRouteImport, +} as any) +const SassMixinRoute = SassMixinRouteImport.update({ + id: '/sass-mixin', + path: '/sass-mixin', getParentRoute: () => rootRouteImport, } as any) @@ -71,18 +71,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/sass-mixin': { - id: '/sass-mixin' - path: '/sass-mixin' - fullPath: '/sass-mixin' - preLoaderRoute: typeof SassMixinRouteImport - parentRoute: typeof rootRouteImport - } - '/quotes': { - id: '/quotes' - path: '/quotes' - fullPath: '/quotes' - preLoaderRoute: typeof QuotesRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/modules': { @@ -92,11 +85,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ModulesRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/quotes': { + id: '/quotes' + path: '/quotes' + fullPath: '/quotes' + preLoaderRoute: typeof QuotesRouteImport + parentRoute: typeof rootRouteImport + } + '/sass-mixin': { + id: '/sass-mixin' + path: '/sass-mixin' + fullPath: '/sass-mixin' + preLoaderRoute: typeof SassMixinRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/custom-basepath/package.json b/e2e/react-start/custom-basepath/package.json index f32c9772b9..fe79b158ea 100644 --- a/e2e/react-start/custom-basepath/package.json +++ b/e2e/react-start/custom-basepath/package.json @@ -20,7 +20,7 @@ "redaxios": "^0.5.1" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/express": "^5.0.6", @@ -32,7 +32,8 @@ "srvx": "^0.11.9", "tailwindcss": "^4.2.2", "tsx": "^4.20.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/custom-basepath/src/routeTree.gen.ts b/e2e/react-start/custom-basepath/src/routeTree.gen.ts index 9aacbfbdf0..18cc9b5163 100644 --- a/e2e/react-start/custom-basepath/src/routeTree.gen.ts +++ b/e2e/react-start/custom-basepath/src/routeTree.gen.ts @@ -9,30 +9,35 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NavigateTestRouteImport } from './routes/navigate-test' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as DeferredRouteImport } from './routes/deferred' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as NavigateTestRouteImport } from './routes/navigate-test' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as UsersRouteImport } from './routes/users' +import { Route as ApiUsersRouteImport } from './routes/api.users' import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as RedirectThrowItRouteImport } from './routes/redirect/throw-it' 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 { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectThrowItRouteImport } from './routes/redirect/throw-it' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRouteImport, +} as any) +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) const NavigateTestRoute = NavigateTestRouteImport.update({ @@ -40,66 +45,61 @@ const NavigateTestRoute = NavigateTestRouteImport.update({ path: '/navigate-test', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => UsersRoute, + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any) const RedirectIndexRoute = RedirectIndexRouteImport.update({ id: '/redirect/', path: '/redirect/', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ +const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ + id: '/redirect/throw-it', + path: '/redirect/throw-it', + getParentRoute: () => rootRouteImport, +} as any) +const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => PostsRoute, + getParentRoute: () => UsersRoute, } as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRoute, } as any) -const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ - id: '/redirect/throw-it', - path: '/redirect/throw-it', - getParentRoute: () => rootRouteImport, -} as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, } as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => ApiUsersRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -218,25 +218,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/navigate-test': { - id: '/navigate-test' - path: '/navigate-test' - fullPath: '/navigate-test' - preLoaderRoute: typeof NavigateTestRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/logout': { @@ -246,32 +239,32 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/navigate-test': { + id: '/navigate-test' + path: '/navigate-test' + fullPath: '/navigate-test' + preLoaderRoute: typeof NavigateTestRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRouteImport } - '/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 } '/posts/': { @@ -281,20 +274,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } - '/redirect/throw-it': { - id: '/redirect/throw-it' - path: '/redirect/throw-it' - fullPath: '/redirect/throw-it' - preLoaderRoute: typeof RedirectThrowItRouteImport - parentRoute: typeof rootRouteImport - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -302,20 +281,34 @@ 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 + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport + '/redirect/throw-it': { + id: '/redirect/throw-it' + path: '/redirect/throw-it' + fullPath: '/redirect/throw-it' + preLoaderRoute: typeof RedirectThrowItRouteImport parentRoute: typeof rootRouteImport } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } '/api/users/$id': { id: '/api/users/$id' path: '/$id' @@ -323,6 +316,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiUsersIdRouteImport parentRoute: typeof ApiUsersRoute } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/custom-server-rsbuild/package.json b/e2e/react-start/custom-server-rsbuild/package.json index 75634f79f5..865e424daa 100644 --- a/e2e/react-start/custom-server-rsbuild/package.json +++ b/e2e/react-start/custom-server-rsbuild/package.json @@ -20,8 +20,8 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", @@ -33,6 +33,7 @@ "srvx": "^0.11.9", "tailwindcss": "^4.2.2", "tsx": "^4.20.3", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/e2e/react-start/deferred-hydration/package.json b/e2e/react-start/deferred-hydration/package.json index b225528b49..fb8a99c8e3 100644 --- a/e2e/react-start/deferred-hydration/package.json +++ b/e2e/react-start/deferred-hydration/package.json @@ -26,7 +26,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@rsbuild/core": "^2.0.11", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -34,7 +34,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/react-start/deferred-hydration/src/routeTree.gen.ts b/e2e/react-start/deferred-hydration/src/routeTree.gen.ts index a5b35a698f..451af49ddf 100644 --- a/e2e/react-start/deferred-hydration/src/routeTree.gen.ts +++ b/e2e/react-start/deferred-hydration/src/routeTree.gen.ts @@ -9,26 +9,21 @@ // 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 ScrollRestorationRouteImport } from './routes/scroll-restoration' -import { Route as ImportedRouteImport } from './routes/imported' -import { Route as EnhancedRouteImport } from './routes/enhanced' -import { Route as CssRouteImport } from './routes/css' -import { Route as ComponentsRouteImport } from './routes/components' import { Route as IndexRouteImport } from './routes/index' +import { Route as ComponentsRouteImport } from './routes/components' +import { Route as CssRouteImport } from './routes/css' +import { Route as EnhancedRouteImport } from './routes/enhanced' +import { Route as ImportedRouteImport } from './routes/imported' +import { Route as ScrollRestorationRouteImport } from './routes/scroll-restoration' -const ScrollRestorationRoute = ScrollRestorationRouteImport.update({ - id: '/scroll-restoration', - path: '/scroll-restoration', - getParentRoute: () => rootRouteImport, -} as any) -const ImportedRoute = ImportedRouteImport.update({ - id: '/imported', - path: '/imported', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const EnhancedRoute = EnhancedRouteImport.update({ - id: '/enhanced', - path: '/enhanced', +const ComponentsRoute = ComponentsRouteImport.update({ + id: '/components', + path: '/components', getParentRoute: () => rootRouteImport, } as any) const CssRoute = CssRouteImport.update({ @@ -36,14 +31,19 @@ const CssRoute = CssRouteImport.update({ path: '/css', getParentRoute: () => rootRouteImport, } as any) -const ComponentsRoute = ComponentsRouteImport.update({ - id: '/components', - path: '/components', +const EnhancedRoute = EnhancedRouteImport.update({ + id: '/enhanced', + path: '/enhanced', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ImportedRoute = ImportedRouteImport.update({ + id: '/imported', + path: '/imported', + getParentRoute: () => rootRouteImport, +} as any) +const ScrollRestorationRoute = ScrollRestorationRouteImport.update({ + id: '/scroll-restoration', + path: '/scroll-restoration', getParentRoute: () => rootRouteImport, } as any) @@ -110,25 +110,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/scroll-restoration': { - id: '/scroll-restoration' - path: '/scroll-restoration' - fullPath: '/scroll-restoration' - preLoaderRoute: typeof ScrollRestorationRouteImport - parentRoute: typeof rootRouteImport - } - '/imported': { - id: '/imported' - path: '/imported' - fullPath: '/imported' - preLoaderRoute: typeof ImportedRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/enhanced': { - id: '/enhanced' - path: '/enhanced' - fullPath: '/enhanced' - preLoaderRoute: typeof EnhancedRouteImport + '/components': { + id: '/components' + path: '/components' + fullPath: '/components' + preLoaderRoute: typeof ComponentsRouteImport parentRoute: typeof rootRouteImport } '/css': { @@ -138,18 +131,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CssRouteImport parentRoute: typeof rootRouteImport } - '/components': { - id: '/components' - path: '/components' - fullPath: '/components' - preLoaderRoute: typeof ComponentsRouteImport + '/enhanced': { + id: '/enhanced' + path: '/enhanced' + fullPath: '/enhanced' + preLoaderRoute: typeof EnhancedRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/imported': { + id: '/imported' + path: '/imported' + fullPath: '/imported' + preLoaderRoute: typeof ImportedRouteImport + parentRoute: typeof rootRouteImport + } + '/scroll-restoration': { + id: '/scroll-restoration' + path: '/scroll-restoration' + fullPath: '/scroll-restoration' + preLoaderRoute: typeof ScrollRestorationRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/dev-ssr-styles/package.json b/e2e/react-start/dev-ssr-styles/package.json index 91c5cc1281..c9c14943d4 100644 --- a/e2e/react-start/dev-ssr-styles/package.json +++ b/e2e/react-start/dev-ssr-styles/package.json @@ -37,7 +37,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-core": "workspace:*", "@tanstack/router-e2e-utils": "workspace:*", "@types/node": "^22.10.2", @@ -46,7 +46,8 @@ "@vitejs/plugin-react": "^6.0.1", "nitro": "^3.0.260311-beta", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "zod": "^4.4.3" } diff --git a/e2e/react-start/early-hints/package.json b/e2e/react-start/early-hints/package.json index 932ab20cdc..2945148950 100644 --- a/e2e/react-start/early-hints/package.json +++ b/e2e/react-start/early-hints/package.json @@ -19,7 +19,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/node-forge": "^1.3.14", @@ -29,7 +29,8 @@ "node-forge": "^1.3.1", "srvx": "0.11.15", "tsx": "4.20.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/early-hints/src/routeTree.gen.ts b/e2e/react-start/early-hints/src/routeTree.gen.ts index 14e2fd341e..5196090061 100644 --- a/e2e/react-start/early-hints/src/routeTree.gen.ts +++ b/e2e/react-start/early-hints/src/routeTree.gen.ts @@ -9,22 +9,17 @@ // 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 RedirectRouteImport } from './routes/redirect' -import { Route as ParentRouteImport } from './routes/parent' -import { Route as OtherRouteImport } from './routes/other' import { Route as IndexRouteImport } from './routes/index' -import { Route as ParentChildRouteImport } from './routes/parent/child' +import { Route as OtherRouteImport } from './routes/other' +import { Route as ParentRouteImport } from './routes/parent' +import { Route as RedirectRouteImport } from './routes/redirect' import { Route as OtherNestedRouteImport } from './routes/other/nested' +import { Route as ParentChildRouteImport } from './routes/parent/child' import { Route as ParentChildGrandchildRouteImport } from './routes/parent/child/grandchild' -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', - getParentRoute: () => rootRouteImport, -} as any) -const ParentRoute = ParentRouteImport.update({ - id: '/parent', - path: '/parent', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const OtherRoute = OtherRouteImport.update({ @@ -32,21 +27,26 @@ const OtherRoute = OtherRouteImport.update({ path: '/other', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ParentRoute = ParentRouteImport.update({ + id: '/parent', + path: '/parent', getParentRoute: () => rootRouteImport, } as any) -const ParentChildRoute = ParentChildRouteImport.update({ - id: '/child', - path: '/child', - getParentRoute: () => ParentRoute, +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', + getParentRoute: () => rootRouteImport, } as any) const OtherNestedRoute = OtherNestedRouteImport.update({ id: '/nested', path: '/nested', getParentRoute: () => OtherRoute, } as any) +const ParentChildRoute = ParentChildRouteImport.update({ + id: '/child', + path: '/child', + getParentRoute: () => ParentRoute, +} as any) const ParentChildGrandchildRoute = ParentChildGrandchildRouteImport.update({ id: '/grandchild', path: '/grandchild', @@ -120,18 +120,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport - parentRoute: typeof rootRouteImport - } - '/parent': { - id: '/parent' - path: '/parent' - fullPath: '/parent' - preLoaderRoute: typeof ParentRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/other': { @@ -141,19 +134,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OtherRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/parent': { + id: '/parent' + path: '/parent' + fullPath: '/parent' + preLoaderRoute: typeof ParentRouteImport parentRoute: typeof rootRouteImport } - '/parent/child': { - id: '/parent/child' - path: '/child' - fullPath: '/parent/child' - preLoaderRoute: typeof ParentChildRouteImport - parentRoute: typeof ParentRoute + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport + parentRoute: typeof rootRouteImport } '/other/nested': { id: '/other/nested' @@ -162,6 +155,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OtherNestedRouteImport parentRoute: typeof OtherRoute } + '/parent/child': { + id: '/parent/child' + path: '/child' + fullPath: '/parent/child' + preLoaderRoute: typeof ParentChildRouteImport + parentRoute: typeof ParentRoute + } '/parent/child/grandchild': { id: '/parent/child/grandchild' path: '/grandchild' diff --git a/e2e/react-start/flamegraph-bench/package.json b/e2e/react-start/flamegraph-bench/package.json index 87c19a6d4b..5f14aed8a3 100644 --- a/e2e/react-start/flamegraph-bench/package.json +++ b/e2e/react-start/flamegraph-bench/package.json @@ -22,7 +22,8 @@ "nitro": "^3.0.260311-beta", "autocannon": "^8.0.0", "concurrently": "9.2.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/flamegraph-bench/src/routeTree.gen.ts b/e2e/react-start/flamegraph-bench/src/routeTree.gen.ts index 198d538cd1..cac5558477 100644 --- a/e2e/react-start/flamegraph-bench/src/routeTree.gen.ts +++ b/e2e/react-start/flamegraph-bench/src/routeTree.gen.ts @@ -9,10 +9,10 @@ // 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 SearchRouteImport } from './routes/search' import { Route as IndexRouteImport } from './routes/index' -import { Route as PageIdRouteImport } from './routes/page.$id' +import { Route as SearchRouteImport } from './routes/search' import { Route as NestedARouteImport } from './routes/nested/$a' +import { Route as PageIdRouteImport } from './routes/page.$id' import { Route as NestedABRouteImport } from './routes/nested/$a.$b' import { Route as NestedABCRouteImport } from './routes/nested/$a.$b.$c' import { Route as NestedABCDRouteImport } from './routes/nested/$a.$b.$c.$d' @@ -39,19 +39,14 @@ import { Route as NestedABCDEFGHIJKLMNOPQRSTUVWXRouteImport } from './routes/nes import { Route as NestedABCDEFGHIJKLMNOPQRSTUVWXYRouteImport } from './routes/nested/$a.$b.$c.$d.$e.$f.$g.$h.$i.$j.$k.$l.$m.$n.$o.$p.$q.$r.$s.$t.$u.$v.$w.$x.$y' import { Route as NestedABCDEFGHIJKLMNOPQRSTUVWXYZRouteImport } from './routes/nested/$a.$b.$c.$d.$e.$f.$g.$h.$i.$j.$k.$l.$m.$n.$o.$p.$q.$r.$s.$t.$u.$v.$w.$x.$y.$z' -const SearchRoute = SearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const PageIdRoute = PageIdRouteImport.update({ - id: '/page/$id', - path: '/page/$id', +const SearchRoute = SearchRouteImport.update({ + id: '/search', + path: '/search', getParentRoute: () => rootRouteImport, } as any) const NestedARoute = NestedARouteImport.update({ @@ -59,6 +54,11 @@ const NestedARoute = NestedARouteImport.update({ path: '/nested/$a', getParentRoute: () => rootRouteImport, } as any) +const PageIdRoute = PageIdRouteImport.update({ + id: '/page/$id', + path: '/page/$id', + getParentRoute: () => rootRouteImport, +} as any) const NestedABRoute = NestedABRouteImport.update({ id: '/$b', path: '/$b', @@ -393,13 +393,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/search': { - id: '/search' - path: '/search' - fullPath: '/search' - preLoaderRoute: typeof SearchRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -407,11 +400,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/page/$id': { - id: '/page/$id' - path: '/page/$id' - fullPath: '/page/$id' - preLoaderRoute: typeof PageIdRouteImport + '/search': { + id: '/search' + path: '/search' + fullPath: '/search' + preLoaderRoute: typeof SearchRouteImport parentRoute: typeof rootRouteImport } '/nested/$a': { @@ -421,6 +414,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedARouteImport parentRoute: typeof rootRouteImport } + '/page/$id': { + id: '/page/$id' + path: '/page/$id' + fullPath: '/page/$id' + preLoaderRoute: typeof PageIdRouteImport + parentRoute: typeof rootRouteImport + } '/nested/$a/$b': { id: '/nested/$a/$b' path: '/$b' diff --git a/e2e/react-start/hmr/package.json b/e2e/react-start/hmr/package.json index b3a58125b9..7a6dc23f76 100644 --- a/e2e/react-start/hmr/package.json +++ b/e2e/react-start/hmr/package.json @@ -20,8 +20,8 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", @@ -31,7 +31,8 @@ "@types/react-dom": "^19.0.3", "tailwindcss": "^4.2.2", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/react-start/hmr/src/routeTree.gen.ts b/e2e/react-start/hmr/src/routeTree.gen.ts index 53c89f8eb5..73d8bd14f5 100644 --- a/e2e/react-start/hmr/src/routeTree.gen.ts +++ b/e2e/react-start/hmr/src/routeTree.gen.ts @@ -9,45 +9,46 @@ // 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 ServerFnHmrRouteImport } from './routes/server-fn-hmr' -import { Route as InputsRouteImport } from './routes/inputs' -import { Route as ComponentHmrNamedSplitRouteImport } from './routes/component-hmr-named-split' -import { Route as ComponentHmrNamedNosplitRouteImport } from './routes/component-hmr-named-nosplit' -import { Route as ComponentHmrNamedErrorSplitRouteImport } from './routes/component-hmr-named-error-split' -import { Route as ComponentHmrInlineSplitRouteImport } from './routes/component-hmr-inline-split' -import { Route as ComponentHmrInlineNosplitRouteImport } from './routes/component-hmr-inline-nosplit' -import { Route as ComponentHmrInlineErrorSplitRouteImport } from './routes/component-hmr-inline-error-split' -import { Route as ComponentHmrRouteImport } from './routes/component-hmr' -import { Route as ChildRouteImport } from './routes/child' import { Route as IndexRouteImport } from './routes/index' +import { Route as ChildRouteImport } from './routes/child' +import { Route as ComponentHmrRouteImport } from './routes/component-hmr' +import { Route as ComponentHmrInlineErrorSplitRouteImport } from './routes/component-hmr-inline-error-split' +import { Route as ComponentHmrInlineNosplitRouteImport } from './routes/component-hmr-inline-nosplit' +import { Route as ComponentHmrInlineSplitRouteImport } from './routes/component-hmr-inline-split' +import { Route as ComponentHmrLowercaseNamedSplitRouteImport } from './routes/component-hmr-lowercase-named-split' +import { Route as ComponentHmrNamedErrorSplitRouteImport } from './routes/component-hmr-named-error-split' +import { Route as ComponentHmrNamedNosplitRouteImport } from './routes/component-hmr-named-nosplit' +import { Route as ComponentHmrNamedSplitRouteImport } from './routes/component-hmr-named-split' +import { Route as InputsRouteImport } from './routes/inputs' +import { Route as ServerFnHmrRouteImport } from './routes/server-fn-hmr' import { Route as AliasedRouteImportsIdRouteImport } from './routes/aliased-route-imports.$id' import { Route as AliasedRouteImportsIdChildRouteImport } from './routes/aliased-route-imports.$id.child' -const ServerFnHmrRoute = ServerFnHmrRouteImport.update({ - id: '/server-fn-hmr', - path: '/server-fn-hmr', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const InputsRoute = InputsRouteImport.update({ - id: '/inputs', - path: '/inputs', +const ChildRoute = ChildRouteImport.update({ + id: '/child', + path: '/child', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrNamedSplitRoute = ComponentHmrNamedSplitRouteImport.update({ - id: '/component-hmr-named-split', - path: '/component-hmr-named-split', +const ComponentHmrRoute = ComponentHmrRouteImport.update({ + id: '/component-hmr', + path: '/component-hmr', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrNamedNosplitRoute = - ComponentHmrNamedNosplitRouteImport.update({ - id: '/component-hmr-named-nosplit', - path: '/component-hmr-named-nosplit', +const ComponentHmrInlineErrorSplitRoute = + ComponentHmrInlineErrorSplitRouteImport.update({ + id: '/component-hmr-inline-error-split', + path: '/component-hmr-inline-error-split', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrNamedErrorSplitRoute = - ComponentHmrNamedErrorSplitRouteImport.update({ - id: '/component-hmr-named-error-split', - path: '/component-hmr-named-error-split', +const ComponentHmrInlineNosplitRoute = + ComponentHmrInlineNosplitRouteImport.update({ + id: '/component-hmr-inline-nosplit', + path: '/component-hmr-inline-nosplit', getParentRoute: () => rootRouteImport, } as any) const ComponentHmrInlineSplitRoute = ComponentHmrInlineSplitRouteImport.update({ @@ -55,31 +56,37 @@ const ComponentHmrInlineSplitRoute = ComponentHmrInlineSplitRouteImport.update({ path: '/component-hmr-inline-split', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrInlineNosplitRoute = - ComponentHmrInlineNosplitRouteImport.update({ - id: '/component-hmr-inline-nosplit', - path: '/component-hmr-inline-nosplit', +const ComponentHmrLowercaseNamedSplitRoute = + ComponentHmrLowercaseNamedSplitRouteImport.update({ + id: '/component-hmr-lowercase-named-split', + path: '/component-hmr-lowercase-named-split', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrInlineErrorSplitRoute = - ComponentHmrInlineErrorSplitRouteImport.update({ - id: '/component-hmr-inline-error-split', - path: '/component-hmr-inline-error-split', +const ComponentHmrNamedErrorSplitRoute = + ComponentHmrNamedErrorSplitRouteImport.update({ + id: '/component-hmr-named-error-split', + path: '/component-hmr-named-error-split', getParentRoute: () => rootRouteImport, } as any) -const ComponentHmrRoute = ComponentHmrRouteImport.update({ - id: '/component-hmr', - path: '/component-hmr', +const ComponentHmrNamedNosplitRoute = + ComponentHmrNamedNosplitRouteImport.update({ + id: '/component-hmr-named-nosplit', + path: '/component-hmr-named-nosplit', + getParentRoute: () => rootRouteImport, + } as any) +const ComponentHmrNamedSplitRoute = ComponentHmrNamedSplitRouteImport.update({ + id: '/component-hmr-named-split', + path: '/component-hmr-named-split', getParentRoute: () => rootRouteImport, } as any) -const ChildRoute = ChildRouteImport.update({ - id: '/child', - path: '/child', +const InputsRoute = InputsRouteImport.update({ + id: '/inputs', + path: '/inputs', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ServerFnHmrRoute = ServerFnHmrRouteImport.update({ + id: '/server-fn-hmr', + path: '/server-fn-hmr', getParentRoute: () => rootRouteImport, } as any) const AliasedRouteImportsIdRoute = AliasedRouteImportsIdRouteImport.update({ @@ -101,6 +108,7 @@ export interface FileRoutesByFullPath { '/component-hmr-inline-error-split': typeof ComponentHmrInlineErrorSplitRoute '/component-hmr-inline-nosplit': typeof ComponentHmrInlineNosplitRoute '/component-hmr-inline-split': typeof ComponentHmrInlineSplitRoute + '/component-hmr-lowercase-named-split': typeof ComponentHmrLowercaseNamedSplitRoute '/component-hmr-named-error-split': typeof ComponentHmrNamedErrorSplitRoute '/component-hmr-named-nosplit': typeof ComponentHmrNamedNosplitRoute '/component-hmr-named-split': typeof ComponentHmrNamedSplitRoute @@ -116,6 +124,7 @@ export interface FileRoutesByTo { '/component-hmr-inline-error-split': typeof ComponentHmrInlineErrorSplitRoute '/component-hmr-inline-nosplit': typeof ComponentHmrInlineNosplitRoute '/component-hmr-inline-split': typeof ComponentHmrInlineSplitRoute + '/component-hmr-lowercase-named-split': typeof ComponentHmrLowercaseNamedSplitRoute '/component-hmr-named-error-split': typeof ComponentHmrNamedErrorSplitRoute '/component-hmr-named-nosplit': typeof ComponentHmrNamedNosplitRoute '/component-hmr-named-split': typeof ComponentHmrNamedSplitRoute @@ -132,6 +141,7 @@ export interface FileRoutesById { '/component-hmr-inline-error-split': typeof ComponentHmrInlineErrorSplitRoute '/component-hmr-inline-nosplit': typeof ComponentHmrInlineNosplitRoute '/component-hmr-inline-split': typeof ComponentHmrInlineSplitRoute + '/component-hmr-lowercase-named-split': typeof ComponentHmrLowercaseNamedSplitRoute '/component-hmr-named-error-split': typeof ComponentHmrNamedErrorSplitRoute '/component-hmr-named-nosplit': typeof ComponentHmrNamedNosplitRoute '/component-hmr-named-split': typeof ComponentHmrNamedSplitRoute @@ -149,6 +159,7 @@ export interface FileRouteTypes { | '/component-hmr-inline-error-split' | '/component-hmr-inline-nosplit' | '/component-hmr-inline-split' + | '/component-hmr-lowercase-named-split' | '/component-hmr-named-error-split' | '/component-hmr-named-nosplit' | '/component-hmr-named-split' @@ -164,6 +175,7 @@ export interface FileRouteTypes { | '/component-hmr-inline-error-split' | '/component-hmr-inline-nosplit' | '/component-hmr-inline-split' + | '/component-hmr-lowercase-named-split' | '/component-hmr-named-error-split' | '/component-hmr-named-nosplit' | '/component-hmr-named-split' @@ -179,6 +191,7 @@ export interface FileRouteTypes { | '/component-hmr-inline-error-split' | '/component-hmr-inline-nosplit' | '/component-hmr-inline-split' + | '/component-hmr-lowercase-named-split' | '/component-hmr-named-error-split' | '/component-hmr-named-nosplit' | '/component-hmr-named-split' @@ -195,6 +208,7 @@ export interface RootRouteChildren { ComponentHmrInlineErrorSplitRoute: typeof ComponentHmrInlineErrorSplitRoute ComponentHmrInlineNosplitRoute: typeof ComponentHmrInlineNosplitRoute ComponentHmrInlineSplitRoute: typeof ComponentHmrInlineSplitRoute + ComponentHmrLowercaseNamedSplitRoute: typeof ComponentHmrLowercaseNamedSplitRoute ComponentHmrNamedErrorSplitRoute: typeof ComponentHmrNamedErrorSplitRoute ComponentHmrNamedNosplitRoute: typeof ComponentHmrNamedNosplitRoute ComponentHmrNamedSplitRoute: typeof ComponentHmrNamedSplitRoute @@ -205,39 +219,39 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/server-fn-hmr': { - id: '/server-fn-hmr' - path: '/server-fn-hmr' - fullPath: '/server-fn-hmr' - preLoaderRoute: typeof ServerFnHmrRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/inputs': { - id: '/inputs' - path: '/inputs' - fullPath: '/inputs' - preLoaderRoute: typeof InputsRouteImport + '/child': { + id: '/child' + path: '/child' + fullPath: '/child' + preLoaderRoute: typeof ChildRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr-named-split': { - id: '/component-hmr-named-split' - path: '/component-hmr-named-split' - fullPath: '/component-hmr-named-split' - preLoaderRoute: typeof ComponentHmrNamedSplitRouteImport + '/component-hmr': { + id: '/component-hmr' + path: '/component-hmr' + fullPath: '/component-hmr' + preLoaderRoute: typeof ComponentHmrRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr-named-nosplit': { - id: '/component-hmr-named-nosplit' - path: '/component-hmr-named-nosplit' - fullPath: '/component-hmr-named-nosplit' - preLoaderRoute: typeof ComponentHmrNamedNosplitRouteImport + '/component-hmr-inline-error-split': { + id: '/component-hmr-inline-error-split' + path: '/component-hmr-inline-error-split' + fullPath: '/component-hmr-inline-error-split' + preLoaderRoute: typeof ComponentHmrInlineErrorSplitRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr-named-error-split': { - id: '/component-hmr-named-error-split' - path: '/component-hmr-named-error-split' - fullPath: '/component-hmr-named-error-split' - preLoaderRoute: typeof ComponentHmrNamedErrorSplitRouteImport + '/component-hmr-inline-nosplit': { + id: '/component-hmr-inline-nosplit' + path: '/component-hmr-inline-nosplit' + fullPath: '/component-hmr-inline-nosplit' + preLoaderRoute: typeof ComponentHmrInlineNosplitRouteImport parentRoute: typeof rootRouteImport } '/component-hmr-inline-split': { @@ -247,39 +261,46 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ComponentHmrInlineSplitRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr-inline-nosplit': { - id: '/component-hmr-inline-nosplit' - path: '/component-hmr-inline-nosplit' - fullPath: '/component-hmr-inline-nosplit' - preLoaderRoute: typeof ComponentHmrInlineNosplitRouteImport + '/component-hmr-lowercase-named-split': { + id: '/component-hmr-lowercase-named-split' + path: '/component-hmr-lowercase-named-split' + fullPath: '/component-hmr-lowercase-named-split' + preLoaderRoute: typeof ComponentHmrLowercaseNamedSplitRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr-inline-error-split': { - id: '/component-hmr-inline-error-split' - path: '/component-hmr-inline-error-split' - fullPath: '/component-hmr-inline-error-split' - preLoaderRoute: typeof ComponentHmrInlineErrorSplitRouteImport + '/component-hmr-named-error-split': { + id: '/component-hmr-named-error-split' + path: '/component-hmr-named-error-split' + fullPath: '/component-hmr-named-error-split' + preLoaderRoute: typeof ComponentHmrNamedErrorSplitRouteImport parentRoute: typeof rootRouteImport } - '/component-hmr': { - id: '/component-hmr' - path: '/component-hmr' - fullPath: '/component-hmr' - preLoaderRoute: typeof ComponentHmrRouteImport + '/component-hmr-named-nosplit': { + id: '/component-hmr-named-nosplit' + path: '/component-hmr-named-nosplit' + fullPath: '/component-hmr-named-nosplit' + preLoaderRoute: typeof ComponentHmrNamedNosplitRouteImport parentRoute: typeof rootRouteImport } - '/child': { - id: '/child' - path: '/child' - fullPath: '/child' - preLoaderRoute: typeof ChildRouteImport + '/component-hmr-named-split': { + id: '/component-hmr-named-split' + path: '/component-hmr-named-split' + fullPath: '/component-hmr-named-split' + preLoaderRoute: typeof ComponentHmrNamedSplitRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/inputs': { + id: '/inputs' + path: '/inputs' + fullPath: '/inputs' + preLoaderRoute: typeof InputsRouteImport + parentRoute: typeof rootRouteImport + } + '/server-fn-hmr': { + id: '/server-fn-hmr' + path: '/server-fn-hmr' + fullPath: '/server-fn-hmr' + preLoaderRoute: typeof ServerFnHmrRouteImport parentRoute: typeof rootRouteImport } '/aliased-route-imports/$id': { @@ -319,6 +340,7 @@ const rootRouteChildren: RootRouteChildren = { ComponentHmrInlineErrorSplitRoute: ComponentHmrInlineErrorSplitRoute, ComponentHmrInlineNosplitRoute: ComponentHmrInlineNosplitRoute, ComponentHmrInlineSplitRoute: ComponentHmrInlineSplitRoute, + ComponentHmrLowercaseNamedSplitRoute: ComponentHmrLowercaseNamedSplitRoute, ComponentHmrNamedErrorSplitRoute: ComponentHmrNamedErrorSplitRoute, ComponentHmrNamedNosplitRoute: ComponentHmrNamedNosplitRoute, ComponentHmrNamedSplitRoute: ComponentHmrNamedSplitRoute, diff --git a/e2e/react-start/hmr/src/routes/__root.tsx b/e2e/react-start/hmr/src/routes/__root.tsx index b2ec48fb70..22dddf1b3c 100644 --- a/e2e/react-start/hmr/src/routes/__root.tsx +++ b/e2e/react-start/hmr/src/routes/__root.tsx @@ -164,6 +164,12 @@ function RootContent() { label: 'Named Split', detail: 'Named component with default splitting', }, + { + testId: 'component-hmr-lowercase-named-split-link', + to: '/component-hmr-lowercase-named-split', + label: 'Lowercase Named Split', + detail: 'Lowercase named component with default splitting', + }, { testId: 'component-hmr-named-nosplit-link', to: '/component-hmr-named-nosplit', diff --git a/e2e/react-start/hmr/src/routes/component-hmr-lowercase-named-split.tsx b/e2e/react-start/hmr/src/routes/component-hmr-lowercase-named-split.tsx new file mode 100644 index 0000000000..957e14d7a4 --- /dev/null +++ b/e2e/react-start/hmr/src/routes/component-hmr-lowercase-named-split.tsx @@ -0,0 +1,26 @@ +import { createFileRoute } from '@tanstack/react-router' +import { useState } from 'react' + +export const Route = createFileRoute('/component-hmr-lowercase-named-split')({ + component, +}) + +function component() { + const [count, setCount] = useState(0) + + return ( +
    +

    + component-hmr-lowercase-named-split-baseline +

    +

    Count: {count}

    + + +
    + ) +} diff --git a/e2e/react-start/hmr/tests/app.spec.ts b/e2e/react-start/hmr/tests/app.spec.ts index 10499e4b88..69ce1d20f3 100644 --- a/e2e/react-start/hmr/tests/app.spec.ts +++ b/e2e/react-start/hmr/tests/app.spec.ts @@ -11,6 +11,7 @@ import type { Page } from '@playwright/test' const whitelistErrors = [ 'Failed to load resource: net::ERR_NAME_NOT_RESOLVED', 'Failed to load resource: the server responded with a status of 504', + 'root loader error', ] const hmrExpect = expect.configure({ timeout: 20_000 }) @@ -24,6 +25,8 @@ const routeFilePaths = { componentHmrInlineSplit: 'routes/component-hmr-inline-split.tsx', componentHmrInlineNosplit: 'routes/component-hmr-inline-nosplit.tsx', componentHmrNamedSplit: 'routes/component-hmr-named-split.tsx', + componentHmrLowercaseNamedSplit: + 'routes/component-hmr-lowercase-named-split.tsx', componentHmrNamedNosplit: 'routes/component-hmr-named-nosplit.tsx', componentHmrInlineErrorSplit: 'routes/component-hmr-inline-error-split.tsx', componentHmrNamedErrorSplit: 'routes/component-hmr-named-error-split.tsx', @@ -35,6 +38,21 @@ const routeFilePaths = { type RouteFileKey = keyof typeof routeFilePaths +const rootLoaderBlock = " loader: () => ({\n crumb: 'Home',\n }),\n" +const redirectRootLoaderBlock = + " staleTime: Infinity,\n loader: () => ({\n crumb:\n typeof window === 'undefined'\n ? 'Home Armed'\n : (window.sessionStorage.getItem('hmr-root-crumb') ?? 'Home Armed'),\n }),\n" +const childBeforeLoadBlock = + " beforeLoad: () => ({\n greeting: 'Hello',\n }),\n" +const childLoaderBlock = " loader: () => ({\n crumb: 'Child',\n }),\n" +const failingChildOnStayBlock = + " onStay: () => {\n ;(window as any).__TSR_HMR_FAILURE_RAN__ = true\n throw new Error('intentional HMR lifecycle failure')\n },\n" +const pendingNavigationChildLoaderBlock = + " loader: async () => {\n if (typeof window !== 'undefined') {\n const gate = (window as any).__TSR_HMR_PENDING_NAV_GATE__\n if (gate) {\n ;(window as any).__TSR_HMR_PENDING_NAV_GATE__ = undefined\n ;(window as any).__TSR_HMR_PENDING_NAV_EVENTS__.push('loader-start')\n await gate\n ;(window as any).__TSR_HMR_PENDING_NAV_EVENTS__.push('loader-end')\n }\n }\n return { crumb: 'Child' }\n },\n" +const pendingNavigationChildOnStayBlock = + " onStay: () => {\n ;(window as any).__TSR_HMR_PENDING_NAV_EVENTS__.push('onStay')\n throw new Error('intentional HMR failure after pending navigation')\n },\n" +const redirectChildBeforeLoadBlock = + " beforeLoad: () => {\n throw redirect({ to: '/' })\n },\n" + const routeFiles = Object.fromEntries( Object.entries(routeFilePaths).map(([key, relativePath]) => [ key, @@ -99,6 +117,11 @@ const routeFileRestoreChecks: Partial< testId: 'component-hmr-marker', text: 'component-hmr-named-split-baseline', }, + componentHmrLowercaseNamedSplit: { + url: '/component-hmr-lowercase-named-split', + testId: 'component-hmr-marker', + text: 'component-hmr-lowercase-named-split-baseline', + }, componentHmrNamedNosplit: { url: '/component-hmr-named-nosplit', testId: 'component-hmr-marker', @@ -133,6 +156,8 @@ function normalizeRouteSource(routeFileKey: RouteFileKey, source: string) { } if (routeFileKey === 'root') { + next = next.replace(redirectRootLoaderBlock, rootLoaderBlock) + for (const marker of [ 'root-component-inline-baseline', 'root-component-inline-updated', @@ -158,6 +183,16 @@ function normalizeRouteSource(routeFileKey: RouteFileKey, source: string) { ' component: RootComponent,', ) + next = next.replace( + ' shellComponent: shellComponent,\n component: RootContent,', + ' component: RootComponent,', + ) + + next = next.replace( + " loader: () => {\n throw new Error('root loader error')\n },\n errorComponent: errorComponent,\n component: RootComponent,", + " loader: () => ({\n crumb: 'Home',\n }),\n component: RootComponent,", + ) + for (const marker of ['root-shell-baseline', 'root-shell-updated']) { next = next.replace( `function RootShell({ children }: { children: ReactNode }) {\n return {children}\n}\n\nfunction Breadcrumbs() {`, @@ -165,36 +200,62 @@ function normalizeRouteSource(routeFileKey: RouteFileKey, source: string) { ) } + for (const marker of [ + 'root-shell-lowercase-baseline', + 'root-shell-lowercase-updated', + ]) { + next = next.replace( + `function shellComponent({ children }: { children: ReactNode }) {\n return {children}\n}\n\nfunction Breadcrumbs() {`, + 'function Breadcrumbs() {', + ) + } + + for (const marker of [ + 'root-error-lowercase-baseline', + 'root-error-lowercase-updated', + ]) { + next = next.replace( + `function errorComponent() {\n return (\n \n

    ${marker}

    \n
    \n )\n}\n\nfunction Breadcrumbs() {`, + 'function Breadcrumbs() {', + ) + } + next = replaceAll(next, "crumb: 'Home Updated'", "crumb: 'Home'") next = replaceAll(next, 'root-component-updated', 'root-component-baseline') next = replaceAll(next, 'root-shell-updated', 'root-shell-baseline') } if (routeFileKey === 'child') { - const beforeLoadBlock = - " beforeLoad: () => ({\n greeting: 'Hello',\n }),\n" - const loaderBlock = " loader: () => ({\n crumb: 'Child',\n }),\n" - next = replaceAll(next, "greeting: 'Hi'", "greeting: 'Hello'") + next = next.replace(failingChildOnStayBlock, '') + next = next.replace(pendingNavigationChildOnStayBlock, '') + next = next.replace(pendingNavigationChildLoaderBlock, childLoaderBlock) + next = next.replace(redirectChildBeforeLoadBlock, childBeforeLoadBlock) + next = next.replace( + "import { createFileRoute, redirect } from '@tanstack/react-router'", + "import { createFileRoute } from '@tanstack/react-router'", + ) + next = replaceAll(next, "crumb: 'Child Recovered'", "crumb: 'Child'") + next = replaceAll(next, "crumb: 'Child Failed'", "crumb: 'Child'") next = replaceAll(next, "crumb: 'Child Updated Again'", "crumb: 'Child'") next = replaceAll(next, "crumb: 'Child Updated'", "crumb: 'Child'") - if (!next.includes(beforeLoadBlock)) { + if (!next.includes(childBeforeLoadBlock)) { next = next.replace( ' component: Child,\n', - `${beforeLoadBlock} component: Child,\n`, + `${childBeforeLoadBlock} component: Child,\n`, ) } - if (!next.includes(loaderBlock)) { + if (!next.includes(childLoaderBlock)) { const withLoaderAfterBeforeLoad = next.replace( - `${beforeLoadBlock} component: Child,\n`, - `${beforeLoadBlock}${loaderBlock} component: Child,\n`, + `${childBeforeLoadBlock} component: Child,\n`, + `${childBeforeLoadBlock}${childLoaderBlock} component: Child,\n`, ) next = withLoaderAfterBeforeLoad === next ? next.replace( ' component: Child,\n', - `${loaderBlock} component: Child,\n`, + `${childLoaderBlock} component: Child,\n`, ) : withLoaderAfterBeforeLoad } @@ -214,6 +275,10 @@ function normalizeRouteSource(routeFileKey: RouteFileKey, source: string) { 'component-hmr-named-split-updated', 'component-hmr-named-split-baseline', ], + componentHmrLowercaseNamedSplit: [ + 'component-hmr-lowercase-named-split-updated', + 'component-hmr-lowercase-named-split-baseline', + ], componentHmrNamedNosplit: [ 'component-hmr-named-nosplit-updated', 'component-hmr-named-nosplit-baseline', @@ -619,6 +684,178 @@ test.describe('react-start hmr', () => { await expect(page.getByTestId('child')).toHaveText('child') }) + test('rolls back a failed route refresh and accepts the next HMR update', async ({ + page, + }) => { + await page.goto('/child') + await page.getByTestId('hydrated').waitFor({ state: 'visible' }) + await page.getByTestId('root-message').fill('preserved through failure') + await expect(page.getByTestId('crumb-/child')).toHaveText('Child') + + await page.evaluate(() => { + ;(window as any).__TSR_HMR_FAILURE_RAN__ = false + }) + + await rewriteRouteFile( + page, + 'child', + (source) => + source.replace( + childLoaderBlock, + `${failingChildOnStayBlock} loader: () => ({\n crumb: 'Child Failed',\n }),\n`, + ), + async () => { + await waitForRouteModuleUpdate(page, '/child', 'Child Failed') + await page.waitForFunction( + () => (window as any).__TSR_HMR_FAILURE_RAN__ === true, + ) + }, + ) + + await expect(page.getByTestId('crumb-/child')).toHaveText('Child') + await expect(page.getByTestId('root-message')).toHaveValue( + 'preserved through failure', + ) + + await rewriteRouteFile( + page, + 'child', + (source) => + source + .replace(failingChildOnStayBlock, '') + .replace("crumb: 'Child Failed'", "crumb: 'Child Recovered'"), + async () => { + await waitForRouteModuleUpdate(page, '/child', 'Child Recovered') + await hmrExpect(page.getByTestId('crumb-/child')).toHaveText( + 'Child Recovered', + ) + }, + ) + + await expect(page.getByTestId('root-message')).toHaveValue( + 'preserved through failure', + ) + }) + + test('rematerializes retained routes when an HMR update redirects', async ({ + page, + }) => { + await page.goto('/child') + await page.getByTestId('hydrated').waitFor({ state: 'visible' }) + await page.getByTestId('root-message').fill('preserved through redirect') + + await rewriteRouteFile( + page, + 'root', + (source) => source.replace(rootLoaderBlock, redirectRootLoaderBlock), + async () => { + await waitForRouteModuleUpdate(page, '__root__', 'Home Armed') + await hmrExpect(page.getByTestId('crumb-__root__')).toHaveText( + 'Home Armed', + ) + }, + ) + + await page.evaluate(() => { + window.sessionStorage.setItem('hmr-root-crumb', 'Home Redirected') + }) + + await rewriteRouteFile( + page, + 'child', + (source) => + source + .replace( + "import { createFileRoute } from '@tanstack/react-router'", + "import { createFileRoute, redirect } from '@tanstack/react-router'", + ) + .replace(childBeforeLoadBlock, redirectChildBeforeLoadBlock), + async () => { + await hmrExpect(page).toHaveURL((url) => url.pathname === '/') + await hmrExpect(page.getByTestId('crumb-__root__')).toHaveText( + 'Home Redirected', + ) + }, + ) + + await expect(page.getByTestId('root-message')).toHaveValue( + 'preserved through redirect', + ) + }) + + test('waits for a pending navigation before applying an HMR refresh', async ({ + page, + }) => { + await page.goto('/') + await page.getByTestId('hydrated').waitFor({ state: 'visible' }) + await page.getByTestId('root-message').fill('preserved while pending') + + await page.evaluate(() => { + let release!: () => void + const gate = new Promise((resolve) => { + release = resolve + }) + ;(window as any).__TSR_HMR_PENDING_NAV_EVENTS__ = [] + ;(window as any).__TSR_HMR_PENDING_NAV_GATE__ = gate + ;(window as any).__TSR_HMR_RELEASE_PENDING_NAV__ = release + }) + + await rewriteRouteFile( + page, + 'child', + (source) => + source.replace(childLoaderBlock, pendingNavigationChildLoaderBlock), + async () => { + await page.waitForFunction(() => { + const loader = (window as any).__TSR_ROUTER__?.routesById?.['/child'] + ?.options?.loader + return String(loader).includes('__TSR_HMR_PENDING_NAV_GATE__') + }) + }, + ) + + await page.getByTestId('child-link').click() + await page.waitForFunction( + () => + (window as any).__TSR_HMR_PENDING_NAV_EVENTS__?.[0] === 'loader-start', + ) + + await rewriteRouteFile( + page, + 'child', + (source) => + source.replace( + pendingNavigationChildLoaderBlock, + `${pendingNavigationChildOnStayBlock}${pendingNavigationChildLoaderBlock}`, + ), + async () => { + await page.waitForFunction(() => { + const onStay = (window as any).__TSR_ROUTER__?.routesById?.['/child'] + ?.options?.onStay + return String(onStay).includes( + 'intentional HMR failure after pending navigation', + ) + }) + }, + ) + + await page.evaluate(() => { + ;(window as any).__TSR_HMR_RELEASE_PENDING_NAV__() + }) + + await page.waitForFunction(() => + (window as any).__TSR_HMR_PENDING_NAV_EVENTS__?.includes('onStay'), + ) + await hmrExpect(page.getByTestId('child')).toHaveText('child') + await hmrExpect(page.getByTestId('crumb-/child')).toHaveText('Child') + expect( + await page.evaluate(() => (window as any).__TSR_HMR_PENDING_NAV_EVENTS__), + ).toEqual(['loader-start', 'loader-end', 'onStay']) + await expect(page.getByTestId('root-message')).toHaveValue( + 'preserved while pending', + ) + }) + test('adds a createFileRoute property during HMR', async ({ page }) => { await page.goto('/') await page.getByTestId('hydrated').waitFor({ state: 'visible' }) @@ -1032,6 +1269,89 @@ test.describe('react-start hmr', () => { ) }) + test('updates root route shellComponent during HMR when its function name is lowercase', async ({ + page, + }) => { + await rewriteRouteFile( + page, + 'root', + (source) => + source + .replace( + ' component: RootComponent,', + ' shellComponent: shellComponent,\n component: RootContent,', + ) + .replace( + 'function Breadcrumbs() {', + 'function shellComponent({ children }: { children: ReactNode }) {\n return {children}\n}\n\nfunction Breadcrumbs() {', + ), + async () => {}, + ) + await page.waitForTimeout(300) + + await reloadPageAndWaitForText( + page, + '/', + 'root-shell-marker', + 'root-shell-lowercase-baseline', + ) + await page.getByTestId('increment').click() + await page.getByTestId('message').fill('index preserved') + await page.getByTestId('root-message').fill('root preserved') + + await replaceRouteTextAndWait( + page, + 'root', + 'root-shell-lowercase-baseline', + 'root-shell-lowercase-updated', + async () => { + await hmrExpect(page.getByTestId('root-shell-marker')).toHaveText( + 'root-shell-lowercase-updated', + ) + }, + ) + + await expectHomeStatePreserved(page) + }) + + test('updates root route errorComponent during HMR when its function name is lowercase', async ({ + page, + }) => { + await seedHomeState(page) + + await rewriteRouteFile( + page, + 'root', + (source) => + source + .replace( + " loader: () => ({\n crumb: 'Home',\n }),\n component: RootComponent,", + " loader: () => {\n throw new Error('root loader error')\n },\n errorComponent: errorComponent,\n component: RootComponent,", + ) + .replace( + 'function Breadcrumbs() {', + 'function errorComponent() {\n return (\n \n

    root-error-lowercase-baseline

    \n
    \n )\n}\n\nfunction Breadcrumbs() {', + ), + async () => { + await hmrExpect(page.getByTestId('root-error-marker')).toHaveText( + 'root-error-lowercase-baseline', + ) + }, + ) + + await replaceRouteTextAndWait( + page, + 'root', + 'root-error-lowercase-baseline', + 'root-error-lowercase-updated', + async () => { + await hmrExpect(page.getByTestId('root-error-marker')).toHaveText( + 'root-error-lowercase-updated', + ) + }, + ) + }) + test('updates non-root route component during HMR when component is defined inline and codeSplitGroupings is undefined', async ({ page, }) => { @@ -1092,6 +1412,26 @@ test.describe('react-start hmr', () => { await expectComponentHmrStatePreserved(page) }) + test('updates a code-split route component whose function name is lowercase', async ({ + page, + }) => { + await seedComponentHmrState(page, '/component-hmr-lowercase-named-split') + + await replaceRouteTextAndWait( + page, + 'componentHmrLowercaseNamedSplit', + 'component-hmr-lowercase-named-split-baseline', + 'component-hmr-lowercase-named-split-updated', + async () => { + await hmrExpect(page.getByTestId('component-hmr-marker')).toHaveText( + 'component-hmr-lowercase-named-split-updated', + ) + }, + ) + + await expectComponentHmrStatePreserved(page) + }) + test('updates non-root route component during HMR when component is not defined inline and codeSplitGroupings disables code splitting', async ({ page, }) => { diff --git a/e2e/react-start/i18n-paraglide/package.json b/e2e/react-start/i18n-paraglide/package.json index bfb7b5eb26..5b525891a6 100644 --- a/e2e/react-start/i18n-paraglide/package.json +++ b/e2e/react-start/i18n-paraglide/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@inlang/paraglide-js": "^2.4.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -27,7 +27,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/i18n-paraglide/src/routeTree.gen.ts b/e2e/react-start/i18n-paraglide/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/e2e/react-start/i18n-paraglide/src/routeTree.gen.ts +++ b/e2e/react-start/i18n-paraglide/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/import-protection-custom-config/package.json b/e2e/react-start/import-protection-custom-config/package.json index 1175bd4ac0..25ff3f2738 100644 --- a/e2e/react-start/import-protection-custom-config/package.json +++ b/e2e/react-start/import-protection-custom-config/package.json @@ -19,14 +19,15 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/import-protection-custom-config/src/routeTree.gen.ts b/e2e/react-start/import-protection-custom-config/src/routeTree.gen.ts index 44861559e3..6a2acc53ef 100644 --- a/e2e/react-start/import-protection-custom-config/src/routeTree.gen.ts +++ b/e2e/react-start/import-protection-custom-config/src/routeTree.gen.ts @@ -9,13 +9,13 @@ // 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 FrontendLeakRouteImport } from './routes/frontend-leak' -import { Route as BackendLeakRouteImport } from './routes/backend-leak' import { Route as IndexRouteImport } from './routes/index' +import { Route as BackendLeakRouteImport } from './routes/backend-leak' +import { Route as FrontendLeakRouteImport } from './routes/frontend-leak' -const FrontendLeakRoute = FrontendLeakRouteImport.update({ - id: '/frontend-leak', - path: '/frontend-leak', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const BackendLeakRoute = BackendLeakRouteImport.update({ @@ -23,9 +23,9 @@ const BackendLeakRoute = BackendLeakRouteImport.update({ path: '/backend-leak', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const FrontendLeakRoute = FrontendLeakRouteImport.update({ + id: '/frontend-leak', + path: '/frontend-leak', getParentRoute: () => rootRouteImport, } as any) @@ -61,11 +61,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/frontend-leak': { - id: '/frontend-leak' - path: '/frontend-leak' - fullPath: '/frontend-leak' - preLoaderRoute: typeof FrontendLeakRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/backend-leak': { @@ -75,11 +75,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BackendLeakRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/frontend-leak': { + id: '/frontend-leak' + path: '/frontend-leak' + fullPath: '/frontend-leak' + preLoaderRoute: typeof FrontendLeakRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/import-protection/package.json b/e2e/react-start/import-protection/package.json index 65ad70126b..5459b84437 100644 --- a/e2e/react-start/import-protection/package.json +++ b/e2e/react-start/import-protection/package.json @@ -11,10 +11,7 @@ "build": "pnpm build:vite", "build:vite": "vite build && tsc --noEmit", "build:rsbuild": "rsbuild build && tsc --noEmit", - "start": "node server.js", - "test:e2e:mockMode": "rm -rf node_modules/.vite; rm -rf port*.txt; playwright test --project=chromium", - "test:e2e:errorMode": "rm -rf node_modules/.vite; rm -rf port*.txt; BEHAVIOR=error playwright test --project=chromium", - "test:e2e": "pnpm run test:e2e:mockMode && pnpm run test:e2e:errorMode" + "start": "node server.js" }, "dependencies": { "@tanstack/react-router": "workspace:^", @@ -24,8 +21,8 @@ "react-tweet": "^3.3.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -33,7 +30,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { @@ -57,6 +55,7 @@ "toolchain": "vite", "mode": "ssr", "env": { + "E2E_BUILD_LOG": "webserver-build.vite-ssr.log", "E2E_VITE_BUNDLED_DEV": "false" } }, @@ -65,12 +64,16 @@ "mode": "ssr", "name": "bundled-dev", "env": { + "E2E_BUILD_LOG": "webserver-build.vite-ssr-bundled-dev.log", "E2E_VITE_BUNDLED_DEV": "true" } }, { "toolchain": "rsbuild", - "mode": "ssr" + "mode": "ssr", + "env": { + "E2E_BUILD_LOG": "webserver-build.rsbuild-ssr.log" + } } ] } diff --git a/e2e/react-start/import-protection/playwright.config.ts b/e2e/react-start/import-protection/playwright.config.ts index ac239f36fe..778e865125 100644 --- a/e2e/react-start/import-protection/playwright.config.ts +++ b/e2e/react-start/import-protection/playwright.config.ts @@ -1,6 +1,7 @@ import { defineConfig, devices } from '@playwright/test' import { getTestServerPort } from '@tanstack/router-e2e-utils' import { isErrorMode } from './tests/utils/isErrorMode' +import { getViolationArtifactName } from './tests/violations.utils' import packageJson from './package.json' with { type: 'json' } const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite' @@ -11,8 +12,12 @@ const e2ePortKey = const distDir = process.env.E2E_DIST_DIR ?? 'dist' const PORT = await getTestServerPort(e2ePortKey) const baseURL = `http://localhost:${PORT}` -const buildCommand = - toolchain === 'rsbuild' ? 'pnpm build:rsbuild' : 'pnpm build:vite' +const violationArtifacts = [ + getViolationArtifactName('build'), + getViolationArtifactName('dev'), + getViolationArtifactName('dev.cold'), + getViolationArtifactName('dev.warm'), +].join(' ') console.log('running in error mode:', isErrorMode.toString()) @@ -36,7 +41,7 @@ export default defineConfig({ ? {} : { webServer: { - command: `rm -f webserver-build.log webserver-dev.log violations.build.json violations.dev.json && VITE_SERVER_PORT=${PORT} PORT=${PORT} ${buildCommand} > webserver-build.log 2>&1 && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, + command: `rm -f webserver-dev.log ${violationArtifacts} && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', diff --git a/e2e/react-start/import-protection/src/routes/issue-7725.tsx b/e2e/react-start/import-protection/src/routes/issue-7725.tsx new file mode 100644 index 0000000000..ef0152dd13 --- /dev/null +++ b/e2e/react-start/import-protection/src/routes/issue-7725.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' +import { issue7725ServerFnFactory } from '../violations/issue-7725-server-fn-factory' + +const getIssue7725Data = issue7725ServerFnFactory.handler(async () => { + return 'issue 7725 server function' +}) + +export const Route = createFileRoute('/issue-7725')({ + loader: () => getIssue7725Data(), + component: Issue7725, +}) + +function Issue7725() { + return

    Issue 7725

    +} diff --git a/e2e/react-start/import-protection/src/violations/issue-7725-server-fn-factory.ts b/e2e/react-start/import-protection/src/violations/issue-7725-server-fn-factory.ts new file mode 100644 index 0000000000..45be765b1b --- /dev/null +++ b/e2e/react-start/import-protection/src/violations/issue-7725-server-fn-factory.ts @@ -0,0 +1,3 @@ +import { createServerFn } from '@tanstack/react-start' + +export const issue7725ServerFnFactory = createServerFn() diff --git a/e2e/react-start/import-protection/tests/import-protection.spec.ts b/e2e/react-start/import-protection/tests/import-protection.spec.ts index cdc9aea32c..16b5b073eb 100644 --- a/e2e/react-start/import-protection/tests/import-protection.spec.ts +++ b/e2e/react-start/import-protection/tests/import-protection.spec.ts @@ -2,6 +2,7 @@ import path from 'node:path' import fs from 'node:fs' import { expect } from '@playwright/test' import { test } from '@tanstack/router-e2e-utils' +import { getViolationArtifactName } from './violations.utils' import type { Violation } from './violations.utils' import type { Page } from '@playwright/test' @@ -37,7 +38,7 @@ function normalizeKeyPath(value: string): string { async function readViolations( type: 'build' | 'dev' | 'dev.cold' | 'dev.warm', ): Promise> { - const filename = `violations.${type}.json` + const filename = getViolationArtifactName(type) const violationsPath = path.resolve(import.meta.dirname, '..', filename) const mod = await import(violationsPath, { with: { type: 'json' }, @@ -161,7 +162,8 @@ test('client-only violations route loads in mock mode', async ({ page }) => { }) for (const mode of ['build', 'dev'] as const) { - test(`violations.${mode}.json is written during ${mode}`, async () => { + const artifactName = getViolationArtifactName(mode) + test(`${artifactName} is written during ${mode}`, async () => { const violations = await readViolations(mode) expect(violations.length).toBeGreaterThan(0) }) @@ -204,7 +206,7 @@ test('build log does not contain mock-edge missing export warnings', () => { const buildLogPath = path.resolve( import.meta.dirname, '..', - 'webserver-build.log', + process.env.E2E_BUILD_LOG ?? 'webserver-build.log', ) if (!fs.existsSync(buildLogPath)) { diff --git a/e2e/react-start/import-protection/tests/violations.setup.ts b/e2e/react-start/import-protection/tests/violations.setup.ts index 02796227a7..2f863b398e 100644 --- a/e2e/react-start/import-protection/tests/violations.setup.ts +++ b/e2e/react-start/import-protection/tests/violations.setup.ts @@ -5,7 +5,10 @@ import { chromium } from '@playwright/test' import { getTestServerPort } from '@tanstack/router-e2e-utils' import packageJson from '../package.json' with { type: 'json' } -import { extractViolationsFromLog } from './violations.utils' +import { + extractViolationsFromLog, + getViolationArtifactName, +} from './violations.utils' import type { FullConfig } from '@playwright/test' import type { Violation } from './violations.utils' @@ -250,11 +253,11 @@ async function captureDevViolations(cwd: string): Promise { ) fs.writeFileSync( - path.resolve(cwd, 'violations.dev.json'), + path.resolve(cwd, getViolationArtifactName('dev')), JSON.stringify(coldViolations, null, 2), ) fs.writeFileSync( - path.resolve(cwd, 'violations.dev.cold.json'), + path.resolve(cwd, getViolationArtifactName('dev.cold')), JSON.stringify(coldViolations, null, 2), ) @@ -265,7 +268,7 @@ async function captureDevViolations(cwd: string): Promise { ) fs.writeFileSync( - path.resolve(cwd, 'violations.dev.warm.json'), + path.resolve(cwd, getViolationArtifactName('dev.warm')), JSON.stringify(warmViolations, null, 2), ) } @@ -275,19 +278,22 @@ export default async function globalSetup(config: FullConfig) { // This file lives in ./tests; fixture root is one directory up. const cwd = path.resolve(import.meta.dirname, '..') - // webServer.command writes build output to this file. - const logFile = path.resolve(cwd, 'webserver-build.log') + // The Nx build dependency writes build output to this file. + const logFile = path.resolve( + cwd, + process.env.E2E_BUILD_LOG ?? 'webserver-build.log', + ) if (!fs.existsSync(logFile)) { // If the log doesn't exist, leave an empty violations file. - fs.writeFileSync(path.resolve(cwd, 'violations.build.json'), '[]') + fs.writeFileSync(path.resolve(cwd, getViolationArtifactName('build')), '[]') return } const text = fs.readFileSync(logFile, 'utf-8') const violations = extractViolationsFromLog(text) fs.writeFileSync( - path.resolve(cwd, 'violations.build.json'), + path.resolve(cwd, getViolationArtifactName('build')), JSON.stringify(violations, null, 2), ) diff --git a/e2e/react-start/import-protection/tests/violations.utils.ts b/e2e/react-start/import-protection/tests/violations.utils.ts index c08a9b7a82..006c51543b 100644 --- a/e2e/react-start/import-protection/tests/violations.utils.ts +++ b/e2e/react-start/import-protection/tests/violations.utils.ts @@ -21,6 +21,15 @@ export type Violation = { message?: string } +export type ViolationArtifact = 'build' | 'dev' | 'dev.cold' | 'dev.warm' + +export function getViolationArtifactName(artifact: ViolationArtifact): string { + const modeKey = process.env.E2E_MODE_KEY + return modeKey + ? `violations.${modeKey}.${artifact}.json` + : `violations.${artifact}.json` +} + export function stripAnsi(input: string): string { // eslint-disable-next-line no-control-regex return input.replace(/\u001b\[[0-9;]*m/g, '') diff --git a/e2e/react-start/import-protection/vite.config.ts b/e2e/react-start/import-protection/vite.config.ts index 3163ded433..6a17f3a7cd 100644 --- a/e2e/react-start/import-protection/vite.config.ts +++ b/e2e/react-start/import-protection/vite.config.ts @@ -8,20 +8,43 @@ const outDir = process.env.E2E_DIST_DIR ?? 'dist' const startModeConfig = getStartModeConfig() const bundledDev = process.env.E2E_VITE_BUNDLED_DEV === 'true' -export default defineConfig({ - resolve: { tsconfigPaths: true }, - experimental: bundledDev ? { bundledDev: true } : undefined, - build: { - outDir, - }, - server: { - port: 3000, - }, - plugins: [tanstackStart(startModeConfig), viteReact()], - // react-tweet's package.json exports resolve to `index.client.js` which - // matches the default **/*.client.* deny pattern. Bundling it via - // noExternal must NOT trigger a false-positive import-protection violation. - ssr: { - noExternal: ['react-tweet'], - }, +const ISSUE_7725_DENIED_SPECIFIER = '../violations/issue-7725-server-fn-factory' + +export default defineConfig(({ command }) => { + const importProtection = + command === 'build' + ? { + ...startModeConfig.importProtection, + client: { + // Reproduce build-only issue #7725: resolving this imported + // server-fn builder must load import protection's mock virtual + // module, including its leading null-byte ID. + specifiers: [ISSUE_7725_DENIED_SPECIFIER], + }, + } + : startModeConfig.importProtection + + return { + resolve: { tsconfigPaths: true }, + experimental: bundledDev ? { bundledDev: true } : undefined, + build: { + outDir, + }, + server: { + port: 3000, + }, + plugins: [ + tanstackStart({ + ...startModeConfig, + importProtection, + }), + viteReact(), + ], + // react-tweet's package.json exports resolve to `index.client.js` which + // matches the default **/*.client.* deny pattern. Bundling it via + // noExternal must NOT trigger a false-positive import-protection violation. + ssr: { + noExternal: ['react-tweet'], + }, + } }) diff --git a/e2e/react-start/query-integration/package.json b/e2e/react-start/query-integration/package.json index 6a22be7871..4ad1839537 100644 --- a/e2e/react-start/query-integration/package.json +++ b/e2e/react-start/query-integration/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -32,7 +32,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/query-integration/src/routeTree.gen.ts b/e2e/react-start/query-integration/src/routeTree.gen.ts index 22c6f3293d..ee7ef43f26 100644 --- a/e2e/react-start/query-integration/src/routeTree.gen.ts +++ b/e2e/react-start/query-integration/src/routeTree.gen.ts @@ -9,14 +9,14 @@ // 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 UseSuspenseQueryRouteImport } from './routes/useSuspenseQuery' -import { Route as UseQueryRouteImport } from './routes/useQuery' import { Route as IndexRouteImport } from './routes/index' +import { Route as UseQueryRouteImport } from './routes/useQuery' +import { Route as UseSuspenseQueryRouteImport } from './routes/useSuspenseQuery' import { Route as LoaderFetchQueryTypeRouteImport } from './routes/loader-fetchQuery/$type' -const UseSuspenseQueryRoute = UseSuspenseQueryRouteImport.update({ - id: '/useSuspenseQuery', - path: '/useSuspenseQuery', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const UseQueryRoute = UseQueryRouteImport.update({ @@ -24,9 +24,9 @@ const UseQueryRoute = UseQueryRouteImport.update({ path: '/useQuery', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UseSuspenseQueryRoute = UseSuspenseQueryRouteImport.update({ + id: '/useSuspenseQuery', + path: '/useSuspenseQuery', getParentRoute: () => rootRouteImport, } as any) const LoaderFetchQueryTypeRoute = LoaderFetchQueryTypeRouteImport.update({ @@ -80,11 +80,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/useSuspenseQuery': { - id: '/useSuspenseQuery' - path: '/useSuspenseQuery' - fullPath: '/useSuspenseQuery' - preLoaderRoute: typeof UseSuspenseQueryRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/useQuery': { @@ -94,11 +94,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof UseQueryRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/useSuspenseQuery': { + id: '/useSuspenseQuery' + path: '/useSuspenseQuery' + fullPath: '/useSuspenseQuery' + preLoaderRoute: typeof UseSuspenseQueryRouteImport parentRoute: typeof rootRouteImport } '/loader-fetchQuery/$type': { diff --git a/e2e/react-start/rsbuild/.gitignore b/e2e/react-start/rsbuild/.gitignore new file mode 100644 index 0000000000..1593a8a0aa --- /dev/null +++ b/e2e/react-start/rsbuild/.gitignore @@ -0,0 +1,21 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +.output + +/build/ +/api/ +/server/build +/public/build +/dist/ +/dist-rsbuild-*/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +port-*.txt diff --git a/e2e/react-start/rsbuild/.prettierignore b/e2e/react-start/rsbuild/.prettierignore new file mode 100644 index 0000000000..69750fb197 --- /dev/null +++ b/e2e/react-start/rsbuild/.prettierignore @@ -0,0 +1 @@ +.prettierrc diff --git a/e2e/react-start/rsbuild/package.json b/e2e/react-start/rsbuild/package.json new file mode 100644 index 0000000000..d0b7566489 --- /dev/null +++ b/e2e/react-start/rsbuild/package.json @@ -0,0 +1,40 @@ +{ + "name": "tanstack-react-start-e2e-rsbuild", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "rsbuild dev", + "dev:e2e": "rsbuild dev --port $PORT", + "build": "rsbuild build && tsc --noEmit", + "start": "node server.js", + "test:e2e-full": "pnpm build && sh -c 'rm -f \"port-${E2E_PORT_KEY:-$npm_package_name}.txt\" \"port-${E2E_PORT_KEY:-$npm_package_name}-syntax-error-overlay.txt\"' && playwright test --project=chromium" + }, + "nx": { + "metadata": { + "playwrightModes": [ + { + "toolchain": "rsbuild", + "mode": "ssr" + } + ] + } + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/react-start": "workspace:^", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", + "@rsbuild/plugin-react": "^2.0.0", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/node": "^22.10.2", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "srvx": "^0.10.0", + "typescript": "^5.7.2" + } +} diff --git a/e2e/react-start/rsbuild/playwright.config.ts b/e2e/react-start/rsbuild/playwright.config.ts new file mode 100644 index 0000000000..6e8f176361 --- /dev/null +++ b/e2e/react-start/rsbuild/playwright.config.ts @@ -0,0 +1,29 @@ +import fs from 'node:fs' +import { defineConfig, devices } from '@playwright/test' +import packageJson from './package.json' with { type: 'json' } + +const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name + +if (process.env.TEST_WORKER_INDEX === undefined) { + for (const portFile of [ + `port-${e2ePortKey}.txt`, + `port-${e2ePortKey}-syntax-error-overlay.txt`, + ]) { + fs.rmSync(portFile, { force: true }) + } +} + +export default defineConfig({ + testDir: './tests', + workers: 1, + reporter: [['line']], + + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], +}) diff --git a/e2e/react-start/rsbuild/rsbuild.config.ts b/e2e/react-start/rsbuild/rsbuild.config.ts new file mode 100644 index 0000000000..5f8b31dc0b --- /dev/null +++ b/e2e/react-start/rsbuild/rsbuild.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' + +const outDir = process.env.E2E_DIST_DIR ?? 'dist' + +export default defineConfig({ + plugins: [pluginReact({ splitChunks: false }), tanstackStart()], + output: { + distPath: { + root: outDir, + }, + }, +}) diff --git a/e2e/react-start/rsbuild/server.js b/e2e/react-start/rsbuild/server.js new file mode 100644 index 0000000000..f3c49399e6 --- /dev/null +++ b/e2e/react-start/rsbuild/server.js @@ -0,0 +1,62 @@ +import fs from 'node:fs' +import path from 'node:path' +import { spawn } from 'node:child_process' +import { pathToFileURL } from 'node:url' + +const distDir = process.env.E2E_DIST_DIR || 'dist' + +function resolveDistClientDir() { + return path.resolve(distDir, 'client') +} + +function resolveDistServerEntryPath() { + const serverJsPath = path.resolve(distDir, 'server', 'server.js') + if (fs.existsSync(serverJsPath)) { + return serverJsPath + } + + const indexJsPath = path.resolve(distDir, 'server', 'index.js') + if (fs.existsSync(indexJsPath)) { + return indexJsPath + } + + return serverJsPath +} + +export function resolveStartCommand() { + const distClientDir = resolveDistClientDir() + const distServerEntryPath = resolveDistServerEntryPath() + return `srvx --prod -s ${JSON.stringify(distClientDir)} ${JSON.stringify(distServerEntryPath)}` +} + +export function start() { + const child = spawn( + 'srvx', + ['--prod', '-s', resolveDistClientDir(), resolveDistServerEntryPath()], + { + stdio: 'inherit', + shell: process.platform === 'win32', + }, + ) + + child.on('error', (error) => { + console.error('Failed to start srvx:', error) + process.exit(1) + }) + + child.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal) + return + } + + process.exit(code ?? 0) + }) +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + start() +} diff --git a/e2e/react-start/rsbuild/src/routeTree.gen.ts b/e2e/react-start/rsbuild/src/routeTree.gen.ts new file mode 100644 index 0000000000..dceedffdc1 --- /dev/null +++ b/e2e/react-start/rsbuild/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* 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' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +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 { + ssr: true + router: Awaited> + } +} diff --git a/e2e/react-start/rsbuild/src/router.tsx b/e2e/react-start/rsbuild/src/router.tsx new file mode 100644 index 0000000000..e59e199b48 --- /dev/null +++ b/e2e/react-start/rsbuild/src/router.tsx @@ -0,0 +1,10 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function getRouter() { + return createRouter({ + routeTree, + scrollRestoration: true, + defaultPreload: 'intent', + }) +} diff --git a/e2e/react-start/rsbuild/src/routes/__root.tsx b/e2e/react-start/rsbuild/src/routes/__root.tsx new file mode 100644 index 0000000000..c73ca2a11d --- /dev/null +++ b/e2e/react-start/rsbuild/src/routes/__root.tsx @@ -0,0 +1,37 @@ +import { + HeadContent, + Outlet, + Scripts, + createRootRoute, + useHydrated, +} from '@tanstack/react-router' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { title: 'Rsbuild test' }, + ], + }), + component: RootComponent, +}) + +function RootComponent() { + const hydrated = useHydrated() + + return ( + + + + + + + {hydrated ? 'hydrated' : 'hydrating'} + + + + + + ) +} diff --git a/e2e/react-start/rsbuild/src/routes/index.tsx b/e2e/react-start/rsbuild/src/routes/index.tsx new file mode 100644 index 0000000000..4b8cede36e --- /dev/null +++ b/e2e/react-start/rsbuild/src/routes/index.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
    +

    Rsbuild fixture

    +

    Error overlay fixture

    +
    + ) +} diff --git a/e2e/react-start/rsbuild/tests/error-overlay.spec.ts b/e2e/react-start/rsbuild/tests/error-overlay.spec.ts new file mode 100644 index 0000000000..5d6497f740 --- /dev/null +++ b/e2e/react-start/rsbuild/tests/error-overlay.spec.ts @@ -0,0 +1,401 @@ +import fs from 'node:fs' +import path from 'node:path' +import { spawn } from 'node:child_process' +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 e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name +const fixtureRoot = path.resolve(import.meta.dirname, '..') +const workspaceRoot = path.resolve(import.meta.dirname, '../../../..') +const errorOverlaySelector = 'rsbuild-error-overlay' +const errorOverlayTimeout = 30_000 +const syntaxErrorRoutePatch = '\nexport const broken = ;\n' +const devServerTimeout = 30_000 + +class DevServer { + readonly url: string + private stopPromise: Promise | null = null + private startupError: Error | null = null + private readonly startupErrorPromise: Promise + + private constructor( + readonly port: number, + private readonly childProcess: ReturnType, + ) { + this.url = `http://localhost:${port}` + this.startupErrorPromise = new Promise((_, reject) => { + this.childProcess.once('error', (error) => { + this.startupError = error + reject(this.createStartupError(error)) + }) + }) + this.startupErrorPromise.catch(() => {}) + } + + static async start(port: number): Promise { + const devServer = new DevServer( + port, + spawn('pnpm', ['exec', 'rsbuild', 'dev', '--port', String(port)], { + cwd: fixtureRoot, + env: { + ...process.env, + PORT: String(port), + VITE_SERVER_PORT: String(port), + VITE_NODE_ENV: 'test', + }, + stdio: 'ignore', + }), + ) + + try { + await devServer.waitUntilReady() + return devServer + } catch (error) { + await devServer.stop() + throw error + } + } + + async waitUntilReady(timeoutMs = devServerTimeout): Promise { + const startedAt = Date.now() + + while (Date.now() - startedAt <= timeoutMs) { + this.throwIfStartupFailed() + + if (this.childProcess.exitCode !== null) { + throw new Error( + `Dev server exited before responding: code=${this.childProcess.exitCode} signal=${this.childProcess.signalCode}`, + ) + } + + try { + const res = await this.awaitWithStartupFailure( + fetch(this.url, { + signal: AbortSignal.timeout(1000), + }), + ) + if (res.ok) { + return + } + } catch { + this.throwIfStartupFailed() + // ignore + } + + await this.awaitWithStartupFailure(this.delay(200)) + } + + throw new Error(`Timed out waiting for ${this.url}`) + } + + private async awaitWithStartupFailure(promise: Promise): Promise { + this.throwIfStartupFailed() + return await Promise.race([promise, this.startupErrorPromise]) + } + + private throwIfStartupFailed(): void { + if (this.startupError) { + throw this.createStartupError(this.startupError) + } + } + + private createStartupError(error: Error): Error { + return new Error('Dev server failed to start', { + cause: error, + }) + } + + private async delay(ms: number): Promise { + await new Promise((resolve) => setTimeout(resolve, ms)) + } + + async stop(): Promise { + if (this.childProcess.exitCode !== null || this.childProcess.killed) { + return + } + + this.stopPromise ??= new Promise((resolve) => { + let resolved = false + let forceKillTimer: ReturnType | null = null + let forceResolveTimer: ReturnType | null = null + + const done = () => { + if (resolved) { + return + } + resolved = true + + if (forceKillTimer) { + clearTimeout(forceKillTimer) + } + if (forceResolveTimer) { + clearTimeout(forceResolveTimer) + } + + this.childProcess.off('exit', done) + this.childProcess.off('error', done) + resolve() + } + + this.childProcess.once('exit', done) + this.childProcess.once('error', done) + + try { + this.childProcess.kill('SIGTERM') + } catch { + done() + return + } + + forceKillTimer = setTimeout(() => { + try { + this.childProcess.kill('SIGKILL') + } catch { + // ignore + } + + forceResolveTimer = setTimeout(done, 500) + }, 3000) + }) + + await this.stopPromise + } + + async [Symbol.asyncDispose](): Promise { + await this.stop() + } +} + +type ErrorOverlayIssue = { + title: string | null + description: string | null + runtime: 'client' | 'server' | null + sourceFrame: string | null + importTrace: Array +} + +const runtimeTraceEntries = [ + ['/default-entry/client.tsx', 'client'], + ['/default-entry/server.ts', 'server'], +] as const + +function normalizeErrorOverlayText(value: string): string { + return value + .replaceAll(fixtureRoot, '') + .replaceAll(workspaceRoot, '') + .replace( + /from [^)]+@rsbuild\/core\/dist\/transformLoader\.mjs/g, + 'from ', + ) + .replaceAll('\u00d7', 'x') + .replaceAll('\u2570\u2500\u25b6', '->') + .replaceAll('\u2026', '...') + .replaceAll('\u2192', '->') + .replace(/[ \t]+\n/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim() +} + +async function readErrorOverlay(page: Page): Promise<{ + title: string | null + content: string +}> { + const snapshot = await page + .locator(errorOverlaySelector) + .evaluate((element) => { + const root = (element as HTMLElement).shadowRoot ?? element + const titleElement = root.querySelector('.title') + const contentElement = root.querySelector('.content') + const titleText = titleElement ? titleElement.textContent : null + const contentText = contentElement + ? contentElement.textContent + : root.textContent + + return { + title: titleText === null ? null : titleText.trim(), + content: contentText, + } + }) + + return { + title: snapshot.title, + content: normalizeErrorOverlayText(snapshot.content), + } +} + +function getErrorRuntime(block: string): ErrorOverlayIssue['runtime'] { + return ( + runtimeTraceEntries.find(([traceEntry]) => + block.includes(traceEntry), + )?.[1] ?? null + ) +} + +function findTrimmedLine(lines: Array, text: string): string | null { + return lines.find((line) => line.includes(text))?.trim() ?? null +} + +function getSyntaxErrorDescription(lines: Array): string | null { + return ( + findTrimmedLine(lines, 'SyntaxError:') + ?.replace(/^.*SyntaxError:/, 'SyntaxError:') + .trim() ?? null + ) +} + +function getSourceFrame(lines: Array): string | null { + const sourceFrame = [ + lines[0]?.trim(), + findTrimmedLine(lines, 'Module build failed'), + findTrimmedLine(lines, 'SyntaxError:'), + ].filter(Boolean) + + return sourceFrame.length > 0 ? sourceFrame.join('\n') : null +} + +function getImportTrace(lines: Array): Array { + const traceStartIndex = lines.findIndex((line) => + line.startsWith('Import traces'), + ) + + if (traceStartIndex === -1) { + return [] + } + + return lines + .slice(traceStartIndex + 1) + .map((line) => line.trim()) + .filter(Boolean) +} + +function parseErrorOverlayIssue( + block: string, + title: string | null, +): ErrorOverlayIssue { + const lines = block.split('\n') + + return { + title, + description: getSyntaxErrorDescription(lines), + runtime: getErrorRuntime(block), + sourceFrame: getSourceFrame(lines), + importTrace: getImportTrace(lines), + } +} + +async function captureErrorOverlaySnapshot( + page: Page, +): Promise> { + const { title, content } = await readErrorOverlay(page) + const blocks = content.split(/\n(?=File: )/).filter(Boolean) + + return blocks.map((block) => parseErrorOverlayIssue(block, title)) +} + +function formatErrorOverlaySnapshot( + snapshot: Array, +): string { + return `${JSON.stringify(snapshot, null, 2)}\n` +} + +function dedentSnapshot(snapshot: string): string { + const lines = snapshot + .replace(/^\n/, '') + .replace(/\n\s*$/, '') + .split('\n') + const indent = Math.min( + ...lines + .filter((line) => line.trim() !== '') + .map((line) => line.match(/^ */)?.[0].length ?? 0), + ) + + return `${lines.map((line) => line.slice(indent)).join('\n')}\n` +} + +async function toDisplayRedbox( + page: Page, + expectedSnapshot: string, +): Promise { + await expect(page.locator(errorOverlaySelector)).toBeAttached({ + timeout: errorOverlayTimeout, + }) + + const normalizedExpectedSnapshot = dedentSnapshot(expectedSnapshot) + + await expect + .poll( + async () => { + return formatErrorOverlaySnapshot( + await captureErrorOverlaySnapshot(page), + ) + }, + { timeout: errorOverlayTimeout }, + ) + .toBe(normalizedExpectedSnapshot) +} + +test.use({ + whitelistErrors: [ + 'Build failed', + 'Module build failed', + 'SyntaxError', + 'Syntax Error', + 'Expression expected', + ], +}) + +test('shows a module syntax error through the error overlay', async ({ + page, +}) => { + const homeRouteFile = path.resolve(fixtureRoot, 'src/routes/index.tsx') + const originalSource = await fs.promises.readFile(homeRouteFile, 'utf-8') + const port = await getTestServerPort(`${e2ePortKey}-syntax-error-overlay`) + await using devServer = await DevServer.start(port) + + try { + await page.goto(devServer.url, { waitUntil: 'domcontentloaded' }) + + await fs.promises.writeFile( + homeRouteFile, + `${originalSource}${syntaxErrorRoutePatch}`, + ) + + await toDisplayRedbox( + page, + ` + [ + { + "title": "Build failed", + "description": "SyntaxError: Unexpected token (16:22)", + "runtime": "client", + "sourceFrame": "File: ./src/routes/index.tsx:1:1\\nx Module build failed (from ):\\n-> x SyntaxError: Unexpected token (16:22)", + "importTrace": [ + "../../../packages/react-start/dist/plugin/default-entry/client.tsx", + "../../../packages/react-start/dist/esm/client.js", + "... (5 hidden)", + "./src/routeTree.gen.ts", + "./src/routes/index.tsx x" + ] + }, + { + "title": "Build failed", + "description": "SyntaxError: Unexpected token (16:22)", + "runtime": "server", + "sourceFrame": "File: ./src/routes/index.tsx:1:1\\nx Module build failed (from ):\\n-> x SyntaxError: Unexpected token (16:22)", + "importTrace": [ + "../../../packages/react-start/dist/plugin/default-entry/server.ts", + "../../../packages/react-start/dist/esm/server.js", + "... (4 hidden)", + "./src/routeTree.gen.ts", + "./src/routes/index.tsx x" + ] + } + ] + `, + ) + } finally { + await fs.promises.writeFile(homeRouteFile, originalSource) + } +}) diff --git a/e2e/react-start/rsbuild/tsconfig.json b/e2e/react-start/rsbuild/tsconfig.json new file mode 100644 index 0000000000..cef9369516 --- /dev/null +++ b/e2e/react-start/rsbuild/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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, + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +} diff --git a/e2e/react-start/rsc-deferred-hydration/package.json b/e2e/react-start/rsc-deferred-hydration/package.json index e14d919490..434cabb24b 100644 --- a/e2e/react-start/rsc-deferred-hydration/package.json +++ b/e2e/react-start/rsc-deferred-hydration/package.json @@ -31,15 +31,16 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "@vitejs/plugin-rsc": "^0.5.20", + "@vitejs/plugin-rsc": "^0.5.30", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/rsc-deferred-hydration/src/routeTree.gen.ts b/e2e/react-start/rsc-deferred-hydration/src/routeTree.gen.ts index 246737b515..f2a58f4f6f 100644 --- a/e2e/react-start/rsc-deferred-hydration/src/routeTree.gen.ts +++ b/e2e/react-start/rsc-deferred-hydration/src/routeTree.gen.ts @@ -9,19 +9,14 @@ // 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 ServerClientRouteImport } from './routes/server-client' -import { Route as CssRouteImport } from './routes/css' -import { Route as CompositeRouteImport } from './routes/composite' import { Route as IndexRouteImport } from './routes/index' +import { Route as CompositeRouteImport } from './routes/composite' +import { Route as CssRouteImport } from './routes/css' +import { Route as ServerClientRouteImport } from './routes/server-client' -const ServerClientRoute = ServerClientRouteImport.update({ - id: '/server-client', - path: '/server-client', - getParentRoute: () => rootRouteImport, -} as any) -const CssRoute = CssRouteImport.update({ - id: '/css', - path: '/css', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const CompositeRoute = CompositeRouteImport.update({ @@ -29,9 +24,14 @@ const CompositeRoute = CompositeRouteImport.update({ path: '/composite', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const CssRoute = CssRouteImport.update({ + id: '/css', + path: '/css', + getParentRoute: () => rootRouteImport, +} as any) +const ServerClientRoute = ServerClientRouteImport.update({ + id: '/server-client', + path: '/server-client', getParentRoute: () => rootRouteImport, } as any) @@ -71,18 +71,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/server-client': { - id: '/server-client' - path: '/server-client' - fullPath: '/server-client' - preLoaderRoute: typeof ServerClientRouteImport - parentRoute: typeof rootRouteImport - } - '/css': { - id: '/css' - path: '/css' - fullPath: '/css' - preLoaderRoute: typeof CssRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/composite': { @@ -92,11 +85,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CompositeRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/css': { + id: '/css' + path: '/css' + fullPath: '/css' + preLoaderRoute: typeof CssRouteImport + parentRoute: typeof rootRouteImport + } + '/server-client': { + id: '/server-client' + path: '/server-client' + fullPath: '/server-client' + preLoaderRoute: typeof ServerClientRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/rsc-query/package.json b/e2e/react-start/rsc-query/package.json index 6049fa77a9..24c7b488f6 100644 --- a/e2e/react-start/rsc-query/package.json +++ b/e2e/react-start/rsc-query/package.json @@ -22,13 +22,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "@vitejs/plugin-rsc": "^0.5.20", + "@vitejs/plugin-rsc": "^0.5.30", "srvx": "^0.10.0", "typescript": "^5.7.2", "vite": "^8.0.14" diff --git a/e2e/react-start/rsc-rsbuild/package.json b/e2e/react-start/rsc-rsbuild/package.json index 6dc5f5f5a1..e3800f291a 100644 --- a/e2e/react-start/rsc-rsbuild/package.json +++ b/e2e/react-start/rsc-rsbuild/package.json @@ -27,8 +27,8 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.8", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", diff --git a/e2e/react-start/rsc-rsbuild/src/env.d.ts b/e2e/react-start/rsc-rsbuild/src/env.d.ts new file mode 100644 index 0000000000..8993929b40 --- /dev/null +++ b/e2e/react-start/rsc-rsbuild/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.css' +declare module '*.css?url' { + const url: string + export default url +} diff --git a/e2e/react-start/rsc-rsbuild/src/node_modules/rsc-client-pkg/index.js b/e2e/react-start/rsc-rsbuild/src/node_modules/rsc-client-pkg/index.js index 75080ca08a..066938631b 100644 --- a/e2e/react-start/rsc-rsbuild/src/node_modules/rsc-client-pkg/index.js +++ b/e2e/react-start/rsc-rsbuild/src/node_modules/rsc-client-pkg/index.js @@ -2,6 +2,24 @@ import * as React from 'react' +const buttonStyle = { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + minHeight: '40px', + padding: '0 16px', + backgroundColor: '#16a34a', + border: '0', + borderRadius: '6px', + boxShadow: '0 1px 2px rgba(15, 23, 42, 0.16)', + color: 'white', + cursor: 'pointer', + fontFamily: + '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + fontSize: '13px', + fontWeight: 700, +} + export function NodeModuleClientWidget(props) { const [count, setCount] = React.useState(0) @@ -10,6 +28,7 @@ export function NodeModuleClientWidget(props) { { type: 'button', 'data-testid': 'node-module-client-widget', + style: buttonStyle, onClick: () => setCount((value) => value + 1), }, `${props.label}: ${count}`, diff --git a/e2e/react-start/rsc-rsbuild/src/routeTree.gen.ts b/e2e/react-start/rsc-rsbuild/src/routeTree.gen.ts index 84c964d11b..393ffe98a9 100644 --- a/e2e/react-start/rsc-rsbuild/src/routeTree.gen.ts +++ b/e2e/react-start/rsc-rsbuild/src/routeTree.gen.ts @@ -9,55 +9,58 @@ // 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 RscNodeModuleClientRouteImport } from './routes/rsc-node-module-client' import { Route as IndexRouteImport } from './routes/index' +import { Route as RscCssUrlRouteImport } from './routes/rsc-css-url' +import { Route as RscNodeModuleClientRouteImport } from './routes/rsc-node-module-client' -const RscNodeModuleClientRoute = RscNodeModuleClientRouteImport.update({ - id: '/rsc-node-module-client', - path: '/rsc-node-module-client', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const RscCssUrlRoute = RscCssUrlRouteImport.update({ + id: '/rsc-css-url', + path: '/rsc-css-url', + getParentRoute: () => rootRouteImport, +} as any) +const RscNodeModuleClientRoute = RscNodeModuleClientRouteImport.update({ + id: '/rsc-node-module-client', + path: '/rsc-node-module-client', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/rsc-css-url': typeof RscCssUrlRoute '/rsc-node-module-client': typeof RscNodeModuleClientRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/rsc-css-url': typeof RscCssUrlRoute '/rsc-node-module-client': typeof RscNodeModuleClientRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/rsc-css-url': typeof RscCssUrlRoute '/rsc-node-module-client': typeof RscNodeModuleClientRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/rsc-node-module-client' + fullPaths: '/' | '/rsc-css-url' | '/rsc-node-module-client' fileRoutesByTo: FileRoutesByTo - to: '/' | '/rsc-node-module-client' - id: '__root__' | '/' | '/rsc-node-module-client' + to: '/' | '/rsc-css-url' | '/rsc-node-module-client' + id: '__root__' | '/' | '/rsc-css-url' | '/rsc-node-module-client' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + RscCssUrlRoute: typeof RscCssUrlRoute RscNodeModuleClientRoute: typeof RscNodeModuleClientRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/rsc-node-module-client': { - id: '/rsc-node-module-client' - path: '/rsc-node-module-client' - fullPath: '/rsc-node-module-client' - preLoaderRoute: typeof RscNodeModuleClientRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,11 +68,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/rsc-css-url': { + id: '/rsc-css-url' + path: '/rsc-css-url' + fullPath: '/rsc-css-url' + preLoaderRoute: typeof RscCssUrlRouteImport + parentRoute: typeof rootRouteImport + } + '/rsc-node-module-client': { + id: '/rsc-node-module-client' + path: '/rsc-node-module-client' + fullPath: '/rsc-node-module-client' + preLoaderRoute: typeof RscNodeModuleClientRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + RscCssUrlRoute: RscCssUrlRoute, RscNodeModuleClientRoute: RscNodeModuleClientRoute, } export const routeTree = rootRouteImport diff --git a/e2e/react-start/rsc-rsbuild/src/routes/index.tsx b/e2e/react-start/rsc-rsbuild/src/routes/index.tsx index d66168b65d..0e2f7f455c 100644 --- a/e2e/react-start/rsc-rsbuild/src/routes/index.tsx +++ b/e2e/react-start/rsc-rsbuild/src/routes/index.tsx @@ -1,14 +1,162 @@ -import { Link, createFileRoute } from '@tanstack/react-router' +import { createFileRoute, Link, linkOptions } from '@tanstack/react-router' +import type { CSSProperties } from 'react' export const Route = createFileRoute('/')({ component: Home, }) +const examples = linkOptions([ + { + to: '/rsc-node-module-client', + title: 'Node module client component', + description: + 'Hydrates a client component imported from node_modules inside an RSC route.', + marker: 'RSC', + markerColor: '#0284c7', + }, + { + to: '/rsc-css-url', + title: 'css?url stylesheet', + description: + 'Applies a stylesheet imported with the ?url query from route head metadata.', + marker: 'CSS', + markerColor: '#16a34a', + }, +]) + +const colors = { + server: '#0284c7', + client: '#16a34a', + async: '#f59e0b', +} + +const styles = { + page: { + maxWidth: '800px', + padding: '20px', + fontFamily: + '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + }, + title: { + margin: '0 0 8px 0', + color: '#1e293b', + fontSize: '24px', + }, + description: { + color: '#64748b', + lineHeight: '1.5', + marginBottom: '20px', + }, + legend: { + display: 'flex', + gap: '16px', + marginBottom: '20px', + padding: '12px', + backgroundColor: '#f8fafc', + borderRadius: '8px', + flexWrap: 'wrap', + }, + legendItem: { + display: 'flex', + alignItems: 'center', + gap: '6px', + }, + legendText: { + color: '#475569', + fontSize: '13px', + }, + legendColor: { + width: '16px', + height: '16px', + borderRadius: '4px', + }, + grid: { + display: 'grid', + gap: '16px', + gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', + }, + card: { + display: 'block', + padding: '16px', + backgroundColor: '#f8fafc', + borderRadius: '8px', + border: '1px solid #e2e8f0', + textDecoration: 'none', + }, + marker: { + display: 'grid', + placeItems: 'center', + width: '40px', + height: '32px', + marginBottom: '8px', + borderRadius: '6px', + color: 'white', + fontSize: '13px', + fontWeight: 'bold', + }, + cardTitle: { + marginBottom: '4px', + color: '#0f172a', + fontWeight: 'bold', + }, + cardDescription: { + color: '#64748b', + fontSize: '13px', + lineHeight: '1.4', + }, +} satisfies Record + +function legendColor(color: string): CSSProperties { + return { + ...styles.legendColor, + backgroundColor: color, + } +} + +function markerStyle(color: string): CSSProperties { + return { + ...styles.marker, + backgroundColor: color, + } +} + function Home() { return ( -
    -

    Rsbuild RSC fixture

    - Node module client component +
    +

    + React Server Components Rsbuild E2E Tests +

    +

    + These examples cover Rsbuild-specific RSC behavior with clear visual + distinction between server-rendered content and client-side assets. +

    + +
    +
    + + Server Rendered (RSC) +
    +
    + + Client Hydration and Assets +
    +
    + + Rsbuild SSR Coverage +
    +
    + +
    ) } diff --git a/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.css b/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.css new file mode 100644 index 0000000000..c6b47196e6 --- /dev/null +++ b/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.css @@ -0,0 +1,30 @@ +.rsc-css-url-card { + display: grid; + gap: 12px; + max-width: 480px; + padding: 18px; + background-color: #ecfdf5; + border: 2px solid #10b981; + border-radius: 8px; + color: #064e3b; +} + +.rsc-css-url-badge { + width: max-content; + padding: 3px 8px; + background-color: #047857; + border-radius: 4px; + color: #fff; + font-size: 11px; + font-weight: 700; +} + +.rsc-css-url-title { + margin: 0; + font-size: 18px; +} + +.rsc-css-url-text { + margin: 0; + line-height: 1.5; +} diff --git a/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.tsx b/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.tsx new file mode 100644 index 0000000000..563e2fb46c --- /dev/null +++ b/e2e/react-start/rsc-rsbuild/src/routes/rsc-css-url.tsx @@ -0,0 +1,24 @@ +import { createFileRoute } from '@tanstack/react-router' +import cssUrl from './rsc-css-url.css?url' + +export const Route = createFileRoute('/rsc-css-url')({ + head: () => ({ + links: [{ rel: 'stylesheet', href: cssUrl }], + }), + component: RscCssUrlComponent, +}) + +function RscCssUrlComponent() { + return ( +
    +

    RSC css?url stylesheet

    +
    + CSS URL +

    Stylesheet imported with ?url

    +

    + The SSR head should point at a client-served asset URL. +

    +
    +
    + ) +} diff --git a/e2e/react-start/rsc-rsbuild/src/routes/rsc-node-module-client.tsx b/e2e/react-start/rsc-rsbuild/src/routes/rsc-node-module-client.tsx index 00994ffb50..1bf4e7760b 100644 --- a/e2e/react-start/rsc-rsbuild/src/routes/rsc-node-module-client.tsx +++ b/e2e/react-start/rsc-rsbuild/src/routes/rsc-node-module-client.tsx @@ -1,4 +1,5 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, Link } from '@tanstack/react-router' +import type { CSSProperties } from 'react' import { getNodeModuleClientServerComponent } from '~/utils/nodeModuleClientServerComponent' export const Route = createFileRoute('/rsc-node-module-client')({ @@ -9,14 +10,86 @@ export const Route = createFileRoute('/rsc-node-module-client')({ component: RscNodeModuleClientComponent, }) +const styles = { + page: { + maxWidth: '800px', + padding: '20px', + fontFamily: + '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + }, + backLink: { + display: 'inline-block', + marginBottom: '16px', + color: '#0284c7', + fontSize: '13px', + fontWeight: 'bold', + textDecoration: 'none', + }, + title: { + margin: '0 0 8px 0', + color: '#1e293b', + fontSize: '24px', + }, + description: { + maxWidth: '680px', + margin: '0 0 20px 0', + color: '#64748b', + lineHeight: '1.5', + }, + summary: { + display: 'grid', + gap: '12px', + gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', + marginBottom: '20px', + }, + summaryItem: { + padding: '12px', + backgroundColor: '#f8fafc', + border: '1px solid #e2e8f0', + borderRadius: '8px', + }, + summaryLabel: { + marginBottom: '4px', + color: '#475569', + fontSize: '12px', + fontWeight: 'bold', + }, + summaryText: { + color: '#0f172a', + fontSize: '13px', + lineHeight: '1.4', + }, +} satisfies Record + function RscNodeModuleClientComponent() { const { Server } = Route.useLoaderData() return ( -
    -

    +
    + + Back to examples + +

    RSC node_modules client component

    +

    + This route renders a server component that imports a client component + from a package-style module and hydrates it on the client. +

    +
    +
    +
    Server source
    +
    + Route loader + RSC server function +
    +
    +
    +
    Client source
    +
    + Component exported from node_modules +
    +
    +
    {Server}
    ) diff --git a/e2e/react-start/rsc-rsbuild/src/utils/RscClientPkgContent.tsx b/e2e/react-start/rsc-rsbuild/src/utils/RscClientPkgContent.tsx index 742f6cbc1a..1b22dd9afe 100644 --- a/e2e/react-start/rsc-rsbuild/src/utils/RscClientPkgContent.tsx +++ b/e2e/react-start/rsc-rsbuild/src/utils/RscClientPkgContent.tsx @@ -1,12 +1,82 @@ import { NodeModuleClientWidget } from 'rsc-client-pkg' +import type { CSSProperties } from 'react' + +const styles = { + container: { + padding: '16px', + backgroundColor: '#e0f2fe', + border: '2px solid #0284c7', + borderRadius: '8px', + }, + header: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '12px', + marginBottom: '12px', + }, + badge: { + display: 'inline-block', + padding: '2px 8px', + backgroundColor: '#0284c7', + borderRadius: '4px', + color: 'white', + fontSize: '11px', + fontWeight: 'bold', + }, + meta: { + color: '#64748b', + fontSize: '12px', + }, + title: { + margin: '0 0 8px 0', + color: '#0c4a6e', + fontSize: '18px', + }, + description: { + margin: '0 0 16px 0', + color: '#0369a1', + fontSize: '13px', + lineHeight: '1.5', + }, + clientSlot: { + padding: '14px', + backgroundColor: '#dcfce7', + border: '2px solid #16a34a', + borderRadius: '8px', + }, + clientLabel: { + marginBottom: '10px', + color: '#166534', + fontSize: '11px', + fontWeight: 'bold', + }, +} satisfies Record export function RscClientPkgContent() { return ( -
    -

    +
    +
    + SERVER + Rendered before hydration +
    +

    Server rendered package boundary

    - +

    + The server component owns this blue boundary, then hands the interactive + island below to a client component exported from a module package. +

    +
    +
    CLIENT COMPONENT FROM NODE_MODULES
    + +
    ) } diff --git a/e2e/react-start/rsc-rsbuild/tests/rsc-css-url.spec.ts b/e2e/react-start/rsc-rsbuild/tests/rsc-css-url.spec.ts new file mode 100644 index 0000000000..377c4e6011 --- /dev/null +++ b/e2e/react-start/rsc-rsbuild/tests/rsc-css-url.spec.ts @@ -0,0 +1,15 @@ +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' + +test('applies styles imported with css?url during SSR', async ({ page }) => { + const response = await page.goto('/rsc-css-url') + expect(response?.status()).toBe(200) + + const card = page.getByTestId('rsc-css-url-card') + await expect(card).toBeVisible() + + const backgroundColor = await card.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(236, 253, 245)') +}) diff --git a/e2e/react-start/rsc/package.json b/e2e/react-start/rsc/package.json index 36f822e660..0e7414e2aa 100644 --- a/e2e/react-start/rsc/package.json +++ b/e2e/react-start/rsc/package.json @@ -39,8 +39,8 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tanstack/eslint-plugin-start": "workspace:^", "@tanstack/router-e2e-utils": "workspace:^", @@ -49,7 +49,7 @@ "@types/react-dom": "^19.0.3", "@typescript-eslint/parser": "^8.23.0", "@vitejs/plugin-react": "^6.0.1", - "@vitejs/plugin-rsc": "^0.5.20", + "@vitejs/plugin-rsc": "^0.5.30", "eslint": "^9.22.0", "srvx": "^0.10.0", "typescript": "^5.7.2", diff --git a/e2e/react-start/rsc/src/routeTree.gen.ts b/e2e/react-start/rsc/src/routeTree.gen.ts index 015d3edf82..38137dfb4b 100644 --- a/e2e/react-start/rsc/src/routeTree.gen.ts +++ b/e2e/react-start/rsc/src/routeTree.gen.ts @@ -9,157 +9,153 @@ // 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 RscUseServerFnRouteImport } from './routes/rsc-use-server-fn' -import { Route as RscTreeRouteImport } from './routes/rsc-tree' -import { Route as RscSuspenseRouteImport } from './routes/rsc-suspense' -import { Route as RscStreamingRouteImport } from './routes/rsc-streaming' -import { Route as RscStreamReadableRouteImport } from './routes/rsc-stream-readable' -import { Route as RscStreamLoaderRouteImport } from './routes/rsc-stream-loader' -import { Route as RscStreamGeneratorRouteImport } from './routes/rsc-stream-generator' -import { Route as RscSsrFalseRouteImport } from './routes/rsc-ssr-false' -import { Route as RscSsrDataOnlyRouteImport } from './routes/rsc-ssr-data-only' -import { Route as RscSlotsRouteImport } from './routes/rsc-slots' -import { Route as RscSlotJsxArgsRouteImport } from './routes/rsc-slot-jsx-args' -import { Route as RscServerRedirectRouteImport } from './routes/rsc-server-redirect' -import { Route as RscServerNotFoundRouteImport } from './routes/rsc-server-not-found' -import { Route as RscRequestHeadersRouteImport } from './routes/rsc-request-headers' -import { Route as RscReactCacheRouteImport } from './routes/rsc-react-cache' -import { Route as RscParallelRouteImport } from './routes/rsc-parallel' -import { Route as RscNestedStructureRouteImport } from './routes/rsc-nested-structure' -import { Route as RscNestedRouteImport } from './routes/rsc-nested' -import { Route as RscMultiRouteImport } from './routes/rsc-multi' -import { Route as RscLinkRouteImport } from './routes/rsc-link' -import { Route as RscLargeRouteImport } from './routes/rsc-large' -import { Route as RscInvalidationRouteImport } from './routes/rsc-invalidation' -import { Route as RscHydrationRouteImport } from './routes/rsc-hydration' -import { Route as RscGlobalCssRouteImport } from './routes/rsc-global-css' -import { Route as RscFormsRouteImport } from './routes/rsc-forms' -import { Route as RscFlightApiRouteImport } from './routes/rsc-flight-api' -import { Route as RscExternalRouteImport } from './routes/rsc-external' -import { Route as RscErrorRouteImport } from './routes/rsc-error' -import { Route as RscDirectLoaderRouteImport } from './routes/rsc-direct-loader' -import { Route as RscDeferredComponentRouteImport } from './routes/rsc-deferred-component' -import { Route as RscDeferredRouteImport } from './routes/rsc-deferred' -import { Route as RscCssPreloadComplexRouteImport } from './routes/rsc-css-preload-complex' -import { Route as RscCssModulesRouteImport } from './routes/rsc-css-modules' -import { Route as RscCssAutoinjectRouteImport } from './routes/rsc-css-autoinject' -import { Route as RscContextRouteImport } from './routes/rsc-context' -import { Route as RscComponentSlotRouteImport } from './routes/rsc-component-slot' -import { Route as RscClientPreloadRouteImport } from './routes/rsc-client-preload' -import { Route as RscCachingRouteImport } from './routes/rsc-caching' -import { Route as RscBundleRouteImport } from './routes/rsc-bundle' -import { Route as RscBasicRouteImport } from './routes/rsc-basic' -import { Route as RscAsyncBundleRouteImport } from './routes/rsc-async-bundle' import { Route as IndexRouteImport } from './routes/index' -import { Route as RscParamIndexRouteImport } from './routes/rsc-param/index' +import { Route as RscAsyncBundleRouteImport } from './routes/rsc-async-bundle' +import { Route as RscBasicRouteImport } from './routes/rsc-basic' +import { Route as RscBundleRouteImport } from './routes/rsc-bundle' +import { Route as RscCachingRouteImport } from './routes/rsc-caching' +import { Route as RscClientPreloadRouteImport } from './routes/rsc-client-preload' +import { Route as RscComponentSlotRouteImport } from './routes/rsc-component-slot' +import { Route as RscContextRouteImport } from './routes/rsc-context' +import { Route as RscCssAutoinjectRouteImport } from './routes/rsc-css-autoinject' +import { Route as RscCssModulesRouteImport } from './routes/rsc-css-modules' +import { Route as RscCssPreloadComplexRouteImport } from './routes/rsc-css-preload-complex' +import { Route as RscDeferredRouteImport } from './routes/rsc-deferred' +import { Route as RscDeferredComponentRouteImport } from './routes/rsc-deferred-component' +import { Route as RscDirectLoaderRouteImport } from './routes/rsc-direct-loader' +import { Route as RscErrorRouteImport } from './routes/rsc-error' +import { Route as RscExternalRouteImport } from './routes/rsc-external' +import { Route as RscFlightApiRouteImport } from './routes/rsc-flight-api' +import { Route as RscFormsRouteImport } from './routes/rsc-forms' +import { Route as RscGlobalCssRouteImport } from './routes/rsc-global-css' +import { Route as RscHydrationRouteImport } from './routes/rsc-hydration' +import { Route as RscInvalidationRouteImport } from './routes/rsc-invalidation' +import { Route as RscLargeRouteImport } from './routes/rsc-large' +import { Route as RscLinkRouteImport } from './routes/rsc-link' +import { Route as RscMultiRouteImport } from './routes/rsc-multi' +import { Route as RscNestedRouteImport } from './routes/rsc-nested' +import { Route as RscNestedStructureRouteImport } from './routes/rsc-nested-structure' +import { Route as RscParallelRouteImport } from './routes/rsc-parallel' +import { Route as RscReactCacheRouteImport } from './routes/rsc-react-cache' +import { Route as RscRequestHeadersRouteImport } from './routes/rsc-request-headers' +import { Route as RscServerNotFoundRouteImport } from './routes/rsc-server-not-found' +import { Route as RscServerRedirectRouteImport } from './routes/rsc-server-redirect' +import { Route as RscSlotJsxArgsRouteImport } from './routes/rsc-slot-jsx-args' +import { Route as RscSlotsRouteImport } from './routes/rsc-slots' +import { Route as RscSsrDataOnlyRouteImport } from './routes/rsc-ssr-data-only' +import { Route as RscSsrFalseRouteImport } from './routes/rsc-ssr-false' +import { Route as RscStreamGeneratorRouteImport } from './routes/rsc-stream-generator' +import { Route as RscStreamLoaderRouteImport } from './routes/rsc-stream-loader' +import { Route as RscStreamReadableRouteImport } from './routes/rsc-stream-readable' +import { Route as RscStreamingRouteImport } from './routes/rsc-streaming' +import { Route as RscSuspenseRouteImport } from './routes/rsc-suspense' +import { Route as RscTreeRouteImport } from './routes/rsc-tree' +import { Route as RscUseServerFnRouteImport } from './routes/rsc-use-server-fn' +import { Route as ApiRscBuildBoundaryRouteImport } from './routes/api.rsc-build-boundary' +import { Route as ApiRscFlightRouteImport } from './routes/api.rsc-flight' import { Route as RscCssConditionalIndexRouteImport } from './routes/rsc-css-conditional.index' -import { Route as RscParamIdRouteImport } from './routes/rsc-param/$id' import { Route as RscCssConditionalBranchRouteImport } from './routes/rsc-css-conditional.$branch' -import { Route as ApiRscFlightRouteImport } from './routes/api.rsc-flight' +import { Route as RscParamIndexRouteImport } from './routes/rsc-param/index' +import { Route as RscParamIdRouteImport } from './routes/rsc-param/$id' -const RscUseServerFnRoute = RscUseServerFnRouteImport.update({ - id: '/rsc-use-server-fn', - path: '/rsc-use-server-fn', - getParentRoute: () => rootRouteImport, -} as any) -const RscTreeRoute = RscTreeRouteImport.update({ - id: '/rsc-tree', - path: '/rsc-tree', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RscSuspenseRoute = RscSuspenseRouteImport.update({ - id: '/rsc-suspense', - path: '/rsc-suspense', +const RscAsyncBundleRoute = RscAsyncBundleRouteImport.update({ + id: '/rsc-async-bundle', + path: '/rsc-async-bundle', getParentRoute: () => rootRouteImport, } as any) -const RscStreamingRoute = RscStreamingRouteImport.update({ - id: '/rsc-streaming', - path: '/rsc-streaming', +const RscBasicRoute = RscBasicRouteImport.update({ + id: '/rsc-basic', + path: '/rsc-basic', getParentRoute: () => rootRouteImport, } as any) -const RscStreamReadableRoute = RscStreamReadableRouteImport.update({ - id: '/rsc-stream-readable', - path: '/rsc-stream-readable', +const RscBundleRoute = RscBundleRouteImport.update({ + id: '/rsc-bundle', + path: '/rsc-bundle', getParentRoute: () => rootRouteImport, } as any) -const RscStreamLoaderRoute = RscStreamLoaderRouteImport.update({ - id: '/rsc-stream-loader', - path: '/rsc-stream-loader', +const RscCachingRoute = RscCachingRouteImport.update({ + id: '/rsc-caching', + path: '/rsc-caching', getParentRoute: () => rootRouteImport, } as any) -const RscStreamGeneratorRoute = RscStreamGeneratorRouteImport.update({ - id: '/rsc-stream-generator', - path: '/rsc-stream-generator', +const RscClientPreloadRoute = RscClientPreloadRouteImport.update({ + id: '/rsc-client-preload', + path: '/rsc-client-preload', getParentRoute: () => rootRouteImport, } as any) -const RscSsrFalseRoute = RscSsrFalseRouteImport.update({ - id: '/rsc-ssr-false', - path: '/rsc-ssr-false', +const RscComponentSlotRoute = RscComponentSlotRouteImport.update({ + id: '/rsc-component-slot', + path: '/rsc-component-slot', getParentRoute: () => rootRouteImport, } as any) -const RscSsrDataOnlyRoute = RscSsrDataOnlyRouteImport.update({ - id: '/rsc-ssr-data-only', - path: '/rsc-ssr-data-only', +const RscContextRoute = RscContextRouteImport.update({ + id: '/rsc-context', + path: '/rsc-context', getParentRoute: () => rootRouteImport, } as any) -const RscSlotsRoute = RscSlotsRouteImport.update({ - id: '/rsc-slots', - path: '/rsc-slots', +const RscCssAutoinjectRoute = RscCssAutoinjectRouteImport.update({ + id: '/rsc-css-autoinject', + path: '/rsc-css-autoinject', getParentRoute: () => rootRouteImport, } as any) -const RscSlotJsxArgsRoute = RscSlotJsxArgsRouteImport.update({ - id: '/rsc-slot-jsx-args', - path: '/rsc-slot-jsx-args', +const RscCssModulesRoute = RscCssModulesRouteImport.update({ + id: '/rsc-css-modules', + path: '/rsc-css-modules', getParentRoute: () => rootRouteImport, } as any) -const RscServerRedirectRoute = RscServerRedirectRouteImport.update({ - id: '/rsc-server-redirect', - path: '/rsc-server-redirect', +const RscCssPreloadComplexRoute = RscCssPreloadComplexRouteImport.update({ + id: '/rsc-css-preload-complex', + path: '/rsc-css-preload-complex', getParentRoute: () => rootRouteImport, } as any) -const RscServerNotFoundRoute = RscServerNotFoundRouteImport.update({ - id: '/rsc-server-not-found', - path: '/rsc-server-not-found', +const RscDeferredRoute = RscDeferredRouteImport.update({ + id: '/rsc-deferred', + path: '/rsc-deferred', getParentRoute: () => rootRouteImport, } as any) -const RscRequestHeadersRoute = RscRequestHeadersRouteImport.update({ - id: '/rsc-request-headers', - path: '/rsc-request-headers', +const RscDeferredComponentRoute = RscDeferredComponentRouteImport.update({ + id: '/rsc-deferred-component', + path: '/rsc-deferred-component', getParentRoute: () => rootRouteImport, } as any) -const RscReactCacheRoute = RscReactCacheRouteImport.update({ - id: '/rsc-react-cache', - path: '/rsc-react-cache', +const RscDirectLoaderRoute = RscDirectLoaderRouteImport.update({ + id: '/rsc-direct-loader', + path: '/rsc-direct-loader', getParentRoute: () => rootRouteImport, } as any) -const RscParallelRoute = RscParallelRouteImport.update({ - id: '/rsc-parallel', - path: '/rsc-parallel', +const RscErrorRoute = RscErrorRouteImport.update({ + id: '/rsc-error', + path: '/rsc-error', getParentRoute: () => rootRouteImport, } as any) -const RscNestedStructureRoute = RscNestedStructureRouteImport.update({ - id: '/rsc-nested-structure', - path: '/rsc-nested-structure', +const RscExternalRoute = RscExternalRouteImport.update({ + id: '/rsc-external', + path: '/rsc-external', getParentRoute: () => rootRouteImport, } as any) -const RscNestedRoute = RscNestedRouteImport.update({ - id: '/rsc-nested', - path: '/rsc-nested', +const RscFlightApiRoute = RscFlightApiRouteImport.update({ + id: '/rsc-flight-api', + path: '/rsc-flight-api', getParentRoute: () => rootRouteImport, } as any) -const RscMultiRoute = RscMultiRouteImport.update({ - id: '/rsc-multi', - path: '/rsc-multi', +const RscFormsRoute = RscFormsRouteImport.update({ + id: '/rsc-forms', + path: '/rsc-forms', getParentRoute: () => rootRouteImport, } as any) -const RscLinkRoute = RscLinkRouteImport.update({ - id: '/rsc-link', - path: '/rsc-link', +const RscGlobalCssRoute = RscGlobalCssRouteImport.update({ + id: '/rsc-global-css', + path: '/rsc-global-css', getParentRoute: () => rootRouteImport, } as any) -const RscLargeRoute = RscLargeRouteImport.update({ - id: '/rsc-large', - path: '/rsc-large', +const RscHydrationRoute = RscHydrationRouteImport.update({ + id: '/rsc-hydration', + path: '/rsc-hydration', getParentRoute: () => rootRouteImport, } as any) const RscInvalidationRoute = RscInvalidationRouteImport.update({ @@ -167,109 +163,119 @@ const RscInvalidationRoute = RscInvalidationRouteImport.update({ path: '/rsc-invalidation', getParentRoute: () => rootRouteImport, } as any) -const RscHydrationRoute = RscHydrationRouteImport.update({ - id: '/rsc-hydration', - path: '/rsc-hydration', +const RscLargeRoute = RscLargeRouteImport.update({ + id: '/rsc-large', + path: '/rsc-large', getParentRoute: () => rootRouteImport, } as any) -const RscGlobalCssRoute = RscGlobalCssRouteImport.update({ - id: '/rsc-global-css', - path: '/rsc-global-css', +const RscLinkRoute = RscLinkRouteImport.update({ + id: '/rsc-link', + path: '/rsc-link', getParentRoute: () => rootRouteImport, } as any) -const RscFormsRoute = RscFormsRouteImport.update({ - id: '/rsc-forms', - path: '/rsc-forms', +const RscMultiRoute = RscMultiRouteImport.update({ + id: '/rsc-multi', + path: '/rsc-multi', getParentRoute: () => rootRouteImport, } as any) -const RscFlightApiRoute = RscFlightApiRouteImport.update({ - id: '/rsc-flight-api', - path: '/rsc-flight-api', +const RscNestedRoute = RscNestedRouteImport.update({ + id: '/rsc-nested', + path: '/rsc-nested', getParentRoute: () => rootRouteImport, } as any) -const RscExternalRoute = RscExternalRouteImport.update({ - id: '/rsc-external', - path: '/rsc-external', +const RscNestedStructureRoute = RscNestedStructureRouteImport.update({ + id: '/rsc-nested-structure', + path: '/rsc-nested-structure', getParentRoute: () => rootRouteImport, } as any) -const RscErrorRoute = RscErrorRouteImport.update({ - id: '/rsc-error', - path: '/rsc-error', +const RscParallelRoute = RscParallelRouteImport.update({ + id: '/rsc-parallel', + path: '/rsc-parallel', getParentRoute: () => rootRouteImport, } as any) -const RscDirectLoaderRoute = RscDirectLoaderRouteImport.update({ - id: '/rsc-direct-loader', - path: '/rsc-direct-loader', +const RscReactCacheRoute = RscReactCacheRouteImport.update({ + id: '/rsc-react-cache', + path: '/rsc-react-cache', getParentRoute: () => rootRouteImport, } as any) -const RscDeferredComponentRoute = RscDeferredComponentRouteImport.update({ - id: '/rsc-deferred-component', - path: '/rsc-deferred-component', +const RscRequestHeadersRoute = RscRequestHeadersRouteImport.update({ + id: '/rsc-request-headers', + path: '/rsc-request-headers', getParentRoute: () => rootRouteImport, } as any) -const RscDeferredRoute = RscDeferredRouteImport.update({ - id: '/rsc-deferred', - path: '/rsc-deferred', +const RscServerNotFoundRoute = RscServerNotFoundRouteImport.update({ + id: '/rsc-server-not-found', + path: '/rsc-server-not-found', getParentRoute: () => rootRouteImport, } as any) -const RscCssPreloadComplexRoute = RscCssPreloadComplexRouteImport.update({ - id: '/rsc-css-preload-complex', - path: '/rsc-css-preload-complex', +const RscServerRedirectRoute = RscServerRedirectRouteImport.update({ + id: '/rsc-server-redirect', + path: '/rsc-server-redirect', getParentRoute: () => rootRouteImport, } as any) -const RscCssModulesRoute = RscCssModulesRouteImport.update({ - id: '/rsc-css-modules', - path: '/rsc-css-modules', +const RscSlotJsxArgsRoute = RscSlotJsxArgsRouteImport.update({ + id: '/rsc-slot-jsx-args', + path: '/rsc-slot-jsx-args', getParentRoute: () => rootRouteImport, } as any) -const RscCssAutoinjectRoute = RscCssAutoinjectRouteImport.update({ - id: '/rsc-css-autoinject', - path: '/rsc-css-autoinject', +const RscSlotsRoute = RscSlotsRouteImport.update({ + id: '/rsc-slots', + path: '/rsc-slots', getParentRoute: () => rootRouteImport, } as any) -const RscContextRoute = RscContextRouteImport.update({ - id: '/rsc-context', - path: '/rsc-context', +const RscSsrDataOnlyRoute = RscSsrDataOnlyRouteImport.update({ + id: '/rsc-ssr-data-only', + path: '/rsc-ssr-data-only', + getParentRoute: () => rootRouteImport, +} as any) +const RscSsrFalseRoute = RscSsrFalseRouteImport.update({ + id: '/rsc-ssr-false', + path: '/rsc-ssr-false', getParentRoute: () => rootRouteImport, } as any) -const RscComponentSlotRoute = RscComponentSlotRouteImport.update({ - id: '/rsc-component-slot', - path: '/rsc-component-slot', +const RscStreamGeneratorRoute = RscStreamGeneratorRouteImport.update({ + id: '/rsc-stream-generator', + path: '/rsc-stream-generator', getParentRoute: () => rootRouteImport, } as any) -const RscClientPreloadRoute = RscClientPreloadRouteImport.update({ - id: '/rsc-client-preload', - path: '/rsc-client-preload', +const RscStreamLoaderRoute = RscStreamLoaderRouteImport.update({ + id: '/rsc-stream-loader', + path: '/rsc-stream-loader', getParentRoute: () => rootRouteImport, } as any) -const RscCachingRoute = RscCachingRouteImport.update({ - id: '/rsc-caching', - path: '/rsc-caching', +const RscStreamReadableRoute = RscStreamReadableRouteImport.update({ + id: '/rsc-stream-readable', + path: '/rsc-stream-readable', getParentRoute: () => rootRouteImport, } as any) -const RscBundleRoute = RscBundleRouteImport.update({ - id: '/rsc-bundle', - path: '/rsc-bundle', +const RscStreamingRoute = RscStreamingRouteImport.update({ + id: '/rsc-streaming', + path: '/rsc-streaming', getParentRoute: () => rootRouteImport, } as any) -const RscBasicRoute = RscBasicRouteImport.update({ - id: '/rsc-basic', - path: '/rsc-basic', +const RscSuspenseRoute = RscSuspenseRouteImport.update({ + id: '/rsc-suspense', + path: '/rsc-suspense', getParentRoute: () => rootRouteImport, } as any) -const RscAsyncBundleRoute = RscAsyncBundleRouteImport.update({ - id: '/rsc-async-bundle', - path: '/rsc-async-bundle', +const RscTreeRoute = RscTreeRouteImport.update({ + id: '/rsc-tree', + path: '/rsc-tree', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RscUseServerFnRoute = RscUseServerFnRouteImport.update({ + id: '/rsc-use-server-fn', + path: '/rsc-use-server-fn', getParentRoute: () => rootRouteImport, } as any) -const RscParamIndexRoute = RscParamIndexRouteImport.update({ - id: '/rsc-param/', - path: '/rsc-param/', +const ApiRscBuildBoundaryRoute = ApiRscBuildBoundaryRouteImport.update({ + id: '/api/rsc-build-boundary', + path: '/api/rsc-build-boundary', + getParentRoute: () => rootRouteImport, +} as any) +const ApiRscFlightRoute = ApiRscFlightRouteImport.update({ + id: '/api/rsc-flight', + path: '/api/rsc-flight', getParentRoute: () => rootRouteImport, } as any) const RscCssConditionalIndexRoute = RscCssConditionalIndexRouteImport.update({ @@ -277,19 +283,19 @@ const RscCssConditionalIndexRoute = RscCssConditionalIndexRouteImport.update({ path: '/rsc-css-conditional/', getParentRoute: () => rootRouteImport, } as any) -const RscParamIdRoute = RscParamIdRouteImport.update({ - id: '/rsc-param/$id', - path: '/rsc-param/$id', - getParentRoute: () => rootRouteImport, -} as any) const RscCssConditionalBranchRoute = RscCssConditionalBranchRouteImport.update({ id: '/rsc-css-conditional/$branch', path: '/rsc-css-conditional/$branch', getParentRoute: () => rootRouteImport, } as any) -const ApiRscFlightRoute = ApiRscFlightRouteImport.update({ - id: '/api/rsc-flight', - path: '/api/rsc-flight', +const RscParamIndexRoute = RscParamIndexRouteImport.update({ + id: '/rsc-param/', + path: '/rsc-param/', + getParentRoute: () => rootRouteImport, +} as any) +const RscParamIdRoute = RscParamIdRouteImport.update({ + id: '/rsc-param/$id', + path: '/rsc-param/$id', getParentRoute: () => rootRouteImport, } as any) @@ -336,6 +342,7 @@ export interface FileRoutesByFullPath { '/rsc-suspense': typeof RscSuspenseRoute '/rsc-tree': typeof RscTreeRoute '/rsc-use-server-fn': typeof RscUseServerFnRoute + '/api/rsc-build-boundary': typeof ApiRscBuildBoundaryRoute '/api/rsc-flight': typeof ApiRscFlightRoute '/rsc-css-conditional/$branch': typeof RscCssConditionalBranchRoute '/rsc-param/$id': typeof RscParamIdRoute @@ -385,6 +392,7 @@ export interface FileRoutesByTo { '/rsc-suspense': typeof RscSuspenseRoute '/rsc-tree': typeof RscTreeRoute '/rsc-use-server-fn': typeof RscUseServerFnRoute + '/api/rsc-build-boundary': typeof ApiRscBuildBoundaryRoute '/api/rsc-flight': typeof ApiRscFlightRoute '/rsc-css-conditional/$branch': typeof RscCssConditionalBranchRoute '/rsc-param/$id': typeof RscParamIdRoute @@ -435,6 +443,7 @@ export interface FileRoutesById { '/rsc-suspense': typeof RscSuspenseRoute '/rsc-tree': typeof RscTreeRoute '/rsc-use-server-fn': typeof RscUseServerFnRoute + '/api/rsc-build-boundary': typeof ApiRscBuildBoundaryRoute '/api/rsc-flight': typeof ApiRscFlightRoute '/rsc-css-conditional/$branch': typeof RscCssConditionalBranchRoute '/rsc-param/$id': typeof RscParamIdRoute @@ -486,6 +495,7 @@ export interface FileRouteTypes { | '/rsc-suspense' | '/rsc-tree' | '/rsc-use-server-fn' + | '/api/rsc-build-boundary' | '/api/rsc-flight' | '/rsc-css-conditional/$branch' | '/rsc-param/$id' @@ -535,6 +545,7 @@ export interface FileRouteTypes { | '/rsc-suspense' | '/rsc-tree' | '/rsc-use-server-fn' + | '/api/rsc-build-boundary' | '/api/rsc-flight' | '/rsc-css-conditional/$branch' | '/rsc-param/$id' @@ -584,6 +595,7 @@ export interface FileRouteTypes { | '/rsc-suspense' | '/rsc-tree' | '/rsc-use-server-fn' + | '/api/rsc-build-boundary' | '/api/rsc-flight' | '/rsc-css-conditional/$branch' | '/rsc-param/$id' @@ -634,6 +646,7 @@ export interface RootRouteChildren { RscSuspenseRoute: typeof RscSuspenseRoute RscTreeRoute: typeof RscTreeRoute RscUseServerFnRoute: typeof RscUseServerFnRoute + ApiRscBuildBoundaryRoute: typeof ApiRscBuildBoundaryRoute ApiRscFlightRoute: typeof ApiRscFlightRoute RscCssConditionalBranchRoute: typeof RscCssConditionalBranchRoute RscParamIdRoute: typeof RscParamIdRoute @@ -643,144 +656,151 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/rsc-use-server-fn': { - id: '/rsc-use-server-fn' - path: '/rsc-use-server-fn' - fullPath: '/rsc-use-server-fn' - preLoaderRoute: typeof RscUseServerFnRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/rsc-tree': { - id: '/rsc-tree' - path: '/rsc-tree' - fullPath: '/rsc-tree' - preLoaderRoute: typeof RscTreeRouteImport + '/rsc-async-bundle': { + id: '/rsc-async-bundle' + path: '/rsc-async-bundle' + fullPath: '/rsc-async-bundle' + preLoaderRoute: typeof RscAsyncBundleRouteImport parentRoute: typeof rootRouteImport } - '/rsc-suspense': { - id: '/rsc-suspense' - path: '/rsc-suspense' - fullPath: '/rsc-suspense' - preLoaderRoute: typeof RscSuspenseRouteImport + '/rsc-basic': { + id: '/rsc-basic' + path: '/rsc-basic' + fullPath: '/rsc-basic' + preLoaderRoute: typeof RscBasicRouteImport parentRoute: typeof rootRouteImport } - '/rsc-streaming': { - id: '/rsc-streaming' - path: '/rsc-streaming' - fullPath: '/rsc-streaming' - preLoaderRoute: typeof RscStreamingRouteImport + '/rsc-bundle': { + id: '/rsc-bundle' + path: '/rsc-bundle' + fullPath: '/rsc-bundle' + preLoaderRoute: typeof RscBundleRouteImport parentRoute: typeof rootRouteImport } - '/rsc-stream-readable': { - id: '/rsc-stream-readable' - path: '/rsc-stream-readable' - fullPath: '/rsc-stream-readable' - preLoaderRoute: typeof RscStreamReadableRouteImport + '/rsc-caching': { + id: '/rsc-caching' + path: '/rsc-caching' + fullPath: '/rsc-caching' + preLoaderRoute: typeof RscCachingRouteImport parentRoute: typeof rootRouteImport } - '/rsc-stream-loader': { - id: '/rsc-stream-loader' - path: '/rsc-stream-loader' - fullPath: '/rsc-stream-loader' - preLoaderRoute: typeof RscStreamLoaderRouteImport + '/rsc-client-preload': { + id: '/rsc-client-preload' + path: '/rsc-client-preload' + fullPath: '/rsc-client-preload' + preLoaderRoute: typeof RscClientPreloadRouteImport parentRoute: typeof rootRouteImport } - '/rsc-stream-generator': { - id: '/rsc-stream-generator' - path: '/rsc-stream-generator' - fullPath: '/rsc-stream-generator' - preLoaderRoute: typeof RscStreamGeneratorRouteImport + '/rsc-component-slot': { + id: '/rsc-component-slot' + path: '/rsc-component-slot' + fullPath: '/rsc-component-slot' + preLoaderRoute: typeof RscComponentSlotRouteImport parentRoute: typeof rootRouteImport } - '/rsc-ssr-false': { - id: '/rsc-ssr-false' - path: '/rsc-ssr-false' - fullPath: '/rsc-ssr-false' - preLoaderRoute: typeof RscSsrFalseRouteImport + '/rsc-context': { + id: '/rsc-context' + path: '/rsc-context' + fullPath: '/rsc-context' + preLoaderRoute: typeof RscContextRouteImport parentRoute: typeof rootRouteImport } - '/rsc-ssr-data-only': { - id: '/rsc-ssr-data-only' - path: '/rsc-ssr-data-only' - fullPath: '/rsc-ssr-data-only' - preLoaderRoute: typeof RscSsrDataOnlyRouteImport + '/rsc-css-autoinject': { + id: '/rsc-css-autoinject' + path: '/rsc-css-autoinject' + fullPath: '/rsc-css-autoinject' + preLoaderRoute: typeof RscCssAutoinjectRouteImport parentRoute: typeof rootRouteImport } - '/rsc-slots': { - id: '/rsc-slots' - path: '/rsc-slots' - fullPath: '/rsc-slots' - preLoaderRoute: typeof RscSlotsRouteImport + '/rsc-css-modules': { + id: '/rsc-css-modules' + path: '/rsc-css-modules' + fullPath: '/rsc-css-modules' + preLoaderRoute: typeof RscCssModulesRouteImport parentRoute: typeof rootRouteImport } - '/rsc-slot-jsx-args': { - id: '/rsc-slot-jsx-args' - path: '/rsc-slot-jsx-args' - fullPath: '/rsc-slot-jsx-args' - preLoaderRoute: typeof RscSlotJsxArgsRouteImport + '/rsc-css-preload-complex': { + id: '/rsc-css-preload-complex' + path: '/rsc-css-preload-complex' + fullPath: '/rsc-css-preload-complex' + preLoaderRoute: typeof RscCssPreloadComplexRouteImport parentRoute: typeof rootRouteImport } - '/rsc-server-redirect': { - id: '/rsc-server-redirect' - path: '/rsc-server-redirect' - fullPath: '/rsc-server-redirect' - preLoaderRoute: typeof RscServerRedirectRouteImport + '/rsc-deferred': { + id: '/rsc-deferred' + path: '/rsc-deferred' + fullPath: '/rsc-deferred' + preLoaderRoute: typeof RscDeferredRouteImport parentRoute: typeof rootRouteImport } - '/rsc-server-not-found': { - id: '/rsc-server-not-found' - path: '/rsc-server-not-found' - fullPath: '/rsc-server-not-found' - preLoaderRoute: typeof RscServerNotFoundRouteImport + '/rsc-deferred-component': { + id: '/rsc-deferred-component' + path: '/rsc-deferred-component' + fullPath: '/rsc-deferred-component' + preLoaderRoute: typeof RscDeferredComponentRouteImport parentRoute: typeof rootRouteImport } - '/rsc-request-headers': { - id: '/rsc-request-headers' - path: '/rsc-request-headers' - fullPath: '/rsc-request-headers' - preLoaderRoute: typeof RscRequestHeadersRouteImport + '/rsc-direct-loader': { + id: '/rsc-direct-loader' + path: '/rsc-direct-loader' + fullPath: '/rsc-direct-loader' + preLoaderRoute: typeof RscDirectLoaderRouteImport parentRoute: typeof rootRouteImport } - '/rsc-react-cache': { - id: '/rsc-react-cache' - path: '/rsc-react-cache' - fullPath: '/rsc-react-cache' - preLoaderRoute: typeof RscReactCacheRouteImport + '/rsc-error': { + id: '/rsc-error' + path: '/rsc-error' + fullPath: '/rsc-error' + preLoaderRoute: typeof RscErrorRouteImport parentRoute: typeof rootRouteImport } - '/rsc-parallel': { - id: '/rsc-parallel' - path: '/rsc-parallel' - fullPath: '/rsc-parallel' - preLoaderRoute: typeof RscParallelRouteImport + '/rsc-external': { + id: '/rsc-external' + path: '/rsc-external' + fullPath: '/rsc-external' + preLoaderRoute: typeof RscExternalRouteImport parentRoute: typeof rootRouteImport } - '/rsc-nested-structure': { - id: '/rsc-nested-structure' - path: '/rsc-nested-structure' - fullPath: '/rsc-nested-structure' - preLoaderRoute: typeof RscNestedStructureRouteImport + '/rsc-flight-api': { + id: '/rsc-flight-api' + path: '/rsc-flight-api' + fullPath: '/rsc-flight-api' + preLoaderRoute: typeof RscFlightApiRouteImport parentRoute: typeof rootRouteImport } - '/rsc-nested': { - id: '/rsc-nested' - path: '/rsc-nested' - fullPath: '/rsc-nested' - preLoaderRoute: typeof RscNestedRouteImport + '/rsc-forms': { + id: '/rsc-forms' + path: '/rsc-forms' + fullPath: '/rsc-forms' + preLoaderRoute: typeof RscFormsRouteImport parentRoute: typeof rootRouteImport } - '/rsc-multi': { - id: '/rsc-multi' - path: '/rsc-multi' - fullPath: '/rsc-multi' - preLoaderRoute: typeof RscMultiRouteImport + '/rsc-global-css': { + id: '/rsc-global-css' + path: '/rsc-global-css' + fullPath: '/rsc-global-css' + preLoaderRoute: typeof RscGlobalCssRouteImport parentRoute: typeof rootRouteImport } - '/rsc-link': { - id: '/rsc-link' - path: '/rsc-link' - fullPath: '/rsc-link' - preLoaderRoute: typeof RscLinkRouteImport + '/rsc-hydration': { + id: '/rsc-hydration' + path: '/rsc-hydration' + fullPath: '/rsc-hydration' + preLoaderRoute: typeof RscHydrationRouteImport + parentRoute: typeof rootRouteImport + } + '/rsc-invalidation': { + id: '/rsc-invalidation' + path: '/rsc-invalidation' + fullPath: '/rsc-invalidation' + preLoaderRoute: typeof RscInvalidationRouteImport parentRoute: typeof rootRouteImport } '/rsc-large': { @@ -790,158 +810,158 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RscLargeRouteImport parentRoute: typeof rootRouteImport } - '/rsc-invalidation': { - id: '/rsc-invalidation' - path: '/rsc-invalidation' - fullPath: '/rsc-invalidation' - preLoaderRoute: typeof RscInvalidationRouteImport + '/rsc-link': { + id: '/rsc-link' + path: '/rsc-link' + fullPath: '/rsc-link' + preLoaderRoute: typeof RscLinkRouteImport parentRoute: typeof rootRouteImport } - '/rsc-hydration': { - id: '/rsc-hydration' - path: '/rsc-hydration' - fullPath: '/rsc-hydration' - preLoaderRoute: typeof RscHydrationRouteImport + '/rsc-multi': { + id: '/rsc-multi' + path: '/rsc-multi' + fullPath: '/rsc-multi' + preLoaderRoute: typeof RscMultiRouteImport parentRoute: typeof rootRouteImport } - '/rsc-global-css': { - id: '/rsc-global-css' - path: '/rsc-global-css' - fullPath: '/rsc-global-css' - preLoaderRoute: typeof RscGlobalCssRouteImport + '/rsc-nested': { + id: '/rsc-nested' + path: '/rsc-nested' + fullPath: '/rsc-nested' + preLoaderRoute: typeof RscNestedRouteImport parentRoute: typeof rootRouteImport } - '/rsc-forms': { - id: '/rsc-forms' - path: '/rsc-forms' - fullPath: '/rsc-forms' - preLoaderRoute: typeof RscFormsRouteImport + '/rsc-nested-structure': { + id: '/rsc-nested-structure' + path: '/rsc-nested-structure' + fullPath: '/rsc-nested-structure' + preLoaderRoute: typeof RscNestedStructureRouteImport parentRoute: typeof rootRouteImport } - '/rsc-flight-api': { - id: '/rsc-flight-api' - path: '/rsc-flight-api' - fullPath: '/rsc-flight-api' - preLoaderRoute: typeof RscFlightApiRouteImport + '/rsc-parallel': { + id: '/rsc-parallel' + path: '/rsc-parallel' + fullPath: '/rsc-parallel' + preLoaderRoute: typeof RscParallelRouteImport parentRoute: typeof rootRouteImport } - '/rsc-external': { - id: '/rsc-external' - path: '/rsc-external' - fullPath: '/rsc-external' - preLoaderRoute: typeof RscExternalRouteImport + '/rsc-react-cache': { + id: '/rsc-react-cache' + path: '/rsc-react-cache' + fullPath: '/rsc-react-cache' + preLoaderRoute: typeof RscReactCacheRouteImport parentRoute: typeof rootRouteImport } - '/rsc-error': { - id: '/rsc-error' - path: '/rsc-error' - fullPath: '/rsc-error' - preLoaderRoute: typeof RscErrorRouteImport + '/rsc-request-headers': { + id: '/rsc-request-headers' + path: '/rsc-request-headers' + fullPath: '/rsc-request-headers' + preLoaderRoute: typeof RscRequestHeadersRouteImport parentRoute: typeof rootRouteImport } - '/rsc-direct-loader': { - id: '/rsc-direct-loader' - path: '/rsc-direct-loader' - fullPath: '/rsc-direct-loader' - preLoaderRoute: typeof RscDirectLoaderRouteImport + '/rsc-server-not-found': { + id: '/rsc-server-not-found' + path: '/rsc-server-not-found' + fullPath: '/rsc-server-not-found' + preLoaderRoute: typeof RscServerNotFoundRouteImport parentRoute: typeof rootRouteImport } - '/rsc-deferred-component': { - id: '/rsc-deferred-component' - path: '/rsc-deferred-component' - fullPath: '/rsc-deferred-component' - preLoaderRoute: typeof RscDeferredComponentRouteImport + '/rsc-server-redirect': { + id: '/rsc-server-redirect' + path: '/rsc-server-redirect' + fullPath: '/rsc-server-redirect' + preLoaderRoute: typeof RscServerRedirectRouteImport parentRoute: typeof rootRouteImport } - '/rsc-deferred': { - id: '/rsc-deferred' - path: '/rsc-deferred' - fullPath: '/rsc-deferred' - preLoaderRoute: typeof RscDeferredRouteImport + '/rsc-slot-jsx-args': { + id: '/rsc-slot-jsx-args' + path: '/rsc-slot-jsx-args' + fullPath: '/rsc-slot-jsx-args' + preLoaderRoute: typeof RscSlotJsxArgsRouteImport parentRoute: typeof rootRouteImport } - '/rsc-css-preload-complex': { - id: '/rsc-css-preload-complex' - path: '/rsc-css-preload-complex' - fullPath: '/rsc-css-preload-complex' - preLoaderRoute: typeof RscCssPreloadComplexRouteImport + '/rsc-slots': { + id: '/rsc-slots' + path: '/rsc-slots' + fullPath: '/rsc-slots' + preLoaderRoute: typeof RscSlotsRouteImport parentRoute: typeof rootRouteImport } - '/rsc-css-modules': { - id: '/rsc-css-modules' - path: '/rsc-css-modules' - fullPath: '/rsc-css-modules' - preLoaderRoute: typeof RscCssModulesRouteImport + '/rsc-ssr-data-only': { + id: '/rsc-ssr-data-only' + path: '/rsc-ssr-data-only' + fullPath: '/rsc-ssr-data-only' + preLoaderRoute: typeof RscSsrDataOnlyRouteImport parentRoute: typeof rootRouteImport } - '/rsc-css-autoinject': { - id: '/rsc-css-autoinject' - path: '/rsc-css-autoinject' - fullPath: '/rsc-css-autoinject' - preLoaderRoute: typeof RscCssAutoinjectRouteImport + '/rsc-ssr-false': { + id: '/rsc-ssr-false' + path: '/rsc-ssr-false' + fullPath: '/rsc-ssr-false' + preLoaderRoute: typeof RscSsrFalseRouteImport parentRoute: typeof rootRouteImport } - '/rsc-context': { - id: '/rsc-context' - path: '/rsc-context' - fullPath: '/rsc-context' - preLoaderRoute: typeof RscContextRouteImport + '/rsc-stream-generator': { + id: '/rsc-stream-generator' + path: '/rsc-stream-generator' + fullPath: '/rsc-stream-generator' + preLoaderRoute: typeof RscStreamGeneratorRouteImport parentRoute: typeof rootRouteImport } - '/rsc-component-slot': { - id: '/rsc-component-slot' - path: '/rsc-component-slot' - fullPath: '/rsc-component-slot' - preLoaderRoute: typeof RscComponentSlotRouteImport + '/rsc-stream-loader': { + id: '/rsc-stream-loader' + path: '/rsc-stream-loader' + fullPath: '/rsc-stream-loader' + preLoaderRoute: typeof RscStreamLoaderRouteImport parentRoute: typeof rootRouteImport } - '/rsc-client-preload': { - id: '/rsc-client-preload' - path: '/rsc-client-preload' - fullPath: '/rsc-client-preload' - preLoaderRoute: typeof RscClientPreloadRouteImport + '/rsc-stream-readable': { + id: '/rsc-stream-readable' + path: '/rsc-stream-readable' + fullPath: '/rsc-stream-readable' + preLoaderRoute: typeof RscStreamReadableRouteImport parentRoute: typeof rootRouteImport } - '/rsc-caching': { - id: '/rsc-caching' - path: '/rsc-caching' - fullPath: '/rsc-caching' - preLoaderRoute: typeof RscCachingRouteImport + '/rsc-streaming': { + id: '/rsc-streaming' + path: '/rsc-streaming' + fullPath: '/rsc-streaming' + preLoaderRoute: typeof RscStreamingRouteImport parentRoute: typeof rootRouteImport } - '/rsc-bundle': { - id: '/rsc-bundle' - path: '/rsc-bundle' - fullPath: '/rsc-bundle' - preLoaderRoute: typeof RscBundleRouteImport + '/rsc-suspense': { + id: '/rsc-suspense' + path: '/rsc-suspense' + fullPath: '/rsc-suspense' + preLoaderRoute: typeof RscSuspenseRouteImport parentRoute: typeof rootRouteImport } - '/rsc-basic': { - id: '/rsc-basic' - path: '/rsc-basic' - fullPath: '/rsc-basic' - preLoaderRoute: typeof RscBasicRouteImport + '/rsc-tree': { + id: '/rsc-tree' + path: '/rsc-tree' + fullPath: '/rsc-tree' + preLoaderRoute: typeof RscTreeRouteImport parentRoute: typeof rootRouteImport } - '/rsc-async-bundle': { - id: '/rsc-async-bundle' - path: '/rsc-async-bundle' - fullPath: '/rsc-async-bundle' - preLoaderRoute: typeof RscAsyncBundleRouteImport + '/rsc-use-server-fn': { + id: '/rsc-use-server-fn' + path: '/rsc-use-server-fn' + fullPath: '/rsc-use-server-fn' + preLoaderRoute: typeof RscUseServerFnRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/api/rsc-build-boundary': { + id: '/api/rsc-build-boundary' + path: '/api/rsc-build-boundary' + fullPath: '/api/rsc-build-boundary' + preLoaderRoute: typeof ApiRscBuildBoundaryRouteImport parentRoute: typeof rootRouteImport } - '/rsc-param/': { - id: '/rsc-param/' - path: '/rsc-param' - fullPath: '/rsc-param/' - preLoaderRoute: typeof RscParamIndexRouteImport + '/api/rsc-flight': { + id: '/api/rsc-flight' + path: '/api/rsc-flight' + fullPath: '/api/rsc-flight' + preLoaderRoute: typeof ApiRscFlightRouteImport parentRoute: typeof rootRouteImport } '/rsc-css-conditional/': { @@ -951,13 +971,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RscCssConditionalIndexRouteImport parentRoute: typeof rootRouteImport } - '/rsc-param/$id': { - id: '/rsc-param/$id' - path: '/rsc-param/$id' - fullPath: '/rsc-param/$id' - preLoaderRoute: typeof RscParamIdRouteImport - parentRoute: typeof rootRouteImport - } '/rsc-css-conditional/$branch': { id: '/rsc-css-conditional/$branch' path: '/rsc-css-conditional/$branch' @@ -965,11 +978,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RscCssConditionalBranchRouteImport parentRoute: typeof rootRouteImport } - '/api/rsc-flight': { - id: '/api/rsc-flight' - path: '/api/rsc-flight' - fullPath: '/api/rsc-flight' - preLoaderRoute: typeof ApiRscFlightRouteImport + '/rsc-param/': { + id: '/rsc-param/' + path: '/rsc-param' + fullPath: '/rsc-param/' + preLoaderRoute: typeof RscParamIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/rsc-param/$id': { + id: '/rsc-param/$id' + path: '/rsc-param/$id' + fullPath: '/rsc-param/$id' + preLoaderRoute: typeof RscParamIdRouteImport parentRoute: typeof rootRouteImport } } @@ -1018,6 +1038,7 @@ const rootRouteChildren: RootRouteChildren = { RscSuspenseRoute: RscSuspenseRoute, RscTreeRoute: RscTreeRoute, RscUseServerFnRoute: RscUseServerFnRoute, + ApiRscBuildBoundaryRoute: ApiRscBuildBoundaryRoute, ApiRscFlightRoute: ApiRscFlightRoute, RscCssConditionalBranchRoute: RscCssConditionalBranchRoute, RscParamIdRoute: RscParamIdRoute, diff --git a/e2e/react-start/rsc/src/routes/api.rsc-build-boundary.ts b/e2e/react-start/rsc/src/routes/api.rsc-build-boundary.ts new file mode 100644 index 0000000000..d29d2a8cb8 --- /dev/null +++ b/e2e/react-start/rsc/src/routes/api.rsc-build-boundary.ts @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' +import { getRscBuildBoundarySentinel } from '~/utils/rscBuildBoundarySentinel' + +export const Route = createFileRoute('/api/rsc-build-boundary')({ + server: { + handlers: { + GET: () => { + return new Response(getRscBuildBoundarySentinel(), { + headers: { 'Content-Type': 'text/plain' }, + }) + }, + }, + }, +}) diff --git a/e2e/react-start/rsc/src/utils/rscBuildBoundarySentinel.ts b/e2e/react-start/rsc/src/utils/rscBuildBoundarySentinel.ts new file mode 100644 index 0000000000..706adb30d9 --- /dev/null +++ b/e2e/react-start/rsc/src/utils/rscBuildBoundarySentinel.ts @@ -0,0 +1,3 @@ +export function getRscBuildBoundarySentinel() { + return 'tanstack-start-rsc-server-only-route-sentinel' +} diff --git a/e2e/react-start/rsc/tests/rsc-build-boundary.spec.ts b/e2e/react-start/rsc/tests/rsc-build-boundary.spec.ts new file mode 100644 index 0000000000..f57fefe994 --- /dev/null +++ b/e2e/react-start/rsc/tests/rsc-build-boundary.spec.ts @@ -0,0 +1,87 @@ +import { readdir, readFile } from 'node:fs/promises' +import path from 'node:path' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' + +// This marker deliberately lives behind a server-only route. If its route +// dependency graph enters the RSC build, the emitted-file assertion will fail. +const SENTINEL = 'tanstack-start-rsc-server-only-route-sentinel' +const EXECUTABLE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs']) + +const distDir = path.resolve(process.cwd(), process.env.E2E_DIST_DIR ?? 'dist') +const serverDir = path.join(distDir, 'server') +const rscDir = path.join(serverDir, 'rsc') + +async function findExecutableFiles( + directory: string, + excludedDirectories = new Set(), +): Promise> { + const entries = await readdir(directory, { withFileTypes: true }) + const files: Array = [] + + for (const entry of entries) { + const entryPath = path.join(directory, entry.name) + + if (entry.isDirectory()) { + if (!excludedDirectories.has(entryPath)) { + files.push( + ...(await findExecutableFiles(entryPath, excludedDirectories)), + ) + } + } else if ( + entry.isFile() && + EXECUTABLE_EXTENSIONS.has(path.extname(entry.name)) + ) { + files.push(entryPath) + } + } + + return files.sort() +} + +async function findSentinelMatches(files: Array) { + const matches: Array = [] + + for (const file of files) { + if ((await readFile(file, 'utf-8')).includes(SENTINEL)) { + matches.push(path.relative(distDir, file)) + } + } + + return matches +} + +test('server-only route dependencies stay out of the RSC build output', async ({ + request, +}) => { + test.skip( + (process.env.E2E_TOOLCHAIN ?? 'vite') !== 'vite', + 'Vite emits the RSC environment as a separate output directory', + ) + + const response = await request.get('/api/rsc-build-boundary') + expect(response.ok()).toBe(true) + expect(await response.text()).toBe(SENTINEL) + + const [ssrFiles, rscFiles] = await Promise.all([ + findExecutableFiles(serverDir, new Set([rscDir])), + findExecutableFiles(rscDir), + ]) + const [ssrMatches, rscMatches] = await Promise.all([ + findSentinelMatches(ssrFiles), + findSentinelMatches(rscFiles), + ]) + + expect( + ssrMatches, + `Expected the sentinel in the SSR output. Scanned files:\n${ssrFiles + .map((file) => path.relative(distDir, file)) + .join('\n')}`, + ).not.toEqual([]) + expect( + rscMatches, + `The server-only route dependency leaked into these RSC files:\n${rscMatches.join( + '\n', + )}`, + ).toEqual([]) +}) diff --git a/e2e/react-start/scroll-restoration/package.json b/e2e/react-start/scroll-restoration/package.json index 3dff10b56e..79c04d5c9d 100644 --- a/e2e/react-start/scroll-restoration/package.json +++ b/e2e/react-start/scroll-restoration/package.json @@ -22,7 +22,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -32,7 +32,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/scroll-restoration/src/routeTree.gen.ts b/e2e/react-start/scroll-restoration/src/routeTree.gen.ts index 1df27a0424..f25d1f25c0 100644 --- a/e2e/react-start/scroll-restoration/src/routeTree.gen.ts +++ b/e2e/react-start/scroll-restoration/src/routeTree.gen.ts @@ -10,48 +10,68 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' -import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' -import { Route as testsSsrScrollKeyRouteImport } from './routes/(tests)/ssr-scroll-key' -import { Route as testsResetScrollFalseCRouteImport } from './routes/(tests)/reset-scroll-false-c' -import { Route as testsResetScrollFalseBRouteImport } from './routes/(tests)/reset-scroll-false-b' -import { Route as testsResetScrollFalseARouteImport } from './routes/(tests)/reset-scroll-false-a' -import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' -import { Route as testsNestedScrollSearchRouteImport } from './routes/(tests)/nested-scroll-search' -import { Route as testsNestedScrollCarryOverBRouteImport } from './routes/(tests)/nested-scroll-carry-over-b' -import { Route as testsNestedScrollCarryOverARouteImport } from './routes/(tests)/nested-scroll-carry-over-a' -import { Route as testsNestedScrollAwayRouteImport } from './routes/(tests)/nested-scroll-away' -import { Route as testsHashScrollReproRouteImport } from './routes/(tests)/hash-scroll-repro' import { Route as testsHashScrollAboutRouteImport } from './routes/(tests)/hash-scroll-about' +import { Route as testsHashScrollReproRouteImport } from './routes/(tests)/hash-scroll-repro' +import { Route as testsIssue7687RouteImport } from './routes/(tests)/issue-7687' +import { Route as testsNestedScrollAwayRouteImport } from './routes/(tests)/nested-scroll-away' +import { Route as testsNestedScrollCarryOverARouteImport } from './routes/(tests)/nested-scroll-carry-over-a' +import { Route as testsNestedScrollCarryOverBRouteImport } from './routes/(tests)/nested-scroll-carry-over-b' +import { Route as testsNestedScrollSearchRouteImport } from './routes/(tests)/nested-scroll-search' +import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsResetScrollFalseARouteImport } from './routes/(tests)/reset-scroll-false-a' +import { Route as testsResetScrollFalseBRouteImport } from './routes/(tests)/reset-scroll-false-b' +import { Route as testsResetScrollFalseCRouteImport } from './routes/(tests)/reset-scroll-false-c' +import { Route as testsSsrScrollKeyRouteImport } from './routes/(tests)/ssr-scroll-key' +import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' +import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' +import { Route as testsIssue7687IndexRouteImport } from './routes/(tests)/issue-7687.index' +import { Route as testsIssue7687DetailRouteImport } from './routes/(tests)/issue-7687.detail' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsWithSearchRoute = testsWithSearchRouteImport.update({ - id: '/(tests)/with-search', - path: '/with-search', +const testsHashScrollAboutRoute = testsHashScrollAboutRouteImport.update({ + id: '/(tests)/hash-scroll-about', + path: '/hash-scroll-about', getParentRoute: () => rootRouteImport, } as any) -const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ - id: '/(tests)/with-loader', - path: '/with-loader', +const testsHashScrollReproRoute = testsHashScrollReproRouteImport.update({ + id: '/(tests)/hash-scroll-repro', + path: '/hash-scroll-repro', getParentRoute: () => rootRouteImport, } as any) -const testsSsrScrollKeyRoute = testsSsrScrollKeyRouteImport.update({ - id: '/(tests)/ssr-scroll-key', - path: '/ssr-scroll-key', +const testsIssue7687Route = testsIssue7687RouteImport.update({ + id: '/(tests)/issue-7687', + path: '/issue-7687', getParentRoute: () => rootRouteImport, } as any) -const testsResetScrollFalseCRoute = testsResetScrollFalseCRouteImport.update({ - id: '/(tests)/reset-scroll-false-c', - path: '/reset-scroll-false-c', +const testsNestedScrollAwayRoute = testsNestedScrollAwayRouteImport.update({ + id: '/(tests)/nested-scroll-away', + path: '/nested-scroll-away', getParentRoute: () => rootRouteImport, } as any) -const testsResetScrollFalseBRoute = testsResetScrollFalseBRouteImport.update({ - id: '/(tests)/reset-scroll-false-b', - path: '/reset-scroll-false-b', +const testsNestedScrollCarryOverARoute = + testsNestedScrollCarryOverARouteImport.update({ + id: '/(tests)/nested-scroll-carry-over-a', + path: '/nested-scroll-carry-over-a', + getParentRoute: () => rootRouteImport, + } as any) +const testsNestedScrollCarryOverBRoute = + testsNestedScrollCarryOverBRouteImport.update({ + id: '/(tests)/nested-scroll-carry-over-b', + path: '/nested-scroll-carry-over-b', + getParentRoute: () => rootRouteImport, + } as any) +const testsNestedScrollSearchRoute = testsNestedScrollSearchRouteImport.update({ + id: '/(tests)/nested-scroll-search', + path: '/nested-scroll-search', + getParentRoute: () => rootRouteImport, +} as any) +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', getParentRoute: () => rootRouteImport, } as any) const testsResetScrollFalseARoute = testsResetScrollFalseARouteImport.update({ @@ -59,48 +79,47 @@ const testsResetScrollFalseARoute = testsResetScrollFalseARouteImport.update({ path: '/reset-scroll-false-a', getParentRoute: () => rootRouteImport, } as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', +const testsResetScrollFalseBRoute = testsResetScrollFalseBRouteImport.update({ + id: '/(tests)/reset-scroll-false-b', + path: '/reset-scroll-false-b', getParentRoute: () => rootRouteImport, } as any) -const testsNestedScrollSearchRoute = testsNestedScrollSearchRouteImport.update({ - id: '/(tests)/nested-scroll-search', - path: '/nested-scroll-search', +const testsResetScrollFalseCRoute = testsResetScrollFalseCRouteImport.update({ + id: '/(tests)/reset-scroll-false-c', + path: '/reset-scroll-false-c', getParentRoute: () => rootRouteImport, } as any) -const testsNestedScrollCarryOverBRoute = - testsNestedScrollCarryOverBRouteImport.update({ - id: '/(tests)/nested-scroll-carry-over-b', - path: '/nested-scroll-carry-over-b', - getParentRoute: () => rootRouteImport, - } as any) -const testsNestedScrollCarryOverARoute = - testsNestedScrollCarryOverARouteImport.update({ - id: '/(tests)/nested-scroll-carry-over-a', - path: '/nested-scroll-carry-over-a', - getParentRoute: () => rootRouteImport, - } as any) -const testsNestedScrollAwayRoute = testsNestedScrollAwayRouteImport.update({ - id: '/(tests)/nested-scroll-away', - path: '/nested-scroll-away', +const testsSsrScrollKeyRoute = testsSsrScrollKeyRouteImport.update({ + id: '/(tests)/ssr-scroll-key', + path: '/ssr-scroll-key', getParentRoute: () => rootRouteImport, } as any) -const testsHashScrollReproRoute = testsHashScrollReproRouteImport.update({ - id: '/(tests)/hash-scroll-repro', - path: '/hash-scroll-repro', +const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ + id: '/(tests)/with-loader', + path: '/with-loader', getParentRoute: () => rootRouteImport, } as any) -const testsHashScrollAboutRoute = testsHashScrollAboutRouteImport.update({ - id: '/(tests)/hash-scroll-about', - path: '/hash-scroll-about', +const testsWithSearchRoute = testsWithSearchRouteImport.update({ + id: '/(tests)/with-search', + path: '/with-search', getParentRoute: () => rootRouteImport, } as any) +const testsIssue7687IndexRoute = testsIssue7687IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => testsIssue7687Route, +} as any) +const testsIssue7687DetailRoute = testsIssue7687DetailRouteImport.update({ + id: '/detail', + path: '/detail', + getParentRoute: () => testsIssue7687Route, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/hash-scroll-about': typeof testsHashScrollAboutRoute '/hash-scroll-repro': typeof testsHashScrollReproRoute + '/issue-7687': typeof testsIssue7687RouteWithChildren '/nested-scroll-away': typeof testsNestedScrollAwayRoute '/nested-scroll-carry-over-a': typeof testsNestedScrollCarryOverARoute '/nested-scroll-carry-over-b': typeof testsNestedScrollCarryOverBRoute @@ -112,6 +131,8 @@ export interface FileRoutesByFullPath { '/ssr-scroll-key': typeof testsSsrScrollKeyRoute '/with-loader': typeof testsWithLoaderRoute '/with-search': typeof testsWithSearchRoute + '/issue-7687/detail': typeof testsIssue7687DetailRoute + '/issue-7687/': typeof testsIssue7687IndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -128,12 +149,15 @@ export interface FileRoutesByTo { '/ssr-scroll-key': typeof testsSsrScrollKeyRoute '/with-loader': typeof testsWithLoaderRoute '/with-search': typeof testsWithSearchRoute + '/issue-7687/detail': typeof testsIssue7687DetailRoute + '/issue-7687': typeof testsIssue7687IndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/(tests)/hash-scroll-about': typeof testsHashScrollAboutRoute '/(tests)/hash-scroll-repro': typeof testsHashScrollReproRoute + '/(tests)/issue-7687': typeof testsIssue7687RouteWithChildren '/(tests)/nested-scroll-away': typeof testsNestedScrollAwayRoute '/(tests)/nested-scroll-carry-over-a': typeof testsNestedScrollCarryOverARoute '/(tests)/nested-scroll-carry-over-b': typeof testsNestedScrollCarryOverBRoute @@ -145,6 +169,8 @@ export interface FileRoutesById { '/(tests)/ssr-scroll-key': typeof testsSsrScrollKeyRoute '/(tests)/with-loader': typeof testsWithLoaderRoute '/(tests)/with-search': typeof testsWithSearchRoute + '/(tests)/issue-7687/detail': typeof testsIssue7687DetailRoute + '/(tests)/issue-7687/': typeof testsIssue7687IndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -152,6 +178,7 @@ export interface FileRouteTypes { | '/' | '/hash-scroll-about' | '/hash-scroll-repro' + | '/issue-7687' | '/nested-scroll-away' | '/nested-scroll-carry-over-a' | '/nested-scroll-carry-over-b' @@ -163,6 +190,8 @@ export interface FileRouteTypes { | '/ssr-scroll-key' | '/with-loader' | '/with-search' + | '/issue-7687/detail' + | '/issue-7687/' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -179,11 +208,14 @@ export interface FileRouteTypes { | '/ssr-scroll-key' | '/with-loader' | '/with-search' + | '/issue-7687/detail' + | '/issue-7687' id: | '__root__' | '/' | '/(tests)/hash-scroll-about' | '/(tests)/hash-scroll-repro' + | '/(tests)/issue-7687' | '/(tests)/nested-scroll-away' | '/(tests)/nested-scroll-carry-over-a' | '/(tests)/nested-scroll-carry-over-b' @@ -195,12 +227,15 @@ export interface FileRouteTypes { | '/(tests)/ssr-scroll-key' | '/(tests)/with-loader' | '/(tests)/with-search' + | '/(tests)/issue-7687/detail' + | '/(tests)/issue-7687/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute testsHashScrollAboutRoute: typeof testsHashScrollAboutRoute testsHashScrollReproRoute: typeof testsHashScrollReproRoute + testsIssue7687Route: typeof testsIssue7687RouteWithChildren testsNestedScrollAwayRoute: typeof testsNestedScrollAwayRoute testsNestedScrollCarryOverARoute: typeof testsNestedScrollCarryOverARoute testsNestedScrollCarryOverBRoute: typeof testsNestedScrollCarryOverBRoute @@ -223,46 +258,53 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/with-search': { - id: '/(tests)/with-search' - path: '/with-search' - fullPath: '/with-search' - preLoaderRoute: typeof testsWithSearchRouteImport + '/(tests)/hash-scroll-about': { + id: '/(tests)/hash-scroll-about' + path: '/hash-scroll-about' + fullPath: '/hash-scroll-about' + preLoaderRoute: typeof testsHashScrollAboutRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/with-loader': { - id: '/(tests)/with-loader' - path: '/with-loader' - fullPath: '/with-loader' - preLoaderRoute: typeof testsWithLoaderRouteImport + '/(tests)/hash-scroll-repro': { + id: '/(tests)/hash-scroll-repro' + path: '/hash-scroll-repro' + fullPath: '/hash-scroll-repro' + preLoaderRoute: typeof testsHashScrollReproRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/ssr-scroll-key': { - id: '/(tests)/ssr-scroll-key' - path: '/ssr-scroll-key' - fullPath: '/ssr-scroll-key' - preLoaderRoute: typeof testsSsrScrollKeyRouteImport + '/(tests)/issue-7687': { + id: '/(tests)/issue-7687' + path: '/issue-7687' + fullPath: '/issue-7687' + preLoaderRoute: typeof testsIssue7687RouteImport parentRoute: typeof rootRouteImport } - '/(tests)/reset-scroll-false-c': { - id: '/(tests)/reset-scroll-false-c' - path: '/reset-scroll-false-c' - fullPath: '/reset-scroll-false-c' - preLoaderRoute: typeof testsResetScrollFalseCRouteImport + '/(tests)/nested-scroll-away': { + id: '/(tests)/nested-scroll-away' + path: '/nested-scroll-away' + fullPath: '/nested-scroll-away' + preLoaderRoute: typeof testsNestedScrollAwayRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/reset-scroll-false-b': { - id: '/(tests)/reset-scroll-false-b' - path: '/reset-scroll-false-b' - fullPath: '/reset-scroll-false-b' - preLoaderRoute: typeof testsResetScrollFalseBRouteImport + '/(tests)/nested-scroll-carry-over-a': { + id: '/(tests)/nested-scroll-carry-over-a' + path: '/nested-scroll-carry-over-a' + fullPath: '/nested-scroll-carry-over-a' + preLoaderRoute: typeof testsNestedScrollCarryOverARouteImport parentRoute: typeof rootRouteImport } - '/(tests)/reset-scroll-false-a': { - id: '/(tests)/reset-scroll-false-a' - path: '/reset-scroll-false-a' - fullPath: '/reset-scroll-false-a' - preLoaderRoute: typeof testsResetScrollFalseARouteImport + '/(tests)/nested-scroll-carry-over-b': { + id: '/(tests)/nested-scroll-carry-over-b' + path: '/nested-scroll-carry-over-b' + fullPath: '/nested-scroll-carry-over-b' + preLoaderRoute: typeof testsNestedScrollCarryOverBRouteImport + parentRoute: typeof rootRouteImport + } + '/(tests)/nested-scroll-search': { + id: '/(tests)/nested-scroll-search' + path: '/nested-scroll-search' + fullPath: '/nested-scroll-search' + preLoaderRoute: typeof testsNestedScrollSearchRouteImport parentRoute: typeof rootRouteImport } '/(tests)/normal-page': { @@ -272,55 +314,84 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/nested-scroll-search': { - id: '/(tests)/nested-scroll-search' - path: '/nested-scroll-search' - fullPath: '/nested-scroll-search' - preLoaderRoute: typeof testsNestedScrollSearchRouteImport + '/(tests)/reset-scroll-false-a': { + id: '/(tests)/reset-scroll-false-a' + path: '/reset-scroll-false-a' + fullPath: '/reset-scroll-false-a' + preLoaderRoute: typeof testsResetScrollFalseARouteImport parentRoute: typeof rootRouteImport } - '/(tests)/nested-scroll-carry-over-b': { - id: '/(tests)/nested-scroll-carry-over-b' - path: '/nested-scroll-carry-over-b' - fullPath: '/nested-scroll-carry-over-b' - preLoaderRoute: typeof testsNestedScrollCarryOverBRouteImport + '/(tests)/reset-scroll-false-b': { + id: '/(tests)/reset-scroll-false-b' + path: '/reset-scroll-false-b' + fullPath: '/reset-scroll-false-b' + preLoaderRoute: typeof testsResetScrollFalseBRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/nested-scroll-carry-over-a': { - id: '/(tests)/nested-scroll-carry-over-a' - path: '/nested-scroll-carry-over-a' - fullPath: '/nested-scroll-carry-over-a' - preLoaderRoute: typeof testsNestedScrollCarryOverARouteImport + '/(tests)/reset-scroll-false-c': { + id: '/(tests)/reset-scroll-false-c' + path: '/reset-scroll-false-c' + fullPath: '/reset-scroll-false-c' + preLoaderRoute: typeof testsResetScrollFalseCRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/nested-scroll-away': { - id: '/(tests)/nested-scroll-away' - path: '/nested-scroll-away' - fullPath: '/nested-scroll-away' - preLoaderRoute: typeof testsNestedScrollAwayRouteImport + '/(tests)/ssr-scroll-key': { + id: '/(tests)/ssr-scroll-key' + path: '/ssr-scroll-key' + fullPath: '/ssr-scroll-key' + preLoaderRoute: typeof testsSsrScrollKeyRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/hash-scroll-repro': { - id: '/(tests)/hash-scroll-repro' - path: '/hash-scroll-repro' - fullPath: '/hash-scroll-repro' - preLoaderRoute: typeof testsHashScrollReproRouteImport + '/(tests)/with-loader': { + id: '/(tests)/with-loader' + path: '/with-loader' + fullPath: '/with-loader' + preLoaderRoute: typeof testsWithLoaderRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/hash-scroll-about': { - id: '/(tests)/hash-scroll-about' - path: '/hash-scroll-about' - fullPath: '/hash-scroll-about' - preLoaderRoute: typeof testsHashScrollAboutRouteImport + '/(tests)/with-search': { + id: '/(tests)/with-search' + path: '/with-search' + fullPath: '/with-search' + preLoaderRoute: typeof testsWithSearchRouteImport parentRoute: typeof rootRouteImport } + '/(tests)/issue-7687/': { + id: '/(tests)/issue-7687/' + path: '/' + fullPath: '/issue-7687/' + preLoaderRoute: typeof testsIssue7687IndexRouteImport + parentRoute: typeof testsIssue7687Route + } + '/(tests)/issue-7687/detail': { + id: '/(tests)/issue-7687/detail' + path: '/detail' + fullPath: '/issue-7687/detail' + preLoaderRoute: typeof testsIssue7687DetailRouteImport + parentRoute: typeof testsIssue7687Route + } } } +interface testsIssue7687RouteChildren { + testsIssue7687DetailRoute: typeof testsIssue7687DetailRoute + testsIssue7687IndexRoute: typeof testsIssue7687IndexRoute +} + +const testsIssue7687RouteChildren: testsIssue7687RouteChildren = { + testsIssue7687DetailRoute: testsIssue7687DetailRoute, + testsIssue7687IndexRoute: testsIssue7687IndexRoute, +} + +const testsIssue7687RouteWithChildren = testsIssue7687Route._addFileChildren( + testsIssue7687RouteChildren, +) + const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, testsHashScrollAboutRoute: testsHashScrollAboutRoute, testsHashScrollReproRoute: testsHashScrollReproRoute, + testsIssue7687Route: testsIssue7687RouteWithChildren, testsNestedScrollAwayRoute: testsNestedScrollAwayRoute, testsNestedScrollCarryOverARoute: testsNestedScrollCarryOverARoute, testsNestedScrollCarryOverBRoute: testsNestedScrollCarryOverBRoute, diff --git a/e2e/react-start/scroll-restoration/src/router.tsx b/e2e/react-start/scroll-restoration/src/router.tsx index eb81f5bfa9..27d68369db 100644 --- a/e2e/react-start/scroll-restoration/src/router.tsx +++ b/e2e/react-start/scroll-restoration/src/router.tsx @@ -7,13 +7,32 @@ export function getRouter() { const router = createRouter({ routeTree, scrollRestoration: true, - scrollToTopSelectors: ['[data-scroll-restoration-id="carry-over-reset"]'], + scrollToTopSelectors: [ + '[data-scroll-restoration-id="carry-over-reset"]', + '#hash-scroll-nested', + '#hash-scroll-reset-target', + '#issue-7687-reset-probe', + '#issue-7687-scroller', + () => + typeof document === 'undefined' + ? undefined + : document.getElementById('issue-7687-stale-selector'), + '#ssr-scroll-key-nested', + ], getScrollRestorationKey: (location) => { if (location.pathname === '/ssr-scroll-key') { return 'ssr-scroll-key' } if (location.pathname === '/hash-scroll-repro') { + const scrollKey = + 'scrollKey' in location.search ? location.search.scrollKey : undefined + return typeof scrollKey === 'string' + ? `${location.pathname}:${scrollKey}` + : location.pathname + } + + if (location.pathname.startsWith('/issue-7687')) { return location.pathname } diff --git a/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx b/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx index 2c2748e6a5..1ee24f4b1f 100644 --- a/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx +++ b/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-about.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { createFileRoute } from '@tanstack/react-router' +import { Link, createFileRoute } from '@tanstack/react-router' import { sleep } from '~/utils/posts' export const Route = createFileRoute('/(tests)/hash-scroll-about')({ @@ -17,6 +17,20 @@ function Component() {

    This route exists so hover preloading can reproduce the hash scroll bug.

    + + Return to #one + + + Return to the destination key +

) } diff --git a/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx b/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx index 1017b63bc3..c2c681247a 100644 --- a/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx +++ b/e2e/react-start/scroll-restoration/src/routes/(tests)/hash-scroll-repro.tsx @@ -1,8 +1,13 @@ import * as React from 'react' import { Link, createFileRoute, useRouter } from '@tanstack/react-router' +import { z } from 'zod' import { sleep } from '~/utils/posts' export const Route = createFileRoute('/(tests)/hash-scroll-repro')({ + validateSearch: z.object({ + scrollKey: z.string().optional(), + invalidateOnMount: z.boolean().optional(), + }), loader: async () => { await sleep(50) return null @@ -14,7 +19,20 @@ const sectionIds = ['one', 'two', 'three', 'four', 'five'] as const function Component() { const router = useRouter() + const { invalidateOnMount } = Route.useSearch() const [invalidateCount, setInvalidateCount] = React.useState(0) + const invalidatedOnMount = React.useRef(false) + + React.useLayoutEffect(() => { + if (!invalidateOnMount || invalidatedOnMount.current) { + return + } + + invalidatedOnMount.current = true + void router.invalidate().then(() => { + setInvalidateCount((count) => count + 1) + }) + }, [invalidateOnMount, router]) return (
@@ -61,6 +79,24 @@ function Component() { > #four no reset + + #one different key + + + #one and invalidate in layout effect +
@@ -74,6 +110,16 @@ function Component() { ))}
+
+ {Array.from({ length: 20 }).map((_, i) => ( +
Hash reset target row {i}
+ ))} +
+
{sectionIds.map((sectionId) => (
+

issue-7687-detail

+ {Array.from({ length: 40 }).map((_, i) => ( +
Detail row {i}
+ ))} +
+ ) +} diff --git a/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.index.tsx b/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.index.tsx new file mode 100644 index 0000000000..82450e43f3 --- /dev/null +++ b/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.index.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/(tests)/issue-7687/')({ + component: Component, +}) + +const visitKey = 'issue-7687-list-visits' + +function ResetProbe() { + const ref = React.useRef(null) + + React.useLayoutEffect(() => { + const visits = Number(window.sessionStorage.getItem(visitKey) ?? 0) + window.sessionStorage.setItem(visitKey, String(visits + 1)) + + if (visits > 0 && ref.current) { + ref.current.scrollTop = 80 + } + }, []) + + return ( +
+ {Array.from({ length: 20 }).map((_, i) => ( +
Reset probe row {i}
+ ))} +
+ ) +} + +function Component() { + return ( +
+

issue-7687-list

+ + {Array.from({ length: 40 }).map((_, i) => ( +
List row {i}
+ ))} +
+ ) +} diff --git a/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.tsx b/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.tsx new file mode 100644 index 0000000000..f7091cd829 --- /dev/null +++ b/e2e/react-start/scroll-restoration/src/routes/(tests)/issue-7687.tsx @@ -0,0 +1,49 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/(tests)/issue-7687')({ + component: Component, +}) + +function Component() { + return ( +
+ + +
+ +
+ +
+ {Array.from({ length: 20 }).map((_, i) => ( +
Stale selector row {i}
+ ))} +
+ +
+
+ ) +} diff --git a/e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx b/e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx index e224ed9137..dd7375660b 100644 --- a/e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx +++ b/e2e/react-start/scroll-restoration/src/routes/(tests)/ssr-scroll-key.tsx @@ -7,9 +7,26 @@ export const Route = createFileRoute('/(tests)/ssr-scroll-key')({ }) function Component() { + const [hydrated, setHydrated] = React.useState(false) + + React.useEffect(() => { + setHydrated(true) + }, []) + return (

ssr-scroll-key

+ {hydrated ? : null} +
+ {Array.from({ length: 20 }).map((_, i) => ( +
Nested SSR row {i}
+ ))} +
) diff --git a/e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts b/e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts index d203ea8c26..7a6aa72161 100644 --- a/e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts +++ b/e2e/react-start/scroll-restoration/tests/hash-scroll-repro.spec.ts @@ -103,43 +103,59 @@ test('hash navigation wins over stale same-tab scroll restoration entries', asyn expect(scrollYAfterHashNavigation).toBeLessThan(staleScrollY) }) -test('hash navigation still runs when only nested scroll entries restore', async ({ +test('hash navigation wins when the destination invalidates in a layout effect', async ({ page, }) => { await goToRepro(page) + const staleScrollY = await scrollUpFromHashTarget(page) - const nestedScrollTop = await page.evaluate(() => { - const nested = document.querySelector('[data-testid="hash-scroll-nested"]') - if (!(nested instanceof HTMLElement)) { - throw new Error('Missing nested scroller') - } + await page.reload() + await page.waitForLoadState('networkidle') + await expect( + page.getByTestId('hash-scroll-repro-invalidate-count'), + ).toHaveText('Invalidate count: 0') + + await page.getByTestId('hash-scroll-layout-invalidate-link').click() + await expect(page).toHaveURL(/invalidateOnMount=true#one$/) + await expect( + page.getByTestId('hash-scroll-repro-invalidate-count'), + ).toHaveText('Invalidate count: 1') + + await expect(page.getByTestId('hash-scroll-section-one')).toBeInViewport() + await expect( + page.getByTestId('hash-scroll-section-five'), + ).not.toBeInViewport() - nested.scrollTop = 80 - window.dispatchEvent(new PageTransitionEvent('pagehide')) - return nested.scrollTop + const scrollYAfterHashNavigation = await page.evaluate(() => window.scrollY) + expect(scrollYAfterHashNavigation).toBeLessThan(staleScrollY) +}) + +test('hash navigation still runs when a configured nested target restores', async ({ + page, +}) => { + await goToRepro(page) + + const nested = page.getByTestId('hash-scroll-nested') + const cachedScrollTop = await nested.evaluate(async (element) => { + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + await new Promise((resolve) => requestAnimationFrame(resolve)) + return element.scrollTop }) - expect(nestedScrollTop).toBeGreaterThan(0) + expect(cachedScrollTop).toBeGreaterThan(0) - await page.reload() - await page.waitForLoadState('networkidle') + await page.getByTestId('hash-scroll-about-link').click() await expect( - page.getByTestId('hash-scroll-repro-invalidate-count'), + page.getByRole('heading', { name: 'hash-scroll-about' }), ).toBeVisible() - await page.getByTestId('hash-scroll-section-one-link').click() + await page.getByTestId('hash-scroll-restore-link').click() await expect(page.getByTestId('hash-scroll-section-one')).toBeInViewport() await expect - .poll(async () => { - return page.evaluate(() => { - const nested = document.querySelector( - '[data-testid="hash-scroll-nested"]', - ) - return nested instanceof HTMLElement ? nested.scrollTop : 0 - }) - }) - .toBe(nestedScrollTop) + .poll(async () => nested.evaluate((element) => element.scrollTop)) + .toBe(cachedScrollTop) }) test('hash navigation scrolls when resetScroll is false', async ({ page }) => { @@ -157,3 +173,60 @@ test('hash navigation scrolls when resetScroll is false', async ({ page }) => { .poll(async () => page.evaluate(() => window.scrollY)) .toBeGreaterThan(scrollYBeforeHashNavigation) }) + +test('hash navigation does not reset configured nested targets', async ({ + page, +}) => { + await goToRepro(page) + + const resetTarget = page.getByTestId('hash-scroll-reset-target') + const scrollTop = await resetTarget.evaluate((element) => { + element.scrollTop = 80 + return element.scrollTop + }) + expect(scrollTop).toBeGreaterThan(0) + + await page.getByTestId('hash-scroll-section-one-link').click() + await expect(page).toHaveURL(/#one$/) + await expect(page.getByTestId('hash-scroll-section-one')).toBeInViewport() + await expect + .poll(async () => resetTarget.evaluate((el) => el.scrollTop)) + .toBe(scrollTop) +}) + +test('hash navigation does not carry configured targets into a different key', async ({ + page, +}) => { + await goToRepro(page) + + const resetTarget = page.getByTestId('hash-scroll-reset-target') + const sourceScrollTop = await resetTarget.evaluate(async (element) => { + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + await new Promise((resolve) => requestAnimationFrame(resolve)) + return element.scrollTop + }) + expect(sourceScrollTop).toBeGreaterThan(0) + + await page.getByTestId('hash-scroll-different-key-link').click() + await expect(page).toHaveURL(/scrollKey=destination#one$/) + await expect(page.getByTestId('hash-scroll-section-one')).toBeInViewport() + await expect + .poll(async () => resetTarget.evaluate((element) => element.scrollTop)) + .toBe(sourceScrollTop) + + await page.getByTestId('hash-scroll-about-link').click() + await expect( + page.getByRole('heading', { name: 'hash-scroll-about' }), + ).toBeVisible() + + await page.getByTestId('hash-scroll-destination-link').click() + await expect(page).toHaveURL(/scrollKey=destination$/) + await expect + .poll(async () => + page + .getByTestId('hash-scroll-reset-target') + .evaluate((element) => element.scrollTop), + ) + .toBe(0) +}) diff --git a/e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts b/e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts new file mode 100644 index 0000000000..85a5bd5d43 --- /dev/null +++ b/e2e/react-start/scroll-restoration/tests/issue-7687.spec.ts @@ -0,0 +1,242 @@ +import { expect, test } from '@playwright/test' +import type { Page } from '@playwright/test' + +async function goToList(page: Page) { + await page.goto('/issue-7687') + await page.waitForLoadState('networkidle') + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() +} + +test('restores a configured nested target independently from the window', async ({ + page, +}) => { + await goToList(page) + + const scroller = page.getByTestId('issue-7687-scroller') + const scrollTop = await scroller.evaluate((element) => { + element.scrollTop = 400 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + return element.scrollTop + }) + + expect(scrollTop).toBeGreaterThan(0) + expect(await page.evaluate(() => window.scrollY)).toBe(0) + + await page.getByTestId('issue-7687-detail-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + await expect.poll(async () => scroller.evaluate((el) => el.scrollTop)).toBe(0) + + const outgoingWindowScrollY = await page.evaluate(() => { + window.scrollTo(0, 600) + window.dispatchEvent(new Event('scroll')) + return window.scrollY + }) + expect(outgoingWindowScrollY).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-list-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() + + await expect + .poll(async () => scroller.evaluate((el) => el.scrollTop)) + .toBe(scrollTop) + await expect.poll(async () => page.evaluate(() => window.scrollY)).toBe(0) + await expect + .poll(async () => + page.getByTestId('issue-7687-reset-probe').evaluate((el) => el.scrollTop), + ) + .toBe(0) +}) + +test('resets an uncached configured target when the window restores', async ({ + page, +}) => { + await goToList(page) + + const windowScrollY = await page.evaluate(() => { + window.scrollTo(0, 600) + window.dispatchEvent(new Event('scroll')) + return window.scrollY + }) + expect(windowScrollY).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-detail-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + await expect.poll(async () => page.evaluate(() => window.scrollY)).toBe(0) + + await page.getByTestId('issue-7687-list-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() + + await expect + .poll(async () => page.evaluate(() => window.scrollY)) + .toBe(windowScrollY) + await expect + .poll(async () => + page.getByTestId('issue-7687-reset-probe').evaluate((el) => el.scrollTop), + ) + .toBe(0) +}) + +test('restores and resets nested targets independently on browser back', async ({ + page, +}) => { + await goToList(page) + + const cachedScrollTop = await page + .getByTestId('issue-7687-scroller') + .evaluate(async (element) => { + element.scrollTop = 400 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + await new Promise((resolve) => requestAnimationFrame(resolve)) + return element.scrollTop + }) + expect(cachedScrollTop).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-detail-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + + await page.goBack() + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() + + await expect + .poll(async () => + page + .getByTestId('issue-7687-scroller') + .evaluate((element) => element.scrollTop), + ) + .toBe(cachedScrollTop) + await expect + .poll(async () => + page + .getByTestId('issue-7687-reset-probe') + .evaluate((element) => element.scrollTop), + ) + .toBe(0) +}) + +test('resets a live configured target when its cached selector becomes stale', async ({ + page, +}) => { + await goToList(page) + + const target = page.getByTestId('issue-7687-stale-selector') + const cachedScrollTop = await target.evaluate(async (element) => { + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + await new Promise((resolve) => requestAnimationFrame(resolve)) + return element.scrollTop + }) + expect(cachedScrollTop).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-detail-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + await expect.poll(async () => target.evaluate((el) => el.scrollTop)).toBe(0) + + const outgoingScrollTop = await target.evaluate(async (element) => { + element.dataset.scrollRestorationId = 'issue-7687-stale-current' + element.scrollTop = 40 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + await new Promise((resolve) => requestAnimationFrame(resolve)) + return element.scrollTop + }) + expect(outgoingScrollTop).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-list-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() + + await expect.poll(async () => target.evaluate((el) => el.scrollTop)).toBe(0) +}) + +test('resetScroll false preserves window and configured nested targets', async ({ + page, +}) => { + await goToList(page) + + const positions = await page.evaluate(() => { + const scroller = document.querySelector( + '[data-testid="issue-7687-scroller"]', + ) + if (!(scroller instanceof HTMLElement)) { + throw new Error('Missing issue-7687 scroller') + } + + scroller.scrollTop = 400 + scroller.dispatchEvent(new Event('scroll', { bubbles: true })) + window.scrollTo(0, 600) + window.dispatchEvent(new Event('scroll')) + + return { nested: scroller.scrollTop, window: window.scrollY } + }) + + expect(positions.nested).toBeGreaterThan(0) + expect(positions.window).toBeGreaterThan(0) + + await page.getByTestId('issue-7687-detail-no-reset-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + + await expect + .poll(async () => page.evaluate(() => window.scrollY)) + .toBe(positions.window) + await expect + .poll(async () => + page.getByTestId('issue-7687-scroller').evaluate((el) => el.scrollTop), + ) + .toBe(positions.nested) + + await page.getByTestId('issue-7687-list-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-list' }), + ).toBeVisible() + + const changedPositions = await page.evaluate(async () => { + const scroller = document.querySelector( + '[data-testid="issue-7687-scroller"]', + ) + if (!(scroller instanceof HTMLElement)) { + throw new Error('Missing issue-7687 scroller') + } + + scroller.scrollTop = 200 + scroller.dispatchEvent(new Event('scroll', { bubbles: true })) + window.scrollTo(0, 300) + window.dispatchEvent(new Event('scroll')) + await new Promise((resolve) => requestAnimationFrame(resolve)) + + return { nested: scroller.scrollTop, window: window.scrollY } + }) + + expect(changedPositions.nested).not.toBe(positions.nested) + expect(changedPositions.window).not.toBe(positions.window) + + await page.getByTestId('issue-7687-detail-link').click() + await expect( + page.getByRole('heading', { name: 'issue-7687-detail' }), + ).toBeVisible() + await expect + .poll(async () => page.evaluate(() => window.scrollY)) + .toBe(positions.window) + await expect + .poll(async () => + page.getByTestId('issue-7687-scroller').evaluate((el) => el.scrollTop), + ) + .toBe(positions.nested) +}) diff --git a/e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts b/e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts index 737695a0cf..eb61a279d2 100644 --- a/e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts +++ b/e2e/react-start/scroll-restoration/tests/ssr-scroll-key.spec.ts @@ -38,3 +38,77 @@ test('SSR scroll restoration uses a custom restoration key', async ({ .poll(async () => page.evaluate(() => window.scrollY)) .toBe(customKeyScrollY) }) + +test('SSR restores a configured nested target without client JavaScript', async ({ + page, +}) => { + const nestedScrollTop = 80 + + await page.addInitScript( + ({ nestedScrollTop, storageKey }) => { + window.sessionStorage.setItem( + storageKey, + JSON.stringify({ + 'ssr-scroll-key': { + '[data-scroll-restoration-id="ssr-scroll-key-nested"]': { + scrollX: 0, + scrollY: nestedScrollTop, + }, + }, + }), + ) + }, + { nestedScrollTop, storageKey }, + ) + await page.route(/\/assets\/.*\.js$/, (route) => route.abort()) + + await page.goto('/ssr-scroll-key', { waitUntil: 'domcontentloaded' }) + await expect( + page.getByRole('heading', { name: 'ssr-scroll-key' }), + ).toBeVisible() + await expect(page.getByTestId('ssr-scroll-key-hydrated')).toHaveCount(0) + await expect + .poll(async () => + page + .getByTestId('ssr-scroll-key-nested') + .evaluate((element) => element.scrollTop), + ) + .toBe(nestedScrollTop) +}) + +test('hydration does not reset a configured nested target restored by SSR', async ({ + page, +}) => { + const nestedScrollTop = 80 + + await page.addInitScript( + ({ nestedScrollTop, storageKey }) => { + window.sessionStorage.setItem( + storageKey, + JSON.stringify({ + 'ssr-scroll-key': { + '[data-scroll-restoration-id="ssr-scroll-key-nested"]': { + scrollX: 0, + scrollY: nestedScrollTop, + }, + }, + }), + ) + }, + { nestedScrollTop, storageKey }, + ) + + await page.goto('/ssr-scroll-key') + await page.waitForLoadState('networkidle') + await expect( + page.getByRole('heading', { name: 'ssr-scroll-key' }), + ).toBeVisible() + await expect(page.getByTestId('ssr-scroll-key-hydrated')).toBeAttached() + await expect + .poll(async () => + page + .getByTestId('ssr-scroll-key-nested') + .evaluate((element) => element.scrollTop), + ) + .toBe(nestedScrollTop) +}) diff --git a/e2e/react-start/selective-ssr/package.json b/e2e/react-start/selective-ssr/package.json index bd2ab622bd..1eb903c183 100644 --- a/e2e/react-start/selective-ssr/package.json +++ b/e2e/react-start/selective-ssr/package.json @@ -28,7 +28,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/selective-ssr/src/routeTree.gen.ts b/e2e/react-start/selective-ssr/src/routeTree.gen.ts index 188cd5c91a..5c7daa4701 100644 --- a/e2e/react-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/react-start/selective-ssr/src/routeTree.gen.ts @@ -9,20 +9,26 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as Issue4614RouteImport } from './routes/issue-4614' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const Issue4614Route = Issue4614RouteImport.update({ + id: '/issue-4614', + path: '/issue-4614', + getParentRoute: () => rootRouteImport, +} as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', @@ -31,42 +37,39 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/issue-4614': typeof Issue4614Route '/posts': typeof PostsRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/issue-4614': typeof Issue4614Route '/posts': typeof PostsRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/issue-4614': typeof Issue4614Route '/posts': typeof PostsRouteWithChildren '/posts/$postId': typeof PostsPostIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/posts' | '/posts/$postId' + fullPaths: '/' | '/issue-4614' | '/posts' | '/posts/$postId' fileRoutesByTo: FileRoutesByTo - to: '/' | '/posts' | '/posts/$postId' - id: '__root__' | '/' | '/posts' | '/posts/$postId' + to: '/' | '/issue-4614' | '/posts' | '/posts/$postId' + id: '__root__' | '/' | '/issue-4614' | '/posts' | '/posts/$postId' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + Issue4614Route: typeof Issue4614Route PostsRoute: typeof PostsRouteWithChildren } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +77,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/issue-4614': { + id: '/issue-4614' + path: '/issue-4614' + fullPath: '/issue-4614' + preLoaderRoute: typeof Issue4614RouteImport + parentRoute: typeof rootRouteImport + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -96,6 +113,7 @@ const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren) const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + Issue4614Route: Issue4614Route, PostsRoute: PostsRouteWithChildren, } export const routeTree = rootRouteImport diff --git a/e2e/react-start/selective-ssr/src/router.tsx b/e2e/react-start/selective-ssr/src/router.tsx index 82a730704a..d227bfa5d2 100644 --- a/e2e/react-start/selective-ssr/src/router.tsx +++ b/e2e/react-start/selective-ssr/src/router.tsx @@ -5,6 +5,9 @@ export function getRouter() { const router = createRouter({ routeTree, scrollRestoration: true, + defaultPreload: 'intent', + defaultPreloadDelay: 0, + defaultPreloadStaleTime: 0, }) return router diff --git a/e2e/react-start/selective-ssr/src/routes/__root.tsx b/e2e/react-start/selective-ssr/src/routes/__root.tsx index 1672e3ff68..3465abf298 100644 --- a/e2e/react-start/selective-ssr/src/routes/__root.tsx +++ b/e2e/react-start/selective-ssr/src/routes/__root.tsx @@ -9,10 +9,10 @@ import { createRootRoute, useRouterState, } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { z } from 'zod' import { ssrSchema } from '~/search' import appCss from '~/styles/app.css?url' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' export const Route = createRootRoute({ head: () => ({ @@ -30,7 +30,11 @@ export const Route = createRootRoute({ ], links: [{ rel: 'stylesheet', href: appCss }], }), - validateSearch: z.object({ root: ssrSchema }), + validateSearch: z.object({ + root: ssrSchema, + issue4614: z.string().optional(), + }), + loaderDeps: ({ search }) => ({ issue4614: search.issue4614 }), ssr: ({ search }) => { if (typeof window !== 'undefined') { const error = `ssr() for ${Route.id} should not be called on the client` @@ -41,7 +45,7 @@ export const Route = createRootRoute({ return search.value.root?.ssr } }, - beforeLoad: ({ search }) => { + beforeLoad: ({ search, location, cause, preload }) => { console.log( `beforeLoad for ${Route.id} called on the ${typeof window !== 'undefined' ? 'client' : 'server'}`, ) @@ -53,9 +57,21 @@ export const Route = createRootRoute({ console.error(error) throw new Error(error) } + const root = typeof window === 'undefined' ? 'server' : 'client' + const issue4614Context = `${root}:${search.issue4614 ?? 'cached'}` + if (typeof window !== 'undefined' && location.pathname === '/issue-4614') { + const calls = ((globalThis as any).__issue4614RootBeforeLoads ??= []) + calls.push({ + cause, + preload, + root, + issue4614Context, + }) + } return { - root: typeof window === 'undefined' ? 'server' : 'client', + root, search, + issue4614Context, } }, loader: ({ context }) => { @@ -133,6 +149,22 @@ function RootDocument({ children }: { children: React.ReactNode }) { > Home + + Issue 4614 cached parent + + + Issue 4614 reloaded parent +

diff --git a/e2e/react-start/selective-ssr/src/routes/issue-4614.tsx b/e2e/react-start/selective-ssr/src/routes/issue-4614.tsx new file mode 100644 index 0000000000..7e59b8d0ac --- /dev/null +++ b/e2e/react-start/selective-ssr/src/routes/issue-4614.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/issue-4614')({ + ssr: false, + beforeLoad: ({ context, cause, preload, search }) => { + ;(globalThis as any).__issue4614TargetBeforeLoad = { + cause, + preload, + rootContext: context.root, + issue4614Context: context.issue4614Context, + scenario: search.issue4614 ?? 'cached', + } + }, + component: () =>
Issue 4614
, +}) diff --git a/e2e/react-start/selective-ssr/tests/app.spec.ts b/e2e/react-start/selective-ssr/tests/app.spec.ts index aea216d506..89eff32cae 100644 --- a/e2e/react-start/selective-ssr/tests/app.spec.ts +++ b/e2e/react-start/selective-ssr/tests/app.spec.ts @@ -4,6 +4,75 @@ import { test } from '@tanstack/router-e2e-utils' const testCount = 7 test.describe('selective ssr', () => { + test('#4614: cached parent loader data does not cache its beforeLoad context', async ({ + page, + }) => { + await page.goto('/') + await page.getByTestId('issue-4614-cached-link').hover() + + await expect + .poll(() => + page.evaluate(() => (globalThis as any).__issue4614TargetBeforeLoad), + ) + .not.toBeUndefined() + + const { rootBeforeLoads, targetBeforeLoad } = await page.evaluate(() => { + const rootBeforeLoads = + (globalThis as any).__issue4614RootBeforeLoads ?? [] + return { + rootBeforeLoads, + targetBeforeLoad: (globalThis as any).__issue4614TargetBeforeLoad, + } + }) + + expect(rootBeforeLoads).toEqual([ + { + cause: 'preload', + preload: true, + root: 'client', + issue4614Context: 'client:cached', + }, + ]) + expect(targetBeforeLoad).toEqual({ + cause: 'preload', + preload: true, + rootContext: 'client', + issue4614Context: 'client:cached', + scenario: 'cached', + }) + }) + + test('new loaderDeps match generation propagates fresh parent context to child (control)', async ({ + page, + }) => { + await page.goto('/') + await page.getByTestId('issue-4614-reload-link').hover() + + await expect + .poll(() => + page.evaluate(() => (globalThis as any).__issue4614RootBeforeLoads), + ) + .toEqual([ + { + cause: 'preload', + preload: true, + root: 'client', + issue4614Context: 'client:reload', + }, + ]) + await expect + .poll(() => + page.evaluate(() => (globalThis as any).__issue4614TargetBeforeLoad), + ) + .toEqual({ + cause: 'preload', + preload: true, + rootContext: 'client', + issue4614Context: 'client:reload', + scenario: 'reload', + }) + }) + test('testcount matches', async ({ page }) => { await page.goto('/') diff --git a/e2e/react-start/serialization-adapters/package.json b/e2e/react-start/serialization-adapters/package.json index f3cf7e6512..0e0d157733 100644 --- a/e2e/react-start/serialization-adapters/package.json +++ b/e2e/react-start/serialization-adapters/package.json @@ -28,7 +28,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/serialization-adapters/src/routeTree.gen.ts b/e2e/react-start/serialization-adapters/src/routeTree.gen.ts index 3536724473..1b21d4200b 100644 --- a/e2e/react-start/serialization-adapters/src/routeTree.gen.ts +++ b/e2e/react-start/serialization-adapters/src/routeTree.gen.ts @@ -10,26 +10,33 @@ 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 SsrNestedRouteImport } from './routes/ssr/nested' -import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' -import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' -import { Route as ServerFunctionLateRawStreamRouteImport } from './routes/server-function/late-raw-stream' import { Route as ServerFunctionCustomErrorRouteImport } from './routes/server-function/custom-error' +import { Route as ServerFunctionLateRawStreamRouteImport } from './routes/server-function/late-raw-stream' +import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' +import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' +import { Route as SsrNestedRouteImport } from './routes/ssr/nested' +import { Route as SsrStreamRouteImport } from './routes/ssr/stream' 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 SsrNestedRoute = SsrNestedRouteImport.update({ - id: '/ssr/nested', - path: '/ssr/nested', +const ServerFunctionCustomErrorRoute = + ServerFunctionCustomErrorRouteImport.update({ + id: '/server-function/custom-error', + path: '/server-function/custom-error', + getParentRoute: () => rootRouteImport, + } as any) +const ServerFunctionLateRawStreamRoute = + ServerFunctionLateRawStreamRouteImport.update({ + id: '/server-function/late-raw-stream', + path: '/server-function/late-raw-stream', + getParentRoute: () => rootRouteImport, + } as any) +const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ + id: '/server-function/nested', + path: '/server-function/nested', getParentRoute: () => rootRouteImport, } as any) const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ @@ -37,23 +44,16 @@ const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ path: '/ssr/data-only', getParentRoute: () => rootRouteImport, } as any) -const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ - id: '/server-function/nested', - path: '/server-function/nested', +const SsrNestedRoute = SsrNestedRouteImport.update({ + id: '/ssr/nested', + path: '/ssr/nested', + getParentRoute: () => rootRouteImport, +} as any) +const SsrStreamRoute = SsrStreamRouteImport.update({ + id: '/ssr/stream', + path: '/ssr/stream', getParentRoute: () => rootRouteImport, } as any) -const ServerFunctionLateRawStreamRoute = - ServerFunctionLateRawStreamRouteImport.update({ - id: '/server-function/late-raw-stream', - path: '/server-function/late-raw-stream', - 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 @@ -132,25 +132,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/ssr/stream': { - id: '/ssr/stream' - path: '/ssr/stream' - fullPath: '/ssr/stream' - preLoaderRoute: typeof SsrStreamRouteImport - parentRoute: typeof rootRouteImport - } - '/ssr/nested': { - id: '/ssr/nested' - path: '/ssr/nested' - fullPath: '/ssr/nested' - preLoaderRoute: typeof SsrNestedRouteImport + '/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 } - '/ssr/data-only': { - id: '/ssr/data-only' - path: '/ssr/data-only' - fullPath: '/ssr/data-only' - preLoaderRoute: typeof SsrDataOnlyRouteImport + '/server-function/late-raw-stream': { + id: '/server-function/late-raw-stream' + path: '/server-function/late-raw-stream' + fullPath: '/server-function/late-raw-stream' + preLoaderRoute: typeof ServerFunctionLateRawStreamRouteImport parentRoute: typeof rootRouteImport } '/server-function/nested': { @@ -160,18 +153,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ServerFunctionNestedRouteImport parentRoute: typeof rootRouteImport } - '/server-function/late-raw-stream': { - id: '/server-function/late-raw-stream' - path: '/server-function/late-raw-stream' - fullPath: '/server-function/late-raw-stream' - preLoaderRoute: typeof ServerFunctionLateRawStreamRouteImport + '/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 + '/ssr/nested': { + id: '/ssr/nested' + path: '/ssr/nested' + fullPath: '/ssr/nested' + preLoaderRoute: typeof SsrNestedRouteImport + parentRoute: typeof rootRouteImport + } + '/ssr/stream': { + id: '/ssr/stream' + path: '/ssr/stream' + fullPath: '/ssr/stream' + preLoaderRoute: typeof SsrStreamRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/server-functions-global-middleware/package.json b/e2e/react-start/server-functions-global-middleware/package.json index 9af23a0df5..5898097c81 100644 --- a/e2e/react-start/server-functions-global-middleware/package.json +++ b/e2e/react-start/server-functions-global-middleware/package.json @@ -18,7 +18,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -27,7 +27,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/server-functions-global-middleware/src/routeTree.gen.ts b/e2e/react-start/server-functions-global-middleware/src/routeTree.gen.ts index 6fb74e28a2..739df05d7a 100644 --- a/e2e/react-start/server-functions-global-middleware/src/routeTree.gen.ts +++ b/e2e/react-start/server-functions-global-middleware/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 SimpleRouteImport } from './routes/simple' -import { Route as PathnameMiddlewareRouteImport } from './routes/pathname-middleware' -import { Route as MultipleServerFunctionsRouteImport } from './routes/multiple-server-functions' -import { Route as ContextCollisionRouteImport } from './routes/context-collision' import { Route as IndexRouteImport } from './routes/index' +import { Route as ContextCollisionRouteImport } from './routes/context-collision' +import { Route as MultipleServerFunctionsRouteImport } from './routes/multiple-server-functions' +import { Route as PathnameMiddlewareRouteImport } from './routes/pathname-middleware' +import { Route as SimpleRouteImport } from './routes/simple' -const SimpleRoute = SimpleRouteImport.update({ - id: '/simple', - path: '/simple', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PathnameMiddlewareRoute = PathnameMiddlewareRouteImport.update({ - id: '/pathname-middleware', - path: '/pathname-middleware', +const ContextCollisionRoute = ContextCollisionRouteImport.update({ + id: '/context-collision', + path: '/context-collision', getParentRoute: () => rootRouteImport, } as any) const MultipleServerFunctionsRoute = MultipleServerFunctionsRouteImport.update({ @@ -30,14 +30,14 @@ const MultipleServerFunctionsRoute = MultipleServerFunctionsRouteImport.update({ path: '/multiple-server-functions', getParentRoute: () => rootRouteImport, } as any) -const ContextCollisionRoute = ContextCollisionRouteImport.update({ - id: '/context-collision', - path: '/context-collision', +const PathnameMiddlewareRoute = PathnameMiddlewareRouteImport.update({ + id: '/pathname-middleware', + path: '/pathname-middleware', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SimpleRoute = SimpleRouteImport.update({ + id: '/simple', + path: '/simple', getParentRoute: () => rootRouteImport, } as any) @@ -97,18 +97,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/simple': { - id: '/simple' - path: '/simple' - fullPath: '/simple' - preLoaderRoute: typeof SimpleRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/pathname-middleware': { - id: '/pathname-middleware' - path: '/pathname-middleware' - fullPath: '/pathname-middleware' - preLoaderRoute: typeof PathnameMiddlewareRouteImport + '/context-collision': { + id: '/context-collision' + path: '/context-collision' + fullPath: '/context-collision' + preLoaderRoute: typeof ContextCollisionRouteImport parentRoute: typeof rootRouteImport } '/multiple-server-functions': { @@ -118,18 +118,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MultipleServerFunctionsRouteImport parentRoute: typeof rootRouteImport } - '/context-collision': { - id: '/context-collision' - path: '/context-collision' - fullPath: '/context-collision' - preLoaderRoute: typeof ContextCollisionRouteImport + '/pathname-middleware': { + id: '/pathname-middleware' + path: '/pathname-middleware' + fullPath: '/pathname-middleware' + preLoaderRoute: typeof PathnameMiddlewareRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/simple': { + id: '/simple' + path: '/simple' + fullPath: '/simple' + preLoaderRoute: typeof SimpleRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/server-functions/package.json b/e2e/react-start/server-functions/package.json index 5efdf6c42a..7c8d4f10ca 100644 --- a/e2e/react-start/server-functions/package.json +++ b/e2e/react-start/server-functions/package.json @@ -30,8 +30,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", @@ -44,7 +44,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/react-start/server-functions/src/routeTree.gen.ts b/e2e/react-start/server-functions/src/routeTree.gen.ts index 249a9c42ce..459d8fd595 100644 --- a/e2e/react-start/server-functions/src/routeTree.gen.ts +++ b/e2e/react-start/server-functions/src/routeTree.gen.ts @@ -9,94 +9,84 @@ // 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 SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' -import { Route as StatusRouteImport } from './routes/status' -import { Route as ServerOnlyFnRouteImport } from './routes/server-only-fn' -import { Route as ServerFnInClientOnlyFnRouteImport } from './routes/server-fn-in-client-only-fn' -import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' -import { Route as ReturnNullRouteImport } from './routes/return-null' -import { Route as RawResponseRouteImport } from './routes/raw-response' -import { Route as MultipartRouteImport } from './routes/multipart' -import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' -import { Route as HeadersRouteImport } from './routes/headers' -import { Route as FormdataContextRouteImport } from './routes/formdata-context' -import { Route as EnvOnlyRouteImport } from './routes/env-only' -import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' -import { Route as CustomFetchRouteImport } from './routes/custom-fetch' -import { Route as ConsistentRouteImport } from './routes/consistent' -import { Route as AsyncValidationRouteImport } from './routes/async-validation' import { Route as IndexRouteImport } from './routes/index' -import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' -import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' -import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' -import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' -import { Route as MethodNotAllowedIndexRouteImport } from './routes/method-not-allowed/index' -import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' -import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/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 AsyncValidationRouteImport } from './routes/async-validation' +import { Route as ConsistentRouteImport } from './routes/consistent' +import { Route as CustomFetchRouteImport } from './routes/custom-fetch' +import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' +import { Route as EnvOnlyRouteImport } from './routes/env-only' +import { Route as FormdataContextRouteImport } from './routes/formdata-context' +import { Route as HeadersRouteImport } from './routes/headers' +import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' +import { Route as MultipartRouteImport } from './routes/multipart' +import { Route as RawResponseRouteImport } from './routes/raw-response' +import { Route as ReturnNullRouteImport } from './routes/return-null' +import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' +import { Route as ServerFnInClientOnlyFnRouteImport } from './routes/server-fn-in-client-only-fn' +import { Route as ServerOnlyFnRouteImport } from './routes/server-only-fn' +import { Route as StatusRouteImport } from './routes/status' +import { Route as SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' import { Route as AbortSignalIndexRouteImport } from './routes/abort-signal/index' -import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' -import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' -import { Route as MiddlewareUnhandledExceptionRouteImport } from './routes/middleware/unhandled-exception' -import { Route as MiddlewareServerImportMiddlewareRouteImport } from './routes/middleware/server-import-middleware' -import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' -import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' -import { Route as MiddlewareMiddlewareFactoryRouteImport } from './routes/middleware/middleware-factory' -import { Route as MiddlewareFunctionMetadataRouteImport } from './routes/middleware/function-metadata' -import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' -import { Route as MiddlewareCatchHandlerErrorRouteImport } from './routes/middleware/catch-handler-error' -import { Route as MethodNotAllowedMethodRouteImport } from './routes/method-not-allowed/$method' -import { Route as CookiesSetRouteImport } from './routes/cookies/set' import { Route as AbortSignalMethodRouteImport } from './routes/abort-signal/$method' +import { Route as CookiesIndexRouteImport } from './routes/cookies/index' +import { Route as CookiesSetRouteImport } from './routes/cookies/set' +import { Route as FactoryIndexRouteImport } from './routes/factory/index' +import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' +import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/index' +import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' +import { Route as MethodNotAllowedIndexRouteImport } from './routes/method-not-allowed/index' +import { Route as MethodNotAllowedMethodRouteImport } from './routes/method-not-allowed/$method' +import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' +import { Route as MiddlewareCatchHandlerErrorRouteImport } from './routes/middleware/catch-handler-error' +import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' +import { Route as MiddlewareFunctionMetadataRouteImport } from './routes/middleware/function-metadata' +import { Route as MiddlewareMiddlewareFactoryRouteImport } from './routes/middleware/middleware-factory' +import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' +import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' +import { Route as MiddlewareServerImportMiddlewareRouteImport } from './routes/middleware/server-import-middleware' +import { Route as MiddlewareUnhandledExceptionRouteImport } from './routes/middleware/unhandled-exception' +import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' +import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' +import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' +import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' +import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' +import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name' import { Route as MiddlewareRedirectWithMiddlewareIndexRouteImport } from './routes/middleware/redirect-with-middleware/index' import { Route as MiddlewareRedirectWithMiddlewareTargetRouteImport } from './routes/middleware/redirect-with-middleware/target' -import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name' -const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ - id: '/submit-post-formdata', - path: '/submit-post-formdata', - getParentRoute: () => rootRouteImport, -} as any) -const StatusRoute = StatusRouteImport.update({ - id: '/status', - path: '/status', - getParentRoute: () => rootRouteImport, -} as any) -const ServerOnlyFnRoute = ServerOnlyFnRouteImport.update({ - id: '/server-only-fn', - path: '/server-only-fn', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const ServerFnInClientOnlyFnRoute = ServerFnInClientOnlyFnRouteImport.update({ - id: '/server-fn-in-client-only-fn', - path: '/server-fn-in-client-only-fn', +const AsyncValidationRoute = AsyncValidationRouteImport.update({ + id: '/async-validation', + path: '/async-validation', getParentRoute: () => rootRouteImport, } as any) -const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ - id: '/serialize-form-data', - path: '/serialize-form-data', +const ConsistentRoute = ConsistentRouteImport.update({ + id: '/consistent', + path: '/consistent', getParentRoute: () => rootRouteImport, } as any) -const ReturnNullRoute = ReturnNullRouteImport.update({ - id: '/return-null', - path: '/return-null', +const CustomFetchRoute = CustomFetchRouteImport.update({ + id: '/custom-fetch', + path: '/custom-fetch', getParentRoute: () => rootRouteImport, } as any) -const RawResponseRoute = RawResponseRouteImport.update({ - id: '/raw-response', - path: '/raw-response', +const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ + id: '/dead-code-preserve', + path: '/dead-code-preserve', getParentRoute: () => rootRouteImport, } as any) -const MultipartRoute = MultipartRouteImport.update({ - id: '/multipart', - path: '/multipart', +const EnvOnlyRoute = EnvOnlyRouteImport.update({ + id: '/env-only', + path: '/env-only', getParentRoute: () => rootRouteImport, } as any) -const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ - id: '/isomorphic-fns', - path: '/isomorphic-fns', +const FormdataContextRoute = FormdataContextRouteImport.update({ + id: '/formdata-context', + path: '/formdata-context', getParentRoute: () => rootRouteImport, } as any) const HeadersRoute = HeadersRouteImport.update({ @@ -104,74 +94,74 @@ const HeadersRoute = HeadersRouteImport.update({ path: '/headers', getParentRoute: () => rootRouteImport, } as any) -const FormdataContextRoute = FormdataContextRouteImport.update({ - id: '/formdata-context', - path: '/formdata-context', +const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ + id: '/isomorphic-fns', + path: '/isomorphic-fns', getParentRoute: () => rootRouteImport, } as any) -const EnvOnlyRoute = EnvOnlyRouteImport.update({ - id: '/env-only', - path: '/env-only', +const MultipartRoute = MultipartRouteImport.update({ + id: '/multipart', + path: '/multipart', getParentRoute: () => rootRouteImport, } as any) -const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ - id: '/dead-code-preserve', - path: '/dead-code-preserve', +const RawResponseRoute = RawResponseRouteImport.update({ + id: '/raw-response', + path: '/raw-response', getParentRoute: () => rootRouteImport, } as any) -const CustomFetchRoute = CustomFetchRouteImport.update({ - id: '/custom-fetch', - path: '/custom-fetch', +const ReturnNullRoute = ReturnNullRouteImport.update({ + id: '/return-null', + path: '/return-null', getParentRoute: () => rootRouteImport, } as any) -const ConsistentRoute = ConsistentRouteImport.update({ - id: '/consistent', - path: '/consistent', +const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ + id: '/serialize-form-data', + path: '/serialize-form-data', getParentRoute: () => rootRouteImport, } as any) -const AsyncValidationRoute = AsyncValidationRouteImport.update({ - id: '/async-validation', - path: '/async-validation', +const ServerFnInClientOnlyFnRoute = ServerFnInClientOnlyFnRouteImport.update({ + id: '/server-fn-in-client-only-fn', + path: '/server-fn-in-client-only-fn', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ServerOnlyFnRoute = ServerOnlyFnRouteImport.update({ + id: '/server-only-fn', + path: '/server-only-fn', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ - id: '/redirect-test/', - path: '/redirect-test/', +const StatusRoute = StatusRouteImport.update({ + id: '/status', + path: '/status', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ - id: '/redirect-test-ssr/', - path: '/redirect-test-ssr/', +const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ + id: '/submit-post-formdata', + path: '/submit-post-formdata', getParentRoute: () => rootRouteImport, } as any) -const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ - id: '/primitives/', - path: '/primitives/', +const AbortSignalIndexRoute = AbortSignalIndexRouteImport.update({ + id: '/abort-signal/', + path: '/abort-signal/', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ - id: '/middleware/', - path: '/middleware/', +const AbortSignalMethodRoute = AbortSignalMethodRouteImport.update({ + id: '/abort-signal/$method', + path: '/abort-signal/$method', getParentRoute: () => rootRouteImport, } as any) -const MethodNotAllowedIndexRoute = MethodNotAllowedIndexRouteImport.update({ - id: '/method-not-allowed/', - path: '/method-not-allowed/', +const CookiesIndexRoute = CookiesIndexRouteImport.update({ + id: '/cookies/', + path: '/cookies/', getParentRoute: () => rootRouteImport, } as any) -const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ - id: '/function-method/', - path: '/function-method/', +const CookiesSetRoute = CookiesSetRouteImport.update({ + id: '/cookies/set', + path: '/cookies/set', getParentRoute: () => rootRouteImport, } as any) -const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ - id: '/function-metadata/', - path: '/function-metadata/', +const FactoryIndexRoute = FactoryIndexRouteImport.update({ + id: '/factory/', + path: '/factory/', getParentRoute: () => rootRouteImport, } as any) const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ @@ -179,52 +169,47 @@ const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ path: '/formdata-redirect/', getParentRoute: () => rootRouteImport, } as any) -const FactoryIndexRoute = FactoryIndexRouteImport.update({ - id: '/factory/', - path: '/factory/', +const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ + id: '/function-metadata/', + path: '/function-metadata/', getParentRoute: () => rootRouteImport, } as any) -const CookiesIndexRoute = CookiesIndexRouteImport.update({ - id: '/cookies/', - path: '/cookies/', +const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ + id: '/function-method/', + path: '/function-method/', getParentRoute: () => rootRouteImport, } as any) -const AbortSignalIndexRoute = AbortSignalIndexRouteImport.update({ - id: '/abort-signal/', - path: '/abort-signal/', +const MethodNotAllowedIndexRoute = MethodNotAllowedIndexRouteImport.update({ + id: '/method-not-allowed/', + path: '/method-not-allowed/', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ - id: '/redirect-test/target', - path: '/redirect-test/target', +const MethodNotAllowedMethodRoute = MethodNotAllowedMethodRouteImport.update({ + id: '/method-not-allowed/$method', + path: '/method-not-allowed/$method', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ - id: '/redirect-test-ssr/target', - path: '/redirect-test-ssr/target', +const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ + id: '/middleware/', + path: '/middleware/', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareUnhandledExceptionRoute = - MiddlewareUnhandledExceptionRouteImport.update({ - id: '/middleware/unhandled-exception', - path: '/middleware/unhandled-exception', +const MiddlewareCatchHandlerErrorRoute = + MiddlewareCatchHandlerErrorRouteImport.update({ + id: '/middleware/catch-handler-error', + path: '/middleware/catch-handler-error', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareServerImportMiddlewareRoute = - MiddlewareServerImportMiddlewareRouteImport.update({ - id: '/middleware/server-import-middleware', - path: '/middleware/server-import-middleware', +const MiddlewareClientMiddlewareRouterRoute = + MiddlewareClientMiddlewareRouterRouteImport.update({ + id: '/middleware/client-middleware-router', + path: '/middleware/client-middleware-router', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ - id: '/middleware/send-serverFn', - path: '/middleware/send-serverFn', - getParentRoute: () => rootRouteImport, -} as any) -const MiddlewareRequestMiddlewareRoute = - MiddlewareRequestMiddlewareRouteImport.update({ - id: '/middleware/request-middleware', - path: '/middleware/request-middleware', +const MiddlewareFunctionMetadataRoute = + MiddlewareFunctionMetadataRouteImport.update({ + id: '/middleware/function-metadata', + path: '/middleware/function-metadata', getParentRoute: () => rootRouteImport, } as any) const MiddlewareMiddlewareFactoryRoute = @@ -233,39 +218,60 @@ const MiddlewareMiddlewareFactoryRoute = path: '/middleware/middleware-factory', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareFunctionMetadataRoute = - MiddlewareFunctionMetadataRouteImport.update({ - id: '/middleware/function-metadata', - path: '/middleware/function-metadata', +const MiddlewareRequestMiddlewareRoute = + MiddlewareRequestMiddlewareRouteImport.update({ + id: '/middleware/request-middleware', + path: '/middleware/request-middleware', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareClientMiddlewareRouterRoute = - MiddlewareClientMiddlewareRouterRouteImport.update({ - id: '/middleware/client-middleware-router', - path: '/middleware/client-middleware-router', +const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ + id: '/middleware/send-serverFn', + path: '/middleware/send-serverFn', + getParentRoute: () => rootRouteImport, +} as any) +const MiddlewareServerImportMiddlewareRoute = + MiddlewareServerImportMiddlewareRouteImport.update({ + id: '/middleware/server-import-middleware', + path: '/middleware/server-import-middleware', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareCatchHandlerErrorRoute = - MiddlewareCatchHandlerErrorRouteImport.update({ - id: '/middleware/catch-handler-error', - path: '/middleware/catch-handler-error', +const MiddlewareUnhandledExceptionRoute = + MiddlewareUnhandledExceptionRouteImport.update({ + id: '/middleware/unhandled-exception', + path: '/middleware/unhandled-exception', getParentRoute: () => rootRouteImport, } as any) -const MethodNotAllowedMethodRoute = MethodNotAllowedMethodRouteImport.update({ - id: '/method-not-allowed/$method', - path: '/method-not-allowed/$method', +const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ + id: '/primitives/', + path: '/primitives/', getParentRoute: () => rootRouteImport, } as any) -const CookiesSetRoute = CookiesSetRouteImport.update({ - id: '/cookies/set', - path: '/cookies/set', +const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ + id: '/redirect-test-ssr/', + path: '/redirect-test-ssr/', getParentRoute: () => rootRouteImport, } as any) -const AbortSignalMethodRoute = AbortSignalMethodRouteImport.update({ - id: '/abort-signal/$method', - path: '/abort-signal/$method', +const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ + id: '/redirect-test-ssr/target', + path: '/redirect-test-ssr/target', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ + id: '/redirect-test/', + path: '/redirect-test/', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ + id: '/redirect-test/target', + path: '/redirect-test/target', getParentRoute: () => rootRouteImport, } as any) +const FormdataRedirectTargetNameRoute = + FormdataRedirectTargetNameRouteImport.update({ + id: '/formdata-redirect/target/$name', + path: '/formdata-redirect/target/$name', + getParentRoute: () => rootRouteImport, + } as any) const MiddlewareRedirectWithMiddlewareIndexRoute = MiddlewareRedirectWithMiddlewareIndexRouteImport.update({ id: '/middleware/redirect-with-middleware/', @@ -278,12 +284,6 @@ const MiddlewareRedirectWithMiddlewareTargetRoute = path: '/middleware/redirect-with-middleware/target', getParentRoute: () => rootRouteImport, } as any) -const FormdataRedirectTargetNameRoute = - FormdataRedirectTargetNameRouteImport.update({ - id: '/formdata-redirect/target/$name', - path: '/formdata-redirect/target/$name', - getParentRoute: () => rootRouteImport, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -614,60 +614,60 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/submit-post-formdata': { - id: '/submit-post-formdata' - path: '/submit-post-formdata' - fullPath: '/submit-post-formdata' - preLoaderRoute: typeof SubmitPostFormdataRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/status': { - id: '/status' - path: '/status' - fullPath: '/status' - preLoaderRoute: typeof StatusRouteImport + '/async-validation': { + id: '/async-validation' + path: '/async-validation' + fullPath: '/async-validation' + preLoaderRoute: typeof AsyncValidationRouteImport parentRoute: typeof rootRouteImport } - '/server-only-fn': { - id: '/server-only-fn' - path: '/server-only-fn' - fullPath: '/server-only-fn' - preLoaderRoute: typeof ServerOnlyFnRouteImport + '/consistent': { + id: '/consistent' + path: '/consistent' + fullPath: '/consistent' + preLoaderRoute: typeof ConsistentRouteImport parentRoute: typeof rootRouteImport } - '/server-fn-in-client-only-fn': { - id: '/server-fn-in-client-only-fn' - path: '/server-fn-in-client-only-fn' - fullPath: '/server-fn-in-client-only-fn' - preLoaderRoute: typeof ServerFnInClientOnlyFnRouteImport + '/custom-fetch': { + id: '/custom-fetch' + path: '/custom-fetch' + fullPath: '/custom-fetch' + preLoaderRoute: typeof CustomFetchRouteImport parentRoute: typeof rootRouteImport } - '/serialize-form-data': { - id: '/serialize-form-data' - path: '/serialize-form-data' - fullPath: '/serialize-form-data' - preLoaderRoute: typeof SerializeFormDataRouteImport + '/dead-code-preserve': { + id: '/dead-code-preserve' + path: '/dead-code-preserve' + fullPath: '/dead-code-preserve' + preLoaderRoute: typeof DeadCodePreserveRouteImport parentRoute: typeof rootRouteImport } - '/return-null': { - id: '/return-null' - path: '/return-null' - fullPath: '/return-null' - preLoaderRoute: typeof ReturnNullRouteImport + '/env-only': { + id: '/env-only' + path: '/env-only' + fullPath: '/env-only' + preLoaderRoute: typeof EnvOnlyRouteImport parentRoute: typeof rootRouteImport } - '/raw-response': { - id: '/raw-response' - path: '/raw-response' - fullPath: '/raw-response' - preLoaderRoute: typeof RawResponseRouteImport + '/formdata-context': { + id: '/formdata-context' + path: '/formdata-context' + fullPath: '/formdata-context' + preLoaderRoute: typeof FormdataContextRouteImport parentRoute: typeof rootRouteImport } - '/multipart': { - id: '/multipart' - path: '/multipart' - fullPath: '/multipart' - preLoaderRoute: typeof MultipartRouteImport + '/headers': { + id: '/headers' + path: '/headers' + fullPath: '/headers' + preLoaderRoute: typeof HeadersRouteImport parentRoute: typeof rootRouteImport } '/isomorphic-fns': { @@ -677,102 +677,102 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IsomorphicFnsRouteImport parentRoute: typeof rootRouteImport } - '/headers': { - id: '/headers' - path: '/headers' - fullPath: '/headers' - preLoaderRoute: typeof HeadersRouteImport + '/multipart': { + id: '/multipart' + path: '/multipart' + fullPath: '/multipart' + preLoaderRoute: typeof MultipartRouteImport parentRoute: typeof rootRouteImport } - '/formdata-context': { - id: '/formdata-context' - path: '/formdata-context' - fullPath: '/formdata-context' - preLoaderRoute: typeof FormdataContextRouteImport + '/raw-response': { + id: '/raw-response' + path: '/raw-response' + fullPath: '/raw-response' + preLoaderRoute: typeof RawResponseRouteImport parentRoute: typeof rootRouteImport } - '/env-only': { - id: '/env-only' - path: '/env-only' - fullPath: '/env-only' - preLoaderRoute: typeof EnvOnlyRouteImport + '/return-null': { + id: '/return-null' + path: '/return-null' + fullPath: '/return-null' + preLoaderRoute: typeof ReturnNullRouteImport parentRoute: typeof rootRouteImport } - '/dead-code-preserve': { - id: '/dead-code-preserve' - path: '/dead-code-preserve' - fullPath: '/dead-code-preserve' - preLoaderRoute: typeof DeadCodePreserveRouteImport + '/serialize-form-data': { + id: '/serialize-form-data' + path: '/serialize-form-data' + fullPath: '/serialize-form-data' + preLoaderRoute: typeof SerializeFormDataRouteImport parentRoute: typeof rootRouteImport } - '/custom-fetch': { - id: '/custom-fetch' - path: '/custom-fetch' - fullPath: '/custom-fetch' - preLoaderRoute: typeof CustomFetchRouteImport + '/server-fn-in-client-only-fn': { + id: '/server-fn-in-client-only-fn' + path: '/server-fn-in-client-only-fn' + fullPath: '/server-fn-in-client-only-fn' + preLoaderRoute: typeof ServerFnInClientOnlyFnRouteImport parentRoute: typeof rootRouteImport } - '/consistent': { - id: '/consistent' - path: '/consistent' - fullPath: '/consistent' - preLoaderRoute: typeof ConsistentRouteImport + '/server-only-fn': { + id: '/server-only-fn' + path: '/server-only-fn' + fullPath: '/server-only-fn' + preLoaderRoute: typeof ServerOnlyFnRouteImport parentRoute: typeof rootRouteImport } - '/async-validation': { - id: '/async-validation' - path: '/async-validation' - fullPath: '/async-validation' - preLoaderRoute: typeof AsyncValidationRouteImport + '/status': { + id: '/status' + path: '/status' + fullPath: '/status' + preLoaderRoute: typeof StatusRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/submit-post-formdata': { + id: '/submit-post-formdata' + path: '/submit-post-formdata' + fullPath: '/submit-post-formdata' + preLoaderRoute: typeof SubmitPostFormdataRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/': { - id: '/redirect-test/' - path: '/redirect-test' - fullPath: '/redirect-test/' - preLoaderRoute: typeof RedirectTestIndexRouteImport + '/abort-signal/': { + id: '/abort-signal/' + path: '/abort-signal' + fullPath: '/abort-signal/' + preLoaderRoute: typeof AbortSignalIndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/': { - id: '/redirect-test-ssr/' - path: '/redirect-test-ssr' - fullPath: '/redirect-test-ssr/' - preLoaderRoute: typeof RedirectTestSsrIndexRouteImport + '/abort-signal/$method': { + id: '/abort-signal/$method' + path: '/abort-signal/$method' + fullPath: '/abort-signal/$method' + preLoaderRoute: typeof AbortSignalMethodRouteImport parentRoute: typeof rootRouteImport } - '/primitives/': { - id: '/primitives/' - path: '/primitives' - fullPath: '/primitives/' - preLoaderRoute: typeof PrimitivesIndexRouteImport + '/cookies/': { + id: '/cookies/' + path: '/cookies' + fullPath: '/cookies/' + preLoaderRoute: typeof CookiesIndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware/': { - id: '/middleware/' - path: '/middleware' - fullPath: '/middleware/' - preLoaderRoute: typeof MiddlewareIndexRouteImport + '/cookies/set': { + id: '/cookies/set' + path: '/cookies/set' + fullPath: '/cookies/set' + preLoaderRoute: typeof CookiesSetRouteImport parentRoute: typeof rootRouteImport } - '/method-not-allowed/': { - id: '/method-not-allowed/' - path: '/method-not-allowed' - fullPath: '/method-not-allowed/' - preLoaderRoute: typeof MethodNotAllowedIndexRouteImport + '/factory/': { + id: '/factory/' + path: '/factory' + fullPath: '/factory/' + preLoaderRoute: typeof FactoryIndexRouteImport parentRoute: typeof rootRouteImport } - '/function-method/': { - id: '/function-method/' - path: '/function-method' - fullPath: '/function-method/' - preLoaderRoute: typeof FunctionMethodIndexRouteImport + '/formdata-redirect/': { + id: '/formdata-redirect/' + path: '/formdata-redirect' + fullPath: '/formdata-redirect/' + preLoaderRoute: typeof FormdataRedirectIndexRouteImport parentRoute: typeof rootRouteImport } '/function-metadata/': { @@ -782,60 +782,67 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FunctionMetadataIndexRouteImport parentRoute: typeof rootRouteImport } - '/formdata-redirect/': { - id: '/formdata-redirect/' - path: '/formdata-redirect' - fullPath: '/formdata-redirect/' - preLoaderRoute: typeof FormdataRedirectIndexRouteImport + '/function-method/': { + id: '/function-method/' + path: '/function-method' + fullPath: '/function-method/' + preLoaderRoute: typeof FunctionMethodIndexRouteImport parentRoute: typeof rootRouteImport } - '/factory/': { - id: '/factory/' - path: '/factory' - fullPath: '/factory/' - preLoaderRoute: typeof FactoryIndexRouteImport + '/method-not-allowed/': { + id: '/method-not-allowed/' + path: '/method-not-allowed' + fullPath: '/method-not-allowed/' + preLoaderRoute: typeof MethodNotAllowedIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/': { - id: '/cookies/' - path: '/cookies' - fullPath: '/cookies/' - preLoaderRoute: typeof CookiesIndexRouteImport + '/method-not-allowed/$method': { + id: '/method-not-allowed/$method' + path: '/method-not-allowed/$method' + fullPath: '/method-not-allowed/$method' + preLoaderRoute: typeof MethodNotAllowedMethodRouteImport parentRoute: typeof rootRouteImport } - '/abort-signal/': { - id: '/abort-signal/' - path: '/abort-signal' - fullPath: '/abort-signal/' - preLoaderRoute: typeof AbortSignalIndexRouteImport + '/middleware/': { + id: '/middleware/' + path: '/middleware' + fullPath: '/middleware/' + preLoaderRoute: typeof MiddlewareIndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/target': { - id: '/redirect-test/target' - path: '/redirect-test/target' - fullPath: '/redirect-test/target' - preLoaderRoute: typeof RedirectTestTargetRouteImport + '/middleware/catch-handler-error': { + id: '/middleware/catch-handler-error' + path: '/middleware/catch-handler-error' + fullPath: '/middleware/catch-handler-error' + preLoaderRoute: typeof MiddlewareCatchHandlerErrorRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/target': { - id: '/redirect-test-ssr/target' - path: '/redirect-test-ssr/target' - fullPath: '/redirect-test-ssr/target' - preLoaderRoute: typeof RedirectTestSsrTargetRouteImport + '/middleware/client-middleware-router': { + id: '/middleware/client-middleware-router' + path: '/middleware/client-middleware-router' + fullPath: '/middleware/client-middleware-router' + preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport parentRoute: typeof rootRouteImport } - '/middleware/unhandled-exception': { - id: '/middleware/unhandled-exception' - path: '/middleware/unhandled-exception' - fullPath: '/middleware/unhandled-exception' - preLoaderRoute: typeof MiddlewareUnhandledExceptionRouteImport + '/middleware/function-metadata': { + id: '/middleware/function-metadata' + path: '/middleware/function-metadata' + fullPath: '/middleware/function-metadata' + preLoaderRoute: typeof MiddlewareFunctionMetadataRouteImport parentRoute: typeof rootRouteImport } - '/middleware/server-import-middleware': { - id: '/middleware/server-import-middleware' - path: '/middleware/server-import-middleware' - fullPath: '/middleware/server-import-middleware' - preLoaderRoute: typeof MiddlewareServerImportMiddlewareRouteImport + '/middleware/middleware-factory': { + id: '/middleware/middleware-factory' + path: '/middleware/middleware-factory' + fullPath: '/middleware/middleware-factory' + preLoaderRoute: typeof MiddlewareMiddlewareFactoryRouteImport + parentRoute: typeof rootRouteImport + } + '/middleware/request-middleware': { + id: '/middleware/request-middleware' + path: '/middleware/request-middleware' + fullPath: '/middleware/request-middleware' + preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport parentRoute: typeof rootRouteImport } '/middleware/send-serverFn': { @@ -845,60 +852,60 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MiddlewareSendServerFnRouteImport parentRoute: typeof rootRouteImport } - '/middleware/request-middleware': { - id: '/middleware/request-middleware' - path: '/middleware/request-middleware' - fullPath: '/middleware/request-middleware' - preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport + '/middleware/server-import-middleware': { + id: '/middleware/server-import-middleware' + path: '/middleware/server-import-middleware' + fullPath: '/middleware/server-import-middleware' + preLoaderRoute: typeof MiddlewareServerImportMiddlewareRouteImport parentRoute: typeof rootRouteImport } - '/middleware/middleware-factory': { - id: '/middleware/middleware-factory' - path: '/middleware/middleware-factory' - fullPath: '/middleware/middleware-factory' - preLoaderRoute: typeof MiddlewareMiddlewareFactoryRouteImport + '/middleware/unhandled-exception': { + id: '/middleware/unhandled-exception' + path: '/middleware/unhandled-exception' + fullPath: '/middleware/unhandled-exception' + preLoaderRoute: typeof MiddlewareUnhandledExceptionRouteImport parentRoute: typeof rootRouteImport } - '/middleware/function-metadata': { - id: '/middleware/function-metadata' - path: '/middleware/function-metadata' - fullPath: '/middleware/function-metadata' - preLoaderRoute: typeof MiddlewareFunctionMetadataRouteImport + '/primitives/': { + id: '/primitives/' + path: '/primitives' + fullPath: '/primitives/' + preLoaderRoute: typeof PrimitivesIndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware/client-middleware-router': { - id: '/middleware/client-middleware-router' - path: '/middleware/client-middleware-router' - fullPath: '/middleware/client-middleware-router' - preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport + '/redirect-test-ssr/': { + id: '/redirect-test-ssr/' + path: '/redirect-test-ssr' + fullPath: '/redirect-test-ssr/' + preLoaderRoute: typeof RedirectTestSsrIndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware/catch-handler-error': { - id: '/middleware/catch-handler-error' - path: '/middleware/catch-handler-error' - fullPath: '/middleware/catch-handler-error' - preLoaderRoute: typeof MiddlewareCatchHandlerErrorRouteImport + '/redirect-test-ssr/target': { + id: '/redirect-test-ssr/target' + path: '/redirect-test-ssr/target' + fullPath: '/redirect-test-ssr/target' + preLoaderRoute: typeof RedirectTestSsrTargetRouteImport parentRoute: typeof rootRouteImport } - '/method-not-allowed/$method': { - id: '/method-not-allowed/$method' - path: '/method-not-allowed/$method' - fullPath: '/method-not-allowed/$method' - preLoaderRoute: typeof MethodNotAllowedMethodRouteImport + '/redirect-test/': { + id: '/redirect-test/' + path: '/redirect-test' + fullPath: '/redirect-test/' + preLoaderRoute: typeof RedirectTestIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/set': { - id: '/cookies/set' - path: '/cookies/set' - fullPath: '/cookies/set' - preLoaderRoute: typeof CookiesSetRouteImport + '/redirect-test/target': { + id: '/redirect-test/target' + path: '/redirect-test/target' + fullPath: '/redirect-test/target' + preLoaderRoute: typeof RedirectTestTargetRouteImport parentRoute: typeof rootRouteImport } - '/abort-signal/$method': { - id: '/abort-signal/$method' - path: '/abort-signal/$method' - fullPath: '/abort-signal/$method' - preLoaderRoute: typeof AbortSignalMethodRouteImport + '/formdata-redirect/target/$name': { + id: '/formdata-redirect/target/$name' + path: '/formdata-redirect/target/$name' + fullPath: '/formdata-redirect/target/$name' + preLoaderRoute: typeof FormdataRedirectTargetNameRouteImport parentRoute: typeof rootRouteImport } '/middleware/redirect-with-middleware/': { @@ -915,13 +922,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MiddlewareRedirectWithMiddlewareTargetRouteImport parentRoute: typeof rootRouteImport } - '/formdata-redirect/target/$name': { - id: '/formdata-redirect/target/$name' - path: '/formdata-redirect/target/$name' - fullPath: '/formdata-redirect/target/$name' - preLoaderRoute: typeof FormdataRedirectTargetNameRouteImport - parentRoute: typeof rootRouteImport - } } } diff --git a/e2e/react-start/server-routes-global-middleware/package.json b/e2e/react-start/server-routes-global-middleware/package.json index 0757885b5c..6887b19b5f 100644 --- a/e2e/react-start/server-routes-global-middleware/package.json +++ b/e2e/react-start/server-routes-global-middleware/package.json @@ -18,7 +18,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -27,7 +27,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/server-routes-global-middleware/src/routeTree.gen.ts b/e2e/react-start/server-routes-global-middleware/src/routeTree.gen.ts index 6ec0a95128..3221c03ad6 100644 --- a/e2e/react-start/server-routes-global-middleware/src/routeTree.gen.ts +++ b/e2e/react-start/server-routes-global-middleware/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 ServerRouteWithMiddlewareRouteImport } from './routes/server-route-with-middleware' import { Route as IndexRouteImport } from './routes/index' +import { Route as ServerRouteWithMiddlewareRouteImport } from './routes/server-route-with-middleware' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const ServerRouteWithMiddlewareRoute = ServerRouteWithMiddlewareRouteImport.update({ id: '/server-route-with-middleware', path: '/server-route-with-middleware', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -52,13 +52,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/server-route-with-middleware': { - id: '/server-route-with-middleware' - path: '/server-route-with-middleware' - fullPath: '/server-route-with-middleware' - preLoaderRoute: typeof ServerRouteWithMiddlewareRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -66,6 +59,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/server-route-with-middleware': { + id: '/server-route-with-middleware' + path: '/server-route-with-middleware' + fullPath: '/server-route-with-middleware' + preLoaderRoute: typeof ServerRouteWithMiddlewareRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/server-routes/package.json b/e2e/react-start/server-routes/package.json index 41d6c6743b..f8082e3618 100644 --- a/e2e/react-start/server-routes/package.json +++ b/e2e/react-start/server-routes/package.json @@ -25,7 +25,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", @@ -36,7 +36,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/server-routes/src/routeTree.gen.ts b/e2e/react-start/server-routes/src/routeTree.gen.ts index 2b8bac6e09..2454c3bb32 100644 --- a/e2e/react-start/server-routes/src/routeTree.gen.ts +++ b/e2e/react-start/server-routes/src/routeTree.gen.ts @@ -9,19 +9,24 @@ // 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 MergeMiddlewareContextRouteImport } from './routes/merge-middleware-context' import { Route as MethodsRouteRouteImport } from './routes/methods/route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiGetAndAnyRouteImport } from './routes/api/get-and-any' +import { Route as ApiHeadFallbackRouteImport } from './routes/api/head-fallback' +import { Route as ApiHeadRedirectFallbackRouteImport } from './routes/api/head-redirect-fallback' +import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' +import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' import { Route as MethodsIndexRouteImport } from './routes/methods/index' import { Route as MethodsOnlyAnyRouteImport } from './routes/methods/only-any' -import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' -import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' -import { Route as ApiHeadRedirectFallbackRouteImport } from './routes/api/head-redirect-fallback' -import { Route as ApiHeadFallbackRouteImport } from './routes/api/head-fallback' -import { Route as ApiGetAndAnyRouteImport } from './routes/api/get-and-any' import { Route as ApiParamsFooRouteRouteImport } from './routes/api/params/$foo/route' import { Route as ApiParamsFooBarRouteImport } from './routes/api/params/$foo/$bar' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const MergeMiddlewareContextRoute = MergeMiddlewareContextRouteImport.update({ id: '/merge-middleware-context', path: '/merge-middleware-context', @@ -32,24 +37,19 @@ const MethodsRouteRoute = MethodsRouteRouteImport.update({ path: '/methods', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ApiGetAndAnyRoute = ApiGetAndAnyRouteImport.update({ + id: '/api/get-and-any', + path: '/api/get-and-any', getParentRoute: () => rootRouteImport, } as any) -const MethodsIndexRoute = MethodsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MethodsRouteRoute, -} as any) -const MethodsOnlyAnyRoute = MethodsOnlyAnyRouteImport.update({ - id: '/only-any', - path: '/only-any', - getParentRoute: () => MethodsRouteRoute, +const ApiHeadFallbackRoute = ApiHeadFallbackRouteImport.update({ + id: '/api/head-fallback', + path: '/api/head-fallback', + getParentRoute: () => rootRouteImport, } as any) -const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ - id: '/api/only-any', - path: '/api/only-any', +const ApiHeadRedirectFallbackRoute = ApiHeadRedirectFallbackRouteImport.update({ + id: '/api/head-redirect-fallback', + path: '/api/head-redirect-fallback', getParentRoute: () => rootRouteImport, } as any) const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ @@ -57,20 +57,20 @@ const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ path: '/api/middleware-context', getParentRoute: () => rootRouteImport, } as any) -const ApiHeadRedirectFallbackRoute = ApiHeadRedirectFallbackRouteImport.update({ - id: '/api/head-redirect-fallback', - path: '/api/head-redirect-fallback', +const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ + id: '/api/only-any', + path: '/api/only-any', getParentRoute: () => rootRouteImport, } as any) -const ApiHeadFallbackRoute = ApiHeadFallbackRouteImport.update({ - id: '/api/head-fallback', - path: '/api/head-fallback', - getParentRoute: () => rootRouteImport, +const MethodsIndexRoute = MethodsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => MethodsRouteRoute, } as any) -const ApiGetAndAnyRoute = ApiGetAndAnyRouteImport.update({ - id: '/api/get-and-any', - path: '/api/get-and-any', - getParentRoute: () => rootRouteImport, +const MethodsOnlyAnyRoute = MethodsOnlyAnyRouteImport.update({ + id: '/only-any', + path: '/only-any', + getParentRoute: () => MethodsRouteRoute, } as any) const ApiParamsFooRouteRoute = ApiParamsFooRouteRouteImport.update({ id: '/api/params/$foo', @@ -183,6 +183,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/merge-middleware-context': { id: '/merge-middleware-context' path: '/merge-middleware-context' @@ -197,32 +204,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MethodsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/api/get-and-any': { + id: '/api/get-and-any' + path: '/api/get-and-any' + fullPath: '/api/get-and-any' + preLoaderRoute: typeof ApiGetAndAnyRouteImport parentRoute: typeof rootRouteImport } - '/methods/': { - id: '/methods/' - path: '/' - fullPath: '/methods/' - preLoaderRoute: typeof MethodsIndexRouteImport - parentRoute: typeof MethodsRouteRoute - } - '/methods/only-any': { - id: '/methods/only-any' - path: '/only-any' - fullPath: '/methods/only-any' - preLoaderRoute: typeof MethodsOnlyAnyRouteImport - parentRoute: typeof MethodsRouteRoute + '/api/head-fallback': { + id: '/api/head-fallback' + path: '/api/head-fallback' + fullPath: '/api/head-fallback' + preLoaderRoute: typeof ApiHeadFallbackRouteImport + parentRoute: typeof rootRouteImport } - '/api/only-any': { - id: '/api/only-any' - path: '/api/only-any' - fullPath: '/api/only-any' - preLoaderRoute: typeof ApiOnlyAnyRouteImport + '/api/head-redirect-fallback': { + id: '/api/head-redirect-fallback' + path: '/api/head-redirect-fallback' + fullPath: '/api/head-redirect-fallback' + preLoaderRoute: typeof ApiHeadRedirectFallbackRouteImport parentRoute: typeof rootRouteImport } '/api/middleware-context': { @@ -232,26 +232,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiMiddlewareContextRouteImport parentRoute: typeof rootRouteImport } - '/api/head-redirect-fallback': { - id: '/api/head-redirect-fallback' - path: '/api/head-redirect-fallback' - fullPath: '/api/head-redirect-fallback' - preLoaderRoute: typeof ApiHeadRedirectFallbackRouteImport + '/api/only-any': { + id: '/api/only-any' + path: '/api/only-any' + fullPath: '/api/only-any' + preLoaderRoute: typeof ApiOnlyAnyRouteImport parentRoute: typeof rootRouteImport } - '/api/head-fallback': { - id: '/api/head-fallback' - path: '/api/head-fallback' - fullPath: '/api/head-fallback' - preLoaderRoute: typeof ApiHeadFallbackRouteImport - parentRoute: typeof rootRouteImport + '/methods/': { + id: '/methods/' + path: '/' + fullPath: '/methods/' + preLoaderRoute: typeof MethodsIndexRouteImport + parentRoute: typeof MethodsRouteRoute } - '/api/get-and-any': { - id: '/api/get-and-any' - path: '/api/get-and-any' - fullPath: '/api/get-and-any' - preLoaderRoute: typeof ApiGetAndAnyRouteImport - parentRoute: typeof rootRouteImport + '/methods/only-any': { + id: '/methods/only-any' + path: '/only-any' + fullPath: '/methods/only-any' + preLoaderRoute: typeof MethodsOnlyAnyRouteImport + parentRoute: typeof MethodsRouteRoute } '/api/params/$foo': { id: '/api/params/$foo' diff --git a/e2e/react-start/spa-mode/package.json b/e2e/react-start/spa-mode/package.json index 0442b5d7f3..1810493b1a 100644 --- a/e2e/react-start/spa-mode/package.json +++ b/e2e/react-start/spa-mode/package.json @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/spa-mode/src/routeTree.gen.ts b/e2e/react-start/spa-mode/src/routeTree.gen.ts index 188cd5c91a..1bba3ef326 100644 --- a/e2e/react-start/spa-mode/src/routeTree.gen.ts +++ b/e2e/react-start/spa-mode/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', @@ -60,13 +60,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +67,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' diff --git a/e2e/react-start/split-base-and-basepath/package.json b/e2e/react-start/split-base-and-basepath/package.json index 5f4e7a14c1..c0066b7aaf 100644 --- a/e2e/react-start/split-base-and-basepath/package.json +++ b/e2e/react-start/split-base-and-basepath/package.json @@ -20,14 +20,15 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/split-base-and-basepath/src/routeTree.gen.ts b/e2e/react-start/split-base-and-basepath/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/e2e/react-start/split-base-and-basepath/src/routeTree.gen.ts +++ b/e2e/react-start/split-base-and-basepath/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/start-manifest/package.json b/e2e/react-start/start-manifest/package.json index 7c932cb5d5..939882a346 100644 --- a/e2e/react-start/start-manifest/package.json +++ b/e2e/react-start/start-manifest/package.json @@ -15,14 +15,15 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "nitro": "^3.0.260311-beta", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/react-start/start-manifest/src/routeTree.gen.ts b/e2e/react-start/start-manifest/src/routeTree.gen.ts index b7ee033842..f89a740ba6 100644 --- a/e2e/react-start/start-manifest/src/routeTree.gen.ts +++ b/e2e/react-start/start-manifest/src/routeTree.gen.ts @@ -9,54 +9,49 @@ // 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 SharedCRouteImport } from './routes/shared-c' -import { Route as SharedBRouteImport } from './routes/shared-b' -import { Route as SharedARouteImport } from './routes/shared-a' -import { Route as R6RouteImport } from './routes/r6' -import { Route as R5RouteImport } from './routes/r5' -import { Route as R4RouteImport } from './routes/r4' -import { Route as R3RouteImport } from './routes/r3' -import { Route as R2RouteImport } from './routes/r2' -import { Route as R1RouteImport } from './routes/r1' -import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' -import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' -import { Route as BRouteImport } from './routes/b' -import { Route as ARouteImport } from './routes/a' import { Route as IndexRouteImport } from './routes/index' +import { Route as ARouteImport } from './routes/a' +import { Route as BRouteImport } from './routes/b' +import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' +import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' +import { Route as R1RouteImport } from './routes/r1' +import { Route as R2RouteImport } from './routes/r2' +import { Route as R3RouteImport } from './routes/r3' +import { Route as R4RouteImport } from './routes/r4' +import { Route as R5RouteImport } from './routes/r5' +import { Route as R6RouteImport } from './routes/r6' +import { Route as SharedARouteImport } from './routes/shared-a' +import { Route as SharedBRouteImport } from './routes/shared-b' +import { Route as SharedCRouteImport } from './routes/shared-c' -const SharedCRoute = SharedCRouteImport.update({ - id: '/shared-c', - path: '/shared-c', - getParentRoute: () => rootRouteImport, -} as any) -const SharedBRoute = SharedBRouteImport.update({ - id: '/shared-b', - path: '/shared-b', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SharedARoute = SharedARouteImport.update({ - id: '/shared-a', - path: '/shared-a', +const ARoute = ARouteImport.update({ + id: '/a', + path: '/a', getParentRoute: () => rootRouteImport, } as any) -const R6Route = R6RouteImport.update({ - id: '/r6', - path: '/r6', +const BRoute = BRouteImport.update({ + id: '/b', + path: '/b', getParentRoute: () => rootRouteImport, } as any) -const R5Route = R5RouteImport.update({ - id: '/r5', - path: '/r5', +const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ + id: '/lazy-css-lazy', + path: '/lazy-css-lazy', getParentRoute: () => rootRouteImport, } as any) -const R4Route = R4RouteImport.update({ - id: '/r4', - path: '/r4', +const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ + id: '/lazy-css-static', + path: '/lazy-css-static', getParentRoute: () => rootRouteImport, } as any) -const R3Route = R3RouteImport.update({ - id: '/r3', - path: '/r3', +const R1Route = R1RouteImport.update({ + id: '/r1', + path: '/r1', getParentRoute: () => rootRouteImport, } as any) const R2Route = R2RouteImport.update({ @@ -64,34 +59,39 @@ const R2Route = R2RouteImport.update({ path: '/r2', getParentRoute: () => rootRouteImport, } as any) -const R1Route = R1RouteImport.update({ - id: '/r1', - path: '/r1', +const R3Route = R3RouteImport.update({ + id: '/r3', + path: '/r3', getParentRoute: () => rootRouteImport, } as any) -const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ - id: '/lazy-css-static', - path: '/lazy-css-static', +const R4Route = R4RouteImport.update({ + id: '/r4', + path: '/r4', getParentRoute: () => rootRouteImport, } as any) -const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ - id: '/lazy-css-lazy', - path: '/lazy-css-lazy', +const R5Route = R5RouteImport.update({ + id: '/r5', + path: '/r5', getParentRoute: () => rootRouteImport, } as any) -const BRoute = BRouteImport.update({ - id: '/b', - path: '/b', +const R6Route = R6RouteImport.update({ + id: '/r6', + path: '/r6', getParentRoute: () => rootRouteImport, } as any) -const ARoute = ARouteImport.update({ - id: '/a', - path: '/a', +const SharedARoute = SharedARouteImport.update({ + id: '/shared-a', + path: '/shared-a', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SharedBRoute = SharedBRouteImport.update({ + id: '/shared-b', + path: '/shared-b', + getParentRoute: () => rootRouteImport, +} as any) +const SharedCRoute = SharedCRouteImport.update({ + id: '/shared-c', + path: '/shared-c', getParentRoute: () => rootRouteImport, } as any) @@ -214,53 +214,46 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/shared-c': { - id: '/shared-c' - path: '/shared-c' - fullPath: '/shared-c' - preLoaderRoute: typeof SharedCRouteImport - parentRoute: typeof rootRouteImport - } - '/shared-b': { - id: '/shared-b' - path: '/shared-b' - fullPath: '/shared-b' - preLoaderRoute: typeof SharedBRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/shared-a': { - id: '/shared-a' - path: '/shared-a' - fullPath: '/shared-a' - preLoaderRoute: typeof SharedARouteImport + '/a': { + id: '/a' + path: '/a' + fullPath: '/a' + preLoaderRoute: typeof ARouteImport parentRoute: typeof rootRouteImport } - '/r6': { - id: '/r6' - path: '/r6' - fullPath: '/r6' - preLoaderRoute: typeof R6RouteImport + '/b': { + id: '/b' + path: '/b' + fullPath: '/b' + preLoaderRoute: typeof BRouteImport parentRoute: typeof rootRouteImport } - '/r5': { - id: '/r5' - path: '/r5' - fullPath: '/r5' - preLoaderRoute: typeof R5RouteImport + '/lazy-css-lazy': { + id: '/lazy-css-lazy' + path: '/lazy-css-lazy' + fullPath: '/lazy-css-lazy' + preLoaderRoute: typeof LazyCssLazyRouteImport parentRoute: typeof rootRouteImport } - '/r4': { - id: '/r4' - path: '/r4' - fullPath: '/r4' - preLoaderRoute: typeof R4RouteImport + '/lazy-css-static': { + id: '/lazy-css-static' + path: '/lazy-css-static' + fullPath: '/lazy-css-static' + preLoaderRoute: typeof LazyCssStaticRouteImport parentRoute: typeof rootRouteImport } - '/r3': { - id: '/r3' - path: '/r3' - fullPath: '/r3' - preLoaderRoute: typeof R3RouteImport + '/r1': { + id: '/r1' + path: '/r1' + fullPath: '/r1' + preLoaderRoute: typeof R1RouteImport parentRoute: typeof rootRouteImport } '/r2': { @@ -270,46 +263,53 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof R2RouteImport parentRoute: typeof rootRouteImport } - '/r1': { - id: '/r1' - path: '/r1' - fullPath: '/r1' - preLoaderRoute: typeof R1RouteImport + '/r3': { + id: '/r3' + path: '/r3' + fullPath: '/r3' + preLoaderRoute: typeof R3RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-static': { - id: '/lazy-css-static' - path: '/lazy-css-static' - fullPath: '/lazy-css-static' - preLoaderRoute: typeof LazyCssStaticRouteImport + '/r4': { + id: '/r4' + path: '/r4' + fullPath: '/r4' + preLoaderRoute: typeof R4RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-lazy': { - id: '/lazy-css-lazy' - path: '/lazy-css-lazy' - fullPath: '/lazy-css-lazy' - preLoaderRoute: typeof LazyCssLazyRouteImport + '/r5': { + id: '/r5' + path: '/r5' + fullPath: '/r5' + preLoaderRoute: typeof R5RouteImport parentRoute: typeof rootRouteImport } - '/b': { - id: '/b' - path: '/b' - fullPath: '/b' - preLoaderRoute: typeof BRouteImport + '/r6': { + id: '/r6' + path: '/r6' + fullPath: '/r6' + preLoaderRoute: typeof R6RouteImport parentRoute: typeof rootRouteImport } - '/a': { - id: '/a' - path: '/a' - fullPath: '/a' - preLoaderRoute: typeof ARouteImport + '/shared-a': { + id: '/shared-a' + path: '/shared-a' + fullPath: '/shared-a' + preLoaderRoute: typeof SharedARouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/shared-b': { + id: '/shared-b' + path: '/shared-b' + fullPath: '/shared-b' + preLoaderRoute: typeof SharedBRouteImport + parentRoute: typeof rootRouteImport + } + '/shared-c': { + id: '/shared-c' + path: '/shared-c' + fullPath: '/shared-c' + preLoaderRoute: typeof SharedCRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/static-server-functions/package.json b/e2e/react-start/static-server-functions/package.json index 47d8a73156..6aa146fc59 100644 --- a/e2e/react-start/static-server-functions/package.json +++ b/e2e/react-start/static-server-functions/package.json @@ -20,13 +20,14 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:*", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/static-server-functions/src/routeTree.gen.ts b/e2e/react-start/static-server-functions/src/routeTree.gen.ts index fa19f26a67..ea6a6a3806 100644 --- a/e2e/react-start/static-server-functions/src/routeTree.gen.ts +++ b/e2e/react-start/static-server-functions/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsIndexRouteImport } from './routes/posts.index' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -59,13 +59,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -73,6 +66,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' diff --git a/e2e/react-start/streaming-ssr/package.json b/e2e/react-start/streaming-ssr/package.json index 0cbadeae74..fcf78262d5 100644 --- a/e2e/react-start/streaming-ssr/package.json +++ b/e2e/react-start/streaming-ssr/package.json @@ -21,13 +21,14 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/streaming-ssr/src/routeTree.gen.ts b/e2e/react-start/streaming-ssr/src/routeTree.gen.ts index e1e3b367f1..c1a51b62f8 100644 --- a/e2e/react-start/streaming-ssr/src/routeTree.gen.ts +++ b/e2e/react-start/streaming-ssr/src/routeTree.gen.ts @@ -9,41 +9,41 @@ // 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 SyncOnlyRouteImport } from './routes/sync-only' -import { Route as StreamRouteImport } from './routes/stream' -import { Route as SlowRenderRouteImport } from './routes/slow-render' -import { Route as QueryHeavyRouteImport } from './routes/query-heavy' -import { Route as NestedDeferredRouteImport } from './routes/nested-deferred' -import { Route as ManyPromisesRouteImport } from './routes/many-promises' -import { Route as FastSerialRouteImport } from './routes/fast-serial' -import { Route as DeferredRejectionRouteImport } from './routes/deferred-rejection' -import { Route as DeferredRouteImport } from './routes/deferred' -import { Route as ConcurrentRouteImport } from './routes/concurrent' import { Route as IndexRouteImport } from './routes/index' +import { Route as ConcurrentRouteImport } from './routes/concurrent' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as DeferredRejectionRouteImport } from './routes/deferred-rejection' +import { Route as FastSerialRouteImport } from './routes/fast-serial' +import { Route as ManyPromisesRouteImport } from './routes/many-promises' +import { Route as NestedDeferredRouteImport } from './routes/nested-deferred' +import { Route as QueryHeavyRouteImport } from './routes/query-heavy' +import { Route as SlowRenderRouteImport } from './routes/slow-render' +import { Route as StreamRouteImport } from './routes/stream' +import { Route as SyncOnlyRouteImport } from './routes/sync-only' -const SyncOnlyRoute = SyncOnlyRouteImport.update({ - id: '/sync-only', - path: '/sync-only', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const StreamRoute = StreamRouteImport.update({ - id: '/stream', - path: '/stream', +const ConcurrentRoute = ConcurrentRouteImport.update({ + id: '/concurrent', + path: '/concurrent', getParentRoute: () => rootRouteImport, } as any) -const SlowRenderRoute = SlowRenderRouteImport.update({ - id: '/slow-render', - path: '/slow-render', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const QueryHeavyRoute = QueryHeavyRouteImport.update({ - id: '/query-heavy', - path: '/query-heavy', +const DeferredRejectionRoute = DeferredRejectionRouteImport.update({ + id: '/deferred-rejection', + path: '/deferred-rejection', getParentRoute: () => rootRouteImport, } as any) -const NestedDeferredRoute = NestedDeferredRouteImport.update({ - id: '/nested-deferred', - path: '/nested-deferred', +const FastSerialRoute = FastSerialRouteImport.update({ + id: '/fast-serial', + path: '/fast-serial', getParentRoute: () => rootRouteImport, } as any) const ManyPromisesRoute = ManyPromisesRouteImport.update({ @@ -51,29 +51,29 @@ const ManyPromisesRoute = ManyPromisesRouteImport.update({ path: '/many-promises', getParentRoute: () => rootRouteImport, } as any) -const FastSerialRoute = FastSerialRouteImport.update({ - id: '/fast-serial', - path: '/fast-serial', +const NestedDeferredRoute = NestedDeferredRouteImport.update({ + id: '/nested-deferred', + path: '/nested-deferred', getParentRoute: () => rootRouteImport, } as any) -const DeferredRejectionRoute = DeferredRejectionRouteImport.update({ - id: '/deferred-rejection', - path: '/deferred-rejection', +const QueryHeavyRoute = QueryHeavyRouteImport.update({ + id: '/query-heavy', + path: '/query-heavy', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const SlowRenderRoute = SlowRenderRouteImport.update({ + id: '/slow-render', + path: '/slow-render', getParentRoute: () => rootRouteImport, } as any) -const ConcurrentRoute = ConcurrentRouteImport.update({ - id: '/concurrent', - path: '/concurrent', +const StreamRoute = StreamRouteImport.update({ + id: '/stream', + path: '/stream', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SyncOnlyRoute = SyncOnlyRouteImport.update({ + id: '/sync-only', + path: '/sync-only', getParentRoute: () => rootRouteImport, } as any) @@ -175,39 +175,39 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/sync-only': { - id: '/sync-only' - path: '/sync-only' - fullPath: '/sync-only' - preLoaderRoute: typeof SyncOnlyRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/stream': { - id: '/stream' - path: '/stream' - fullPath: '/stream' - preLoaderRoute: typeof StreamRouteImport + '/concurrent': { + id: '/concurrent' + path: '/concurrent' + fullPath: '/concurrent' + preLoaderRoute: typeof ConcurrentRouteImport parentRoute: typeof rootRouteImport } - '/slow-render': { - id: '/slow-render' - path: '/slow-render' - fullPath: '/slow-render' - preLoaderRoute: typeof SlowRenderRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/query-heavy': { - id: '/query-heavy' - path: '/query-heavy' - fullPath: '/query-heavy' - preLoaderRoute: typeof QueryHeavyRouteImport + '/deferred-rejection': { + id: '/deferred-rejection' + path: '/deferred-rejection' + fullPath: '/deferred-rejection' + preLoaderRoute: typeof DeferredRejectionRouteImport parentRoute: typeof rootRouteImport } - '/nested-deferred': { - id: '/nested-deferred' - path: '/nested-deferred' - fullPath: '/nested-deferred' - preLoaderRoute: typeof NestedDeferredRouteImport + '/fast-serial': { + id: '/fast-serial' + path: '/fast-serial' + fullPath: '/fast-serial' + preLoaderRoute: typeof FastSerialRouteImport parentRoute: typeof rootRouteImport } '/many-promises': { @@ -217,39 +217,39 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ManyPromisesRouteImport parentRoute: typeof rootRouteImport } - '/fast-serial': { - id: '/fast-serial' - path: '/fast-serial' - fullPath: '/fast-serial' - preLoaderRoute: typeof FastSerialRouteImport + '/nested-deferred': { + id: '/nested-deferred' + path: '/nested-deferred' + fullPath: '/nested-deferred' + preLoaderRoute: typeof NestedDeferredRouteImport parentRoute: typeof rootRouteImport } - '/deferred-rejection': { - id: '/deferred-rejection' - path: '/deferred-rejection' - fullPath: '/deferred-rejection' - preLoaderRoute: typeof DeferredRejectionRouteImport + '/query-heavy': { + id: '/query-heavy' + path: '/query-heavy' + fullPath: '/query-heavy' + preLoaderRoute: typeof QueryHeavyRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/slow-render': { + id: '/slow-render' + path: '/slow-render' + fullPath: '/slow-render' + preLoaderRoute: typeof SlowRenderRouteImport parentRoute: typeof rootRouteImport } - '/concurrent': { - id: '/concurrent' - path: '/concurrent' - fullPath: '/concurrent' - preLoaderRoute: typeof ConcurrentRouteImport + '/stream': { + id: '/stream' + path: '/stream' + fullPath: '/stream' + preLoaderRoute: typeof StreamRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/sync-only': { + id: '/sync-only' + path: '/sync-only' + fullPath: '/sync-only' + preLoaderRoute: typeof SyncOnlyRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/react-start/transform-asset-urls/package.json b/e2e/react-start/transform-asset-urls/package.json index 40ffdd2569..95e39e459b 100644 --- a/e2e/react-start/transform-asset-urls/package.json +++ b/e2e/react-start/transform-asset-urls/package.json @@ -40,14 +40,15 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/transform-asset-urls/src/routeTree.gen.ts b/e2e/react-start/transform-asset-urls/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/e2e/react-start/transform-asset-urls/src/routeTree.gen.ts +++ b/e2e/react-start/transform-asset-urls/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/react-start/virtual-routes/package.json b/e2e/react-start/virtual-routes/package.json index 0ba0855037..e5e0acfcec 100644 --- a/e2e/react-start/virtual-routes/package.json +++ b/e2e/react-start/virtual-routes/package.json @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -33,7 +33,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/virtual-routes/src/routeTree.gen.ts b/e2e/react-start/virtual-routes/src/routeTree.gen.ts index 6a1de929b3..9addb6f078 100644 --- a/e2e/react-start/virtual-routes/src/routeTree.gen.ts +++ b/e2e/react-start/virtual-routes/src/routeTree.gen.ts @@ -9,83 +9,83 @@ // 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 pipeRouteImport } from './routes/pipe' -import { Route as postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' +import { Route as pipeRouteImport } from './routes/pipe' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const pipeRoute = pipeRouteImport.update({ - id: '/special|pipe', - path: '/special|pipe', - getParentRoute: () => rootRouteImport, -} as any) -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, +const pipeRoute = pipeRouteImport.update({ + id: '/special|pipe', + path: '/special|pipe', + getParentRoute: () => rootRouteImport, } as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -179,18 +179,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/special|pipe': { - id: '/special|pipe' - path: '/special|pipe' - fullPath: '/special|pipe' - preLoaderRoute: typeof pipeRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -200,19 +193,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute + '/special|pipe': { + id: '/special|pipe' + path: '/special|pipe' + fullPath: '/special|pipe' + preLoaderRoute: typeof pipeRouteImport + parentRoute: typeof rootRouteImport } '/_first/_second-layout': { id: '/_first/_second-layout' @@ -221,6 +214,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -228,12 +228,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + parentRoute: typeof postsPostsRoute } '/classic/hello/': { id: '/classic/hello/' @@ -242,13 +242,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -256,12 +249,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } '/_first/_second-layout/route-without-file/layout-a': { id: '/_first/_second-layout/route-without-file/layout-a' @@ -270,6 +263,13 @@ declare module '@tanstack/react-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/e2e/react-start/website/package.json b/e2e/react-start/website/package.json index ad1ab75d5b..54b3734c4c 100644 --- a/e2e/react-start/website/package.json +++ b/e2e/react-start/website/package.json @@ -22,7 +22,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -31,7 +31,8 @@ "@vitejs/plugin-react": "^6.0.1", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/react-start/website/src/routeTree.gen.ts b/e2e/react-start/website/src/routeTree.gen.ts index e26402107f..03770b5ad9 100644 --- a/e2e/react-start/website/src/routeTree.gen.ts +++ b/e2e/react-start/website/src/routeTree.gen.ts @@ -10,47 +10,47 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LibraryRouteImport } from './routes/_library' -import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as ProjectIndexRouteImport } from './routes/$project.index' +import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as LibraryProjectRouteImport } from './routes/_library.$project' -import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.index' import { Route as ProjectVersionDocsIndexRouteImport } from './routes/$project.$version.docs.index' +import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.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 ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport } from './routes/$project.$version.docs.framework.$framework.{$}[.]md' import { Route as ProjectVersionDocsFrameworkFrameworkExamplesSplatRouteImport } from './routes/$project.$version.docs.framework.$framework.examples.$' const LibraryRoute = LibraryRouteImport.update({ id: '/_library', getParentRoute: () => rootRouteImport, } as any) -const LibraryIndexRoute = LibraryIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => LibraryRoute, -} as any) const ProjectIndexRoute = ProjectIndexRouteImport.update({ id: '/$project/', path: '/$project/', getParentRoute: () => rootRouteImport, } as any) +const LibraryIndexRoute = LibraryIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LibraryRoute, +} as any) const LibraryProjectRoute = LibraryProjectRouteImport.update({ id: '/$project', path: '/$project', getParentRoute: () => LibraryRoute, } as any) +const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ + id: '/$project/$version/docs/', + path: '/$project/$version/docs/', + getParentRoute: () => rootRouteImport, +} as any) const LibraryProjectVersionIndexRoute = LibraryProjectVersionIndexRouteImport.update({ id: '/$version/', path: '/$version/', getParentRoute: () => LibraryProjectRoute, } as any) -const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ - id: '/$project/$version/docs/', - path: '/$project/$version/docs/', - getParentRoute: () => rootRouteImport, -} as any) const ProjectVersionDocsFrameworkFrameworkRoute = ProjectVersionDocsFrameworkFrameworkRouteImport.update({ id: '/$project/$version/docs/framework/$framework', @@ -63,18 +63,18 @@ const ProjectVersionDocsFrameworkFrameworkIndexRoute = path: '/', getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, } as any) -const ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute = - ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport.update({ - id: '/{$}.md', - path: '/{$}.md', - getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, - } as any) const ProjectVersionDocsFrameworkFrameworkSplatRoute = ProjectVersionDocsFrameworkFrameworkSplatRouteImport.update({ id: '/$', path: '/$', getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, } as any) +const ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRoute = + ProjectVersionDocsFrameworkFrameworkChar123Char125DotmdRouteImport.update({ + id: '/{$}.md', + path: '/{$}.md', + getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute, + } as any) const ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute = ProjectVersionDocsFrameworkFrameworkExamplesSplatRouteImport.update({ id: '/examples/$', @@ -172,13 +172,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LibraryRouteImport parentRoute: typeof rootRouteImport } - '/_library/': { - id: '/_library/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof LibraryIndexRouteImport - parentRoute: typeof LibraryRoute - } '/$project/': { id: '/$project/' path: '/$project' @@ -186,6 +179,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/': { + id: '/_library/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof LibraryIndexRouteImport + parentRoute: typeof LibraryRoute + } '/_library/$project': { id: '/_library/$project' path: '/$project' @@ -193,13 +193,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LibraryProjectRouteImport parentRoute: typeof LibraryRoute } - '/_library/$project/$version/': { - id: '/_library/$project/$version/' - path: '/$version' - fullPath: '/$project/$version/' - preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport - parentRoute: typeof LibraryProjectRoute - } '/$project/$version/docs/': { id: '/$project/$version/docs/' path: '/$project/$version/docs' @@ -207,6 +200,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectVersionDocsIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/$project/$version/': { + id: '/_library/$project/$version/' + path: '/$version' + fullPath: '/$project/$version/' + preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport + parentRoute: typeof LibraryProjectRoute + } '/$project/$version/docs/framework/$framework': { id: '/$project/$version/docs/framework/$framework' path: '/$project/$version/docs/framework/$framework' @@ -221,13 +221,6 @@ 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: '/$' @@ -235,6 +228,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkSplatRouteImport 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/examples/$': { id: '/$project/$version/docs/framework/$framework/examples/$' path: '/examples/$' diff --git a/e2e/solid-router/basepath-file-based/package.json b/e2e/solid-router/basepath-file-based/package.json index 6f7142b43c..7e911dc029 100644 --- a/e2e/solid-router/basepath-file-based/package.json +++ b/e2e/solid-router/basepath-file-based/package.json @@ -18,7 +18,7 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite-plugin-solid": "^3.0.0-next.21", "vite": "^8.0.14" diff --git a/e2e/solid-router/basepath-file-based/src/routeTree.gen.ts b/e2e/solid-router/basepath-file-based/src/routeTree.gen.ts index 333a815c38..98df1e83d8 100644 --- a/e2e/solid-router/basepath-file-based/src/routeTree.gen.ts +++ b/e2e/solid-router/basepath-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/solid-router/basic-esbuild-file-based/package.json b/e2e/solid-router/basic-esbuild-file-based/package.json index b4f9a6272a..f46cca8253 100644 --- a/e2e/solid-router/basic-esbuild-file-based/package.json +++ b/e2e/solid-router/basic-esbuild-file-based/package.json @@ -19,7 +19,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "esbuild": "^0.27.4", "esbuild-plugin-solid": "^0.6.0" 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 baf19eeba0..d3f412f9f3 100644 --- a/e2e/solid-router/basic-file-based-code-splitting/package.json +++ b/e2e/solid-router/basic-file-based-code-splitting/package.json @@ -21,7 +21,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" 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 c0f1e2bc16..17076d5404 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 @@ -9,25 +9,24 @@ // 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 WithoutLoaderRouteImport } from './routes/without-loader' -import { Route as ViewportTestRouteImport } from './routes/viewport-test' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as ViewportTestRouteImport } from './routes/viewport-test' +import { Route as WithoutLoaderRouteImport } from './routes/without-loader' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -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 LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' -const WithoutLoaderRoute = WithoutLoaderRouteImport.update({ - id: '/without-loader', - path: '/without-loader', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const ViewportTestRoute = ViewportTestRouteImport.update({ - id: '/viewport-test', - path: '/viewport-test', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -35,15 +34,20 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const ViewportTestRoute = ViewportTestRouteImport.update({ + id: '/viewport-test', + path: '/viewport-test', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const WithoutLoaderRoute = WithoutLoaderRouteImport.update({ + id: '/without-loader', + path: '/without-loader', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -54,20 +58,16 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -145,18 +145,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/without-loader': { - id: '/without-loader' - path: '/without-loader' - fullPath: '/without-loader' - preLoaderRoute: typeof WithoutLoaderRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport 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 } '/posts': { @@ -166,20 +166,27 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteImport 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 } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/without-loader': { + id: '/without-loader' + path: '/without-loader' + fullPath: '/without-loader' + preLoaderRoute: typeof WithoutLoaderRouteImport parentRoute: typeof rootRouteImport } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -194,12 +201,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/_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' @@ -208,13 +215,6 @@ 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 08f964c8b6..9e7f8cb495 100644 --- a/e2e/solid-router/basic-file-based/package.json +++ b/e2e/solid-router/basic-file-based/package.json @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "combinate": "^1.1.11", "vite": "^8.0.14", diff --git a/e2e/solid-router/basic-file-based/src/routeTree.gen.ts b/e2e/solid-router/basic-file-based/src/routeTree.gen.ts index 04fe587374..c7cbb94b56 100644 --- a/e2e/solid-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/solid-router/basic-file-based/src/routeTree.gen.ts @@ -9,135 +9,134 @@ // 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 RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as MasksRouteImport } from './routes/masks' -import { Route as HoverPreloadHashRouteImport } from './routes/hover-preload-hash' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as ComponentTypesTestRouteImport } from './routes/component-types-test' -import { Route as AnchorRouteImport } from './routes/anchor' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as Char45824Char54620Char48124Char44397RouteRouteImport } from './routes/대한민국/route' -import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as AnchorRouteImport } from './routes/anchor' +import { Route as ComponentTypesTestRouteImport } from './routes/component-types-test' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' +import { Route as HoverPreloadHashRouteImport } from './routes/hover-preload-hash' +import { Route as MasksRouteImport } from './routes/masks' import { Route as NonNestedRouteRouteImport } from './routes/non-nested/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' -import { Route as RelativeIndexRouteImport } from './routes/relative/index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as Char45824Char54620Char48124Char44397RouteRouteImport } from './routes/대한민국/route' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as NonNestedDeepRouteRouteImport } from './routes/non-nested/deep/route' +import { Route as NonNestedNamedRouteRouteImport } from './routes/non-nested/named/route' +import { Route as NonNestedPathRouteRouteImport } from './routes/non-nested/path/route' +import { Route as NonNestedPrefixRouteRouteImport } from './routes/non-nested/prefix/route' +import { Route as NonNestedSuffixRouteRouteImport } from './routes/non-nested/suffix/route' import { Route as ParamsPsIndexRouteImport } from './routes/params-ps/index' -import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' -import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as ParamsPsNonNestedRouteRouteImport } from './routes/params-ps/non-nested/route' +import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as RelativeUseNavigateRouteRouteImport } from './routes/relative/useNavigate/route' +import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as RelativeIndexRouteImport } from './routes/relative/index' import { Route as RelativeLinkRouteRouteImport } from './routes/relative/link/route' -import { Route as ParamsPsNonNestedRouteRouteImport } from './routes/params-ps/non-nested/route' -import { Route as NonNestedSuffixRouteRouteImport } from './routes/non-nested/suffix/route' -import { Route as NonNestedPrefixRouteRouteImport } from './routes/non-nested/prefix/route' -import { Route as NonNestedPathRouteRouteImport } from './routes/non-nested/path/route' -import { Route as NonNestedNamedRouteRouteImport } from './routes/non-nested/named/route' -import { Route as NonNestedDeepRouteRouteImport } from './routes/non-nested/deep/route' -import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' -import { Route as ParamsPsWildcardIndexRouteImport } from './routes/params-ps/wildcard/index' +import { Route as RelativeUseNavigateRouteRouteImport } from './routes/relative/useNavigate/route' +import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' +import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' +import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as MasksAdminUserIdRouteImport } from './routes/masks.admin.$userId' +import { Route as MasksPublicUsernameRouteImport } from './routes/masks.public.$username' +import { Route as NonNestedDeepBazRouteRouteImport } from './routes/non-nested/deep/$baz.route' +import { Route as NonNestedNamedBazRouteRouteImport } from './routes/non-nested/named/$baz.route' +import { Route as NonNestedPathBazRouteRouteImport } from './routes/non-nested/path/baz.route' +import { Route as NonNestedPrefixPrefixChar123bazChar125RouteRouteImport } from './routes/non-nested/prefix/prefix{$baz}.route' +import { Route as NonNestedSuffixChar123bazChar125suffixRouteRouteImport } from './routes/non-nested/suffix/{$baz}suffix.route' import { Route as ParamsPsNamedIndexRouteImport } from './routes/params-ps/named/index' -import { Route as Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport } from './routes/대한민국/🚀.$id' -import { Route as Char45824Char54620Char48124Char44397WildcardSplatRouteImport } from './routes/대한민국/wildcard.$' -import { Route as TransitionTypingCreateResourceRouteImport } from './routes/transition/typing/create-resource' -import { Route as TransitionCountCreateResourceRouteImport } from './routes/transition/count/create-resource' -import { Route as RelativeUseNavigateRelativeUseNavigateBRouteImport } from './routes/relative/useNavigate/relative-useNavigate-b' -import { Route as RelativeUseNavigateRelativeUseNavigateARouteImport } from './routes/relative/useNavigate/relative-useNavigate-a' -import { Route as RelativeLinkRelativeLinkBRouteImport } from './routes/relative/link/relative-link-b' -import { Route as RelativeLinkRelativeLinkARouteImport } from './routes/relative/link/relative-link-a' -import { Route as RedirectPreloadThirdRouteImport } from './routes/redirect/preload/third' -import { Route as RedirectPreloadSecondRouteImport } from './routes/redirect/preload/second' -import { Route as RedirectPreloadFirstRouteImport } from './routes/redirect/preload/first' -import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' -import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -import { Route as ParamsSingleValueRouteImport } from './routes/params.single.$value' -import { Route as ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport } from './routes/params-ps/wildcard/{$}suffix@대' -import { Route as ParamsPsWildcardChar123Char125suffixRouteImport } from './routes/params-ps/wildcard/{$}suffix' -import { Route as ParamsPsWildcardPrefixChar123Char125RouteImport } from './routes/params-ps/wildcard/prefix{$}' -import { Route as ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport } from './routes/params-ps/wildcard/prefix@대{$}' -import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$' -import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix' +import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route' import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}' -import { Route as MasksPublicUsernameRouteImport } from './routes/masks.public.$username' -import { Route as MasksAdminUserIdRouteImport } from './routes/masks.admin.$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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' -import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix' import { Route as ParamsPsNonNestedFooRouteRouteImport } from './routes/params-ps/non-nested/$foo_/route' -import { Route as ParamsPsNamedFooRouteRouteImport } from './routes/params-ps/named/$foo/route' -import { Route as NonNestedSuffixChar123bazChar125suffixRouteRouteImport } from './routes/non-nested/suffix/{$baz}suffix.route' -import { Route as NonNestedPrefixPrefixChar123bazChar125RouteRouteImport } from './routes/non-nested/prefix/prefix{$baz}.route' -import { Route as NonNestedPathBazRouteRouteImport } from './routes/non-nested/path/baz.route' -import { Route as NonNestedNamedBazRouteRouteImport } from './routes/non-nested/named/$baz.route' -import { Route as NonNestedDeepBazRouteRouteImport } from './routes/non-nested/deep/$baz.route' -import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index' -import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index' -import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index' -import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index' -import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index' -import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index' -import { Route as NonNestedSuffixChar123bazChar125suffixIndexRouteImport } from './routes/non-nested/suffix/{$baz}suffix.index' -import { Route as NonNestedPrefixPrefixChar123bazChar125IndexRouteImport } from './routes/non-nested/prefix/prefix{$baz}.index' -import { Route as NonNestedPathBazIndexRouteImport } from './routes/non-nested/path/baz.index' -import { Route as NonNestedNamedBazIndexRouteImport } from './routes/non-nested/named/$baz.index' +import { Route as ParamsPsWildcardIndexRouteImport } from './routes/params-ps/wildcard/index' +import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wildcard/$' +import { Route as ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport } from './routes/params-ps/wildcard/prefix@대{$}' +import { Route as ParamsPsWildcardPrefixChar123Char125RouteImport } from './routes/params-ps/wildcard/prefix{$}' +import { Route as ParamsPsWildcardChar123Char125suffixRouteImport } from './routes/params-ps/wildcard/{$}suffix' +import { Route as ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport } from './routes/params-ps/wildcard/{$}suffix@대' +import { Route as ParamsSingleValueRouteImport } from './routes/params.single.$value' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' +import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' +import { Route as RedirectTargetViaBeforeLoadRouteImport } from './routes/redirect/$target/via-beforeLoad' +import { Route as RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' +import { Route as RedirectPreloadFirstRouteImport } from './routes/redirect/preload/first' +import { Route as RedirectPreloadSecondRouteImport } from './routes/redirect/preload/second' +import { Route as RedirectPreloadThirdRouteImport } from './routes/redirect/preload/third' +import { Route as RelativeLinkRelativeLinkARouteImport } from './routes/relative/link/relative-link-a' +import { Route as RelativeLinkRelativeLinkBRouteImport } from './routes/relative/link/relative-link-b' +import { Route as RelativeUseNavigateRelativeUseNavigateARouteImport } from './routes/relative/useNavigate/relative-useNavigate-a' +import { Route as RelativeUseNavigateRelativeUseNavigateBRouteImport } from './routes/relative/useNavigate/relative-useNavigate-b' +import { Route as TransitionCountCreateResourceRouteImport } from './routes/transition/count/create-resource' +import { Route as TransitionTypingCreateResourceRouteImport } from './routes/transition/typing/create-resource' +import { Route as Char45824Char54620Char48124Char44397WildcardSplatRouteImport } from './routes/대한민국/wildcard.$' +import { Route as Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport } from './routes/대한민국/🚀.$id' import { Route as NonNestedDeepBazIndexRouteImport } from './routes/non-nested/deep/$baz.index' -import { Route as ParamsPsNonNestedFooBarRouteImport } from './routes/params-ps/non-nested/$foo_/$bar' -import { Route as NonNestedSuffixChar123bazChar125suffixBarRouteImport } from './routes/non-nested/suffix/{$baz}suffix_.bar' -import { Route as NonNestedSuffixChar123bazChar125suffixFooRouteImport } from './routes/non-nested/suffix/{$baz}suffix.foo' -import { Route as NonNestedPrefixPrefixChar123bazChar125BarRouteImport } from './routes/non-nested/prefix/prefix{$baz}_.bar' -import { Route as NonNestedPrefixPrefixChar123bazChar125FooRouteImport } from './routes/non-nested/prefix/prefix{$baz}.foo' -import { Route as NonNestedPathBazBarRouteImport } from './routes/non-nested/path/baz_.bar' -import { Route as NonNestedPathBazFooRouteImport } from './routes/non-nested/path/baz.foo' -import { Route as NonNestedNamedBazBarRouteImport } from './routes/non-nested/named/$baz_.bar' +import { Route as NonNestedDeepBazBarRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.route' +import { Route as NonNestedNamedBazIndexRouteImport } from './routes/non-nested/named/$baz.index' import { Route as NonNestedNamedBazFooRouteImport } from './routes/non-nested/named/$baz.foo' +import { Route as NonNestedNamedBazBarRouteImport } from './routes/non-nested/named/$baz_.bar' +import { Route as NonNestedPathBazIndexRouteImport } from './routes/non-nested/path/baz.index' +import { Route as NonNestedPathBazFooRouteImport } from './routes/non-nested/path/baz.foo' +import { Route as NonNestedPathBazBarRouteImport } from './routes/non-nested/path/baz_.bar' +import { Route as NonNestedPrefixPrefixChar123bazChar125IndexRouteImport } from './routes/non-nested/prefix/prefix{$baz}.index' +import { Route as NonNestedPrefixPrefixChar123bazChar125FooRouteImport } from './routes/non-nested/prefix/prefix{$baz}.foo' +import { Route as NonNestedPrefixPrefixChar123bazChar125BarRouteImport } from './routes/non-nested/prefix/prefix{$baz}_.bar' +import { Route as NonNestedSuffixChar123bazChar125suffixIndexRouteImport } from './routes/non-nested/suffix/{$baz}suffix.index' +import { Route as NonNestedSuffixChar123bazChar125suffixFooRouteImport } from './routes/non-nested/suffix/{$baz}suffix.foo' +import { Route as NonNestedSuffixChar123bazChar125suffixBarRouteImport } from './routes/non-nested/suffix/{$baz}suffix_.bar' import { Route as ParamsPsNamedFooBarRouteRouteImport } from './routes/params-ps/named/$foo/$bar.route' -import { Route as NonNestedDeepBazBarRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.route' -import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index' -import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index' -import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index' -import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' +import { Route as ParamsPsNonNestedFooBarRouteImport } from './routes/params-ps/non-nested/$foo_/$bar' +import { Route as RelativeLinkNestedIndexRouteImport } from './routes/relative/link/nested/index' +import { Route as RelativeLinkPathIndexRouteImport } from './routes/relative/link/path/index' +import { Route as RelativeLinkWithSearchIndexRouteImport } from './routes/relative/link/with-search/index' +import { Route as RelativeUseNavigateNestedIndexRouteImport } from './routes/relative/useNavigate/nested/index' +import { Route as RelativeUseNavigatePathIndexRouteImport } from './routes/relative/useNavigate/path/index' +import { Route as RelativeUseNavigateWithSearchIndexRouteImport } from './routes/relative/useNavigate/with-search/index' import { Route as NonNestedDeepBazBarIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.index' -import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' -import { Route as NonNestedDeepBazBarQuxRouteImport } from './routes/non-nested/deep/$baz_.bar_.qux' import { Route as NonNestedDeepBazBarFooRouteRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.route' +import { Route as NonNestedDeepBazBarQuxRouteImport } from './routes/non-nested/deep/$baz_.bar_.qux' +import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' +import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' +import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative/link/path/$path/index' +import { Route as RelativeUseNavigateNestedDeepIndexRouteImport } from './routes/relative/useNavigate/nested/deep/index' +import { Route as RelativeUseNavigatePathPathIndexRouteImport } from './routes/relative/useNavigate/path/$path/index' import { Route as NonNestedDeepBazBarFooIndexRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo.index' import { Route as NonNestedDeepBazBarFooQuxRouteImport } from './routes/non-nested/deep/$baz_.bar.$foo_.qux' -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ - id: '/notRemountDeps', - path: '/notRemountDeps', +const AnchorRoute = AnchorRouteImport.update({ + id: '/anchor', + path: '/anchor', getParentRoute: () => rootRouteImport, } as any) -const MasksRoute = MasksRouteImport.update({ - id: '/masks', - path: '/masks', +const ComponentTypesTestRoute = ComponentTypesTestRouteImport.update({ + id: '/component-types-test', + path: '/component-types-test', getParentRoute: () => rootRouteImport, } as any) -const HoverPreloadHashRoute = HoverPreloadHashRouteImport.update({ - id: '/hover-preload-hash', - path: '/hover-preload-hash', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', getParentRoute: () => rootRouteImport, } as any) const EditingBRoute = EditingBRouteImport.update({ @@ -145,234 +144,286 @@ const EditingBRoute = EditingBRouteImport.update({ path: '/editing-b', getParentRoute: () => rootRouteImport, } as any) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const HoverPreloadHashRoute = HoverPreloadHashRouteImport.update({ + id: '/hover-preload-hash', + path: '/hover-preload-hash', getParentRoute: () => rootRouteImport, } as any) -const ComponentTypesTestRoute = ComponentTypesTestRouteImport.update({ - id: '/component-types-test', - path: '/component-types-test', +const MasksRoute = MasksRouteImport.update({ + id: '/masks', + path: '/masks', getParentRoute: () => rootRouteImport, } as any) -const AnchorRoute = AnchorRouteImport.update({ - id: '/anchor', - path: '/anchor', +const NonNestedRouteRoute = NonNestedRouteRouteImport.update({ + id: '/non-nested', + path: '/non-nested', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ + id: '/notRemountDeps', + path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any) -const Char45824Char54620Char48124Char44397RouteRoute = - Char45824Char54620Char48124Char44397RouteRouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any) -const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ - id: '/search-params', - path: '/search-params', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const NonNestedRouteRoute = NonNestedRouteRouteImport.update({ - id: '/non-nested', - path: '/non-nested', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ + id: '/search-params', + path: '/search-params', getParentRoute: () => rootRouteImport, } as any) -const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RelativeIndexRoute = RelativeIndexRouteImport.update({ - id: '/relative/', - path: '/relative/', +const Char45824Char54620Char48124Char44397RouteRoute = + Char45824Char54620Char48124Char44397RouteRouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => rootRouteImport, + } as any) +const anotherGroupOnlyrouteinsideRoute = + anotherGroupOnlyrouteinsideRouteImport.update({ + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', + getParentRoute: () => rootRouteImport, + } as any) +const groupLayoutRoute = groupLayoutRouteImport.update({ + id: '/(group)/_layout', getParentRoute: () => rootRouteImport, } as any) -const RedirectIndexRoute = RedirectIndexRouteImport.update({ - id: '/redirect/', - path: '/redirect/', +const groupInsideRoute = groupInsideRouteImport.update({ + id: '/(group)/inside', + path: '/inside', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, +const groupLazyinsideRoute = groupLazyinsideRouteImport + .update({ + id: '/(group)/lazyinside', + path: '/lazyinside', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => import('./routes/(group)/lazyinside.lazy').then((d) => d.Route)) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) +const NonNestedDeepRouteRoute = NonNestedDeepRouteRouteImport.update({ + id: '/deep', + path: '/deep', + getParentRoute: () => NonNestedRouteRoute, +} as any) +const NonNestedNamedRouteRoute = NonNestedNamedRouteRouteImport.update({ + id: '/named', + path: '/named', + getParentRoute: () => NonNestedRouteRoute, +} as any) +const NonNestedPathRouteRoute = NonNestedPathRouteRouteImport.update({ + id: '/path', + path: '/path', + getParentRoute: () => NonNestedRouteRoute, +} as any) +const NonNestedPrefixRouteRoute = NonNestedPrefixRouteRouteImport.update({ + id: '/prefix', + path: '/prefix', + getParentRoute: () => NonNestedRouteRoute, +} as any) +const NonNestedSuffixRouteRoute = NonNestedSuffixRouteRouteImport.update({ + id: '/suffix', + path: '/suffix', + getParentRoute: () => NonNestedRouteRoute, } as any) const ParamsPsIndexRoute = ParamsPsIndexRouteImport.update({ id: '/params-ps/', path: '/params-ps/', getParentRoute: () => rootRouteImport, } as any) -const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ - id: '/default', - path: '/default', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RedirectTargetRoute = RedirectTargetRouteImport.update({ - id: '/redirect/$target', - path: '/redirect/$target', +const ParamsPsNonNestedRouteRoute = ParamsPsNonNestedRouteRouteImport.update({ + id: '/params-ps/non-nested', + path: '/params-ps/non-nested', getParentRoute: () => rootRouteImport, } as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const RedirectIndexRoute = RedirectIndexRouteImport.update({ + id: '/redirect/', + path: '/redirect/', + getParentRoute: () => rootRouteImport, } as any) -const groupLazyinsideRoute = groupLazyinsideRouteImport - .update({ - id: '/(group)/lazyinside', - path: '/lazyinside', - getParentRoute: () => rootRouteImport, - } as any) - .lazy(() => import('./routes/(group)/lazyinside.lazy').then((d) => d.Route)) -const groupInsideRoute = groupInsideRouteImport.update({ - id: '/(group)/inside', - path: '/inside', +const RedirectTargetRoute = RedirectTargetRouteImport.update({ + id: '/redirect/$target', + path: '/redirect/$target', getParentRoute: () => rootRouteImport, } as any) -const groupLayoutRoute = groupLayoutRouteImport.update({ - id: '/(group)/_layout', +const RelativeIndexRoute = RelativeIndexRouteImport.update({ + id: '/relative/', + path: '/relative/', + getParentRoute: () => rootRouteImport, +} as any) +const RelativeLinkRouteRoute = RelativeLinkRouteRouteImport.update({ + id: '/relative/link', + path: '/relative/link', getParentRoute: () => rootRouteImport, } as any) -const anotherGroupOnlyrouteinsideRoute = - anotherGroupOnlyrouteinsideRouteImport.update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', - getParentRoute: () => rootRouteImport, - } as any) const RelativeUseNavigateRouteRoute = RelativeUseNavigateRouteRouteImport.update({ id: '/relative/useNavigate', path: '/relative/useNavigate', getParentRoute: () => rootRouteImport, } as any) -const RelativeLinkRouteRoute = RelativeLinkRouteRouteImport.update({ - id: '/relative/link', - path: '/relative/link', - getParentRoute: () => rootRouteImport, +const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const ParamsPsNonNestedRouteRoute = ParamsPsNonNestedRouteRouteImport.update({ - id: '/params-ps/non-nested', - path: '/params-ps/non-nested', +const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ + id: '/default', + path: '/default', + getParentRoute: () => SearchParamsRouteRoute, +} as any) +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, +} as any) +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', getParentRoute: () => rootRouteImport, } as any) -const NonNestedSuffixRouteRoute = NonNestedSuffixRouteRouteImport.update({ - id: '/suffix', - path: '/suffix', - getParentRoute: () => NonNestedRouteRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) -const NonNestedPrefixRouteRoute = NonNestedPrefixRouteRouteImport.update({ - id: '/prefix', - path: '/prefix', - getParentRoute: () => NonNestedRouteRoute, +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, } as any) -const NonNestedPathRouteRoute = NonNestedPathRouteRouteImport.update({ - id: '/path', - path: '/path', - getParentRoute: () => NonNestedRouteRoute, +const MasksAdminUserIdRoute = MasksAdminUserIdRouteImport.update({ + id: '/admin/$userId', + path: '/admin/$userId', + getParentRoute: () => MasksRoute, } as any) -const NonNestedNamedRouteRoute = NonNestedNamedRouteRouteImport.update({ - id: '/named', - path: '/named', - getParentRoute: () => NonNestedRouteRoute, +const MasksPublicUsernameRoute = MasksPublicUsernameRouteImport.update({ + id: '/public/$username', + path: '/public/$username', + getParentRoute: () => MasksRoute, } as any) -const NonNestedDeepRouteRoute = NonNestedDeepRouteRouteImport.update({ - id: '/deep', - path: '/deep', - getParentRoute: () => NonNestedRouteRoute, +const NonNestedDeepBazRouteRoute = NonNestedDeepBazRouteRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => NonNestedDeepRouteRoute, } as any) -const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => RedirectTargetRoute, +const NonNestedNamedBazRouteRoute = NonNestedNamedBazRouteRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => NonNestedNamedRouteRoute, } as any) -const ParamsPsWildcardIndexRoute = ParamsPsWildcardIndexRouteImport.update({ - id: '/params-ps/wildcard/', - path: '/params-ps/wildcard/', - getParentRoute: () => rootRouteImport, +const NonNestedPathBazRouteRoute = NonNestedPathBazRouteRouteImport.update({ + id: '/baz', + path: '/baz', + getParentRoute: () => NonNestedPathRouteRoute, } as any) +const NonNestedPrefixPrefixChar123bazChar125RouteRoute = + NonNestedPrefixPrefixChar123bazChar125RouteRouteImport.update({ + id: '/prefix{$baz}', + path: '/prefix{$baz}', + getParentRoute: () => NonNestedPrefixRouteRoute, + } as any) +const NonNestedSuffixChar123bazChar125suffixRouteRoute = + NonNestedSuffixChar123bazChar125suffixRouteRouteImport.update({ + id: '/{$baz}suffix', + path: '/{$baz}suffix', + getParentRoute: () => NonNestedSuffixRouteRoute, + } as any) const ParamsPsNamedIndexRoute = ParamsPsNamedIndexRouteImport.update({ id: '/params-ps/named/', path: '/params-ps/named/', getParentRoute: () => rootRouteImport, } as any) -const Char45824Char54620Char48124Char44397Char55357Char56960IdRoute = - Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport.update({ - id: '/🚀/$id', - path: '/🚀/$id', - getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, - } as any) -const Char45824Char54620Char48124Char44397WildcardSplatRoute = - Char45824Char54620Char48124Char44397WildcardSplatRouteImport.update({ - id: '/wildcard/$', - path: '/wildcard/$', - getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, - } as any) -const TransitionTypingCreateResourceRoute = - TransitionTypingCreateResourceRouteImport.update({ - id: '/transition/typing/create-resource', - path: '/transition/typing/create-resource', +const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({ + id: '/params-ps/named/$foo', + path: '/params-ps/named/$foo', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsNamedPrefixChar123fooChar125Route = + ParamsPsNamedPrefixChar123fooChar125RouteImport.update({ + id: '/params-ps/named/prefix{$foo}', + path: '/params-ps/named/prefix{$foo}', getParentRoute: () => rootRouteImport, } as any) -const TransitionCountCreateResourceRoute = - TransitionCountCreateResourceRouteImport.update({ - id: '/transition/count/create-resource', - path: '/transition/count/create-resource', +const ParamsPsNamedChar123fooChar125suffixRoute = + ParamsPsNamedChar123fooChar125suffixRouteImport.update({ + id: '/params-ps/named/{$foo}suffix', + path: '/params-ps/named/{$foo}suffix', getParentRoute: () => rootRouteImport, } as any) -const RelativeUseNavigateRelativeUseNavigateBRoute = - RelativeUseNavigateRelativeUseNavigateBRouteImport.update({ - id: '/relative-useNavigate-b', - path: '/relative-useNavigate-b', - getParentRoute: () => RelativeUseNavigateRouteRoute, +const ParamsPsNonNestedFooRouteRoute = + ParamsPsNonNestedFooRouteRouteImport.update({ + id: '/$foo_', + path: '/$foo', + getParentRoute: () => ParamsPsNonNestedRouteRoute, } as any) -const RelativeUseNavigateRelativeUseNavigateARoute = - RelativeUseNavigateRelativeUseNavigateARouteImport.update({ - id: '/relative-useNavigate-a', - path: '/relative-useNavigate-a', - getParentRoute: () => RelativeUseNavigateRouteRoute, +const ParamsPsWildcardIndexRoute = ParamsPsWildcardIndexRouteImport.update({ + id: '/params-ps/wildcard/', + path: '/params-ps/wildcard/', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsWildcardSplatRoute = ParamsPsWildcardSplatRouteImport.update({ + id: '/params-ps/wildcard/$', + path: '/params-ps/wildcard/$', + getParentRoute: () => rootRouteImport, +} as any) +const ParamsPsWildcardPrefixAtChar45824Char123Char125Route = + ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport.update({ + id: '/params-ps/wildcard/prefix@대{$}', + path: '/params-ps/wildcard/prefix@대{$}', + getParentRoute: () => rootRouteImport, } as any) -const RelativeLinkRelativeLinkBRoute = - RelativeLinkRelativeLinkBRouteImport.update({ - id: '/relative-link-b', - path: '/relative-link-b', - getParentRoute: () => RelativeLinkRouteRoute, +const ParamsPsWildcardPrefixChar123Char125Route = + ParamsPsWildcardPrefixChar123Char125RouteImport.update({ + id: '/params-ps/wildcard/prefix{$}', + path: '/params-ps/wildcard/prefix{$}', + getParentRoute: () => rootRouteImport, } as any) -const RelativeLinkRelativeLinkARoute = - RelativeLinkRelativeLinkARouteImport.update({ - id: '/relative-link-a', - path: '/relative-link-a', - getParentRoute: () => RelativeLinkRouteRoute, +const ParamsPsWildcardChar123Char125suffixRoute = + ParamsPsWildcardChar123Char125suffixRouteImport.update({ + id: '/params-ps/wildcard/{$}suffix', + path: '/params-ps/wildcard/{$}suffix', + getParentRoute: () => rootRouteImport, } as any) -const RedirectPreloadThirdRoute = RedirectPreloadThirdRouteImport.update({ - id: '/redirect/preload/third', - path: '/redirect/preload/third', - getParentRoute: () => rootRouteImport, -} as any) -const RedirectPreloadSecondRoute = RedirectPreloadSecondRouteImport.update({ - id: '/redirect/preload/second', - path: '/redirect/preload/second', +const ParamsPsWildcardChar123Char125suffixAtChar45824Route = + ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport.update({ + id: '/params-ps/wildcard/{$}suffix@대', + path: '/params-ps/wildcard/{$}suffix@대', + getParentRoute: () => rootRouteImport, + } as any) +const ParamsSingleValueRoute = ParamsSingleValueRouteImport.update({ + id: '/params/single/$value', + path: '/params/single/$value', getParentRoute: () => rootRouteImport, } as any) -const RedirectPreloadFirstRoute = RedirectPreloadFirstRouteImport.update({ - id: '/redirect/preload/first', - path: '/redirect/preload/first', +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', getParentRoute: () => rootRouteImport, } as any) -const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', +const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => RedirectTargetRoute, } as any) const RedirectTargetViaBeforeLoadRoute = @@ -381,270 +432,195 @@ const RedirectTargetViaBeforeLoadRoute = path: '/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', +const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => RedirectTargetRoute, +} as any) +const RedirectPreloadFirstRoute = RedirectPreloadFirstRouteImport.update({ + id: '/redirect/preload/first', + path: '/redirect/preload/first', getParentRoute: () => rootRouteImport, } as any) -const ParamsSingleValueRoute = ParamsSingleValueRouteImport.update({ - id: '/params/single/$value', - path: '/params/single/$value', +const RedirectPreloadSecondRoute = RedirectPreloadSecondRouteImport.update({ + id: '/redirect/preload/second', + path: '/redirect/preload/second', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsWildcardChar123Char125suffixAtChar45824Route = - ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport.update({ - id: '/params-ps/wildcard/{$}suffix@대', - path: '/params-ps/wildcard/{$}suffix@대', - getParentRoute: () => rootRouteImport, +const RedirectPreloadThirdRoute = RedirectPreloadThirdRouteImport.update({ + id: '/redirect/preload/third', + path: '/redirect/preload/third', + getParentRoute: () => rootRouteImport, +} as any) +const RelativeLinkRelativeLinkARoute = + RelativeLinkRelativeLinkARouteImport.update({ + id: '/relative-link-a', + path: '/relative-link-a', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const ParamsPsWildcardChar123Char125suffixRoute = - ParamsPsWildcardChar123Char125suffixRouteImport.update({ - id: '/params-ps/wildcard/{$}suffix', - path: '/params-ps/wildcard/{$}suffix', - getParentRoute: () => rootRouteImport, +const RelativeLinkRelativeLinkBRoute = + RelativeLinkRelativeLinkBRouteImport.update({ + id: '/relative-link-b', + path: '/relative-link-b', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const ParamsPsWildcardPrefixChar123Char125Route = - ParamsPsWildcardPrefixChar123Char125RouteImport.update({ - id: '/params-ps/wildcard/prefix{$}', - path: '/params-ps/wildcard/prefix{$}', - getParentRoute: () => rootRouteImport, +const RelativeUseNavigateRelativeUseNavigateARoute = + RelativeUseNavigateRelativeUseNavigateARouteImport.update({ + id: '/relative-useNavigate-a', + path: '/relative-useNavigate-a', + getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const ParamsPsWildcardPrefixAtChar45824Char123Char125Route = - ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport.update({ - id: '/params-ps/wildcard/prefix@대{$}', - path: '/params-ps/wildcard/prefix@대{$}', - getParentRoute: () => rootRouteImport, +const RelativeUseNavigateRelativeUseNavigateBRoute = + RelativeUseNavigateRelativeUseNavigateBRouteImport.update({ + id: '/relative-useNavigate-b', + path: '/relative-useNavigate-b', + getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const ParamsPsWildcardSplatRoute = ParamsPsWildcardSplatRouteImport.update({ - id: '/params-ps/wildcard/$', - path: '/params-ps/wildcard/$', - getParentRoute: () => rootRouteImport, -} as any) -const ParamsPsNamedChar123fooChar125suffixRoute = - ParamsPsNamedChar123fooChar125suffixRouteImport.update({ - id: '/params-ps/named/{$foo}suffix', - path: '/params-ps/named/{$foo}suffix', +const TransitionCountCreateResourceRoute = + TransitionCountCreateResourceRouteImport.update({ + id: '/transition/count/create-resource', + path: '/transition/count/create-resource', getParentRoute: () => rootRouteImport, } as any) -const ParamsPsNamedPrefixChar123fooChar125Route = - ParamsPsNamedPrefixChar123fooChar125RouteImport.update({ - id: '/params-ps/named/prefix{$foo}', - path: '/params-ps/named/prefix{$foo}', +const TransitionTypingCreateResourceRoute = + TransitionTypingCreateResourceRouteImport.update({ + id: '/transition/typing/create-resource', + path: '/transition/typing/create-resource', getParentRoute: () => rootRouteImport, } as any) -const MasksPublicUsernameRoute = MasksPublicUsernameRouteImport.update({ - id: '/public/$username', - path: '/public/$username', - getParentRoute: () => MasksRoute, +const Char45824Char54620Char48124Char44397WildcardSplatRoute = + Char45824Char54620Char48124Char44397WildcardSplatRouteImport.update({ + id: '/wildcard/$', + path: '/wildcard/$', + getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, + } as any) +const Char45824Char54620Char48124Char44397Char55357Char56960IdRoute = + Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport.update({ + id: '/🚀/$id', + path: '/🚀/$id', + getParentRoute: () => Char45824Char54620Char48124Char44397RouteRoute, + } as any) +const NonNestedDeepBazIndexRoute = NonNestedDeepBazIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedDeepBazRouteRoute, } as any) -const MasksAdminUserIdRoute = MasksAdminUserIdRouteImport.update({ - id: '/admin/$userId', - path: '/admin/$userId', - getParentRoute: () => MasksRoute, +const NonNestedDeepBazBarRouteRoute = + NonNestedDeepBazBarRouteRouteImport.update({ + id: '/$baz_/bar', + path: '/$baz/bar', + getParentRoute: () => NonNestedDeepRouteRoute, + } as any) +const NonNestedNamedBazIndexRoute = NonNestedNamedBazIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedNamedBazRouteRoute, } as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const NonNestedNamedBazFooRoute = NonNestedNamedBazFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NonNestedNamedBazRouteRoute, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const NonNestedNamedBazBarRoute = NonNestedNamedBazBarRouteImport.update({ + id: '/$baz_/bar', + path: '/$baz/bar', + getParentRoute: () => NonNestedNamedRouteRoute, } as any) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', - getParentRoute: () => rootRouteImport, +const NonNestedPathBazIndexRoute = NonNestedPathBazIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedPathBazRouteRoute, } as any) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, +const NonNestedPathBazFooRoute = NonNestedPathBazFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NonNestedPathBazRouteRoute, } as any) -const ParamsPsNonNestedFooRouteRoute = - ParamsPsNonNestedFooRouteRouteImport.update({ - id: '/$foo_', - path: '/$foo', - getParentRoute: () => ParamsPsNonNestedRouteRoute, - } as any) -const ParamsPsNamedFooRouteRoute = ParamsPsNamedFooRouteRouteImport.update({ - id: '/params-ps/named/$foo', - path: '/params-ps/named/$foo', - getParentRoute: () => rootRouteImport, +const NonNestedPathBazBarRoute = NonNestedPathBazBarRouteImport.update({ + id: '/baz_/bar', + path: '/baz/bar', + getParentRoute: () => NonNestedPathRouteRoute, } as any) -const NonNestedSuffixChar123bazChar125suffixRouteRoute = - NonNestedSuffixChar123bazChar125suffixRouteRouteImport.update({ - id: '/{$baz}suffix', - path: '/{$baz}suffix', - getParentRoute: () => NonNestedSuffixRouteRoute, +const NonNestedPrefixPrefixChar123bazChar125IndexRoute = + NonNestedPrefixPrefixChar123bazChar125IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125RouteRoute = - NonNestedPrefixPrefixChar123bazChar125RouteRouteImport.update({ - id: '/prefix{$baz}', - path: '/prefix{$baz}', +const NonNestedPrefixPrefixChar123bazChar125FooRoute = + NonNestedPrefixPrefixChar123bazChar125FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, + } as any) +const NonNestedPrefixPrefixChar123bazChar125BarRoute = + NonNestedPrefixPrefixChar123bazChar125BarRouteImport.update({ + id: '/prefix{$baz}_/bar', + path: '/prefix{$baz}/bar', getParentRoute: () => NonNestedPrefixRouteRoute, } as any) -const NonNestedPathBazRouteRoute = NonNestedPathBazRouteRouteImport.update({ - id: '/baz', - path: '/baz', - getParentRoute: () => NonNestedPathRouteRoute, -} as any) -const NonNestedNamedBazRouteRoute = NonNestedNamedBazRouteRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => NonNestedNamedRouteRoute, -} as any) -const NonNestedDeepBazRouteRoute = NonNestedDeepBazRouteRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => NonNestedDeepRouteRoute, -} as any) -const RelativeUseNavigateWithSearchIndexRoute = - RelativeUseNavigateWithSearchIndexRouteImport.update({ - id: '/with-search/', - path: '/with-search/', - getParentRoute: () => RelativeUseNavigateRouteRoute, - } as any) -const RelativeUseNavigatePathIndexRoute = - RelativeUseNavigatePathIndexRouteImport.update({ - id: '/path/', - path: '/path/', - getParentRoute: () => RelativeUseNavigateRouteRoute, - } as any) -const RelativeUseNavigateNestedIndexRoute = - RelativeUseNavigateNestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => RelativeUseNavigateRouteRoute, - } as any) -const RelativeLinkWithSearchIndexRoute = - RelativeLinkWithSearchIndexRouteImport.update({ - id: '/with-search/', - path: '/with-search/', - getParentRoute: () => RelativeLinkRouteRoute, - } as any) -const RelativeLinkPathIndexRoute = RelativeLinkPathIndexRouteImport.update({ - id: '/path/', - path: '/path/', - getParentRoute: () => RelativeLinkRouteRoute, -} as any) -const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => RelativeLinkRouteRoute, -} as any) const NonNestedSuffixChar123bazChar125suffixIndexRoute = NonNestedSuffixChar123bazChar125suffixIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125IndexRoute = - NonNestedPrefixPrefixChar123bazChar125IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, - } as any) -const NonNestedPathBazIndexRoute = NonNestedPathBazIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedPathBazRouteRoute, -} as any) -const NonNestedNamedBazIndexRoute = NonNestedNamedBazIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedNamedBazRouteRoute, -} as any) -const NonNestedDeepBazIndexRoute = NonNestedDeepBazIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => NonNestedDeepBazRouteRoute, -} as any) -const ParamsPsNonNestedFooBarRoute = ParamsPsNonNestedFooBarRouteImport.update({ - id: '/$bar', - path: '/$bar', - getParentRoute: () => ParamsPsNonNestedFooRouteRoute, -} as any) -const NonNestedSuffixChar123bazChar125suffixBarRoute = - NonNestedSuffixChar123bazChar125suffixBarRouteImport.update({ - id: '/{$baz}suffix_/bar', - path: '/{$baz}suffix/bar', - getParentRoute: () => NonNestedSuffixRouteRoute, - } as any) const NonNestedSuffixChar123bazChar125suffixFooRoute = NonNestedSuffixChar123bazChar125suffixFooRouteImport.update({ id: '/foo', path: '/foo', getParentRoute: () => NonNestedSuffixChar123bazChar125suffixRouteRoute, } as any) -const NonNestedPrefixPrefixChar123bazChar125BarRoute = - NonNestedPrefixPrefixChar123bazChar125BarRouteImport.update({ - id: '/prefix{$baz}_/bar', - path: '/prefix{$baz}/bar', - getParentRoute: () => NonNestedPrefixRouteRoute, - } as any) -const NonNestedPrefixPrefixChar123bazChar125FooRoute = - NonNestedPrefixPrefixChar123bazChar125FooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => NonNestedPrefixPrefixChar123bazChar125RouteRoute, +const NonNestedSuffixChar123bazChar125suffixBarRoute = + NonNestedSuffixChar123bazChar125suffixBarRouteImport.update({ + id: '/{$baz}suffix_/bar', + path: '/{$baz}suffix/bar', + getParentRoute: () => NonNestedSuffixRouteRoute, } as any) -const NonNestedPathBazBarRoute = NonNestedPathBazBarRouteImport.update({ - id: '/baz_/bar', - path: '/baz/bar', - getParentRoute: () => NonNestedPathRouteRoute, -} as any) -const NonNestedPathBazFooRoute = NonNestedPathBazFooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => NonNestedPathBazRouteRoute, -} as any) -const NonNestedNamedBazBarRoute = NonNestedNamedBazBarRouteImport.update({ - id: '/$baz_/bar', - path: '/$baz/bar', - getParentRoute: () => NonNestedNamedRouteRoute, -} as any) -const NonNestedNamedBazFooRoute = NonNestedNamedBazFooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => NonNestedNamedBazRouteRoute, -} as any) const ParamsPsNamedFooBarRouteRoute = ParamsPsNamedFooBarRouteRouteImport.update({ id: '/$bar', path: '/$bar', getParentRoute: () => ParamsPsNamedFooRouteRoute, } as any) -const NonNestedDeepBazBarRouteRoute = - NonNestedDeepBazBarRouteRouteImport.update({ - id: '/$baz_/bar', - path: '/$baz/bar', - getParentRoute: () => NonNestedDeepRouteRoute, +const ParamsPsNonNestedFooBarRoute = ParamsPsNonNestedFooBarRouteImport.update({ + id: '/$bar', + path: '/$bar', + getParentRoute: () => ParamsPsNonNestedFooRouteRoute, +} as any) +const RelativeLinkNestedIndexRoute = RelativeLinkNestedIndexRouteImport.update({ + id: '/nested/', + path: '/nested/', + getParentRoute: () => RelativeLinkRouteRoute, +} as any) +const RelativeLinkPathIndexRoute = RelativeLinkPathIndexRouteImport.update({ + id: '/path/', + path: '/path/', + getParentRoute: () => RelativeLinkRouteRoute, +} as any) +const RelativeLinkWithSearchIndexRoute = + RelativeLinkWithSearchIndexRouteImport.update({ + id: '/with-search/', + path: '/with-search/', + getParentRoute: () => RelativeLinkRouteRoute, } as any) -const RelativeUseNavigatePathPathIndexRoute = - RelativeUseNavigatePathPathIndexRouteImport.update({ - id: '/path/$path/', - path: '/path/$path/', +const RelativeUseNavigateNestedIndexRoute = + RelativeUseNavigateNestedIndexRouteImport.update({ + id: '/nested/', + path: '/nested/', getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeUseNavigateNestedDeepIndexRoute = - RelativeUseNavigateNestedDeepIndexRouteImport.update({ - id: '/nested/deep/', - path: '/nested/deep/', +const RelativeUseNavigatePathIndexRoute = + RelativeUseNavigatePathIndexRouteImport.update({ + id: '/path/', + path: '/path/', getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) -const RelativeLinkPathPathIndexRoute = - RelativeLinkPathPathIndexRouteImport.update({ - id: '/path/$path/', - path: '/path/$path/', - getParentRoute: () => RelativeLinkRouteRoute, - } as any) -const RelativeLinkNestedDeepIndexRoute = - RelativeLinkNestedDeepIndexRouteImport.update({ - id: '/nested/deep/', - path: '/nested/deep/', - getParentRoute: () => RelativeLinkRouteRoute, +const RelativeUseNavigateWithSearchIndexRoute = + RelativeUseNavigateWithSearchIndexRouteImport.update({ + id: '/with-search/', + path: '/with-search/', + getParentRoute: () => RelativeUseNavigateRouteRoute, } as any) const NonNestedDeepBazBarIndexRoute = NonNestedDeepBazBarIndexRouteImport.update({ @@ -652,22 +628,46 @@ const NonNestedDeepBazBarIndexRoute = path: '/', getParentRoute: () => NonNestedDeepBazBarRouteRoute, } as any) -const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ - id: '/$baz', - path: '/$baz', - getParentRoute: () => ParamsPsNamedFooBarRouteRoute, -} as any) -const NonNestedDeepBazBarQuxRoute = NonNestedDeepBazBarQuxRouteImport.update({ - id: '/$baz_/bar_/qux', - path: '/$baz/bar/qux', - getParentRoute: () => NonNestedDeepRouteRoute, -} as any) const NonNestedDeepBazBarFooRouteRoute = NonNestedDeepBazBarFooRouteRouteImport.update({ id: '/$foo', path: '/$foo', getParentRoute: () => NonNestedDeepBazBarRouteRoute, } as any) +const NonNestedDeepBazBarQuxRoute = NonNestedDeepBazBarQuxRouteImport.update({ + id: '/$baz_/bar_/qux', + path: '/$baz/bar/qux', + getParentRoute: () => NonNestedDeepRouteRoute, +} as any) +const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ + id: '/$baz', + path: '/$baz', + getParentRoute: () => ParamsPsNamedFooBarRouteRoute, +} as any) +const RelativeLinkNestedDeepIndexRoute = + RelativeLinkNestedDeepIndexRouteImport.update({ + id: '/nested/deep/', + path: '/nested/deep/', + getParentRoute: () => RelativeLinkRouteRoute, + } as any) +const RelativeLinkPathPathIndexRoute = + RelativeLinkPathPathIndexRouteImport.update({ + id: '/path/$path/', + path: '/path/$path/', + getParentRoute: () => RelativeLinkRouteRoute, + } as any) +const RelativeUseNavigateNestedDeepIndexRoute = + RelativeUseNavigateNestedDeepIndexRouteImport.update({ + id: '/nested/deep/', + path: '/nested/deep/', + getParentRoute: () => RelativeUseNavigateRouteRoute, + } as any) +const RelativeUseNavigatePathPathIndexRoute = + RelativeUseNavigatePathPathIndexRouteImport.update({ + id: '/path/$path/', + path: '/path/$path/', + getParentRoute: () => RelativeUseNavigateRouteRoute, + } as any) const NonNestedDeepBazBarFooIndexRoute = NonNestedDeepBazBarFooIndexRouteImport.update({ id: '/', @@ -1343,39 +1343,39 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/notRemountDeps': { - id: '/notRemountDeps' - path: '/notRemountDeps' - fullPath: '/notRemountDeps' - preLoaderRoute: typeof NotRemountDepsRouteImport + '/anchor': { + id: '/anchor' + path: '/anchor' + fullPath: '/anchor' + preLoaderRoute: typeof AnchorRouteImport parentRoute: typeof rootRouteImport } - '/masks': { - id: '/masks' - path: '/masks' - fullPath: '/masks' - preLoaderRoute: typeof MasksRouteImport + '/component-types-test': { + id: '/component-types-test' + path: '/component-types-test' + fullPath: '/component-types-test' + preLoaderRoute: typeof ComponentTypesTestRouteImport parentRoute: typeof rootRouteImport } - '/hover-preload-hash': { - id: '/hover-preload-hash' - path: '/hover-preload-hash' - fullPath: '/hover-preload-hash' - preLoaderRoute: typeof HoverPreloadHashRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport parentRoute: typeof rootRouteImport } '/editing-b': { @@ -1385,39 +1385,46 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/hover-preload-hash': { + id: '/hover-preload-hash' + path: '/hover-preload-hash' + fullPath: '/hover-preload-hash' + preLoaderRoute: typeof HoverPreloadHashRouteImport parentRoute: typeof rootRouteImport } - '/component-types-test': { - id: '/component-types-test' - path: '/component-types-test' - fullPath: '/component-types-test' - preLoaderRoute: typeof ComponentTypesTestRouteImport + '/masks': { + id: '/masks' + path: '/masks' + fullPath: '/masks' + preLoaderRoute: typeof MasksRouteImport parentRoute: typeof rootRouteImport } - '/anchor': { - id: '/anchor' - path: '/anchor' - fullPath: '/anchor' - preLoaderRoute: typeof AnchorRouteImport + '/non-nested': { + id: '/non-nested' + path: '/non-nested' + fullPath: '/non-nested' + preLoaderRoute: typeof NonNestedRouteRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/notRemountDeps': { + id: '/notRemountDeps' + path: '/notRemountDeps' + fullPath: '/notRemountDeps' + preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } '/search-params': { @@ -1427,47 +1434,82 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/non-nested': { - id: '/non-nested' - path: '/non-nested' - fullPath: '/non-nested' - preLoaderRoute: typeof NonNestedRouteRouteImport + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport parentRoute: typeof rootRouteImport } - '/search-params/': { - id: '/search-params/' - path: '/' - fullPath: '/search-params/' - preLoaderRoute: typeof SearchParamsIndexRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/(group)/_layout': { + id: '/(group)/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport + parentRoute: typeof rootRouteImport } - '/relative/': { - id: '/relative/' - path: '/relative' - fullPath: '/relative/' - preLoaderRoute: typeof RelativeIndexRouteImport + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport parentRoute: typeof rootRouteImport } - '/redirect/': { - id: '/redirect/' - path: '/redirect' - fullPath: '/redirect/' - preLoaderRoute: typeof RedirectIndexRouteImport + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } + '/non-nested/deep': { + id: '/non-nested/deep' + path: '/deep' + fullPath: '/non-nested/deep' + preLoaderRoute: typeof NonNestedDeepRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/named': { + id: '/non-nested/named' + path: '/named' + fullPath: '/non-nested/named' + preLoaderRoute: typeof NonNestedNamedRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/path': { + id: '/non-nested/path' + path: '/path' + fullPath: '/non-nested/path' + preLoaderRoute: typeof NonNestedPathRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/prefix': { + id: '/non-nested/prefix' + path: '/prefix' + fullPath: '/non-nested/prefix' + preLoaderRoute: typeof NonNestedPrefixRouteRouteImport + parentRoute: typeof NonNestedRouteRoute + } + '/non-nested/suffix': { + id: '/non-nested/suffix' + path: '/suffix' + fullPath: '/non-nested/suffix' + preLoaderRoute: typeof NonNestedSuffixRouteRouteImport + parentRoute: typeof NonNestedRouteRoute } '/params-ps/': { id: '/params-ps/' @@ -1476,20 +1518,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsPsIndexRouteImport parentRoute: typeof rootRouteImport } - '/search-params/default': { - id: '/search-params/default' - path: '/default' - fullPath: '/search-params/default' - preLoaderRoute: typeof SearchParamsDefaultRouteImport - parentRoute: typeof SearchParamsRouteRoute - } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport + '/params-ps/non-nested': { + id: '/params-ps/non-nested' + path: '/params-ps/non-nested' + fullPath: '/params-ps/non-nested' + preLoaderRoute: typeof ParamsPsNonNestedRouteRouteImport parentRoute: typeof rootRouteImport } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -1497,39 +1539,32 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport + '/relative/': { + id: '/relative/' + path: '/relative' + fullPath: '/relative/' + preLoaderRoute: typeof RelativeIndexRouteImport parentRoute: typeof rootRouteImport } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/relative/link': { + id: '/relative/link' + path: '/relative/link' + fullPath: '/relative/link' + preLoaderRoute: typeof RelativeLinkRouteRouteImport parentRoute: typeof rootRouteImport } '/relative/useNavigate': { @@ -1539,193 +1574,151 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof RelativeUseNavigateRouteRouteImport parentRoute: typeof rootRouteImport } - '/relative/link': { - id: '/relative/link' - path: '/relative/link' - fullPath: '/relative/link' - preLoaderRoute: typeof RelativeLinkRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/params-ps/non-nested': { - id: '/params-ps/non-nested' - path: '/params-ps/non-nested' - fullPath: '/params-ps/non-nested' - preLoaderRoute: typeof ParamsPsNonNestedRouteRouteImport - parentRoute: typeof rootRouteImport + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/non-nested/suffix': { - id: '/non-nested/suffix' - path: '/suffix' - fullPath: '/non-nested/suffix' - preLoaderRoute: typeof NonNestedSuffixRouteRouteImport - parentRoute: typeof NonNestedRouteRoute - } - '/non-nested/prefix': { - id: '/non-nested/prefix' - path: '/prefix' - fullPath: '/non-nested/prefix' - preLoaderRoute: typeof NonNestedPrefixRouteRouteImport - parentRoute: typeof NonNestedRouteRoute - } - '/non-nested/path': { - id: '/non-nested/path' - path: '/path' - fullPath: '/non-nested/path' - preLoaderRoute: typeof NonNestedPathRouteRouteImport - parentRoute: typeof NonNestedRouteRoute - } - '/non-nested/named': { - id: '/non-nested/named' - path: '/named' - fullPath: '/non-nested/named' - preLoaderRoute: typeof NonNestedNamedRouteRouteImport - parentRoute: typeof NonNestedRouteRoute - } - '/non-nested/deep': { - id: '/non-nested/deep' - path: '/deep' - fullPath: '/non-nested/deep' - preLoaderRoute: typeof NonNestedDeepRouteRouteImport - parentRoute: typeof NonNestedRouteRoute + '/search-params/default': { + id: '/search-params/default' + path: '/default' + fullPath: '/search-params/default' + preLoaderRoute: typeof SearchParamsDefaultRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/redirect/$target/': { - id: '/redirect/$target/' - path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport - parentRoute: typeof RedirectTargetRoute + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/params-ps/wildcard/': { - id: '/params-ps/wildcard/' - path: '/params-ps/wildcard' - fullPath: '/params-ps/wildcard/' - preLoaderRoute: typeof ParamsPsWildcardIndexRouteImport + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/named/': { - id: '/params-ps/named/' - path: '/params-ps/named' - fullPath: '/params-ps/named/' - preLoaderRoute: typeof ParamsPsNamedIndexRouteImport - parentRoute: typeof rootRouteImport + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/대한민국/🚀/$id': { - id: '/대한민국/🚀/$id' - path: '/🚀/$id' - fullPath: '/대한민국/🚀/$id' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport - parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute + '/_layout/_layout-2/layout-b': { + id: '/_layout/_layout-2/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport + parentRoute: typeof LayoutLayout2Route } - '/대한민국/wildcard/$': { - id: '/대한민국/wildcard/$' - path: '/wildcard/$' - fullPath: '/대한민국/wildcard/$' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397WildcardSplatRouteImport - parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute + '/masks/admin/$userId': { + id: '/masks/admin/$userId' + path: '/admin/$userId' + fullPath: '/masks/admin/$userId' + preLoaderRoute: typeof MasksAdminUserIdRouteImport + parentRoute: typeof MasksRoute } - '/transition/typing/create-resource': { - id: '/transition/typing/create-resource' - path: '/transition/typing/create-resource' - fullPath: '/transition/typing/create-resource' - preLoaderRoute: typeof TransitionTypingCreateResourceRouteImport - parentRoute: typeof rootRouteImport + '/masks/public/$username': { + id: '/masks/public/$username' + path: '/public/$username' + fullPath: '/masks/public/$username' + preLoaderRoute: typeof MasksPublicUsernameRouteImport + parentRoute: typeof MasksRoute } - '/transition/count/create-resource': { - id: '/transition/count/create-resource' - path: '/transition/count/create-resource' - fullPath: '/transition/count/create-resource' - preLoaderRoute: typeof TransitionCountCreateResourceRouteImport - parentRoute: typeof rootRouteImport + '/non-nested/deep/$baz': { + id: '/non-nested/deep/$baz' + path: '/$baz' + fullPath: '/non-nested/deep/$baz' + preLoaderRoute: typeof NonNestedDeepBazRouteRouteImport + parentRoute: typeof NonNestedDeepRouteRoute } - '/relative/useNavigate/relative-useNavigate-b': { - id: '/relative/useNavigate/relative-useNavigate-b' - path: '/relative-useNavigate-b' - fullPath: '/relative/useNavigate/relative-useNavigate-b' - preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateBRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/non-nested/named/$baz': { + id: '/non-nested/named/$baz' + path: '/$baz' + fullPath: '/non-nested/named/$baz' + preLoaderRoute: typeof NonNestedNamedBazRouteRouteImport + parentRoute: typeof NonNestedNamedRouteRoute } - '/relative/useNavigate/relative-useNavigate-a': { - id: '/relative/useNavigate/relative-useNavigate-a' - path: '/relative-useNavigate-a' - fullPath: '/relative/useNavigate/relative-useNavigate-a' - preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateARouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/non-nested/path/baz': { + id: '/non-nested/path/baz' + path: '/baz' + fullPath: '/non-nested/path/baz' + preLoaderRoute: typeof NonNestedPathBazRouteRouteImport + parentRoute: typeof NonNestedPathRouteRoute } - '/relative/link/relative-link-b': { - id: '/relative/link/relative-link-b' - path: '/relative-link-b' - fullPath: '/relative/link/relative-link-b' - preLoaderRoute: typeof RelativeLinkRelativeLinkBRouteImport - parentRoute: typeof RelativeLinkRouteRoute + '/non-nested/prefix/prefix{$baz}': { + id: '/non-nested/prefix/prefix{$baz}' + path: '/prefix{$baz}' + fullPath: '/non-nested/prefix/prefix{$baz}' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRouteImport + parentRoute: typeof NonNestedPrefixRouteRoute } - '/relative/link/relative-link-a': { - id: '/relative/link/relative-link-a' - path: '/relative-link-a' - fullPath: '/relative/link/relative-link-a' - preLoaderRoute: typeof RelativeLinkRelativeLinkARouteImport - parentRoute: typeof RelativeLinkRouteRoute + '/non-nested/suffix/{$baz}suffix': { + id: '/non-nested/suffix/{$baz}suffix' + path: '/{$baz}suffix' + fullPath: '/non-nested/suffix/{$baz}suffix' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRouteImport + parentRoute: typeof NonNestedSuffixRouteRoute } - '/redirect/preload/third': { - id: '/redirect/preload/third' - path: '/redirect/preload/third' - fullPath: '/redirect/preload/third' - preLoaderRoute: typeof RedirectPreloadThirdRouteImport + '/params-ps/named/': { + id: '/params-ps/named/' + path: '/params-ps/named' + fullPath: '/params-ps/named/' + preLoaderRoute: typeof ParamsPsNamedIndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect/preload/second': { - id: '/redirect/preload/second' - path: '/redirect/preload/second' - fullPath: '/redirect/preload/second' - preLoaderRoute: typeof RedirectPreloadSecondRouteImport + '/params-ps/named/$foo': { + id: '/params-ps/named/$foo' + path: '/params-ps/named/$foo' + fullPath: '/params-ps/named/$foo' + preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport parentRoute: typeof rootRouteImport } - '/redirect/preload/first': { - id: '/redirect/preload/first' - path: '/redirect/preload/first' - fullPath: '/redirect/preload/first' - preLoaderRoute: typeof RedirectPreloadFirstRouteImport + '/params-ps/named/prefix{$foo}': { + id: '/params-ps/named/prefix{$foo}' + path: '/params-ps/named/prefix{$foo}' + fullPath: '/params-ps/named/prefix{$foo}' + preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport parentRoute: typeof rootRouteImport } - '/redirect/$target/via-loader': { - id: '/redirect/$target/via-loader' - path: '/via-loader' - fullPath: '/redirect/$target/via-loader' - preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport - parentRoute: typeof RedirectTargetRoute - } - '/redirect/$target/via-beforeLoad': { - id: '/redirect/$target/via-beforeLoad' - path: '/via-beforeLoad' - fullPath: '/redirect/$target/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute - } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport + '/params-ps/named/{$foo}suffix': { + id: '/params-ps/named/{$foo}suffix' + path: '/params-ps/named/{$foo}suffix' + fullPath: '/params-ps/named/{$foo}suffix' + preLoaderRoute: typeof ParamsPsNamedChar123fooChar125suffixRouteImport parentRoute: typeof rootRouteImport } - '/params/single/$value': { - id: '/params/single/$value' - path: '/params/single/$value' - fullPath: '/params/single/$value' - preLoaderRoute: typeof ParamsSingleValueRouteImport + '/params-ps/non-nested/$foo_': { + id: '/params-ps/non-nested/$foo_' + path: '/$foo' + fullPath: '/params-ps/non-nested/$foo' + preLoaderRoute: typeof ParamsPsNonNestedFooRouteRouteImport + parentRoute: typeof ParamsPsNonNestedRouteRoute + } + '/params-ps/wildcard/': { + id: '/params-ps/wildcard/' + path: '/params-ps/wildcard' + fullPath: '/params-ps/wildcard/' + preLoaderRoute: typeof ParamsPsWildcardIndexRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/wildcard/{$}suffix@대': { - id: '/params-ps/wildcard/{$}suffix@대' - path: '/params-ps/wildcard/{$}suffix@대' - fullPath: '/params-ps/wildcard/{$}suffix@대' - preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport + '/params-ps/wildcard/$': { + id: '/params-ps/wildcard/$' + path: '/params-ps/wildcard/$' + fullPath: '/params-ps/wildcard/$' + preLoaderRoute: typeof ParamsPsWildcardSplatRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/wildcard/{$}suffix': { - id: '/params-ps/wildcard/{$}suffix' - path: '/params-ps/wildcard/{$}suffix' - fullPath: '/params-ps/wildcard/{$}suffix' - preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixRouteImport + '/params-ps/wildcard/prefix@대{$}': { + id: '/params-ps/wildcard/prefix@대{$}' + path: '/params-ps/wildcard/prefix@대{$}' + fullPath: '/params-ps/wildcard/prefix@대{$}' + preLoaderRoute: typeof ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport parentRoute: typeof rootRouteImport } '/params-ps/wildcard/prefix{$}': { @@ -1735,194 +1728,131 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsPsWildcardPrefixChar123Char125RouteImport parentRoute: typeof rootRouteImport } - '/params-ps/wildcard/prefix@대{$}': { - id: '/params-ps/wildcard/prefix@대{$}' - path: '/params-ps/wildcard/prefix@대{$}' - fullPath: '/params-ps/wildcard/prefix@대{$}' - preLoaderRoute: typeof ParamsPsWildcardPrefixAtChar45824Char123Char125RouteImport + '/params-ps/wildcard/{$}suffix': { + id: '/params-ps/wildcard/{$}suffix' + path: '/params-ps/wildcard/{$}suffix' + fullPath: '/params-ps/wildcard/{$}suffix' + preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/wildcard/$': { - id: '/params-ps/wildcard/$' - path: '/params-ps/wildcard/$' - fullPath: '/params-ps/wildcard/$' - preLoaderRoute: typeof ParamsPsWildcardSplatRouteImport + '/params-ps/wildcard/{$}suffix@대': { + id: '/params-ps/wildcard/{$}suffix@대' + path: '/params-ps/wildcard/{$}suffix@대' + fullPath: '/params-ps/wildcard/{$}suffix@대' + preLoaderRoute: typeof ParamsPsWildcardChar123Char125suffixAtChar45824RouteImport parentRoute: typeof rootRouteImport } - '/params-ps/named/{$foo}suffix': { - id: '/params-ps/named/{$foo}suffix' - path: '/params-ps/named/{$foo}suffix' - fullPath: '/params-ps/named/{$foo}suffix' - preLoaderRoute: typeof ParamsPsNamedChar123fooChar125suffixRouteImport + '/params/single/$value': { + id: '/params/single/$value' + path: '/params/single/$value' + fullPath: '/params/single/$value' + preLoaderRoute: typeof ParamsSingleValueRouteImport parentRoute: typeof rootRouteImport } - '/params-ps/named/prefix{$foo}': { - id: '/params-ps/named/prefix{$foo}' - path: '/params-ps/named/prefix{$foo}' - fullPath: '/params-ps/named/prefix{$foo}' - preLoaderRoute: typeof ParamsPsNamedPrefixChar123fooChar125RouteImport + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport parentRoute: typeof rootRouteImport } - '/masks/public/$username': { - id: '/masks/public/$username' - path: '/public/$username' - fullPath: '/masks/public/$username' - preLoaderRoute: typeof MasksPublicUsernameRouteImport - parentRoute: typeof MasksRoute - } - '/masks/admin/$userId': { - id: '/masks/admin/$userId' - path: '/admin/$userId' - fullPath: '/masks/admin/$userId' - preLoaderRoute: typeof MasksAdminUserIdRouteImport - parentRoute: typeof MasksRoute + '/redirect/$target/': { + id: '/redirect/$target/' + path: '/' + fullPath: '/redirect/$target/' + preLoaderRoute: typeof RedirectTargetIndexRouteImport + 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/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/_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/via-loader': { + id: '/redirect/$target/via-loader' + path: '/via-loader' + fullPath: '/redirect/$target/via-loader' + preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport + parentRoute: typeof RedirectTargetRoute } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/redirect/preload/first': { + id: '/redirect/preload/first' + path: '/redirect/preload/first' + fullPath: '/redirect/preload/first' + preLoaderRoute: typeof RedirectPreloadFirstRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } - '/params-ps/non-nested/$foo_': { - id: '/params-ps/non-nested/$foo_' - path: '/$foo' - fullPath: '/params-ps/non-nested/$foo' - preLoaderRoute: typeof ParamsPsNonNestedFooRouteRouteImport - parentRoute: typeof ParamsPsNonNestedRouteRoute - } - '/params-ps/named/$foo': { - id: '/params-ps/named/$foo' - path: '/params-ps/named/$foo' - fullPath: '/params-ps/named/$foo' - preLoaderRoute: typeof ParamsPsNamedFooRouteRouteImport + '/redirect/preload/second': { + id: '/redirect/preload/second' + path: '/redirect/preload/second' + fullPath: '/redirect/preload/second' + preLoaderRoute: typeof RedirectPreloadSecondRouteImport parentRoute: typeof rootRouteImport } - '/non-nested/suffix/{$baz}suffix': { - id: '/non-nested/suffix/{$baz}suffix' - path: '/{$baz}suffix' - fullPath: '/non-nested/suffix/{$baz}suffix' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRouteImport - parentRoute: typeof NonNestedSuffixRouteRoute - } - '/non-nested/prefix/prefix{$baz}': { - id: '/non-nested/prefix/prefix{$baz}' - path: '/prefix{$baz}' - fullPath: '/non-nested/prefix/prefix{$baz}' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRouteImport - parentRoute: typeof NonNestedPrefixRouteRoute - } - '/non-nested/path/baz': { - id: '/non-nested/path/baz' - path: '/baz' - fullPath: '/non-nested/path/baz' - preLoaderRoute: typeof NonNestedPathBazRouteRouteImport - parentRoute: typeof NonNestedPathRouteRoute - } - '/non-nested/named/$baz': { - id: '/non-nested/named/$baz' - path: '/$baz' - fullPath: '/non-nested/named/$baz' - preLoaderRoute: typeof NonNestedNamedBazRouteRouteImport - parentRoute: typeof NonNestedNamedRouteRoute + '/redirect/preload/third': { + id: '/redirect/preload/third' + path: '/redirect/preload/third' + fullPath: '/redirect/preload/third' + preLoaderRoute: typeof RedirectPreloadThirdRouteImport + parentRoute: typeof rootRouteImport } - '/non-nested/deep/$baz': { - id: '/non-nested/deep/$baz' - path: '/$baz' - fullPath: '/non-nested/deep/$baz' - preLoaderRoute: typeof NonNestedDeepBazRouteRouteImport - parentRoute: typeof NonNestedDeepRouteRoute + '/relative/link/relative-link-a': { + id: '/relative/link/relative-link-a' + path: '/relative-link-a' + fullPath: '/relative/link/relative-link-a' + preLoaderRoute: typeof RelativeLinkRelativeLinkARouteImport + parentRoute: typeof RelativeLinkRouteRoute } - '/relative/useNavigate/with-search/': { - id: '/relative/useNavigate/with-search/' - path: '/with-search' - fullPath: '/relative/useNavigate/with-search/' - preLoaderRoute: typeof RelativeUseNavigateWithSearchIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/relative/link/relative-link-b': { + id: '/relative/link/relative-link-b' + path: '/relative-link-b' + fullPath: '/relative/link/relative-link-b' + preLoaderRoute: typeof RelativeLinkRelativeLinkBRouteImport + parentRoute: typeof RelativeLinkRouteRoute } - '/relative/useNavigate/path/': { - id: '/relative/useNavigate/path/' - path: '/path' - fullPath: '/relative/useNavigate/path/' - preLoaderRoute: typeof RelativeUseNavigatePathIndexRouteImport + '/relative/useNavigate/relative-useNavigate-a': { + id: '/relative/useNavigate/relative-useNavigate-a' + path: '/relative-useNavigate-a' + fullPath: '/relative/useNavigate/relative-useNavigate-a' + preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateARouteImport parentRoute: typeof RelativeUseNavigateRouteRoute } - '/relative/useNavigate/nested/': { - id: '/relative/useNavigate/nested/' - path: '/nested' - fullPath: '/relative/useNavigate/nested/' - preLoaderRoute: typeof RelativeUseNavigateNestedIndexRouteImport + '/relative/useNavigate/relative-useNavigate-b': { + id: '/relative/useNavigate/relative-useNavigate-b' + path: '/relative-useNavigate-b' + fullPath: '/relative/useNavigate/relative-useNavigate-b' + preLoaderRoute: typeof RelativeUseNavigateRelativeUseNavigateBRouteImport parentRoute: typeof RelativeUseNavigateRouteRoute } - '/relative/link/with-search/': { - id: '/relative/link/with-search/' - path: '/with-search' - fullPath: '/relative/link/with-search/' - preLoaderRoute: typeof RelativeLinkWithSearchIndexRouteImport - parentRoute: typeof RelativeLinkRouteRoute - } - '/relative/link/path/': { - id: '/relative/link/path/' - path: '/path' - fullPath: '/relative/link/path/' - preLoaderRoute: typeof RelativeLinkPathIndexRouteImport - parentRoute: typeof RelativeLinkRouteRoute - } - '/relative/link/nested/': { - id: '/relative/link/nested/' - path: '/nested' - fullPath: '/relative/link/nested/' - preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport - parentRoute: typeof RelativeLinkRouteRoute - } - '/non-nested/suffix/{$baz}suffix/': { - id: '/non-nested/suffix/{$baz}suffix/' - path: '/' - fullPath: '/non-nested/suffix/{$baz}suffix/' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixIndexRouteImport - parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + '/transition/count/create-resource': { + id: '/transition/count/create-resource' + path: '/transition/count/create-resource' + fullPath: '/transition/count/create-resource' + preLoaderRoute: typeof TransitionCountCreateResourceRouteImport + parentRoute: typeof rootRouteImport } - '/non-nested/prefix/prefix{$baz}/': { - id: '/non-nested/prefix/prefix{$baz}/' - path: '/' - fullPath: '/non-nested/prefix/prefix{$baz}/' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125IndexRouteImport - parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + '/transition/typing/create-resource': { + id: '/transition/typing/create-resource' + path: '/transition/typing/create-resource' + fullPath: '/transition/typing/create-resource' + preLoaderRoute: typeof TransitionTypingCreateResourceRouteImport + parentRoute: typeof rootRouteImport } - '/non-nested/path/baz/': { - id: '/non-nested/path/baz/' - path: '/' - fullPath: '/non-nested/path/baz/' - preLoaderRoute: typeof NonNestedPathBazIndexRouteImport - parentRoute: typeof NonNestedPathBazRouteRoute + '/대한민국/wildcard/$': { + id: '/대한민국/wildcard/$' + path: '/wildcard/$' + fullPath: '/대한민국/wildcard/$' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397WildcardSplatRouteImport + parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute } - '/non-nested/named/$baz/': { - id: '/non-nested/named/$baz/' - path: '/' - fullPath: '/non-nested/named/$baz/' - preLoaderRoute: typeof NonNestedNamedBazIndexRouteImport - parentRoute: typeof NonNestedNamedBazRouteRoute + '/대한민국/🚀/$id': { + id: '/대한민국/🚀/$id' + path: '/🚀/$id' + fullPath: '/대한민국/🚀/$id' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397Char55357Char56960IdRouteImport + parentRoute: typeof Char45824Char54620Char48124Char44397RouteRoute } '/non-nested/deep/$baz/': { id: '/non-nested/deep/$baz/' @@ -1931,40 +1861,47 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NonNestedDeepBazIndexRouteImport parentRoute: typeof NonNestedDeepBazRouteRoute } - '/params-ps/non-nested/$foo_/$bar': { - id: '/params-ps/non-nested/$foo_/$bar' - path: '/$bar' - fullPath: '/params-ps/non-nested/$foo/$bar' - preLoaderRoute: typeof ParamsPsNonNestedFooBarRouteImport - parentRoute: typeof ParamsPsNonNestedFooRouteRoute + '/non-nested/deep/$baz_/bar': { + id: '/non-nested/deep/$baz_/bar' + path: '/$baz/bar' + fullPath: '/non-nested/deep/$baz/bar' + preLoaderRoute: typeof NonNestedDeepBazBarRouteRouteImport + parentRoute: typeof NonNestedDeepRouteRoute } - '/non-nested/suffix/{$baz}suffix_/bar': { - id: '/non-nested/suffix/{$baz}suffix_/bar' - path: '/{$baz}suffix/bar' - fullPath: '/non-nested/suffix/{$baz}suffix/bar' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixBarRouteImport - parentRoute: typeof NonNestedSuffixRouteRoute + '/non-nested/named/$baz/': { + id: '/non-nested/named/$baz/' + path: '/' + fullPath: '/non-nested/named/$baz/' + preLoaderRoute: typeof NonNestedNamedBazIndexRouteImport + parentRoute: typeof NonNestedNamedBazRouteRoute } - '/non-nested/suffix/{$baz}suffix/foo': { - id: '/non-nested/suffix/{$baz}suffix/foo' + '/non-nested/named/$baz/foo': { + id: '/non-nested/named/$baz/foo' path: '/foo' - fullPath: '/non-nested/suffix/{$baz}suffix/foo' - preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixFooRouteImport - parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + fullPath: '/non-nested/named/$baz/foo' + preLoaderRoute: typeof NonNestedNamedBazFooRouteImport + parentRoute: typeof NonNestedNamedBazRouteRoute } - '/non-nested/prefix/prefix{$baz}_/bar': { - id: '/non-nested/prefix/prefix{$baz}_/bar' - path: '/prefix{$baz}/bar' - fullPath: '/non-nested/prefix/prefix{$baz}/bar' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125BarRouteImport - parentRoute: typeof NonNestedPrefixRouteRoute + '/non-nested/named/$baz_/bar': { + id: '/non-nested/named/$baz_/bar' + path: '/$baz/bar' + fullPath: '/non-nested/named/$baz/bar' + preLoaderRoute: typeof NonNestedNamedBazBarRouteImport + parentRoute: typeof NonNestedNamedRouteRoute } - '/non-nested/prefix/prefix{$baz}/foo': { - id: '/non-nested/prefix/prefix{$baz}/foo' + '/non-nested/path/baz/': { + id: '/non-nested/path/baz/' + path: '/' + fullPath: '/non-nested/path/baz/' + preLoaderRoute: typeof NonNestedPathBazIndexRouteImport + parentRoute: typeof NonNestedPathBazRouteRoute + } + '/non-nested/path/baz/foo': { + id: '/non-nested/path/baz/foo' path: '/foo' - fullPath: '/non-nested/prefix/prefix{$baz}/foo' - preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125FooRouteImport - parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + fullPath: '/non-nested/path/baz/foo' + preLoaderRoute: typeof NonNestedPathBazFooRouteImport + parentRoute: typeof NonNestedPathBazRouteRoute } '/non-nested/path/baz_/bar': { id: '/non-nested/path/baz_/bar' @@ -1973,26 +1910,47 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NonNestedPathBazBarRouteImport parentRoute: typeof NonNestedPathRouteRoute } - '/non-nested/path/baz/foo': { - id: '/non-nested/path/baz/foo' + '/non-nested/prefix/prefix{$baz}/': { + id: '/non-nested/prefix/prefix{$baz}/' + path: '/' + fullPath: '/non-nested/prefix/prefix{$baz}/' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125IndexRouteImport + parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute + } + '/non-nested/prefix/prefix{$baz}/foo': { + id: '/non-nested/prefix/prefix{$baz}/foo' path: '/foo' - fullPath: '/non-nested/path/baz/foo' - preLoaderRoute: typeof NonNestedPathBazFooRouteImport - parentRoute: typeof NonNestedPathBazRouteRoute + fullPath: '/non-nested/prefix/prefix{$baz}/foo' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125FooRouteImport + parentRoute: typeof NonNestedPrefixPrefixChar123bazChar125RouteRoute } - '/non-nested/named/$baz_/bar': { - id: '/non-nested/named/$baz_/bar' - path: '/$baz/bar' - fullPath: '/non-nested/named/$baz/bar' - preLoaderRoute: typeof NonNestedNamedBazBarRouteImport - parentRoute: typeof NonNestedNamedRouteRoute + '/non-nested/prefix/prefix{$baz}_/bar': { + id: '/non-nested/prefix/prefix{$baz}_/bar' + path: '/prefix{$baz}/bar' + fullPath: '/non-nested/prefix/prefix{$baz}/bar' + preLoaderRoute: typeof NonNestedPrefixPrefixChar123bazChar125BarRouteImport + parentRoute: typeof NonNestedPrefixRouteRoute } - '/non-nested/named/$baz/foo': { - id: '/non-nested/named/$baz/foo' + '/non-nested/suffix/{$baz}suffix/': { + id: '/non-nested/suffix/{$baz}suffix/' + path: '/' + fullPath: '/non-nested/suffix/{$baz}suffix/' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixIndexRouteImport + parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + } + '/non-nested/suffix/{$baz}suffix/foo': { + id: '/non-nested/suffix/{$baz}suffix/foo' path: '/foo' - fullPath: '/non-nested/named/$baz/foo' - preLoaderRoute: typeof NonNestedNamedBazFooRouteImport - parentRoute: typeof NonNestedNamedBazRouteRoute + fullPath: '/non-nested/suffix/{$baz}suffix/foo' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixFooRouteImport + parentRoute: typeof NonNestedSuffixChar123bazChar125suffixRouteRoute + } + '/non-nested/suffix/{$baz}suffix_/bar': { + id: '/non-nested/suffix/{$baz}suffix_/bar' + path: '/{$baz}suffix/bar' + fullPath: '/non-nested/suffix/{$baz}suffix/bar' + preLoaderRoute: typeof NonNestedSuffixChar123bazChar125suffixBarRouteImport + parentRoute: typeof NonNestedSuffixRouteRoute } '/params-ps/named/$foo/$bar': { id: '/params-ps/named/$foo/$bar' @@ -2001,41 +1959,55 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsPsNamedFooBarRouteRouteImport parentRoute: typeof ParamsPsNamedFooRouteRoute } - '/non-nested/deep/$baz_/bar': { - id: '/non-nested/deep/$baz_/bar' - path: '/$baz/bar' - fullPath: '/non-nested/deep/$baz/bar' - preLoaderRoute: typeof NonNestedDeepBazBarRouteRouteImport - parentRoute: typeof NonNestedDeepRouteRoute - } - '/relative/useNavigate/path/$path/': { - id: '/relative/useNavigate/path/$path/' - path: '/path/$path' - fullPath: '/relative/useNavigate/path/$path/' - preLoaderRoute: typeof RelativeUseNavigatePathPathIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/params-ps/non-nested/$foo_/$bar': { + id: '/params-ps/non-nested/$foo_/$bar' + path: '/$bar' + fullPath: '/params-ps/non-nested/$foo/$bar' + preLoaderRoute: typeof ParamsPsNonNestedFooBarRouteImport + parentRoute: typeof ParamsPsNonNestedFooRouteRoute } - '/relative/useNavigate/nested/deep/': { - id: '/relative/useNavigate/nested/deep/' - path: '/nested/deep' - fullPath: '/relative/useNavigate/nested/deep/' - preLoaderRoute: typeof RelativeUseNavigateNestedDeepIndexRouteImport - parentRoute: typeof RelativeUseNavigateRouteRoute + '/relative/link/nested/': { + id: '/relative/link/nested/' + path: '/nested' + fullPath: '/relative/link/nested/' + preLoaderRoute: typeof RelativeLinkNestedIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute } - '/relative/link/path/$path/': { - id: '/relative/link/path/$path/' - path: '/path/$path' - fullPath: '/relative/link/path/$path/' - preLoaderRoute: typeof RelativeLinkPathPathIndexRouteImport + '/relative/link/path/': { + id: '/relative/link/path/' + path: '/path' + fullPath: '/relative/link/path/' + preLoaderRoute: typeof RelativeLinkPathIndexRouteImport parentRoute: typeof RelativeLinkRouteRoute } - '/relative/link/nested/deep/': { - id: '/relative/link/nested/deep/' - path: '/nested/deep' - fullPath: '/relative/link/nested/deep/' - preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport + '/relative/link/with-search/': { + id: '/relative/link/with-search/' + path: '/with-search' + fullPath: '/relative/link/with-search/' + preLoaderRoute: typeof RelativeLinkWithSearchIndexRouteImport parentRoute: typeof RelativeLinkRouteRoute } + '/relative/useNavigate/nested/': { + id: '/relative/useNavigate/nested/' + path: '/nested' + fullPath: '/relative/useNavigate/nested/' + preLoaderRoute: typeof RelativeUseNavigateNestedIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/path/': { + id: '/relative/useNavigate/path/' + path: '/path' + fullPath: '/relative/useNavigate/path/' + preLoaderRoute: typeof RelativeUseNavigatePathIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/with-search/': { + id: '/relative/useNavigate/with-search/' + path: '/with-search' + fullPath: '/relative/useNavigate/with-search/' + preLoaderRoute: typeof RelativeUseNavigateWithSearchIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } '/non-nested/deep/$baz_/bar/': { id: '/non-nested/deep/$baz_/bar/' path: '/' @@ -2043,12 +2015,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NonNestedDeepBazBarIndexRouteImport parentRoute: typeof NonNestedDeepBazBarRouteRoute } - '/params-ps/named/$foo/$bar/$baz': { - id: '/params-ps/named/$foo/$bar/$baz' - path: '/$baz' - fullPath: '/params-ps/named/$foo/$bar/$baz' - preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport - parentRoute: typeof ParamsPsNamedFooBarRouteRoute + '/non-nested/deep/$baz_/bar/$foo': { + id: '/non-nested/deep/$baz_/bar/$foo' + path: '/$foo' + fullPath: '/non-nested/deep/$baz/bar/$foo' + preLoaderRoute: typeof NonNestedDeepBazBarFooRouteRouteImport + parentRoute: typeof NonNestedDeepBazBarRouteRoute } '/non-nested/deep/$baz_/bar_/qux': { id: '/non-nested/deep/$baz_/bar_/qux' @@ -2057,12 +2029,40 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NonNestedDeepBazBarQuxRouteImport parentRoute: typeof NonNestedDeepRouteRoute } - '/non-nested/deep/$baz_/bar/$foo': { - id: '/non-nested/deep/$baz_/bar/$foo' - path: '/$foo' - fullPath: '/non-nested/deep/$baz/bar/$foo' - preLoaderRoute: typeof NonNestedDeepBazBarFooRouteRouteImport - parentRoute: typeof NonNestedDeepBazBarRouteRoute + '/params-ps/named/$foo/$bar/$baz': { + id: '/params-ps/named/$foo/$bar/$baz' + path: '/$baz' + fullPath: '/params-ps/named/$foo/$bar/$baz' + preLoaderRoute: typeof ParamsPsNamedFooBarBazRouteImport + parentRoute: typeof ParamsPsNamedFooBarRouteRoute + } + '/relative/link/nested/deep/': { + id: '/relative/link/nested/deep/' + path: '/nested/deep' + fullPath: '/relative/link/nested/deep/' + preLoaderRoute: typeof RelativeLinkNestedDeepIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute + } + '/relative/link/path/$path/': { + id: '/relative/link/path/$path/' + path: '/path/$path' + fullPath: '/relative/link/path/$path/' + preLoaderRoute: typeof RelativeLinkPathPathIndexRouteImport + parentRoute: typeof RelativeLinkRouteRoute + } + '/relative/useNavigate/nested/deep/': { + id: '/relative/useNavigate/nested/deep/' + path: '/nested/deep' + fullPath: '/relative/useNavigate/nested/deep/' + preLoaderRoute: typeof RelativeUseNavigateNestedDeepIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute + } + '/relative/useNavigate/path/$path/': { + id: '/relative/useNavigate/path/$path/' + path: '/path/$path' + fullPath: '/relative/useNavigate/path/$path/' + preLoaderRoute: typeof RelativeUseNavigatePathPathIndexRouteImport + parentRoute: typeof RelativeUseNavigateRouteRoute } '/non-nested/deep/$baz_/bar/$foo/': { id: '/non-nested/deep/$baz_/bar/$foo/' diff --git a/e2e/solid-router/basic-scroll-restoration/package.json b/e2e/solid-router/basic-scroll-restoration/package.json index 0d14fa8f90..0fe981ad02 100644 --- a/e2e/solid-router/basic-scroll-restoration/package.json +++ b/e2e/solid-router/basic-scroll-restoration/package.json @@ -21,7 +21,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", 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 0579260043..b24969f0be 100644 --- a/e2e/solid-router/basic-solid-query-file-based/package.json +++ b/e2e/solid-router/basic-solid-query-file-based/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/basic-solid-query-file-based/src/routeTree.gen.ts b/e2e/solid-router/basic-solid-query-file-based/src/routeTree.gen.ts index e955e85ecc..9cc1d71391 100644 --- a/e2e/solid-router/basic-solid-query-file-based/src/routeTree.gen.ts +++ b/e2e/solid-router/basic-solid-query-file-based/src/routeTree.gen.ts @@ -9,29 +9,33 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -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 LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,20 +46,16 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -114,11 +114,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -128,13 +128,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -149,12 +156,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/_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' @@ -163,13 +170,6 @@ 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-solid-query/package.json b/e2e/solid-router/basic-solid-query/package.json index d2a8fcfc6d..b397b6f105 100644 --- a/e2e/solid-router/basic-solid-query/package.json +++ b/e2e/solid-router/basic-solid-query/package.json @@ -22,7 +22,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/basic-virtual-file-based/package.json b/e2e/solid-router/basic-virtual-file-based/package.json index 4567f5e3e1..fbf681226d 100644 --- a/e2e/solid-router/basic-virtual-file-based/package.json +++ b/e2e/solid-router/basic-virtual-file-based/package.json @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" 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 1c9691ab23..0aa373b49f 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 @@ -9,76 +9,76 @@ // 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 postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' -import { Route as aRouteImport } from './routes/a' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, -} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, +} as any) +const aRoute = aRouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) +const bRoute = bRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, -} as any) -const aRoute = aRouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) export interface FileRoutesByFullPath { @@ -166,11 +166,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -180,20 +180,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute - } '/_first/_second': { id: '/_first/_second' path: '' @@ -201,6 +194,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -208,12 +208,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + 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/' @@ -222,13 +236,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -236,19 +243,12 @@ declare module '@tanstack/solid-router' { 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 + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } } } 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 872a5893db..3031536f77 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 @@ -23,7 +23,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/basic/package.json b/e2e/solid-router/basic/package.json index 2f3d2c0bfb..cf4e34dac8 100644 --- a/e2e/solid-router/basic/package.json +++ b/e2e/solid-router/basic/package.json @@ -20,7 +20,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/generator-cli-only/package.json b/e2e/solid-router/generator-cli-only/package.json index ee779d9e95..c7a716cde1 100644 --- a/e2e/solid-router/generator-cli-only/package.json +++ b/e2e/solid-router/generator-cli-only/package.json @@ -21,7 +21,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/generator-cli-only/src/routeTree.gen.ts b/e2e/solid-router/generator-cli-only/src/routeTree.gen.ts index eb6d9fe975..c0f9fbabe4 100644 --- a/e2e/solid-router/generator-cli-only/src/routeTree.gen.ts +++ b/e2e/solid-router/generator-cli-only/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,6 +117,13 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -131,12 +138,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -152,12 +159,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/e2e/solid-router/js-only-file-based/package.json b/e2e/solid-router/js-only-file-based/package.json index ce2fab45ce..79207f930a 100644 --- a/e2e/solid-router/js-only-file-based/package.json +++ b/e2e/solid-router/js-only-file-based/package.json @@ -20,7 +20,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@tanstack/router-plugin": "workspace:^", "vite": "^8.0.14", diff --git a/e2e/solid-router/js-only-file-based/src/routeTree.gen.js b/e2e/solid-router/js-only-file-based/src/routeTree.gen.js index f2d42e5a6c..80683abb30 100644 --- a/e2e/solid-router/js-only-file-based/src/routeTree.gen.js +++ b/e2e/solid-router/js-only-file-based/src/routeTree.gen.js @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +}) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, }) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -}) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + }) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, }) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, }) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, }) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - }) const PostsRouteRouteChildren = { PostsPostIdRoute: PostsPostIdRoute, diff --git a/e2e/solid-router/rspack-basic-file-based/package.json b/e2e/solid-router/rspack-basic-file-based/package.json index db497fd2fe..6c904b659a 100644 --- a/e2e/solid-router/rspack-basic-file-based/package.json +++ b/e2e/solid-router/rspack-basic-file-based/package.json @@ -17,8 +17,8 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-solid": "^2.0.0-beta.0", "@tailwindcss/postcss": "^4.2.2", @@ -26,6 +26,7 @@ "@tanstack/router-plugin": "workspace:^", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json index 48c8382161..152afa1680 100644 --- a/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json +++ b/e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json @@ -17,8 +17,8 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-solid": "^2.0.0-beta.0", "@tailwindcss/postcss": "^4.2.2", @@ -27,6 +27,7 @@ "@tanstack/virtual-file-routes": "workspace:^", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/e2e/solid-router/scroll-restoration-sandbox-vite/package.json b/e2e/solid-router/scroll-restoration-sandbox-vite/package.json index 4804ce3c83..72677e72fa 100644 --- a/e2e/solid-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/solid-router/scroll-restoration-sandbox-vite/package.json @@ -25,7 +25,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts b/e2e/solid-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts index 7108ee0743..bbd53dbfe6 100644 --- a/e2e/solid-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts +++ b/e2e/solid-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts @@ -12,10 +12,10 @@ import { createFileRoute } from '@tanstack/solid-router' import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' -import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' -import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' import { Route as testsLazyPageRouteImport } from './routes/(tests)/lazy-page' +import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' +import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' const testsVirtualPageLazyRouteImport = createFileRoute( '/(tests)/virtual-page', @@ -26,23 +26,13 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport +const testsLazyPageRoute = testsLazyPageRouteImport .update({ - id: '/(tests)/virtual-page', - path: '/virtual-page', + id: '/(tests)/lazy-page', + path: '/lazy-page', getParentRoute: () => rootRouteImport, } as any) - .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) -const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ - id: '/(tests)/page-with-search', - path: '/page-with-search', - getParentRoute: () => rootRouteImport, -} as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', - getParentRoute: () => rootRouteImport, -} as any) + .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .update({ id: '/(tests)/lazy-with-loader-page', @@ -52,13 +42,23 @@ const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .lazy(() => import('./routes/(tests)/lazy-with-loader-page.lazy').then((d) => d.Route), ) -const testsLazyPageRoute = testsLazyPageRouteImport +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', + getParentRoute: () => rootRouteImport, +} as any) +const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ + id: '/(tests)/page-with-search', + path: '/page-with-search', + getParentRoute: () => rootRouteImport, +} as any) +const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport .update({ - id: '/(tests)/lazy-page', - path: '/lazy-page', + id: '/(tests)/virtual-page', + path: '/virtual-page', getParentRoute: () => rootRouteImport, } as any) - .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) + .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -130,18 +130,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/virtual-page': { - id: '/(tests)/virtual-page' - path: '/virtual-page' - fullPath: '/virtual-page' - preLoaderRoute: typeof testsVirtualPageLazyRouteImport + '/(tests)/lazy-page': { + id: '/(tests)/lazy-page' + path: '/lazy-page' + fullPath: '/lazy-page' + preLoaderRoute: typeof testsLazyPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/page-with-search': { - id: '/(tests)/page-with-search' - path: '/page-with-search' - fullPath: '/page-with-search' - preLoaderRoute: typeof testsPageWithSearchRouteImport + '/(tests)/lazy-with-loader-page': { + id: '/(tests)/lazy-with-loader-page' + path: '/lazy-with-loader-page' + fullPath: '/lazy-with-loader-page' + preLoaderRoute: typeof testsLazyWithLoaderPageRouteImport parentRoute: typeof rootRouteImport } '/(tests)/normal-page': { @@ -151,18 +151,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/lazy-with-loader-page': { - id: '/(tests)/lazy-with-loader-page' - path: '/lazy-with-loader-page' - fullPath: '/lazy-with-loader-page' - preLoaderRoute: typeof testsLazyWithLoaderPageRouteImport + '/(tests)/page-with-search': { + id: '/(tests)/page-with-search' + path: '/page-with-search' + fullPath: '/page-with-search' + preLoaderRoute: typeof testsPageWithSearchRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/lazy-page': { - id: '/(tests)/lazy-page' - path: '/lazy-page' - fullPath: '/lazy-page' - preLoaderRoute: typeof testsLazyPageRouteImport + '/(tests)/virtual-page': { + id: '/(tests)/virtual-page' + path: '/virtual-page' + fullPath: '/virtual-page' + preLoaderRoute: typeof testsVirtualPageLazyRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/solid-router/sentry-integration/package.json b/e2e/solid-router/sentry-integration/package.json index 47996df44d..5b099ef3d3 100644 --- a/e2e/solid-router/sentry-integration/package.json +++ b/e2e/solid-router/sentry-integration/package.json @@ -23,7 +23,7 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/e2e/solid-router/view-transitions/package.json b/e2e/solid-router/view-transitions/package.json index ddc037513e..ccdf24437b 100644 --- a/e2e/solid-router/view-transitions/package.json +++ b/e2e/solid-router/view-transitions/package.json @@ -22,9 +22,10 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/e2e/solid-router/view-transitions/src/routeTree.gen.ts b/e2e/solid-router/view-transitions/src/routeTree.gen.ts index 114e34ed9d..01843027dc 100644 --- a/e2e/solid-router/view-transitions/src/routeTree.gen.ts +++ b/e2e/solid-router/view-transitions/src/routeTree.gen.ts @@ -9,16 +9,16 @@ // 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 HowItWorksRouteImport } from './routes/how-it-works' +import { Route as IndexRouteImport } from './routes/index' import { Route as ExploreRouteImport } from './routes/explore' +import { Route as HowItWorksRouteImport } from './routes/how-it-works' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const HowItWorksRoute = HowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ExploreRoute = ExploreRouteImport.update({ @@ -26,16 +26,16 @@ const ExploreRoute = ExploreRouteImport.update({ path: '/explore', getParentRoute: () => rootRouteImport, } as any) +const HowItWorksRoute = HowItWorksRouteImport.update({ + id: '/how-it-works', + path: '/how-it-works', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -101,11 +101,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/how-it-works': { - id: '/how-it-works' - path: '/how-it-works' - fullPath: '/how-it-works' - preLoaderRoute: typeof HowItWorksRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/explore': { @@ -115,6 +115,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ExploreRouteImport parentRoute: typeof rootRouteImport } + '/how-it-works': { + id: '/how-it-works' + path: '/how-it-works' + fullPath: '/how-it-works' + preLoaderRoute: typeof HowItWorksRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -122,13 +129,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/e2e/solid-start/basic-auth/package.json b/e2e/solid-start/basic-auth/package.json index e2f160fe79..389388d3a1 100644 --- a/e2e/solid-start/basic-auth/package.json +++ b/e2e/solid-start/basic-auth/package.json @@ -25,7 +25,7 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -33,8 +33,9 @@ "prisma": "^7.0.0", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/basic-auth/src/routeTree.gen.ts b/e2e/solid-start/basic-auth/src/routeTree.gen.ts index 7019a6c21a..bbebb19df3 100644 --- a/e2e/solid-start/basic-auth/src/routeTree.gen.ts +++ b/e2e/solid-start/basic-auth/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/e2e/solid-start/basic-cloudflare/package.json b/e2e/solid-start/basic-cloudflare/package.json index aed8f9d916..c8255e6e5f 100644 --- a/e2e/solid-start/basic-cloudflare/package.json +++ b/e2e/solid-start/basic-cloudflare/package.json @@ -21,12 +21,13 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.29.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "wrangler": "^4.74.0" diff --git a/e2e/solid-start/basic-cloudflare/src/routeTree.gen.ts b/e2e/solid-start/basic-cloudflare/src/routeTree.gen.ts index 2bd11546dd..4e698ade2c 100644 --- a/e2e/solid-start/basic-cloudflare/src/routeTree.gen.ts +++ b/e2e/solid-start/basic-cloudflare/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 StaticRouteImport } from './routes/static' import { Route as IndexRouteImport } from './routes/index' +import { Route as StaticRouteImport } from './routes/static' -const StaticRoute = StaticRouteImport.update({ - id: '/static', - path: '/static', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const StaticRoute = StaticRouteImport.update({ + id: '/static', + path: '/static', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/static': { - id: '/static' - path: '/static' - fullPath: '/static' - preLoaderRoute: typeof StaticRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/static': { + id: '/static' + path: '/static' + fullPath: '/static' + preLoaderRoute: typeof StaticRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/solid-start/basic-solid-query/package.json b/e2e/solid-start/basic-solid-query/package.json index 1c463f48d1..d8f4bf5004 100644 --- a/e2e/solid-start/basic-solid-query/package.json +++ b/e2e/solid-start/basic-solid-query/package.json @@ -25,14 +25,15 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts index ee02d13d55..a79d1a5880 100644 --- a/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts +++ b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts @@ -9,32 +9,36 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as DeferredRouteImport } from './routes/deferred' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api.users' 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 TransitionCountQueryRouteImport } from './routes/transition/count/query' -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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as TransitionCountQueryRouteImport } from './routes/transition/count/query' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ - id: '/suspense-transition', - path: '/suspense-transition', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -42,73 +46,69 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ + id: '/suspense-transition', + path: '/suspense-transition', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, } as any) -const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ - id: '/transition/count/query', - path: '/transition/count/query', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, } as any) const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ id: '/$id', path: '/$id', getParentRoute: () => ApiUsersRoute, } as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ + id: '/transition/count/query', + path: '/transition/count/query', + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { @@ -231,18 +231,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/suspense-transition': { - id: '/suspense-transition' - path: '/suspense-transition' - fullPath: '/suspense-transition' - preLoaderRoute: typeof SuspenseTransitionRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -252,33 +259,33 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/suspense-transition': { + id: '/suspense-transition' + path: '/suspense-transition' + fullPath: '/suspense-transition' + preLoaderRoute: typeof SuspenseTransitionRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -287,13 +294,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -301,33 +301,33 @@ 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 + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/transition/count/query': { - id: '/transition/count/query' - path: '/transition/count/query' - fullPath: '/transition/count/query' - preLoaderRoute: typeof TransitionCountQueryRouteImport - parentRoute: typeof rootRouteImport + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -336,19 +336,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ApiUsersIdRouteImport parentRoute: typeof ApiUsersRoute } - '/_layout/_layout-2/layout-b': { - id: '/_layout/_layout-2/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport - parentRoute: typeof LayoutLayout2Route + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/transition/count/query': { + id: '/transition/count/query' + path: '/transition/count/query' + fullPath: '/transition/count/query' + preLoaderRoute: typeof TransitionCountQueryRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/e2e/solid-start/basic-tsr-config/package.json b/e2e/solid-start/basic-tsr-config/package.json index c24e839270..c856491800 100644 --- a/e2e/solid-start/basic-tsr-config/package.json +++ b/e2e/solid-start/basic-tsr-config/package.json @@ -21,8 +21,9 @@ "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json index 3bcd62c708..1fe3a782b7 100644 --- a/e2e/solid-start/basic/package.json +++ b/e2e/solid-start/basic/package.json @@ -27,8 +27,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-solid": "^2.0.0-beta.0", "@tailwindcss/postcss": "^4.2.2", @@ -39,7 +39,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" }, diff --git a/e2e/solid-start/basic/src/routeTree.gen.ts b/e2e/solid-start/basic/src/routeTree.gen.ts index c9fba32156..dc8390f6b4 100644 --- a/e2e/solid-start/basic/src/routeTree.gen.ts +++ b/e2e/solid-start/basic/src/routeTree.gen.ts @@ -9,90 +9,89 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as StreamRouteImport } from './routes/stream' -import { Route as ScriptsRouteImport } from './routes/scripts' -import { Route as RawStreamRouteImport } from './routes/raw-stream' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as LinksRouteImport } from './routes/links' -import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' -import { Route as DeferredWithoutSuspenseRouteImport } from './routes/deferred-without-suspense' -import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' -import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as DeferredWithoutSuspenseRouteImport } from './routes/deferred-without-suspense' +import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' +import { Route as LinksRouteImport } from './routes/links' import { Route as NotFoundRouteRouteImport } from './routes/not-found/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' -import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RawStreamRouteImport } from './routes/raw-stream' +import { Route as ScriptsRouteImport } from './routes/scripts' +import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' +import { Route as StreamRouteImport } from './routes/stream' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' -import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' -import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' -import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' -import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' -import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' -import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' -import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' -import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' -import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' -import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' -import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' -import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' -import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' -import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' -import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target' -import { Route as ApiUsersRouteImport } from './routes/api/users' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' +import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' import { Route as NotFoundParentBoundaryRouteRouteImport } from './routes/not-found/parent-boundary/route' +import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' +import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' +import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' +import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' +import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' +import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' +import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' +import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' +import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' +import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' +import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' +import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' +import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' +import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' +import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' +import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' +import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' -import { Route as TransitionTypingCreateResourceRouteImport } from './routes/transition/typing/create-resource' -import { Route as TransitionCountCreateResourceRouteImport } from './routes/transition/count/create-resource' -import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' -import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' -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 NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' -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 RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' +import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' +import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' +import { Route as TransitionCountCreateResourceRouteImport } from './routes/transition/count/create-resource' +import { Route as TransitionTypingCreateResourceRouteImport } from './routes/transition/typing/create-resource' 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 { Route as RedirectTargetServerFnViaLoaderRouteImport } from './routes/redirect/$target/serverFn/via-loader' +import { Route as RedirectTargetServerFnViaUseServerFnRouteImport } from './routes/redirect/$target/serverFn/via-useServerFn' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const StreamRoute = StreamRouteImport.update({ - id: '/stream', - path: '/stream', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const ScriptsRoute = ScriptsRouteImport.update({ - id: '/scripts', - path: '/scripts', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const RawStreamRoute = RawStreamRouteImport.update({ - id: '/raw-stream', - path: '/raw-stream', +const DeferredWithoutSuspenseRoute = DeferredWithoutSuspenseRouteImport.update({ + id: '/deferred-without-suspense', + path: '/deferred-without-suspense', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const InlineScriptsRoute = InlineScriptsRouteImport.update({ + id: '/inline-scripts', + path: '/inline-scripts', getParentRoute: () => rootRouteImport, } as any) const LinksRoute = LinksRouteImport.update({ @@ -100,28 +99,24 @@ const LinksRoute = LinksRouteImport.update({ path: '/links', getParentRoute: () => rootRouteImport, } as any) -const InlineScriptsRoute = InlineScriptsRouteImport.update({ - id: '/inline-scripts', - path: '/inline-scripts', - getParentRoute: () => rootRouteImport, -} as any) -const DeferredWithoutSuspenseRoute = DeferredWithoutSuspenseRouteImport.update({ - id: '/deferred-without-suspense', - path: '/deferred-without-suspense', +const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ + id: '/not-found', + path: '/not-found', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const RawStreamRoute = RawStreamRouteImport.update({ + id: '/raw-stream', + path: '/raw-stream', getParentRoute: () => rootRouteImport, } as any) -const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ - id: '/specialChars', - path: '/specialChars', +const ScriptsRoute = ScriptsRouteImport.update({ + id: '/scripts', + path: '/scripts', getParentRoute: () => rootRouteImport, } as any) const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ @@ -129,102 +124,97 @@ const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ path: '/search-params', getParentRoute: () => rootRouteImport, } as any) -const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ - id: '/not-found', - path: '/not-found', +const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ + id: '/specialChars', + path: '/specialChars', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const StreamRoute = StreamRouteImport.update({ + id: '/stream', + path: '/stream', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRouteImport, } as any) -const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SearchParamsRouteRoute, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const RedirectIndexRoute = RedirectIndexRouteImport.update({ - id: '/redirect/', - path: '/redirect/', +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ +const MultiCookieRedirectIndexRoute = + MultiCookieRedirectIndexRouteImport.update({ + id: '/multi-cookie-redirect/', + path: '/multi-cookie-redirect/', + getParentRoute: () => rootRouteImport, + } as any) +const MultiCookieRedirectTargetRoute = + MultiCookieRedirectTargetRouteImport.update({ + id: '/multi-cookie-redirect/target', + path: '/multi-cookie-redirect/target', + getParentRoute: () => rootRouteImport, + } as any) +const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RawStreamRoute, + getParentRoute: () => NotFoundRouteRoute, +} as any) +const NotFoundParentBoundaryRouteRoute = + NotFoundParentBoundaryRouteRouteImport.update({ + id: '/parent-boundary', + path: '/parent-boundary', + getParentRoute: () => NotFoundRouteRoute, + } as any) +const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ + id: '/via-beforeLoad', + path: '/via-beforeLoad', + getParentRoute: () => NotFoundRouteRoute, +} as any) +const NotFoundViaBeforeLoadTargetRootRoute = + NotFoundViaBeforeLoadTargetRootRouteImport.update({ + id: '/via-beforeLoad-target-root', + path: '/via-beforeLoad-target-root', + getParentRoute: () => NotFoundRouteRoute, + } as any) +const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => NotFoundRouteRoute, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NotFoundRouteRoute, + getParentRoute: () => RawStreamRoute, } as any) -const MultiCookieRedirectIndexRoute = - MultiCookieRedirectIndexRouteImport.update({ - id: '/multi-cookie-redirect/', - path: '/multi-cookie-redirect/', - getParentRoute: () => rootRouteImport, - } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, +const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ + id: '/client-call', + path: '/client-call', + getParentRoute: () => RawStreamRoute, } as any) -const SpecialCharsChar45824Char54620Char48124Char44397Route = - SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => SpecialCharsRouteRoute, - } as any) -const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsRouteRoute, -} as any) -const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ - id: '/hash', - path: '/hash', - getParentRoute: () => SpecialCharsRouteRoute, -} as any) -const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsRouteRoute, -} as any) -const SearchParamsLoaderThrowsRedirectRoute = - SearchParamsLoaderThrowsRedirectRouteImport.update({ - id: '/loader-throws-redirect', - path: '/loader-throws-redirect', - getParentRoute: () => SearchParamsRouteRoute, - } as any) -const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ - id: '/default', - path: '/default', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RedirectTargetRoute = RedirectTargetRouteImport.update({ - id: '/redirect/$target', - path: '/redirect/$target', - getParentRoute: () => rootRouteImport, -} as any) -const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ - id: '/ssr-text-hint', - path: '/ssr-text-hint', +const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ + id: '/ssr-binary-hint', + path: '/ssr-binary-hint', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ - id: '/ssr-single', - path: '/ssr-single', +const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ + id: '/ssr-mixed', + path: '/ssr-mixed', getParentRoute: () => RawStreamRoute, } as any) const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ @@ -232,56 +222,51 @@ const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ path: '/ssr-multiple', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ - id: '/ssr-mixed', - path: '/ssr-mixed', +const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ + id: '/ssr-single', + path: '/ssr-single', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ - id: '/ssr-binary-hint', - path: '/ssr-binary-hint', +const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ + id: '/ssr-text-hint', + path: '/ssr-text-hint', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ - id: '/client-call', - path: '/client-call', - getParentRoute: () => RawStreamRoute, +const RedirectIndexRoute = RedirectIndexRouteImport.update({ + id: '/redirect/', + path: '/redirect/', + getParentRoute: () => rootRouteImport, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, +const RedirectTargetRoute = RedirectTargetRouteImport.update({ + id: '/redirect/$target', + path: '/redirect/$target', + getParentRoute: () => rootRouteImport, } as any) -const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => NotFoundRouteRoute, +const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const NotFoundViaBeforeLoadTargetRootRoute = - NotFoundViaBeforeLoadTargetRootRouteImport.update({ - id: '/via-beforeLoad-target-root', - path: '/via-beforeLoad-target-root', - getParentRoute: () => NotFoundRouteRoute, - } as any) -const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ - id: '/via-beforeLoad', - path: '/via-beforeLoad', - getParentRoute: () => NotFoundRouteRoute, +const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ + id: '/default', + path: '/default', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const MultiCookieRedirectTargetRoute = - MultiCookieRedirectTargetRouteImport.update({ - id: '/multi-cookie-redirect/target', - path: '/multi-cookie-redirect/target', - getParentRoute: () => rootRouteImport, +const SearchParamsLoaderThrowsRedirectRoute = + SearchParamsLoaderThrowsRedirectRouteImport.update({ + id: '/loader-throws-redirect', + path: '/loader-throws-redirect', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ + id: '/hash', + path: '/hash', + getParentRoute: () => SpecialCharsRouteRoute, } as any) const SpecialCharsMalformedRouteRoute = SpecialCharsMalformedRouteRouteImport.update({ @@ -289,88 +274,103 @@ const SpecialCharsMalformedRouteRoute = path: '/malformed', getParentRoute: () => SpecialCharsRouteRoute, } as any) -const NotFoundParentBoundaryRouteRoute = - NotFoundParentBoundaryRouteRouteImport.update({ - id: '/parent-boundary', - path: '/parent-boundary', - getParentRoute: () => NotFoundRouteRoute, +const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsRouteRoute, +} as any) +const SpecialCharsChar45824Char54620Char48124Char44397Route = + SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ +const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => UsersRoute, } as any) -const TransitionTypingCreateResourceRoute = - TransitionTypingCreateResourceRouteImport.update({ - id: '/transition/typing/create-resource', - path: '/transition/typing/create-resource', - getParentRoute: () => rootRouteImport, - } as any) -const TransitionCountCreateResourceRoute = - TransitionCountCreateResourceRouteImport.update({ - id: '/transition/count/create-resource', - path: '/transition/count/create-resource', - getParentRoute: () => rootRouteImport, - } as any) -const SpecialCharsMalformedSearchRoute = - SpecialCharsMalformedSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const SpecialCharsMalformedParamRoute = - SpecialCharsMalformedParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => RedirectTargetRoute, +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, } as any) -const RedirectTargetViaBeforeLoadRoute = - RedirectTargetViaBeforeLoadRouteImport.update({ +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const NotFoundParentBoundaryViaBeforeLoadRoute = + NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => NotFoundParentBoundaryRouteRoute, } as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const NotFoundParentBoundaryViaBeforeLoadRoute = - NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ +const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => RedirectTargetRoute, +} as any) +const RedirectTargetViaBeforeLoadRoute = + RedirectTargetViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', - getParentRoute: () => NotFoundParentBoundaryRouteRoute, + getParentRoute: () => RedirectTargetRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersRoute, -} as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, -} as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => RedirectTargetRoute, } as any) +const SpecialCharsMalformedParamRoute = + SpecialCharsMalformedParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsMalformedRouteRoute, + } as any) +const SpecialCharsMalformedSearchRoute = + SpecialCharsMalformedSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsMalformedRouteRoute, + } as any) +const TransitionCountCreateResourceRoute = + TransitionCountCreateResourceRouteImport.update({ + id: '/transition/count/create-resource', + path: '/transition/count/create-resource', + getParentRoute: () => rootRouteImport, + } as any) +const TransitionTypingCreateResourceRoute = + TransitionTypingCreateResourceRouteImport.update({ + id: '/transition/typing/create-resource', + path: '/transition/typing/create-resource', + getParentRoute: () => rootRouteImport, + } as any) const RedirectTargetServerFnIndexRoute = RedirectTargetServerFnIndexRouteImport.update({ id: '/serverFn/', path: '/serverFn/', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaUseServerFnRoute = - RedirectTargetServerFnViaUseServerFnRouteImport.update({ - id: '/serverFn/via-useServerFn', - path: '/serverFn/via-useServerFn', +const RedirectTargetServerFnViaBeforeLoadRoute = + RedirectTargetServerFnViaBeforeLoadRouteImport.update({ + id: '/serverFn/via-beforeLoad', + path: '/serverFn/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) const RedirectTargetServerFnViaLoaderRoute = @@ -379,10 +379,10 @@ const RedirectTargetServerFnViaLoaderRoute = path: '/serverFn/via-loader', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaBeforeLoadRoute = - RedirectTargetServerFnViaBeforeLoadRouteImport.update({ - id: '/serverFn/via-beforeLoad', - path: '/serverFn/via-beforeLoad', +const RedirectTargetServerFnViaUseServerFnRoute = + RedirectTargetServerFnViaUseServerFnRouteImport.update({ + id: '/serverFn/via-useServerFn', + path: '/serverFn/via-useServerFn', getParentRoute: () => RedirectTargetRoute, } as any) @@ -769,39 +769,39 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/stream': { - id: '/stream' - path: '/stream' - fullPath: '/stream' - preLoaderRoute: typeof StreamRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/scripts': { - id: '/scripts' - path: '/scripts' - fullPath: '/scripts' - preLoaderRoute: typeof ScriptsRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/raw-stream': { - id: '/raw-stream' - path: '/raw-stream' - fullPath: '/raw-stream' - preLoaderRoute: typeof RawStreamRouteImport + '/deferred-without-suspense': { + id: '/deferred-without-suspense' + path: '/deferred-without-suspense' + fullPath: '/deferred-without-suspense' + preLoaderRoute: typeof DeferredWithoutSuspenseRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/inline-scripts': { + id: '/inline-scripts' + path: '/inline-scripts' + fullPath: '/inline-scripts' + preLoaderRoute: typeof InlineScriptsRouteImport parentRoute: typeof rootRouteImport } '/links': { @@ -811,39 +811,32 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LinksRouteImport parentRoute: typeof rootRouteImport } - '/inline-scripts': { - id: '/inline-scripts' - path: '/inline-scripts' - fullPath: '/inline-scripts' - preLoaderRoute: typeof InlineScriptsRouteImport - parentRoute: typeof rootRouteImport - } - '/deferred-without-suspense': { - id: '/deferred-without-suspense' - path: '/deferred-without-suspense' - fullPath: '/deferred-without-suspense' - preLoaderRoute: typeof DeferredWithoutSuspenseRouteImport + '/not-found': { + id: '/not-found' + path: '/not-found' + fullPath: '/not-found' + preLoaderRoute: typeof NotFoundRouteRouteImport 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 } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/raw-stream': { + id: '/raw-stream' + path: '/raw-stream' + fullPath: '/raw-stream' + preLoaderRoute: typeof RawStreamRouteImport parentRoute: typeof rootRouteImport } - '/specialChars': { - id: '/specialChars' - path: '/specialChars' - fullPath: '/specialChars' - preLoaderRoute: typeof SpecialCharsRouteRouteImport + '/scripts': { + id: '/scripts' + path: '/scripts' + fullPath: '/scripts' + preLoaderRoute: typeof ScriptsRouteImport parentRoute: typeof rootRouteImport } '/search-params': { @@ -853,54 +846,54 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/not-found': { - id: '/not-found' - path: '/not-found' - fullPath: '/not-found' - preLoaderRoute: typeof NotFoundRouteRouteImport + '/specialChars': { + id: '/specialChars' + path: '/specialChars' + fullPath: '/specialChars' + preLoaderRoute: typeof SpecialCharsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/stream': { + id: '/stream' + path: '/stream' + fullPath: '/stream' + preLoaderRoute: typeof StreamRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRouteImport } - '/search-params/': { - id: '/search-params/' - path: '/' - fullPath: '/search-params/' - preLoaderRoute: typeof SearchParamsIndexRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/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 } - '/raw-stream/': { - id: '/raw-stream/' - path: '/' - fullPath: '/raw-stream/' - preLoaderRoute: typeof RawStreamIndexRouteImport - parentRoute: typeof RawStreamRoute + '/multi-cookie-redirect/': { + id: '/multi-cookie-redirect/' + path: '/multi-cookie-redirect' + fullPath: '/multi-cookie-redirect/' + preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport + parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/multi-cookie-redirect/target': { + id: '/multi-cookie-redirect/target' + path: '/multi-cookie-redirect/target' + fullPath: '/multi-cookie-redirect/target' + preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport + parentRoute: typeof rootRouteImport } '/not-found/': { id: '/not-found/' @@ -909,88 +902,67 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NotFoundIndexRouteImport parentRoute: typeof NotFoundRouteRoute } - '/multi-cookie-redirect/': { - id: '/multi-cookie-redirect/' - path: '/multi-cookie-redirect' - fullPath: '/multi-cookie-redirect/' - preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport - parentRoute: typeof rootRouteImport + '/not-found/parent-boundary': { + id: '/not-found/parent-boundary' + path: '/parent-boundary' + fullPath: '/not-found/parent-boundary' + preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute + '/not-found/via-beforeLoad': { + id: '/not-found/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/not-found/via-beforeLoad' + preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/대한민국': { - id: '/specialChars/대한민국' - path: '/대한민국' - fullPath: '/specialChars/대한민국' - preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-beforeLoad-target-root': { + id: '/not-found/via-beforeLoad-target-root' + path: '/via-beforeLoad-target-root' + fullPath: '/not-found/via-beforeLoad-target-root' + preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/search': { - id: '/specialChars/search' - path: '/search' - fullPath: '/specialChars/search' - preLoaderRoute: typeof SpecialCharsSearchRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-loader': { + id: '/not-found/via-loader' + path: '/via-loader' + fullPath: '/not-found/via-loader' + preLoaderRoute: typeof NotFoundViaLoaderRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/hash': { - id: '/specialChars/hash' - path: '/hash' - fullPath: '/specialChars/hash' - preLoaderRoute: typeof SpecialCharsHashRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } - '/specialChars/$param': { - id: '/specialChars/$param' - path: '/$param' - fullPath: '/specialChars/$param' - preLoaderRoute: typeof SpecialCharsParamRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute } - '/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' - path: '/default' - fullPath: '/search-params/default' - preLoaderRoute: typeof SearchParamsDefaultRouteImport - parentRoute: typeof SearchParamsRouteRoute - } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport - parentRoute: typeof rootRouteImport - } - '/raw-stream/ssr-text-hint': { - id: '/raw-stream/ssr-text-hint' - path: '/ssr-text-hint' - fullPath: '/raw-stream/ssr-text-hint' - preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + '/raw-stream/': { + id: '/raw-stream/' + path: '/' + fullPath: '/raw-stream/' + preLoaderRoute: typeof RawStreamIndexRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-single': { - id: '/raw-stream/ssr-single' - path: '/ssr-single' - fullPath: '/raw-stream/ssr-single' - preLoaderRoute: typeof RawStreamSsrSingleRouteImport + '/raw-stream/client-call': { + id: '/raw-stream/client-call' + path: '/client-call' + fullPath: '/raw-stream/client-call' + preLoaderRoute: typeof RawStreamClientCallRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-multiple': { - id: '/raw-stream/ssr-multiple' - path: '/ssr-multiple' - fullPath: '/raw-stream/ssr-multiple' - preLoaderRoute: typeof RawStreamSsrMultipleRouteImport + '/raw-stream/ssr-binary-hint': { + id: '/raw-stream/ssr-binary-hint' + path: '/ssr-binary-hint' + fullPath: '/raw-stream/ssr-binary-hint' + preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport parentRoute: typeof RawStreamRoute } '/raw-stream/ssr-mixed': { @@ -1000,68 +972,75 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof RawStreamSsrMixedRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-binary-hint': { - id: '/raw-stream/ssr-binary-hint' - path: '/ssr-binary-hint' - fullPath: '/raw-stream/ssr-binary-hint' - preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport + '/raw-stream/ssr-multiple': { + id: '/raw-stream/ssr-multiple' + path: '/ssr-multiple' + fullPath: '/raw-stream/ssr-multiple' + preLoaderRoute: typeof RawStreamSsrMultipleRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/client-call': { - id: '/raw-stream/client-call' - path: '/client-call' - fullPath: '/raw-stream/client-call' - preLoaderRoute: typeof RawStreamClientCallRouteImport + '/raw-stream/ssr-single': { + id: '/raw-stream/ssr-single' + path: '/ssr-single' + fullPath: '/raw-stream/ssr-single' + preLoaderRoute: typeof RawStreamSsrSingleRouteImport parentRoute: typeof RawStreamRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsPostIdRouteImport - parentRoute: typeof PostsRoute + '/raw-stream/ssr-text-hint': { + id: '/raw-stream/ssr-text-hint' + path: '/ssr-text-hint' + fullPath: '/raw-stream/ssr-text-hint' + preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + parentRoute: typeof RawStreamRoute } - '/not-found/via-loader': { - id: '/not-found/via-loader' - path: '/via-loader' - fullPath: '/not-found/via-loader' - preLoaderRoute: typeof NotFoundViaLoaderRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport + parentRoute: typeof rootRouteImport } - '/not-found/via-beforeLoad-target-root': { - id: '/not-found/via-beforeLoad-target-root' - path: '/via-beforeLoad-target-root' - fullPath: '/not-found/via-beforeLoad-target-root' - preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport + 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 + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/multi-cookie-redirect/target': { - id: '/multi-cookie-redirect/target' - path: '/multi-cookie-redirect/target' - fullPath: '/multi-cookie-redirect/target' - preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport - parentRoute: typeof rootRouteImport + '/search-params/default': { + id: '/search-params/default' + path: '/default' + fullPath: '/search-params/default' + preLoaderRoute: typeof SearchParamsDefaultRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersRouteImport - 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 } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/specialChars/$param': { + id: '/specialChars/$param' + path: '/$param' + fullPath: '/specialChars/$param' + preLoaderRoute: typeof SpecialCharsParamRouteImport + parentRoute: typeof SpecialCharsRouteRoute + } + '/specialChars/hash': { + id: '/specialChars/hash' + path: '/hash' + fullPath: '/specialChars/hash' + preLoaderRoute: typeof SpecialCharsHashRouteImport + parentRoute: typeof SpecialCharsRouteRoute } '/specialChars/malformed': { id: '/specialChars/malformed' @@ -1070,61 +1049,61 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof SpecialCharsMalformedRouteRouteImport parentRoute: typeof SpecialCharsRouteRoute } - '/not-found/parent-boundary': { - id: '/not-found/parent-boundary' - path: '/parent-boundary' - fullPath: '/not-found/parent-boundary' - preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport - parentRoute: typeof NotFoundRouteRoute + '/specialChars/search': { + id: '/specialChars/search' + path: '/search' + fullPath: '/specialChars/search' + preLoaderRoute: typeof SpecialCharsSearchRouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/redirect/$target/': { - id: '/redirect/$target/' - path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport - parentRoute: typeof RedirectTargetRoute + '/specialChars/대한민국': { + id: '/specialChars/대한민국' + path: '/대한민국' + fullPath: '/specialChars/대한민국' + preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/transition/typing/create-resource': { - id: '/transition/typing/create-resource' - path: '/transition/typing/create-resource' - fullPath: '/transition/typing/create-resource' - preLoaderRoute: typeof TransitionTypingCreateResourceRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/transition/count/create-resource': { - id: '/transition/count/create-resource' - path: '/transition/count/create-resource' - fullPath: '/transition/count/create-resource' - preLoaderRoute: typeof TransitionCountCreateResourceRouteImport - parentRoute: typeof rootRouteImport + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/specialChars/malformed/search': { - id: '/specialChars/malformed/search' - path: '/search' - fullPath: '/specialChars/malformed/search' - preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/specialChars/malformed/$param': { - id: '/specialChars/malformed/$param' - path: '/$param' - fullPath: '/specialChars/malformed/$param' - preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute + '/_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/via-loader': { - id: '/redirect/$target/via-loader' - path: '/via-loader' - fullPath: '/redirect/$target/via-loader' - preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport - parentRoute: typeof RedirectTargetRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute } - '/redirect/$target/via-beforeLoad': { - id: '/redirect/$target/via-beforeLoad' + '/not-found/parent-boundary/via-beforeLoad': { + id: '/not-found/parent-boundary/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/redirect/$target/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + fullPath: '/not-found/parent-boundary/via-beforeLoad' + preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport + parentRoute: typeof NotFoundParentBoundaryRouteRoute } '/posts_/$postId/deep': { id: '/posts_/$postId/deep' @@ -1133,33 +1112,54 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } - '/not-found/parent-boundary/via-beforeLoad': { - id: '/not-found/parent-boundary/via-beforeLoad' + '/redirect/$target/': { + id: '/redirect/$target/' + path: '/' + fullPath: '/redirect/$target/' + preLoaderRoute: typeof RedirectTargetIndexRouteImport + parentRoute: typeof RedirectTargetRoute + } + '/redirect/$target/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/not-found/parent-boundary/via-beforeLoad' - preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport - parentRoute: typeof NotFoundParentBoundaryRouteRoute + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/api/users/$userId': { - id: '/api/users/$userId' - path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + '/redirect/$target/via-loader': { + id: '/redirect/$target/via-loader' + path: '/via-loader' + fullPath: '/redirect/$target/via-loader' + preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport + 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 + '/specialChars/malformed/$param': { + id: '/specialChars/malformed/$param' + path: '/$param' + fullPath: '/specialChars/malformed/$param' + preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/specialChars/malformed/search': { + id: '/specialChars/malformed/search' + path: '/search' + fullPath: '/specialChars/malformed/search' + preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute + } + '/transition/count/create-resource': { + id: '/transition/count/create-resource' + path: '/transition/count/create-resource' + fullPath: '/transition/count/create-resource' + preLoaderRoute: typeof TransitionCountCreateResourceRouteImport + parentRoute: typeof rootRouteImport + } + '/transition/typing/create-resource': { + id: '/transition/typing/create-resource' + path: '/transition/typing/create-resource' + fullPath: '/transition/typing/create-resource' + preLoaderRoute: typeof TransitionTypingCreateResourceRouteImport + parentRoute: typeof rootRouteImport } '/redirect/$target/serverFn/': { id: '/redirect/$target/serverFn/' @@ -1168,11 +1168,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof RedirectTargetServerFnIndexRouteImport parentRoute: typeof RedirectTargetRoute } - '/redirect/$target/serverFn/via-useServerFn': { - id: '/redirect/$target/serverFn/via-useServerFn' - path: '/serverFn/via-useServerFn' - fullPath: '/redirect/$target/serverFn/via-useServerFn' - preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport + '/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 } '/redirect/$target/serverFn/via-loader': { @@ -1182,11 +1182,11 @@ declare module '@tanstack/solid-router' { 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 + '/redirect/$target/serverFn/via-useServerFn': { + id: '/redirect/$target/serverFn/via-useServerFn' + path: '/serverFn/via-useServerFn' + fullPath: '/redirect/$target/serverFn/via-useServerFn' + preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport parentRoute: typeof RedirectTargetRoute } } diff --git a/e2e/solid-start/csp/package.json b/e2e/solid-start/csp/package.json index 9f4f8b3ba0..86f94b60b2 100644 --- a/e2e/solid-start/csp/package.json +++ b/e2e/solid-start/csp/package.json @@ -17,12 +17,13 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/css-modules/package.json b/e2e/solid-start/css-modules/package.json index c0176dfccd..01cb9f3a72 100644 --- a/e2e/solid-start/css-modules/package.json +++ b/e2e/solid-start/css-modules/package.json @@ -20,12 +20,13 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:*", "@types/node": "^22.10.2", "sass": "^1.97.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/solid-start/custom-basepath/package.json b/e2e/solid-start/custom-basepath/package.json index aa73f280e0..cc4129dbc8 100644 --- a/e2e/solid-start/custom-basepath/package.json +++ b/e2e/solid-start/custom-basepath/package.json @@ -20,7 +20,7 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/express": "^5.0.6", @@ -29,7 +29,8 @@ "srvx": "^0.11.9", "tailwindcss": "^4.2.2", "tsx": "^4.20.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "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 f87b2b3731..ddc9f81ab0 100644 --- a/e2e/solid-start/custom-basepath/src/routeTree.gen.ts +++ b/e2e/solid-start/custom-basepath/src/routeTree.gen.ts @@ -9,30 +9,35 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NavigateTestRouteImport } from './routes/navigate-test' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as DeferredRouteImport } from './routes/deferred' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as RedirectIndexRouteImport } from './routes/redirect.index' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as NavigateTestRouteImport } from './routes/navigate-test' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as UsersRouteImport } from './routes/users' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as RedirectThrowItRouteImport } from './routes/redirect.throw-it' 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 { Route as RedirectIndexRouteImport } from './routes/redirect.index' +import { Route as RedirectThrowItRouteImport } from './routes/redirect.throw-it' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRouteImport, +} as any) +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) const NavigateTestRoute = NavigateTestRouteImport.update({ @@ -40,66 +45,61 @@ const NavigateTestRoute = NavigateTestRouteImport.update({ path: '/navigate-test', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => UsersRoute, + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any) const RedirectIndexRoute = RedirectIndexRouteImport.update({ id: '/redirect/', path: '/redirect/', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ +const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ + id: '/redirect/throw-it', + path: '/redirect/throw-it', + getParentRoute: () => rootRouteImport, +} as any) +const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => PostsRoute, + getParentRoute: () => UsersRoute, } as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRoute, } as any) -const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ - id: '/redirect/throw-it', - path: '/redirect/throw-it', - getParentRoute: () => rootRouteImport, -} as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, } as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -218,25 +218,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/navigate-test': { - id: '/navigate-test' - path: '/navigate-test' - fullPath: '/navigate-test' - preLoaderRoute: typeof NavigateTestRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/logout': { @@ -246,32 +239,32 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/navigate-test': { + id: '/navigate-test' + path: '/navigate-test' + fullPath: '/navigate-test' + preLoaderRoute: typeof NavigateTestRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRouteImport } - '/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 } '/posts/': { @@ -281,20 +274,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } - '/redirect/throw-it': { - id: '/redirect/throw-it' - path: '/redirect/throw-it' - fullPath: '/redirect/throw-it' - preLoaderRoute: typeof RedirectThrowItRouteImport - parentRoute: typeof rootRouteImport - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -302,20 +281,34 @@ 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 + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport + '/redirect/throw-it': { + id: '/redirect/throw-it' + path: '/redirect/throw-it' + fullPath: '/redirect/throw-it' + preLoaderRoute: typeof RedirectThrowItRouteImport parentRoute: typeof rootRouteImport } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } '/api/users/$userId': { id: '/api/users/$userId' path: '/$userId' @@ -323,6 +316,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ApiUsersUserIdRouteImport parentRoute: typeof ApiUsersRoute } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/solid-start/deferred-hydration/package.json b/e2e/solid-start/deferred-hydration/package.json index a4ccdb9439..825b564b7b 100644 --- a/e2e/solid-start/deferred-hydration/package.json +++ b/e2e/solid-start/deferred-hydration/package.json @@ -24,13 +24,14 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@rsbuild/core": "^2.0.11", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-solid": "^2.0.0-beta.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" }, diff --git a/e2e/solid-start/deferred-hydration/src/routeTree.gen.ts b/e2e/solid-start/deferred-hydration/src/routeTree.gen.ts index e75225b500..e689dd19b8 100644 --- a/e2e/solid-start/deferred-hydration/src/routeTree.gen.ts +++ b/e2e/solid-start/deferred-hydration/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 ImportedRouteImport } from './routes/imported' -import { Route as EnhancedRouteImport } from './routes/enhanced' -import { Route as CssRouteImport } from './routes/css' -import { Route as ComponentsRouteImport } from './routes/components' import { Route as IndexRouteImport } from './routes/index' +import { Route as ComponentsRouteImport } from './routes/components' +import { Route as CssRouteImport } from './routes/css' +import { Route as EnhancedRouteImport } from './routes/enhanced' +import { Route as ImportedRouteImport } from './routes/imported' -const ImportedRoute = ImportedRouteImport.update({ - id: '/imported', - path: '/imported', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const EnhancedRoute = EnhancedRouteImport.update({ - id: '/enhanced', - path: '/enhanced', +const ComponentsRoute = ComponentsRouteImport.update({ + id: '/components', + path: '/components', getParentRoute: () => rootRouteImport, } as any) const CssRoute = CssRouteImport.update({ @@ -30,14 +30,14 @@ const CssRoute = CssRouteImport.update({ path: '/css', getParentRoute: () => rootRouteImport, } as any) -const ComponentsRoute = ComponentsRouteImport.update({ - id: '/components', - path: '/components', +const EnhancedRoute = EnhancedRouteImport.update({ + id: '/enhanced', + path: '/enhanced', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ImportedRoute = ImportedRouteImport.update({ + id: '/imported', + path: '/imported', getParentRoute: () => rootRouteImport, } as any) @@ -81,18 +81,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/imported': { - id: '/imported' - path: '/imported' - fullPath: '/imported' - preLoaderRoute: typeof ImportedRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/enhanced': { - id: '/enhanced' - path: '/enhanced' - fullPath: '/enhanced' - preLoaderRoute: typeof EnhancedRouteImport + '/components': { + id: '/components' + path: '/components' + fullPath: '/components' + preLoaderRoute: typeof ComponentsRouteImport parentRoute: typeof rootRouteImport } '/css': { @@ -102,18 +102,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof CssRouteImport parentRoute: typeof rootRouteImport } - '/components': { - id: '/components' - path: '/components' - fullPath: '/components' - preLoaderRoute: typeof ComponentsRouteImport + '/enhanced': { + id: '/enhanced' + path: '/enhanced' + fullPath: '/enhanced' + preLoaderRoute: typeof EnhancedRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/imported': { + id: '/imported' + path: '/imported' + fullPath: '/imported' + preLoaderRoute: typeof ImportedRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/solid-start/query-integration/package.json b/e2e/solid-start/query-integration/package.json index 780966ab91..65491290b0 100644 --- a/e2e/solid-start/query-integration/package.json +++ b/e2e/solid-start/query-integration/package.json @@ -24,13 +24,14 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/query-integration/src/routeTree.gen.ts b/e2e/solid-start/query-integration/src/routeTree.gen.ts index 6b183b98e2..cc6dad1cff 100644 --- a/e2e/solid-start/query-integration/src/routeTree.gen.ts +++ b/e2e/solid-start/query-integration/src/routeTree.gen.ts @@ -9,16 +9,11 @@ // 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 UseQueryRouteImport } from './routes/useQuery' import { Route as IndexRouteImport } from './routes/index' import { Route as NotFoundReloadIdRouteImport } from './routes/not-found-reload.$id' +import { Route as UseQueryRouteImport } from './routes/useQuery' import { Route as LoaderFetchQueryTypeRouteImport } from './routes/loader-fetchQuery/$type' -const UseQueryRoute = UseQueryRouteImport.update({ - id: '/useQuery', - path: '/useQuery', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -29,6 +24,11 @@ const NotFoundReloadIdRoute = NotFoundReloadIdRouteImport.update({ path: '/not-found-reload/$id', getParentRoute: () => rootRouteImport, } as any) +const UseQueryRoute = UseQueryRouteImport.update({ + id: '/useQuery', + path: '/useQuery', + getParentRoute: () => rootRouteImport, +} as any) const LoaderFetchQueryTypeRoute = LoaderFetchQueryTypeRouteImport.update({ id: '/loader-fetchQuery/$type', path: '/loader-fetchQuery/$type', @@ -80,13 +80,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/useQuery': { - id: '/useQuery' - path: '/useQuery' - fullPath: '/useQuery' - preLoaderRoute: typeof UseQueryRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -101,6 +94,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof NotFoundReloadIdRouteImport parentRoute: typeof rootRouteImport } + '/useQuery': { + id: '/useQuery' + path: '/useQuery' + fullPath: '/useQuery' + preLoaderRoute: typeof UseQueryRouteImport + parentRoute: typeof rootRouteImport + } '/loader-fetchQuery/$type': { id: '/loader-fetchQuery/$type' path: '/loader-fetchQuery/$type' diff --git a/e2e/solid-start/scroll-restoration/package.json b/e2e/solid-start/scroll-restoration/package.json index 3ffa9d313e..4aedd048e7 100644 --- a/e2e/solid-start/scroll-restoration/package.json +++ b/e2e/solid-start/scroll-restoration/package.json @@ -22,15 +22,16 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts b/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts index 4b8d19fbef..7668df5930 100644 --- a/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts +++ b/e2e/solid-start/scroll-restoration/src/routeTree.gen.ts @@ -10,18 +10,18 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' -import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' +import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsWithSearchRoute = testsWithSearchRouteImport.update({ - id: '/(tests)/with-search', - path: '/with-search', +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', getParentRoute: () => rootRouteImport, } as any) const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ @@ -29,9 +29,9 @@ const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ path: '/with-loader', getParentRoute: () => rootRouteImport, } as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', +const testsWithSearchRoute = testsWithSearchRouteImport.update({ + id: '/(tests)/with-search', + path: '/with-search', getParentRoute: () => rootRouteImport, } as any) @@ -83,11 +83,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/with-search': { - id: '/(tests)/with-search' - path: '/with-search' - fullPath: '/with-search' - preLoaderRoute: typeof testsWithSearchRouteImport + '/(tests)/normal-page': { + id: '/(tests)/normal-page' + path: '/normal-page' + fullPath: '/normal-page' + preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } '/(tests)/with-loader': { @@ -97,11 +97,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof testsWithLoaderRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/normal-page': { - id: '/(tests)/normal-page' - path: '/normal-page' - fullPath: '/normal-page' - preLoaderRoute: typeof testsNormalPageRouteImport + '/(tests)/with-search': { + id: '/(tests)/with-search' + path: '/with-search' + fullPath: '/with-search' + preLoaderRoute: typeof testsWithSearchRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/solid-start/selective-ssr/dev-server.ts b/e2e/solid-start/selective-ssr/dev-server.ts new file mode 100644 index 0000000000..519075f261 --- /dev/null +++ b/e2e/solid-start/selective-ssr/dev-server.ts @@ -0,0 +1,5 @@ +import { getTestServerPort } from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +export const devPort = await getTestServerPort(`${packageJson.name}-dev`) +export const devBaseURL = `http://localhost:${devPort}` diff --git a/e2e/solid-start/selective-ssr/package.json b/e2e/solid-start/selective-ssr/package.json index 3beb229c8f..9deaa42c7b 100644 --- a/e2e/solid-start/selective-ssr/package.json +++ b/e2e/solid-start/selective-ssr/package.json @@ -23,7 +23,8 @@ "@tanstack/router-e2e-utils": "workspace:^", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/solid-start/selective-ssr/playwright.config.ts b/e2e/solid-start/selective-ssr/playwright.config.ts index badd6db0eb..09e5c50410 100644 --- a/e2e/solid-start/selective-ssr/playwright.config.ts +++ b/e2e/solid-start/selective-ssr/playwright.config.ts @@ -1,6 +1,7 @@ import { defineConfig, devices } from '@playwright/test' import { getTestServerPort } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } +import { devBaseURL, devPort } from './dev-server' const PORT = await getTestServerPort(packageJson.name) const baseURL = `http://localhost:${PORT}` @@ -18,12 +19,21 @@ export default defineConfig({ 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', - }, + 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', + }, + { + command: `NODE_ENV=development VITE_SERVER_PORT=${devPort} pnpm dev:e2e --host localhost --port ${devPort} --strictPort`, + url: devBaseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + timeout: 90_000, + }, + ], projects: [ { diff --git a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts index df2b865e51..d0ea8db3a2 100644 --- a/e2e/solid-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/solid-start/selective-ssr/src/routeTree.gen.ts @@ -9,14 +9,15 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as DataOnlyPendingComponentRouteImport } from './routes/data-only-pending-component' import { Route as IndexRouteImport } from './routes/index' +import { Route as DataOnlyPendingComponentRouteImport } from './routes/data-only-pending-component' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as SsrFalsePendingMinRouteImport } from './routes/ssr-false-pending-min' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const DataOnlyPendingComponentRoute = @@ -25,9 +26,14 @@ const DataOnlyPendingComponentRoute = path: '/data-only-pending-component', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const SsrFalsePendingMinRoute = SsrFalsePendingMinRouteImport.update({ + id: '/ssr-false-pending-min', + path: '/ssr-false-pending-min', getParentRoute: () => rootRouteImport, } as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ @@ -40,12 +46,14 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-min': typeof SsrFalsePendingMinRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-min': typeof SsrFalsePendingMinRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRoutesById { @@ -53,18 +61,30 @@ export interface FileRoutesById { '/': typeof IndexRoute '/data-only-pending-component': typeof DataOnlyPendingComponentRoute '/posts': typeof PostsRouteWithChildren + '/ssr-false-pending-min': typeof SsrFalsePendingMinRoute '/posts/$postId': typeof PostsPostIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId' + fullPaths: + | '/' + | '/data-only-pending-component' + | '/posts' + | '/ssr-false-pending-min' + | '/posts/$postId' fileRoutesByTo: FileRoutesByTo - to: '/' | '/data-only-pending-component' | '/posts' | '/posts/$postId' + to: + | '/' + | '/data-only-pending-component' + | '/posts' + | '/ssr-false-pending-min' + | '/posts/$postId' id: | '__root__' | '/' | '/data-only-pending-component' | '/posts' + | '/ssr-false-pending-min' | '/posts/$postId' fileRoutesById: FileRoutesById } @@ -72,15 +92,16 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute DataOnlyPendingComponentRoute: typeof DataOnlyPendingComponentRoute PostsRoute: typeof PostsRouteWithChildren + SsrFalsePendingMinRoute: typeof SsrFalsePendingMinRoute } declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/data-only-pending-component': { @@ -90,11 +111,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DataOnlyPendingComponentRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/ssr-false-pending-min': { + id: '/ssr-false-pending-min' + path: '/ssr-false-pending-min' + fullPath: '/ssr-false-pending-min' + preLoaderRoute: typeof SsrFalsePendingMinRouteImport parentRoute: typeof rootRouteImport } '/posts/$postId': { @@ -121,6 +149,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, DataOnlyPendingComponentRoute: DataOnlyPendingComponentRoute, PostsRoute: PostsRouteWithChildren, + SsrFalsePendingMinRoute: SsrFalsePendingMinRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-min.tsx b/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-min.tsx new file mode 100644 index 0000000000..12ab6b5f78 --- /dev/null +++ b/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-min.tsx @@ -0,0 +1,48 @@ +import { onCleanup, onSettled } from 'solid-js' +import { createFileRoute } from '@tanstack/solid-router' + +const pendingMinMs = 1500 + +function recordEvent(type: string) { + if (typeof window === 'undefined') { + return + } + + const global = window as any + global.__events ??= [] + global.__events.push({ type, t: performance.now() }) +} + +function Pending() { + onSettled(() => { + recordEvent('pending-mounted') + }) + onCleanup(() => { + recordEvent('pending-unmounted') + }) + + return
Loading SSR false route...
+} + +function Target() { + onSettled(() => { + recordEvent('target-mounted') + }) + + return
SSR false route loaded
+} + +export const Route = createFileRoute('/ssr-false-pending-min')({ + ssr: false, + pendingMs: 0, + pendingMinMs, + pendingComponent: Pending, + loader: async () => { + recordEvent('loader-start') + await new Promise((resolve) => setTimeout(resolve, 50)) + recordEvent('loader-done') + + return { ok: true } + }, + component: Target, +}) diff --git a/e2e/solid-start/selective-ssr/tests/issue-7283-dev-hydration.spec.ts b/e2e/solid-start/selective-ssr/tests/issue-7283-dev-hydration.spec.ts new file mode 100644 index 0000000000..a054a0433a --- /dev/null +++ b/e2e/solid-start/selective-ssr/tests/issue-7283-dev-hydration.spec.ts @@ -0,0 +1,84 @@ +import { expect, test } from '@playwright/test' +import { devBaseURL } from '../dev-server' +import type { Page } from '@playwright/test' + +// Regression test for https://github.com/TanStack/router/issues/7283 +// +// Selective SSR routes (`ssr: false` / `ssr: 'data-only'`) that declare a +// `pendingComponent` must hydrate cleanly in DEV mode. Solid only validates +// hydration markers in dev, so the production webServer configured in +// playwright.config.ts masks the mismatch ("template is not a function", +// route never reaches its loaded component). Playwright therefore owns a +// separate `vite dev` server and drives the app against it. + +function collectHydrationFailures(page: Page) { + const failures: Array = [] + const isHydrationFailure = (text: string) => + text.includes('template is not a function') || + text.includes("wasn't caught by any route") || + text.includes('Hydration Mismatch') + page.on('pageerror', (err) => { + if (isHydrationFailure(err.message)) failures.push(err.message) + }) + page.on('console', (msg) => { + if (msg.type() === 'error' || msg.type() === 'warning') { + const text = msg.text() + if (isHydrationFailure(text)) failures.push(text) + } + }) + return failures +} + +test.describe('dev-mode hydration of selective SSR routes with pendingComponent', () => { + test.setTimeout(120_000) + + test('ssr:false route with pendingComponent hydrates cleanly and reaches its loaded component (issue #7283)', async ({ + page, + }) => { + const failures = collectHydrationFailures(page) + + await page.goto(`${devBaseURL}/ssr-false-pending-min`) + + // The loaded component is the issue oracle: main never reaches it. + await expect(page.getByTestId('ssr-false-target')).toBeVisible({ + timeout: 15_000, + }) + + // No dev hydration mismatch may occur along the way. + expect(failures).toEqual([]) + + await expect(page.getByTestId('ssr-false-pending')).not.toBeAttached() + + const lifecycleEvents = await page.evaluate(() => + ((globalThis as any).__events ?? []) + .map((event: { type: string }) => event.type) + .filter((type: string) => + ['pending-mounted', 'pending-unmounted', 'target-mounted'].includes( + type, + ), + ), + ) + expect(lifecycleEvents.slice(-3)).toEqual([ + 'pending-mounted', + 'pending-unmounted', + 'target-mounted', + ]) + }) + + test('data-only route with pendingComponent hydrates cleanly and reaches its loaded component (regression vs main)', async ({ + page, + }) => { + const failures = collectHydrationFailures(page) + + await page.goto(`${devBaseURL}/data-only-pending-component`) + + await expect( + page.getByTestId('data-only-pending-component-pending'), + ).toBeAttached() + await expect( + page.getByTestId('data-only-pending-component-ready-label'), + ).toHaveText('OK - loader finished', { timeout: 15_000 }) + + expect(failures).toEqual([]) + }) +}) diff --git a/e2e/solid-start/serialization-adapters/package.json b/e2e/solid-start/serialization-adapters/package.json index d3a8ad6e4a..c7ce487189 100644 --- a/e2e/solid-start/serialization-adapters/package.json +++ b/e2e/solid-start/serialization-adapters/package.json @@ -26,8 +26,9 @@ "@types/node": "^22.10.2", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/serialization-adapters/src/routeTree.gen.ts b/e2e/solid-start/serialization-adapters/src/routeTree.gen.ts index 09d0211154..94ebb49d16 100644 --- a/e2e/solid-start/serialization-adapters/src/routeTree.gen.ts +++ b/e2e/solid-start/serialization-adapters/src/routeTree.gen.ts @@ -10,25 +10,26 @@ 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 SsrNestedRouteImport } from './routes/ssr/nested' -import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' -import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' import { Route as ServerFunctionCustomErrorRouteImport } from './routes/server-function/custom-error' +import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' +import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' +import { Route as SsrNestedRouteImport } from './routes/ssr/nested' +import { Route as SsrStreamRouteImport } from './routes/ssr/stream' 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 SsrNestedRoute = SsrNestedRouteImport.update({ - id: '/ssr/nested', - path: '/ssr/nested', +const ServerFunctionCustomErrorRoute = + ServerFunctionCustomErrorRouteImport.update({ + id: '/server-function/custom-error', + path: '/server-function/custom-error', + getParentRoute: () => rootRouteImport, + } as any) +const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ + id: '/server-function/nested', + path: '/server-function/nested', getParentRoute: () => rootRouteImport, } as any) const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ @@ -36,17 +37,16 @@ const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ path: '/ssr/data-only', getParentRoute: () => rootRouteImport, } as any) -const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ - id: '/server-function/nested', - path: '/server-function/nested', +const SsrNestedRoute = SsrNestedRouteImport.update({ + id: '/ssr/nested', + path: '/ssr/nested', + getParentRoute: () => rootRouteImport, +} as any) +const SsrStreamRoute = SsrStreamRouteImport.update({ + id: '/ssr/stream', + path: '/ssr/stream', 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 @@ -118,18 +118,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/ssr/stream': { - id: '/ssr/stream' - path: '/ssr/stream' - fullPath: '/ssr/stream' - preLoaderRoute: typeof SsrStreamRouteImport + '/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 } - '/ssr/nested': { - id: '/ssr/nested' - path: '/ssr/nested' - fullPath: '/ssr/nested' - preLoaderRoute: typeof SsrNestedRouteImport + '/server-function/nested': { + id: '/server-function/nested' + path: '/server-function/nested' + fullPath: '/server-function/nested' + preLoaderRoute: typeof ServerFunctionNestedRouteImport parentRoute: typeof rootRouteImport } '/ssr/data-only': { @@ -139,18 +139,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof SsrDataOnlyRouteImport parentRoute: typeof rootRouteImport } - '/server-function/nested': { - id: '/server-function/nested' - path: '/server-function/nested' - fullPath: '/server-function/nested' - preLoaderRoute: typeof ServerFunctionNestedRouteImport + '/ssr/nested': { + id: '/ssr/nested' + path: '/ssr/nested' + fullPath: '/ssr/nested' + preLoaderRoute: typeof SsrNestedRouteImport 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 + '/ssr/stream': { + id: '/ssr/stream' + path: '/ssr/stream' + fullPath: '/ssr/stream' + preLoaderRoute: typeof SsrStreamRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/solid-start/server-functions/package.json b/e2e/solid-start/server-functions/package.json index 116de774ed..4cf6d90a1f 100644 --- a/e2e/solid-start/server-functions/package.json +++ b/e2e/solid-start/server-functions/package.json @@ -26,7 +26,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", @@ -34,8 +34,9 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/server-functions/src/routeTree.gen.ts b/e2e/solid-start/server-functions/src/routeTree.gen.ts index 329a70726e..2d3b197b77 100644 --- a/e2e/solid-start/server-functions/src/routeTree.gen.ts +++ b/e2e/solid-start/server-functions/src/routeTree.gen.ts @@ -9,60 +9,65 @@ // 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 SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' -import { Route as StatusRouteImport } from './routes/status' -import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' -import { Route as ReturnNullRouteImport } from './routes/return-null' -import { Route as RawResponseRouteImport } from './routes/raw-response' -import { Route as MultipartRouteImport } from './routes/multipart' -import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' -import { Route as HeadersRouteImport } from './routes/headers' -import { Route as EnvOnlyRouteImport } from './routes/env-only' -import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' -import { Route as ConsistentRouteImport } from './routes/consistent' import { Route as IndexRouteImport } from './routes/index' -import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' -import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' -import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' -import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' -import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' -import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/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 ConsistentRouteImport } from './routes/consistent' +import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' +import { Route as EnvOnlyRouteImport } from './routes/env-only' +import { Route as HeadersRouteImport } from './routes/headers' +import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' +import { Route as MultipartRouteImport } from './routes/multipart' +import { Route as RawResponseRouteImport } from './routes/raw-response' +import { Route as ReturnNullRouteImport } from './routes/return-null' +import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' +import { Route as StatusRouteImport } from './routes/status' +import { Route as SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' import { Route as AbortSignalIndexRouteImport } from './routes/abort-signal/index' -import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' -import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' -import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' -import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' -import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' -import { Route as CookiesSetRouteImport } from './routes/cookies/set' import { Route as AbortSignalMethodRouteImport } from './routes/abort-signal/$method' +import { Route as CookiesIndexRouteImport } from './routes/cookies/index' +import { Route as CookiesSetRouteImport } from './routes/cookies/set' +import { Route as FactoryIndexRouteImport } from './routes/factory/index' +import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' +import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/index' +import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' +import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' +import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' +import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' +import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' +import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' +import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' +import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' +import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' +import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name' -const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ - id: '/submit-post-formdata', - path: '/submit-post-formdata', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const StatusRoute = StatusRouteImport.update({ - id: '/status', - path: '/status', +const ConsistentRoute = ConsistentRouteImport.update({ + id: '/consistent', + path: '/consistent', getParentRoute: () => rootRouteImport, } as any) -const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ - id: '/serialize-form-data', - path: '/serialize-form-data', +const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ + id: '/dead-code-preserve', + path: '/dead-code-preserve', getParentRoute: () => rootRouteImport, } as any) -const ReturnNullRoute = ReturnNullRouteImport.update({ - id: '/return-null', - path: '/return-null', +const EnvOnlyRoute = EnvOnlyRouteImport.update({ + id: '/env-only', + path: '/env-only', getParentRoute: () => rootRouteImport, } as any) -const RawResponseRoute = RawResponseRouteImport.update({ - id: '/raw-response', - path: '/raw-response', +const HeadersRoute = HeadersRouteImport.update({ + id: '/headers', + path: '/headers', + getParentRoute: () => rootRouteImport, +} as any) +const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ + id: '/isomorphic-fns', + path: '/isomorphic-fns', getParentRoute: () => rootRouteImport, } as any) const MultipartRoute = MultipartRouteImport.update({ @@ -70,59 +75,59 @@ const MultipartRoute = MultipartRouteImport.update({ path: '/multipart', getParentRoute: () => rootRouteImport, } as any) -const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ - id: '/isomorphic-fns', - path: '/isomorphic-fns', +const RawResponseRoute = RawResponseRouteImport.update({ + id: '/raw-response', + path: '/raw-response', getParentRoute: () => rootRouteImport, } as any) -const HeadersRoute = HeadersRouteImport.update({ - id: '/headers', - path: '/headers', +const ReturnNullRoute = ReturnNullRouteImport.update({ + id: '/return-null', + path: '/return-null', getParentRoute: () => rootRouteImport, } as any) -const EnvOnlyRoute = EnvOnlyRouteImport.update({ - id: '/env-only', - path: '/env-only', +const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ + id: '/serialize-form-data', + path: '/serialize-form-data', getParentRoute: () => rootRouteImport, } as any) -const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ - id: '/dead-code-preserve', - path: '/dead-code-preserve', +const StatusRoute = StatusRouteImport.update({ + id: '/status', + path: '/status', getParentRoute: () => rootRouteImport, } as any) -const ConsistentRoute = ConsistentRouteImport.update({ - id: '/consistent', - path: '/consistent', +const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ + id: '/submit-post-formdata', + path: '/submit-post-formdata', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const AbortSignalIndexRoute = AbortSignalIndexRouteImport.update({ + id: '/abort-signal/', + path: '/abort-signal/', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ - id: '/redirect-test/', - path: '/redirect-test/', +const AbortSignalMethodRoute = AbortSignalMethodRouteImport.update({ + id: '/abort-signal/$method', + path: '/abort-signal/$method', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ - id: '/redirect-test-ssr/', - path: '/redirect-test-ssr/', +const CookiesIndexRoute = CookiesIndexRouteImport.update({ + id: '/cookies/', + path: '/cookies/', getParentRoute: () => rootRouteImport, } as any) -const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ - id: '/primitives/', - path: '/primitives/', +const CookiesSetRoute = CookiesSetRouteImport.update({ + id: '/cookies/set', + path: '/cookies/set', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ - id: '/middleware/', - path: '/middleware/', +const FactoryIndexRoute = FactoryIndexRouteImport.update({ + id: '/factory/', + path: '/factory/', getParentRoute: () => rootRouteImport, } as any) -const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ - id: '/function-method/', - path: '/function-method/', +const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ + id: '/formdata-redirect/', + path: '/formdata-redirect/', getParentRoute: () => rootRouteImport, } as any) const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ @@ -130,29 +135,41 @@ const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ path: '/function-metadata/', getParentRoute: () => rootRouteImport, } as any) -const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ - id: '/formdata-redirect/', - path: '/formdata-redirect/', +const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ + id: '/function-method/', + path: '/function-method/', getParentRoute: () => rootRouteImport, } as any) -const FactoryIndexRoute = FactoryIndexRouteImport.update({ - id: '/factory/', - path: '/factory/', +const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ + id: '/middleware/', + path: '/middleware/', getParentRoute: () => rootRouteImport, } as any) -const CookiesIndexRoute = CookiesIndexRouteImport.update({ - id: '/cookies/', - path: '/cookies/', +const MiddlewareClientMiddlewareRouterRoute = + MiddlewareClientMiddlewareRouterRouteImport.update({ + id: '/middleware/client-middleware-router', + path: '/middleware/client-middleware-router', + getParentRoute: () => rootRouteImport, + } as any) +const MiddlewareRequestMiddlewareRoute = + MiddlewareRequestMiddlewareRouteImport.update({ + id: '/middleware/request-middleware', + path: '/middleware/request-middleware', + getParentRoute: () => rootRouteImport, + } as any) +const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ + id: '/middleware/send-serverFn', + path: '/middleware/send-serverFn', getParentRoute: () => rootRouteImport, } as any) -const AbortSignalIndexRoute = AbortSignalIndexRouteImport.update({ - id: '/abort-signal/', - path: '/abort-signal/', +const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ + id: '/primitives/', + path: '/primitives/', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ - id: '/redirect-test/target', - path: '/redirect-test/target', +const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ + id: '/redirect-test-ssr/', + path: '/redirect-test-ssr/', getParentRoute: () => rootRouteImport, } as any) const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ @@ -160,31 +177,14 @@ const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ path: '/redirect-test-ssr/target', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ - id: '/middleware/send-serverFn', - path: '/middleware/send-serverFn', - getParentRoute: () => rootRouteImport, -} as any) -const MiddlewareRequestMiddlewareRoute = - MiddlewareRequestMiddlewareRouteImport.update({ - id: '/middleware/request-middleware', - path: '/middleware/request-middleware', - getParentRoute: () => rootRouteImport, - } as any) -const MiddlewareClientMiddlewareRouterRoute = - MiddlewareClientMiddlewareRouterRouteImport.update({ - id: '/middleware/client-middleware-router', - path: '/middleware/client-middleware-router', - getParentRoute: () => rootRouteImport, - } as any) -const CookiesSetRoute = CookiesSetRouteImport.update({ - id: '/cookies/set', - path: '/cookies/set', +const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ + id: '/redirect-test/', + path: '/redirect-test/', getParentRoute: () => rootRouteImport, } as any) -const AbortSignalMethodRoute = AbortSignalMethodRouteImport.update({ - id: '/abort-signal/$method', - path: '/abort-signal/$method', +const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ + id: '/redirect-test/target', + path: '/redirect-test/target', getParentRoute: () => rootRouteImport, } as any) const FormdataRedirectTargetNameRoute = @@ -425,46 +425,39 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/submit-post-formdata': { - id: '/submit-post-formdata' - path: '/submit-post-formdata' - fullPath: '/submit-post-formdata' - preLoaderRoute: typeof SubmitPostFormdataRouteImport - parentRoute: typeof rootRouteImport - } - '/status': { - id: '/status' - path: '/status' - fullPath: '/status' - preLoaderRoute: typeof StatusRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/serialize-form-data': { - id: '/serialize-form-data' - path: '/serialize-form-data' - fullPath: '/serialize-form-data' - preLoaderRoute: typeof SerializeFormDataRouteImport + '/consistent': { + id: '/consistent' + path: '/consistent' + fullPath: '/consistent' + preLoaderRoute: typeof ConsistentRouteImport parentRoute: typeof rootRouteImport } - '/return-null': { - id: '/return-null' - path: '/return-null' - fullPath: '/return-null' - preLoaderRoute: typeof ReturnNullRouteImport + '/dead-code-preserve': { + id: '/dead-code-preserve' + path: '/dead-code-preserve' + fullPath: '/dead-code-preserve' + preLoaderRoute: typeof DeadCodePreserveRouteImport parentRoute: typeof rootRouteImport } - '/raw-response': { - id: '/raw-response' - path: '/raw-response' - fullPath: '/raw-response' - preLoaderRoute: typeof RawResponseRouteImport + '/env-only': { + id: '/env-only' + path: '/env-only' + fullPath: '/env-only' + preLoaderRoute: typeof EnvOnlyRouteImport parentRoute: typeof rootRouteImport } - '/multipart': { - id: '/multipart' - path: '/multipart' - fullPath: '/multipart' - preLoaderRoute: typeof MultipartRouteImport + '/headers': { + id: '/headers' + path: '/headers' + fullPath: '/headers' + preLoaderRoute: typeof HeadersRouteImport parentRoute: typeof rootRouteImport } '/isomorphic-fns': { @@ -474,81 +467,81 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IsomorphicFnsRouteImport parentRoute: typeof rootRouteImport } - '/headers': { - id: '/headers' - path: '/headers' - fullPath: '/headers' - preLoaderRoute: typeof HeadersRouteImport + '/multipart': { + id: '/multipart' + path: '/multipart' + fullPath: '/multipart' + preLoaderRoute: typeof MultipartRouteImport parentRoute: typeof rootRouteImport } - '/env-only': { - id: '/env-only' - path: '/env-only' - fullPath: '/env-only' - preLoaderRoute: typeof EnvOnlyRouteImport + '/raw-response': { + id: '/raw-response' + path: '/raw-response' + fullPath: '/raw-response' + preLoaderRoute: typeof RawResponseRouteImport parentRoute: typeof rootRouteImport } - '/dead-code-preserve': { - id: '/dead-code-preserve' - path: '/dead-code-preserve' - fullPath: '/dead-code-preserve' - preLoaderRoute: typeof DeadCodePreserveRouteImport + '/return-null': { + id: '/return-null' + path: '/return-null' + fullPath: '/return-null' + preLoaderRoute: typeof ReturnNullRouteImport parentRoute: typeof rootRouteImport } - '/consistent': { - id: '/consistent' - path: '/consistent' - fullPath: '/consistent' - preLoaderRoute: typeof ConsistentRouteImport + '/serialize-form-data': { + id: '/serialize-form-data' + path: '/serialize-form-data' + fullPath: '/serialize-form-data' + preLoaderRoute: typeof SerializeFormDataRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/status': { + id: '/status' + path: '/status' + fullPath: '/status' + preLoaderRoute: typeof StatusRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/': { - id: '/redirect-test/' - path: '/redirect-test' - fullPath: '/redirect-test/' - preLoaderRoute: typeof RedirectTestIndexRouteImport + '/submit-post-formdata': { + id: '/submit-post-formdata' + path: '/submit-post-formdata' + fullPath: '/submit-post-formdata' + preLoaderRoute: typeof SubmitPostFormdataRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/': { - id: '/redirect-test-ssr/' - path: '/redirect-test-ssr' - fullPath: '/redirect-test-ssr/' - preLoaderRoute: typeof RedirectTestSsrIndexRouteImport + '/abort-signal/': { + id: '/abort-signal/' + path: '/abort-signal' + fullPath: '/abort-signal/' + preLoaderRoute: typeof AbortSignalIndexRouteImport parentRoute: typeof rootRouteImport } - '/primitives/': { - id: '/primitives/' - path: '/primitives' - fullPath: '/primitives/' - preLoaderRoute: typeof PrimitivesIndexRouteImport + '/abort-signal/$method': { + id: '/abort-signal/$method' + path: '/abort-signal/$method' + fullPath: '/abort-signal/$method' + preLoaderRoute: typeof AbortSignalMethodRouteImport parentRoute: typeof rootRouteImport } - '/middleware/': { - id: '/middleware/' - path: '/middleware' - fullPath: '/middleware/' - preLoaderRoute: typeof MiddlewareIndexRouteImport + '/cookies/': { + id: '/cookies/' + path: '/cookies' + fullPath: '/cookies/' + preLoaderRoute: typeof CookiesIndexRouteImport parentRoute: typeof rootRouteImport } - '/function-method/': { - id: '/function-method/' - path: '/function-method' - fullPath: '/function-method/' - preLoaderRoute: typeof FunctionMethodIndexRouteImport + '/cookies/set': { + id: '/cookies/set' + path: '/cookies/set' + fullPath: '/cookies/set' + preLoaderRoute: typeof CookiesSetRouteImport parentRoute: typeof rootRouteImport } - '/function-metadata/': { - id: '/function-metadata/' - path: '/function-metadata' - fullPath: '/function-metadata/' - preLoaderRoute: typeof FunctionMetadataIndexRouteImport + '/factory/': { + id: '/factory/' + path: '/factory' + fullPath: '/factory/' + preLoaderRoute: typeof FactoryIndexRouteImport parentRoute: typeof rootRouteImport } '/formdata-redirect/': { @@ -558,39 +551,39 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof FormdataRedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/factory/': { - id: '/factory/' - path: '/factory' - fullPath: '/factory/' - preLoaderRoute: typeof FactoryIndexRouteImport + '/function-metadata/': { + id: '/function-metadata/' + path: '/function-metadata' + fullPath: '/function-metadata/' + preLoaderRoute: typeof FunctionMetadataIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/': { - id: '/cookies/' - path: '/cookies' - fullPath: '/cookies/' - preLoaderRoute: typeof CookiesIndexRouteImport + '/function-method/': { + id: '/function-method/' + path: '/function-method' + fullPath: '/function-method/' + preLoaderRoute: typeof FunctionMethodIndexRouteImport parentRoute: typeof rootRouteImport } - '/abort-signal/': { - id: '/abort-signal/' - path: '/abort-signal' - fullPath: '/abort-signal/' - preLoaderRoute: typeof AbortSignalIndexRouteImport + '/middleware/': { + id: '/middleware/' + path: '/middleware' + fullPath: '/middleware/' + preLoaderRoute: typeof MiddlewareIndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/target': { - id: '/redirect-test/target' - path: '/redirect-test/target' - fullPath: '/redirect-test/target' - preLoaderRoute: typeof RedirectTestTargetRouteImport + '/middleware/client-middleware-router': { + id: '/middleware/client-middleware-router' + path: '/middleware/client-middleware-router' + fullPath: '/middleware/client-middleware-router' + preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/target': { - id: '/redirect-test-ssr/target' - path: '/redirect-test-ssr/target' - fullPath: '/redirect-test-ssr/target' - preLoaderRoute: typeof RedirectTestSsrTargetRouteImport + '/middleware/request-middleware': { + id: '/middleware/request-middleware' + path: '/middleware/request-middleware' + fullPath: '/middleware/request-middleware' + preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport parentRoute: typeof rootRouteImport } '/middleware/send-serverFn': { @@ -600,32 +593,39 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof MiddlewareSendServerFnRouteImport parentRoute: typeof rootRouteImport } - '/middleware/request-middleware': { - id: '/middleware/request-middleware' - path: '/middleware/request-middleware' - fullPath: '/middleware/request-middleware' - preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport + '/primitives/': { + id: '/primitives/' + path: '/primitives' + fullPath: '/primitives/' + preLoaderRoute: typeof PrimitivesIndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware/client-middleware-router': { - id: '/middleware/client-middleware-router' - path: '/middleware/client-middleware-router' - fullPath: '/middleware/client-middleware-router' - preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport + '/redirect-test-ssr/': { + id: '/redirect-test-ssr/' + path: '/redirect-test-ssr' + fullPath: '/redirect-test-ssr/' + preLoaderRoute: typeof RedirectTestSsrIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/set': { - id: '/cookies/set' - path: '/cookies/set' - fullPath: '/cookies/set' - preLoaderRoute: typeof CookiesSetRouteImport + '/redirect-test-ssr/target': { + id: '/redirect-test-ssr/target' + path: '/redirect-test-ssr/target' + fullPath: '/redirect-test-ssr/target' + preLoaderRoute: typeof RedirectTestSsrTargetRouteImport parentRoute: typeof rootRouteImport } - '/abort-signal/$method': { - id: '/abort-signal/$method' - path: '/abort-signal/$method' - fullPath: '/abort-signal/$method' - preLoaderRoute: typeof AbortSignalMethodRouteImport + '/redirect-test/': { + id: '/redirect-test/' + path: '/redirect-test' + fullPath: '/redirect-test/' + preLoaderRoute: typeof RedirectTestIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/redirect-test/target': { + id: '/redirect-test/target' + path: '/redirect-test/target' + fullPath: '/redirect-test/target' + preLoaderRoute: typeof RedirectTestTargetRouteImport parentRoute: typeof rootRouteImport } '/formdata-redirect/target/$name': { diff --git a/e2e/solid-start/server-routes/package.json b/e2e/solid-start/server-routes/package.json index d0446b2037..9ce0e340cf 100644 --- a/e2e/solid-start/server-routes/package.json +++ b/e2e/solid-start/server-routes/package.json @@ -25,7 +25,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", @@ -33,8 +33,9 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/server-routes/src/routeTree.gen.ts b/e2e/solid-start/server-routes/src/routeTree.gen.ts index 87cd58b0ea..75cee36b01 100644 --- a/e2e/solid-start/server-routes/src/routeTree.gen.ts +++ b/e2e/solid-start/server-routes/src/routeTree.gen.ts @@ -9,16 +9,21 @@ // 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 MergeMiddlewareContextRouteImport } from './routes/merge-middleware-context' import { Route as MethodsRouteRouteImport } from './routes/methods/route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' +import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' import { Route as MethodsIndexRouteImport } from './routes/methods/index' import { Route as MethodsOnlyAnyRouteImport } from './routes/methods/only-any' -import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' -import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' import { Route as ApiParamsFooRouteRouteImport } from './routes/api/params/$foo/route' import { Route as ApiParamsFooBarRouteImport } from './routes/api/params/$foo/$bar' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const MergeMiddlewareContextRoute = MergeMiddlewareContextRouteImport.update({ id: '/merge-middleware-context', path: '/merge-middleware-context', @@ -29,9 +34,14 @@ const MethodsRouteRoute = MethodsRouteRouteImport.update({ path: '/methods', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ + id: '/api/middleware-context', + path: '/api/middleware-context', + getParentRoute: () => rootRouteImport, +} as any) +const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ + id: '/api/only-any', + path: '/api/only-any', getParentRoute: () => rootRouteImport, } as any) const MethodsIndexRoute = MethodsIndexRouteImport.update({ @@ -44,16 +54,6 @@ const MethodsOnlyAnyRoute = MethodsOnlyAnyRouteImport.update({ path: '/only-any', getParentRoute: () => MethodsRouteRoute, } as any) -const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ - id: '/api/only-any', - path: '/api/only-any', - getParentRoute: () => rootRouteImport, -} as any) -const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ - id: '/api/middleware-context', - path: '/api/middleware-context', - getParentRoute: () => rootRouteImport, -} as any) const ApiParamsFooRouteRoute = ApiParamsFooRouteRouteImport.update({ id: '/api/params/$foo', path: '/api/params/$foo', @@ -144,6 +144,13 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/merge-middleware-context': { id: '/merge-middleware-context' path: '/merge-middleware-context' @@ -158,11 +165,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof MethodsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/api/middleware-context': { + id: '/api/middleware-context' + path: '/api/middleware-context' + fullPath: '/api/middleware-context' + preLoaderRoute: typeof ApiMiddlewareContextRouteImport + parentRoute: typeof rootRouteImport + } + '/api/only-any': { + id: '/api/only-any' + path: '/api/only-any' + fullPath: '/api/only-any' + preLoaderRoute: typeof ApiOnlyAnyRouteImport parentRoute: typeof rootRouteImport } '/methods/': { @@ -179,20 +193,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof MethodsOnlyAnyRouteImport parentRoute: typeof MethodsRouteRoute } - '/api/only-any': { - id: '/api/only-any' - path: '/api/only-any' - fullPath: '/api/only-any' - preLoaderRoute: typeof ApiOnlyAnyRouteImport - parentRoute: typeof rootRouteImport - } - '/api/middleware-context': { - id: '/api/middleware-context' - path: '/api/middleware-context' - fullPath: '/api/middleware-context' - preLoaderRoute: typeof ApiMiddlewareContextRouteImport - parentRoute: typeof rootRouteImport - } '/api/params/$foo': { id: '/api/params/$foo' path: '/api/params/$foo' diff --git a/e2e/solid-start/solid-query-layout-suspense/package.json b/e2e/solid-start/solid-query-layout-suspense/package.json index 31eec11365..dfca78e7d2 100644 --- a/e2e/solid-start/solid-query-layout-suspense/package.json +++ b/e2e/solid-start/solid-query-layout-suspense/package.json @@ -17,10 +17,11 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/e2e/solid-start/solid-query-layout-suspense/src/routeTree.gen.ts b/e2e/solid-start/solid-query-layout-suspense/src/routeTree.gen.ts index e120a08bd4..4da4991cd2 100644 --- a/e2e/solid-start/solid-query-layout-suspense/src/routeTree.gen.ts +++ b/e2e/solid-start/solid-query-layout-suspense/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 LayoutRouteImport } from './routes/layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/layout' import { Route as LayoutPage2RouteImport } from './routes/layout.page2' -const LayoutRoute = LayoutRouteImport.update({ - id: '/layout', - path: '/layout', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const LayoutRoute = LayoutRouteImport.update({ + id: '/layout', + path: '/layout', + getParentRoute: () => rootRouteImport, +} as any) const LayoutPage2Route = LayoutPage2RouteImport.update({ id: '/page2', path: '/page2', @@ -60,13 +60,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/layout': { - id: '/layout' - path: '/layout' - fullPath: '/layout' - preLoaderRoute: typeof LayoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +67,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/layout': { + id: '/layout' + path: '/layout' + fullPath: '/layout' + preLoaderRoute: typeof LayoutRouteImport + parentRoute: typeof rootRouteImport + } '/layout/page2': { id: '/layout/page2' path: '/page2' diff --git a/e2e/solid-start/spa-mode/package.json b/e2e/solid-start/spa-mode/package.json index 5a8e53b936..6b998be3a9 100644 --- a/e2e/solid-start/spa-mode/package.json +++ b/e2e/solid-start/spa-mode/package.json @@ -23,7 +23,8 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/e2e/solid-start/spa-mode/src/routeTree.gen.ts b/e2e/solid-start/spa-mode/src/routeTree.gen.ts index 423535df71..e65284f0e5 100644 --- a/e2e/solid-start/spa-mode/src/routeTree.gen.ts +++ b/e2e/solid-start/spa-mode/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', @@ -60,13 +60,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +67,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' diff --git a/e2e/solid-start/start-manifest/package.json b/e2e/solid-start/start-manifest/package.json index 2947474479..d124e6a2de 100644 --- a/e2e/solid-start/start-manifest/package.json +++ b/e2e/solid-start/start-manifest/package.json @@ -16,11 +16,12 @@ "solid-js": "2.0.0-beta.29" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" }, diff --git a/e2e/solid-start/start-manifest/src/routeTree.gen.ts b/e2e/solid-start/start-manifest/src/routeTree.gen.ts index 982f80b150..5e0ea176d5 100644 --- a/e2e/solid-start/start-manifest/src/routeTree.gen.ts +++ b/e2e/solid-start/start-manifest/src/routeTree.gen.ts @@ -9,54 +9,49 @@ // 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 SharedCRouteImport } from './routes/shared-c' -import { Route as SharedBRouteImport } from './routes/shared-b' -import { Route as SharedARouteImport } from './routes/shared-a' -import { Route as R6RouteImport } from './routes/r6' -import { Route as R5RouteImport } from './routes/r5' -import { Route as R4RouteImport } from './routes/r4' -import { Route as R3RouteImport } from './routes/r3' -import { Route as R2RouteImport } from './routes/r2' -import { Route as R1RouteImport } from './routes/r1' -import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' -import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' -import { Route as BRouteImport } from './routes/b' -import { Route as ARouteImport } from './routes/a' import { Route as IndexRouteImport } from './routes/index' +import { Route as ARouteImport } from './routes/a' +import { Route as BRouteImport } from './routes/b' +import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' +import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' +import { Route as R1RouteImport } from './routes/r1' +import { Route as R2RouteImport } from './routes/r2' +import { Route as R3RouteImport } from './routes/r3' +import { Route as R4RouteImport } from './routes/r4' +import { Route as R5RouteImport } from './routes/r5' +import { Route as R6RouteImport } from './routes/r6' +import { Route as SharedARouteImport } from './routes/shared-a' +import { Route as SharedBRouteImport } from './routes/shared-b' +import { Route as SharedCRouteImport } from './routes/shared-c' -const SharedCRoute = SharedCRouteImport.update({ - id: '/shared-c', - path: '/shared-c', - getParentRoute: () => rootRouteImport, -} as any) -const SharedBRoute = SharedBRouteImport.update({ - id: '/shared-b', - path: '/shared-b', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SharedARoute = SharedARouteImport.update({ - id: '/shared-a', - path: '/shared-a', +const ARoute = ARouteImport.update({ + id: '/a', + path: '/a', getParentRoute: () => rootRouteImport, } as any) -const R6Route = R6RouteImport.update({ - id: '/r6', - path: '/r6', +const BRoute = BRouteImport.update({ + id: '/b', + path: '/b', getParentRoute: () => rootRouteImport, } as any) -const R5Route = R5RouteImport.update({ - id: '/r5', - path: '/r5', +const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ + id: '/lazy-css-lazy', + path: '/lazy-css-lazy', getParentRoute: () => rootRouteImport, } as any) -const R4Route = R4RouteImport.update({ - id: '/r4', - path: '/r4', +const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ + id: '/lazy-css-static', + path: '/lazy-css-static', getParentRoute: () => rootRouteImport, } as any) -const R3Route = R3RouteImport.update({ - id: '/r3', - path: '/r3', +const R1Route = R1RouteImport.update({ + id: '/r1', + path: '/r1', getParentRoute: () => rootRouteImport, } as any) const R2Route = R2RouteImport.update({ @@ -64,34 +59,39 @@ const R2Route = R2RouteImport.update({ path: '/r2', getParentRoute: () => rootRouteImport, } as any) -const R1Route = R1RouteImport.update({ - id: '/r1', - path: '/r1', +const R3Route = R3RouteImport.update({ + id: '/r3', + path: '/r3', getParentRoute: () => rootRouteImport, } as any) -const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ - id: '/lazy-css-static', - path: '/lazy-css-static', +const R4Route = R4RouteImport.update({ + id: '/r4', + path: '/r4', getParentRoute: () => rootRouteImport, } as any) -const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ - id: '/lazy-css-lazy', - path: '/lazy-css-lazy', +const R5Route = R5RouteImport.update({ + id: '/r5', + path: '/r5', getParentRoute: () => rootRouteImport, } as any) -const BRoute = BRouteImport.update({ - id: '/b', - path: '/b', +const R6Route = R6RouteImport.update({ + id: '/r6', + path: '/r6', getParentRoute: () => rootRouteImport, } as any) -const ARoute = ARouteImport.update({ - id: '/a', - path: '/a', +const SharedARoute = SharedARouteImport.update({ + id: '/shared-a', + path: '/shared-a', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SharedBRoute = SharedBRouteImport.update({ + id: '/shared-b', + path: '/shared-b', + getParentRoute: () => rootRouteImport, +} as any) +const SharedCRoute = SharedCRouteImport.update({ + id: '/shared-c', + path: '/shared-c', getParentRoute: () => rootRouteImport, } as any) @@ -214,53 +214,46 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/shared-c': { - id: '/shared-c' - path: '/shared-c' - fullPath: '/shared-c' - preLoaderRoute: typeof SharedCRouteImport - parentRoute: typeof rootRouteImport - } - '/shared-b': { - id: '/shared-b' - path: '/shared-b' - fullPath: '/shared-b' - preLoaderRoute: typeof SharedBRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/shared-a': { - id: '/shared-a' - path: '/shared-a' - fullPath: '/shared-a' - preLoaderRoute: typeof SharedARouteImport + '/a': { + id: '/a' + path: '/a' + fullPath: '/a' + preLoaderRoute: typeof ARouteImport parentRoute: typeof rootRouteImport } - '/r6': { - id: '/r6' - path: '/r6' - fullPath: '/r6' - preLoaderRoute: typeof R6RouteImport + '/b': { + id: '/b' + path: '/b' + fullPath: '/b' + preLoaderRoute: typeof BRouteImport parentRoute: typeof rootRouteImport } - '/r5': { - id: '/r5' - path: '/r5' - fullPath: '/r5' - preLoaderRoute: typeof R5RouteImport + '/lazy-css-lazy': { + id: '/lazy-css-lazy' + path: '/lazy-css-lazy' + fullPath: '/lazy-css-lazy' + preLoaderRoute: typeof LazyCssLazyRouteImport parentRoute: typeof rootRouteImport } - '/r4': { - id: '/r4' - path: '/r4' - fullPath: '/r4' - preLoaderRoute: typeof R4RouteImport + '/lazy-css-static': { + id: '/lazy-css-static' + path: '/lazy-css-static' + fullPath: '/lazy-css-static' + preLoaderRoute: typeof LazyCssStaticRouteImport parentRoute: typeof rootRouteImport } - '/r3': { - id: '/r3' - path: '/r3' - fullPath: '/r3' - preLoaderRoute: typeof R3RouteImport + '/r1': { + id: '/r1' + path: '/r1' + fullPath: '/r1' + preLoaderRoute: typeof R1RouteImport parentRoute: typeof rootRouteImport } '/r2': { @@ -270,46 +263,53 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof R2RouteImport parentRoute: typeof rootRouteImport } - '/r1': { - id: '/r1' - path: '/r1' - fullPath: '/r1' - preLoaderRoute: typeof R1RouteImport + '/r3': { + id: '/r3' + path: '/r3' + fullPath: '/r3' + preLoaderRoute: typeof R3RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-static': { - id: '/lazy-css-static' - path: '/lazy-css-static' - fullPath: '/lazy-css-static' - preLoaderRoute: typeof LazyCssStaticRouteImport + '/r4': { + id: '/r4' + path: '/r4' + fullPath: '/r4' + preLoaderRoute: typeof R4RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-lazy': { - id: '/lazy-css-lazy' - path: '/lazy-css-lazy' - fullPath: '/lazy-css-lazy' - preLoaderRoute: typeof LazyCssLazyRouteImport + '/r5': { + id: '/r5' + path: '/r5' + fullPath: '/r5' + preLoaderRoute: typeof R5RouteImport parentRoute: typeof rootRouteImport } - '/b': { - id: '/b' - path: '/b' - fullPath: '/b' - preLoaderRoute: typeof BRouteImport + '/r6': { + id: '/r6' + path: '/r6' + fullPath: '/r6' + preLoaderRoute: typeof R6RouteImport parentRoute: typeof rootRouteImport } - '/a': { - id: '/a' - path: '/a' - fullPath: '/a' - preLoaderRoute: typeof ARouteImport + '/shared-a': { + id: '/shared-a' + path: '/shared-a' + fullPath: '/shared-a' + preLoaderRoute: typeof SharedARouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/shared-b': { + id: '/shared-b' + path: '/shared-b' + fullPath: '/shared-b' + preLoaderRoute: typeof SharedBRouteImport + parentRoute: typeof rootRouteImport + } + '/shared-c': { + id: '/shared-c' + path: '/shared-c' + fullPath: '/shared-c' + preLoaderRoute: typeof SharedCRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/solid-start/virtual-routes/package.json b/e2e/solid-start/virtual-routes/package.json index b62137680c..f3155fd62e 100644 --- a/e2e/solid-start/virtual-routes/package.json +++ b/e2e/solid-start/virtual-routes/package.json @@ -23,15 +23,16 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/virtual-routes/src/routeTree.gen.ts b/e2e/solid-start/virtual-routes/src/routeTree.gen.ts index 810468171a..39172d43b4 100644 --- a/e2e/solid-start/virtual-routes/src/routeTree.gen.ts +++ b/e2e/solid-start/virtual-routes/src/routeTree.gen.ts @@ -9,83 +9,83 @@ // 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 pipeRouteImport } from './routes/pipe' -import { Route as postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' +import { Route as pipeRouteImport } from './routes/pipe' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const pipeRoute = pipeRouteImport.update({ - id: '/special|pipe', - path: '/special|pipe', - getParentRoute: () => rootRouteImport, -} as any) -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, +const pipeRoute = pipeRouteImport.update({ + id: '/special|pipe', + path: '/special|pipe', + getParentRoute: () => rootRouteImport, } as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -179,18 +179,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/special|pipe': { - id: '/special|pipe' - path: '/special|pipe' - fullPath: '/special|pipe' - preLoaderRoute: typeof pipeRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -200,19 +193,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute + '/special|pipe': { + id: '/special|pipe' + path: '/special|pipe' + fullPath: '/special|pipe' + preLoaderRoute: typeof pipeRouteImport + parentRoute: typeof rootRouteImport } '/_first/_second-layout': { id: '/_first/_second-layout' @@ -221,6 +214,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -228,12 +228,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + parentRoute: typeof postsPostsRoute } '/classic/hello/': { id: '/classic/hello/' @@ -242,13 +242,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -256,12 +249,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } '/_first/_second-layout/route-without-file/layout-a': { id: '/_first/_second-layout/route-without-file/layout-a' @@ -270,6 +263,13 @@ declare module '@tanstack/solid-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/e2e/solid-start/website/package.json b/e2e/solid-start/website/package.json index 5d22864783..861070f5ea 100644 --- a/e2e/solid-start/website/package.json +++ b/e2e/solid-start/website/package.json @@ -22,14 +22,15 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite-plugin-solid": "^3.0.0-next.21", - "vite": "^8.0.14" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "^8.0.14", + "vite-plugin-solid": "^3.0.0-next.21" } } diff --git a/e2e/solid-start/website/src/routeTree.gen.ts b/e2e/solid-start/website/src/routeTree.gen.ts index c37e78d2f1..fcc677e13e 100644 --- a/e2e/solid-start/website/src/routeTree.gen.ts +++ b/e2e/solid-start/website/src/routeTree.gen.ts @@ -10,11 +10,11 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LibraryRouteImport } from './routes/_library' -import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as ProjectIndexRouteImport } from './routes/$project.index' +import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as LibraryProjectRouteImport } from './routes/_library.$project' -import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.index' import { Route as ProjectVersionDocsIndexRouteImport } from './routes/$project.$version.docs.index' +import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.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 ProjectVersionDocsFrameworkFrameworkSplatRouteImport } from './routes/$project.$version.docs.framework.$framework.$' @@ -24,32 +24,32 @@ const LibraryRoute = LibraryRouteImport.update({ id: '/_library', getParentRoute: () => rootRouteImport, } as any) -const LibraryIndexRoute = LibraryIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => LibraryRoute, -} as any) const ProjectIndexRoute = ProjectIndexRouteImport.update({ id: '/$project/', path: '/$project/', getParentRoute: () => rootRouteImport, } as any) +const LibraryIndexRoute = LibraryIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LibraryRoute, +} as any) const LibraryProjectRoute = LibraryProjectRouteImport.update({ id: '/$project', path: '/$project', getParentRoute: () => LibraryRoute, } as any) +const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ + id: '/$project/$version/docs/', + path: '/$project/$version/docs/', + getParentRoute: () => rootRouteImport, +} as any) const LibraryProjectVersionIndexRoute = LibraryProjectVersionIndexRouteImport.update({ id: '/$version/', path: '/$version/', getParentRoute: () => LibraryProjectRoute, } as any) -const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ - id: '/$project/$version/docs/', - path: '/$project/$version/docs/', - getParentRoute: () => rootRouteImport, -} as any) const ProjectVersionDocsFrameworkFrameworkRoute = ProjectVersionDocsFrameworkFrameworkRouteImport.update({ id: '/$project/$version/docs/framework/$framework', @@ -159,13 +159,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LibraryRouteImport parentRoute: typeof rootRouteImport } - '/_library/': { - id: '/_library/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof LibraryIndexRouteImport - parentRoute: typeof LibraryRoute - } '/$project/': { id: '/$project/' path: '/$project' @@ -173,6 +166,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ProjectIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/': { + id: '/_library/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof LibraryIndexRouteImport + parentRoute: typeof LibraryRoute + } '/_library/$project': { id: '/_library/$project' path: '/$project' @@ -180,13 +180,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LibraryProjectRouteImport parentRoute: typeof LibraryRoute } - '/_library/$project/$version/': { - id: '/_library/$project/$version/' - path: '/$version' - fullPath: '/$project/$version/' - preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport - parentRoute: typeof LibraryProjectRoute - } '/$project/$version/docs/': { id: '/$project/$version/docs/' path: '/$project/$version/docs' @@ -194,6 +187,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ProjectVersionDocsIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/$project/$version/': { + id: '/_library/$project/$version/' + path: '/$version' + fullPath: '/$project/$version/' + preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport + parentRoute: typeof LibraryProjectRoute + } '/$project/$version/docs/framework/$framework': { id: '/$project/$version/docs/framework/$framework' path: '/$project/$version/docs/framework/$framework' diff --git a/e2e/vue-router/basepath-file-based/package.json b/e2e/vue-router/basepath-file-based/package.json index 4e3deac1c7..c9e06e47b3 100644 --- a/e2e/vue-router/basepath-file-based/package.json +++ b/e2e/vue-router/basepath-file-based/package.json @@ -22,12 +22,12 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts b/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts index 82b561baf1..75b8119285 100644 --- a/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts +++ b/e2e/vue-router/basepath-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/vue-router/basic-esbuild-file-based/package.json b/e2e/vue-router/basic-esbuild-file-based/package.json index e5422cd436..fb0109db1a 100644 --- a/e2e/vue-router/basic-esbuild-file-based/package.json +++ b/e2e/vue-router/basic-esbuild-file-based/package.json @@ -20,15 +20,16 @@ }, "devDependencies": { "@eslint/js": "^9.36.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@typescript-eslint/eslint-plugin": "^8.44.1", "@typescript-eslint/parser": "^8.44.1", "esbuild": "^0.27.4", "esbuild-plugin-vue3": "^0.5.1", "eslint-plugin-vue": "^9.33.0", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vue-eslint-parser": "^9.4.3", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-file-based-jsx/package.json b/e2e/vue-router/basic-file-based-jsx/package.json index b029b1ab4f..ceb7352a88 100644 --- a/e2e/vue-router/basic-file-based-jsx/package.json +++ b/e2e/vue-router/basic-file-based-jsx/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@eslint/js": "^9.36.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@typescript-eslint/eslint-plugin": "^8.44.1", "@typescript-eslint/parser": "^8.44.1", @@ -32,6 +32,6 @@ "typescript": "~5.8.3", "vite": "^8.0.14", "vue-eslint-parser": "^9.4.3", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-file-based-jsx/src/routeTree.gen.ts b/e2e/vue-router/basic-file-based-jsx/src/routeTree.gen.ts index f5cd519d7f..3922025c82 100644 --- a/e2e/vue-router/basic-file-based-jsx/src/routeTree.gen.ts +++ b/e2e/vue-router/basic-file-based-jsx/src/routeTree.gen.ts @@ -11,52 +11,45 @@ import { lazyRouteComponent } from '@tanstack/vue-router' import { Route as rootRouteImport } from './routes/__root' -import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' -import { Route as SfcComponentRouteImport } from './routes/sfcComponent' -import { Route as RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as SfcComponentRouteImport } from './routes/sfcComponent' +import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -const Char45824Char54620Char48124Char44397Route = - Char45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any) -const SfcComponentRoute = SfcComponentRouteImport.update({ - id: '/sfcComponent', - path: '/sfcComponent', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/sfcComponent.component.vue'), - 'default', - ), -}) -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +} as any) +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', + getParentRoute: () => rootRouteImport, +} as any) +const EditingBRoute = EditingBRouteImport.update({ + id: '/editing-b', + path: '/editing-b', getParentRoute: () => rootRouteImport, } as any) const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ @@ -64,25 +57,56 @@ const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any) -const EditingBRoute = EditingBRouteImport.update({ - id: '/editing-b', - path: '/editing-b', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const SfcComponentRoute = SfcComponentRouteImport.update({ + id: '/sfcComponent', + path: '/sfcComponent', + getParentRoute: () => rootRouteImport, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/sfcComponent.component.vue'), + 'default', + ), +}) +const Char45824Char54620Char48124Char44397Route = + Char45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => rootRouteImport, + } as any) +const anotherGroupOnlyrouteinsideRoute = + anotherGroupOnlyrouteinsideRouteImport.update({ + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', + getParentRoute: () => rootRouteImport, + } as any) +const groupLayoutRoute = groupLayoutRouteImport.update({ + id: '/(group)/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const groupInsideRoute = groupInsideRouteImport.update({ + id: '/(group)/inside', + path: '/inside', + getParentRoute: () => rootRouteImport, +} as any) +const groupLazyinsideRoute = groupLazyinsideRouteImport.update({ + id: '/(group)/lazyinside', + path: '/lazyinside', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -93,55 +117,31 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, -} as any) -const groupLazyinsideRoute = groupLazyinsideRouteImport.update({ - id: '/(group)/lazyinside', - path: '/lazyinside', - getParentRoute: () => rootRouteImport, -} as any) -const groupInsideRoute = groupInsideRouteImport.update({ - id: '/(group)/inside', - path: '/inside', - getParentRoute: () => rootRouteImport, +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, } as any) -const groupLayoutRoute = groupLayoutRouteImport.update({ - id: '/(group)/_layout', +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', getParentRoute: () => rootRouteImport, } as any) -const anotherGroupOnlyrouteinsideRoute = - anotherGroupOnlyrouteinsideRouteImport.update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', - getParentRoute: () => rootRouteImport, - } as any) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', getParentRoute: () => rootRouteImport, } as any) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -291,32 +291,32 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/sfcComponent': { - id: '/sfcComponent' - path: '/sfcComponent' - fullPath: '/sfcComponent' - preLoaderRoute: typeof SfcComponentRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/editing-b': { + id: '/editing-b' + path: '/editing-b' + fullPath: '/editing-b' + preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } '/notRemountDeps': { @@ -326,34 +326,69 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/editing-b': { - id: '/editing-b' - path: '/editing-b' - fullPath: '/editing-b' - preLoaderRoute: typeof EditingBRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' + '/sfcComponent': { + id: '/sfcComponent' + path: '/sfcComponent' + fullPath: '/sfcComponent' + preLoaderRoute: typeof SfcComponentRouteImport + parentRoute: typeof rootRouteImport + } + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + parentRoute: typeof rootRouteImport + } + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/(group)/_layout': { + id: '/(group)/_layout' path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport parentRoute: typeof rootRouteImport } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -368,47 +403,26 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport - parentRoute: typeof rootRouteImport + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -417,27 +431,13 @@ declare module '@tanstack/vue-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 - } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } } } diff --git a/e2e/vue-router/basic-file-based-sfc/package.json b/e2e/vue-router/basic-file-based-sfc/package.json index 369a0b1fdc..69e7338500 100644 --- a/e2e/vue-router/basic-file-based-sfc/package.json +++ b/e2e/vue-router/basic-file-based-sfc/package.json @@ -21,12 +21,12 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-file-based-sfc/src/routeTree.gen.ts b/e2e/vue-router/basic-file-based-sfc/src/routeTree.gen.ts index fa00eb1ce0..09d62b0706 100644 --- a/e2e/vue-router/basic-file-based-sfc/src/routeTree.gen.ts +++ b/e2e/vue-router/basic-file-based-sfc/src/routeTree.gen.ts @@ -11,65 +11,53 @@ import { lazyRouteComponent } from '@tanstack/vue-router' import { Route as rootRouteImport } from './routes/__root' -import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' -import { Route as RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -const Char45824Char54620Char48124Char44397Route = - Char45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any).update({ - component: lazyRouteComponent( - () => import('./routes/대한민국.component.vue'), - 'default', - ), - }) -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/remountDeps.component.vue'), + () => import('./routes/index.component.vue'), 'default', ), }) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/posts.component.vue'), + () => import('./routes/_layout.component.vue'), 'default', ), }) -const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ - id: '/notRemountDeps', - path: '/notRemountDeps', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/notRemountDeps.component.vue'), + () => import('./routes/editing-a.component.vue'), 'default', ), }) @@ -83,89 +71,56 @@ const EditingBRoute = EditingBRouteImport.update({ 'default', ), }) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ + id: '/notRemountDeps', + path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/editing-a.component.vue'), + () => import('./routes/notRemountDeps.component.vue'), 'default', ), }) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout.component.vue'), + () => import('./routes/posts.component.vue'), 'default', ), }) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/index.component.vue'), - 'default', - ), -}) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/posts.index.component.vue'), - 'default', - ), -}) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/posts.$postId.component.vue'), - 'default', - ), - errorComponent: lazyRouteComponent( - () => import('./routes/posts.$postId.errorComponent.vue'), - 'default', - ), -}) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2.component.vue'), + () => import('./routes/remountDeps.component.vue'), 'default', ), }) -const groupLazyinsideRoute = groupLazyinsideRouteImport - .update({ - id: '/(group)/lazyinside', - path: '/lazyinside', +const Char45824Char54620Char48124Char44397Route = + Char45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', getParentRoute: () => rootRouteImport, - } as any) - .update({ + } as any).update({ component: lazyRouteComponent( - () => import('./routes/(group)/lazyinside.component.vue'), + () => import('./routes/대한민국.component.vue'), 'default', ), }) -const groupInsideRoute = groupInsideRouteImport +const anotherGroupOnlyrouteinsideRoute = anotherGroupOnlyrouteinsideRouteImport .update({ - id: '/(group)/inside', - path: '/inside', + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/inside.component.vue'), + () => import('./routes/(another-group)/onlyrouteinside.component.vue'), 'default', ), }) @@ -180,72 +135,117 @@ const groupLayoutRoute = groupLayoutRouteImport 'default', ), }) -const anotherGroupOnlyrouteinsideRoute = anotherGroupOnlyrouteinsideRouteImport +const groupInsideRoute = groupInsideRouteImport .update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', + id: '/(group)/inside', + path: '/inside', getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(another-group)/onlyrouteinside.component.vue'), + () => import('./routes/(group)/inside.component.vue'), 'default', ), }) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', - getParentRoute: () => rootRouteImport, +const groupLazyinsideRoute = groupLazyinsideRouteImport + .update({ + id: '/(group)/lazyinside', + path: '/lazyinside', + getParentRoute: () => rootRouteImport, + } as any) + .update({ + component: lazyRouteComponent( + () => import('./routes/(group)/lazyinside.component.vue'), + 'default', + ), + }) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/posts_.$postId.edit.component.vue'), + () => import('./routes/_layout/_layout-2.component.vue'), 'default', ), }) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2/layout-b.component.vue'), + () => import('./routes/posts.index.component.vue'), 'default', ), }) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2/layout-a.component.vue'), + () => import('./routes/posts.$postId.component.vue'), + 'default', + ), + errorComponent: lazyRouteComponent( + () => import('./routes/posts.$postId.errorComponent.vue'), 'default', ), }) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport .update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', - getParentRoute: () => rootRouteImport, + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/subfolder/inside.component.vue'), + () => import('./routes/(group)/_layout.insidelayout.component.vue'), 'default', ), }) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport .update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', + getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/_layout.insidelayout.component.vue'), + () => import('./routes/(group)/subfolder/inside.component.vue'), 'default', ), }) +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/_layout/_layout-2/layout-a.component.vue'), + 'default', + ), +}) +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/_layout/_layout-2/layout-b.component.vue'), + 'default', + ), +}) +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', + getParentRoute: () => rootRouteImport, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/posts_.$postId.edit.component.vue'), + 'default', + ), +}) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -388,25 +388,32 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport + parentRoute: typeof rootRouteImport + } + '/editing-b': { + id: '/editing-b' + path: '/editing-b' + fullPath: '/editing-b' + preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } '/notRemountDeps': { @@ -416,34 +423,62 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/editing-b': { - id: '/editing-b' - path: '/editing-b' - fullPath: '/editing-b' - preLoaderRoute: typeof EditingBRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + parentRoute: typeof rootRouteImport + } + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/(group)/_layout': { + id: '/(group)/_layout' path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport parentRoute: typeof rootRouteImport } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -458,47 +493,26 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport - parentRoute: typeof rootRouteImport + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -507,27 +521,13 @@ declare module '@tanstack/vue-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 - } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } } } diff --git a/e2e/vue-router/basic-scroll-restoration/package.json b/e2e/vue-router/basic-scroll-restoration/package.json index daa2519eed..3a3a7c375e 100644 --- a/e2e/vue-router/basic-scroll-restoration/package.json +++ b/e2e/vue-router/basic-scroll-restoration/package.json @@ -20,12 +20,12 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "vite": "^8.0.14", - "vue-tsc": "^3.1.5", + "vue-tsc": "^3.3.8", "typescript": "~5.9.0" } } diff --git a/e2e/vue-router/basic-virtual-file-based/package.json b/e2e/vue-router/basic-virtual-file-based/package.json index 946addb3bf..567999f255 100644 --- a/e2e/vue-router/basic-virtual-file-based/package.json +++ b/e2e/vue-router/basic-virtual-file-based/package.json @@ -23,12 +23,12 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-virtual-file-based/src/routeTree.gen.ts b/e2e/vue-router/basic-virtual-file-based/src/routeTree.gen.ts index 3798afaa01..1bae3d69a1 100644 --- a/e2e/vue-router/basic-virtual-file-based/src/routeTree.gen.ts +++ b/e2e/vue-router/basic-virtual-file-based/src/routeTree.gen.ts @@ -9,76 +9,76 @@ // 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 postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' -import { Route as aRouteImport } from './routes/a' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, -} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, +} as any) +const aRoute = aRouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) +const bRoute = bRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, -} as any) -const aRoute = aRouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) export interface FileRoutesByFullPath { @@ -166,11 +166,11 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -180,20 +180,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute - } '/_first/_second': { id: '/_first/_second' path: '' @@ -201,6 +194,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -208,12 +208,26 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + 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/' @@ -222,13 +236,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -236,19 +243,12 @@ declare module '@tanstack/vue-router' { 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 + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } } } diff --git a/e2e/vue-router/basic-virtual-named-export-config-file-based/package.json b/e2e/vue-router/basic-virtual-named-export-config-file-based/package.json index f73d3ad8b2..66f1b47955 100644 --- a/e2e/vue-router/basic-virtual-named-export-config-file-based/package.json +++ b/e2e/vue-router/basic-virtual-named-export-config-file-based/package.json @@ -23,12 +23,12 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-vue-query-file-based/package.json b/e2e/vue-router/basic-vue-query-file-based/package.json index 75395b20f8..772e630366 100644 --- a/e2e/vue-router/basic-vue-query-file-based/package.json +++ b/e2e/vue-router/basic-vue-query-file-based/package.json @@ -23,12 +23,12 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts b/e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts index 9dd7ada796..0af0e21dbd 100644 --- a/e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts +++ b/e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts @@ -9,29 +9,33 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -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 LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,20 +46,16 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -114,11 +114,11 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -128,13 +128,20 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -149,12 +156,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/_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' @@ -163,13 +170,6 @@ declare module '@tanstack/vue-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/vue-router/basic-vue-query/package.json b/e2e/vue-router/basic-vue-query/package.json index be46a064c6..b1bc234595 100644 --- a/e2e/vue-router/basic-vue-query/package.json +++ b/e2e/vue-router/basic-vue-query/package.json @@ -21,12 +21,12 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/basic/package.json b/e2e/vue-router/basic/package.json index c779a572e9..00ec22e5b4 100644 --- a/e2e/vue-router/basic/package.json +++ b/e2e/vue-router/basic/package.json @@ -19,11 +19,11 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/generator-cli-only/package.json b/e2e/vue-router/generator-cli-only/package.json index 14cdafcd48..6f605caa76 100644 --- a/e2e/vue-router/generator-cli-only/package.json +++ b/e2e/vue-router/generator-cli-only/package.json @@ -20,12 +20,12 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/generator-cli-only/src/routeTree.gen.ts b/e2e/vue-router/generator-cli-only/src/routeTree.gen.ts index c769c841f0..decb53754c 100644 --- a/e2e/vue-router/generator-cli-only/src/routeTree.gen.ts +++ b/e2e/vue-router/generator-cli-only/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,6 +117,13 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -131,12 +138,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -152,12 +159,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/e2e/vue-router/js-only-file-based/package.json b/e2e/vue-router/js-only-file-based/package.json index 05a86b7cf1..d7f062038e 100644 --- a/e2e/vue-router/js-only-file-based/package.json +++ b/e2e/vue-router/js-only-file-based/package.json @@ -22,7 +22,7 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", diff --git a/e2e/vue-router/js-only-file-based/src/routeTree.gen.ts b/e2e/vue-router/js-only-file-based/src/routeTree.gen.ts index c769c841f0..decb53754c 100644 --- a/e2e/vue-router/js-only-file-based/src/routeTree.gen.ts +++ b/e2e/vue-router/js-only-file-based/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,6 +117,13 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -131,12 +138,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -152,12 +159,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/e2e/vue-router/rspack-basic-file-based/package.json b/e2e/vue-router/rspack-basic-file-based/package.json index 5d8a072d87..d80486bc1a 100644 --- a/e2e/vue-router/rspack-basic-file-based/package.json +++ b/e2e/vue-router/rspack-basic-file-based/package.json @@ -16,8 +16,8 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-vue": "^1.2.7", "@rsbuild/plugin-vue-jsx": "^2.0.0", @@ -27,7 +27,8 @@ "@tanstack/virtual-file-routes": "workspace:^", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vue-tsc": "^3.1.5" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json b/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json index 11f61237d3..17d8fd711e 100644 --- a/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json +++ b/e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json @@ -16,8 +16,8 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-vue": "^1.2.7", "@rsbuild/plugin-vue-jsx": "^2.0.0", @@ -27,7 +27,8 @@ "@tanstack/virtual-file-routes": "workspace:^", "postcss": "^8.5.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vue-tsc": "^3.1.5" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/scroll-restoration-sandbox-vite/package.json b/e2e/vue-router/scroll-restoration-sandbox-vite/package.json index a7dc67fd6b..de5da25aaf 100644 --- a/e2e/vue-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/vue-router/scroll-restoration-sandbox-vite/package.json @@ -24,12 +24,12 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts b/e2e/vue-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts index 69dda324fe..e85be2bf37 100644 --- a/e2e/vue-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts +++ b/e2e/vue-router/scroll-restoration-sandbox-vite/src/routeTree.gen.ts @@ -12,10 +12,10 @@ import { createFileRoute } from '@tanstack/vue-router' import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' -import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' -import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' import { Route as testsLazyPageRouteImport } from './routes/(tests)/lazy-page' +import { Route as testsLazyWithLoaderPageRouteImport } from './routes/(tests)/lazy-with-loader-page' +import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsPageWithSearchRouteImport } from './routes/(tests)/page-with-search' const testsVirtualPageLazyRouteImport = createFileRoute( '/(tests)/virtual-page', @@ -26,23 +26,13 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport +const testsLazyPageRoute = testsLazyPageRouteImport .update({ - id: '/(tests)/virtual-page', - path: '/virtual-page', + id: '/(tests)/lazy-page', + path: '/lazy-page', getParentRoute: () => rootRouteImport, } as any) - .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) -const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ - id: '/(tests)/page-with-search', - path: '/page-with-search', - getParentRoute: () => rootRouteImport, -} as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', - getParentRoute: () => rootRouteImport, -} as any) + .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .update({ id: '/(tests)/lazy-with-loader-page', @@ -52,13 +42,23 @@ const testsLazyWithLoaderPageRoute = testsLazyWithLoaderPageRouteImport .lazy(() => import('./routes/(tests)/lazy-with-loader-page.lazy').then((d) => d.Route), ) -const testsLazyPageRoute = testsLazyPageRouteImport +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', + getParentRoute: () => rootRouteImport, +} as any) +const testsPageWithSearchRoute = testsPageWithSearchRouteImport.update({ + id: '/(tests)/page-with-search', + path: '/page-with-search', + getParentRoute: () => rootRouteImport, +} as any) +const testsVirtualPageLazyRoute = testsVirtualPageLazyRouteImport .update({ - id: '/(tests)/lazy-page', - path: '/lazy-page', + id: '/(tests)/virtual-page', + path: '/virtual-page', getParentRoute: () => rootRouteImport, } as any) - .lazy(() => import('./routes/(tests)/lazy-page.lazy').then((d) => d.Route)) + .lazy(() => import('./routes/(tests)/virtual-page.lazy').then((d) => d.Route)) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -130,18 +130,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/virtual-page': { - id: '/(tests)/virtual-page' - path: '/virtual-page' - fullPath: '/virtual-page' - preLoaderRoute: typeof testsVirtualPageLazyRouteImport + '/(tests)/lazy-page': { + id: '/(tests)/lazy-page' + path: '/lazy-page' + fullPath: '/lazy-page' + preLoaderRoute: typeof testsLazyPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/page-with-search': { - id: '/(tests)/page-with-search' - path: '/page-with-search' - fullPath: '/page-with-search' - preLoaderRoute: typeof testsPageWithSearchRouteImport + '/(tests)/lazy-with-loader-page': { + id: '/(tests)/lazy-with-loader-page' + path: '/lazy-with-loader-page' + fullPath: '/lazy-with-loader-page' + preLoaderRoute: typeof testsLazyWithLoaderPageRouteImport parentRoute: typeof rootRouteImport } '/(tests)/normal-page': { @@ -151,18 +151,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/lazy-with-loader-page': { - id: '/(tests)/lazy-with-loader-page' - path: '/lazy-with-loader-page' - fullPath: '/lazy-with-loader-page' - preLoaderRoute: typeof testsLazyWithLoaderPageRouteImport + '/(tests)/page-with-search': { + id: '/(tests)/page-with-search' + path: '/page-with-search' + fullPath: '/page-with-search' + preLoaderRoute: typeof testsPageWithSearchRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/lazy-page': { - id: '/(tests)/lazy-page' - path: '/lazy-page' - fullPath: '/lazy-page' - preLoaderRoute: typeof testsLazyPageRouteImport + '/(tests)/virtual-page': { + id: '/(tests)/virtual-page' + path: '/virtual-page' + fullPath: '/virtual-page' + preLoaderRoute: typeof testsVirtualPageLazyRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/vue-router/sentry-integration/package.json b/e2e/vue-router/sentry-integration/package.json index cbf6bf8499..d75ebed0c7 100644 --- a/e2e/vue-router/sentry-integration/package.json +++ b/e2e/vue-router/sentry-integration/package.json @@ -22,11 +22,11 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/view-transitions/package.json b/e2e/vue-router/view-transitions/package.json index 493b3e45ce..1e693e25db 100644 --- a/e2e/vue-router/view-transitions/package.json +++ b/e2e/vue-router/view-transitions/package.json @@ -22,12 +22,12 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-router/view-transitions/src/routeTree.gen.ts b/e2e/vue-router/view-transitions/src/routeTree.gen.ts index 465362a7a2..04708c29a8 100644 --- a/e2e/vue-router/view-transitions/src/routeTree.gen.ts +++ b/e2e/vue-router/view-transitions/src/routeTree.gen.ts @@ -9,16 +9,16 @@ // 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 HowItWorksRouteImport } from './routes/how-it-works' +import { Route as IndexRouteImport } from './routes/index' import { Route as ExploreRouteImport } from './routes/explore' +import { Route as HowItWorksRouteImport } from './routes/how-it-works' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const HowItWorksRoute = HowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ExploreRoute = ExploreRouteImport.update({ @@ -26,16 +26,16 @@ const ExploreRoute = ExploreRouteImport.update({ path: '/explore', getParentRoute: () => rootRouteImport, } as any) +const HowItWorksRoute = HowItWorksRouteImport.update({ + id: '/how-it-works', + path: '/how-it-works', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -101,11 +101,11 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/how-it-works': { - id: '/how-it-works' - path: '/how-it-works' - fullPath: '/how-it-works' - preLoaderRoute: typeof HowItWorksRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/explore': { @@ -115,6 +115,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ExploreRouteImport parentRoute: typeof rootRouteImport } + '/how-it-works': { + id: '/how-it-works' + path: '/how-it-works' + fullPath: '/how-it-works' + preLoaderRoute: typeof HowItWorksRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -122,13 +129,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/e2e/vue-start/basic-auth/package.json b/e2e/vue-start/basic-auth/package.json index 3a572536e9..c57e0973ca 100644 --- a/e2e/vue-start/basic-auth/package.json +++ b/e2e/vue-start/basic-auth/package.json @@ -24,7 +24,7 @@ "vue": "^3.5.25" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -34,7 +34,8 @@ "prisma": "^7.0.0", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/basic-auth/src/routeTree.gen.ts b/e2e/vue-start/basic-auth/src/routeTree.gen.ts index a0a61b0279..058f8674ac 100644 --- a/e2e/vue-start/basic-auth/src/routeTree.gen.ts +++ b/e2e/vue-start/basic-auth/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/e2e/vue-start/basic-cloudflare/package.json b/e2e/vue-start/basic-cloudflare/package.json index c7e55f7937..612a6b8d19 100644 --- a/e2e/vue-start/basic-cloudflare/package.json +++ b/e2e/vue-start/basic-cloudflare/package.json @@ -20,12 +20,13 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.29.0", - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", diff --git a/e2e/vue-start/basic-cloudflare/src/routeTree.gen.ts b/e2e/vue-start/basic-cloudflare/src/routeTree.gen.ts index 30451e7ab7..c52a88360b 100644 --- a/e2e/vue-start/basic-cloudflare/src/routeTree.gen.ts +++ b/e2e/vue-start/basic-cloudflare/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 StaticRouteImport } from './routes/static' import { Route as IndexRouteImport } from './routes/index' +import { Route as StaticRouteImport } from './routes/static' -const StaticRoute = StaticRouteImport.update({ - id: '/static', - path: '/static', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const StaticRoute = StaticRouteImport.update({ + id: '/static', + path: '/static', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/static': { - id: '/static' - path: '/static' - fullPath: '/static' - preLoaderRoute: typeof StaticRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/static': { + id: '/static' + path: '/static' + fullPath: '/static' + preLoaderRoute: typeof StaticRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/vue-start/basic-tsr-config/package.json b/e2e/vue-start/basic-tsr-config/package.json index 93c3b272fa..11c2973f13 100644 --- a/e2e/vue-start/basic-tsr-config/package.json +++ b/e2e/vue-start/basic-tsr-config/package.json @@ -20,7 +20,8 @@ "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "vite": "^8.0.14" diff --git a/e2e/vue-start/basic-vue-query/package.json b/e2e/vue-start/basic-vue-query/package.json index 3189e8e867..9162a2b6a6 100644 --- a/e2e/vue-start/basic-vue-query/package.json +++ b/e2e/vue-start/basic-vue-query/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -32,7 +32,8 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/basic-vue-query/src/routeTree.gen.ts b/e2e/vue-start/basic-vue-query/src/routeTree.gen.ts index 2c514d54ea..10e8a56450 100644 --- a/e2e/vue-start/basic-vue-query/src/routeTree.gen.ts +++ b/e2e/vue-start/basic-vue-query/src/routeTree.gen.ts @@ -9,32 +9,36 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as DeferredRouteImport } from './routes/deferred' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as SuspenseTransitionRouteImport } from './routes/suspense-transition' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api.users' 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 TransitionCountQueryRouteImport } from './routes/transition/count/query' -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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as TransitionCountQueryRouteImport } from './routes/transition/count/query' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ - id: '/suspense-transition', - path: '/suspense-transition', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -42,73 +46,69 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const SuspenseTransitionRoute = SuspenseTransitionRouteImport.update({ + id: '/suspense-transition', + path: '/suspense-transition', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, } as any) -const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ - id: '/transition/count/query', - path: '/transition/count/query', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, } as any) const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ id: '/$id', path: '/$id', getParentRoute: () => ApiUsersRoute, } as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const TransitionCountQueryRoute = TransitionCountQueryRouteImport.update({ + id: '/transition/count/query', + path: '/transition/count/query', + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { @@ -231,18 +231,25 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/suspense-transition': { - id: '/suspense-transition' - path: '/suspense-transition' - fullPath: '/suspense-transition' - preLoaderRoute: typeof SuspenseTransitionRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -252,33 +259,33 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/suspense-transition': { + id: '/suspense-transition' + path: '/suspense-transition' + fullPath: '/suspense-transition' + preLoaderRoute: typeof SuspenseTransitionRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -287,13 +294,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -301,33 +301,33 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/transition/count/query': { - id: '/transition/count/query' - path: '/transition/count/query' - fullPath: '/transition/count/query' - preLoaderRoute: typeof TransitionCountQueryRouteImport - parentRoute: typeof rootRouteImport + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutARouteImport + parentRoute: typeof LayoutLayout2Route } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -336,19 +336,19 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ApiUsersIdRouteImport parentRoute: typeof ApiUsersRoute } - '/_layout/_layout-2/layout-b': { - id: '/_layout/_layout-2/layout-b' - path: '/layout-b' - fullPath: '/layout-b' - preLoaderRoute: typeof LayoutLayout2LayoutBRouteImport - parentRoute: typeof LayoutLayout2Route + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/transition/count/query': { + id: '/transition/count/query' + path: '/transition/count/query' + fullPath: '/transition/count/query' + preLoaderRoute: typeof TransitionCountQueryRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/e2e/vue-start/basic/package.json b/e2e/vue-start/basic/package.json index 073627701a..173ab3175b 100644 --- a/e2e/vue-start/basic/package.json +++ b/e2e/vue-start/basic/package.json @@ -26,11 +26,11 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@rsbuild/core": "^2.0.11", - "@rsbuild/plugin-babel": "^1.0.5", - "@rsbuild/plugin-vue": "^1.2.2", - "@rsbuild/plugin-vue-jsx": "^1.1.1", + "@playwright/test": "^1.61.0", + "@rsbuild/core": "^2.1.0", + "@rsbuild/plugin-babel": "^1.1.2", + "@rsbuild/plugin-vue": "^1.2.7", + "@rsbuild/plugin-vue-jsx": "^2.0.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", @@ -41,7 +41,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/vue-start/basic/src/routeTree.gen.ts b/e2e/vue-start/basic/src/routeTree.gen.ts index 016a292835..7fbcd192b6 100644 --- a/e2e/vue-start/basic/src/routeTree.gen.ts +++ b/e2e/vue-start/basic/src/routeTree.gen.ts @@ -9,87 +9,81 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as StreamRouteImport } from './routes/stream' -import { Route as ScriptsRouteImport } from './routes/scripts' -import { Route as RawStreamRouteImport } from './routes/raw-stream' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as LinksRouteImport } from './routes/links' -import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' -import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' -import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as InlineScriptsRouteImport } from './routes/inline-scripts' +import { Route as LinksRouteImport } from './routes/links' import { Route as NotFoundRouteRouteImport } from './routes/not-found/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' -import { Route as RedirectIndexRouteImport } from './routes/redirect/index' -import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RawStreamRouteImport } from './routes/raw-stream' +import { Route as ScriptsRouteImport } from './routes/scripts' +import { Route as SearchParamsRouteRouteImport } from './routes/search-params/route' +import { Route as SpecialCharsRouteRouteImport } from './routes/specialChars/route' +import { Route as StreamRouteImport } from './routes/stream' +import { Route as UsersRouteImport } from './routes/users' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-cookie-redirect/index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' -import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' -import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' -import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' -import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' -import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' -import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' -import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' -import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' -import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' -import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' -import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' -import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' -import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' -import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' -import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target' -import { Route as ApiUsersRouteImport } from './routes/api/users' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' +import { Route as NotFoundIndexRouteImport } from './routes/not-found/index' import { Route as NotFoundParentBoundaryRouteRouteImport } from './routes/not-found/parent-boundary/route' +import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad' +import { Route as NotFoundViaBeforeLoadTargetRootRouteImport } from './routes/not-found/via-beforeLoad-target-root' +import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as RawStreamIndexRouteImport } from './routes/raw-stream/index' +import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call' +import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint' +import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-mixed' +import { Route as RawStreamSsrMultipleRouteImport } from './routes/raw-stream/ssr-multiple' +import { Route as RawStreamSsrSingleRouteImport } from './routes/raw-stream/ssr-single' +import { Route as RawStreamSsrTextHintRouteImport } from './routes/raw-stream/ssr-text-hint' +import { Route as RedirectIndexRouteImport } from './routes/redirect/index' +import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' +import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' +import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' +import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect' +import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param' +import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash' +import { Route as SpecialCharsMalformedRouteRouteImport } from './routes/specialChars/malformed/route' +import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search' +import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' import { Route as RedirectTargetIndexRouteImport } from './routes/redirect/$target/index' -import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' -import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' -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 NotFoundParentBoundaryViaBeforeLoadRouteImport } from './routes/not-found/parent-boundary/via-beforeLoad' -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 RedirectTargetViaLoaderRouteImport } from './routes/redirect/$target/via-loader' +import { Route as SpecialCharsMalformedParamRouteImport } from './routes/specialChars/malformed/$param' +import { Route as SpecialCharsMalformedSearchRouteImport } from './routes/specialChars/malformed/search' 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 { Route as RedirectTargetServerFnViaLoaderRouteImport } from './routes/redirect/$target/serverFn/via-loader' +import { Route as RedirectTargetServerFnViaUseServerFnRouteImport } from './routes/redirect/$target/serverFn/via-useServerFn' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', - getParentRoute: () => rootRouteImport, -} as any) -const StreamRoute = StreamRouteImport.update({ - id: '/stream', - path: '/stream', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const ScriptsRoute = ScriptsRouteImport.update({ - id: '/scripts', - path: '/scripts', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const RawStreamRoute = RawStreamRouteImport.update({ - id: '/raw-stream', - path: '/raw-stream', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const InlineScriptsRoute = InlineScriptsRouteImport.update({ + id: '/inline-scripts', + path: '/inline-scripts', getParentRoute: () => rootRouteImport, } as any) const LinksRoute = LinksRouteImport.update({ @@ -97,23 +91,24 @@ const LinksRoute = LinksRouteImport.update({ path: '/links', getParentRoute: () => rootRouteImport, } as any) -const InlineScriptsRoute = InlineScriptsRouteImport.update({ - id: '/inline-scripts', - path: '/inline-scripts', +const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ + id: '/not-found', + path: '/not-found', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const RawStreamRoute = RawStreamRouteImport.update({ + id: '/raw-stream', + path: '/raw-stream', getParentRoute: () => rootRouteImport, } as any) -const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ - id: '/specialChars', - path: '/specialChars', +const ScriptsRoute = ScriptsRouteImport.update({ + id: '/scripts', + path: '/scripts', getParentRoute: () => rootRouteImport, } as any) const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ @@ -121,102 +116,97 @@ const SearchParamsRouteRoute = SearchParamsRouteRouteImport.update({ path: '/search-params', getParentRoute: () => rootRouteImport, } as any) -const NotFoundRouteRoute = NotFoundRouteRouteImport.update({ - id: '/not-found', - path: '/not-found', +const SpecialCharsRouteRoute = SpecialCharsRouteRouteImport.update({ + id: '/specialChars', + path: '/specialChars', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const StreamRoute = StreamRouteImport.update({ + id: '/stream', + path: '/stream', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRouteImport, } as any) -const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => SearchParamsRouteRoute, +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any) -const RedirectIndexRoute = RedirectIndexRouteImport.update({ - id: '/redirect/', - path: '/redirect/', +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ +const MultiCookieRedirectIndexRoute = + MultiCookieRedirectIndexRouteImport.update({ + id: '/multi-cookie-redirect/', + path: '/multi-cookie-redirect/', + getParentRoute: () => rootRouteImport, + } as any) +const MultiCookieRedirectTargetRoute = + MultiCookieRedirectTargetRouteImport.update({ + id: '/multi-cookie-redirect/target', + path: '/multi-cookie-redirect/target', + getParentRoute: () => rootRouteImport, + } as any) +const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RawStreamRoute, + getParentRoute: () => NotFoundRouteRoute, +} as any) +const NotFoundParentBoundaryRouteRoute = + NotFoundParentBoundaryRouteRouteImport.update({ + id: '/parent-boundary', + path: '/parent-boundary', + getParentRoute: () => NotFoundRouteRoute, + } as any) +const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ + id: '/via-beforeLoad', + path: '/via-beforeLoad', + getParentRoute: () => NotFoundRouteRoute, +} as any) +const NotFoundViaBeforeLoadTargetRootRoute = + NotFoundViaBeforeLoadTargetRootRouteImport.update({ + id: '/via-beforeLoad-target-root', + path: '/via-beforeLoad-target-root', + getParentRoute: () => NotFoundRouteRoute, + } as any) +const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => NotFoundRouteRoute, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const NotFoundIndexRoute = NotFoundIndexRouteImport.update({ +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const RawStreamIndexRoute = RawStreamIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NotFoundRouteRoute, + getParentRoute: () => RawStreamRoute, } as any) -const MultiCookieRedirectIndexRoute = - MultiCookieRedirectIndexRouteImport.update({ - id: '/multi-cookie-redirect/', - path: '/multi-cookie-redirect/', - getParentRoute: () => rootRouteImport, - } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, +const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ + id: '/client-call', + path: '/client-call', + getParentRoute: () => RawStreamRoute, } as any) -const SpecialCharsChar45824Char54620Char48124Char44397Route = - SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => SpecialCharsRouteRoute, - } as any) -const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsRouteRoute, +const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ + id: '/ssr-binary-hint', + path: '/ssr-binary-hint', + getParentRoute: () => RawStreamRoute, } as any) -const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ - id: '/hash', - path: '/hash', - getParentRoute: () => SpecialCharsRouteRoute, -} as any) -const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsRouteRoute, -} as any) -const SearchParamsLoaderThrowsRedirectRoute = - SearchParamsLoaderThrowsRedirectRouteImport.update({ - id: '/loader-throws-redirect', - path: '/loader-throws-redirect', - getParentRoute: () => SearchParamsRouteRoute, - } as any) -const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ - id: '/default', - path: '/default', - getParentRoute: () => SearchParamsRouteRoute, -} as any) -const RedirectTargetRoute = RedirectTargetRouteImport.update({ - id: '/redirect/$target', - path: '/redirect/$target', - getParentRoute: () => rootRouteImport, -} as any) -const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ - id: '/ssr-text-hint', - path: '/ssr-text-hint', - getParentRoute: () => RawStreamRoute, -} as any) -const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ - id: '/ssr-single', - path: '/ssr-single', +const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ + id: '/ssr-mixed', + path: '/ssr-mixed', getParentRoute: () => RawStreamRoute, } as any) const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ @@ -224,56 +214,51 @@ const RawStreamSsrMultipleRoute = RawStreamSsrMultipleRouteImport.update({ path: '/ssr-multiple', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrMixedRoute = RawStreamSsrMixedRouteImport.update({ - id: '/ssr-mixed', - path: '/ssr-mixed', +const RawStreamSsrSingleRoute = RawStreamSsrSingleRouteImport.update({ + id: '/ssr-single', + path: '/ssr-single', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamSsrBinaryHintRoute = RawStreamSsrBinaryHintRouteImport.update({ - id: '/ssr-binary-hint', - path: '/ssr-binary-hint', +const RawStreamSsrTextHintRoute = RawStreamSsrTextHintRouteImport.update({ + id: '/ssr-text-hint', + path: '/ssr-text-hint', getParentRoute: () => RawStreamRoute, } as any) -const RawStreamClientCallRoute = RawStreamClientCallRouteImport.update({ - id: '/client-call', - path: '/client-call', - getParentRoute: () => RawStreamRoute, +const RedirectIndexRoute = RedirectIndexRouteImport.update({ + id: '/redirect/', + path: '/redirect/', + getParentRoute: () => rootRouteImport, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, +const RedirectTargetRoute = RedirectTargetRouteImport.update({ + id: '/redirect/$target', + path: '/redirect/$target', + getParentRoute: () => rootRouteImport, } as any) -const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => NotFoundRouteRoute, +const SearchParamsIndexRoute = SearchParamsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const NotFoundViaBeforeLoadTargetRootRoute = - NotFoundViaBeforeLoadTargetRootRouteImport.update({ - id: '/via-beforeLoad-target-root', - path: '/via-beforeLoad-target-root', - getParentRoute: () => NotFoundRouteRoute, - } as any) -const NotFoundViaBeforeLoadRoute = NotFoundViaBeforeLoadRouteImport.update({ - id: '/via-beforeLoad', - path: '/via-beforeLoad', - getParentRoute: () => NotFoundRouteRoute, +const SearchParamsDefaultRoute = SearchParamsDefaultRouteImport.update({ + id: '/default', + path: '/default', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const MultiCookieRedirectTargetRoute = - MultiCookieRedirectTargetRouteImport.update({ - id: '/multi-cookie-redirect/target', - path: '/multi-cookie-redirect/target', - getParentRoute: () => rootRouteImport, +const SearchParamsLoaderThrowsRedirectRoute = + SearchParamsLoaderThrowsRedirectRouteImport.update({ + id: '/loader-throws-redirect', + path: '/loader-throws-redirect', + getParentRoute: () => SearchParamsRouteRoute, } as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, +const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({ + id: '/hash', + path: '/hash', + getParentRoute: () => SpecialCharsRouteRoute, } as any) const SpecialCharsMalformedRouteRoute = SpecialCharsMalformedRouteRouteImport.update({ @@ -281,76 +266,91 @@ const SpecialCharsMalformedRouteRoute = path: '/malformed', getParentRoute: () => SpecialCharsRouteRoute, } as any) -const NotFoundParentBoundaryRouteRoute = - NotFoundParentBoundaryRouteRouteImport.update({ - id: '/parent-boundary', - path: '/parent-boundary', - getParentRoute: () => NotFoundRouteRoute, +const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsRouteRoute, +} as any) +const SpecialCharsChar45824Char54620Char48124Char44397Route = + SpecialCharsChar45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => SpecialCharsRouteRoute, } as any) -const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ +const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => UsersRoute, } as any) -const SpecialCharsMalformedSearchRoute = - SpecialCharsMalformedSearchRouteImport.update({ - id: '/search', - path: '/search', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const SpecialCharsMalformedParamRoute = - SpecialCharsMalformedParamRouteImport.update({ - id: '/$param', - path: '/$param', - getParentRoute: () => SpecialCharsMalformedRouteRoute, - } as any) -const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ - id: '/via-loader', - path: '/via-loader', - getParentRoute: () => RedirectTargetRoute, +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, } as any) -const RedirectTargetViaBeforeLoadRoute = - RedirectTargetViaBeforeLoadRouteImport.update({ +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const NotFoundParentBoundaryViaBeforeLoadRoute = + NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', - getParentRoute: () => RedirectTargetRoute, + getParentRoute: () => NotFoundParentBoundaryRouteRoute, } as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const NotFoundParentBoundaryViaBeforeLoadRoute = - NotFoundParentBoundaryViaBeforeLoadRouteImport.update({ +const RedirectTargetIndexRoute = RedirectTargetIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => RedirectTargetRoute, +} as any) +const RedirectTargetViaBeforeLoadRoute = + RedirectTargetViaBeforeLoadRouteImport.update({ id: '/via-beforeLoad', path: '/via-beforeLoad', - getParentRoute: () => NotFoundParentBoundaryRouteRoute, + getParentRoute: () => RedirectTargetRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersRoute, -} as any) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, -} as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const RedirectTargetViaLoaderRoute = RedirectTargetViaLoaderRouteImport.update({ + id: '/via-loader', + path: '/via-loader', + getParentRoute: () => RedirectTargetRoute, } as any) +const SpecialCharsMalformedParamRoute = + SpecialCharsMalformedParamRouteImport.update({ + id: '/$param', + path: '/$param', + getParentRoute: () => SpecialCharsMalformedRouteRoute, + } as any) +const SpecialCharsMalformedSearchRoute = + SpecialCharsMalformedSearchRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => SpecialCharsMalformedRouteRoute, + } as any) const RedirectTargetServerFnIndexRoute = RedirectTargetServerFnIndexRouteImport.update({ id: '/serverFn/', path: '/serverFn/', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaUseServerFnRoute = - RedirectTargetServerFnViaUseServerFnRouteImport.update({ - id: '/serverFn/via-useServerFn', - path: '/serverFn/via-useServerFn', +const RedirectTargetServerFnViaBeforeLoadRoute = + RedirectTargetServerFnViaBeforeLoadRouteImport.update({ + id: '/serverFn/via-beforeLoad', + path: '/serverFn/via-beforeLoad', getParentRoute: () => RedirectTargetRoute, } as any) const RedirectTargetServerFnViaLoaderRoute = @@ -359,10 +359,10 @@ const RedirectTargetServerFnViaLoaderRoute = path: '/serverFn/via-loader', getParentRoute: () => RedirectTargetRoute, } as any) -const RedirectTargetServerFnViaBeforeLoadRoute = - RedirectTargetServerFnViaBeforeLoadRouteImport.update({ - id: '/serverFn/via-beforeLoad', - path: '/serverFn/via-beforeLoad', +const RedirectTargetServerFnViaUseServerFnRoute = + RedirectTargetServerFnViaUseServerFnRouteImport.update({ + id: '/serverFn/via-useServerFn', + path: '/serverFn/via-useServerFn', getParentRoute: () => RedirectTargetRoute, } as any) @@ -728,39 +728,32 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport - parentRoute: typeof rootRouteImport - } - '/stream': { - id: '/stream' - path: '/stream' - fullPath: '/stream' - preLoaderRoute: typeof StreamRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/scripts': { - id: '/scripts' - path: '/scripts' - fullPath: '/scripts' - preLoaderRoute: typeof ScriptsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/raw-stream': { - id: '/raw-stream' - path: '/raw-stream' - fullPath: '/raw-stream' - preLoaderRoute: typeof RawStreamRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/inline-scripts': { + id: '/inline-scripts' + path: '/inline-scripts' + fullPath: '/inline-scripts' + preLoaderRoute: typeof InlineScriptsRouteImport parentRoute: typeof rootRouteImport } '/links': { @@ -770,32 +763,32 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LinksRouteImport parentRoute: typeof rootRouteImport } - '/inline-scripts': { - id: '/inline-scripts' - path: '/inline-scripts' - fullPath: '/inline-scripts' - preLoaderRoute: typeof InlineScriptsRouteImport + '/not-found': { + id: '/not-found' + path: '/not-found' + fullPath: '/not-found' + preLoaderRoute: typeof NotFoundRouteRouteImport 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 } - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + '/raw-stream': { + id: '/raw-stream' + path: '/raw-stream' + fullPath: '/raw-stream' + preLoaderRoute: typeof RawStreamRouteImport parentRoute: typeof rootRouteImport } - '/specialChars': { - id: '/specialChars' - path: '/specialChars' - fullPath: '/specialChars' - preLoaderRoute: typeof SpecialCharsRouteRouteImport + '/scripts': { + id: '/scripts' + path: '/scripts' + fullPath: '/scripts' + preLoaderRoute: typeof ScriptsRouteImport parentRoute: typeof rootRouteImport } '/search-params': { @@ -805,61 +798,40 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof SearchParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/not-found': { - id: '/not-found' - path: '/not-found' - fullPath: '/not-found' - preLoaderRoute: typeof NotFoundRouteRouteImport + '/specialChars': { + id: '/specialChars' + path: '/specialChars' + fullPath: '/specialChars' + preLoaderRoute: typeof SpecialCharsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/stream': { + id: '/stream' + path: '/stream' + fullPath: '/stream' + preLoaderRoute: typeof StreamRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute - } - '/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 + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/raw-stream/': { - id: '/raw-stream/' - path: '/' - fullPath: '/raw-stream/' - preLoaderRoute: typeof RawStreamIndexRouteImport - parentRoute: typeof RawStreamRoute - } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRoute + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute } - '/not-found/': { - id: '/not-found/' - path: '/' - fullPath: '/not-found/' - preLoaderRoute: typeof NotFoundIndexRouteImport - parentRoute: typeof NotFoundRouteRoute + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/multi-cookie-redirect/': { id: '/multi-cookie-redirect/' @@ -868,81 +840,81 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof MultiCookieRedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute + '/multi-cookie-redirect/target': { + id: '/multi-cookie-redirect/target' + path: '/multi-cookie-redirect/target' + fullPath: '/multi-cookie-redirect/target' + preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport + parentRoute: typeof rootRouteImport } - '/specialChars/대한민국': { - id: '/specialChars/대한민국' - path: '/대한민국' - fullPath: '/specialChars/대한민국' - preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/': { + id: '/not-found/' + path: '/' + fullPath: '/not-found/' + preLoaderRoute: typeof NotFoundIndexRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/search': { - id: '/specialChars/search' - path: '/search' - fullPath: '/specialChars/search' - preLoaderRoute: typeof SpecialCharsSearchRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/parent-boundary': { + id: '/not-found/parent-boundary' + path: '/parent-boundary' + fullPath: '/not-found/parent-boundary' + preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/hash': { - id: '/specialChars/hash' - path: '/hash' - fullPath: '/specialChars/hash' - preLoaderRoute: typeof SpecialCharsHashRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-beforeLoad': { + id: '/not-found/via-beforeLoad' + path: '/via-beforeLoad' + fullPath: '/not-found/via-beforeLoad' + preLoaderRoute: typeof NotFoundViaBeforeLoadRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/specialChars/$param': { - id: '/specialChars/$param' - path: '/$param' - fullPath: '/specialChars/$param' - preLoaderRoute: typeof SpecialCharsParamRouteImport - parentRoute: typeof SpecialCharsRouteRoute + '/not-found/via-beforeLoad-target-root': { + id: '/not-found/via-beforeLoad-target-root' + path: '/via-beforeLoad-target-root' + fullPath: '/not-found/via-beforeLoad-target-root' + preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/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 + '/not-found/via-loader': { + id: '/not-found/via-loader' + path: '/via-loader' + fullPath: '/not-found/via-loader' + preLoaderRoute: typeof NotFoundViaLoaderRouteImport + parentRoute: typeof NotFoundRouteRoute } - '/search-params/default': { - id: '/search-params/default' - path: '/default' - fullPath: '/search-params/default' - preLoaderRoute: typeof SearchParamsDefaultRouteImport - parentRoute: typeof SearchParamsRouteRoute + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute } - '/redirect/$target': { - id: '/redirect/$target' - path: '/redirect/$target' - fullPath: '/redirect/$target' - preLoaderRoute: typeof RedirectTargetRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute } - '/raw-stream/ssr-text-hint': { - id: '/raw-stream/ssr-text-hint' - path: '/ssr-text-hint' - fullPath: '/raw-stream/ssr-text-hint' - preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + '/raw-stream/': { + id: '/raw-stream/' + path: '/' + fullPath: '/raw-stream/' + preLoaderRoute: typeof RawStreamIndexRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-single': { - id: '/raw-stream/ssr-single' - path: '/ssr-single' - fullPath: '/raw-stream/ssr-single' - preLoaderRoute: typeof RawStreamSsrSingleRouteImport + '/raw-stream/client-call': { + id: '/raw-stream/client-call' + path: '/client-call' + fullPath: '/raw-stream/client-call' + preLoaderRoute: typeof RawStreamClientCallRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-multiple': { - id: '/raw-stream/ssr-multiple' - path: '/ssr-multiple' - fullPath: '/raw-stream/ssr-multiple' - preLoaderRoute: typeof RawStreamSsrMultipleRouteImport + '/raw-stream/ssr-binary-hint': { + id: '/raw-stream/ssr-binary-hint' + path: '/ssr-binary-hint' + fullPath: '/raw-stream/ssr-binary-hint' + preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport parentRoute: typeof RawStreamRoute } '/raw-stream/ssr-mixed': { @@ -952,68 +924,75 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof RawStreamSsrMixedRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/ssr-binary-hint': { - id: '/raw-stream/ssr-binary-hint' - path: '/ssr-binary-hint' - fullPath: '/raw-stream/ssr-binary-hint' - preLoaderRoute: typeof RawStreamSsrBinaryHintRouteImport + '/raw-stream/ssr-multiple': { + id: '/raw-stream/ssr-multiple' + path: '/ssr-multiple' + fullPath: '/raw-stream/ssr-multiple' + preLoaderRoute: typeof RawStreamSsrMultipleRouteImport parentRoute: typeof RawStreamRoute } - '/raw-stream/client-call': { - id: '/raw-stream/client-call' - path: '/client-call' - fullPath: '/raw-stream/client-call' - preLoaderRoute: typeof RawStreamClientCallRouteImport + '/raw-stream/ssr-single': { + id: '/raw-stream/ssr-single' + path: '/ssr-single' + fullPath: '/raw-stream/ssr-single' + preLoaderRoute: typeof RawStreamSsrSingleRouteImport parentRoute: typeof RawStreamRoute } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsPostIdRouteImport - parentRoute: typeof PostsRoute + '/raw-stream/ssr-text-hint': { + id: '/raw-stream/ssr-text-hint' + path: '/ssr-text-hint' + fullPath: '/raw-stream/ssr-text-hint' + preLoaderRoute: typeof RawStreamSsrTextHintRouteImport + parentRoute: typeof RawStreamRoute } - '/not-found/via-loader': { - id: '/not-found/via-loader' - path: '/via-loader' - fullPath: '/not-found/via-loader' - preLoaderRoute: typeof NotFoundViaLoaderRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport + parentRoute: typeof rootRouteImport } - '/not-found/via-beforeLoad-target-root': { - id: '/not-found/via-beforeLoad-target-root' - path: '/via-beforeLoad-target-root' - fullPath: '/not-found/via-beforeLoad-target-root' - preLoaderRoute: typeof NotFoundViaBeforeLoadTargetRootRouteImport - parentRoute: typeof NotFoundRouteRoute + '/redirect/$target': { + id: '/redirect/$target' + path: '/redirect/$target' + fullPath: '/redirect/$target' + preLoaderRoute: typeof RedirectTargetRouteImport + 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 + '/search-params/': { + id: '/search-params/' + path: '/' + fullPath: '/search-params/' + preLoaderRoute: typeof SearchParamsIndexRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/multi-cookie-redirect/target': { - id: '/multi-cookie-redirect/target' - path: '/multi-cookie-redirect/target' - fullPath: '/multi-cookie-redirect/target' - preLoaderRoute: typeof MultiCookieRedirectTargetRouteImport - parentRoute: typeof rootRouteImport + '/search-params/default': { + id: '/search-params/default' + path: '/default' + fullPath: '/search-params/default' + preLoaderRoute: typeof SearchParamsDefaultRouteImport + parentRoute: typeof SearchParamsRouteRoute } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersRouteImport - 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 } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute + '/specialChars/$param': { + id: '/specialChars/$param' + path: '/$param' + fullPath: '/specialChars/$param' + preLoaderRoute: typeof SpecialCharsParamRouteImport + parentRoute: typeof SpecialCharsRouteRoute + } + '/specialChars/hash': { + id: '/specialChars/hash' + path: '/hash' + fullPath: '/specialChars/hash' + preLoaderRoute: typeof SpecialCharsHashRouteImport + parentRoute: typeof SpecialCharsRouteRoute } '/specialChars/malformed': { id: '/specialChars/malformed' @@ -1022,47 +1001,61 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof SpecialCharsMalformedRouteRouteImport parentRoute: typeof SpecialCharsRouteRoute } - '/not-found/parent-boundary': { - id: '/not-found/parent-boundary' - path: '/parent-boundary' - fullPath: '/not-found/parent-boundary' - preLoaderRoute: typeof NotFoundParentBoundaryRouteRouteImport - parentRoute: typeof NotFoundRouteRoute + '/specialChars/search': { + id: '/specialChars/search' + path: '/search' + fullPath: '/specialChars/search' + preLoaderRoute: typeof SpecialCharsSearchRouteImport + parentRoute: typeof SpecialCharsRouteRoute } - '/redirect/$target/': { - id: '/redirect/$target/' + '/specialChars/대한민국': { + id: '/specialChars/대한민국' + path: '/대한민국' + fullPath: '/specialChars/대한민국' + preLoaderRoute: typeof SpecialCharsChar45824Char54620Char48124Char44397RouteImport + parentRoute: typeof SpecialCharsRouteRoute + } + '/users/': { + id: '/users/' path: '/' - fullPath: '/redirect/$target/' - preLoaderRoute: typeof RedirectTargetIndexRouteImport - parentRoute: typeof RedirectTargetRoute + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/specialChars/malformed/search': { - id: '/specialChars/malformed/search' - path: '/search' - fullPath: '/specialChars/malformed/search' - preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute } - '/specialChars/malformed/$param': { - id: '/specialChars/malformed/$param' - path: '/$param' - fullPath: '/specialChars/malformed/$param' - preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport - parentRoute: typeof SpecialCharsMalformedRouteRoute + '/_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/via-loader': { - id: '/redirect/$target/via-loader' - path: '/via-loader' - fullPath: '/redirect/$target/via-loader' - preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport - 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/via-beforeLoad': { - id: '/redirect/$target/via-beforeLoad' + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/not-found/parent-boundary/via-beforeLoad': { + id: '/not-found/parent-boundary/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/redirect/$target/via-beforeLoad' - preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport - parentRoute: typeof RedirectTargetRoute + fullPath: '/not-found/parent-boundary/via-beforeLoad' + preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport + parentRoute: typeof NotFoundParentBoundaryRouteRoute } '/posts_/$postId/deep': { id: '/posts_/$postId/deep' @@ -1071,33 +1064,40 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof rootRouteImport } - '/not-found/parent-boundary/via-beforeLoad': { - id: '/not-found/parent-boundary/via-beforeLoad' + '/redirect/$target/': { + id: '/redirect/$target/' + path: '/' + fullPath: '/redirect/$target/' + preLoaderRoute: typeof RedirectTargetIndexRouteImport + parentRoute: typeof RedirectTargetRoute + } + '/redirect/$target/via-beforeLoad': { + id: '/redirect/$target/via-beforeLoad' path: '/via-beforeLoad' - fullPath: '/not-found/parent-boundary/via-beforeLoad' - preLoaderRoute: typeof NotFoundParentBoundaryViaBeforeLoadRouteImport - parentRoute: typeof NotFoundParentBoundaryRouteRoute + fullPath: '/redirect/$target/via-beforeLoad' + preLoaderRoute: typeof RedirectTargetViaBeforeLoadRouteImport + parentRoute: typeof RedirectTargetRoute } - '/api/users/$userId': { - id: '/api/users/$userId' - path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + '/redirect/$target/via-loader': { + id: '/redirect/$target/via-loader' + path: '/via-loader' + fullPath: '/redirect/$target/via-loader' + preLoaderRoute: typeof RedirectTargetViaLoaderRouteImport + 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 + '/specialChars/malformed/$param': { + id: '/specialChars/malformed/$param' + path: '/$param' + fullPath: '/specialChars/malformed/$param' + preLoaderRoute: typeof SpecialCharsMalformedParamRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute } - '/_layout/_layout-2/layout-a': { - id: '/_layout/_layout-2/layout-a' - path: '/layout-a' - fullPath: '/layout-a' - preLoaderRoute: typeof LayoutLayout2LayoutARouteImport - parentRoute: typeof LayoutLayout2Route + '/specialChars/malformed/search': { + id: '/specialChars/malformed/search' + path: '/search' + fullPath: '/specialChars/malformed/search' + preLoaderRoute: typeof SpecialCharsMalformedSearchRouteImport + parentRoute: typeof SpecialCharsMalformedRouteRoute } '/redirect/$target/serverFn/': { id: '/redirect/$target/serverFn/' @@ -1106,11 +1106,11 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof RedirectTargetServerFnIndexRouteImport parentRoute: typeof RedirectTargetRoute } - '/redirect/$target/serverFn/via-useServerFn': { - id: '/redirect/$target/serverFn/via-useServerFn' - path: '/serverFn/via-useServerFn' - fullPath: '/redirect/$target/serverFn/via-useServerFn' - preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport + '/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 } '/redirect/$target/serverFn/via-loader': { @@ -1120,11 +1120,11 @@ declare module '@tanstack/vue-router' { 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 + '/redirect/$target/serverFn/via-useServerFn': { + id: '/redirect/$target/serverFn/via-useServerFn' + path: '/serverFn/via-useServerFn' + fullPath: '/redirect/$target/serverFn/via-useServerFn' + preLoaderRoute: typeof RedirectTargetServerFnViaUseServerFnRouteImport parentRoute: typeof RedirectTargetRoute } } diff --git a/e2e/vue-start/css-modules/package.json b/e2e/vue-start/css-modules/package.json index 9b7d36333d..c739f09ea0 100644 --- a/e2e/vue-start/css-modules/package.json +++ b/e2e/vue-start/css-modules/package.json @@ -19,13 +19,14 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:*", "@types/node": "^22.10.2", "@vitejs/plugin-vue-jsx": "^5.1.5", "sass": "^1.97.2", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/custom-basepath/package.json b/e2e/vue-start/custom-basepath/package.json index 759e13b48e..787d0480a5 100644 --- a/e2e/vue-start/custom-basepath/package.json +++ b/e2e/vue-start/custom-basepath/package.json @@ -19,7 +19,7 @@ "vue": "^3.5.25" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/express": "^5.0.6", @@ -28,7 +28,8 @@ "srvx": "^0.11.9", "tailwindcss": "^4.2.2", "tsx": "^4.20.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5" diff --git a/e2e/vue-start/custom-basepath/src/routeTree.gen.ts b/e2e/vue-start/custom-basepath/src/routeTree.gen.ts index ea7e127d8e..605193fbec 100644 --- a/e2e/vue-start/custom-basepath/src/routeTree.gen.ts +++ b/e2e/vue-start/custom-basepath/src/routeTree.gen.ts @@ -9,29 +9,29 @@ // 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 UsersRouteImport } from './routes/users' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as DeferredRouteImport } from './routes/deferred' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' -import { Route as RedirectIndexRouteImport } from './routes/redirect.index' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as UsersRouteImport } from './routes/users' +import { Route as ApiUsersRouteImport } from './routes/api/users' import { Route as PostsIndexRouteImport } from './routes/posts.index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -import { Route as RedirectThrowItRouteImport } from './routes/redirect.throw-it' 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 { Route as RedirectIndexRouteImport } from './routes/redirect.index' +import { Route as RedirectThrowItRouteImport } from './routes/redirect.throw-it' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const LogoutRoute = LogoutRouteImport.update({ @@ -39,61 +39,61 @@ const LogoutRoute = LogoutRouteImport.update({ path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => UsersRoute, + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any) const RedirectIndexRoute = RedirectIndexRouteImport.update({ id: '/redirect/', path: '/redirect/', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ +const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ + id: '/redirect/throw-it', + path: '/redirect/throw-it', + getParentRoute: () => rootRouteImport, +} as any) +const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => PostsRoute, + getParentRoute: () => UsersRoute, } as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRoute, } as any) -const RedirectThrowItRoute = RedirectThrowItRouteImport.update({ - id: '/redirect/throw-it', - path: '/redirect/throw-it', - getParentRoute: () => rootRouteImport, -} as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const ApiUsersRoute = ApiUsersRouteImport.update({ - id: '/api/users', - path: '/api/users', - getParentRoute: () => rootRouteImport, +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, } as any) const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ id: '/posts_/$postId/deep', path: '/posts/$postId/deep', getParentRoute: () => rootRouteImport, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => ApiUsersRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -205,18 +205,18 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport 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 } '/logout': { @@ -226,32 +226,25 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LogoutRouteImport 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 } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute - } - '/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 } '/posts/': { @@ -261,20 +254,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } - '/redirect/throw-it': { - id: '/redirect/throw-it' - path: '/redirect/throw-it' - fullPath: '/redirect/throw-it' - preLoaderRoute: typeof RedirectThrowItRouteImport - parentRoute: typeof rootRouteImport - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -282,20 +261,34 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/api/users': { - id: '/api/users' - path: '/api/users' - fullPath: '/api/users' - preLoaderRoute: typeof ApiUsersRouteImport + '/redirect/': { + id: '/redirect/' + path: '/redirect' + fullPath: '/redirect/' + preLoaderRoute: typeof RedirectIndexRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport + '/redirect/throw-it': { + id: '/redirect/throw-it' + path: '/redirect/throw-it' + fullPath: '/redirect/throw-it' + preLoaderRoute: typeof RedirectThrowItRouteImport parentRoute: typeof rootRouteImport } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } '/api/users/$userId': { id: '/api/users/$userId' path: '/$userId' @@ -303,6 +296,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ApiUsersUserIdRouteImport parentRoute: typeof ApiUsersRoute } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/e2e/vue-start/query-integration/package.json b/e2e/vue-start/query-integration/package.json index d44bd58376..a3b775e0eb 100644 --- a/e2e/vue-start/query-integration/package.json +++ b/e2e/vue-start/query-integration/package.json @@ -23,14 +23,15 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/query-integration/src/routeTree.gen.ts b/e2e/vue-start/query-integration/src/routeTree.gen.ts index d4d30083a0..c5a9f02a7a 100644 --- a/e2e/vue-start/query-integration/src/routeTree.gen.ts +++ b/e2e/vue-start/query-integration/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 UseQueryRouteImport } from './routes/useQuery' import { Route as IndexRouteImport } from './routes/index' +import { Route as UseQueryRouteImport } from './routes/useQuery' import { Route as LoaderFetchQueryTypeRouteImport } from './routes/loader-fetchQuery/$type' -const UseQueryRoute = UseQueryRouteImport.update({ - id: '/useQuery', - path: '/useQuery', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const UseQueryRoute = UseQueryRouteImport.update({ + id: '/useQuery', + path: '/useQuery', + getParentRoute: () => rootRouteImport, +} as any) const LoaderFetchQueryTypeRoute = LoaderFetchQueryTypeRouteImport.update({ id: '/loader-fetchQuery/$type', path: '/loader-fetchQuery/$type', @@ -61,13 +61,6 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/useQuery': { - id: '/useQuery' - path: '/useQuery' - fullPath: '/useQuery' - preLoaderRoute: typeof UseQueryRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -75,6 +68,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/useQuery': { + id: '/useQuery' + path: '/useQuery' + fullPath: '/useQuery' + preLoaderRoute: typeof UseQueryRouteImport + parentRoute: typeof rootRouteImport + } '/loader-fetchQuery/$type': { id: '/loader-fetchQuery/$type' path: '/loader-fetchQuery/$type' diff --git a/e2e/vue-start/scroll-restoration/package.json b/e2e/vue-start/scroll-restoration/package.json index 871200e852..b81d8af48b 100644 --- a/e2e/vue-start/scroll-restoration/package.json +++ b/e2e/vue-start/scroll-restoration/package.json @@ -21,7 +21,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -30,7 +30,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/scroll-restoration/src/routeTree.gen.ts b/e2e/vue-start/scroll-restoration/src/routeTree.gen.ts index 86dc966e78..49b34fc68f 100644 --- a/e2e/vue-start/scroll-restoration/src/routeTree.gen.ts +++ b/e2e/vue-start/scroll-restoration/src/routeTree.gen.ts @@ -10,18 +10,18 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' -import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page' +import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader' +import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const testsWithSearchRoute = testsWithSearchRouteImport.update({ - id: '/(tests)/with-search', - path: '/with-search', +const testsNormalPageRoute = testsNormalPageRouteImport.update({ + id: '/(tests)/normal-page', + path: '/normal-page', getParentRoute: () => rootRouteImport, } as any) const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ @@ -29,9 +29,9 @@ const testsWithLoaderRoute = testsWithLoaderRouteImport.update({ path: '/with-loader', getParentRoute: () => rootRouteImport, } as any) -const testsNormalPageRoute = testsNormalPageRouteImport.update({ - id: '/(tests)/normal-page', - path: '/normal-page', +const testsWithSearchRoute = testsWithSearchRouteImport.update({ + id: '/(tests)/with-search', + path: '/with-search', getParentRoute: () => rootRouteImport, } as any) @@ -83,11 +83,11 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/with-search': { - id: '/(tests)/with-search' - path: '/with-search' - fullPath: '/with-search' - preLoaderRoute: typeof testsWithSearchRouteImport + '/(tests)/normal-page': { + id: '/(tests)/normal-page' + path: '/normal-page' + fullPath: '/normal-page' + preLoaderRoute: typeof testsNormalPageRouteImport parentRoute: typeof rootRouteImport } '/(tests)/with-loader': { @@ -97,11 +97,11 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof testsWithLoaderRouteImport parentRoute: typeof rootRouteImport } - '/(tests)/normal-page': { - id: '/(tests)/normal-page' - path: '/normal-page' - fullPath: '/normal-page' - preLoaderRoute: typeof testsNormalPageRouteImport + '/(tests)/with-search': { + id: '/(tests)/with-search' + path: '/with-search' + fullPath: '/with-search' + preLoaderRoute: typeof testsWithSearchRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/vue-start/selective-ssr/package.json b/e2e/vue-start/selective-ssr/package.json index 95c82b2511..7ffcaae743 100644 --- a/e2e/vue-start/selective-ssr/package.json +++ b/e2e/vue-start/selective-ssr/package.json @@ -24,8 +24,9 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", - "vue-tsc": "^3.1.8" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-start/selective-ssr/src/routeTree.gen.ts b/e2e/vue-start/selective-ssr/src/routeTree.gen.ts index 474705b7e7..0d44b648ee 100644 --- a/e2e/vue-start/selective-ssr/src/routeTree.gen.ts +++ b/e2e/vue-start/selective-ssr/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', @@ -60,13 +60,6 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +67,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' diff --git a/e2e/vue-start/serialization-adapters/package.json b/e2e/vue-start/serialization-adapters/package.json index d854c0bb7c..c5b80bdced 100644 --- a/e2e/vue-start/serialization-adapters/package.json +++ b/e2e/vue-start/serialization-adapters/package.json @@ -25,8 +25,9 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", - "vue-tsc": "^3.1.8" + "vue-tsc": "^3.3.8" } } diff --git a/e2e/vue-start/serialization-adapters/src/routeTree.gen.ts b/e2e/vue-start/serialization-adapters/src/routeTree.gen.ts index 79b17ac82b..c684bbc82c 100644 --- a/e2e/vue-start/serialization-adapters/src/routeTree.gen.ts +++ b/e2e/vue-start/serialization-adapters/src/routeTree.gen.ts @@ -10,25 +10,26 @@ 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 SsrNestedRouteImport } from './routes/ssr/nested' -import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' -import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' import { Route as ServerFunctionCustomErrorRouteImport } from './routes/server-function/custom-error' +import { Route as ServerFunctionNestedRouteImport } from './routes/server-function/nested' +import { Route as SsrDataOnlyRouteImport } from './routes/ssr/data-only' +import { Route as SsrNestedRouteImport } from './routes/ssr/nested' +import { Route as SsrStreamRouteImport } from './routes/ssr/stream' 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 SsrNestedRoute = SsrNestedRouteImport.update({ - id: '/ssr/nested', - path: '/ssr/nested', +const ServerFunctionCustomErrorRoute = + ServerFunctionCustomErrorRouteImport.update({ + id: '/server-function/custom-error', + path: '/server-function/custom-error', + getParentRoute: () => rootRouteImport, + } as any) +const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ + id: '/server-function/nested', + path: '/server-function/nested', getParentRoute: () => rootRouteImport, } as any) const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ @@ -36,17 +37,16 @@ const SsrDataOnlyRoute = SsrDataOnlyRouteImport.update({ path: '/ssr/data-only', getParentRoute: () => rootRouteImport, } as any) -const ServerFunctionNestedRoute = ServerFunctionNestedRouteImport.update({ - id: '/server-function/nested', - path: '/server-function/nested', +const SsrNestedRoute = SsrNestedRouteImport.update({ + id: '/ssr/nested', + path: '/ssr/nested', + getParentRoute: () => rootRouteImport, +} as any) +const SsrStreamRoute = SsrStreamRouteImport.update({ + id: '/ssr/stream', + path: '/ssr/stream', 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 @@ -118,18 +118,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/ssr/stream': { - id: '/ssr/stream' - path: '/ssr/stream' - fullPath: '/ssr/stream' - preLoaderRoute: typeof SsrStreamRouteImport + '/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 } - '/ssr/nested': { - id: '/ssr/nested' - path: '/ssr/nested' - fullPath: '/ssr/nested' - preLoaderRoute: typeof SsrNestedRouteImport + '/server-function/nested': { + id: '/server-function/nested' + path: '/server-function/nested' + fullPath: '/server-function/nested' + preLoaderRoute: typeof ServerFunctionNestedRouteImport parentRoute: typeof rootRouteImport } '/ssr/data-only': { @@ -139,18 +139,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof SsrDataOnlyRouteImport parentRoute: typeof rootRouteImport } - '/server-function/nested': { - id: '/server-function/nested' - path: '/server-function/nested' - fullPath: '/server-function/nested' - preLoaderRoute: typeof ServerFunctionNestedRouteImport + '/ssr/nested': { + id: '/ssr/nested' + path: '/ssr/nested' + fullPath: '/ssr/nested' + preLoaderRoute: typeof SsrNestedRouteImport 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 + '/ssr/stream': { + id: '/ssr/stream' + path: '/ssr/stream' + fullPath: '/ssr/stream' + preLoaderRoute: typeof SsrStreamRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/vue-start/server-functions/package.json b/e2e/vue-start/server-functions/package.json index 2e84c6238e..7c631b1fcc 100644 --- a/e2e/vue-start/server-functions/package.json +++ b/e2e/vue-start/server-functions/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", @@ -34,7 +34,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/server-functions/src/routeTree.gen.ts b/e2e/vue-start/server-functions/src/routeTree.gen.ts index 43a6eb4ae6..96e5b7ba29 100644 --- a/e2e/vue-start/server-functions/src/routeTree.gen.ts +++ b/e2e/vue-start/server-functions/src/routeTree.gen.ts @@ -9,64 +9,64 @@ // 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 SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' -import { Route as StatusRouteImport } from './routes/status' -import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' -import { Route as ReturnNullRouteImport } from './routes/return-null' -import { Route as RawResponseRouteImport } from './routes/raw-response' -import { Route as MultipartRouteImport } from './routes/multipart' -import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' -import { Route as HeadersRouteImport } from './routes/headers' -import { Route as EnvOnlyRouteImport } from './routes/env-only' -import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' -import { Route as ConsistentRouteImport } from './routes/consistent' -import { Route as AbortSignalRouteImport } from './routes/abort-signal' import { Route as IndexRouteImport } from './routes/index' -import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' -import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' -import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' -import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' -import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' -import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/index' -import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' -import { Route as FactoryIndexRouteImport } from './routes/factory/index' +import { Route as AbortSignalRouteImport } from './routes/abort-signal' +import { Route as ConsistentRouteImport } from './routes/consistent' +import { Route as DeadCodePreserveRouteImport } from './routes/dead-code-preserve' +import { Route as EnvOnlyRouteImport } from './routes/env-only' +import { Route as HeadersRouteImport } from './routes/headers' +import { Route as IsomorphicFnsRouteImport } from './routes/isomorphic-fns' +import { Route as MultipartRouteImport } from './routes/multipart' +import { Route as RawResponseRouteImport } from './routes/raw-response' +import { Route as ReturnNullRouteImport } from './routes/return-null' +import { Route as SerializeFormDataRouteImport } from './routes/serialize-form-data' +import { Route as StatusRouteImport } from './routes/status' +import { Route as SubmitPostFormdataRouteImport } from './routes/submit-post-formdata' import { Route as CookiesIndexRouteImport } from './routes/cookies/index' -import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' -import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' -import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' -import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' -import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' import { Route as CookiesSetRouteImport } from './routes/cookies/set' +import { Route as FactoryIndexRouteImport } from './routes/factory/index' +import { Route as FormdataRedirectIndexRouteImport } from './routes/formdata-redirect/index' +import { Route as FunctionMetadataIndexRouteImport } from './routes/function-metadata/index' +import { Route as FunctionMethodIndexRouteImport } from './routes/function-method/index' +import { Route as MiddlewareIndexRouteImport } from './routes/middleware/index' +import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' +import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware' +import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn' +import { Route as PrimitivesIndexRouteImport } from './routes/primitives/index' +import { Route as RedirectTestSsrIndexRouteImport } from './routes/redirect-test-ssr/index' +import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-test-ssr/target' +import { Route as RedirectTestIndexRouteImport } from './routes/redirect-test/index' +import { Route as RedirectTestTargetRouteImport } from './routes/redirect-test/target' import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name' -const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ - id: '/submit-post-formdata', - path: '/submit-post-formdata', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const StatusRoute = StatusRouteImport.update({ - id: '/status', - path: '/status', +const AbortSignalRoute = AbortSignalRouteImport.update({ + id: '/abort-signal', + path: '/abort-signal', getParentRoute: () => rootRouteImport, } as any) -const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ - id: '/serialize-form-data', - path: '/serialize-form-data', +const ConsistentRoute = ConsistentRouteImport.update({ + id: '/consistent', + path: '/consistent', getParentRoute: () => rootRouteImport, } as any) -const ReturnNullRoute = ReturnNullRouteImport.update({ - id: '/return-null', - path: '/return-null', +const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ + id: '/dead-code-preserve', + path: '/dead-code-preserve', getParentRoute: () => rootRouteImport, } as any) -const RawResponseRoute = RawResponseRouteImport.update({ - id: '/raw-response', - path: '/raw-response', +const EnvOnlyRoute = EnvOnlyRouteImport.update({ + id: '/env-only', + path: '/env-only', getParentRoute: () => rootRouteImport, } as any) -const MultipartRoute = MultipartRouteImport.update({ - id: '/multipart', - path: '/multipart', +const HeadersRoute = HeadersRouteImport.update({ + id: '/headers', + path: '/headers', getParentRoute: () => rootRouteImport, } as any) const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ @@ -74,59 +74,54 @@ const IsomorphicFnsRoute = IsomorphicFnsRouteImport.update({ path: '/isomorphic-fns', getParentRoute: () => rootRouteImport, } as any) -const HeadersRoute = HeadersRouteImport.update({ - id: '/headers', - path: '/headers', - getParentRoute: () => rootRouteImport, -} as any) -const EnvOnlyRoute = EnvOnlyRouteImport.update({ - id: '/env-only', - path: '/env-only', +const MultipartRoute = MultipartRouteImport.update({ + id: '/multipart', + path: '/multipart', getParentRoute: () => rootRouteImport, } as any) -const DeadCodePreserveRoute = DeadCodePreserveRouteImport.update({ - id: '/dead-code-preserve', - path: '/dead-code-preserve', +const RawResponseRoute = RawResponseRouteImport.update({ + id: '/raw-response', + path: '/raw-response', getParentRoute: () => rootRouteImport, } as any) -const ConsistentRoute = ConsistentRouteImport.update({ - id: '/consistent', - path: '/consistent', +const ReturnNullRoute = ReturnNullRouteImport.update({ + id: '/return-null', + path: '/return-null', getParentRoute: () => rootRouteImport, } as any) -const AbortSignalRoute = AbortSignalRouteImport.update({ - id: '/abort-signal', - path: '/abort-signal', +const SerializeFormDataRoute = SerializeFormDataRouteImport.update({ + id: '/serialize-form-data', + path: '/serialize-form-data', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const StatusRoute = StatusRouteImport.update({ + id: '/status', + path: '/status', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ - id: '/redirect-test/', - path: '/redirect-test/', +const SubmitPostFormdataRoute = SubmitPostFormdataRouteImport.update({ + id: '/submit-post-formdata', + path: '/submit-post-formdata', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ - id: '/redirect-test-ssr/', - path: '/redirect-test-ssr/', +const CookiesIndexRoute = CookiesIndexRouteImport.update({ + id: '/cookies/', + path: '/cookies/', getParentRoute: () => rootRouteImport, } as any) -const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ - id: '/primitives/', - path: '/primitives/', +const CookiesSetRoute = CookiesSetRouteImport.update({ + id: '/cookies/set', + path: '/cookies/set', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ - id: '/middleware/', - path: '/middleware/', +const FactoryIndexRoute = FactoryIndexRouteImport.update({ + id: '/factory/', + path: '/factory/', getParentRoute: () => rootRouteImport, } as any) -const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ - id: '/function-method/', - path: '/function-method/', +const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ + id: '/formdata-redirect/', + path: '/formdata-redirect/', getParentRoute: () => rootRouteImport, } as any) const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ @@ -134,24 +129,41 @@ const FunctionMetadataIndexRoute = FunctionMetadataIndexRouteImport.update({ path: '/function-metadata/', getParentRoute: () => rootRouteImport, } as any) -const FormdataRedirectIndexRoute = FormdataRedirectIndexRouteImport.update({ - id: '/formdata-redirect/', - path: '/formdata-redirect/', +const FunctionMethodIndexRoute = FunctionMethodIndexRouteImport.update({ + id: '/function-method/', + path: '/function-method/', getParentRoute: () => rootRouteImport, } as any) -const FactoryIndexRoute = FactoryIndexRouteImport.update({ - id: '/factory/', - path: '/factory/', +const MiddlewareIndexRoute = MiddlewareIndexRouteImport.update({ + id: '/middleware/', + path: '/middleware/', getParentRoute: () => rootRouteImport, } as any) -const CookiesIndexRoute = CookiesIndexRouteImport.update({ - id: '/cookies/', - path: '/cookies/', +const MiddlewareClientMiddlewareRouterRoute = + MiddlewareClientMiddlewareRouterRouteImport.update({ + id: '/middleware/client-middleware-router', + path: '/middleware/client-middleware-router', + getParentRoute: () => rootRouteImport, + } as any) +const MiddlewareRequestMiddlewareRoute = + MiddlewareRequestMiddlewareRouteImport.update({ + id: '/middleware/request-middleware', + path: '/middleware/request-middleware', + getParentRoute: () => rootRouteImport, + } as any) +const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ + id: '/middleware/send-serverFn', + path: '/middleware/send-serverFn', getParentRoute: () => rootRouteImport, } as any) -const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ - id: '/redirect-test/target', - path: '/redirect-test/target', +const PrimitivesIndexRoute = PrimitivesIndexRouteImport.update({ + id: '/primitives/', + path: '/primitives/', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectTestSsrIndexRoute = RedirectTestSsrIndexRouteImport.update({ + id: '/redirect-test-ssr/', + path: '/redirect-test-ssr/', getParentRoute: () => rootRouteImport, } as any) const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ @@ -159,26 +171,14 @@ const RedirectTestSsrTargetRoute = RedirectTestSsrTargetRouteImport.update({ path: '/redirect-test-ssr/target', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareSendServerFnRoute = MiddlewareSendServerFnRouteImport.update({ - id: '/middleware/send-serverFn', - path: '/middleware/send-serverFn', +const RedirectTestIndexRoute = RedirectTestIndexRouteImport.update({ + id: '/redirect-test/', + path: '/redirect-test/', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareRequestMiddlewareRoute = - MiddlewareRequestMiddlewareRouteImport.update({ - id: '/middleware/request-middleware', - path: '/middleware/request-middleware', - getParentRoute: () => rootRouteImport, - } as any) -const MiddlewareClientMiddlewareRouterRoute = - MiddlewareClientMiddlewareRouterRouteImport.update({ - id: '/middleware/client-middleware-router', - path: '/middleware/client-middleware-router', - getParentRoute: () => rootRouteImport, - } as any) -const CookiesSetRoute = CookiesSetRouteImport.update({ - id: '/cookies/set', - path: '/cookies/set', +const RedirectTestTargetRoute = RedirectTestTargetRouteImport.update({ + id: '/redirect-test/target', + path: '/redirect-test/target', getParentRoute: () => rootRouteImport, } as any) const FormdataRedirectTargetNameRoute = @@ -412,46 +412,46 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/submit-post-formdata': { - id: '/submit-post-formdata' - path: '/submit-post-formdata' - fullPath: '/submit-post-formdata' - preLoaderRoute: typeof SubmitPostFormdataRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/status': { - id: '/status' - path: '/status' - fullPath: '/status' - preLoaderRoute: typeof StatusRouteImport + '/abort-signal': { + id: '/abort-signal' + path: '/abort-signal' + fullPath: '/abort-signal' + preLoaderRoute: typeof AbortSignalRouteImport parentRoute: typeof rootRouteImport } - '/serialize-form-data': { - id: '/serialize-form-data' - path: '/serialize-form-data' - fullPath: '/serialize-form-data' - preLoaderRoute: typeof SerializeFormDataRouteImport + '/consistent': { + id: '/consistent' + path: '/consistent' + fullPath: '/consistent' + preLoaderRoute: typeof ConsistentRouteImport parentRoute: typeof rootRouteImport } - '/return-null': { - id: '/return-null' - path: '/return-null' - fullPath: '/return-null' - preLoaderRoute: typeof ReturnNullRouteImport + '/dead-code-preserve': { + id: '/dead-code-preserve' + path: '/dead-code-preserve' + fullPath: '/dead-code-preserve' + preLoaderRoute: typeof DeadCodePreserveRouteImport parentRoute: typeof rootRouteImport } - '/raw-response': { - id: '/raw-response' - path: '/raw-response' - fullPath: '/raw-response' - preLoaderRoute: typeof RawResponseRouteImport + '/env-only': { + id: '/env-only' + path: '/env-only' + fullPath: '/env-only' + preLoaderRoute: typeof EnvOnlyRouteImport parentRoute: typeof rootRouteImport } - '/multipart': { - id: '/multipart' - path: '/multipart' - fullPath: '/multipart' - preLoaderRoute: typeof MultipartRouteImport + '/headers': { + id: '/headers' + path: '/headers' + fullPath: '/headers' + preLoaderRoute: typeof HeadersRouteImport parentRoute: typeof rootRouteImport } '/isomorphic-fns': { @@ -461,81 +461,74 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IsomorphicFnsRouteImport parentRoute: typeof rootRouteImport } - '/headers': { - id: '/headers' - path: '/headers' - fullPath: '/headers' - preLoaderRoute: typeof HeadersRouteImport - parentRoute: typeof rootRouteImport - } - '/env-only': { - id: '/env-only' - path: '/env-only' - fullPath: '/env-only' - preLoaderRoute: typeof EnvOnlyRouteImport + '/multipart': { + id: '/multipart' + path: '/multipart' + fullPath: '/multipart' + preLoaderRoute: typeof MultipartRouteImport parentRoute: typeof rootRouteImport } - '/dead-code-preserve': { - id: '/dead-code-preserve' - path: '/dead-code-preserve' - fullPath: '/dead-code-preserve' - preLoaderRoute: typeof DeadCodePreserveRouteImport + '/raw-response': { + id: '/raw-response' + path: '/raw-response' + fullPath: '/raw-response' + preLoaderRoute: typeof RawResponseRouteImport parentRoute: typeof rootRouteImport } - '/consistent': { - id: '/consistent' - path: '/consistent' - fullPath: '/consistent' - preLoaderRoute: typeof ConsistentRouteImport + '/return-null': { + id: '/return-null' + path: '/return-null' + fullPath: '/return-null' + preLoaderRoute: typeof ReturnNullRouteImport parentRoute: typeof rootRouteImport } - '/abort-signal': { - id: '/abort-signal' - path: '/abort-signal' - fullPath: '/abort-signal' - preLoaderRoute: typeof AbortSignalRouteImport + '/serialize-form-data': { + id: '/serialize-form-data' + path: '/serialize-form-data' + fullPath: '/serialize-form-data' + preLoaderRoute: typeof SerializeFormDataRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/status': { + id: '/status' + path: '/status' + fullPath: '/status' + preLoaderRoute: typeof StatusRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/': { - id: '/redirect-test/' - path: '/redirect-test' - fullPath: '/redirect-test/' - preLoaderRoute: typeof RedirectTestIndexRouteImport + '/submit-post-formdata': { + id: '/submit-post-formdata' + path: '/submit-post-formdata' + fullPath: '/submit-post-formdata' + preLoaderRoute: typeof SubmitPostFormdataRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/': { - id: '/redirect-test-ssr/' - path: '/redirect-test-ssr' - fullPath: '/redirect-test-ssr/' - preLoaderRoute: typeof RedirectTestSsrIndexRouteImport + '/cookies/': { + id: '/cookies/' + path: '/cookies' + fullPath: '/cookies/' + preLoaderRoute: typeof CookiesIndexRouteImport parentRoute: typeof rootRouteImport } - '/primitives/': { - id: '/primitives/' - path: '/primitives' - fullPath: '/primitives/' - preLoaderRoute: typeof PrimitivesIndexRouteImport + '/cookies/set': { + id: '/cookies/set' + path: '/cookies/set' + fullPath: '/cookies/set' + preLoaderRoute: typeof CookiesSetRouteImport parentRoute: typeof rootRouteImport } - '/middleware/': { - id: '/middleware/' - path: '/middleware' - fullPath: '/middleware/' - preLoaderRoute: typeof MiddlewareIndexRouteImport + '/factory/': { + id: '/factory/' + path: '/factory' + fullPath: '/factory/' + preLoaderRoute: typeof FactoryIndexRouteImport parentRoute: typeof rootRouteImport } - '/function-method/': { - id: '/function-method/' - path: '/function-method' - fullPath: '/function-method/' - preLoaderRoute: typeof FunctionMethodIndexRouteImport + '/formdata-redirect/': { + id: '/formdata-redirect/' + path: '/formdata-redirect' + fullPath: '/formdata-redirect/' + preLoaderRoute: typeof FormdataRedirectIndexRouteImport parentRoute: typeof rootRouteImport } '/function-metadata/': { @@ -545,39 +538,32 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof FunctionMetadataIndexRouteImport parentRoute: typeof rootRouteImport } - '/formdata-redirect/': { - id: '/formdata-redirect/' - path: '/formdata-redirect' - fullPath: '/formdata-redirect/' - preLoaderRoute: typeof FormdataRedirectIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/factory/': { - id: '/factory/' - path: '/factory' - fullPath: '/factory/' - preLoaderRoute: typeof FactoryIndexRouteImport + '/function-method/': { + id: '/function-method/' + path: '/function-method' + fullPath: '/function-method/' + preLoaderRoute: typeof FunctionMethodIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/': { - id: '/cookies/' - path: '/cookies' - fullPath: '/cookies/' - preLoaderRoute: typeof CookiesIndexRouteImport + '/middleware/': { + id: '/middleware/' + path: '/middleware' + fullPath: '/middleware/' + preLoaderRoute: typeof MiddlewareIndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test/target': { - id: '/redirect-test/target' - path: '/redirect-test/target' - fullPath: '/redirect-test/target' - preLoaderRoute: typeof RedirectTestTargetRouteImport + '/middleware/client-middleware-router': { + id: '/middleware/client-middleware-router' + path: '/middleware/client-middleware-router' + fullPath: '/middleware/client-middleware-router' + preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport parentRoute: typeof rootRouteImport } - '/redirect-test-ssr/target': { - id: '/redirect-test-ssr/target' - path: '/redirect-test-ssr/target' - fullPath: '/redirect-test-ssr/target' - preLoaderRoute: typeof RedirectTestSsrTargetRouteImport + '/middleware/request-middleware': { + id: '/middleware/request-middleware' + path: '/middleware/request-middleware' + fullPath: '/middleware/request-middleware' + preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport parentRoute: typeof rootRouteImport } '/middleware/send-serverFn': { @@ -587,25 +573,39 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof MiddlewareSendServerFnRouteImport parentRoute: typeof rootRouteImport } - '/middleware/request-middleware': { - id: '/middleware/request-middleware' - path: '/middleware/request-middleware' - fullPath: '/middleware/request-middleware' - preLoaderRoute: typeof MiddlewareRequestMiddlewareRouteImport + '/primitives/': { + id: '/primitives/' + path: '/primitives' + fullPath: '/primitives/' + preLoaderRoute: typeof PrimitivesIndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware/client-middleware-router': { - id: '/middleware/client-middleware-router' - path: '/middleware/client-middleware-router' - fullPath: '/middleware/client-middleware-router' - preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport + '/redirect-test-ssr/': { + id: '/redirect-test-ssr/' + path: '/redirect-test-ssr' + fullPath: '/redirect-test-ssr/' + preLoaderRoute: typeof RedirectTestSsrIndexRouteImport parentRoute: typeof rootRouteImport } - '/cookies/set': { - id: '/cookies/set' - path: '/cookies/set' - fullPath: '/cookies/set' - preLoaderRoute: typeof CookiesSetRouteImport + '/redirect-test-ssr/target': { + id: '/redirect-test-ssr/target' + path: '/redirect-test-ssr/target' + fullPath: '/redirect-test-ssr/target' + preLoaderRoute: typeof RedirectTestSsrTargetRouteImport + parentRoute: typeof rootRouteImport + } + '/redirect-test/': { + id: '/redirect-test/' + path: '/redirect-test' + fullPath: '/redirect-test/' + preLoaderRoute: typeof RedirectTestIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/redirect-test/target': { + id: '/redirect-test/target' + path: '/redirect-test/target' + fullPath: '/redirect-test/target' + preLoaderRoute: typeof RedirectTestTargetRouteImport parentRoute: typeof rootRouteImport } '/formdata-redirect/target/$name': { diff --git a/e2e/vue-start/server-routes/package.json b/e2e/vue-start/server-routes/package.json index 6e5c7cdced..4407103e81 100644 --- a/e2e/vue-start/server-routes/package.json +++ b/e2e/vue-start/server-routes/package.json @@ -24,7 +24,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/js-cookie": "^3.0.6", @@ -34,7 +34,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/server-routes/src/routeTree.gen.ts b/e2e/vue-start/server-routes/src/routeTree.gen.ts index 7d9d463772..603560da2a 100644 --- a/e2e/vue-start/server-routes/src/routeTree.gen.ts +++ b/e2e/vue-start/server-routes/src/routeTree.gen.ts @@ -9,16 +9,21 @@ // 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 MergeMiddlewareContextRouteImport } from './routes/merge-middleware-context' import { Route as MethodsRouteRouteImport } from './routes/methods/route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' +import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' import { Route as MethodsIndexRouteImport } from './routes/methods/index' import { Route as MethodsOnlyAnyRouteImport } from './routes/methods/only-any' -import { Route as ApiOnlyAnyRouteImport } from './routes/api/only-any' -import { Route as ApiMiddlewareContextRouteImport } from './routes/api/middleware-context' import { Route as ApiParamsFooRouteRouteImport } from './routes/api/params/$foo/route' import { Route as ApiParamsFooBarRouteImport } from './routes/api/params/$foo/$bar' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const MergeMiddlewareContextRoute = MergeMiddlewareContextRouteImport.update({ id: '/merge-middleware-context', path: '/merge-middleware-context', @@ -29,9 +34,14 @@ const MethodsRouteRoute = MethodsRouteRouteImport.update({ path: '/methods', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ + id: '/api/middleware-context', + path: '/api/middleware-context', + getParentRoute: () => rootRouteImport, +} as any) +const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ + id: '/api/only-any', + path: '/api/only-any', getParentRoute: () => rootRouteImport, } as any) const MethodsIndexRoute = MethodsIndexRouteImport.update({ @@ -44,16 +54,6 @@ const MethodsOnlyAnyRoute = MethodsOnlyAnyRouteImport.update({ path: '/only-any', getParentRoute: () => MethodsRouteRoute, } as any) -const ApiOnlyAnyRoute = ApiOnlyAnyRouteImport.update({ - id: '/api/only-any', - path: '/api/only-any', - getParentRoute: () => rootRouteImport, -} as any) -const ApiMiddlewareContextRoute = ApiMiddlewareContextRouteImport.update({ - id: '/api/middleware-context', - path: '/api/middleware-context', - getParentRoute: () => rootRouteImport, -} as any) const ApiParamsFooRouteRoute = ApiParamsFooRouteRouteImport.update({ id: '/api/params/$foo', path: '/api/params/$foo', @@ -144,6 +144,13 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/merge-middleware-context': { id: '/merge-middleware-context' path: '/merge-middleware-context' @@ -158,11 +165,18 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof MethodsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/api/middleware-context': { + id: '/api/middleware-context' + path: '/api/middleware-context' + fullPath: '/api/middleware-context' + preLoaderRoute: typeof ApiMiddlewareContextRouteImport + parentRoute: typeof rootRouteImport + } + '/api/only-any': { + id: '/api/only-any' + path: '/api/only-any' + fullPath: '/api/only-any' + preLoaderRoute: typeof ApiOnlyAnyRouteImport parentRoute: typeof rootRouteImport } '/methods/': { @@ -179,20 +193,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof MethodsOnlyAnyRouteImport parentRoute: typeof MethodsRouteRoute } - '/api/only-any': { - id: '/api/only-any' - path: '/api/only-any' - fullPath: '/api/only-any' - preLoaderRoute: typeof ApiOnlyAnyRouteImport - parentRoute: typeof rootRouteImport - } - '/api/middleware-context': { - id: '/api/middleware-context' - path: '/api/middleware-context' - fullPath: '/api/middleware-context' - preLoaderRoute: typeof ApiMiddlewareContextRouteImport - parentRoute: typeof rootRouteImport - } '/api/params/$foo': { id: '/api/params/$foo' path: '/api/params/$foo' diff --git a/e2e/vue-start/spa-mode/package.json b/e2e/vue-start/spa-mode/package.json index 1795d2d5c4..b33ed9c859 100644 --- a/e2e/vue-start/spa-mode/package.json +++ b/e2e/vue-start/spa-mode/package.json @@ -24,7 +24,8 @@ "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/spa-mode/src/routeTree.gen.ts b/e2e/vue-start/spa-mode/src/routeTree.gen.ts index 474705b7e7..0d44b648ee 100644 --- a/e2e/vue-start/spa-mode/src/routeTree.gen.ts +++ b/e2e/vue-start/spa-mode/src/routeTree.gen.ts @@ -9,20 +9,20 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', path: '/$postId', @@ -60,13 +60,6 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -74,6 +67,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' diff --git a/e2e/vue-start/start-manifest/package.json b/e2e/vue-start/start-manifest/package.json index 841aa5da85..dbf889db71 100644 --- a/e2e/vue-start/start-manifest/package.json +++ b/e2e/vue-start/start-manifest/package.json @@ -15,12 +15,13 @@ "vue": "^3.5.16" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", "@vitejs/plugin-vue-jsx": "^5.1.5", "srvx": "^0.11.9", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "nx": { diff --git a/e2e/vue-start/start-manifest/src/routeTree.gen.ts b/e2e/vue-start/start-manifest/src/routeTree.gen.ts index 357d3be62b..fc42a1e404 100644 --- a/e2e/vue-start/start-manifest/src/routeTree.gen.ts +++ b/e2e/vue-start/start-manifest/src/routeTree.gen.ts @@ -9,54 +9,49 @@ // 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 SharedCRouteImport } from './routes/shared-c' -import { Route as SharedBRouteImport } from './routes/shared-b' -import { Route as SharedARouteImport } from './routes/shared-a' -import { Route as R6RouteImport } from './routes/r6' -import { Route as R5RouteImport } from './routes/r5' -import { Route as R4RouteImport } from './routes/r4' -import { Route as R3RouteImport } from './routes/r3' -import { Route as R2RouteImport } from './routes/r2' -import { Route as R1RouteImport } from './routes/r1' -import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' -import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' -import { Route as BRouteImport } from './routes/b' -import { Route as ARouteImport } from './routes/a' import { Route as IndexRouteImport } from './routes/index' +import { Route as ARouteImport } from './routes/a' +import { Route as BRouteImport } from './routes/b' +import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy' +import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static' +import { Route as R1RouteImport } from './routes/r1' +import { Route as R2RouteImport } from './routes/r2' +import { Route as R3RouteImport } from './routes/r3' +import { Route as R4RouteImport } from './routes/r4' +import { Route as R5RouteImport } from './routes/r5' +import { Route as R6RouteImport } from './routes/r6' +import { Route as SharedARouteImport } from './routes/shared-a' +import { Route as SharedBRouteImport } from './routes/shared-b' +import { Route as SharedCRouteImport } from './routes/shared-c' -const SharedCRoute = SharedCRouteImport.update({ - id: '/shared-c', - path: '/shared-c', - getParentRoute: () => rootRouteImport, -} as any) -const SharedBRoute = SharedBRouteImport.update({ - id: '/shared-b', - path: '/shared-b', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const SharedARoute = SharedARouteImport.update({ - id: '/shared-a', - path: '/shared-a', +const ARoute = ARouteImport.update({ + id: '/a', + path: '/a', getParentRoute: () => rootRouteImport, } as any) -const R6Route = R6RouteImport.update({ - id: '/r6', - path: '/r6', +const BRoute = BRouteImport.update({ + id: '/b', + path: '/b', getParentRoute: () => rootRouteImport, } as any) -const R5Route = R5RouteImport.update({ - id: '/r5', - path: '/r5', +const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ + id: '/lazy-css-lazy', + path: '/lazy-css-lazy', getParentRoute: () => rootRouteImport, } as any) -const R4Route = R4RouteImport.update({ - id: '/r4', - path: '/r4', +const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ + id: '/lazy-css-static', + path: '/lazy-css-static', getParentRoute: () => rootRouteImport, } as any) -const R3Route = R3RouteImport.update({ - id: '/r3', - path: '/r3', +const R1Route = R1RouteImport.update({ + id: '/r1', + path: '/r1', getParentRoute: () => rootRouteImport, } as any) const R2Route = R2RouteImport.update({ @@ -64,34 +59,39 @@ const R2Route = R2RouteImport.update({ path: '/r2', getParentRoute: () => rootRouteImport, } as any) -const R1Route = R1RouteImport.update({ - id: '/r1', - path: '/r1', +const R3Route = R3RouteImport.update({ + id: '/r3', + path: '/r3', getParentRoute: () => rootRouteImport, } as any) -const LazyCssStaticRoute = LazyCssStaticRouteImport.update({ - id: '/lazy-css-static', - path: '/lazy-css-static', +const R4Route = R4RouteImport.update({ + id: '/r4', + path: '/r4', getParentRoute: () => rootRouteImport, } as any) -const LazyCssLazyRoute = LazyCssLazyRouteImport.update({ - id: '/lazy-css-lazy', - path: '/lazy-css-lazy', +const R5Route = R5RouteImport.update({ + id: '/r5', + path: '/r5', getParentRoute: () => rootRouteImport, } as any) -const BRoute = BRouteImport.update({ - id: '/b', - path: '/b', +const R6Route = R6RouteImport.update({ + id: '/r6', + path: '/r6', getParentRoute: () => rootRouteImport, } as any) -const ARoute = ARouteImport.update({ - id: '/a', - path: '/a', +const SharedARoute = SharedARouteImport.update({ + id: '/shared-a', + path: '/shared-a', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SharedBRoute = SharedBRouteImport.update({ + id: '/shared-b', + path: '/shared-b', + getParentRoute: () => rootRouteImport, +} as any) +const SharedCRoute = SharedCRouteImport.update({ + id: '/shared-c', + path: '/shared-c', getParentRoute: () => rootRouteImport, } as any) @@ -214,53 +214,46 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/shared-c': { - id: '/shared-c' - path: '/shared-c' - fullPath: '/shared-c' - preLoaderRoute: typeof SharedCRouteImport - parentRoute: typeof rootRouteImport - } - '/shared-b': { - id: '/shared-b' - path: '/shared-b' - fullPath: '/shared-b' - preLoaderRoute: typeof SharedBRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/shared-a': { - id: '/shared-a' - path: '/shared-a' - fullPath: '/shared-a' - preLoaderRoute: typeof SharedARouteImport + '/a': { + id: '/a' + path: '/a' + fullPath: '/a' + preLoaderRoute: typeof ARouteImport parentRoute: typeof rootRouteImport } - '/r6': { - id: '/r6' - path: '/r6' - fullPath: '/r6' - preLoaderRoute: typeof R6RouteImport + '/b': { + id: '/b' + path: '/b' + fullPath: '/b' + preLoaderRoute: typeof BRouteImport parentRoute: typeof rootRouteImport } - '/r5': { - id: '/r5' - path: '/r5' - fullPath: '/r5' - preLoaderRoute: typeof R5RouteImport + '/lazy-css-lazy': { + id: '/lazy-css-lazy' + path: '/lazy-css-lazy' + fullPath: '/lazy-css-lazy' + preLoaderRoute: typeof LazyCssLazyRouteImport parentRoute: typeof rootRouteImport } - '/r4': { - id: '/r4' - path: '/r4' - fullPath: '/r4' - preLoaderRoute: typeof R4RouteImport + '/lazy-css-static': { + id: '/lazy-css-static' + path: '/lazy-css-static' + fullPath: '/lazy-css-static' + preLoaderRoute: typeof LazyCssStaticRouteImport parentRoute: typeof rootRouteImport } - '/r3': { - id: '/r3' - path: '/r3' - fullPath: '/r3' - preLoaderRoute: typeof R3RouteImport + '/r1': { + id: '/r1' + path: '/r1' + fullPath: '/r1' + preLoaderRoute: typeof R1RouteImport parentRoute: typeof rootRouteImport } '/r2': { @@ -270,46 +263,53 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof R2RouteImport parentRoute: typeof rootRouteImport } - '/r1': { - id: '/r1' - path: '/r1' - fullPath: '/r1' - preLoaderRoute: typeof R1RouteImport + '/r3': { + id: '/r3' + path: '/r3' + fullPath: '/r3' + preLoaderRoute: typeof R3RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-static': { - id: '/lazy-css-static' - path: '/lazy-css-static' - fullPath: '/lazy-css-static' - preLoaderRoute: typeof LazyCssStaticRouteImport + '/r4': { + id: '/r4' + path: '/r4' + fullPath: '/r4' + preLoaderRoute: typeof R4RouteImport parentRoute: typeof rootRouteImport } - '/lazy-css-lazy': { - id: '/lazy-css-lazy' - path: '/lazy-css-lazy' - fullPath: '/lazy-css-lazy' - preLoaderRoute: typeof LazyCssLazyRouteImport + '/r5': { + id: '/r5' + path: '/r5' + fullPath: '/r5' + preLoaderRoute: typeof R5RouteImport parentRoute: typeof rootRouteImport } - '/b': { - id: '/b' - path: '/b' - fullPath: '/b' - preLoaderRoute: typeof BRouteImport + '/r6': { + id: '/r6' + path: '/r6' + fullPath: '/r6' + preLoaderRoute: typeof R6RouteImport parentRoute: typeof rootRouteImport } - '/a': { - id: '/a' - path: '/a' - fullPath: '/a' - preLoaderRoute: typeof ARouteImport + '/shared-a': { + id: '/shared-a' + path: '/shared-a' + fullPath: '/shared-a' + preLoaderRoute: typeof SharedARouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/shared-b': { + id: '/shared-b' + path: '/shared-b' + fullPath: '/shared-b' + preLoaderRoute: typeof SharedBRouteImport + parentRoute: typeof rootRouteImport + } + '/shared-c': { + id: '/shared-c' + path: '/shared-c' + fullPath: '/shared-c' + preLoaderRoute: typeof SharedCRouteImport parentRoute: typeof rootRouteImport } } diff --git a/e2e/vue-start/virtual-routes/package.json b/e2e/vue-start/virtual-routes/package.json index 41b1355414..efafbb2b35 100644 --- a/e2e/vue-start/virtual-routes/package.json +++ b/e2e/vue-start/virtual-routes/package.json @@ -22,7 +22,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -31,7 +31,8 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/virtual-routes/src/routeTree.gen.ts b/e2e/vue-start/virtual-routes/src/routeTree.gen.ts index 94664b2e5b..1a1bc2d27b 100644 --- a/e2e/vue-start/virtual-routes/src/routeTree.gen.ts +++ b/e2e/vue-start/virtual-routes/src/routeTree.gen.ts @@ -9,83 +9,83 @@ // 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 pipeRouteImport } from './routes/pipe' -import { Route as postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' +import { Route as pipeRouteImport } from './routes/pipe' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const pipeRoute = pipeRouteImport.update({ - id: '/special|pipe', - path: '/special|pipe', - getParentRoute: () => rootRouteImport, -} as any) -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, +const pipeRoute = pipeRouteImport.update({ + id: '/special|pipe', + path: '/special|pipe', + getParentRoute: () => rootRouteImport, } as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -179,18 +179,11 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/special|pipe': { - id: '/special|pipe' - path: '/special|pipe' - fullPath: '/special|pipe' - preLoaderRoute: typeof pipeRouteImport - parentRoute: typeof rootRouteImport - } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -200,19 +193,19 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute + '/special|pipe': { + id: '/special|pipe' + path: '/special|pipe' + fullPath: '/special|pipe' + preLoaderRoute: typeof pipeRouteImport + parentRoute: typeof rootRouteImport } '/_first/_second-layout': { id: '/_first/_second-layout' @@ -221,6 +214,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -228,12 +228,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + parentRoute: typeof postsPostsRoute } '/classic/hello/': { id: '/classic/hello/' @@ -242,13 +242,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -256,12 +249,12 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } '/_first/_second-layout/route-without-file/layout-a': { id: '/_first/_second-layout/route-without-file/layout-a' @@ -270,6 +263,13 @@ declare module '@tanstack/vue-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/e2e/vue-start/website/package.json b/e2e/vue-start/website/package.json index 8ec13f6dbc..9ddcbc13fe 100644 --- a/e2e/vue-start/website/package.json +++ b/e2e/vue-start/website/package.json @@ -21,7 +21,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.61.0", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", "@types/node": "^22.10.2", @@ -29,7 +29,8 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/e2e/vue-start/website/src/routeTree.gen.ts b/e2e/vue-start/website/src/routeTree.gen.ts index e7b58aae2a..0909725a4b 100644 --- a/e2e/vue-start/website/src/routeTree.gen.ts +++ b/e2e/vue-start/website/src/routeTree.gen.ts @@ -10,11 +10,11 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LibraryRouteImport } from './routes/_library' -import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as ProjectIndexRouteImport } from './routes/$project.index' +import { Route as LibraryIndexRouteImport } from './routes/_library.index' import { Route as LibraryProjectRouteImport } from './routes/_library.$project' -import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.index' import { Route as ProjectVersionDocsIndexRouteImport } from './routes/$project.$version.docs.index' +import { Route as LibraryProjectVersionIndexRouteImport } from './routes/_library.$project.$version.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 ProjectVersionDocsFrameworkFrameworkSplatRouteImport } from './routes/$project.$version.docs.framework.$framework.$' @@ -24,32 +24,32 @@ const LibraryRoute = LibraryRouteImport.update({ id: '/_library', getParentRoute: () => rootRouteImport, } as any) -const LibraryIndexRoute = LibraryIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => LibraryRoute, -} as any) const ProjectIndexRoute = ProjectIndexRouteImport.update({ id: '/$project/', path: '/$project/', getParentRoute: () => rootRouteImport, } as any) +const LibraryIndexRoute = LibraryIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LibraryRoute, +} as any) const LibraryProjectRoute = LibraryProjectRouteImport.update({ id: '/$project', path: '/$project', getParentRoute: () => LibraryRoute, } as any) +const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ + id: '/$project/$version/docs/', + path: '/$project/$version/docs/', + getParentRoute: () => rootRouteImport, +} as any) const LibraryProjectVersionIndexRoute = LibraryProjectVersionIndexRouteImport.update({ id: '/$version/', path: '/$version/', getParentRoute: () => LibraryProjectRoute, } as any) -const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexRouteImport.update({ - id: '/$project/$version/docs/', - path: '/$project/$version/docs/', - getParentRoute: () => rootRouteImport, -} as any) const ProjectVersionDocsFrameworkFrameworkRoute = ProjectVersionDocsFrameworkFrameworkRouteImport.update({ id: '/$project/$version/docs/framework/$framework', @@ -159,13 +159,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LibraryRouteImport parentRoute: typeof rootRouteImport } - '/_library/': { - id: '/_library/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof LibraryIndexRouteImport - parentRoute: typeof LibraryRoute - } '/$project/': { id: '/$project/' path: '/$project' @@ -173,6 +166,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ProjectIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/': { + id: '/_library/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof LibraryIndexRouteImport + parentRoute: typeof LibraryRoute + } '/_library/$project': { id: '/_library/$project' path: '/$project' @@ -180,13 +180,6 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof LibraryProjectRouteImport parentRoute: typeof LibraryRoute } - '/_library/$project/$version/': { - id: '/_library/$project/$version/' - path: '/$version' - fullPath: '/$project/$version/' - preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport - parentRoute: typeof LibraryProjectRoute - } '/$project/$version/docs/': { id: '/$project/$version/docs/' path: '/$project/$version/docs' @@ -194,6 +187,13 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof ProjectVersionDocsIndexRouteImport parentRoute: typeof rootRouteImport } + '/_library/$project/$version/': { + id: '/_library/$project/$version/' + path: '/$version' + fullPath: '/$project/$version/' + preLoaderRoute: typeof LibraryProjectVersionIndexRouteImport + parentRoute: typeof LibraryProjectRoute + } '/$project/$version/docs/framework/$framework': { id: '/$project/$version/docs/framework/$framework' path: '/$project/$version/docs/framework/$framework' diff --git a/examples/react/authenticated-routes-firebase/package.json b/examples/react/authenticated-routes-firebase/package.json index 58c677ed26..c6465c1940 100644 --- a/examples/react/authenticated-routes-firebase/package.json +++ b/examples/react/authenticated-routes-firebase/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "firebase": "^11.4.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/authenticated-routes-firebase/src/routeTree.gen.ts b/examples/react/authenticated-routes-firebase/src/routeTree.gen.ts index 0e81c27c1c..bcc454ab03 100644 --- a/examples/react/authenticated-routes-firebase/src/routeTree.gen.ts +++ b/examples/react/authenticated-routes-firebase/src/routeTree.gen.ts @@ -9,38 +9,38 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' -import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' +import { Route as AuthRouteImport } from './routes/_auth' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthDashboardRouteImport } from './routes/_auth.dashboard' +import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' import { Route as AuthInvoicesIndexRouteImport } from './routes/_auth.invoices.index' import { Route as AuthInvoicesInvoiceIdRouteImport } from './routes/_auth.invoices.$invoiceId' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => AuthRoute, -} as any) const AuthDashboardRoute = AuthDashboardRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => AuthRoute, } as any) +const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => AuthRoute, +} as any) const AuthInvoicesIndexRoute = AuthInvoicesIndexRouteImport.update({ id: '/', path: '/', @@ -107,11 +107,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -121,20 +121,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_auth/invoices': { - id: '/_auth/invoices' - path: '/invoices' - fullPath: '/invoices' - preLoaderRoute: typeof AuthInvoicesRouteImport - parentRoute: typeof AuthRoute - } '/_auth/dashboard': { id: '/_auth/dashboard' path: '/dashboard' @@ -142,6 +135,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthDashboardRouteImport parentRoute: typeof AuthRoute } + '/_auth/invoices': { + id: '/_auth/invoices' + path: '/invoices' + fullPath: '/invoices' + preLoaderRoute: typeof AuthInvoicesRouteImport + parentRoute: typeof AuthRoute + } '/_auth/invoices/': { id: '/_auth/invoices/' path: '/' diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index 5517800c01..7e2743b760 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/authenticated-routes/src/routeTree.gen.ts b/examples/react/authenticated-routes/src/routeTree.gen.ts index 0e81c27c1c..bcc454ab03 100644 --- a/examples/react/authenticated-routes/src/routeTree.gen.ts +++ b/examples/react/authenticated-routes/src/routeTree.gen.ts @@ -9,38 +9,38 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' -import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' +import { Route as AuthRouteImport } from './routes/_auth' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthDashboardRouteImport } from './routes/_auth.dashboard' +import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' import { Route as AuthInvoicesIndexRouteImport } from './routes/_auth.invoices.index' import { Route as AuthInvoicesInvoiceIdRouteImport } from './routes/_auth.invoices.$invoiceId' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => AuthRoute, -} as any) const AuthDashboardRoute = AuthDashboardRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => AuthRoute, } as any) +const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => AuthRoute, +} as any) const AuthInvoicesIndexRoute = AuthInvoicesIndexRouteImport.update({ id: '/', path: '/', @@ -107,11 +107,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -121,20 +121,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_auth/invoices': { - id: '/_auth/invoices' - path: '/invoices' - fullPath: '/invoices' - preLoaderRoute: typeof AuthInvoicesRouteImport - parentRoute: typeof AuthRoute - } '/_auth/dashboard': { id: '/_auth/dashboard' path: '/dashboard' @@ -142,6 +135,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthDashboardRouteImport parentRoute: typeof AuthRoute } + '/_auth/invoices': { + id: '/_auth/invoices' + path: '/invoices' + fullPath: '/invoices' + preLoaderRoute: typeof AuthInvoicesRouteImport + parentRoute: typeof AuthRoute + } '/_auth/invoices/': { id: '/_auth/invoices/' path: '/' diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index 97bfd6d8d4..69239de4e0 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-devtools-panel/package.json b/examples/react/basic-devtools-panel/package.json index 9ff1c8fefb..9f83a68648 100644 --- a/examples/react/basic-devtools-panel/package.json +++ b/examples/react/basic-devtools-panel/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query-devtools": "^5.67.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index bca55ff7d5..b13da4adca 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-file-based/src/routeTree.gen.ts b/examples/react/basic-file-based/src/routeTree.gen.ts index 9d28e9a236..f370ad5d03 100644 --- a/examples/react/basic-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-file-based/src/routeTree.gen.ts @@ -9,35 +9,40 @@ // 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 AnchorRouteImport } from './routes/anchor' +import { Route as IndexRouteImport } from './routes/index' import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as AnchorRouteImport } from './routes/anchor' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' -const AnchorRoute = AnchorRouteImport.update({ - id: '/anchor', - path: '/anchor', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) +const AnchorRoute = AnchorRouteImport.update({ + id: '/anchor', + path: '/anchor', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -48,10 +53,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -59,12 +65,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -129,11 +129,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/anchor': { - id: '/anchor' - path: '/anchor' - fullPath: '/anchor' - preLoaderRoute: typeof AnchorRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_pathlessLayout': { @@ -143,6 +143,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } + '/anchor': { + id: '/anchor' + path: '/anchor' + fullPath: '/anchor' + preLoaderRoute: typeof AnchorRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -150,12 +157,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -171,12 +178,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -185,13 +192,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/examples/react/basic-non-nested-devtools/package.json b/examples/react/basic-non-nested-devtools/package.json index fe6f2a04bd..0c747b097e 100644 --- a/examples/react/basic-non-nested-devtools/package.json +++ b/examples/react/basic-non-nested-devtools/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -21,7 +21,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index 501a22dcc3..aff759e64c 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -12,9 +12,9 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-react-query-file-based/src/routeTree.gen.ts b/examples/react/basic-react-query-file-based/src/routeTree.gen.ts index 28cfbec8ed..9676858c7c 100644 --- a/examples/react/basic-react-query-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-react-query-file-based/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, @@ -27,11 +32,11 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRouteRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,6 +117,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -131,12 +138,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -152,12 +159,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRouteRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index b56851bdac..443c16f8c8 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -12,8 +12,8 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index 3fa61c784f..c046f9e6d1 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -11,8 +11,8 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "compression": "^1.8.0", "express": "^5.2.1", "get-port": "^7.1.0", @@ -21,12 +21,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-devtools": "^1.167.1", "@types/express": "^5.0.6", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-ssr-file-based/src/routeTree.gen.ts b/examples/react/basic-ssr-file-based/src/routeTree.gen.ts index 4e61620e43..6f9f3f0f5e 100644 --- a/examples/react/basic-ssr-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-ssr-file-based/src/routeTree.gen.ts @@ -9,12 +9,17 @@ // 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 ErrorRouteImport } from './routes/error' import { Route as PostsRouteRouteImport } from './routes/posts/route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts/index' import { Route as PostsPostIdRouteImport } from './routes/posts/$postId' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const ErrorRoute = ErrorRouteImport.update({ id: '/error', path: '/error', @@ -25,11 +30,6 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -78,6 +78,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/error': { id: '/error' path: '/error' @@ -92,13 +99,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index b24243e276..f20318d88d 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -11,8 +11,8 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "compression": "^1.8.0", "express": "^5.2.1", "get-port": "^7.1.0", @@ -21,12 +21,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-devtools": "^1.167.1", "@types/express": "^5.0.6", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/basic-ssr-streaming-file-based/src/routeTree.gen.ts b/examples/react/basic-ssr-streaming-file-based/src/routeTree.gen.ts index 4e61620e43..6f9f3f0f5e 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-ssr-streaming-file-based/src/routeTree.gen.ts @@ -9,12 +9,17 @@ // 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 ErrorRouteImport } from './routes/error' import { Route as PostsRouteRouteImport } from './routes/posts/route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts/index' import { Route as PostsPostIdRouteImport } from './routes/posts/$postId' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const ErrorRoute = ErrorRouteImport.update({ id: '/error', path: '/error', @@ -25,11 +30,6 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -78,6 +78,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/error': { id: '/error' path: '/error' @@ -92,13 +99,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index 922c460906..b55c0b1bdf 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/virtual-file-routes": "^1.162.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -24,7 +24,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } 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 2fe7840f19..6745e373aa 100644 --- a/examples/react/basic-virtual-file-based/src/routeTree.gen.ts +++ b/examples/react/basic-virtual-file-based/src/routeTree.gen.ts @@ -9,77 +9,77 @@ // 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 postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, -} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -166,11 +166,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -180,20 +180,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute - } '/_first/_second-layout': { id: '/_first/_second-layout' path: '' @@ -201,6 +194,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -208,12 +208,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + parentRoute: typeof postsPostsRoute } '/classic/hello/': { id: '/classic/hello/' @@ -222,13 +222,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -236,12 +229,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } '/_first/_second-layout/route-without-file/layout-a': { id: '/_first/_second-layout/route-without-file/layout-a' @@ -250,6 +243,13 @@ declare module '@tanstack/react-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + 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 a24bf519d9..473dd8a81c 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/virtual-file-routes": "^1.162.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -24,7 +24,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } 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 947fd037d9..cda1f85f15 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 @@ -9,36 +9,31 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as PostsDetailsRouteImport } from './routes/posts/details' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsHomeRouteImport } from './routes/posts/home' -import { Route as PostsLetsGoIndexRouteImport } from './routes/posts/lets-go/index' -import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsDetailsRouteImport } from './routes/posts/details' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsLetsGoIndexRouteImport } from './routes/posts/lets-go/index' import { Route as PostsLetsGoDeeperHomeRouteImport } from './routes/posts/lets-go/deeper/home' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PostsDetailsRoute = PostsDetailsRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -48,20 +43,25 @@ const PostsHomeRoute = PostsHomeRouteImport.update({ path: '/', getParentRoute: () => PostsRoute, } as any) -const PostsLetsGoIndexRoute = PostsLetsGoIndexRouteImport.update({ - id: '/inception/', - path: '/inception/', +const PostsDetailsRoute = PostsDetailsRouteImport.update({ + id: '/$postId', + path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const PostsLetsGoIndexRoute = PostsLetsGoIndexRouteImport.update({ + id: '/inception/', + path: '/inception/', + getParentRoute: () => PostsRoute, } as any) const PostsLetsGoDeeperHomeRoute = PostsLetsGoDeeperHomeRouteImport.update({ id: '/inception/deeper/', @@ -143,11 +143,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -157,20 +157,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsDetailsRouteImport - parentRoute: typeof PostsRoute - } '/_layout/_layout-2': { id: '/_layout/_layout-2' path: '' @@ -185,13 +178,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsHomeRouteImport parentRoute: typeof PostsRoute } - '/posts/inception/': { - id: '/posts/inception/' - path: '/inception' - fullPath: '/posts/inception/' - preLoaderRoute: typeof PostsLetsGoIndexRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsDetailsRouteImport 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/layout-b': { id: '/_layout/_layout-2/layout-b' path: '/layout-b' @@ -199,12 +199,12 @@ 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 + '/posts/inception/': { + id: '/posts/inception/' + path: '/inception' + fullPath: '/posts/inception/' + preLoaderRoute: typeof PostsLetsGoIndexRouteImport + parentRoute: typeof PostsRoute } '/posts/inception/deeper/': { id: '/posts/inception/deeper/' diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index d245cc9f0e..c030b321f1 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -21,7 +21,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index b55fb41ccc..c36bb4fafa 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/i18n-paraglide/package.json b/examples/react/i18n-paraglide/package.json index d9a9602c02..f82d68e8cd 100644 --- a/examples/react/i18n-paraglide/package.json +++ b/examples/react/i18n-paraglide/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.2.2" @@ -22,7 +22,8 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "@inlang/paraglide-js": "^2.4.0" } diff --git a/examples/react/i18n-paraglide/src/routeTree.gen.ts b/examples/react/i18n-paraglide/src/routeTree.gen.ts index 59499d9fbf..12bc916ae4 100644 --- a/examples/react/i18n-paraglide/src/routeTree.gen.ts +++ b/examples/react/i18n-paraglide/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 2e18eb4358..c453ff25f6 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -24,7 +24,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/kitchen-sink-file-based/src/routeTree.gen.ts b/examples/react/kitchen-sink-file-based/src/routeTree.gen.ts index 1e29ba5794..03af4e1e58 100644 --- a/examples/react/kitchen-sink-file-based/src/routeTree.gen.ts +++ b/examples/react/kitchen-sink-file-based/src/routeTree.gen.ts @@ -9,81 +9,71 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as IndexRouteImport } from './routes/index' import { Route as AuthRouteImport } from './routes/_auth' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' -import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' -import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' -import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' -import { Route as AuthProfileRouteImport } from './routes/_auth.profile' +import { Route as LoginRouteImport } from './routes/login' import { Route as thisFolderIsNotInTheUrlRouteGroupRouteImport } from './routes/(this-folder-is-not-in-the-url)/route-group' -import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as AuthProfileRouteImport } from './routes/_auth.profile' +import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' +import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' -import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' -import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' +import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) const DashboardRouteRoute = DashboardRouteRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ - id: '/expensive/', - path: '/expensive/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const DashboardIndexRoute = DashboardIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardRouteRoute, -} as any) -const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutRoute, +const thisFolderIsNotInTheUrlRouteGroupRoute = + thisFolderIsNotInTheUrlRouteGroupRouteImport.update({ + id: '/(this-folder-is-not-in-the-url)/route-group', + path: '/route-group', + getParentRoute: () => rootRouteImport, + } as any) +const AuthProfileRoute = AuthProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => AuthRoute, } as any) const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutRoute, } as any) -const AuthProfileRoute = AuthProfileRouteImport.update({ - id: '/profile', - path: '/profile', - getParentRoute: () => AuthRoute, +const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutRoute, } as any) -const thisFolderIsNotInTheUrlRouteGroupRoute = - thisFolderIsNotInTheUrlRouteGroupRouteImport.update({ - id: '/(this-folder-is-not-in-the-url)/route-group', - path: '/route-group', - getParentRoute: () => rootRouteImport, - } as any) -const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ - id: '/users', - path: '/users', +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => DashboardRouteRoute, } as any) const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ @@ -91,27 +81,37 @@ const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ path: '/invoices', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardUsersRouteRoute, +const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => DashboardRouteRoute, +} as any) +const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ + id: '/expensive/', + path: '/expensive/', + getParentRoute: () => rootRouteImport, } as any) const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) -const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ - id: '/user', - path: '/user', - getParentRoute: () => DashboardUsersRouteRoute, -} as any) const DashboardInvoicesInvoiceIdRoute = DashboardInvoicesInvoiceIdRouteImport.update({ id: '/$invoiceId', path: '/$invoiceId', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) +const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) +const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ + id: '/user', + path: '/user', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -229,18 +229,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' + '/': { + id: '/' + path: '/' fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -250,6 +243,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard': { id: '/dashboard' path: '/dashboard' @@ -257,33 +257,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/expensive/': { - id: '/expensive/' - path: '/expensive' - fullPath: '/expensive/' - preLoaderRoute: typeof ExpensiveIndexRouteImport + '/(this-folder-is-not-in-the-url)/route-group': { + id: '/(this-folder-is-not-in-the-url)/route-group' + path: '/route-group' + fullPath: '/route-group' + preLoaderRoute: typeof thisFolderIsNotInTheUrlRouteGroupRouteImport parentRoute: typeof rootRouteImport } - '/dashboard/': { - id: '/dashboard/' - path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof DashboardIndexRouteImport - parentRoute: typeof DashboardRouteRoute - } - '/_pathlessLayout/route-b': { - id: '/_pathlessLayout/route-b' - path: '/route-b' - fullPath: '/route-b' - preLoaderRoute: typeof PathlessLayoutRouteBRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_auth/profile': { + id: '/_auth/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof AuthProfileRouteImport + parentRoute: typeof AuthRoute } '/_pathlessLayout/route-a': { id: '/_pathlessLayout/route-a' @@ -292,25 +285,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteARouteImport parentRoute: typeof PathlessLayoutRoute } - '/_auth/profile': { - id: '/_auth/profile' - path: '/profile' - fullPath: '/profile' - preLoaderRoute: typeof AuthProfileRouteImport - parentRoute: typeof AuthRoute - } - '/(this-folder-is-not-in-the-url)/route-group': { - id: '/(this-folder-is-not-in-the-url)/route-group' - path: '/route-group' - fullPath: '/route-group' - preLoaderRoute: typeof thisFolderIsNotInTheUrlRouteGroupRouteImport - parentRoute: typeof rootRouteImport + '/_pathlessLayout/route-b': { + id: '/_pathlessLayout/route-b' + path: '/route-b' + fullPath: '/route-b' + preLoaderRoute: typeof PathlessLayoutRouteBRouteImport + parentRoute: typeof PathlessLayoutRoute } - '/dashboard/users': { - id: '/dashboard/users' - path: '/users' - fullPath: '/dashboard/users' - preLoaderRoute: typeof DashboardUsersRouteRouteImport + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport parentRoute: typeof DashboardRouteRoute } '/dashboard/invoices': { @@ -320,12 +306,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardInvoicesRouteRouteImport parentRoute: typeof DashboardRouteRoute } - '/dashboard/users/': { - id: '/dashboard/users/' - path: '/' - fullPath: '/dashboard/users/' - preLoaderRoute: typeof DashboardUsersIndexRouteImport - parentRoute: typeof DashboardUsersRouteRoute + '/dashboard/users': { + id: '/dashboard/users' + path: '/users' + fullPath: '/dashboard/users' + preLoaderRoute: typeof DashboardUsersRouteRouteImport + parentRoute: typeof DashboardRouteRoute + } + '/expensive/': { + id: '/expensive/' + path: '/expensive' + fullPath: '/expensive/' + preLoaderRoute: typeof ExpensiveIndexRouteImport + parentRoute: typeof rootRouteImport } '/dashboard/invoices/': { id: '/dashboard/invoices/' @@ -334,13 +327,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardInvoicesIndexRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } - '/dashboard/users/user': { - id: '/dashboard/users/user' - path: '/user' - fullPath: '/dashboard/users/user' - preLoaderRoute: typeof DashboardUsersUserRouteImport - parentRoute: typeof DashboardUsersRouteRoute - } '/dashboard/invoices/$invoiceId': { id: '/dashboard/invoices/$invoiceId' path: '/$invoiceId' @@ -348,6 +334,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } + '/dashboard/users/': { + id: '/dashboard/users/' + path: '/' + fullPath: '/dashboard/users/' + preLoaderRoute: typeof DashboardUsersIndexRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } + '/dashboard/users/user': { + id: '/dashboard/users/user' + path: '/user' + fullPath: '/dashboard/users/user' + preLoaderRoute: typeof DashboardUsersUserRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } } } 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 f712e86a8f..66bce428ea 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -12,9 +12,9 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -26,7 +26,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/kitchen-sink-react-query-file-based/src/routeTree.gen.ts b/examples/react/kitchen-sink-react-query-file-based/src/routeTree.gen.ts index 66180a8554..dbce8cf604 100644 --- a/examples/react/kitchen-sink-react-query-file-based/src/routeTree.gen.ts +++ b/examples/react/kitchen-sink-react-query-file-based/src/routeTree.gen.ts @@ -9,108 +9,108 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as IndexRouteImport } from './routes/index' import { Route as AuthRouteImport } from './routes/_auth' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' -import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' -import { Route as FooBarRouteImport } from './routes/foo/bar' -import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' -import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthProfileRouteImport } from './routes/_auth.profile' -import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' +import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' -import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' +import { Route as FooBarRouteImport } from './routes/foo/bar' import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' -import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' +import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) const DashboardRouteRoute = DashboardRouteRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ - id: '/expensive/', - path: '/expensive/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const DashboardIndexRoute = DashboardIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardRouteRoute, +const AuthProfileRoute = AuthProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => AuthRoute, } as any) -const FooBarRoute = FooBarRouteImport.update({ - id: '/foo/bar', - path: '/foo/bar', - getParentRoute: () => rootRouteImport, +const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutRoute, } as any) const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ id: '/route-b', path: '/route-b', getParentRoute: () => PathlessLayoutRoute, } as any) -const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutRoute, +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardRouteRoute, } as any) -const AuthProfileRoute = AuthProfileRouteImport.update({ - id: '/profile', - path: '/profile', - getParentRoute: () => AuthRoute, +const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => DashboardRouteRoute, } as any) const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ id: '/users', path: '/users', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => DashboardRouteRoute, +const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ + id: '/expensive/', + path: '/expensive/', + getParentRoute: () => rootRouteImport, } as any) -const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardUsersRouteRoute, +const FooBarRoute = FooBarRouteImport.update({ + id: '/foo/bar', + path: '/foo/bar', + getParentRoute: () => rootRouteImport, } as any) const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) -const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ - id: '/user', - path: '/user', - getParentRoute: () => DashboardUsersRouteRoute, -} as any) const DashboardInvoicesInvoiceIdRoute = DashboardInvoicesInvoiceIdRouteImport.update({ id: '/$invoiceId', path: '/$invoiceId', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) +const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) +const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ + id: '/user', + path: '/user', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -228,18 +228,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' + '/': { + id: '/' + path: '/' fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -249,6 +242,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard': { id: '/dashboard' path: '/dashboard' @@ -256,33 +256,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/expensive/': { - id: '/expensive/' - path: '/expensive' - fullPath: '/expensive/' - preLoaderRoute: typeof ExpensiveIndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/dashboard/': { - id: '/dashboard/' - path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof DashboardIndexRouteImport - parentRoute: typeof DashboardRouteRoute + '/_auth/profile': { + id: '/_auth/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof AuthProfileRouteImport + parentRoute: typeof AuthRoute } - '/foo/bar': { - id: '/foo/bar' - path: '/foo/bar' - fullPath: '/foo/bar' - preLoaderRoute: typeof FooBarRouteImport - parentRoute: typeof rootRouteImport + '/_pathlessLayout/route-a': { + id: '/_pathlessLayout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutRoute } '/_pathlessLayout/route-b': { id: '/_pathlessLayout/route-b' @@ -291,19 +284,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutRoute } - '/_pathlessLayout/route-a': { - id: '/_pathlessLayout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutRoute + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport + parentRoute: typeof DashboardRouteRoute } - '/_auth/profile': { - id: '/_auth/profile' - path: '/profile' - fullPath: '/profile' - preLoaderRoute: typeof AuthProfileRouteImport - parentRoute: typeof AuthRoute + '/dashboard/invoices': { + id: '/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof DashboardInvoicesRouteRouteImport + parentRoute: typeof DashboardRouteRoute } '/dashboard/users': { id: '/dashboard/users' @@ -312,19 +305,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardUsersRouteRouteImport parentRoute: typeof DashboardRouteRoute } - '/dashboard/invoices': { - id: '/dashboard/invoices' - path: '/invoices' - fullPath: '/dashboard/invoices' - preLoaderRoute: typeof DashboardInvoicesRouteRouteImport - parentRoute: typeof DashboardRouteRoute + '/expensive/': { + id: '/expensive/' + path: '/expensive' + fullPath: '/expensive/' + preLoaderRoute: typeof ExpensiveIndexRouteImport + parentRoute: typeof rootRouteImport } - '/dashboard/users/': { - id: '/dashboard/users/' - path: '/' - fullPath: '/dashboard/users/' - preLoaderRoute: typeof DashboardUsersIndexRouteImport - parentRoute: typeof DashboardUsersRouteRoute + '/foo/bar': { + id: '/foo/bar' + path: '/foo/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof FooBarRouteImport + parentRoute: typeof rootRouteImport } '/dashboard/invoices/': { id: '/dashboard/invoices/' @@ -333,13 +326,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardInvoicesIndexRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } - '/dashboard/users/user': { - id: '/dashboard/users/user' - path: '/user' - fullPath: '/dashboard/users/user' - preLoaderRoute: typeof DashboardUsersUserRouteImport - parentRoute: typeof DashboardUsersRouteRoute - } '/dashboard/invoices/$invoiceId': { id: '/dashboard/invoices/$invoiceId' path: '/$invoiceId' @@ -347,6 +333,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } + '/dashboard/users/': { + id: '/dashboard/users/' + path: '/' + fullPath: '/dashboard/users/' + preLoaderRoute: typeof DashboardUsersIndexRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } + '/dashboard/users/user': { + id: '/dashboard/users/user' + path: '/user' + fullPath: '/dashboard/users/user' + preLoaderRoute: typeof DashboardUsersUserRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } } } diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index 3003408f70..3ce98536f4 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -12,8 +12,8 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index 4414b457f2..7bae6392fe 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 16fd095515..121a73a568 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -13,9 +13,9 @@ "dependencies": { "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -26,7 +26,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/large-file-based/src/routeTree.gen.ts b/examples/react/large-file-based/src/routeTree.gen.ts index 93a5be975c..8494f51d73 100644 --- a/examples/react/large-file-based/src/routeTree.gen.ts +++ b/examples/react/large-file-based/src/routeTree.gen.ts @@ -9,23 +9,18 @@ // 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 RelativeRouteImport } from './routes/relative' -import { Route as LinkPropsRouteImport } from './routes/linkProps' +import { Route as IndexRouteImport } from './routes/index' import { Route as AbsoluteRouteImport } from './routes/absolute' -import { Route as SearchRouteRouteImport } from './routes/search/route' +import { Route as LinkPropsRouteImport } from './routes/linkProps' import { Route as ParamsRouteRouteImport } from './routes/params/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' +import { Route as RelativeRouteImport } from './routes/relative' +import { Route as SearchRouteRouteImport } from './routes/search/route' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' +import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' -const RelativeRoute = RelativeRouteImport.update({ - id: '/relative', - path: '/relative', - getParentRoute: () => rootRouteImport, -} as any) -const LinkPropsRoute = LinkPropsRouteImport.update({ - id: '/linkProps', - path: '/linkProps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AbsoluteRoute = AbsoluteRouteImport.update({ @@ -33,9 +28,9 @@ const AbsoluteRoute = AbsoluteRouteImport.update({ path: '/absolute', getParentRoute: () => rootRouteImport, } as any) -const SearchRouteRoute = SearchRouteRouteImport.update({ - id: '/search', - path: '/search', +const LinkPropsRoute = LinkPropsRouteImport.update({ + id: '/linkProps', + path: '/linkProps', getParentRoute: () => rootRouteImport, } as any) const ParamsRouteRoute = ParamsRouteRouteImport.update({ @@ -43,21 +38,26 @@ const ParamsRouteRoute = ParamsRouteRouteImport.update({ path: '/params', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RelativeRoute = RelativeRouteImport.update({ + id: '/relative', + path: '/relative', getParentRoute: () => rootRouteImport, } as any) -const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ - id: '/searchPlaceholder', - path: '/searchPlaceholder', - getParentRoute: () => SearchRouteRoute, +const SearchRouteRoute = SearchRouteRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => rootRouteImport, } as any) const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderRouteImport.update({ id: '/$paramsPlaceholder', path: '/$paramsPlaceholder', getParentRoute: () => ParamsRouteRoute, } as any) +const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ + id: '/searchPlaceholder', + path: '/searchPlaceholder', + getParentRoute: () => SearchRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -134,18 +134,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/relative': { - id: '/relative' - path: '/relative' - fullPath: '/relative' - preLoaderRoute: typeof RelativeRouteImport - parentRoute: typeof rootRouteImport - } - '/linkProps': { - id: '/linkProps' - path: '/linkProps' - fullPath: '/linkProps' - preLoaderRoute: typeof LinkPropsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/absolute': { @@ -155,11 +148,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AbsoluteRouteImport parentRoute: typeof rootRouteImport } - '/search': { - id: '/search' - path: '/search' - fullPath: '/search' - preLoaderRoute: typeof SearchRouteRouteImport + '/linkProps': { + id: '/linkProps' + path: '/linkProps' + fullPath: '/linkProps' + preLoaderRoute: typeof LinkPropsRouteImport parentRoute: typeof rootRouteImport } '/params': { @@ -169,19 +162,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/relative': { + id: '/relative' + path: '/relative' + fullPath: '/relative' + preLoaderRoute: typeof RelativeRouteImport parentRoute: typeof rootRouteImport } - '/search/searchPlaceholder': { - id: '/search/searchPlaceholder' - path: '/searchPlaceholder' - fullPath: '/search/searchPlaceholder' - preLoaderRoute: typeof SearchSearchPlaceholderRouteImport - parentRoute: typeof SearchRouteRoute + '/search': { + id: '/search' + path: '/search' + fullPath: '/search' + preLoaderRoute: typeof SearchRouteRouteImport + parentRoute: typeof rootRouteImport } '/params/$paramsPlaceholder': { id: '/params/$paramsPlaceholder' @@ -190,6 +183,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsParamsPlaceholderRouteImport parentRoute: typeof ParamsRouteRoute } + '/search/searchPlaceholder': { + id: '/search/searchPlaceholder' + path: '/searchPlaceholder' + fullPath: '/search/searchPlaceholder' + preLoaderRoute: typeof SearchSearchPlaceholderRouteImport + parentRoute: typeof SearchRouteRoute + } } } diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index 76d00658a4..34b52ef958 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -12,8 +12,8 @@ "@radix-ui/react-dialog": "^1.1.6", "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index 757ae9f2e6..cb92d28276 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 300650c666..ad2e79b6ec 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.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "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 31a70cf36c..164630daea 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/quickstart-file-based/src/routeTree.gen.ts b/examples/react/quickstart-file-based/src/routeTree.gen.ts index 59499d9fbf..12bc916ae4 100644 --- a/examples/react/quickstart-file-based/src/routeTree.gen.ts +++ b/examples/react/quickstart-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index f3525defde..b758b8a56c 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -9,19 +9,20 @@ }, "dependencies": { "@tailwindcss/postcss": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "postcss": "^8.5.1", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwindcss": "^4.2.2" }, "devDependencies": { - "@rsbuild/core": "^2.0.11", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-react": "^2.0.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "typescript": "^6.0.2" + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/examples/react/quickstart-rspack-file-based/src/routeTree.gen.ts b/examples/react/quickstart-rspack-file-based/src/routeTree.gen.ts index 59499d9fbf..12bc916ae4 100644 --- a/examples/react/quickstart-rspack-file-based/src/routeTree.gen.ts +++ b/examples/react/quickstart-rspack-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index 4c778eab28..99386be401 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -7,20 +7,21 @@ "build": "webpack build && tsc --noEmit" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@swc/core": "^1.15.33", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "html-webpack-plugin": "^5.6.3", "react-refresh": "^0.18.0", "swc-loader": "^0.2.6", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "webpack": "^5.97.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.2.4" diff --git a/examples/react/quickstart-webpack-file-based/src/routeTree.gen.ts b/examples/react/quickstart-webpack-file-based/src/routeTree.gen.ts index 59499d9fbf..12bc916ae4 100644 --- a/examples/react/quickstart-webpack-file-based/src/routeTree.gen.ts +++ b/examples/react/quickstart-webpack-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index 00269e8524..8e57454acf 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwindcss": "^4.2.2" @@ -20,7 +20,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 75532f61b1..7dfb8c6be6 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.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -23,7 +23,8 @@ "@types/node": "^22.10.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "@vitejs/plugin-react": "^6.0.1", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" 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 f4d7f184f8..336998abe7 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -16,12 +16,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-devtools": "^1.167.1", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "@tailwindcss/vite": "^4.2.2" 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 9618fe3814..21dc0d7793 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 @@ -18,7 +18,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } 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 98833ff2dd..15b1daa463 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 @@ -14,7 +14,8 @@ }, "devDependencies": { "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } 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 136535b761..8840dea47b 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -11,8 +11,8 @@ "@router-mono-react-query/post-query": "workspace:*", "@tanstack/history": "^1.162.0", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/react/router-monorepo-react-query/packages/router/src/routeTree.gen.ts b/examples/react/router-monorepo-react-query/packages/router/src/routeTree.gen.ts index 0820fc5f30..a4ed448d2f 100644 --- a/examples/react/router-monorepo-react-query/packages/router/src/routeTree.gen.ts +++ b/examples/react/router-monorepo-react-query/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/router-monorepo-simple-lazy/package.json b/examples/react/router-monorepo-simple-lazy/package.json index dd06440bf3..6297e540de 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.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -20,7 +20,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" }, 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 a729b79fc9..a1e792cc3d 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/app/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/app/package.json @@ -15,12 +15,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-devtools": "^1.167.1", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "@tailwindcss/vite": "^4.2.2" 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 d2bd3c4cdf..5e387f61f4 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 @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } 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 8b0b87a474..ee48da871f 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/router/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/router/package.json @@ -9,8 +9,8 @@ "types": "./dist/index.d.ts", "dependencies": { "@tanstack/history": "^1.162.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -20,7 +20,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/react/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts b/examples/react/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts index 0820fc5f30..a4ed448d2f 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts +++ b/examples/react/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index 77cf9ecdf5..b6df4df4a5 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.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -20,7 +20,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" }, diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index 3e2b86f6db..01da864c38 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -15,12 +15,13 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-devtools": "^1.167.1", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "@tailwindcss/vite": "^4.2.2" 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 9bdf4683c2..885aa6bbed 100644 --- a/examples/react/router-monorepo-simple/packages/post-feature/package.json +++ b/examples/react/router-monorepo-simple/packages/post-feature/package.json @@ -16,7 +16,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index 6bdb5ec943..b59ef63fc9 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -9,8 +9,8 @@ "types": "./dist/index.d.ts", "dependencies": { "@tanstack/history": "^1.162.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -20,7 +20,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/react/router-monorepo-simple/packages/router/src/routeTree.gen.ts b/examples/react/router-monorepo-simple/packages/router/src/routeTree.gen.ts index 0820fc5f30..a4ed448d2f 100644 --- a/examples/react/router-monorepo-simple/packages/router/src/routeTree.gen.ts +++ b/examples/react/router-monorepo-simple/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index 7a503801e5..76081ff3b0 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "@tanstack/react-virtual": "^3.13.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -21,7 +21,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index 234abba717..86700f3b13 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -13,9 +13,9 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/arktype-adapter": "^1.167.0", "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/valibot-adapter": "^1.167.0", "@tanstack/zod-adapter": "^1.167.0", "arktype": "^2.1.7", @@ -31,7 +31,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/search-validator-adapters/src/routeTree.gen.ts b/examples/react/search-validator-adapters/src/routeTree.gen.ts index 4a973e1b29..d58c1ddfa1 100644 --- a/examples/react/search-validator-adapters/src/routeTree.gen.ts +++ b/examples/react/search-validator-adapters/src/routeTree.gen.ts @@ -10,18 +10,18 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersZodIndexRouteImport } from './routes/users/zod.index' -import { Route as UsersValibotIndexRouteImport } from './routes/users/valibot.index' import { Route as UsersArktypeIndexRouteImport } from './routes/users/arktype.index' +import { Route as UsersValibotIndexRouteImport } from './routes/users/valibot.index' +import { Route as UsersZodIndexRouteImport } from './routes/users/zod.index' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const UsersZodIndexRoute = UsersZodIndexRouteImport.update({ - id: '/users/zod/', - path: '/users/zod/', +const UsersArktypeIndexRoute = UsersArktypeIndexRouteImport.update({ + id: '/users/arktype/', + path: '/users/arktype/', getParentRoute: () => rootRouteImport, } as any) const UsersValibotIndexRoute = UsersValibotIndexRouteImport.update({ @@ -29,9 +29,9 @@ const UsersValibotIndexRoute = UsersValibotIndexRouteImport.update({ path: '/users/valibot/', getParentRoute: () => rootRouteImport, } as any) -const UsersArktypeIndexRoute = UsersArktypeIndexRouteImport.update({ - id: '/users/arktype/', - path: '/users/arktype/', +const UsersZodIndexRoute = UsersZodIndexRouteImport.update({ + id: '/users/zod/', + path: '/users/zod/', getParentRoute: () => rootRouteImport, } as any) @@ -78,11 +78,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport 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 } '/users/valibot/': { @@ -92,11 +92,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof UsersValibotIndexRouteImport 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 } } diff --git a/examples/react/start-bare/package.json b/examples/react/start-bare/package.json index 5a15546ef6..4afcc5b3f6 100644 --- a/examples/react/start-bare/package.json +++ b/examples/react/start-bare/package.json @@ -10,9 +10,9 @@ "start": "pnpx srvx --prod -s ../client dist/server/server.js" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^4.4.3" @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-bare/src/routeTree.gen.ts b/examples/react/start-bare/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/examples/react/start-bare/src/routeTree.gen.ts +++ b/examples/react/start-bare/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index abee47dc52..c4fab434de 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -14,9 +14,9 @@ "@libsql/client": "^0.15.15", "@prisma/adapter-libsql": "^7.0.0", "@prisma/client": "^7.0.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -31,7 +31,8 @@ "dotenv": "^17.2.3", "prisma": "^7.0.0", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-basic-auth/src/routeTree.gen.ts b/examples/react/start-basic-auth/src/routeTree.gen.ts index 389f656645..a74e753aec 100644 --- a/examples/react/start-basic-auth/src/routeTree.gen.ts +++ b/examples/react/start-basic-auth/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteRouteImport } from './routes/_authed/posts.route' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRouteRoute = AuthedPostsRouteRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/examples/react/start-basic-authjs/package.json b/examples/react/start-basic-authjs/package.json index a28f1c313f..7648214f17 100644 --- a/examples/react/start-basic-authjs/package.json +++ b/examples/react/start-basic-authjs/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@auth/core": "^0.41.1", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "start-authjs": "^1.0.0", @@ -26,7 +26,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-basic-authjs/src/routeTree.gen.ts b/examples/react/start-basic-authjs/src/routeTree.gen.ts index 2f59e4ba33..4abcbc8048 100644 --- a/examples/react/start-basic-authjs/src/routeTree.gen.ts +++ b/examples/react/start-basic-authjs/src/routeTree.gen.ts @@ -9,14 +9,14 @@ // 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 ProtectedRouteImport } from './routes/protected' -import { Route as LoginRouteImport } from './routes/login' import { Route as IndexRouteImport } from './routes/index' +import { Route as LoginRouteImport } from './routes/login' +import { Route as ProtectedRouteImport } from './routes/protected' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' -const ProtectedRoute = ProtectedRouteImport.update({ - id: '/protected', - path: '/protected', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -24,9 +24,9 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ProtectedRoute = ProtectedRouteImport.update({ + id: '/protected', + path: '/protected', getParentRoute: () => rootRouteImport, } as any) const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ @@ -71,11 +71,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/protected': { - id: '/protected' - path: '/protected' - fullPath: '/protected' - preLoaderRoute: typeof ProtectedRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -85,11 +85,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/protected': { + id: '/protected' + path: '/protected' + fullPath: '/protected' + preLoaderRoute: typeof ProtectedRouteImport parentRoute: typeof rootRouteImport } '/api/auth/$': { diff --git a/examples/react/start-basic-cloudflare/package.json b/examples/react/start-basic-cloudflare/package.json index f74602ab83..ffc5a68c71 100644 --- a/examples/react/start-basic-cloudflare/package.json +++ b/examples/react/start-basic-cloudflare/package.json @@ -12,9 +12,9 @@ "postinstall": "WRANGLER_LOG=warn npm run --silent cf-typegen" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -26,7 +26,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "wrangler": "^4.74.0" } diff --git a/examples/react/start-basic-cloudflare/src/routeTree.gen.ts b/examples/react/start-basic-cloudflare/src/routeTree.gen.ts index 0bd698a666..57b849492d 100644 --- a/examples/react/start-basic-cloudflare/src/routeTree.gen.ts +++ b/examples/react/start-basic-cloudflare/src/routeTree.gen.ts @@ -9,37 +9,36 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -47,72 +46,73 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -234,25 +234,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport parentRoute: typeof rootRouteImport } '/deferred': { @@ -262,33 +262,40 @@ 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 + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -297,13 +304,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -311,33 +311,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -346,12 +339,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index 40a18be142..eb16a1b7eb 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -12,10 +12,10 @@ "dependencies": { "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -28,7 +28,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } 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 a3a4ddf49f..e010af19fe 100644 --- a/examples/react/start-basic-react-query/src/routeTree.gen.ts +++ b/examples/react/start-basic-react-query/src/routeTree.gen.ts @@ -9,26 +9,30 @@ // 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 RedirectRouteImport } from './routes/redirect' -import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as IndexRouteImport } from './routes/index' import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' -import { Route as UsersRouteRouteImport } from './routes/users.route' +import { Route as DeferredRouteImport } from './routes/deferred' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteRouteImport } from './routes/users.route' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -36,8 +40,14 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) const UsersRouteRoute = UsersRouteRouteImport.update({ @@ -45,68 +55,58 @@ const UsersRouteRoute = UsersRouteRouteImport.update({ path: '/users', getParentRoute: () => rootRouteImport, } as any) -const PostsRouteRoute = PostsRouteRouteImport.update({ - id: '/posts', - path: '/posts', +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => PostsRouteRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRouteRoute, } as any) const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => UsersRouteRoute, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRouteRoute, -} as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRouteRoute, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - 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', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - 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', path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -221,18 +221,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport - parentRoute: typeof rootRouteImport - } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_pathlessLayout': { @@ -242,11 +235,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -256,19 +249,33 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRouteRoute + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -277,13 +284,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRouteRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRouteRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -291,33 +291,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRouteRoute } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRouteRoute } - '/api/users/$id': { - id: '/api/users/$id' - path: '/$id' - fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdRouteImport - parentRoute: typeof ApiUsersRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -326,12 +319,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/react/start-basic-rsbuild/.devcontainer/devcontainer.json b/examples/react/start-basic-rsbuild/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..e5dafce188 --- /dev/null +++ b/examples/react/start-basic-rsbuild/.devcontainer/devcontainer.json @@ -0,0 +1,3 @@ +{ + "image": "mcr.microsoft.com/devcontainers/typescript-node:24" +} diff --git a/examples/react/start-basic-rsbuild/.gitignore b/examples/react/start-basic-rsbuild/.gitignore new file mode 100644 index 0000000000..1f189d9bc0 --- /dev/null +++ b/examples/react/start-basic-rsbuild/.gitignore @@ -0,0 +1,19 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +dist +/build/ +/api/ +/server/build +/public/build# Sentry Config File +.env.sentry-build-plugin +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +.tanstack diff --git a/examples/react/start-basic-rsbuild/.prettierignore b/examples/react/start-basic-rsbuild/.prettierignore new file mode 100644 index 0000000000..2be5eaa6ec --- /dev/null +++ b/examples/react/start-basic-rsbuild/.prettierignore @@ -0,0 +1,4 @@ +**/build +**/public +pnpm-lock.yaml +routeTree.gen.ts \ No newline at end of file diff --git a/examples/react/start-basic-rsbuild/.vscode/settings.json b/examples/react/start-basic-rsbuild/.vscode/settings.json new file mode 100644 index 0000000000..00b5278e58 --- /dev/null +++ b/examples/react/start-basic-rsbuild/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/examples/react/start-basic-rsbuild/README.md b/examples/react/start-basic-rsbuild/README.md new file mode 100644 index 0000000000..64117c915b --- /dev/null +++ b/examples/react/start-basic-rsbuild/README.md @@ -0,0 +1,36 @@ +# TanStack Start - Basic Rsbuild Example + +This is the basic TanStack Start Rsbuild example, demonstrating the fundamentals of building applications with TanStack Router and TanStack Start. + +- [TanStack Router Docs](https://tanstack.com/router) + +It's deployed automagically with Netlify! + +- [Netlify](https://netlify.com/) + +## Start a new project based on this example + +To start a new project based on this example, run: + +```sh +npx gitpick TanStack/router/tree/main/examples/react/start-basic-rsbuild start-basic-rsbuild +``` + +## Getting Started + +From your terminal: + +```sh +pnpm install +pnpm dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## Build + +To build the app for production: + +```sh +pnpm build +``` diff --git a/examples/react/start-basic-rsbuild/package.json b/examples/react/start-basic-rsbuild/package.json new file mode 100644 index 0000000000..9de8f02a01 --- /dev/null +++ b/examples/react/start-basic-rsbuild/package.json @@ -0,0 +1,33 @@ +{ + "name": "tanstack-start-example-basic-rsbuild", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "rsbuild dev --port 3000", + "build": "rsbuild build && tsc --noEmit", + "preview": "rsbuild preview", + "start": "srvx --prod -s ../client dist/server/index.js" + }, + "dependencies": { + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@rsbuild/core": "^2.0.11", + "@rsbuild/plugin-react": "^2.0.0", + "@rsbuild/plugin-tailwindcss": "^2.0.3", + "@types/node": "25.0.9", + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "srvx": "^0.11.18", + "tailwindcss": "^4.2.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" + } +} diff --git a/examples/react/start-basic-rsbuild/public/android-chrome-192x192.png b/examples/react/start-basic-rsbuild/public/android-chrome-192x192.png new file mode 100644 index 0000000000..09c8324f8c Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/android-chrome-192x192.png differ diff --git a/examples/react/start-basic-rsbuild/public/android-chrome-512x512.png b/examples/react/start-basic-rsbuild/public/android-chrome-512x512.png new file mode 100644 index 0000000000..11d626ea3d Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/android-chrome-512x512.png differ diff --git a/examples/react/start-basic-rsbuild/public/apple-touch-icon.png b/examples/react/start-basic-rsbuild/public/apple-touch-icon.png new file mode 100644 index 0000000000..5a9423cc02 Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/apple-touch-icon.png differ diff --git a/examples/react/start-basic-rsbuild/public/favicon-16x16.png b/examples/react/start-basic-rsbuild/public/favicon-16x16.png new file mode 100644 index 0000000000..e3389b0044 Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/favicon-16x16.png differ diff --git a/examples/react/start-basic-rsbuild/public/favicon-32x32.png b/examples/react/start-basic-rsbuild/public/favicon-32x32.png new file mode 100644 index 0000000000..900c77d444 Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/favicon-32x32.png differ diff --git a/examples/react/start-basic-rsbuild/public/favicon.ico b/examples/react/start-basic-rsbuild/public/favicon.ico new file mode 100644 index 0000000000..1a1751676f Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/favicon.ico differ diff --git a/examples/react/start-basic-rsbuild/public/favicon.png b/examples/react/start-basic-rsbuild/public/favicon.png new file mode 100644 index 0000000000..1e77bc0609 Binary files /dev/null and b/examples/react/start-basic-rsbuild/public/favicon.png differ diff --git a/examples/react/start-basic-rsbuild/public/site.webmanifest b/examples/react/start-basic-rsbuild/public/site.webmanifest new file mode 100644 index 0000000000..fa99de77db --- /dev/null +++ b/examples/react/start-basic-rsbuild/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/examples/react/start-basic-rsbuild/rsbuild.config.ts b/examples/react/start-basic-rsbuild/rsbuild.config.ts new file mode 100644 index 0000000000..3705696818 --- /dev/null +++ b/examples/react/start-basic-rsbuild/rsbuild.config.ts @@ -0,0 +1,17 @@ +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' +import { defineConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import { pluginTailwindcss } from '@rsbuild/plugin-tailwindcss' + +export default defineConfig({ + server: { + port: 3000, + }, + plugins: [ + pluginTailwindcss(), + pluginReact(), + tanstackStart({ + srcDirectory: 'src', + }), + ], +}) diff --git a/examples/react/start-basic-rsbuild/src/components/DefaultCatchBoundary.tsx b/examples/react/start-basic-rsbuild/src/components/DefaultCatchBoundary.tsx new file mode 100644 index 0000000000..4a22649ed4 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/components/DefaultCatchBoundary.tsx @@ -0,0 +1,51 @@ +import { + ErrorComponent, + Link, + useLocation, + useRouter, +} from '@tanstack/react-router' +import type { ErrorComponentProps } from '@tanstack/react-router' + +export function DefaultCatchBoundary({ error }: ErrorComponentProps) { + const router = useRouter() + const isRoot = useLocation({ + select: (location) => location.pathname === '/', + }) + + console.error('DefaultCatchBoundary Error:', error) + + return ( +
+ +
+ + {isRoot ? ( + + Home + + ) : ( + { + e.preventDefault() + window.history.back() + }} + > + Go Back + + )} +
+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/components/NotFound.tsx b/examples/react/start-basic-rsbuild/src/components/NotFound.tsx new file mode 100644 index 0000000000..fd4e75befe --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/components/NotFound.tsx @@ -0,0 +1,25 @@ +import { Link } from '@tanstack/react-router' + +export function NotFound({ children }: { children?: any }) { + return ( +
+
+ {children ||

The page you are looking for does not exist.

} +
+

+ + + Start Over + +

+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/components/PostError.tsx b/examples/react/start-basic-rsbuild/src/components/PostError.tsx new file mode 100644 index 0000000000..271f2d1de1 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/components/PostError.tsx @@ -0,0 +1,6 @@ +import { ErrorComponent } from '@tanstack/react-router' +import type { ErrorComponentProps } from '@tanstack/react-router' + +export function PostErrorComponent({ error }: ErrorComponentProps) { + return +} diff --git a/examples/react/start-basic-rsbuild/src/components/UserError.tsx b/examples/react/start-basic-rsbuild/src/components/UserError.tsx new file mode 100644 index 0000000000..af5d0c6f16 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/components/UserError.tsx @@ -0,0 +1,6 @@ +import { ErrorComponent } from '@tanstack/react-router' +import type { ErrorComponentProps } from '@tanstack/react-router' + +export function UserErrorComponent({ error }: ErrorComponentProps) { + return +} diff --git a/examples/react/start-basic-rsbuild/src/env.d.ts b/examples/react/start-basic-rsbuild/src/env.d.ts new file mode 100644 index 0000000000..b0ac762b09 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/start-basic-rsbuild/src/routeTree.gen.ts b/examples/react/start-basic-rsbuild/src/routeTree.gen.ts new file mode 100644 index 0000000000..57b849492d --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routeTree.gen.ts @@ -0,0 +1,447 @@ +/* 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRouteImport, +} as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', + getParentRoute: () => rootRouteImport, +} as any) +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRouteImport, +} as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, +} as any) +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, +} as any) +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} 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 +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport + parentRoute: typeof rootRouteImport + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport + parentRoute: typeof rootRouteImport + } + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute + } + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute + } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute + } + '/_pathlessLayout/_nested-layout/route-b': { + id: '/_pathlessLayout/_nested-layout/route-b' + path: '/route-b' + fullPath: '/route-b' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute + } + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +interface PathlessLayoutNestedLayoutRouteChildren { + PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute + PathlessLayoutNestedLayoutRouteBRoute: typeof PathlessLayoutNestedLayoutRouteBRoute +} + +const PathlessLayoutNestedLayoutRouteChildren: PathlessLayoutNestedLayoutRouteChildren = + { + PathlessLayoutNestedLayoutRouteARoute: + PathlessLayoutNestedLayoutRouteARoute, + PathlessLayoutNestedLayoutRouteBRoute: + PathlessLayoutNestedLayoutRouteBRoute, + } + +const PathlessLayoutNestedLayoutRouteWithChildren = + PathlessLayoutNestedLayoutRoute._addFileChildren( + PathlessLayoutNestedLayoutRouteChildren, + ) + +interface PathlessLayoutRouteChildren { + PathlessLayoutNestedLayoutRoute: typeof PathlessLayoutNestedLayoutRouteWithChildren +} + +const PathlessLayoutRouteChildren: PathlessLayoutRouteChildren = { + PathlessLayoutNestedLayoutRoute: PathlessLayoutNestedLayoutRouteWithChildren, +} + +const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren( + PathlessLayoutRouteChildren, +) + +interface PostsRouteChildren { + PostsPostIdRoute: typeof PostsPostIdRoute + PostsIndexRoute: typeof PostsIndexRoute +} + +const PostsRouteChildren: PostsRouteChildren = { + PostsPostIdRoute: PostsPostIdRoute, + PostsIndexRoute: PostsIndexRoute, +} + +const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren) + +interface UsersRouteChildren { + UsersUserIdRoute: typeof UsersUserIdRoute + UsersIndexRoute: typeof UsersIndexRoute +} + +const UsersRouteChildren: UsersRouteChildren = { + UsersUserIdRoute: UsersUserIdRoute, + UsersIndexRoute: UsersIndexRoute, +} + +const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) + +interface ApiUsersRouteChildren { + ApiUsersUserIdRoute: typeof ApiUsersUserIdRoute +} + +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersUserIdRoute: ApiUsersUserIdRoute, +} + +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() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/examples/react/start-basic-rsbuild/src/router.tsx b/examples/react/start-basic-rsbuild/src/router.tsx new file mode 100644 index 0000000000..f380f73be7 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/router.tsx @@ -0,0 +1,15 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' +import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' +import { NotFound } from './components/NotFound' + +export function getRouter() { + const router = createRouter({ + routeTree, + defaultPreload: 'intent', + defaultErrorComponent: DefaultCatchBoundary, + defaultNotFoundComponent: () => , + scrollRestoration: true, + }) + return router +} diff --git a/examples/react/start-basic-rsbuild/src/routes/__root.tsx b/examples/react/start-basic-rsbuild/src/routes/__root.tsx new file mode 100644 index 0000000000..6d4aed1fe0 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/__root.tsx @@ -0,0 +1,130 @@ +import { + HeadContent, + Link, + Scripts, + createRootRoute, +} from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +import * as React from 'react' +import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary' +import { NotFound } from '~/components/NotFound' +import appCss from '~/styles/app.css?url' +import { seo } from '~/utils/seo' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + ...seo({ + title: + 'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework', + description: `TanStack Start is a type-safe, client-first, full-stack React framework. `, + }), + ], + links: [ + { rel: 'stylesheet', href: appCss }, + { + rel: 'apple-touch-icon', + sizes: '180x180', + href: '/apple-touch-icon.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '32x32', + href: '/favicon-32x32.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '16x16', + href: '/favicon-16x16.png', + }, + { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' }, + { rel: 'icon', href: '/favicon.ico' }, + ], + scripts: [ + { + src: '/customScript.js', + type: 'text/javascript', + }, + ], + }), + errorComponent: DefaultCatchBoundary, + notFoundComponent: () => , + shellComponent: RootDocument, +}) + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + +
+ + Home + {' '} + + Posts + {' '} + + Users + {' '} + + Pathless Layout + {' '} + + Deferred + {' '} + + This Route Does Not Exist + +
+
+ {children} + + + + + ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout.tsx b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout.tsx new file mode 100644 index 0000000000..c3b12442b8 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout.tsx @@ -0,0 +1,16 @@ +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathlessLayout')({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( +
+
I'm a layout
+
+ +
+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout.tsx b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout.tsx new file mode 100644 index 0000000000..9a48b73a46 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout.tsx @@ -0,0 +1,34 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( +
+
I'm a nested layout
+
+ + Go to route A + + + Go to route B + +
+
+ +
+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-a.tsx b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-a.tsx new file mode 100644 index 0000000000..426a8fe486 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-a.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')( + { + component: LayoutAComponent, + }, +) + +function LayoutAComponent() { + return
I'm A!
+} diff --git a/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-b.tsx b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-b.tsx new file mode 100644 index 0000000000..20facf2daf --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/_pathlessLayout/_nested-layout/route-b.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')( + { + component: LayoutBComponent, + }, +) + +function LayoutBComponent() { + return
I'm B!
+} diff --git a/examples/react/start-basic-rsbuild/src/routes/api/users.$userId.ts b/examples/react/start-basic-rsbuild/src/routes/api/users.$userId.ts new file mode 100644 index 0000000000..ee34d7a189 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/api/users.$userId.ts @@ -0,0 +1,31 @@ +import { createFileRoute } from '@tanstack/react-router' +import type { User } from '~/utils/users' + +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 Response.json({ + id: user.id, + name: user.name, + email: user.email, + }) + } catch (e) { + console.error(e) + return Response.json({ error: 'User not found' }, { status: 404 }) + } + }, + }, + }, +}) diff --git a/examples/react/start-basic-rsbuild/src/routes/api/users.ts b/examples/react/start-basic-rsbuild/src/routes/api/users.ts new file mode 100644 index 0000000000..4cecd01852 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/api/users.ts @@ -0,0 +1,63 @@ +import { createFileRoute } from '@tanstack/react-router' +import { getRequestHeaders } from '@tanstack/react-start/server' +import { createMiddleware } from '@tanstack/react-start' +import type { User } from '~/utils/users' + +const userLoggerMiddleware = createMiddleware().server(async ({ next }) => { + console.info('In: /users') + console.info('Request Headers:', getRequestHeaders()) + const result = await next() + result.response.headers.set('x-users', 'true') + console.info('Out: /users') + return result +}) + +const testParentMiddleware = createMiddleware().server(async ({ next }) => { + console.info('In: testParentMiddleware') + const result = await next() + result.response.headers.set('x-test-parent', 'true') + console.info('Out: testParentMiddleware') + return result +}) + +const testMiddleware = createMiddleware() + .middleware([testParentMiddleware]) + .server(async ({ next }) => { + 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 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 list = data.slice(0, 10) + + return Response.json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, + }, +}) diff --git a/examples/react/start-basic-rsbuild/src/routes/customScript[.]js.ts b/examples/react/start-basic-rsbuild/src/routes/customScript[.]js.ts new file mode 100644 index 0000000000..811471ac15 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/customScript[.]js.ts @@ -0,0 +1,15 @@ +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-rsbuild/src/routes/deferred.tsx b/examples/react/start-basic-rsbuild/src/routes/deferred.tsx new file mode 100644 index 0000000000..f3e09d1d4e --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/deferred.tsx @@ -0,0 +1,62 @@ +import { Await, createFileRoute } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' +import { Suspense, useState } from 'react' + +const personServerFn = createServerFn({ method: 'GET' }) + .validator((d: string) => d) + .handler(({ data: name }) => { + return { name, randomNumber: Math.floor(Math.random() * 100) } + }) + +const slowServerFn = createServerFn({ method: 'GET' }) + .validator((d: string) => d) + .handler(async ({ data: name }) => { + await new Promise((r) => setTimeout(r, 1000)) + return { name, randomNumber: Math.floor(Math.random() * 100) } + }) + +export const Route = createFileRoute('/deferred')({ + loader: async () => { + return { + deferredStuff: new Promise((r) => + setTimeout(() => r('Hello deferred!'), 2000), + ), + deferredPerson: slowServerFn({ data: 'Tanner Linsley' }), + person: await personServerFn({ data: 'John Doe' }), + } + }, + component: Deferred, +}) + +function Deferred() { + const [count, setCount] = useState(0) + const { deferredStuff, deferredPerson, person } = Route.useLoaderData() + + return ( +
+
+ {person.name} - {person.randomNumber} +
+ Loading person...
}> + ( +
+ {data.name} - {data.randomNumber} +
+ )} + /> + + Loading stuff...}> +

{data}

} + /> +
+
Count: {count}
+
+ +
+ + ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/index.tsx b/examples/react/start-basic-rsbuild/src/routes/index.tsx new file mode 100644 index 0000000000..09a907cb18 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Welcome Home!!!

+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/posts.$postId.tsx b/examples/react/start-basic-rsbuild/src/routes/posts.$postId.tsx new file mode 100644 index 0000000000..65905bf8c4 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/posts.$postId.tsx @@ -0,0 +1,34 @@ +import { Link, createFileRoute } from '@tanstack/react-router' +import { fetchPost } from '../utils/posts' +import { NotFound } from '~/components/NotFound' +import { PostErrorComponent } from '~/components/PostError' + +export const Route = createFileRoute('/posts/$postId')({ + loader: ({ params: { postId } }) => fetchPost({ data: postId }), + errorComponent: PostErrorComponent, + component: PostComponent, + notFoundComponent: () => { + return Post not found + }, +}) + +function PostComponent() { + const post = Route.useLoaderData() + + return ( +
+

{post.title}

+
{post.body}
+ + Deep View + +
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/posts.index.tsx b/examples/react/start-basic-rsbuild/src/routes/posts.index.tsx new file mode 100644 index 0000000000..5b5f08f95b --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/posts.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Select a post.
+} diff --git a/examples/react/start-basic-rsbuild/src/routes/posts.tsx b/examples/react/start-basic-rsbuild/src/routes/posts.tsx new file mode 100644 index 0000000000..9b9162be7c --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/posts.tsx @@ -0,0 +1,38 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' +import { fetchPosts } from '../utils/posts' + +export const Route = createFileRoute('/posts')({ + loader: async () => fetchPosts(), + component: PostsComponent, +}) + +function PostsComponent() { + const posts = Route.useLoaderData() + + return ( +
+
    + {[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map( + (post) => { + return ( +
  • + +
    {post.title.substring(0, 20)}
    + +
  • + ) + }, + )} +
+
+ +
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/posts_.$postId.deep.tsx b/examples/react/start-basic-rsbuild/src/routes/posts_.$postId.deep.tsx new file mode 100644 index 0000000000..29e6c39b5a --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/posts_.$postId.deep.tsx @@ -0,0 +1,29 @@ +import { Link, createFileRoute } from '@tanstack/react-router' +import { fetchPost } from '../utils/posts' +import { PostErrorComponent } from '~/components/PostError' + +export const Route = createFileRoute('/posts_/$postId/deep')({ + loader: async ({ params: { postId } }) => + fetchPost({ + data: postId, + }), + errorComponent: PostErrorComponent, + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const post = Route.useLoaderData() + + return ( +
+ + ← All Posts + +

{post.title}

+
{post.body}
+
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/redirect.tsx b/examples/react/start-basic-rsbuild/src/routes/redirect.tsx new file mode 100644 index 0000000000..ea34b6e420 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/redirect.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/redirect')({ + beforeLoad: () => { + throw redirect({ + to: '/posts', + }) + }, +}) diff --git a/examples/react/start-basic-rsbuild/src/routes/users.$userId.tsx b/examples/react/start-basic-rsbuild/src/routes/users.$userId.tsx new file mode 100644 index 0000000000..4e4d470075 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/users.$userId.tsx @@ -0,0 +1,45 @@ +import { createFileRoute } from '@tanstack/react-router' +import { NotFound } from '../components/NotFound' +import { UserErrorComponent } from '../components/UserError' +import type { User } from '../utils/users' + +export const Route = createFileRoute('/users/$userId')({ + loader: async ({ params: { userId } }) => { + try { + const res = await fetch('/api/users/' + userId) + if (!res.ok) { + throw new Error('Unexpected status code') + } + + const data = await res.json() + + return data as User + } catch { + throw new Error('Failed to fetch user') + } + }, + errorComponent: UserErrorComponent, + component: UserComponent, + notFoundComponent: () => { + return User not found + }, +}) + +function UserComponent() { + const user = Route.useLoaderData() + + return ( +
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/users.index.tsx b/examples/react/start-basic-rsbuild/src/routes/users.index.tsx new file mode 100644 index 0000000000..27d136987e --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/users.index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/users/')({ + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return ( +
+ Select a user or{' '} + + view as JSON + +
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/routes/users.tsx b/examples/react/start-basic-rsbuild/src/routes/users.tsx new file mode 100644 index 0000000000..64ca3ce156 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/routes/users.tsx @@ -0,0 +1,49 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' +import type { User } from '../utils/users' + +export const Route = createFileRoute('/users')({ + loader: async () => { + const res = await fetch('/api/users') + + if (!res.ok) { + throw new Error('Unexpected status code') + } + + const data = await res.json() + + return data as Array + }, + component: UsersComponent, +}) + +function UsersComponent() { + const users = Route.useLoaderData() + + return ( +
+
    + {[ + ...users, + { id: 'i-do-not-exist', name: 'Non-existent User', email: '' }, + ].map((user) => { + return ( +
  • + +
    {user.name}
    + +
  • + ) + })} +
+
+ +
+ ) +} diff --git a/examples/react/start-basic-rsbuild/src/styles/app.css b/examples/react/start-basic-rsbuild/src/styles/app.css new file mode 100644 index 0000000000..37c1f5a6e2 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/styles/app.css @@ -0,0 +1,30 @@ +@import 'tailwindcss' source('../'); + +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } +} + +@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/examples/react/start-basic-rsbuild/src/utils/loggingMiddleware.tsx b/examples/react/start-basic-rsbuild/src/utils/loggingMiddleware.tsx new file mode 100644 index 0000000000..3ea9a06439 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/utils/loggingMiddleware.tsx @@ -0,0 +1,41 @@ +import { createMiddleware } from '@tanstack/react-start' + +const preLogMiddleware = createMiddleware({ type: 'function' }) + .client(async (ctx) => { + const clientTime = new Date() + + return ctx.next({ + context: { + clientTime, + }, + sendContext: { + clientTime, + }, + }) + }) + .server(async (ctx) => { + const serverTime = new Date() + + return ctx.next({ + sendContext: { + serverTime, + durationToServer: + serverTime.getTime() - ctx.context.clientTime.getTime(), + }, + }) + }) + +export const logMiddleware = createMiddleware({ type: 'function' }) + .middleware([preLogMiddleware]) + .client(async (ctx) => { + const res = await ctx.next() + + const now = new Date() + console.log('Client Req/Res:', { + duration: now.getTime() - res.context.clientTime.getTime(), + durationToServer: res.context.durationToServer, + durationFromServer: now.getTime() - res.context.serverTime.getTime(), + }) + + return res + }) diff --git a/examples/react/start-basic-rsbuild/src/utils/posts.tsx b/examples/react/start-basic-rsbuild/src/utils/posts.tsx new file mode 100644 index 0000000000..ea7dd0a721 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/utils/posts.tsx @@ -0,0 +1,40 @@ +import { notFound } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' + +export type PostType = { + id: number + title: string + body: string +} + +export const fetchPost = createServerFn({ method: 'POST' }) + .validator((d: string) => d) + .handler(async ({ data }) => { + console.info(`Fetching post with id ${data}...`) + const res = await fetch( + `https://jsonplaceholder.typicode.com/posts/${data}`, + ) + if (!res.ok) { + if (res.status === 404) { + throw notFound() + } + + throw new Error('Failed to fetch post') + } + + const post = await res.json() + + return post as PostType + }) + +export const fetchPosts = createServerFn().handler(async () => { + console.info('Fetching posts...') + const res = await fetch('https://jsonplaceholder.typicode.com/posts') + if (!res.ok) { + throw new Error('Failed to fetch posts') + } + + const posts = await res.json() + + return (posts as Array).slice(0, 10) +}) diff --git a/examples/react/start-basic-rsbuild/src/utils/seo.ts b/examples/react/start-basic-rsbuild/src/utils/seo.ts new file mode 100644 index 0000000000..d18ad84b74 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/utils/seo.ts @@ -0,0 +1,33 @@ +export const seo = ({ + title, + description, + keywords, + image, +}: { + title: string + description?: string + image?: string + keywords?: string +}) => { + const tags = [ + { title }, + { name: 'description', content: description }, + { name: 'keywords', content: keywords }, + { name: 'twitter:title', content: title }, + { name: 'twitter:description', content: description }, + { name: 'twitter:creator', content: '@tannerlinsley' }, + { name: 'twitter:site', content: '@tannerlinsley' }, + { name: 'og:type', content: 'website' }, + { name: 'og:title', content: title }, + { name: 'og:description', content: description }, + ...(image + ? [ + { name: 'twitter:image', content: image }, + { name: 'twitter:card', content: 'summary_large_image' }, + { name: 'og:image', content: image }, + ] + : []), + ] + + return tags +} diff --git a/examples/react/start-basic-rsbuild/src/utils/users.tsx b/examples/react/start-basic-rsbuild/src/utils/users.tsx new file mode 100644 index 0000000000..7ba645b383 --- /dev/null +++ b/examples/react/start-basic-rsbuild/src/utils/users.tsx @@ -0,0 +1,5 @@ +export type User = { + id: number + name: string + email: string +} diff --git a/examples/react/start-basic-rsbuild/tsconfig.json b/examples/react/start-basic-rsbuild/tsconfig.json new file mode 100644 index 0000000000..cc479423a1 --- /dev/null +++ b/examples/react/start-basic-rsbuild/tsconfig.json @@ -0,0 +1,21 @@ +{ + "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2024"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2024", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +} diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json index 8ea4c54edc..ac369b8faf 100644 --- a/examples/react/start-basic-static/package.json +++ b/examples/react/start-basic-static/package.json @@ -10,10 +10,10 @@ "start": "pnpx srvx --prod -s ../client dist/server/server.js" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", - "@tanstack/start-static-server-functions": "^1.167.18", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", + "@tanstack/start-static-server-functions": "^1.167.20", "@vitejs/plugin-react": "^6.0.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -26,7 +26,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-basic-static/src/routeTree.gen.ts b/examples/react/start-basic-static/src/routeTree.gen.ts index ddc8fc320a..d4a8e72343 100644 --- a/examples/react/start-basic-static/src/routeTree.gen.ts +++ b/examples/react/start-basic-static/src/routeTree.gen.ts @@ -9,29 +9,33 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' 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 PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -39,62 +43,58 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any) const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => UsersRoute, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, -} as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRoute, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, -} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -196,18 +196,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -217,33 +224,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -252,13 +252,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -266,19 +259,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -287,12 +287,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 931df2a45d..ade898753f 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -10,9 +10,9 @@ "start": "node .output/server/index.mjs" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.6.0", @@ -26,7 +26,8 @@ "@vitejs/plugin-react": "^6.0.1", "nitro": "^3.0.260311-beta", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-basic/src/routeTree.gen.ts b/examples/react/start-basic/src/routeTree.gen.ts index 0bd698a666..57b849492d 100644 --- a/examples/react/start-basic/src/routeTree.gen.ts +++ b/examples/react/start-basic/src/routeTree.gen.ts @@ -9,37 +9,36 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -47,72 +46,73 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -234,25 +234,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport parentRoute: typeof rootRouteImport } '/deferred': { @@ -262,33 +262,40 @@ 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 + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -297,13 +304,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -311,33 +311,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -346,12 +339,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/react/start-bun/package.json b/examples/react/start-bun/package.json index a4ab34310f..9c9b7e20e2 100644 --- a/examples/react/start-bun/package.json +++ b/examples/react/start-bun/package.json @@ -15,11 +15,11 @@ "dependencies": { "@tailwindcss/vite": "^4.2.2", "@tanstack/react-devtools": "^0.7.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.27", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-start": "^1.168.36", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.2.2" @@ -35,7 +35,8 @@ "@vitejs/plugin-react": "^6.0.1", "jsdom": "^27.0.0", "prettier": "^3.6.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vitest": "^4.1.4", "web-vitals": "^5.1.0" diff --git a/examples/react/start-bun/src/routeTree.gen.ts b/examples/react/start-bun/src/routeTree.gen.ts index 18fbc9b60e..77415f9088 100644 --- a/examples/react/start-bun/src/routeTree.gen.ts +++ b/examples/react/start-bun/src/routeTree.gen.ts @@ -11,8 +11,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiDemoNamesRouteImport } from './routes/api.demo-names' -import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.server-funcs' import { Route as DemoStartApiRequestRouteImport } from './routes/demo.start.api-request' +import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.server-funcs' const IndexRoute = IndexRouteImport.update({ id: '/', @@ -24,16 +24,16 @@ const ApiDemoNamesRoute = ApiDemoNamesRouteImport.update({ path: '/api/demo-names', getParentRoute: () => rootRouteImport, } as any) -const DemoStartServerFuncsRoute = DemoStartServerFuncsRouteImport.update({ - id: '/demo/start/server-funcs', - path: '/demo/start/server-funcs', - getParentRoute: () => rootRouteImport, -} as any) const DemoStartApiRequestRoute = DemoStartApiRequestRouteImport.update({ id: '/demo/start/api-request', path: '/demo/start/api-request', getParentRoute: () => rootRouteImport, } as any) +const DemoStartServerFuncsRoute = DemoStartServerFuncsRouteImport.update({ + id: '/demo/start/server-funcs', + path: '/demo/start/server-funcs', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -98,13 +98,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiDemoNamesRouteImport parentRoute: typeof rootRouteImport } - '/demo/start/server-funcs': { - id: '/demo/start/server-funcs' - path: '/demo/start/server-funcs' - fullPath: '/demo/start/server-funcs' - preLoaderRoute: typeof DemoStartServerFuncsRouteImport - parentRoute: typeof rootRouteImport - } '/demo/start/api-request': { id: '/demo/start/api-request' path: '/demo/start/api-request' @@ -112,6 +105,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DemoStartApiRequestRouteImport parentRoute: typeof rootRouteImport } + '/demo/start/server-funcs': { + id: '/demo/start/server-funcs' + path: '/demo/start/server-funcs' + fullPath: '/demo/start/server-funcs' + preLoaderRoute: typeof DemoStartServerFuncsRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json index 7e15b08073..eb068eb88d 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@clerk/tanstack-react-start": "^0.27.14", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "@vitejs/plugin-react": "^6.0.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -27,7 +27,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-clerk-basic/src/routeTree.gen.ts b/examples/react/start-clerk-basic/src/routeTree.gen.ts index 04f57442e2..87269f2faf 100644 --- a/examples/react/start-clerk-basic/src/routeTree.gen.ts +++ b/examples/react/start-clerk-basic/src/routeTree.gen.ts @@ -9,22 +9,22 @@ // 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 AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' -import { Route as AuthedProfileSplatRouteImport } from './routes/_authed/profile.$' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' +import { Route as AuthedProfileSplatRouteImport } from './routes/_authed/profile.$' -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', + getParentRoute: () => rootRouteImport, +} as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ id: '/posts', path: '/posts', @@ -35,16 +35,16 @@ const AuthedPostsIndexRoute = AuthedPostsIndexRouteImport.update({ 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) +const AuthedProfileSplatRoute = AuthedProfileSplatRouteImport.update({ + id: '/profile/$', + path: '/profile/$', + getParentRoute: () => AuthedRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -90,13 +90,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -104,6 +97,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport + parentRoute: typeof rootRouteImport + } '/_authed/posts': { id: '/_authed/posts' path: '/posts' @@ -118,13 +118,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedPostsIndexRouteImport parentRoute: typeof AuthedPostsRoute } - '/_authed/profile/$': { - id: '/_authed/profile/$' - path: '/profile/$' - fullPath: '/profile/$' - preLoaderRoute: typeof AuthedProfileSplatRouteImport - parentRoute: typeof AuthedRoute - } '/_authed/posts/$postId': { id: '/_authed/posts/$postId' path: '/$postId' @@ -132,6 +125,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedPostsPostIdRouteImport parentRoute: typeof AuthedPostsRoute } + '/_authed/profile/$': { + id: '/_authed/profile/$' + path: '/profile/$' + fullPath: '/profile/$' + preLoaderRoute: typeof AuthedProfileSplatRouteImport + parentRoute: typeof AuthedRoute + } } } diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index f2ec73bfe8..998eaa26b5 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -15,10 +15,10 @@ "@convex-dev/react-query": "0.0.0-alpha.8", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-start": "^1.168.36", "concurrently": "^8.2.2", "convex": "^1.19.0", "ky": "^1.7.4", @@ -37,7 +37,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index a7a8397b74..afaedb0b1d 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -10,9 +10,9 @@ "start": "pnpx srvx --prod -s ../client dist/server/server.js" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -21,7 +21,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-i18n-paraglide/package.json b/examples/react/start-i18n-paraglide/package.json index 936f5e040d..88d9c81298 100644 --- a/examples/react/start-i18n-paraglide/package.json +++ b/examples/react/start-i18n-paraglide/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tanstack/react-devtools": "^0.7.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.1.1", "react-dom": "^19.1.1" }, @@ -21,7 +21,8 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "@tailwindcss/vite": "^4.2.2", "tailwindcss": "^4.2.2", diff --git a/examples/react/start-i18n-paraglide/src/routeTree.gen.ts b/examples/react/start-i18n-paraglide/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/examples/react/start-i18n-paraglide/src/routeTree.gen.ts +++ b/examples/react/start-i18n-paraglide/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index d7300fc815..369d1abe20 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -13,9 +13,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -29,7 +29,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" }, "keywords": [], diff --git a/examples/react/start-large/src/routeTree.gen.ts b/examples/react/start-large/src/routeTree.gen.ts index 03cc2b9080..4f1551f71d 100644 --- a/examples/react/start-large/src/routeTree.gen.ts +++ b/examples/react/start-large/src/routeTree.gen.ts @@ -9,23 +9,18 @@ // 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 RelativeRouteImport } from './routes/relative' -import { Route as LinkPropsRouteImport } from './routes/linkProps' +import { Route as IndexRouteImport } from './routes/index' import { Route as AbsoluteRouteImport } from './routes/absolute' -import { Route as SearchRouteRouteImport } from './routes/search/route' +import { Route as LinkPropsRouteImport } from './routes/linkProps' import { Route as ParamsRouteRouteImport } from './routes/params/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' +import { Route as RelativeRouteImport } from './routes/relative' +import { Route as SearchRouteRouteImport } from './routes/search/route' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' +import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' -const RelativeRoute = RelativeRouteImport.update({ - id: '/relative', - path: '/relative', - getParentRoute: () => rootRouteImport, -} as any) -const LinkPropsRoute = LinkPropsRouteImport.update({ - id: '/linkProps', - path: '/linkProps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AbsoluteRoute = AbsoluteRouteImport.update({ @@ -33,9 +28,9 @@ const AbsoluteRoute = AbsoluteRouteImport.update({ path: '/absolute', getParentRoute: () => rootRouteImport, } as any) -const SearchRouteRoute = SearchRouteRouteImport.update({ - id: '/search', - path: '/search', +const LinkPropsRoute = LinkPropsRouteImport.update({ + id: '/linkProps', + path: '/linkProps', getParentRoute: () => rootRouteImport, } as any) const ParamsRouteRoute = ParamsRouteRouteImport.update({ @@ -43,21 +38,26 @@ const ParamsRouteRoute = ParamsRouteRouteImport.update({ path: '/params', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RelativeRoute = RelativeRouteImport.update({ + id: '/relative', + path: '/relative', getParentRoute: () => rootRouteImport, } as any) -const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ - id: '/searchPlaceholder', - path: '/searchPlaceholder', - getParentRoute: () => SearchRouteRoute, +const SearchRouteRoute = SearchRouteRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => rootRouteImport, } as any) const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderRouteImport.update({ id: '/$paramsPlaceholder', path: '/$paramsPlaceholder', getParentRoute: () => ParamsRouteRoute, } as any) +const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ + id: '/searchPlaceholder', + path: '/searchPlaceholder', + getParentRoute: () => SearchRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -134,18 +134,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/relative': { - id: '/relative' - path: '/relative' - fullPath: '/relative' - preLoaderRoute: typeof RelativeRouteImport - parentRoute: typeof rootRouteImport - } - '/linkProps': { - id: '/linkProps' - path: '/linkProps' - fullPath: '/linkProps' - preLoaderRoute: typeof LinkPropsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/absolute': { @@ -155,11 +148,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AbsoluteRouteImport parentRoute: typeof rootRouteImport } - '/search': { - id: '/search' - path: '/search' - fullPath: '/search' - preLoaderRoute: typeof SearchRouteRouteImport + '/linkProps': { + id: '/linkProps' + path: '/linkProps' + fullPath: '/linkProps' + preLoaderRoute: typeof LinkPropsRouteImport parentRoute: typeof rootRouteImport } '/params': { @@ -169,19 +162,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/relative': { + id: '/relative' + path: '/relative' + fullPath: '/relative' + preLoaderRoute: typeof RelativeRouteImport parentRoute: typeof rootRouteImport } - '/search/searchPlaceholder': { - id: '/search/searchPlaceholder' - path: '/searchPlaceholder' - fullPath: '/search/searchPlaceholder' - preLoaderRoute: typeof SearchSearchPlaceholderRouteImport - parentRoute: typeof SearchRouteRoute + '/search': { + id: '/search' + path: '/search' + fullPath: '/search' + preLoaderRoute: typeof SearchRouteRouteImport + parentRoute: typeof rootRouteImport } '/params/$paramsPlaceholder': { id: '/params/$paramsPlaceholder' @@ -190,6 +183,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsParamsPlaceholderRouteImport parentRoute: typeof ParamsRouteRoute } + '/search/searchPlaceholder': { + id: '/search/searchPlaceholder' + path: '/searchPlaceholder' + fullPath: '/search/searchPlaceholder' + preLoaderRoute: typeof SearchSearchPlaceholderRouteImport + parentRoute: typeof SearchRouteRoute + } } } diff --git a/examples/react/start-material-ui/package.json b/examples/react/start-material-ui/package.json index a7623d10f0..3b4588ef3f 100644 --- a/examples/react/start-material-ui/package.json +++ b/examples/react/start-material-ui/package.json @@ -15,9 +15,9 @@ "@emotion/styled": "11.14.0", "@fontsource-variable/roboto": "5.2.5", "@mui/material": "6.4.7", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^4.4.3" @@ -27,7 +27,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-material-ui/src/routeTree.gen.ts b/examples/react/start-material-ui/src/routeTree.gen.ts index 421daf2790..d89758e684 100644 --- a/examples/react/start-material-ui/src/routeTree.gen.ts +++ b/examples/react/start-material-ui/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/react/start-rscs/package.json b/examples/react/start-rscs/package.json index 4e6da2d402..b3e98d6190 100644 --- a/examples/react/start-rscs/package.json +++ b/examples/react/start-rscs/package.json @@ -10,9 +10,9 @@ "start": "node .output/server/index.mjs" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "dexie": "^4.0.10", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -26,7 +26,7 @@ "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@vitejs/plugin-react": "^6.0.1", - "@vitejs/plugin-rsc": "^0.5.20", + "@vitejs/plugin-rsc": "^0.5.30", "nitro": "^3.0.260311-beta", "tailwindcss": "^4.1.18", "typescript": "^5.7.2", diff --git a/examples/react/start-rscs/src/routeTree.gen.ts b/examples/react/start-rscs/src/routeTree.gen.ts index ca4cef3864..d89ab3d3e7 100644 --- a/examples/react/start-rscs/src/routeTree.gen.ts +++ b/examples/react/start-rscs/src/routeTree.gen.ts @@ -9,21 +9,21 @@ // 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 PokemonRscRouteImport } from './routes/pokemon-rsc' -import { Route as PokemonRouteImport } from './routes/pokemon' -import { Route as LowLevelApiRouteImport } from './routes/low-level-api' -import { Route as ECommerceRouteImport } from './routes/e-commerce' import { Route as IndexRouteImport } from './routes/index' +import { Route as ECommerceRouteImport } from './routes/e-commerce' +import { Route as LowLevelApiRouteImport } from './routes/low-level-api' +import { Route as PokemonRouteImport } from './routes/pokemon' +import { Route as PokemonRscRouteImport } from './routes/pokemon-rsc' import { Route as ApiRscRouteImport } from './routes/api/rsc' -const PokemonRscRoute = PokemonRscRouteImport.update({ - id: '/pokemon-rsc', - path: '/pokemon-rsc', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PokemonRoute = PokemonRouteImport.update({ - id: '/pokemon', - path: '/pokemon', +const ECommerceRoute = ECommerceRouteImport.update({ + id: '/e-commerce', + path: '/e-commerce', getParentRoute: () => rootRouteImport, } as any) const LowLevelApiRoute = LowLevelApiRouteImport.update({ @@ -31,14 +31,14 @@ const LowLevelApiRoute = LowLevelApiRouteImport.update({ path: '/low-level-api', getParentRoute: () => rootRouteImport, } as any) -const ECommerceRoute = ECommerceRouteImport.update({ - id: '/e-commerce', - path: '/e-commerce', +const PokemonRoute = PokemonRouteImport.update({ + id: '/pokemon', + path: '/pokemon', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PokemonRscRoute = PokemonRscRouteImport.update({ + id: '/pokemon-rsc', + path: '/pokemon-rsc', getParentRoute: () => rootRouteImport, } as any) const ApiRscRoute = ApiRscRouteImport.update({ @@ -110,18 +110,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/pokemon-rsc': { - id: '/pokemon-rsc' - path: '/pokemon-rsc' - fullPath: '/pokemon-rsc' - preLoaderRoute: typeof PokemonRscRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/pokemon': { - id: '/pokemon' - path: '/pokemon' - fullPath: '/pokemon' - preLoaderRoute: typeof PokemonRouteImport + '/e-commerce': { + id: '/e-commerce' + path: '/e-commerce' + fullPath: '/e-commerce' + preLoaderRoute: typeof ECommerceRouteImport parentRoute: typeof rootRouteImport } '/low-level-api': { @@ -131,18 +131,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LowLevelApiRouteImport parentRoute: typeof rootRouteImport } - '/e-commerce': { - id: '/e-commerce' - path: '/e-commerce' - fullPath: '/e-commerce' - preLoaderRoute: typeof ECommerceRouteImport + '/pokemon': { + id: '/pokemon' + path: '/pokemon' + fullPath: '/pokemon' + preLoaderRoute: typeof PokemonRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/pokemon-rsc': { + id: '/pokemon-rsc' + path: '/pokemon-rsc' + fullPath: '/pokemon-rsc' + preLoaderRoute: typeof PokemonRscRouteImport parentRoute: typeof rootRouteImport } '/api/rsc': { diff --git a/examples/react/start-streaming-data-from-server-functions/package.json b/examples/react/start-streaming-data-from-server-functions/package.json index e5d50b902f..0cf6b2d40a 100644 --- a/examples/react/start-streaming-data-from-server-functions/package.json +++ b/examples/react/start-streaming-data-from-server-functions/package.json @@ -10,9 +10,9 @@ "start": "pnpx srvx --prod -s ../client dist/server/server.js" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^4.4.3" @@ -22,7 +22,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index 30083cbe1d..79132b7bf6 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.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" @@ -28,7 +28,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-supabase-basic/src/routeTree.gen.ts b/examples/react/start-supabase-basic/src/routeTree.gen.ts index 6be84444eb..998d34a709 100644 --- a/examples/react/start-supabase-basic/src/routeTree.gen.ts +++ b/examples/react/start-supabase-basic/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/examples/react/start-tailwind-v4/package.json b/examples/react/start-tailwind-v4/package.json index f8e537c9b4..bbfa2f7dee 100644 --- a/examples/react/start-tailwind-v4/package.json +++ b/examples/react/start-tailwind-v4/package.json @@ -10,9 +10,9 @@ "start": "node .output/server/index.mjs" }, "dependencies": { - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.6.0", @@ -25,7 +25,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index 29bf8bba30..723fe3f70e 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -12,10 +12,10 @@ "dependencies": { "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-start": "^1.168.36", "ky": "^1.7.4", "msw": "^2.7.0", "react": "^19.0.0", @@ -32,7 +32,8 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-workos/README.md b/examples/react/start-workos/README.md index d6e222c7ba..52dde9410a 100644 --- a/examples/react/start-workos/README.md +++ b/examples/react/start-workos/README.md @@ -30,7 +30,7 @@ You will need a [WorkOS account](https://dashboard.workos.com/signup). WORKOS_CLIENT_ID= WORKOS_API_KEY= WORKOS_COOKIE_PASSWORD= - WORKOS_REDIRECT_URI=http://localhost:3000/callback + WORKOS_REDIRECT_URI=http://localhost:3000/api/auth/callback ``` `WORKOS_COOKIE_PASSWORD` is the private key used to encrypt the session cookie. It has to be at least 32 characters long. You can use the [1Password generator](https://1password.com/password-generator/) or the `openssl` library to generate a strong password via the command line: ```bash diff --git a/examples/react/start-workos/package.json b/examples/react/start-workos/package.json index 935c7a0a5f..0fc2bb046c 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.3.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-start": "^1.168.27", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.36", "@workos/authkit-tanstack-react-start": "^0.5.0", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -25,7 +25,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/start-workos/src/routeTree.gen.ts b/examples/react/start-workos/src/routeTree.gen.ts index 49009fc848..559db280de 100644 --- a/examples/react/start-workos/src/routeTree.gen.ts +++ b/examples/react/start-workos/src/routeTree.gen.ts @@ -9,16 +9,20 @@ // 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 LogoutRouteImport } from './routes/logout' -import { Route as ClientRouteImport } from './routes/client' -import { Route as AuthenticatedRouteImport } from './routes/_authenticated' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthenticatedRouteImport } from './routes/_authenticated' +import { Route as ClientRouteImport } from './routes/client' +import { Route as LogoutRouteImport } from './routes/logout' import { Route as AuthenticatedAccountRouteImport } from './routes/_authenticated/account' import { Route as ApiAuthCallbackRouteImport } from './routes/api/auth/callback' -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const AuthenticatedRoute = AuthenticatedRouteImport.update({ + id: '/_authenticated', getParentRoute: () => rootRouteImport, } as any) const ClientRoute = ClientRouteImport.update({ @@ -26,13 +30,9 @@ const ClientRoute = ClientRouteImport.update({ path: '/client', getParentRoute: () => rootRouteImport, } as any) -const AuthenticatedRoute = AuthenticatedRouteImport.update({ - id: '/_authenticated', - getParentRoute: () => rootRouteImport, -} as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) const AuthenticatedAccountRoute = AuthenticatedAccountRouteImport.update({ @@ -94,18 +94,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport - parentRoute: typeof rootRouteImport - } - '/client': { - id: '/client' - path: '/client' - fullPath: '/client' - preLoaderRoute: typeof ClientRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_authenticated': { @@ -115,11 +108,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/client': { + id: '/client' + path: '/client' + fullPath: '/client' + preLoaderRoute: typeof ClientRouteImport + parentRoute: typeof rootRouteImport + } + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } '/_authenticated/account': { diff --git a/examples/react/view-transitions/package.json b/examples/react/view-transitions/package.json index a5cc8bc839..a597e63551 100644 --- a/examples/react/view-transitions/package.json +++ b/examples/react/view-transitions/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/view-transitions/src/routeTree.gen.ts b/examples/react/view-transitions/src/routeTree.gen.ts index 20ee76a1a5..8982fca68f 100644 --- a/examples/react/view-transitions/src/routeTree.gen.ts +++ b/examples/react/view-transitions/src/routeTree.gen.ts @@ -9,16 +9,16 @@ // 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 HowItWorksRouteImport } from './routes/how-it-works' +import { Route as IndexRouteImport } from './routes/index' import { Route as ExploreRouteImport } from './routes/explore' +import { Route as HowItWorksRouteImport } from './routes/how-it-works' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const HowItWorksRoute = HowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ExploreRoute = ExploreRouteImport.update({ @@ -26,16 +26,16 @@ const ExploreRoute = ExploreRouteImport.update({ path: '/explore', getParentRoute: () => rootRouteImport, } as any) +const HowItWorksRoute = HowItWorksRouteImport.update({ + id: '/how-it-works', + path: '/how-it-works', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -101,11 +101,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/how-it-works': { - id: '/how-it-works' - path: '/how-it-works' - fullPath: '/how-it-works' - preLoaderRoute: typeof HowItWorksRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/explore': { @@ -115,6 +115,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ExploreRouteImport parentRoute: typeof rootRouteImport } + '/how-it-works': { + id: '/how-it-works' + path: '/how-it-works' + fullPath: '/how-it-works' + preLoaderRoute: typeof HowItWorksRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -122,13 +129,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/examples/react/view-transitions/src/routes/posts.route.tsx b/examples/react/view-transitions/src/routes/posts.route.tsx index c63dc78ff2..4612ed3d5d 100644 --- a/examples/react/view-transitions/src/routes/posts.route.tsx +++ b/examples/react/view-transitions/src/routes/posts.route.tsx @@ -1,5 +1,10 @@ import * as React from 'react' -import { Link, Outlet, createFileRoute } from '@tanstack/react-router' +import { + Link, + Outlet, + createFileRoute, + useRouter, +} from '@tanstack/react-router' import { fetchPosts } from '../posts' export const Route = createFileRoute('/posts')({ @@ -9,6 +14,7 @@ export const Route = createFileRoute('/posts')({ function PostsLayoutComponent() { const posts = Route.useLoaderData() + const router = useRouter() return (
@@ -24,8 +30,30 @@ function PostsLayoutComponent() { }} className="block py-1 text-blue-600 hover:opacity-75" activeProps={{ className: 'font-bold underline' }} - // see styles.css for 'warp' transition - viewTransition={{ types: ['warp'] }} + // see styles.css for 'warp' and 'warp-backwards' transition + viewTransition={{ + types: ({ fromLocation, toLocation }) => { + const fromRoute = router + .matchRoutes(fromLocation?.pathname ?? '/') + .find((entry) => entry.routeId === '/posts/$postId') + const toRoute = router + .matchRoutes(toLocation?.pathname ?? '/') + .find((entry) => entry.routeId === '/posts/$postId') + + const fromIndex = Number(fromRoute?.params.postId) + const toIndex = Number(toRoute?.params.postId) + + if ( + Number.isNaN(fromIndex) || + Number.isNaN(toIndex) || + fromIndex === toIndex + ) { + return false // no transition + } + + return fromIndex > toIndex ? ['warp-backwards'] : ['warp'] + }, + }} >
{post.title.substring(0, 20)}
diff --git a/examples/react/view-transitions/src/styles.css b/examples/react/view-transitions/src/styles.css index b51e6e1244..502642b383 100644 --- a/examples/react/view-transitions/src/styles.css +++ b/examples/react/view-transitions/src/styles.css @@ -87,6 +87,16 @@ html:active-view-transition-type(warp) { } } +html:active-view-transition-type(warp-backwards) { + &::view-transition-old(post) { + animation: 400ms ease-out both warp-out-backwards; + } + + &::view-transition-new(post) { + animation: 400ms ease-out both warp-in-backwards; + } +} + @keyframes warp-out { from { opacity: 1; @@ -112,3 +122,29 @@ html:active-view-transition-type(warp) { transform: scale(1) rotate(0deg); } } + +@keyframes warp-in-backwards { + from { + opacity: 0; + filter: blur(15px) brightness(1.8); + transform: scale(0.9) rotate(45deg); + } + to { + opacity: 1; + filter: blur(0) brightness(1); + transform: scale(1) rotate(0deg); + } +} + +@keyframes warp-out-backwards { + from { + opacity: 1; + filter: blur(0) brightness(1); + transform: scale(1) rotate(0deg); + } + to { + opacity: 0; + filter: blur(15px) brightness(1.8); + transform: scale(1.1) rotate(-90deg); + } +} diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index c4d861b689..cbd7e1e24f 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", "framer-motion": "^11.18.2", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -23,7 +23,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index 48e7eaa6cb..9e62320366 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -13,9 +13,9 @@ "@tailwindcss/vite": "^4.2.2", "@tanstack/react-query": "^5.90.0", "@tanstack/react-query-devtools": "^5.90.0", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "@trpc/tanstack-react-query": "^11.4.3", diff --git a/examples/react/with-trpc-react-query/src/routeTree.gen.ts b/examples/react/with-trpc-react-query/src/routeTree.gen.ts index b407b58e4c..548a4b9e88 100644 --- a/examples/react/with-trpc-react-query/src/routeTree.gen.ts +++ b/examples/react/with-trpc-react-query/src/routeTree.gen.ts @@ -9,23 +9,23 @@ // 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 DashboardRouteImport } from './routes/dashboard' import { Route as IndexRouteImport } from './routes/index' +import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardPostsRouteImport } from './routes/dashboard.posts' import { Route as DashboardPostsIndexRouteImport } from './routes/dashboard.posts.index' import { Route as DashboardPostsPostIdRouteImport } from './routes/dashboard.posts.$postId' -const DashboardRoute = DashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardRoute = DashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardIndexRoute = DashboardIndexRouteImport.update({ id: '/', path: '/', @@ -98,13 +98,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -112,6 +105,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index 3474e12a6b..6b87233386 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/react-router": "^1.170.17", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/react-router": "^1.170.19", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/router-plugin": "^1.168.24", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "express": "^5.2.1", diff --git a/examples/react/with-trpc/src/routeTree.gen.ts b/examples/react/with-trpc/src/routeTree.gen.ts index b407b58e4c..548a4b9e88 100644 --- a/examples/react/with-trpc/src/routeTree.gen.ts +++ b/examples/react/with-trpc/src/routeTree.gen.ts @@ -9,23 +9,23 @@ // 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 DashboardRouteImport } from './routes/dashboard' import { Route as IndexRouteImport } from './routes/index' +import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardPostsRouteImport } from './routes/dashboard.posts' import { Route as DashboardPostsIndexRouteImport } from './routes/dashboard.posts.index' import { Route as DashboardPostsPostIdRouteImport } from './routes/dashboard.posts.$postId' -const DashboardRoute = DashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardRoute = DashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardIndexRoute = DashboardIndexRouteImport.update({ id: '/', path: '/', @@ -98,13 +98,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -112,6 +105,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/examples/solid/authenticated-routes-firebase/package.json b/examples/solid/authenticated-routes-firebase/package.json index e93521ece2..0883da702c 100644 --- a/examples/solid/authenticated-routes-firebase/package.json +++ b/examples/solid/authenticated-routes-firebase/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "firebase": "^11.4.0", @@ -22,7 +22,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/authenticated-routes-firebase/src/routeTree.gen.ts b/examples/solid/authenticated-routes-firebase/src/routeTree.gen.ts index ff0ae58ad7..4b98258aea 100644 --- a/examples/solid/authenticated-routes-firebase/src/routeTree.gen.ts +++ b/examples/solid/authenticated-routes-firebase/src/routeTree.gen.ts @@ -9,38 +9,38 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' -import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' +import { Route as AuthRouteImport } from './routes/_auth' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthDashboardRouteImport } from './routes/_auth.dashboard' +import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' import { Route as AuthInvoicesIndexRouteImport } from './routes/_auth.invoices.index' import { Route as AuthInvoicesInvoiceIdRouteImport } from './routes/_auth.invoices.$invoiceId' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => AuthRoute, -} as any) const AuthDashboardRoute = AuthDashboardRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => AuthRoute, } as any) +const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => AuthRoute, +} as any) const AuthInvoicesIndexRoute = AuthInvoicesIndexRouteImport.update({ id: '/', path: '/', @@ -107,11 +107,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -121,20 +121,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_auth/invoices': { - id: '/_auth/invoices' - path: '/invoices' - fullPath: '/invoices' - preLoaderRoute: typeof AuthInvoicesRouteImport - parentRoute: typeof AuthRoute - } '/_auth/dashboard': { id: '/_auth/dashboard' path: '/dashboard' @@ -142,6 +135,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthDashboardRouteImport parentRoute: typeof AuthRoute } + '/_auth/invoices': { + id: '/_auth/invoices' + path: '/invoices' + fullPath: '/invoices' + preLoaderRoute: typeof AuthInvoicesRouteImport + parentRoute: typeof AuthRoute + } '/_auth/invoices/': { id: '/_auth/invoices/' path: '/' diff --git a/examples/solid/authenticated-routes/package.json b/examples/solid/authenticated-routes/package.json index 58e7f08ed5..3f2f83dce9 100644 --- a/examples/solid/authenticated-routes/package.json +++ b/examples/solid/authenticated-routes/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "redaxios": "^0.5.1", @@ -20,7 +20,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/authenticated-routes/src/routeTree.gen.ts b/examples/solid/authenticated-routes/src/routeTree.gen.ts index ff0ae58ad7..4b98258aea 100644 --- a/examples/solid/authenticated-routes/src/routeTree.gen.ts +++ b/examples/solid/authenticated-routes/src/routeTree.gen.ts @@ -9,38 +9,38 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' -import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' +import { Route as AuthRouteImport } from './routes/_auth' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthDashboardRouteImport } from './routes/_auth.dashboard' +import { Route as AuthInvoicesRouteImport } from './routes/_auth.invoices' import { Route as AuthInvoicesIndexRouteImport } from './routes/_auth.invoices.index' import { Route as AuthInvoicesInvoiceIdRouteImport } from './routes/_auth.invoices.$invoiceId' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => AuthRoute, -} as any) const AuthDashboardRoute = AuthDashboardRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => AuthRoute, } as any) +const AuthInvoicesRoute = AuthInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => AuthRoute, +} as any) const AuthInvoicesIndexRoute = AuthInvoicesIndexRouteImport.update({ id: '/', path: '/', @@ -107,11 +107,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -121,20 +121,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_auth/invoices': { - id: '/_auth/invoices' - path: '/invoices' - fullPath: '/invoices' - preLoaderRoute: typeof AuthInvoicesRouteImport - parentRoute: typeof AuthRoute - } '/_auth/dashboard': { id: '/_auth/dashboard' path: '/dashboard' @@ -142,6 +135,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthDashboardRouteImport parentRoute: typeof AuthRoute } + '/_auth/invoices': { + id: '/_auth/invoices' + path: '/invoices' + fullPath: '/invoices' + preLoaderRoute: typeof AuthInvoicesRouteImport + parentRoute: typeof AuthRoute + } '/_auth/invoices/': { id: '/_auth/invoices/' path: '/' diff --git a/examples/solid/basic-default-search-params/package.json b/examples/solid/basic-default-search-params/package.json index 164adc091b..15b634c642 100644 --- a/examples/solid/basic-default-search-params/package.json +++ b/examples/solid/basic-default-search-params/package.json @@ -20,7 +20,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-devtools-panel/package.json b/examples/solid/basic-devtools-panel/package.json index 7ec5b60db5..199a2a6a12 100644 --- a/examples/solid/basic-devtools-panel/package.json +++ b/examples/solid/basic-devtools-panel/package.json @@ -18,7 +18,8 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-file-based/package.json b/examples/solid/basic-file-based/package.json index 6a3d8a1ab1..206c3d013b 100644 --- a/examples/solid/basic-file-based/package.json +++ b/examples/solid/basic-file-based/package.json @@ -19,8 +19,9 @@ "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2", + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-file-based/src/routeTree.gen.ts b/examples/solid/basic-file-based/src/routeTree.gen.ts index bde5d68567..5942acc907 100644 --- a/examples/solid/basic-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-file-based/src/routeTree.gen.ts @@ -9,29 +9,34 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,11 +117,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_pathlessLayout': { @@ -131,13 +131,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -152,12 +159,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/examples/solid/basic-non-nested-devtools/package.json b/examples/solid/basic-non-nested-devtools/package.json index 6aa346e419..c8b89044b8 100644 --- a/examples/solid/basic-non-nested-devtools/package.json +++ b/examples/solid/basic-non-nested-devtools/package.json @@ -20,7 +20,8 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-solid-query-file-based/package.json b/examples/solid/basic-solid-query-file-based/package.json index 780c55ea13..b76242fb06 100644 --- a/examples/solid/basic-solid-query-file-based/package.json +++ b/examples/solid/basic-solid-query-file-based/package.json @@ -22,8 +22,9 @@ "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2", + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-solid-query-file-based/src/routeTree.gen.ts b/examples/solid/basic-solid-query-file-based/src/routeTree.gen.ts index bde5d68567..5942acc907 100644 --- a/examples/solid/basic-solid-query-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-solid-query-file-based/src/routeTree.gen.ts @@ -9,29 +9,34 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -42,10 +47,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -53,12 +59,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -117,11 +117,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_pathlessLayout': { @@ -131,13 +131,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -152,12 +159,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -166,13 +173,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/examples/solid/basic-solid-query/package.json b/examples/solid/basic-solid-query/package.json index 26028e22a7..b51904e234 100644 --- a/examples/solid/basic-solid-query/package.json +++ b/examples/solid/basic-solid-query/package.json @@ -20,8 +20,9 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2", + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-ssr-file-based/package.json b/examples/solid/basic-ssr-file-based/package.json index c2f94e16f8..080b9b1c87 100644 --- a/examples/solid/basic-ssr-file-based/package.json +++ b/examples/solid/basic-ssr-file-based/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@solidjs/web": "2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "compression": "^1.8.0", "express": "^5.2.1", @@ -23,7 +23,8 @@ "devDependencies": { "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@types/express": "^5.0.6", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-ssr-file-based/src/routeTree.gen.ts b/examples/solid/basic-ssr-file-based/src/routeTree.gen.ts index 26eb09e67f..fc6a3e4377 100644 --- a/examples/solid/basic-ssr-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-ssr-file-based/src/routeTree.gen.ts @@ -9,12 +9,17 @@ // 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 ErrorRouteImport } from './routes/error' import { Route as PostsRouteRouteImport } from './routes/posts/route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts/index' import { Route as PostsPostIdRouteImport } from './routes/posts/$postId' +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const ErrorRoute = ErrorRouteImport.update({ id: '/error', path: '/error', @@ -25,11 +30,6 @@ const PostsRouteRoute = PostsRouteRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -78,6 +78,13 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/error': { id: '/error' path: '/error' @@ -92,13 +99,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/examples/solid/basic-ssr-streaming-file-based/package.json b/examples/solid/basic-ssr-streaming-file-based/package.json index 51d7d63bae..bf21655416 100644 --- a/examples/solid/basic-ssr-streaming-file-based/package.json +++ b/examples/solid/basic-ssr-streaming-file-based/package.json @@ -25,9 +25,10 @@ "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@types/express": "^5.0.6", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-ssr-streaming-file-based/src/routeTree.gen.ts b/examples/solid/basic-ssr-streaming-file-based/src/routeTree.gen.ts index be2ed5b18d..0161f550f0 100644 --- a/examples/solid/basic-ssr-streaming-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-ssr-streaming-file-based/src/routeTree.gen.ts @@ -9,19 +9,23 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as CounterRouteImport } from './routes/counter' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CounterRouteImport } from './routes/counter' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) const CounterRoute = CounterRouteImport.update({ @@ -29,15 +33,16 @@ const CounterRoute = CounterRouteImport.update({ path: '/counter', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', - getParentRoute: () => rootRouteImport, -} as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -48,10 +53,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ @@ -59,12 +65,6 @@ const PathlessLayoutNestedLayoutRouteBRoute = path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -129,11 +129,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } '/counter': { @@ -143,19 +150,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof CounterRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -171,12 +178,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -185,13 +192,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute - } } } diff --git a/examples/solid/basic-virtual-file-based/package.json b/examples/solid/basic-virtual-file-based/package.json index 7b60756999..d4db7b88a9 100644 --- a/examples/solid/basic-virtual-file-based/package.json +++ b/examples/solid/basic-virtual-file-based/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@tanstack/virtual-file-routes": "^1.162.0", @@ -21,7 +21,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-virtual-file-based/src/routeTree.gen.ts b/examples/solid/basic-virtual-file-based/src/routeTree.gen.ts index cb96aee7ba..c41eda86bf 100644 --- a/examples/solid/basic-virtual-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-virtual-file-based/src/routeTree.gen.ts @@ -9,77 +9,77 @@ // 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 postsPostsRouteImport } from './routes/posts/posts' -import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' -import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' +import { Route as postsPostsRouteImport } from './routes/posts/posts' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' import { Route as ClassicHelloRouteRouteImport } from './routes/file-based-subtree/hello/route' +import { Route as postsPostsHomeRouteImport } from './routes/posts/posts-home' +import { Route as postsPostsDetailRouteImport } from './routes/posts/posts-detail' import { Route as ClassicHelloIndexRouteImport } from './routes/file-based-subtree/hello/index' -import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as ClassicHelloUniverseRouteImport } from './routes/file-based-subtree/hello/universe' -import { Route as bRouteImport } from './routes/b' +import { Route as ClassicHelloWorldRouteImport } from './routes/file-based-subtree/hello/world' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const postsPostsRoute = postsPostsRouteImport.update({ - id: '/posts', - path: '/posts', +const homeRoute = homeRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ id: '/_first', getParentRoute: () => rootRouteImport, } as any) -const homeRoute = homeRouteImport.update({ - id: '/', - path: '/', +const postsPostsRoute = postsPostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => postsPostsRoute, -} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) +const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ + id: '/classic/hello', + path: '/classic/hello', + getParentRoute: () => rootRouteImport, +} as any) const postsPostsHomeRoute = postsPostsHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => postsPostsRoute, } as any) -const ClassicHelloRouteRoute = ClassicHelloRouteRouteImport.update({ - id: '/classic/hello', - path: '/classic/hello', - getParentRoute: () => rootRouteImport, +const postsPostsDetailRoute = postsPostsDetailRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => postsPostsRoute, } as any) const ClassicHelloIndexRoute = ClassicHelloIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ - id: '/world', - path: '/world', - getParentRoute: () => ClassicHelloRouteRoute, -} as any) const ClassicHelloUniverseRoute = ClassicHelloUniverseRouteImport.update({ id: '/universe', path: '/universe', getParentRoute: () => ClassicHelloRouteRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, +const ClassicHelloWorldRoute = ClassicHelloWorldRouteImport.update({ + id: '/world', + path: '/world', + getParentRoute: () => ClassicHelloRouteRoute, } as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -166,11 +166,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof postsPostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } '/_first': { @@ -180,20 +180,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutFirstLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof homeRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof postsPostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof postsPostsDetailRouteImport - parentRoute: typeof postsPostsRoute - } '/_first/_second-layout': { id: '/_first/_second-layout' path: '' @@ -201,6 +194,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } + '/classic/hello': { + id: '/classic/hello' + path: '/classic/hello' + fullPath: '/classic/hello' + preLoaderRoute: typeof ClassicHelloRouteRouteImport + parentRoute: typeof rootRouteImport + } '/posts/': { id: '/posts/' path: '/' @@ -208,12 +208,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof postsPostsHomeRouteImport parentRoute: typeof postsPostsRoute } - '/classic/hello': { - id: '/classic/hello' - path: '/classic/hello' - fullPath: '/classic/hello' - preLoaderRoute: typeof ClassicHelloRouteRouteImport - parentRoute: typeof rootRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof postsPostsDetailRouteImport + parentRoute: typeof postsPostsRoute } '/classic/hello/': { id: '/classic/hello/' @@ -222,13 +222,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloIndexRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/classic/hello/world': { - id: '/classic/hello/world' - path: '/world' - fullPath: '/classic/hello/world' - preLoaderRoute: typeof ClassicHelloWorldRouteImport - parentRoute: typeof ClassicHelloRouteRoute - } '/classic/hello/universe': { id: '/classic/hello/universe' path: '/universe' @@ -236,12 +229,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ClassicHelloUniverseRouteImport parentRoute: typeof ClassicHelloRouteRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - preLoaderRoute: typeof bRouteImport - parentRoute: typeof layoutSecondLayoutRoute + '/classic/hello/world': { + id: '/classic/hello/world' + path: '/world' + fullPath: '/classic/hello/world' + preLoaderRoute: typeof ClassicHelloWorldRouteImport + parentRoute: typeof ClassicHelloRouteRoute } '/_first/_second-layout/route-without-file/layout-a': { id: '/_first/_second-layout/route-without-file/layout-a' @@ -250,6 +243,13 @@ declare module '@tanstack/solid-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/examples/solid/basic-virtual-inside-file-based/package.json b/examples/solid/basic-virtual-inside-file-based/package.json index 5de13a45c1..2fdb2138e3 100644 --- a/examples/solid/basic-virtual-inside-file-based/package.json +++ b/examples/solid/basic-virtual-inside-file-based/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@tanstack/virtual-file-routes": "^1.162.0", @@ -21,7 +21,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/basic-virtual-inside-file-based/src/routeTree.gen.ts b/examples/solid/basic-virtual-inside-file-based/src/routeTree.gen.ts index 037dac78a3..6117330aa3 100644 --- a/examples/solid/basic-virtual-inside-file-based/src/routeTree.gen.ts +++ b/examples/solid/basic-virtual-inside-file-based/src/routeTree.gen.ts @@ -9,36 +9,31 @@ // 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 PostsRouteImport } from './routes/posts' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' -import { Route as PostsDetailsRouteImport } from './routes/posts/details' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as PostsRouteImport } from './routes/posts' import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsHomeRouteImport } from './routes/posts/home' -import { Route as PostsLetsGoIndexRouteImport } from './routes/posts/lets-go/index' -import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsDetailsRouteImport } from './routes/posts/details' import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsLetsGoIndexRouteImport } from './routes/posts/lets-go/index' import { Route as PostsLetsGoDeeperHomeRouteImport } from './routes/posts/lets-go/deeper/home' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PostsDetailsRoute = PostsDetailsRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) const LayoutLayout2Route = LayoutLayout2RouteImport.update({ id: '/_layout-2', getParentRoute: () => LayoutRoute, @@ -48,20 +43,25 @@ const PostsHomeRoute = PostsHomeRouteImport.update({ path: '/', getParentRoute: () => PostsRoute, } as any) -const PostsLetsGoIndexRoute = PostsLetsGoIndexRouteImport.update({ - id: '/inception/', - path: '/inception/', +const PostsDetailsRoute = PostsDetailsRouteImport.update({ + id: '/$postId', + path: '/$postId', getParentRoute: () => PostsRoute, } as any) +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const PostsLetsGoIndexRoute = PostsLetsGoIndexRouteImport.update({ + id: '/inception/', + path: '/inception/', + getParentRoute: () => PostsRoute, } as any) const PostsLetsGoDeeperHomeRoute = PostsLetsGoDeeperHomeRouteImport.update({ id: '/inception/deeper/', @@ -143,11 +143,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout': { @@ -157,20 +157,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId': { - id: '/posts/$postId' - path: '/$postId' - fullPath: '/posts/$postId' - preLoaderRoute: typeof PostsDetailsRouteImport - parentRoute: typeof PostsRoute - } '/_layout/_layout-2': { id: '/_layout/_layout-2' path: '' @@ -185,13 +178,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsHomeRouteImport parentRoute: typeof PostsRoute } - '/posts/inception/': { - id: '/posts/inception/' - path: '/inception' - fullPath: '/posts/inception/' - preLoaderRoute: typeof PostsLetsGoIndexRouteImport + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsDetailsRouteImport 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/layout-b': { id: '/_layout/_layout-2/layout-b' path: '/layout-b' @@ -199,12 +199,12 @@ 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 + '/posts/inception/': { + id: '/posts/inception/' + path: '/inception' + fullPath: '/posts/inception/' + preLoaderRoute: typeof PostsLetsGoIndexRouteImport + parentRoute: typeof PostsRoute } '/posts/inception/deeper/': { id: '/posts/inception/deeper/' diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json index 211949d7f7..2c4b859f88 100644 --- a/examples/solid/basic/package.json +++ b/examples/solid/basic/package.json @@ -20,7 +20,8 @@ "devDependencies": { "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/deferred-data/package.json b/examples/solid/deferred-data/package.json index f6ca477664..6d9035bb11 100644 --- a/examples/solid/deferred-data/package.json +++ b/examples/solid/deferred-data/package.json @@ -19,7 +19,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/i18n-paraglide/package.json b/examples/solid/i18n-paraglide/package.json index b054fca184..122a205b35 100644 --- a/examples/solid/i18n-paraglide/package.json +++ b/examples/solid/i18n-paraglide/package.json @@ -12,7 +12,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "solid-js": "2.0.0-beta.29", "tailwindcss": "^4.2.2" @@ -20,7 +20,8 @@ "devDependencies": { "@inlang/paraglide-js": "^2.4.0", "@types/node": "^22.18.6", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/i18n-paraglide/src/routeTree.gen.ts b/examples/solid/i18n-paraglide/src/routeTree.gen.ts index 333a815c38..98df1e83d8 100644 --- a/examples/solid/i18n-paraglide/src/routeTree.gen.ts +++ b/examples/solid/i18n-paraglide/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/kitchen-sink-file-based/package.json b/examples/solid/kitchen-sink-file-based/package.json index 800187b902..12ab856cb9 100644 --- a/examples/solid/kitchen-sink-file-based/package.json +++ b/examples/solid/kitchen-sink-file-based/package.json @@ -20,8 +20,9 @@ "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2", + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/kitchen-sink-file-based/src/routeTree.gen.ts b/examples/solid/kitchen-sink-file-based/src/routeTree.gen.ts index 0c6e04f63d..76bb314a71 100644 --- a/examples/solid/kitchen-sink-file-based/src/routeTree.gen.ts +++ b/examples/solid/kitchen-sink-file-based/src/routeTree.gen.ts @@ -9,81 +9,71 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as IndexRouteImport } from './routes/index' import { Route as AuthRouteImport } from './routes/_auth' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' -import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' -import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' -import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' -import { Route as AuthProfileRouteImport } from './routes/_auth.profile' +import { Route as LoginRouteImport } from './routes/login' import { Route as thisFolderIsNotInTheUrlRouteGroupRouteImport } from './routes/(this-folder-is-not-in-the-url)/route-group' -import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as AuthProfileRouteImport } from './routes/_auth.profile' +import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' +import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' -import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' -import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' +import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) const DashboardRouteRoute = DashboardRouteRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ - id: '/expensive/', - path: '/expensive/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const DashboardIndexRoute = DashboardIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardRouteRoute, -} as any) -const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutRoute, +const thisFolderIsNotInTheUrlRouteGroupRoute = + thisFolderIsNotInTheUrlRouteGroupRouteImport.update({ + id: '/(this-folder-is-not-in-the-url)/route-group', + path: '/route-group', + getParentRoute: () => rootRouteImport, + } as any) +const AuthProfileRoute = AuthProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => AuthRoute, } as any) const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutRoute, } as any) -const AuthProfileRoute = AuthProfileRouteImport.update({ - id: '/profile', - path: '/profile', - getParentRoute: () => AuthRoute, +const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutRoute, } as any) -const thisFolderIsNotInTheUrlRouteGroupRoute = - thisFolderIsNotInTheUrlRouteGroupRouteImport.update({ - id: '/(this-folder-is-not-in-the-url)/route-group', - path: '/route-group', - getParentRoute: () => rootRouteImport, - } as any) -const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ - id: '/users', - path: '/users', +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => DashboardRouteRoute, } as any) const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ @@ -91,27 +81,37 @@ const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ path: '/invoices', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardUsersRouteRoute, +const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => DashboardRouteRoute, +} as any) +const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ + id: '/expensive/', + path: '/expensive/', + getParentRoute: () => rootRouteImport, } as any) const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) -const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ - id: '/user', - path: '/user', - getParentRoute: () => DashboardUsersRouteRoute, -} as any) const DashboardInvoicesInvoiceIdRoute = DashboardInvoicesInvoiceIdRouteImport.update({ id: '/$invoiceId', path: '/$invoiceId', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) +const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) +const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ + id: '/user', + path: '/user', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -229,18 +229,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' + '/': { + id: '/' + path: '/' fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -250,6 +243,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard': { id: '/dashboard' path: '/dashboard' @@ -257,33 +257,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/expensive/': { - id: '/expensive/' - path: '/expensive' - fullPath: '/expensive/' - preLoaderRoute: typeof ExpensiveIndexRouteImport + '/(this-folder-is-not-in-the-url)/route-group': { + id: '/(this-folder-is-not-in-the-url)/route-group' + path: '/route-group' + fullPath: '/route-group' + preLoaderRoute: typeof thisFolderIsNotInTheUrlRouteGroupRouteImport parentRoute: typeof rootRouteImport } - '/dashboard/': { - id: '/dashboard/' - path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof DashboardIndexRouteImport - parentRoute: typeof DashboardRouteRoute - } - '/_pathlessLayout/route-b': { - id: '/_pathlessLayout/route-b' - path: '/route-b' - fullPath: '/route-b' - preLoaderRoute: typeof PathlessLayoutRouteBRouteImport - parentRoute: typeof PathlessLayoutRoute + '/_auth/profile': { + id: '/_auth/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof AuthProfileRouteImport + parentRoute: typeof AuthRoute } '/_pathlessLayout/route-a': { id: '/_pathlessLayout/route-a' @@ -292,25 +285,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutRouteARouteImport parentRoute: typeof PathlessLayoutRoute } - '/_auth/profile': { - id: '/_auth/profile' - path: '/profile' - fullPath: '/profile' - preLoaderRoute: typeof AuthProfileRouteImport - parentRoute: typeof AuthRoute - } - '/(this-folder-is-not-in-the-url)/route-group': { - id: '/(this-folder-is-not-in-the-url)/route-group' - path: '/route-group' - fullPath: '/route-group' - preLoaderRoute: typeof thisFolderIsNotInTheUrlRouteGroupRouteImport - parentRoute: typeof rootRouteImport + '/_pathlessLayout/route-b': { + id: '/_pathlessLayout/route-b' + path: '/route-b' + fullPath: '/route-b' + preLoaderRoute: typeof PathlessLayoutRouteBRouteImport + parentRoute: typeof PathlessLayoutRoute } - '/dashboard/users': { - id: '/dashboard/users' - path: '/users' - fullPath: '/dashboard/users' - preLoaderRoute: typeof DashboardUsersRouteRouteImport + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport parentRoute: typeof DashboardRouteRoute } '/dashboard/invoices': { @@ -320,12 +306,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardInvoicesRouteRouteImport parentRoute: typeof DashboardRouteRoute } - '/dashboard/users/': { - id: '/dashboard/users/' - path: '/' - fullPath: '/dashboard/users/' - preLoaderRoute: typeof DashboardUsersIndexRouteImport - parentRoute: typeof DashboardUsersRouteRoute + '/dashboard/users': { + id: '/dashboard/users' + path: '/users' + fullPath: '/dashboard/users' + preLoaderRoute: typeof DashboardUsersRouteRouteImport + parentRoute: typeof DashboardRouteRoute + } + '/expensive/': { + id: '/expensive/' + path: '/expensive' + fullPath: '/expensive/' + preLoaderRoute: typeof ExpensiveIndexRouteImport + parentRoute: typeof rootRouteImport } '/dashboard/invoices/': { id: '/dashboard/invoices/' @@ -334,13 +327,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardInvoicesIndexRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } - '/dashboard/users/user': { - id: '/dashboard/users/user' - path: '/user' - fullPath: '/dashboard/users/user' - preLoaderRoute: typeof DashboardUsersUserRouteImport - parentRoute: typeof DashboardUsersRouteRoute - } '/dashboard/invoices/$invoiceId': { id: '/dashboard/invoices/$invoiceId' path: '/$invoiceId' @@ -348,6 +334,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } + '/dashboard/users/': { + id: '/dashboard/users/' + path: '/' + fullPath: '/dashboard/users/' + preLoaderRoute: typeof DashboardUsersIndexRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } + '/dashboard/users/user': { + id: '/dashboard/users/user' + path: '/user' + fullPath: '/dashboard/users/user' + preLoaderRoute: typeof DashboardUsersUserRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } } } diff --git a/examples/solid/kitchen-sink-solid-query-file-based/package.json b/examples/solid/kitchen-sink-solid-query-file-based/package.json index e810fd1edf..2e22e82920 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/package.json +++ b/examples/solid/kitchen-sink-solid-query-file-based/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-query": "^6.0.0-beta.7", "@tanstack/solid-query-devtools": "^6.0.0-beta.7", "@tanstack/solid-router": "^2.0.0-beta.29", @@ -23,7 +23,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts index e296846127..8235344782 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts @@ -9,108 +9,108 @@ // 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 LoginRouteImport } from './routes/login' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as IndexRouteImport } from './routes/index' import { Route as AuthRouteImport } from './routes/_auth' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' -import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' -import { Route as FooBarRouteImport } from './routes/foo/bar' -import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' -import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as LoginRouteImport } from './routes/login' import { Route as AuthProfileRouteImport } from './routes/_auth.profile' -import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' +import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' -import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' +import { Route as FooBarRouteImport } from './routes/foo/bar' import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' -import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' +import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, } as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) const DashboardRouteRoute = DashboardRouteRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ - id: '/expensive/', - path: '/expensive/', +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', getParentRoute: () => rootRouteImport, } as any) -const DashboardIndexRoute = DashboardIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardRouteRoute, +const AuthProfileRoute = AuthProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => AuthRoute, } as any) -const FooBarRoute = FooBarRouteImport.update({ - id: '/foo/bar', - path: '/foo/bar', - getParentRoute: () => rootRouteImport, +const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutRoute, } as any) const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ id: '/route-b', path: '/route-b', getParentRoute: () => PathlessLayoutRoute, } as any) -const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutRoute, +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardRouteRoute, } as any) -const AuthProfileRoute = AuthProfileRouteImport.update({ - id: '/profile', - path: '/profile', - getParentRoute: () => AuthRoute, +const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => DashboardRouteRoute, } as any) const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ id: '/users', path: '/users', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ - id: '/invoices', - path: '/invoices', - getParentRoute: () => DashboardRouteRoute, +const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ + id: '/expensive/', + path: '/expensive/', + getParentRoute: () => rootRouteImport, } as any) -const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DashboardUsersRouteRoute, +const FooBarRoute = FooBarRouteImport.update({ + id: '/foo/bar', + path: '/foo/bar', + getParentRoute: () => rootRouteImport, } as any) const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) -const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ - id: '/user', - path: '/user', - getParentRoute: () => DashboardUsersRouteRoute, -} as any) const DashboardInvoicesInvoiceIdRoute = DashboardInvoicesInvoiceIdRouteImport.update({ id: '/$invoiceId', path: '/$invoiceId', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) +const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) +const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ + id: '/user', + path: '/user', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -228,18 +228,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' + '/': { + id: '/' + path: '/' fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_auth': { @@ -249,6 +242,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard': { id: '/dashboard' path: '/dashboard' @@ -256,33 +256,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/expensive/': { - id: '/expensive/' - path: '/expensive' - fullPath: '/expensive/' - preLoaderRoute: typeof ExpensiveIndexRouteImport + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/dashboard/': { - id: '/dashboard/' - path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof DashboardIndexRouteImport - parentRoute: typeof DashboardRouteRoute + '/_auth/profile': { + id: '/_auth/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof AuthProfileRouteImport + parentRoute: typeof AuthRoute } - '/foo/bar': { - id: '/foo/bar' - path: '/foo/bar' - fullPath: '/foo/bar' - preLoaderRoute: typeof FooBarRouteImport - parentRoute: typeof rootRouteImport + '/_pathlessLayout/route-a': { + id: '/_pathlessLayout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutRoute } '/_pathlessLayout/route-b': { id: '/_pathlessLayout/route-b' @@ -291,19 +284,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutRoute } - '/_pathlessLayout/route-a': { - id: '/_pathlessLayout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutRoute + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport + parentRoute: typeof DashboardRouteRoute } - '/_auth/profile': { - id: '/_auth/profile' - path: '/profile' - fullPath: '/profile' - preLoaderRoute: typeof AuthProfileRouteImport - parentRoute: typeof AuthRoute + '/dashboard/invoices': { + id: '/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof DashboardInvoicesRouteRouteImport + parentRoute: typeof DashboardRouteRoute } '/dashboard/users': { id: '/dashboard/users' @@ -312,19 +305,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardUsersRouteRouteImport parentRoute: typeof DashboardRouteRoute } - '/dashboard/invoices': { - id: '/dashboard/invoices' - path: '/invoices' - fullPath: '/dashboard/invoices' - preLoaderRoute: typeof DashboardInvoicesRouteRouteImport - parentRoute: typeof DashboardRouteRoute + '/expensive/': { + id: '/expensive/' + path: '/expensive' + fullPath: '/expensive/' + preLoaderRoute: typeof ExpensiveIndexRouteImport + parentRoute: typeof rootRouteImport } - '/dashboard/users/': { - id: '/dashboard/users/' - path: '/' - fullPath: '/dashboard/users/' - preLoaderRoute: typeof DashboardUsersIndexRouteImport - parentRoute: typeof DashboardUsersRouteRoute + '/foo/bar': { + id: '/foo/bar' + path: '/foo/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof FooBarRouteImport + parentRoute: typeof rootRouteImport } '/dashboard/invoices/': { id: '/dashboard/invoices/' @@ -333,13 +326,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardInvoicesIndexRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } - '/dashboard/users/user': { - id: '/dashboard/users/user' - path: '/user' - fullPath: '/dashboard/users/user' - preLoaderRoute: typeof DashboardUsersUserRouteImport - parentRoute: typeof DashboardUsersRouteRoute - } '/dashboard/invoices/$invoiceId': { id: '/dashboard/invoices/$invoiceId' path: '/$invoiceId' @@ -347,6 +333,20 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport parentRoute: typeof DashboardInvoicesRouteRoute } + '/dashboard/users/': { + id: '/dashboard/users/' + path: '/' + fullPath: '/dashboard/users/' + preLoaderRoute: typeof DashboardUsersIndexRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } + '/dashboard/users/user': { + id: '/dashboard/users/user' + path: '/user' + fullPath: '/dashboard/users/user' + preLoaderRoute: typeof DashboardUsersUserRouteImport + parentRoute: typeof DashboardUsersRouteRoute + } } } diff --git a/examples/solid/kitchen-sink-solid-query/package.json b/examples/solid/kitchen-sink-solid-query/package.json index 358f8e41fc..7af06c4f44 100644 --- a/examples/solid/kitchen-sink-solid-query/package.json +++ b/examples/solid/kitchen-sink-solid-query/package.json @@ -22,7 +22,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/kitchen-sink/package.json b/examples/solid/kitchen-sink/package.json index 00e7fa13ef..74097e94ab 100644 --- a/examples/solid/kitchen-sink/package.json +++ b/examples/solid/kitchen-sink/package.json @@ -20,7 +20,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/large-file-based/package.json b/examples/solid/large-file-based/package.json index 5d3347b36e..60fbe7ea9f 100644 --- a/examples/solid/large-file-based/package.json +++ b/examples/solid/large-file-based/package.json @@ -13,7 +13,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-query": "^6.0.0-beta.7", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", @@ -23,7 +23,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/large-file-based/src/routeTree.gen.ts b/examples/solid/large-file-based/src/routeTree.gen.ts index d345b69acf..ea7329778c 100644 --- a/examples/solid/large-file-based/src/routeTree.gen.ts +++ b/examples/solid/large-file-based/src/routeTree.gen.ts @@ -9,23 +9,18 @@ // 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 RelativeRouteImport } from './routes/relative' -import { Route as LinkPropsRouteImport } from './routes/linkProps' +import { Route as IndexRouteImport } from './routes/index' import { Route as AbsoluteRouteImport } from './routes/absolute' -import { Route as SearchRouteRouteImport } from './routes/search/route' +import { Route as LinkPropsRouteImport } from './routes/linkProps' import { Route as ParamsRouteRouteImport } from './routes/params/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' +import { Route as RelativeRouteImport } from './routes/relative' +import { Route as SearchRouteRouteImport } from './routes/search/route' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' +import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' -const RelativeRoute = RelativeRouteImport.update({ - id: '/relative', - path: '/relative', - getParentRoute: () => rootRouteImport, -} as any) -const LinkPropsRoute = LinkPropsRouteImport.update({ - id: '/linkProps', - path: '/linkProps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AbsoluteRoute = AbsoluteRouteImport.update({ @@ -33,9 +28,9 @@ const AbsoluteRoute = AbsoluteRouteImport.update({ path: '/absolute', getParentRoute: () => rootRouteImport, } as any) -const SearchRouteRoute = SearchRouteRouteImport.update({ - id: '/search', - path: '/search', +const LinkPropsRoute = LinkPropsRouteImport.update({ + id: '/linkProps', + path: '/linkProps', getParentRoute: () => rootRouteImport, } as any) const ParamsRouteRoute = ParamsRouteRouteImport.update({ @@ -43,21 +38,26 @@ const ParamsRouteRoute = ParamsRouteRouteImport.update({ path: '/params', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RelativeRoute = RelativeRouteImport.update({ + id: '/relative', + path: '/relative', getParentRoute: () => rootRouteImport, } as any) -const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ - id: '/searchPlaceholder', - path: '/searchPlaceholder', - getParentRoute: () => SearchRouteRoute, +const SearchRouteRoute = SearchRouteRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => rootRouteImport, } as any) const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderRouteImport.update({ id: '/$paramsPlaceholder', path: '/$paramsPlaceholder', getParentRoute: () => ParamsRouteRoute, } as any) +const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ + id: '/searchPlaceholder', + path: '/searchPlaceholder', + getParentRoute: () => SearchRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -134,18 +134,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/relative': { - id: '/relative' - path: '/relative' - fullPath: '/relative' - preLoaderRoute: typeof RelativeRouteImport - parentRoute: typeof rootRouteImport - } - '/linkProps': { - id: '/linkProps' - path: '/linkProps' - fullPath: '/linkProps' - preLoaderRoute: typeof LinkPropsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/absolute': { @@ -155,11 +148,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AbsoluteRouteImport parentRoute: typeof rootRouteImport } - '/search': { - id: '/search' - path: '/search' - fullPath: '/search' - preLoaderRoute: typeof SearchRouteRouteImport + '/linkProps': { + id: '/linkProps' + path: '/linkProps' + fullPath: '/linkProps' + preLoaderRoute: typeof LinkPropsRouteImport parentRoute: typeof rootRouteImport } '/params': { @@ -169,19 +162,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/relative': { + id: '/relative' + path: '/relative' + fullPath: '/relative' + preLoaderRoute: typeof RelativeRouteImport parentRoute: typeof rootRouteImport } - '/search/searchPlaceholder': { - id: '/search/searchPlaceholder' - path: '/searchPlaceholder' - fullPath: '/search/searchPlaceholder' - preLoaderRoute: typeof SearchSearchPlaceholderRouteImport - parentRoute: typeof SearchRouteRoute + '/search': { + id: '/search' + path: '/search' + fullPath: '/search' + preLoaderRoute: typeof SearchRouteRouteImport + parentRoute: typeof rootRouteImport } '/params/$paramsPlaceholder': { id: '/params/$paramsPlaceholder' @@ -190,6 +183,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsParamsPlaceholderRouteImport parentRoute: typeof ParamsRouteRoute } + '/search/searchPlaceholder': { + id: '/search/searchPlaceholder' + path: '/searchPlaceholder' + fullPath: '/search/searchPlaceholder' + preLoaderRoute: typeof SearchSearchPlaceholderRouteImport + parentRoute: typeof SearchRouteRoute + } } } diff --git a/examples/solid/location-masking/package.json b/examples/solid/location-masking/package.json index 41f84f5eb0..798c32571d 100644 --- a/examples/solid/location-masking/package.json +++ b/examples/solid/location-masking/package.json @@ -19,7 +19,8 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/navigation-blocking/package.json b/examples/solid/navigation-blocking/package.json index 42e742be94..7c9b08a63d 100644 --- a/examples/solid/navigation-blocking/package.json +++ b/examples/solid/navigation-blocking/package.json @@ -19,7 +19,8 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/quickstart-esbuild-file-based/package.json b/examples/solid/quickstart-esbuild-file-based/package.json index ae4ebdfce3..5c17de060e 100644 --- a/examples/solid/quickstart-esbuild-file-based/package.json +++ b/examples/solid/quickstart-esbuild-file-based/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@solidjs/web": "2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "redaxios": "^0.5.1", diff --git a/examples/solid/quickstart-file-based/package.json b/examples/solid/quickstart-file-based/package.json index 42a109ea12..56286aa8fc 100644 --- a/examples/solid/quickstart-file-based/package.json +++ b/examples/solid/quickstart-file-based/package.json @@ -19,8 +19,9 @@ "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2", + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/quickstart-file-based/src/routeTree.gen.ts b/examples/solid/quickstart-file-based/src/routeTree.gen.ts index 333a815c38..98df1e83d8 100644 --- a/examples/solid/quickstart-file-based/src/routeTree.gen.ts +++ b/examples/solid/quickstart-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/quickstart-rspack-file-based/package.json b/examples/solid/quickstart-rspack-file-based/package.json index 8077f0114f..4d3e6afa50 100644 --- a/examples/solid/quickstart-rspack-file-based/package.json +++ b/examples/solid/quickstart-rspack-file-based/package.json @@ -17,10 +17,11 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "@rsbuild/core": "^2.0.11", + "@rsbuild/core": "^2.1.0", "@rsbuild/plugin-babel": "^1.1.2", "@rsbuild/plugin-solid": "^2.0.0-beta.0", - "@tanstack/router-plugin": "^1.168.19", - "typescript": "^6.0.2" + "@tanstack/router-plugin": "^1.168.24", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2" } } diff --git a/examples/solid/quickstart-rspack-file-based/src/routeTree.gen.ts b/examples/solid/quickstart-rspack-file-based/src/routeTree.gen.ts index 333a815c38..98df1e83d8 100644 --- a/examples/solid/quickstart-rspack-file-based/src/routeTree.gen.ts +++ b/examples/solid/quickstart-rspack-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/quickstart-webpack-file-based/package.json b/examples/solid/quickstart-webpack-file-based/package.json index cacc1b810c..cc7ceb4789 100644 --- a/examples/solid/quickstart-webpack-file-based/package.json +++ b/examples/solid/quickstart-webpack-file-based/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-typescript": "^7.27.1", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "babel-loader": "^10.0.0", "babel-preset-solid": "2.0.0-beta.29", "css-loader": "^7.1.2", @@ -24,7 +24,8 @@ "postcss": "^8.5.6", "postcss-loader": "^8.2.0", "style-loader": "^4.0.0", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "webpack": "^5.97.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.2.4" diff --git a/examples/solid/quickstart-webpack-file-based/src/routeTree.gen.ts b/examples/solid/quickstart-webpack-file-based/src/routeTree.gen.ts index 333a815c38..98df1e83d8 100644 --- a/examples/solid/quickstart-webpack-file-based/src/routeTree.gen.ts +++ b/examples/solid/quickstart-webpack-file-based/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/quickstart/package.json b/examples/solid/quickstart/package.json index 3c1dd46d2b..63632b7acc 100644 --- a/examples/solid/quickstart/package.json +++ b/examples/solid/quickstart/package.json @@ -17,7 +17,8 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/router-monorepo-simple-lazy/package.json b/examples/solid/router-monorepo-simple-lazy/package.json index d801c9179b..6f6377a782 100644 --- a/examples/solid/router-monorepo-simple-lazy/package.json +++ b/examples/solid/router-monorepo-simple-lazy/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@solidjs/web": "2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "redaxios": "^0.5.1", @@ -17,7 +17,8 @@ }, "devDependencies": { "@types/node": "^22.7.4", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/examples/solid/router-monorepo-simple-lazy/packages/app/package.json b/examples/solid/router-monorepo-simple-lazy/packages/app/package.json index 78bfaab83a..cc8ae21100 100644 --- a/examples/solid/router-monorepo-simple-lazy/packages/app/package.json +++ b/examples/solid/router-monorepo-simple-lazy/packages/app/package.json @@ -18,7 +18,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^6.0.1", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "tailwindcss": "^4.2.2", "vite": "^8.0.14", diff --git a/examples/solid/router-monorepo-simple-lazy/packages/post-feature/package.json b/examples/solid/router-monorepo-simple-lazy/packages/post-feature/package.json index 810785366b..18d423ba68 100644 --- a/examples/solid/router-monorepo-simple-lazy/packages/post-feature/package.json +++ b/examples/solid/router-monorepo-simple-lazy/packages/post-feature/package.json @@ -23,7 +23,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json index acfeccdfe7..27a235f483 100644 --- a/examples/solid/router-monorepo-simple-lazy/packages/router/package.json +++ b/examples/solid/router-monorepo-simple-lazy/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.162.0", "@tanstack/solid-router": "^2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "redaxios": "^0.5.1", "zod": "^4.4.3", "solid-js": "2.0.0-beta.29", @@ -18,7 +18,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts b/examples/solid/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts index e8f1a5370b..8760312d24 100644 --- a/examples/solid/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts +++ b/examples/solid/router-monorepo-simple-lazy/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/router-monorepo-simple/package.json b/examples/solid/router-monorepo-simple/package.json index ff00c5c03b..1cd40de1c2 100644 --- a/examples/solid/router-monorepo-simple/package.json +++ b/examples/solid/router-monorepo-simple/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@solidjs/web": "2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "redaxios": "^0.5.1", @@ -17,7 +17,8 @@ }, "devDependencies": { "@types/node": "^22.7.4", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/examples/solid/router-monorepo-simple/packages/app/package.json b/examples/solid/router-monorepo-simple/packages/app/package.json index a4f11e5f65..439bf2fbc5 100644 --- a/examples/solid/router-monorepo-simple/packages/app/package.json +++ b/examples/solid/router-monorepo-simple/packages/app/package.json @@ -16,7 +16,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "vite": "^8.0.14", "tailwindcss": "^4.2.2", diff --git a/examples/solid/router-monorepo-simple/packages/post-feature/package.json b/examples/solid/router-monorepo-simple/packages/post-feature/package.json index 4f9acc251c..77683a1b81 100644 --- a/examples/solid/router-monorepo-simple/packages/post-feature/package.json +++ b/examples/solid/router-monorepo-simple/packages/post-feature/package.json @@ -14,7 +14,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-simple/packages/router/package.json b/examples/solid/router-monorepo-simple/packages/router/package.json index 9b5353fd75..46a3da3a27 100644 --- a/examples/solid/router-monorepo-simple/packages/router/package.json +++ b/examples/solid/router-monorepo-simple/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.162.0", "@tanstack/solid-router": "^2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "redaxios": "^0.5.1", "zod": "^4.4.3", "solid-js": "2.0.0-beta.29", @@ -18,7 +18,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-simple/packages/router/src/routeTree.gen.ts b/examples/solid/router-monorepo-simple/packages/router/src/routeTree.gen.ts index e8f1a5370b..8760312d24 100644 --- a/examples/solid/router-monorepo-simple/packages/router/src/routeTree.gen.ts +++ b/examples/solid/router-monorepo-simple/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/router-monorepo-solid-query/package.json b/examples/solid/router-monorepo-solid-query/package.json index d131143aac..9847559289 100644 --- a/examples/solid/router-monorepo-solid-query/package.json +++ b/examples/solid/router-monorepo-solid-query/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@solidjs/web": "2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-query": "^6.0.0-beta.7", "@tanstack/solid-query-devtools": "^6.0.0-beta.7", "@tanstack/solid-router": "^2.0.0-beta.29", @@ -21,7 +21,8 @@ }, "devDependencies": { "@types/node": "^22.10.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4", "vite-plugin-solid": "^3.0.0-next.21" diff --git a/examples/solid/router-monorepo-solid-query/packages/app/package.json b/examples/solid/router-monorepo-solid-query/packages/app/package.json index 66b0ee1c47..a88ad0b0bb 100644 --- a/examples/solid/router-monorepo-solid-query/packages/app/package.json +++ b/examples/solid/router-monorepo-solid-query/packages/app/package.json @@ -17,7 +17,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "tailwindcss": "^4.2.2", "vite": "^8.0.14", diff --git a/examples/solid/router-monorepo-solid-query/packages/post-feature/package.json b/examples/solid/router-monorepo-solid-query/packages/post-feature/package.json index d301062492..cf358fe85a 100644 --- a/examples/solid/router-monorepo-solid-query/packages/post-feature/package.json +++ b/examples/solid/router-monorepo-solid-query/packages/post-feature/package.json @@ -16,7 +16,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-solid-query/packages/post-query/package.json b/examples/solid/router-monorepo-solid-query/packages/post-query/package.json index 188b57ee21..284b46f351 100644 --- a/examples/solid/router-monorepo-solid-query/packages/post-query/package.json +++ b/examples/solid/router-monorepo-solid-query/packages/post-query/package.json @@ -14,7 +14,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14" } } diff --git a/examples/solid/router-monorepo-solid-query/packages/router/package.json b/examples/solid/router-monorepo-solid-query/packages/router/package.json index ee8ffa6386..0c43684fd6 100644 --- a/examples/solid/router-monorepo-solid-query/packages/router/package.json +++ b/examples/solid/router-monorepo-solid-query/packages/router/package.json @@ -11,7 +11,7 @@ "@tanstack/history": "^1.162.0", "@tanstack/solid-query": "^6.0.0-beta.7", "@tanstack/solid-router": "^2.0.0-beta.29", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@router-solid-mono-solid-query/post-query": "workspace:*", "redaxios": "^0.5.1", "zod": "^4.4.3", @@ -20,7 +20,8 @@ }, "devDependencies": { "vite-plugin-solid": "^3.0.0-next.21", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-dts": "^4.5.4" } diff --git a/examples/solid/router-monorepo-solid-query/packages/router/src/routeTree.gen.ts b/examples/solid/router-monorepo-solid-query/packages/router/src/routeTree.gen.ts index e8f1a5370b..8760312d24 100644 --- a/examples/solid/router-monorepo-solid-query/packages/router/src/routeTree.gen.ts +++ b/examples/solid/router-monorepo-solid-query/packages/router/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 PostIdRouteImport } from './routes/$postId' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostIdRouteImport } from './routes/$postId' -const PostIdRoute = PostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostIdRoute = PostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/$postId': { - id: '/$postId' - path: '/$postId' - fullPath: '/$postId' - preLoaderRoute: typeof PostIdRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/$postId': { + id: '/$postId' + path: '/$postId' + fullPath: '/$postId' + preLoaderRoute: typeof PostIdRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/scroll-restoration/package.json b/examples/solid/scroll-restoration/package.json index 6f62372dc5..96628d01df 100644 --- a/examples/solid/scroll-restoration/package.json +++ b/examples/solid/scroll-restoration/package.json @@ -18,7 +18,8 @@ "tailwindcss": "^4.2.2" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/search-validator-adapters/package.json b/examples/solid/search-validator-adapters/package.json index 79a7c2a655..779a55a460 100644 --- a/examples/solid/search-validator-adapters/package.json +++ b/examples/solid/search-validator-adapters/package.json @@ -13,7 +13,7 @@ "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", "@tanstack/arktype-adapter": "^1.167.0", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-query": "^6.0.0-beta.7", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", @@ -28,7 +28,8 @@ "devDependencies": { "@solidjs/testing-library": "^0.8.10", "@testing-library/jest-dom": "^6.6.3", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/search-validator-adapters/src/routeTree.gen.ts b/examples/solid/search-validator-adapters/src/routeTree.gen.ts index 24089ece84..4cb97b08e9 100644 --- a/examples/solid/search-validator-adapters/src/routeTree.gen.ts +++ b/examples/solid/search-validator-adapters/src/routeTree.gen.ts @@ -10,18 +10,18 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersZodIndexRouteImport } from './routes/users/zod.index' -import { Route as UsersValibotIndexRouteImport } from './routes/users/valibot.index' import { Route as UsersArktypeIndexRouteImport } from './routes/users/arktype.index' +import { Route as UsersValibotIndexRouteImport } from './routes/users/valibot.index' +import { Route as UsersZodIndexRouteImport } from './routes/users/zod.index' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const UsersZodIndexRoute = UsersZodIndexRouteImport.update({ - id: '/users/zod/', - path: '/users/zod/', +const UsersArktypeIndexRoute = UsersArktypeIndexRouteImport.update({ + id: '/users/arktype/', + path: '/users/arktype/', getParentRoute: () => rootRouteImport, } as any) const UsersValibotIndexRoute = UsersValibotIndexRouteImport.update({ @@ -29,9 +29,9 @@ const UsersValibotIndexRoute = UsersValibotIndexRouteImport.update({ path: '/users/valibot/', getParentRoute: () => rootRouteImport, } as any) -const UsersArktypeIndexRoute = UsersArktypeIndexRouteImport.update({ - id: '/users/arktype/', - path: '/users/arktype/', +const UsersZodIndexRoute = UsersZodIndexRouteImport.update({ + id: '/users/zod/', + path: '/users/zod/', getParentRoute: () => rootRouteImport, } as any) @@ -78,11 +78,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport 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 } '/users/valibot/': { @@ -92,11 +92,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof UsersValibotIndexRouteImport 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 } } diff --git a/examples/solid/start-basic-auth/package.json b/examples/solid/start-basic-auth/package.json index e7901ea5a3..5d6db74dec 100644 --- a/examples/solid/start-basic-auth/package.json +++ b/examples/solid/start-basic-auth/package.json @@ -28,7 +28,8 @@ "dotenv": "^17.2.3", "prisma": "^7.0.0", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-basic-auth/src/routeTree.gen.ts b/examples/solid/start-basic-auth/src/routeTree.gen.ts index bad0f1b074..07924d102a 100644 --- a/examples/solid/start-basic-auth/src/routeTree.gen.ts +++ b/examples/solid/start-basic-auth/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteRouteImport } from './routes/_authed/posts.route' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRouteRoute = AuthedPostsRouteRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/examples/solid/start-basic-authjs/package.json b/examples/solid/start-basic-authjs/package.json index e631440e13..ffdda5c1a4 100644 --- a/examples/solid/start-basic-authjs/package.json +++ b/examples/solid/start-basic-authjs/package.json @@ -23,7 +23,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-basic-authjs/src/routeTree.gen.ts b/examples/solid/start-basic-authjs/src/routeTree.gen.ts index e772ba9c3e..7cc6d28cf7 100644 --- a/examples/solid/start-basic-authjs/src/routeTree.gen.ts +++ b/examples/solid/start-basic-authjs/src/routeTree.gen.ts @@ -9,14 +9,14 @@ // 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 ProtectedRouteImport } from './routes/protected' -import { Route as LoginRouteImport } from './routes/login' import { Route as IndexRouteImport } from './routes/index' +import { Route as LoginRouteImport } from './routes/login' +import { Route as ProtectedRouteImport } from './routes/protected' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' -const ProtectedRoute = ProtectedRouteImport.update({ - id: '/protected', - path: '/protected', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -24,9 +24,9 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ProtectedRoute = ProtectedRouteImport.update({ + id: '/protected', + path: '/protected', getParentRoute: () => rootRouteImport, } as any) const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ @@ -71,11 +71,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/protected': { - id: '/protected' - path: '/protected' - fullPath: '/protected' - preLoaderRoute: typeof ProtectedRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -85,11 +85,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/protected': { + id: '/protected' + path: '/protected' + fullPath: '/protected' + preLoaderRoute: typeof ProtectedRouteImport parentRoute: typeof rootRouteImport } '/api/auth/$': { diff --git a/examples/solid/start-basic-cloudflare/package.json b/examples/solid/start-basic-cloudflare/package.json index 868fa166db..e0cf2c2b5c 100644 --- a/examples/solid/start-basic-cloudflare/package.json +++ b/examples/solid/start-basic-cloudflare/package.json @@ -23,7 +23,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4", diff --git a/examples/solid/start-basic-cloudflare/src/routeTree.gen.ts b/examples/solid/start-basic-cloudflare/src/routeTree.gen.ts index c3f6976d03..7cba7e8958 100644 --- a/examples/solid/start-basic-cloudflare/src/routeTree.gen.ts +++ b/examples/solid/start-basic-cloudflare/src/routeTree.gen.ts @@ -9,37 +9,36 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -47,72 +46,73 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -234,25 +234,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport parentRoute: typeof rootRouteImport } '/deferred': { @@ -262,33 +262,40 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/customScript.js': { - id: '/customScript.js' - path: '/customScript.js' - fullPath: '/customScript.js' - preLoaderRoute: typeof CustomScriptDotjsRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -297,13 +304,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -311,33 +311,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -346,12 +339,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-basic-netlify/package.json b/examples/solid/start-basic-netlify/package.json index af3019ed97..629f752b30 100644 --- a/examples/solid/start-basic-netlify/package.json +++ b/examples/solid/start-basic-netlify/package.json @@ -20,7 +20,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-basic-netlify/src/routeTree.gen.ts b/examples/solid/start-basic-netlify/src/routeTree.gen.ts index c3f6976d03..7cba7e8958 100644 --- a/examples/solid/start-basic-netlify/src/routeTree.gen.ts +++ b/examples/solid/start-basic-netlify/src/routeTree.gen.ts @@ -9,37 +9,36 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -47,72 +46,73 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -234,25 +234,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport parentRoute: typeof rootRouteImport } '/deferred': { @@ -262,33 +262,40 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/customScript.js': { - id: '/customScript.js' - path: '/customScript.js' - fullPath: '/customScript.js' - preLoaderRoute: typeof CustomScriptDotjsRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -297,13 +304,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -311,33 +311,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -346,12 +339,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-basic-nitro/package.json b/examples/solid/start-basic-nitro/package.json index a2c223c030..9a32a79bf0 100644 --- a/examples/solid/start-basic-nitro/package.json +++ b/examples/solid/start-basic-nitro/package.json @@ -20,7 +20,8 @@ "@types/node": "^22.5.4", "nitro": "^3.0.260311-beta", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-basic-nitro/src/routeTree.gen.ts b/examples/solid/start-basic-nitro/src/routeTree.gen.ts index c3f6976d03..7cba7e8958 100644 --- a/examples/solid/start-basic-nitro/src/routeTree.gen.ts +++ b/examples/solid/start-basic-nitro/src/routeTree.gen.ts @@ -9,37 +9,36 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as CustomScriptDotjsRouteImport } from './routes/customScript[.]js' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ + id: '/customScript.js', + path: '/customScript.js', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -47,72 +46,73 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const CustomScriptDotjsRoute = CustomScriptDotjsRouteImport.update({ - id: '/customScript.js', - path: '/customScript.js', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, } as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -234,25 +234,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/customScript.js': { + id: '/customScript.js' + path: '/customScript.js' + fullPath: '/customScript.js' + preLoaderRoute: typeof CustomScriptDotjsRouteImport parentRoute: typeof rootRouteImport } '/deferred': { @@ -262,33 +262,40 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } - '/customScript.js': { - id: '/customScript.js' - path: '/customScript.js' - fullPath: '/customScript.js' - preLoaderRoute: typeof CustomScriptDotjsRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -297,13 +304,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -311,33 +311,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -346,12 +339,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-basic-solid-query/package.json b/examples/solid/start-basic-solid-query/package.json index e533f60dc7..c6c535c981 100644 --- a/examples/solid/start-basic-solid-query/package.json +++ b/examples/solid/start-basic-solid-query/package.json @@ -25,7 +25,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-basic-solid-query/src/routeTree.gen.ts b/examples/solid/start-basic-solid-query/src/routeTree.gen.ts index 226b599b8c..4a1747ae36 100644 --- a/examples/solid/start-basic-solid-query/src/routeTree.gen.ts +++ b/examples/solid/start-basic-solid-query/src/routeTree.gen.ts @@ -9,26 +9,30 @@ // 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 RedirectRouteImport } from './routes/redirect' -import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as IndexRouteImport } from './routes/index' import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' -import { Route as UsersRouteRouteImport } from './routes/users.route' +import { Route as DeferredRouteImport } from './routes/deferred' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteRouteImport } from './routes/users.route' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) const DeferredRoute = DeferredRouteImport.update({ @@ -36,8 +40,14 @@ const DeferredRoute = DeferredRouteImport.update({ path: '/deferred', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) const UsersRouteRoute = UsersRouteRouteImport.update({ @@ -45,68 +55,58 @@ const UsersRouteRoute = UsersRouteRouteImport.update({ path: '/users', getParentRoute: () => rootRouteImport, } as any) -const PostsRouteRoute = PostsRouteRouteImport.update({ - id: '/posts', - path: '/posts', +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => PostsRouteRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRouteRoute, } as any) const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => UsersRouteRoute, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRouteRoute, -} as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRouteRoute, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - 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', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - 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', path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -221,18 +221,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport - parentRoute: typeof rootRouteImport - } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_pathlessLayout': { @@ -242,11 +235,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteRouteImport + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -256,19 +249,33 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRouteRoute + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -277,13 +284,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRouteRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRouteRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -291,33 +291,26 @@ declare module '@tanstack/solid-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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRouteRoute } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRouteRoute } - '/api/users/$id': { - id: '/api/users/$id' - path: '/$id' - fullPath: '/api/users/$id' - preLoaderRoute: typeof ApiUsersIdRouteImport - parentRoute: typeof ApiUsersRoute + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -326,12 +319,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-basic-static/package.json b/examples/solid/start-basic-static/package.json index c56ee0f4be..31f3a1d6dc 100644 --- a/examples/solid/start-basic-static/package.json +++ b/examples/solid/start-basic-static/package.json @@ -14,7 +14,7 @@ "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@tanstack/solid-start": "^2.0.0-beta.30", - "@tanstack/start-static-server-functions": "^1.167.18", + "@tanstack/start-static-server-functions": "^1.167.20", "redaxios": "^0.5.1", "solid-js": "2.0.0-beta.29", "tailwind-merge": "^3.6.0" @@ -23,7 +23,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.3" diff --git a/examples/solid/start-basic-static/src/routeTree.gen.ts b/examples/solid/start-basic-static/src/routeTree.gen.ts index 7cad5523aa..a94e0fcf2e 100644 --- a/examples/solid/start-basic-static/src/routeTree.gen.ts +++ b/examples/solid/start-basic-static/src/routeTree.gen.ts @@ -9,29 +9,33 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' 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 PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' -import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -39,62 +43,58 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => PostsRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any) const UsersIndexRoute = UsersIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => UsersRoute, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, -} as any) const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', getParentRoute: () => UsersRoute, } as any) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any) -const PathlessLayoutNestedLayoutRoute = - PathlessLayoutNestedLayoutRouteImport.update({ - id: '/_nested-layout', - getParentRoute: () => PathlessLayoutRoute, +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, -} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', path: '/route-b', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) -const PathlessLayoutNestedLayoutRouteARoute = - PathlessLayoutNestedLayoutRouteARouteImport.update({ - id: '/route-a', - path: '/route-a', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -196,18 +196,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -217,33 +224,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } '/posts/': { id: '/posts/' @@ -252,13 +252,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -266,19 +259,26 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_pathlessLayout/_nested-layout': { - id: '/_pathlessLayout/_nested-layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -287,12 +287,12 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-basic/package.json b/examples/solid/start-basic/package.json index 07568247ec..d3d3c3ace9 100644 --- a/examples/solid/start-basic/package.json +++ b/examples/solid/start-basic/package.json @@ -23,7 +23,8 @@ "@types/node": "^22.5.4", "nitro": "^3.0.260311-beta", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "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 722133b41e..8d6c8ffc2b 100644 --- a/examples/solid/start-basic/src/routeTree.gen.ts +++ b/examples/solid/start-basic/src/routeTree.gen.ts @@ -9,31 +9,35 @@ // 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 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RedirectRouteImport } from './routes/redirect' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as ApiUsersRouteImport } from './routes/api/users' 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 UsersIndexRouteImport } from './routes/users.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' +import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as ApiUsersUserIdRouteImport } from './routes/api/users.$userId' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' -const UsersRoute = UsersRouteImport.update({ - id: '/users', - path: '/users', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const RedirectRoute = RedirectRouteImport.update({ - id: '/redirect', - path: '/redirect', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', getParentRoute: () => rootRouteImport, } as any) const PostsRoute = PostsRouteImport.update({ @@ -41,72 +45,68 @@ const PostsRoute = PostsRouteImport.update({ path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const DeferredRoute = DeferredRouteImport.update({ - id: '/deferred', - path: '/deferred', +const RedirectRoute = RedirectRouteImport.update({ + id: '/redirect', + path: '/redirect', getParentRoute: () => rootRouteImport, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PathlessLayoutNestedLayoutRoute = + PathlessLayoutNestedLayoutRouteImport.update({ + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', getParentRoute: () => rootRouteImport, } as any) -const UsersIndexRoute = UsersIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => UsersRoute, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => PostsRoute, } as any) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/$userId', - path: '/$userId', - getParentRoute: () => UsersRoute, -} as any) const PostsPostIdRoute = PostsPostIdRouteImport.update({ id: '/$postId', 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', - getParentRoute: () => PathlessLayoutRoute, - } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/posts_/$postId/deep', - path: '/posts/$postId/deep', - getParentRoute: () => rootRouteImport, +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, } as any) -const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ +const UsersUserIdRoute = UsersUserIdRouteImport.update({ id: '/$userId', path: '/$userId', - getParentRoute: () => ApiUsersRoute, + getParentRoute: () => UsersRoute, } as any) -const PathlessLayoutNestedLayoutRouteBRoute = - PathlessLayoutNestedLayoutRouteBRouteImport.update({ - id: '/route-b', - path: '/route-b', - getParentRoute: () => PathlessLayoutNestedLayoutRoute, - } as any) const PathlessLayoutNestedLayoutRouteARoute = PathlessLayoutNestedLayoutRouteARouteImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutNestedLayoutRoute, } as any) +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) +const ApiUsersUserIdRoute = ApiUsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => ApiUsersRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -221,18 +221,25 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/users': { - id: '/users' - path: '/users' - fullPath: '/users' - preLoaderRoute: typeof UsersRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/redirect': { - id: '/redirect' - path: '/redirect' - fullPath: '/redirect' - preLoaderRoute: typeof RedirectRouteImport + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '/' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport parentRoute: typeof rootRouteImport } '/posts': { @@ -242,33 +249,33 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/deferred': { - id: '/deferred' - path: '/deferred' - fullPath: '/deferred' - preLoaderRoute: typeof DeferredRouteImport + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectRouteImport parentRoute: typeof rootRouteImport } - '/_pathlessLayout': { - id: '/_pathlessLayout' - path: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutRouteImport + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport + parentRoute: typeof PathlessLayoutRoute } - '/users/': { - id: '/users/' - path: '/' - fullPath: '/users/' - preLoaderRoute: typeof UsersIndexRouteImport - parentRoute: typeof UsersRoute + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport } '/posts/': { id: '/posts/' @@ -277,13 +284,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRoute } - '/users/$userId': { - id: '/users/$userId' - path: '/$userId' - fullPath: '/users/$userId' - preLoaderRoute: typeof UsersUserIdRouteImport - parentRoute: typeof UsersRoute - } '/posts/$postId': { id: '/posts/$postId' path: '/$postId' @@ -291,33 +291,26 @@ 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: '' - fullPath: '/' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport - parentRoute: typeof PathlessLayoutRoute - } - '/posts_/$postId/deep': { - id: '/posts_/$postId/deep' - path: '/posts/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof rootRouteImport + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute } - '/api/users/$userId': { - id: '/api/users/$userId' + '/users/$userId': { + id: '/users/$userId' path: '/$userId' - fullPath: '/api/users/$userId' - preLoaderRoute: typeof ApiUsersUserIdRouteImport - parentRoute: typeof ApiUsersRoute + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutNestedLayoutRoute } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' @@ -326,12 +319,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport parentRoute: typeof PathlessLayoutNestedLayoutRoute } - '/_pathlessLayout/_nested-layout/route-a': { - id: '/_pathlessLayout/_nested-layout/route-a' - path: '/route-a' - fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutNestedLayoutRoute + '/api/users/$userId': { + id: '/api/users/$userId' + path: '/$userId' + fullPath: '/api/users/$userId' + preLoaderRoute: typeof ApiUsersUserIdRouteImport + parentRoute: typeof ApiUsersRoute + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/solid/start-bun/package.json b/examples/solid/start-bun/package.json index 1573ed8b86..73bc4e8e7b 100644 --- a/examples/solid/start-bun/package.json +++ b/examples/solid/start-bun/package.json @@ -15,7 +15,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-devtools": "^0.7.0", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", @@ -32,7 +32,8 @@ "@types/node": "22.10.2", "jsdom": "^27.0.0", "prettier": "^3.6.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vitest": "^4.1.4", diff --git a/examples/solid/start-bun/src/routeTree.gen.ts b/examples/solid/start-bun/src/routeTree.gen.ts index 19009b2f5b..353db2cd02 100644 --- a/examples/solid/start-bun/src/routeTree.gen.ts +++ b/examples/solid/start-bun/src/routeTree.gen.ts @@ -11,8 +11,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiDemoNamesRouteImport } from './routes/api.demo-names' -import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.server-funcs' import { Route as DemoStartApiRequestRouteImport } from './routes/demo.start.api-request' +import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.server-funcs' const IndexRoute = IndexRouteImport.update({ id: '/', @@ -24,16 +24,16 @@ const ApiDemoNamesRoute = ApiDemoNamesRouteImport.update({ path: '/api/demo-names', getParentRoute: () => rootRouteImport, } as any) -const DemoStartServerFuncsRoute = DemoStartServerFuncsRouteImport.update({ - id: '/demo/start/server-funcs', - path: '/demo/start/server-funcs', - getParentRoute: () => rootRouteImport, -} as any) const DemoStartApiRequestRoute = DemoStartApiRequestRouteImport.update({ id: '/demo/start/api-request', path: '/demo/start/api-request', getParentRoute: () => rootRouteImport, } as any) +const DemoStartServerFuncsRoute = DemoStartServerFuncsRouteImport.update({ + id: '/demo/start/server-funcs', + path: '/demo/start/server-funcs', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -98,13 +98,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ApiDemoNamesRouteImport parentRoute: typeof rootRouteImport } - '/demo/start/server-funcs': { - id: '/demo/start/server-funcs' - path: '/demo/start/server-funcs' - fullPath: '/demo/start/server-funcs' - preLoaderRoute: typeof DemoStartServerFuncsRouteImport - parentRoute: typeof rootRouteImport - } '/demo/start/api-request': { id: '/demo/start/api-request' path: '/demo/start/api-request' @@ -112,6 +105,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof DemoStartApiRequestRouteImport parentRoute: typeof rootRouteImport } + '/demo/start/server-funcs': { + id: '/demo/start/server-funcs' + path: '/demo/start/server-funcs' + fullPath: '/demo/start/server-funcs' + preLoaderRoute: typeof DemoStartServerFuncsRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/start-convex-better-auth/package.json b/examples/solid/start-convex-better-auth/package.json index a7b4ecec69..48eb2206c5 100644 --- a/examples/solid/start-convex-better-auth/package.json +++ b/examples/solid/start-convex-better-auth/package.json @@ -17,7 +17,7 @@ "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@tanstack/solid-start": "^2.0.0-beta.30", - "better-auth": "^1.3.27", + "better-auth": "^1.6.19", "clsx": "^2.1.1", "convex": "^1.28.2", "convex-solidjs": "^0.0.3", @@ -30,7 +30,8 @@ "devDependencies": { "@types/node": "^22.10.2", "combinate": "^1.1.11", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-convex-better-auth/src/routeTree.gen.ts b/examples/solid/start-convex-better-auth/src/routeTree.gen.ts index 27dc0ca88c..d2307d1277 100644 --- a/examples/solid/start-convex-better-auth/src/routeTree.gen.ts +++ b/examples/solid/start-convex-better-auth/src/routeTree.gen.ts @@ -9,24 +9,24 @@ // 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 AboutRouteImport } from './routes/about' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as AboutRouteImport } from './routes/about' import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AuthedRoute = AuthedRouteImport.update({ id: '/_authed', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', getParentRoute: () => rootRouteImport, } as any) const AuthedDashboardRoute = AuthedDashboardRouteImport.update({ @@ -83,11 +83,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_authed': { @@ -97,11 +97,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport parentRoute: typeof rootRouteImport } '/_authed/dashboard': { diff --git a/examples/solid/start-counter/package.json b/examples/solid/start-counter/package.json index daeea04b17..9adf93e962 100644 --- a/examples/solid/start-counter/package.json +++ b/examples/solid/start-counter/package.json @@ -22,7 +22,8 @@ "devDependencies": { "@types/node": "^22.10.2", "combinate": "^1.1.11", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/start-counter/src/routeTree.gen.ts b/examples/solid/start-counter/src/routeTree.gen.ts index 3cc45c071e..7495244535 100644 --- a/examples/solid/start-counter/src/routeTree.gen.ts +++ b/examples/solid/start-counter/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/start-i18n-paraglide/package.json b/examples/solid/start-i18n-paraglide/package.json index 13d8940d99..0216a57551 100644 --- a/examples/solid/start-i18n-paraglide/package.json +++ b/examples/solid/start-i18n-paraglide/package.json @@ -21,7 +21,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.18.6", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/start-i18n-paraglide/src/routeTree.gen.ts b/examples/solid/start-i18n-paraglide/src/routeTree.gen.ts index 3cc45c071e..7495244535 100644 --- a/examples/solid/start-i18n-paraglide/src/routeTree.gen.ts +++ b/examples/solid/start-i18n-paraglide/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/solid/start-large/package.json b/examples/solid/start-large/package.json index e4b08153ad..ca4fb37709 100644 --- a/examples/solid/start-large/package.json +++ b/examples/solid/start-large/package.json @@ -26,7 +26,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" }, diff --git a/examples/solid/start-large/src/routeTree.gen.ts b/examples/solid/start-large/src/routeTree.gen.ts index 3fb99e1aca..1febe90621 100644 --- a/examples/solid/start-large/src/routeTree.gen.ts +++ b/examples/solid/start-large/src/routeTree.gen.ts @@ -9,23 +9,18 @@ // 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 RelativeRouteImport } from './routes/relative' -import { Route as LinkPropsRouteImport } from './routes/linkProps' +import { Route as IndexRouteImport } from './routes/index' import { Route as AbsoluteRouteImport } from './routes/absolute' -import { Route as SearchRouteRouteImport } from './routes/search/route' +import { Route as LinkPropsRouteImport } from './routes/linkProps' import { Route as ParamsRouteRouteImport } from './routes/params/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' +import { Route as RelativeRouteImport } from './routes/relative' +import { Route as SearchRouteRouteImport } from './routes/search/route' import { Route as ParamsParamsPlaceholderRouteImport } from './routes/params/$paramsPlaceholder' +import { Route as SearchSearchPlaceholderRouteImport } from './routes/search/searchPlaceholder' -const RelativeRoute = RelativeRouteImport.update({ - id: '/relative', - path: '/relative', - getParentRoute: () => rootRouteImport, -} as any) -const LinkPropsRoute = LinkPropsRouteImport.update({ - id: '/linkProps', - path: '/linkProps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const AbsoluteRoute = AbsoluteRouteImport.update({ @@ -33,9 +28,9 @@ const AbsoluteRoute = AbsoluteRouteImport.update({ path: '/absolute', getParentRoute: () => rootRouteImport, } as any) -const SearchRouteRoute = SearchRouteRouteImport.update({ - id: '/search', - path: '/search', +const LinkPropsRoute = LinkPropsRouteImport.update({ + id: '/linkProps', + path: '/linkProps', getParentRoute: () => rootRouteImport, } as any) const ParamsRouteRoute = ParamsRouteRouteImport.update({ @@ -43,21 +38,26 @@ const ParamsRouteRoute = ParamsRouteRouteImport.update({ path: '/params', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RelativeRoute = RelativeRouteImport.update({ + id: '/relative', + path: '/relative', getParentRoute: () => rootRouteImport, } as any) -const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ - id: '/searchPlaceholder', - path: '/searchPlaceholder', - getParentRoute: () => SearchRouteRoute, +const SearchRouteRoute = SearchRouteRouteImport.update({ + id: '/search', + path: '/search', + getParentRoute: () => rootRouteImport, } as any) const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderRouteImport.update({ id: '/$paramsPlaceholder', path: '/$paramsPlaceholder', getParentRoute: () => ParamsRouteRoute, } as any) +const SearchSearchPlaceholderRoute = SearchSearchPlaceholderRouteImport.update({ + id: '/searchPlaceholder', + path: '/searchPlaceholder', + getParentRoute: () => SearchRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -134,18 +134,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/relative': { - id: '/relative' - path: '/relative' - fullPath: '/relative' - preLoaderRoute: typeof RelativeRouteImport - parentRoute: typeof rootRouteImport - } - '/linkProps': { - id: '/linkProps' - path: '/linkProps' - fullPath: '/linkProps' - preLoaderRoute: typeof LinkPropsRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/absolute': { @@ -155,11 +148,11 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof AbsoluteRouteImport parentRoute: typeof rootRouteImport } - '/search': { - id: '/search' - path: '/search' - fullPath: '/search' - preLoaderRoute: typeof SearchRouteRouteImport + '/linkProps': { + id: '/linkProps' + path: '/linkProps' + fullPath: '/linkProps' + preLoaderRoute: typeof LinkPropsRouteImport parentRoute: typeof rootRouteImport } '/params': { @@ -169,19 +162,19 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/relative': { + id: '/relative' + path: '/relative' + fullPath: '/relative' + preLoaderRoute: typeof RelativeRouteImport parentRoute: typeof rootRouteImport } - '/search/searchPlaceholder': { - id: '/search/searchPlaceholder' - path: '/searchPlaceholder' - fullPath: '/search/searchPlaceholder' - preLoaderRoute: typeof SearchSearchPlaceholderRouteImport - parentRoute: typeof SearchRouteRoute + '/search': { + id: '/search' + path: '/search' + fullPath: '/search' + preLoaderRoute: typeof SearchRouteRouteImport + parentRoute: typeof rootRouteImport } '/params/$paramsPlaceholder': { id: '/params/$paramsPlaceholder' @@ -190,6 +183,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ParamsParamsPlaceholderRouteImport parentRoute: typeof ParamsRouteRoute } + '/search/searchPlaceholder': { + id: '/search/searchPlaceholder' + path: '/searchPlaceholder' + fullPath: '/search/searchPlaceholder' + preLoaderRoute: typeof SearchSearchPlaceholderRouteImport + parentRoute: typeof SearchRouteRoute + } } } diff --git a/examples/solid/start-streaming-data-from-server-functions/package.json b/examples/solid/start-streaming-data-from-server-functions/package.json index 25c8ec40d3..4478ba9c18 100644 --- a/examples/solid/start-streaming-data-from-server-functions/package.json +++ b/examples/solid/start-streaming-data-from-server-functions/package.json @@ -19,7 +19,8 @@ }, "devDependencies": { "@types/node": "^22.5.4", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/start-supabase-basic/package.json b/examples/solid/start-supabase-basic/package.json index c81e5c6206..bf190803d2 100644 --- a/examples/solid/start-supabase-basic/package.json +++ b/examples/solid/start-supabase-basic/package.json @@ -25,7 +25,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/start-supabase-basic/src/routeTree.gen.ts b/examples/solid/start-supabase-basic/src/routeTree.gen.ts index 7019a6c21a..bbebb19df3 100644 --- a/examples/solid/start-supabase-basic/src/routeTree.gen.ts +++ b/examples/solid/start-supabase-basic/src/routeTree.gen.ts @@ -9,23 +9,22 @@ // 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 SignupRouteImport } from './routes/signup' -import { Route as LogoutRouteImport } from './routes/logout' -import { Route as LoginRouteImport } from './routes/login' -import { Route as AuthedRouteImport } from './routes/_authed' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthedRouteImport } from './routes/_authed' +import { Route as LoginRouteImport } from './routes/login' +import { Route as LogoutRouteImport } from './routes/logout' +import { Route as SignupRouteImport } from './routes/signup' import { Route as AuthedPostsRouteImport } from './routes/_authed/posts' import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index' import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId' -const SignupRoute = SignupRouteImport.update({ - id: '/signup', - path: '/signup', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const LogoutRoute = LogoutRouteImport.update({ - id: '/logout', - path: '/logout', +const AuthedRoute = AuthedRouteImport.update({ + id: '/_authed', getParentRoute: () => rootRouteImport, } as any) const LoginRoute = LoginRouteImport.update({ @@ -33,13 +32,14 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) -const AuthedRoute = AuthedRouteImport.update({ - id: '/_authed', +const LogoutRoute = LogoutRouteImport.update({ + id: '/logout', + path: '/logout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const SignupRoute = SignupRouteImport.update({ + id: '/signup', + path: '/signup', getParentRoute: () => rootRouteImport, } as any) const AuthedPostsRoute = AuthedPostsRouteImport.update({ @@ -120,18 +120,18 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/signup': { - id: '/signup' - path: '/signup' - fullPath: '/signup' - preLoaderRoute: typeof SignupRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/logout': { - id: '/logout' - path: '/logout' - fullPath: '/logout' - preLoaderRoute: typeof LogoutRouteImport + '/_authed': { + id: '/_authed' + path: '' + fullPath: '/' + preLoaderRoute: typeof AuthedRouteImport parentRoute: typeof rootRouteImport } '/login': { @@ -141,18 +141,18 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '/' - preLoaderRoute: typeof AuthedRouteImport + '/logout': { + id: '/logout' + path: '/logout' + fullPath: '/logout' + preLoaderRoute: typeof LogoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/signup': { + id: '/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof SignupRouteImport parentRoute: typeof rootRouteImport } '/_authed/posts': { diff --git a/examples/solid/start-tailwind-v4/package.json b/examples/solid/start-tailwind-v4/package.json index 01121682da..0250e91d97 100644 --- a/examples/solid/start-tailwind-v4/package.json +++ b/examples/solid/start-tailwind-v4/package.json @@ -22,7 +22,8 @@ "@tailwindcss/vite": "^4.2.2", "@types/node": "^22.5.4", "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21", "vite-tsconfig-paths": "^5.1.4" diff --git a/examples/solid/view-transitions/package.json b/examples/solid/view-transitions/package.json index 31f316f125..08f60be2c8 100644 --- a/examples/solid/view-transitions/package.json +++ b/examples/solid/view-transitions/package.json @@ -11,7 +11,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "redaxios": "^0.5.1", @@ -20,7 +20,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/view-transitions/src/routeTree.gen.ts b/examples/solid/view-transitions/src/routeTree.gen.ts index 114e34ed9d..01843027dc 100644 --- a/examples/solid/view-transitions/src/routeTree.gen.ts +++ b/examples/solid/view-transitions/src/routeTree.gen.ts @@ -9,16 +9,16 @@ // 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 HowItWorksRouteImport } from './routes/how-it-works' +import { Route as IndexRouteImport } from './routes/index' import { Route as ExploreRouteImport } from './routes/explore' +import { Route as HowItWorksRouteImport } from './routes/how-it-works' import { Route as PostsRouteRouteImport } from './routes/posts.route' -import { Route as IndexRouteImport } from './routes/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -const HowItWorksRoute = HowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const ExploreRoute = ExploreRouteImport.update({ @@ -26,16 +26,16 @@ const ExploreRoute = ExploreRouteImport.update({ path: '/explore', getParentRoute: () => rootRouteImport, } as any) +const HowItWorksRoute = HowItWorksRouteImport.update({ + id: '/how-it-works', + path: '/how-it-works', + getParentRoute: () => rootRouteImport, +} as any) const PostsRouteRoute = PostsRouteRouteImport.update({ id: '/posts', path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -101,11 +101,11 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/how-it-works': { - id: '/how-it-works' - path: '/how-it-works' - fullPath: '/how-it-works' - preLoaderRoute: typeof HowItWorksRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/explore': { @@ -115,6 +115,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof ExploreRouteImport parentRoute: typeof rootRouteImport } + '/how-it-works': { + id: '/how-it-works' + path: '/how-it-works' + fullPath: '/how-it-works' + preLoaderRoute: typeof HowItWorksRouteImport + parentRoute: typeof rootRouteImport + } '/posts': { id: '/posts' path: '/posts' @@ -122,13 +129,6 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/posts/': { id: '/posts/' path: '/' diff --git a/examples/solid/with-framer-motion/package.json b/examples/solid/with-framer-motion/package.json index 501bf2ded9..2e3705c571 100644 --- a/examples/solid/with-framer-motion/package.json +++ b/examples/solid/with-framer-motion/package.json @@ -20,7 +20,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", "vite-plugin-solid": "^3.0.0-next.21" } diff --git a/examples/solid/with-trpc/package.json b/examples/solid/with-trpc/package.json index 858892f717..030e917050 100644 --- a/examples/solid/with-trpc/package.json +++ b/examples/solid/with-trpc/package.json @@ -12,7 +12,7 @@ "dependencies": { "@solidjs/web": "2.0.0-beta.29", "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", + "@tanstack/router-plugin": "^1.168.24", "@tanstack/solid-router": "^2.0.0-beta.29", "@tanstack/solid-router-devtools": "^2.0.0-beta.24", "@trpc/client": "^11.4.3", diff --git a/examples/solid/with-trpc/src/routeTree.gen.ts b/examples/solid/with-trpc/src/routeTree.gen.ts index fd93507694..aa08570aca 100644 --- a/examples/solid/with-trpc/src/routeTree.gen.ts +++ b/examples/solid/with-trpc/src/routeTree.gen.ts @@ -9,23 +9,23 @@ // 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 DashboardRouteImport } from './routes/dashboard' import { Route as IndexRouteImport } from './routes/index' +import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' import { Route as DashboardPostsRouteImport } from './routes/dashboard.posts' import { Route as DashboardPostsIndexRouteImport } from './routes/dashboard.posts.index' import { Route as DashboardPostsPostIdRouteImport } from './routes/dashboard.posts.$postId' -const DashboardRoute = DashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardRoute = DashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardIndexRoute = DashboardIndexRouteImport.update({ id: '/', path: '/', @@ -98,13 +98,6 @@ export interface RootRouteChildren { declare module '@tanstack/solid-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -112,6 +105,13 @@ declare module '@tanstack/solid-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/examples/vue/basic-file-based-jsx/package.json b/examples/vue/basic-file-based-jsx/package.json index 7573628b17..5e94ab51ea 100644 --- a/examples/vue/basic-file-based-jsx/package.json +++ b/examples/vue/basic-file-based-jsx/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", - "@tanstack/vue-router": "^1.170.16", - "@tanstack/vue-router-devtools": "^1.167.0", + "@tanstack/router-plugin": "^1.168.24", + "@tanstack/vue-router": "^1.170.18", + "@tanstack/vue-router-devtools": "^1.167.1", "redaxios": "^0.5.1", "tailwindcss": "^4.2.2", "vue": "^3.5.16", @@ -28,6 +28,6 @@ "typescript": "~5.8.3", "vite": "^8.0.14", "vue-eslint-parser": "^9.4.3", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/examples/vue/basic-file-based-jsx/src/routeTree.gen.ts b/examples/vue/basic-file-based-jsx/src/routeTree.gen.ts index f5cd519d7f..3922025c82 100644 --- a/examples/vue/basic-file-based-jsx/src/routeTree.gen.ts +++ b/examples/vue/basic-file-based-jsx/src/routeTree.gen.ts @@ -11,52 +11,45 @@ import { lazyRouteComponent } from '@tanstack/vue-router' import { Route as rootRouteImport } from './routes/__root' -import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' -import { Route as SfcComponentRouteImport } from './routes/sfcComponent' -import { Route as RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as SfcComponentRouteImport } from './routes/sfcComponent' +import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -const Char45824Char54620Char48124Char44397Route = - Char45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any) -const SfcComponentRoute = SfcComponentRouteImport.update({ - id: '/sfcComponent', - path: '/sfcComponent', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/sfcComponent.component.vue'), - 'default', - ), -}) -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +} as any) +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', + getParentRoute: () => rootRouteImport, +} as any) +const EditingBRoute = EditingBRouteImport.update({ + id: '/editing-b', + path: '/editing-b', getParentRoute: () => rootRouteImport, } as any) const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ @@ -64,25 +57,56 @@ const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any) -const EditingBRoute = EditingBRouteImport.update({ - id: '/editing-b', - path: '/editing-b', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const SfcComponentRoute = SfcComponentRouteImport.update({ + id: '/sfcComponent', + path: '/sfcComponent', + getParentRoute: () => rootRouteImport, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/sfcComponent.component.vue'), + 'default', + ), +}) +const Char45824Char54620Char48124Char44397Route = + Char45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', + getParentRoute: () => rootRouteImport, + } as any) +const anotherGroupOnlyrouteinsideRoute = + anotherGroupOnlyrouteinsideRouteImport.update({ + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', + getParentRoute: () => rootRouteImport, + } as any) +const groupLayoutRoute = groupLayoutRouteImport.update({ + id: '/(group)/_layout', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const groupInsideRoute = groupInsideRouteImport.update({ + id: '/(group)/inside', + path: '/inside', + getParentRoute: () => rootRouteImport, +} as any) +const groupLazyinsideRoute = groupLazyinsideRouteImport.update({ + id: '/(group)/lazyinside', + path: '/lazyinside', getParentRoute: () => rootRouteImport, } as any) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) const PostsIndexRoute = PostsIndexRouteImport.update({ id: '/', path: '/', @@ -93,55 +117,31 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, } as any) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, -} as any) -const groupLazyinsideRoute = groupLazyinsideRouteImport.update({ - id: '/(group)/lazyinside', - path: '/lazyinside', - getParentRoute: () => rootRouteImport, -} as any) -const groupInsideRoute = groupInsideRouteImport.update({ - id: '/(group)/inside', - path: '/inside', - getParentRoute: () => rootRouteImport, +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, } as any) -const groupLayoutRoute = groupLayoutRouteImport.update({ - id: '/(group)/_layout', +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', getParentRoute: () => rootRouteImport, } as any) -const anotherGroupOnlyrouteinsideRoute = - anotherGroupOnlyrouteinsideRouteImport.update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', - getParentRoute: () => rootRouteImport, - } as any) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', - getParentRoute: () => rootRouteImport, +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, } as any) const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ id: '/layout-b', path: '/layout-b', getParentRoute: () => LayoutLayout2Route, } as any) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, -} as any) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport.update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', getParentRoute: () => rootRouteImport, } as any) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport.update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -291,32 +291,32 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/sfcComponent': { - id: '/sfcComponent' - path: '/sfcComponent' - fullPath: '/sfcComponent' - preLoaderRoute: typeof SfcComponentRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/editing-b': { + id: '/editing-b' + path: '/editing-b' + fullPath: '/editing-b' + preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } '/notRemountDeps': { @@ -326,34 +326,69 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/editing-b': { - id: '/editing-b' - path: '/editing-b' - fullPath: '/editing-b' - preLoaderRoute: typeof EditingBRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' + '/sfcComponent': { + id: '/sfcComponent' + path: '/sfcComponent' + fullPath: '/sfcComponent' + preLoaderRoute: typeof SfcComponentRouteImport + parentRoute: typeof rootRouteImport + } + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + parentRoute: typeof rootRouteImport + } + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/(group)/_layout': { + id: '/(group)/_layout' path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport parentRoute: typeof rootRouteImport } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -368,47 +403,26 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport - parentRoute: typeof rootRouteImport + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -417,27 +431,13 @@ declare module '@tanstack/vue-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 - } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } } } diff --git a/examples/vue/basic-file-based-sfc/package.json b/examples/vue/basic-file-based-sfc/package.json index 91581cc30d..f2a41544cb 100644 --- a/examples/vue/basic-file-based-sfc/package.json +++ b/examples/vue/basic-file-based-sfc/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", - "@tanstack/router-plugin": "^1.168.19", - "@tanstack/vue-router": "^1.170.16", - "@tanstack/vue-router-devtools": "^1.167.0", + "@tanstack/router-plugin": "^1.168.24", + "@tanstack/vue-router": "^1.170.18", + "@tanstack/vue-router-devtools": "^1.167.1", "redaxios": "^0.5.1", "tailwindcss": "^4.2.2", "vue": "^3.5.16", @@ -23,6 +23,6 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "typescript": "~5.8.3", "vite": "^8.0.14", - "vue-tsc": "^3.1.5" + "vue-tsc": "^3.3.8" } } diff --git a/examples/vue/basic-file-based-sfc/src/routeTree.gen.ts b/examples/vue/basic-file-based-sfc/src/routeTree.gen.ts index fa00eb1ce0..09d62b0706 100644 --- a/examples/vue/basic-file-based-sfc/src/routeTree.gen.ts +++ b/examples/vue/basic-file-based-sfc/src/routeTree.gen.ts @@ -11,65 +11,53 @@ import { lazyRouteComponent } from '@tanstack/vue-router' import { Route as rootRouteImport } from './routes/__root' -import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' -import { Route as RemountDepsRouteImport } from './routes/remountDeps' -import { Route as PostsRouteImport } from './routes/posts' -import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' -import { Route as EditingBRouteImport } from './routes/editing-b' -import { Route as EditingARouteImport } from './routes/editing-a' -import { Route as LayoutRouteImport } from './routes/_layout' import { Route as IndexRouteImport } from './routes/index' +import { Route as LayoutRouteImport } from './routes/_layout' +import { Route as EditingARouteImport } from './routes/editing-a' +import { Route as EditingBRouteImport } from './routes/editing-b' +import { Route as NotRemountDepsRouteImport } from './routes/notRemountDeps' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as RemountDepsRouteImport } from './routes/remountDeps' +import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' +import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' +import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' +import { Route as groupInsideRouteImport } from './routes/(group)/inside' +import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' +import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' -import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2' -import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside' -import { Route as groupInsideRouteImport } from './routes/(group)/inside' -import { Route as groupLayoutRouteImport } from './routes/(group)/_layout' -import { Route as anotherGroupOnlyrouteinsideRouteImport } from './routes/(another-group)/onlyrouteinside' -import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -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 groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout' +import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside' +import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a' +import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b' +import { Route as PostsPostIdEditRouteImport } from './routes/posts_.$postId.edit' -const Char45824Char54620Char48124Char44397Route = - Char45824Char54620Char48124Char44397RouteImport.update({ - id: '/대한민국', - path: '/대한민국', - getParentRoute: () => rootRouteImport, - } as any).update({ - component: lazyRouteComponent( - () => import('./routes/대한민국.component.vue'), - 'default', - ), - }) -const RemountDepsRoute = RemountDepsRouteImport.update({ - id: '/remountDeps', - path: '/remountDeps', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/remountDeps.component.vue'), + () => import('./routes/index.component.vue'), 'default', ), }) -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/posts.component.vue'), + () => import('./routes/_layout.component.vue'), 'default', ), }) -const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ - id: '/notRemountDeps', - path: '/notRemountDeps', +const EditingARoute = EditingARouteImport.update({ + id: '/editing-a', + path: '/editing-a', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/notRemountDeps.component.vue'), + () => import('./routes/editing-a.component.vue'), 'default', ), }) @@ -83,89 +71,56 @@ const EditingBRoute = EditingBRouteImport.update({ 'default', ), }) -const EditingARoute = EditingARouteImport.update({ - id: '/editing-a', - path: '/editing-a', +const NotRemountDepsRoute = NotRemountDepsRouteImport.update({ + id: '/notRemountDeps', + path: '/notRemountDeps', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/editing-a.component.vue'), + () => import('./routes/notRemountDeps.component.vue'), 'default', ), }) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout.component.vue'), + () => import('./routes/posts.component.vue'), 'default', ), }) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RemountDepsRoute = RemountDepsRouteImport.update({ + id: '/remountDeps', + path: '/remountDeps', getParentRoute: () => rootRouteImport, } as any).update({ component: lazyRouteComponent( - () => import('./routes/index.component.vue'), - 'default', - ), -}) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/posts.index.component.vue'), - 'default', - ), -}) -const PostsPostIdRoute = PostsPostIdRouteImport.update({ - id: '/$postId', - path: '/$postId', - getParentRoute: () => PostsRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/posts.$postId.component.vue'), - 'default', - ), - errorComponent: lazyRouteComponent( - () => import('./routes/posts.$postId.errorComponent.vue'), - 'default', - ), -}) -const LayoutLayout2Route = LayoutLayout2RouteImport.update({ - id: '/_layout-2', - getParentRoute: () => LayoutRoute, -} as any).update({ - component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2.component.vue'), + () => import('./routes/remountDeps.component.vue'), 'default', ), }) -const groupLazyinsideRoute = groupLazyinsideRouteImport - .update({ - id: '/(group)/lazyinside', - path: '/lazyinside', +const Char45824Char54620Char48124Char44397Route = + Char45824Char54620Char48124Char44397RouteImport.update({ + id: '/대한민국', + path: '/대한민국', getParentRoute: () => rootRouteImport, - } as any) - .update({ + } as any).update({ component: lazyRouteComponent( - () => import('./routes/(group)/lazyinside.component.vue'), + () => import('./routes/대한민국.component.vue'), 'default', ), }) -const groupInsideRoute = groupInsideRouteImport +const anotherGroupOnlyrouteinsideRoute = anotherGroupOnlyrouteinsideRouteImport .update({ - id: '/(group)/inside', - path: '/inside', + id: '/(another-group)/onlyrouteinside', + path: '/onlyrouteinside', getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/inside.component.vue'), + () => import('./routes/(another-group)/onlyrouteinside.component.vue'), 'default', ), }) @@ -180,72 +135,117 @@ const groupLayoutRoute = groupLayoutRouteImport 'default', ), }) -const anotherGroupOnlyrouteinsideRoute = anotherGroupOnlyrouteinsideRouteImport +const groupInsideRoute = groupInsideRouteImport .update({ - id: '/(another-group)/onlyrouteinside', - path: '/onlyrouteinside', + id: '/(group)/inside', + path: '/inside', getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(another-group)/onlyrouteinside.component.vue'), + () => import('./routes/(group)/inside.component.vue'), 'default', ), }) -const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ - id: '/posts_/$postId/edit', - path: '/posts/$postId/edit', - getParentRoute: () => rootRouteImport, +const groupLazyinsideRoute = groupLazyinsideRouteImport + .update({ + id: '/(group)/lazyinside', + path: '/lazyinside', + getParentRoute: () => rootRouteImport, + } as any) + .update({ + component: lazyRouteComponent( + () => import('./routes/(group)/lazyinside.component.vue'), + 'default', + ), + }) +const LayoutLayout2Route = LayoutLayout2RouteImport.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/posts_.$postId.edit.component.vue'), + () => import('./routes/_layout/_layout-2.component.vue'), 'default', ), }) -const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ - id: '/layout-b', - path: '/layout-b', - getParentRoute: () => LayoutLayout2Route, +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2/layout-b.component.vue'), + () => import('./routes/posts.index.component.vue'), 'default', ), }) -const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ - id: '/layout-a', - path: '/layout-a', - getParentRoute: () => LayoutLayout2Route, +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, } as any).update({ component: lazyRouteComponent( - () => import('./routes/_layout/_layout-2/layout-a.component.vue'), + () => import('./routes/posts.$postId.component.vue'), + 'default', + ), + errorComponent: lazyRouteComponent( + () => import('./routes/posts.$postId.errorComponent.vue'), 'default', ), }) -const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport +const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport .update({ - id: '/(group)/subfolder/inside', - path: '/subfolder/inside', - getParentRoute: () => rootRouteImport, + id: '/insidelayout', + path: '/insidelayout', + getParentRoute: () => groupLayoutRoute, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/subfolder/inside.component.vue'), + () => import('./routes/(group)/_layout.insidelayout.component.vue'), 'default', ), }) -const groupLayoutInsidelayoutRoute = groupLayoutInsidelayoutRouteImport +const groupSubfolderInsideRoute = groupSubfolderInsideRouteImport .update({ - id: '/insidelayout', - path: '/insidelayout', - getParentRoute: () => groupLayoutRoute, + id: '/(group)/subfolder/inside', + path: '/subfolder/inside', + getParentRoute: () => rootRouteImport, } as any) .update({ component: lazyRouteComponent( - () => import('./routes/(group)/_layout.insidelayout.component.vue'), + () => import('./routes/(group)/subfolder/inside.component.vue'), 'default', ), }) +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutARouteImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/_layout/_layout-2/layout-a.component.vue'), + 'default', + ), +}) +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/_layout/_layout-2/layout-b.component.vue'), + 'default', + ), +}) +const PostsPostIdEditRoute = PostsPostIdEditRouteImport.update({ + id: '/posts_/$postId/edit', + path: '/posts/$postId/edit', + getParentRoute: () => rootRouteImport, +} as any).update({ + component: lazyRouteComponent( + () => import('./routes/posts_.$postId.edit.component.vue'), + 'default', + ), +}) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -388,25 +388,32 @@ export interface RootRouteChildren { declare module '@tanstack/vue-router' { interface FileRoutesByPath { - '/대한민국': { - id: '/대한민국' - path: '/대한민국' - fullPath: '/대한민국' - preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/remountDeps': { - id: '/remountDeps' - path: '/remountDeps' - fullPath: '/remountDeps' - preLoaderRoute: typeof RemountDepsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport + '/editing-a': { + id: '/editing-a' + path: '/editing-a' + fullPath: '/editing-a' + preLoaderRoute: typeof EditingARouteImport + parentRoute: typeof rootRouteImport + } + '/editing-b': { + id: '/editing-b' + path: '/editing-b' + fullPath: '/editing-b' + preLoaderRoute: typeof EditingBRouteImport parentRoute: typeof rootRouteImport } '/notRemountDeps': { @@ -416,34 +423,62 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof NotRemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/editing-b': { - id: '/editing-b' - path: '/editing-b' - fullPath: '/editing-b' - preLoaderRoute: typeof EditingBRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport parentRoute: typeof rootRouteImport } - '/editing-a': { - id: '/editing-a' - path: '/editing-a' - fullPath: '/editing-a' - preLoaderRoute: typeof EditingARouteImport + '/remountDeps': { + id: '/remountDeps' + path: '/remountDeps' + fullPath: '/remountDeps' + preLoaderRoute: typeof RemountDepsRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' + '/대한민국': { + id: '/대한민국' + path: '/대한민국' + fullPath: '/대한민국' + preLoaderRoute: typeof Char45824Char54620Char48124Char44397RouteImport + parentRoute: typeof rootRouteImport + } + '/(another-group)/onlyrouteinside': { + id: '/(another-group)/onlyrouteinside' + path: '/onlyrouteinside' + fullPath: '/onlyrouteinside' + preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/(group)/_layout': { + id: '/(group)/_layout' path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport + fullPath: '' + preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/(group)/inside': { + id: '/(group)/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupInsideRouteImport parentRoute: typeof rootRouteImport } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutLayout2RouteImport + parentRoute: typeof LayoutRoute + } '/posts/': { id: '/posts/' path: '/' @@ -458,47 +493,26 @@ declare module '@tanstack/vue-router' { preLoaderRoute: typeof PostsPostIdRouteImport parentRoute: typeof PostsRoute } - '/_layout/_layout-2': { - id: '/_layout/_layout-2' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutLayout2RouteImport - parentRoute: typeof LayoutRoute - } - '/(group)/lazyinside': { - id: '/(group)/lazyinside' - path: '/lazyinside' - fullPath: '/lazyinside' - preLoaderRoute: typeof groupLazyinsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/inside': { - id: '/(group)/inside' - path: '/inside' - fullPath: '/inside' - preLoaderRoute: typeof groupInsideRouteImport - parentRoute: typeof rootRouteImport - } - '/(group)/_layout': { - id: '/(group)/_layout' - path: '' - fullPath: '' - preLoaderRoute: typeof groupLayoutRouteImport - parentRoute: typeof rootRouteImport + '/(group)/_layout/insidelayout': { + id: '/(group)/_layout/insidelayout' + path: '/insidelayout' + fullPath: '/insidelayout' + preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport + parentRoute: typeof groupLayoutRoute } - '/(another-group)/onlyrouteinside': { - id: '/(another-group)/onlyrouteinside' - path: '/onlyrouteinside' - fullPath: '/onlyrouteinside' - preLoaderRoute: typeof anotherGroupOnlyrouteinsideRouteImport + '/(group)/subfolder/inside': { + id: '/(group)/subfolder/inside' + path: '/subfolder/inside' + fullPath: '/subfolder/inside' + preLoaderRoute: typeof groupSubfolderInsideRouteImport parentRoute: typeof rootRouteImport } - '/posts_/$postId/edit': { - id: '/posts_/$postId/edit' - path: '/posts/$postId/edit' - fullPath: '/posts/$postId/edit' - preLoaderRoute: typeof PostsPostIdEditRouteImport - parentRoute: typeof rootRouteImport + '/_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' @@ -507,27 +521,13 @@ declare module '@tanstack/vue-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 - } - '/(group)/subfolder/inside': { - id: '/(group)/subfolder/inside' - path: '/subfolder/inside' - fullPath: '/subfolder/inside' - preLoaderRoute: typeof groupSubfolderInsideRouteImport + '/posts_/$postId/edit': { + id: '/posts_/$postId/edit' + path: '/posts/$postId/edit' + fullPath: '/posts/$postId/edit' + preLoaderRoute: typeof PostsPostIdEditRouteImport parentRoute: typeof rootRouteImport } - '/(group)/_layout/insidelayout': { - id: '/(group)/_layout/insidelayout' - path: '/insidelayout' - fullPath: '/insidelayout' - preLoaderRoute: typeof groupLayoutInsidelayoutRouteImport - parentRoute: typeof groupLayoutRoute - } } } diff --git a/examples/vue/basic/package.json b/examples/vue/basic/package.json index 046be6614d..16c55a19b5 100644 --- a/examples/vue/basic/package.json +++ b/examples/vue/basic/package.json @@ -9,16 +9,17 @@ "start": "vite" }, "dependencies": { - "@tanstack/vue-router": "^1.170.16", - "@tanstack/vue-router-devtools": "^1.167.0", + "@tanstack/vue-router": "^1.170.18", + "@tanstack/vue-router-devtools": "^1.167.1", "redaxios": "^0.5.1", "vue": "^3.5.13", "tailwindcss": "4.1.18" }, "devDependencies": { - "typescript": "^6.0.2", + "@typescript/native": "npm:typescript@^7.0.2", + "typescript": "npm:@typescript/typescript6@^6.0.2", "vite": "^8.0.14", - "vue-tsc": "^3.1.5", + "vue-tsc": "^3.3.8", "@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue-jsx": "^5.1.5", "@tailwindcss/vite": "^4.2.2" diff --git a/media/header_router.png b/media/header_router.png deleted file mode 100644 index 5f2d58134d..0000000000 Binary files a/media/header_router.png and /dev/null differ diff --git a/media/header_start.png b/media/header_start.png deleted file mode 100644 index 8622986ee1..0000000000 Binary files a/media/header_start.png and /dev/null differ diff --git a/nx.json b/nx.json index 08e7563642..764112a2f6 100644 --- a/nx.json +++ b/nx.json @@ -9,10 +9,18 @@ }, "namedInputs": { "sharedGlobals": [ + "{workspaceRoot}/.npmrc", + "{workspaceRoot}/.nx/workflows/sandboxing-config.yaml", "{workspaceRoot}/.nvmrc", "{workspaceRoot}/package.json", "{workspaceRoot}/tsconfig.json" ], + "dependentTaskOutputs": [ + { + "dependentTasksOutputFiles": "**/*", + "transitive": true + } + ], "default": [ "sharedGlobals", "{projectRoot}/**/*", @@ -22,7 +30,8 @@ "default", "!{projectRoot}/tests/**/*", "!{projectRoot}/eslint.config.js" - ] + ], + "buildProduction": ["sharedGlobals", "{projectRoot}/**/*"] }, "targetDefaults": { "test:docs": { @@ -31,19 +40,34 @@ }, "test:eslint": { "cache": true, - "dependsOn": ["^build"], - "inputs": ["default", "^production", "{workspaceRoot}/eslint.config.js"] + "dependsOn": ["^build", "build"], + "inputs": [ + "default", + "^production", + "{workspaceRoot}/eslint.config.js", + "dependentTaskOutputs" + ] }, "test:unit": { "cache": true, "dependsOn": ["^build"], - "inputs": ["default", "^production"], + "inputs": [ + "default", + "^production", + "{workspaceRoot}/eslint.config.js", + "dependentTaskOutputs" + ], "outputs": ["{projectRoot}/coverage"] }, "test:e2e": { "cache": true, "dependsOn": ["^build", "test:e2e--*"], - "inputs": ["default", "^production"] + "inputs": ["default", "^production", "dependentTaskOutputs"], + "outputs": [ + "{projectRoot}/dist", + "{projectRoot}/port-*.txt", + "{projectRoot}/test-results" + ] }, "test:e2e:nitro": { "cache": true, @@ -62,24 +86,40 @@ }, "test:types": { "cache": true, - "dependsOn": ["^build"], - "inputs": ["default", "^production"] + "dependsOn": ["^build", "build"], + "inputs": [ + "default", + "^production", + "{workspaceRoot}/eslint.config.js", + "dependentTaskOutputs" + ] }, "test:types:ssr": { "cache": true, - "dependsOn": ["^build"], - "inputs": ["default", "^production"] + "dependsOn": ["^build", "build"], + "inputs": [ + "default", + "^production", + "{workspaceRoot}/benchmarks/ssr/bench-utils.ts", + "dependentTaskOutputs" + ] }, "build": { "cache": true, "dependsOn": ["^build"], - "inputs": ["production", "^production"], - "outputs": ["{projectRoot}/build", "{projectRoot}/dist"] + "inputs": ["buildProduction", "^buildProduction", "dependentTaskOutputs"], + "outputs": [ + "{projectRoot}/.netlify", + "{projectRoot}/.wrangler", + "{projectRoot}/.output", + "{projectRoot}/build", + "{projectRoot}/dist" + ] }, "test:build": { "cache": true, "dependsOn": ["build"], - "inputs": ["production"] + "inputs": ["buildProduction", "dependentTaskOutputs"] } }, "plugins": [ diff --git a/package.json b/package.json index cdc2bf2bc8..98682bac38 100644 --- a/package.json +++ b/package.json @@ -93,13 +93,13 @@ "redaxios": "^0.5.1", "rimraf": "^6.1.2", "tinyglobby": "^0.2.15", - "typescript": "^6.0.2", - "typescript55": "npm:typescript@5.5", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vite": "catalog:", + "vitest": "^4.1.4", + "@typescript/native": "npm:typescript@^7.0.2", "typescript56": "npm:typescript@5.6", "typescript57": "npm:typescript@5.7", "typescript58": "npm:typescript@5.8", - "typescript59": "npm:typescript@5.9", - "vite": "catalog:", - "vitest": "^4.1.4" + "typescript59": "npm:typescript@5.9" } } diff --git a/packages/arktype-adapter/package.json b/packages/arktype-adapter/package.json index f7840ba4f8..34bd520f9b 100644 --- a/packages/arktype-adapter/package.json +++ b/packages/arktype-adapter/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch --typecheck", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", diff --git a/packages/eslint-plugin-router/package.json b/packages/eslint-plugin-router/package.json index b99ee55e13..25e742f8f7 100644 --- a/packages/eslint-plugin-router/package.json +++ b/packages/eslint-plugin-router/package.json @@ -18,12 +18,12 @@ "clean": "rimraf ./dist ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch --typecheck", "test:build": "publint --strict && attw --pack .", diff --git a/packages/eslint-plugin-start/package.json b/packages/eslint-plugin-start/package.json index c4867c58d0..df48592327 100644 --- a/packages/eslint-plugin-start/package.json +++ b/packages/eslint-plugin-start/package.json @@ -18,12 +18,12 @@ "clean": "rimraf ./dist ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch --typecheck", "test:build": "publint --strict && attw --pack .", @@ -61,7 +61,7 @@ "typescript": "^5.8.2" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.7.0" } } diff --git a/packages/history/package.json b/packages/history/package.json index 790acabc3c..52e4641a01 100644 --- a/packages/history/package.json +++ b/packages/history/package.json @@ -22,12 +22,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch", diff --git a/packages/nitro-v2-vite-plugin/package.json b/packages/nitro-v2-vite-plugin/package.json index b3021ea3c6..68966c5011 100644 --- a/packages/nitro-v2-vite-plugin/package.json +++ b/packages/nitro-v2-vite-plugin/package.json @@ -29,12 +29,12 @@ "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:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, diff --git a/packages/react-router-devtools/CHANGELOG.md b/packages/react-router-devtools/CHANGELOG.md index 40e065c673..7634f3d19e 100644 --- a/packages/react-router-devtools/CHANGELOG.md +++ b/packages/react-router-devtools/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/react-router-devtools +## 1.167.1 + +### Patch Changes + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + - @tanstack/react-router@1.170.19 + - @tanstack/router-devtools-core@1.168.1 + ## 1.167.0 ### Minor Changes diff --git a/packages/react-router-devtools/README.md b/packages/react-router-devtools/README.md index 7be76f46c2..0079d7c065 100644 --- a/packages/react-router-devtools/README.md +++ b/packages/react-router-devtools/README.md @@ -1,5 +1,22 @@ +
+ + + + TanStack React Router + +
# TanStack React Router Devtools See https://tanstack.com/router/latest/docs/framework/react/devtools diff --git a/packages/react-router-devtools/package.json b/packages/react-router-devtools/package.json index 3cb40e24b0..262a9294fb 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.167.0", + "version": "1.167.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, diff --git a/packages/react-router-ssr-query/README.md b/packages/react-router-ssr-query/README.md index 0a19fd3b81..c3a60d1008 100644 --- a/packages/react-router-ssr-query/README.md +++ b/packages/react-router-ssr-query/README.md @@ -2,7 +2,21 @@ # TanStack React Router -![TanStack Router Header](https://raw.githubusercontent.com/TanStack/router/main/media/header_router.png) + + + + TanStack React Router + 🤖 Type-safe router w/ built-in caching & URL state management for React! diff --git a/packages/react-router-ssr-query/package.json b/packages/react-router-ssr-query/package.json index 82d9917747..2967d695b0 100644 --- a/packages/react-router-ssr-query/package.json +++ b/packages/react-router-ssr-query/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "exit 0; vitest", "test:unit:dev": "pnpm run test:unit --watch", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index dc138ae4ed..49e069817b 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,45 @@ # @tanstack/react-router +## 1.170.19 + +### Patch Changes + +- [#7805](https://github.com/TanStack/router/pull/7805) [`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd) - Rewrite match loading around a lane-based scheduler that tracks each navigation, preload, and background reload as an ordered unit of work. This fixes pending/redirect/retry state leaking between overlapping navigations, restores correct SSR status codes for redirects, errors, and not-found responses, and closes hydration gaps where the client re-ran work the server had already completed. + - Invalidation now retires matching active preloads so older speculative loader results cannot become fresh cache data after invalidation. + - Route `headers()` now only runs on the server, matching the documented behavior — it is no longer invoked during client-side asset projection. + - The documented default `gcTime` and `preloadGcTime` now match the existing runtime default of 5 minutes (`300_000`). + + **Removed / changed exported internals** + - `RouterState` no longer includes `loadedAt`, `isTransitioning`, `statusCode`, or `redirect`. Use `match.updatedAt` in place of `loadedAt`; subscribe to `router.state.status` / `router.state.isLoading` in place of `isTransitioning`; server response status and redirect handling are now internal to the server loader and are no longer exposed on `router.state`. + - `RouteMatch.fetchCount` has been removed, with no replacement — it was purely informational. + - `RouteMatch.status` no longer includes `'redirected'` (it remains `'pending' | 'success' | 'error' | 'notFound'`) — redirected matches are dropped from the match list instead of being rendered. + - `RouteMatch.globalNotFound` has been renamed and privatized to the internal `_notFound` field. Use `match.status === 'notFound'` instead. + - The exported React, Solid, and Vue `Match` components now accept `routeId` instead of `matchId`. + - The exported `RouterStores` adapter contract now uses route-keyed presentation stores: `matchesId` is replaced by `ids`, `matchStores` by `byRoute`, and `getRouteMatchStore()` by `getMatchStore()`. The separate `loadedAt`, `isLoading`, `isTransitioning`, `statusCode`, and `redirect` stores have been removed, along with the pending/cache stores and their setters. `StoreConfig.init` has also been removed. Read application-facing state from `router.state`; preload and cache coordination are now internal. + - Removed `RouterCore` members `getMatch()`, `updateMatch()`, `cancelMatch()`, and `cancelMatches()` — read matches from `router.state.matches` (e.g. `router.state.matches.find((m) => m.id === id)`); there is no replacement for mutating or cancelling an individual in-flight match from outside the router. + - Removed `RouterCore.hasNotFoundMatch()` — use `router.state.matches.some((m) => m.status === 'notFound')`. + - Removed `RouterCore.looseRoutesById` — use `routesById`. + - Removed `RouterCore.isPrerendering()`, `RouterCore.isViewTransitionTypesSupported`, and `RouterCore.viewTransitionPromise`, with no replacement. + - Removed `RouterCore.getParsedLocationHref()` and `RouterCore.clearExpiredCache()`, with no replacement — expired cache entries are now reconciled automatically as part of match commit. + - Removed `RouterCore.latestLoadPromise` and `RouterCore.beforeLoad()`, with no replacement. + - `RouterCore.commitLocationPromise` and `RouterCore.pendingBuiltLocation` have been replaced by the internal `_commitPromise` and `_pendingLocation` fields. + - Removed the exported `GetMatchFn` and `UpdateMatchFn` types, along with the methods they typed. + - Removed the standalone `getMatchedRoutes()` export from `@tanstack/router-core` — use the `router.getMatchedRoutes()` instance method instead. + - `RouterCore.loadRouteChunk()` no longer accepts an array of component types as its second argument. One-argument usage is unchanged; the optional second argument is now `'errorComponent'`, `'notFoundComponent'`, or `false` for internal boundary loading. + - Removed `Redirect.redirectHandled`, which was internal redirect bookkeeping. + - `MatchRoutesOpts.preload` and `MatchRoutesOpts.dest` have been removed. + - `StartTransitionFn` is now `(fn, expected) => Promise` (previously `(fn) => void`). This only affects custom framework adapters that implement `startTransition`. + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + +## 1.170.18 + +### Patch Changes + +- Updated dependencies [[`e2dd204`](https://github.com/TanStack/router/commit/e2dd2049cb42eb219d3b447b8605066d19d9c1fa)]: + - @tanstack/router-core@1.171.15 + ## 1.170.17 ### Patch Changes diff --git a/packages/react-router/README.md b/packages/react-router/README.md index 0a19fd3b81..c3a60d1008 100644 --- a/packages/react-router/README.md +++ b/packages/react-router/README.md @@ -2,7 +2,21 @@ # TanStack React Router -![TanStack Router Header](https://raw.githubusercontent.com/TanStack/router/main/media/header_router.png) + + + + TanStack React Router + 🤖 Type-safe router w/ built-in caching & URL state management for React! diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 2e63f8d973..fdb6266ef2 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router", - "version": "1.170.17", + "version": "1.170.19", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js -p tsconfig.legacy.json", - "test:types:ts60": "tsc -p tsconfig.legacy.json", + "test:types:ts60": "tsc6 -p tsconfig.legacy.json", + "test:types:ts70": "tsc -p tsconfig.legacy.json", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch --hideSkippedTests", "test:perf": "vitest bench", @@ -100,6 +100,7 @@ "devDependencies": { "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", + "@tanstack/react-query": "catalog:", "@types/node": ">=20", "@vitejs/plugin-react": "^4.3.4", "combinate": "^1.1.11", diff --git a/packages/react-router/skills/compositions/router-query/SKILL.md b/packages/react-router/skills/compositions/router-query/SKILL.md index 89fba76b54..bcb2f939cd 100644 --- a/packages/react-router/skills/compositions/router-query/SKILL.md +++ b/packages/react-router/skills/compositions/router-query/SKILL.md @@ -1,14 +1,15 @@ --- -name: compositions/router-query +name: router-query description: >- Integrating TanStack Router with TanStack Query: queryClient in router context, ensureQueryData/prefetchQuery in loaders, useSuspenseQuery in components, defaultPreloadStaleTime: 0, setupRouterSsrQueryIntegration for SSR dehydration/hydration and streaming, per-request QueryClient isolation. -type: composition -library: tanstack-router -library_version: '1.166.2' +metadata: + type: composition + library: tanstack-router + library_version: '1.166.2' requires: - router-core - router-core/data-loading diff --git a/packages/react-router/skills/lifecycle/migrate-from-react-router/SKILL.md b/packages/react-router/skills/lifecycle/migrate-from-react-router/SKILL.md index c9f53358e6..fdc4f4c4e5 100644 --- a/packages/react-router/skills/lifecycle/migrate-from-react-router/SKILL.md +++ b/packages/react-router/skills/lifecycle/migrate-from-react-router/SKILL.md @@ -1,13 +1,14 @@ --- -name: lifecycle/migrate-from-react-router +name: migrate-from-react-router description: >- Step-by-step migration from React Router v7 to TanStack Router: route definition conversion, Link/useNavigate API differences, useSearchParams to validateSearch + useSearch, useParams with from, Outlet replacement, loader conversion, code splitting differences. -type: lifecycle -library: tanstack-router -library_version: '1.166.2' +metadata: + type: lifecycle + library: tanstack-router + library_version: '1.166.2' requires: - router-core - react-router diff --git a/packages/react-router/skills/react-router/SKILL.md b/packages/react-router/skills/react-router/SKILL.md index 074f23e091..53f4135316 100644 --- a/packages/react-router/skills/react-router/SKILL.md +++ b/packages/react-router/skills/react-router/SKILL.md @@ -8,10 +8,11 @@ description: >- Outlet, CatchBoundary, ErrorComponent. React-specific patterns for hooks, providers, SSR hydration, and createLink with forwardRef. -type: framework -library: tanstack-router -library_version: '1.166.2' -framework: react +metadata: + type: framework + library: tanstack-router + library_version: '1.166.2' + framework: react requires: - router-core sources: @@ -24,8 +25,6 @@ sources: This skill builds on router-core. Read [router-core](../../../router-core/skills/router-core/SKILL.md) first for foundational concepts. -This skill covers the React-specific bindings, components, hooks, and setup for TanStack Router. - > **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values. > **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server. > **CRITICAL**: Do not confuse `@tanstack/react-router` with `react-router-dom`/`react-router`. They are completely different libraries with different APIs. diff --git a/packages/react-router/src/CatchBoundary.tsx b/packages/react-router/src/CatchBoundary.tsx index 98743f9447..d97e49f8f5 100644 --- a/packages/react-router/src/CatchBoundary.tsx +++ b/packages/react-router/src/CatchBoundary.tsx @@ -5,44 +5,25 @@ import type { ErrorRouteComponent } from './route' import type { ErrorInfo } from 'react' export function CatchBoundary(props: { - getResetKey: () => number | string + getResetKey: () => unknown children: React.ReactNode errorComponent?: ErrorRouteComponent onCatch?: (error: Error, errorInfo: ErrorInfo) => void }) { - const errorComponent = props.errorComponent ?? ErrorComponent - - return ( - { - if (error) { - return React.createElement(errorComponent, { - error, - reset, - }) - } - - return props.children - }} - /> - ) + return } class CatchBoundaryImpl extends React.Component<{ - getResetKey: () => number | string - children: (props: { - error: Error | null - reset: () => void - }) => React.ReactNode + getResetKey: () => unknown + children: React.ReactNode + errorComponent?: ErrorRouteComponent onCatch?: (error: Error, errorInfo: ErrorInfo) => void }> { - state = { error: null } as { error: Error | null; resetKey?: string | number } + state = { error: null } as { error: Error | null; resetKey?: unknown } static getDerivedStateFromProps( - props: { getResetKey: () => string | number }, - state: { resetKey?: string | number; error: Error | null }, + props: { getResetKey: () => unknown }, + state: { resetKey?: unknown; error: Error | null }, ) { const resetKey = props.getResetKey() @@ -55,21 +36,20 @@ class CatchBoundaryImpl extends React.Component<{ static getDerivedStateFromError(error: Error) { return { error } } - reset() { + reset = () => { this.setState({ error: null }) } componentDidCatch(error: Error, errorInfo: ErrorInfo) { - if (this.props.onCatch) { - this.props.onCatch(error, errorInfo) - } + this.props.onCatch?.(error, errorInfo) } render() { - return this.props.children({ - error: this.state.error, - reset: () => { - this.reset() - }, - }) + const error = this.state.error + return error + ? React.createElement(this.props.errorComponent ?? ErrorComponent, { + error, + reset: this.reset, + }) + : this.props.children } } diff --git a/packages/react-router/src/ClientOnly.tsx b/packages/react-router/src/ClientOnly.tsx index 10f903b204..102859d75a 100644 --- a/packages/react-router/src/ClientOnly.tsx +++ b/packages/react-router/src/ClientOnly.tsx @@ -31,11 +31,7 @@ export interface ClientOnlyProps { * ``` */ export function ClientOnly({ children, fallback = null }: ClientOnlyProps) { - return useHydrated() ? ( - {children} - ) : ( - {fallback} - ) + return {useHydrated() ? children : fallback} } /** diff --git a/packages/react-router/src/Match.tsx b/packages/react-router/src/Match.tsx index 5de5546aeb..6bca031a4d 100644 --- a/packages/react-router/src/Match.tsx +++ b/packages/react-router/src/Match.tsx @@ -2,14 +2,7 @@ import * as React from 'react' import { useStore } from '@tanstack/react-store' -import { - createControlledPromise, - getLocationChangeInfo, - invariant, - isNotFound, - isRedirect, - rootRouteId, -} from '@tanstack/router-core' +import { isNotFound, rootRouteId } from '@tanstack/router-core' import { isServer } from '@tanstack/router-core/isServer' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouter } from './useRouter' @@ -19,131 +12,59 @@ import { SafeFragment } from './SafeFragment' import { renderRouteNotFound } from './renderRouteNotFound' import { ScrollRestoration } from './scroll-restoration' import { ClientOnly } from './ClientOnly' -import { useLayoutEffect } from './utils' import type { AnyRoute, AnyRouteMatch, - ParsedLocation, RootRouteOptions, } from '@tanstack/router-core' +export function renderPending( + router: ReturnType, + route?: AnyRoute, +) { + const PendingComponent = + route?.options.pendingComponent ?? router.options.defaultPendingComponent + return PendingComponent ? : null +} + type OutletMatchSelection = [ - routeId: string | undefined, parentGlobalNotFound: boolean, + parentNotFoundError: unknown, ] -const matchViewFieldsEqual = (a: AnyRouteMatch, b: AnyRouteMatch) => - a.routeId === b.routeId && a._displayPending === b._displayPending - const outletMatchSelectionEqual = ( a: OutletMatchSelection, b: OutletMatchSelection, ) => a[0] === b[0] && a[1] === b[1] export const Match = React.memo(function MatchImpl({ - matchId, + routeId, }: { - matchId: string + routeId: string }) { const router = useRouter() if (isServer ?? router.isServer) { - const match = router.stores.matchStores.get(matchId)?.get() - if (!match) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - `Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`, - ) - } - - invariant() - } - - const routeId = match.routeId as string - const parentRouteId = (router.routesById[routeId] as AnyRoute).parentRoute - ?.id - - return ( - - ) + const match = router.stores.byRoute.get(routeId)!.get()! + return } - // Subscribe directly to the match store from the pool. - // The matchId prop is stable for this component's lifetime (set by Outlet), - // and reconcileMatchPool reuses stores for the same matchId. - - const matchStore = router.stores.matchStores.get(matchId) - if (!matchStore) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - `Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`, - ) - } - - invariant() - } - // eslint-disable-next-line react-hooks/rules-of-hooks - const resetKey = useStore(router.stores.loadedAt, (loadedAt) => loadedAt) + const matchStore = router.stores.getMatchStore(routeId) // eslint-disable-next-line react-hooks/rules-of-hooks - const match = useStore(matchStore, (value) => value, matchViewFieldsEqual) - // eslint-disable-next-line react-hooks/rules-of-hooks - const matchState = React.useMemo(() => { - const routeId = match.routeId as string - const parentRouteId = (router.routesById[routeId] as AnyRoute).parentRoute - ?.id - - return { - routeId, - ssr: match.ssr, - _displayPending: match._displayPending, - parentRouteId: parentRouteId as string | undefined, - } satisfies MatchViewState - }, [match._displayPending, match.routeId, match.ssr, router.routesById]) - - return ( - - ) + const match = useStore(matchStore, (value) => value) + return }) -type MatchViewState = { - routeId: string - ssr: boolean | 'data-only' | undefined - _displayPending: boolean | undefined - parentRouteId: string | undefined -} - function MatchView({ router, - matchId, - resetKey, - matchState, + match, }: { router: ReturnType - matchId: string - resetKey: number - matchState: MatchViewState + match: AnyRouteMatch }) { - const route: AnyRoute = router.routesById[matchState.routeId] + const route: AnyRoute = router.routesById[match.routeId] - const PendingComponent = - route.options.pendingComponent ?? router.options.defaultPendingComponent - - const pendingElement = PendingComponent ? : null + const pendingElement = renderPending(router, route) const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent @@ -151,19 +72,16 @@ function MatchView({ const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch const routeNotFoundComponent = route.isRoot - ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component + ? // If it's the root route, use the _notFound option, with fallback to the notFoundRoute's component (route.options.notFoundComponent ?? router.options.notFoundRoute?.options.component) : route.options.notFoundComponent - const resolvedNoSsr = - matchState.ssr === false || matchState.ssr === 'data-only' + const resolvedNoSsr = match.ssr === false || match.ssr === 'data-only' const ResolvedSuspenseBoundary = - // If we're on the root route, allow forcefully wrapping in suspense - (!route.isRoot || route.options.wrapInSuspense || resolvedNoSsr) && (route.options.wrapInSuspense ?? - PendingComponent ?? - ((route.options.errorComponent as any)?.preload || resolvedNoSsr)) + pendingElement ?? + ((route.options.errorComponent as any)?.preload || resolvedNoSsr)) ? React.Suspense : SafeFragment @@ -180,235 +98,68 @@ function MatchView({ : SafeFragment return ( - + resetKey} - errorComponent={routeErrorComponent || ErrorComponent} + getResetKey={() => match} + errorComponent={routeErrorComponent as any} onCatch={(error, errorInfo) => { // Forward not found errors (we don't want to show the error component for these) if (isNotFound(error)) { - error.routeId ??= matchState.routeId as any + error.routeId ??= match.routeId throw error } if (process.env.NODE_ENV !== 'production') { - console.warn(`Warning: Error in route match: ${matchId}`) + console.warn(`Warning: Error in route match: ${match.id}`) } routeOnCatch?.(error, errorInfo) }} > { - error.routeId ??= matchState.routeId as any - - // If the current not found handler doesn't exist or it has a - // route ID which doesn't match the current route, rethrow the error - if ( - !routeNotFoundComponent || - (error.routeId && error.routeId !== matchState.routeId) || - (!error.routeId && !route.isRoot) - ) + error.routeId ??= match.routeId + + if (error.routeId !== match.routeId) { throw error + } - return React.createElement(routeNotFoundComponent, error as any) + return React.createElement( + routeNotFoundComponent!, + error as any, + ) }} > - {resolvedNoSsr || matchState._displayPending ? ( + {resolvedNoSsr ? ( - + ) : ( - + )} - {matchState.parentRouteId === rootRouteId ? ( - <> - - {router.options.scrollRestoration && (isServer ?? router.isServer) ? ( - - ) : null} - + {(isServer ?? router.isServer) && + route.parentRoute?.id === rootRouteId && + router.options.scrollRestoration ? ( + ) : null} ) } -// On Rendered can't happen above the root layout because it needs to run after -// the route subtree has committed below the root layout. Keeping it here lets -// us fire onRendered even after a hydration mismatch above the root layout -// (like bad head/link tags, which is common). -function OnRendered() { - const router = useRouter() - - if (isServer ?? router.isServer) { - return null - } - - // Track the resolvedLocation as of the last render so that onRendered can - // report the correct fromLocation. By the time this effect fires, - // resolvedLocation has already been updated to the new location by - // Transitioner, so we cannot use router.stores.resolvedLocation.get() - // directly as the fromLocation. - // @ts-expect-error -- init to `undefined` but don't write `undefined` to shave bytes - // eslint-disable-next-line react-hooks/rules-of-hooks - const prevResolvedLocationRef = React.useRef< - ParsedLocation | undefined - >() - // eslint-disable-next-line react-hooks/rules-of-hooks - const renderedLocationKey = useStore( - router.stores.resolvedLocation, - (resolvedLocation) => resolvedLocation?.state.__TSR_key, - ) - - // eslint-disable-next-line react-hooks/rules-of-hooks - useLayoutEffect(() => { - const currentResolvedLocation = router.stores.resolvedLocation.get() - const previousResolvedLocation = prevResolvedLocationRef.current - - if ( - currentResolvedLocation && - (!previousResolvedLocation || - previousResolvedLocation.href !== currentResolvedLocation.href) - ) { - router.emit({ - type: 'onRendered', - ...getLocationChangeInfo( - router.stores.location.get(), - previousResolvedLocation ?? currentResolvedLocation, - ), - }) - } - prevResolvedLocationRef.current = currentResolvedLocation - }, [renderedLocationKey, router]) - - return null -} - export const MatchInner = React.memo(function MatchInnerImpl({ - matchId, + match, }: { - matchId: string + match: AnyRouteMatch }): any { const router = useRouter() - - const getMatchPromise = ( - match: { - id: string - _nonReactive: { - displayPendingPromise?: Promise - minPendingPromise?: Promise - loadPromise?: Promise - } - }, - key: 'displayPendingPromise' | 'minPendingPromise' | 'loadPromise', - ) => { - return ( - router.getMatch(match.id)?._nonReactive[key] ?? match._nonReactive[key] - ) - } - - if (isServer ?? router.isServer) { - const match = router.stores.matchStores.get(matchId)?.get() - if (!match) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - `Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`, - ) - } - - invariant() - } - - const routeId = match.routeId as string - const route = router.routesById[routeId] as AnyRoute - const remountFn = - (router.routesById[routeId] as AnyRoute).options.remountDeps ?? - router.options.defaultRemountDeps - const remountDeps = remountFn?.({ - routeId, - loaderDeps: match.loaderDeps, - params: match._strictParams, - search: match._strictSearch, - }) - const key = remountDeps ? JSON.stringify(remountDeps) : undefined - const Comp = route.options.component ?? router.options.defaultComponent - const out = Comp ? : - - if (match._displayPending) { - throw getMatchPromise(match, 'displayPendingPromise') - } - - if (match._forcePending) { - throw getMatchPromise(match, 'minPendingPromise') - } - - if (match.status === 'pending') { - throw getMatchPromise(match, 'loadPromise') - } - - if (match.status === 'notFound') { - if (!isNotFound(match.error)) { - if (process.env.NODE_ENV !== 'production') { - throw new Error('Invariant failed: Expected a notFound error') - } - - invariant() - } - return renderRouteNotFound(router, route, match.error) - } - - if (match.status === 'redirected') { - if (!isRedirect(match.error)) { - if (process.env.NODE_ENV !== 'production') { - throw new Error('Invariant failed: Expected a redirect error') - } - - invariant() - } - throw getMatchPromise(match, 'loadPromise') - } - - if (match.status === 'error') { - const RouteErrorComponent = - (route.options.errorComponent ?? - router.options.defaultErrorComponent) || - ErrorComponent - return ( - - ) - } - - return out - } - - const matchStore = router.stores.matchStores.get(matchId) - if (!matchStore) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - `Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`, - ) - } - - invariant() - } - // eslint-disable-next-line react-hooks/rules-of-hooks - const match = useStore(matchStore, (value) => value) - const routeId = match.routeId as string + const routeId = match.routeId const route = router.routesById[routeId] as AnyRoute - // eslint-disable-next-line react-hooks/rules-of-hooks const key = React.useMemo(() => { const remountFn = - (router.routesById[routeId] as AnyRoute).options.remountDeps ?? - router.options.defaultRemountDeps + route.options.remountDeps ?? router.options.defaultRemountDeps const remountDeps = remountFn?.({ routeId, loaderDeps: match.loaderDeps, @@ -421,85 +172,26 @@ export const MatchInner = React.memo(function MatchInnerImpl({ match.loaderDeps, match._strictParams, match._strictSearch, + route.options.remountDeps, router.options.defaultRemountDeps, - router.routesById, ]) - - // eslint-disable-next-line react-hooks/rules-of-hooks const out = React.useMemo(() => { const Comp = route.options.component ?? router.options.defaultComponent - if (Comp) { - return - } - return + return Comp ? : }, [key, route.options.component, router.options.defaultComponent]) - if (match._displayPending) { - throw getMatchPromise(match, 'displayPendingPromise') - } - - if (match._forcePending) { - throw getMatchPromise(match, 'minPendingPromise') - } - - // see also hydrate() in packages/router-core/src/ssr/ssr-client.ts if (match.status === 'pending') { - // We're pending, and if we have a minPendingMs, we need to wait for it - const pendingMinMs = - route.options.pendingMinMs ?? router.options.defaultPendingMinMs - if (pendingMinMs) { - const routerMatch = router.getMatch(match.id) - if (routerMatch && !routerMatch._nonReactive.minPendingPromise) { - // Create a promise that will resolve after the minPendingMs - if (!(isServer ?? router.isServer)) { - const minPendingPromise = createControlledPromise() - - routerMatch._nonReactive.minPendingPromise = minPendingPromise - - setTimeout(() => { - minPendingPromise.resolve() - // We've handled the minPendingPromise, so we can delete it - routerMatch._nonReactive.minPendingPromise = undefined - }, pendingMinMs) - } - } + if (router._tx) { + throw router._tx[5] } - throw getMatchPromise(match, 'loadPromise') + return renderPending(router, route) } if (match.status === 'notFound') { - if (!isNotFound(match.error)) { - if (process.env.NODE_ENV !== 'production') { - throw new Error('Invariant failed: Expected a notFound error') - } - - invariant() - } return renderRouteNotFound(router, route, match.error) } - if (match.status === 'redirected') { - // A match can be observed as redirected during an in-flight transition, - // especially when pending UI is already rendering. Suspend on the match's - // load promise so React can abandon this stale render and continue the - // redirect transition. - if (!isRedirect(match.error)) { - if (process.env.NODE_ENV !== 'production') { - throw new Error('Invariant failed: Expected a redirect error') - } - - invariant() - } - - throw getMatchPromise(match, 'loadPromise') - } - if (match.status === 'error') { - // If we're on the server, we need to use React's new and super - // wonky api for throwing errors from a server side render inside - // of a suspense boundary. This is the only way to get - // renderToPipeableStream to not hang indefinitely. - // We'll serialize the error and rethrow it on the client. if (isServer ?? router.isServer) { const RouteErrorComponent = (route.options.errorComponent ?? @@ -515,7 +207,6 @@ export const MatchInner = React.memo(function MatchInnerImpl({ /> ) } - throw match.error } @@ -530,74 +221,54 @@ export const MatchInner = React.memo(function MatchInnerImpl({ */ export const Outlet = React.memo(function OutletImpl() { const router = useRouter() - const matchId = React.useContext(matchContext) + const routeId = React.useContext(matchContext)! - let routeId: string | undefined - let parentGlobalNotFound = false - let childMatchId: string | undefined + let parentGlobalNotFound: boolean + let parentNotFoundError: unknown + let childRouteId: string | undefined if (isServer ?? router.isServer) { const matches = router.stores.matches.get() - const parentIndex = matchId - ? matches.findIndex((match) => match.id === matchId) - : -1 - const parentMatch = parentIndex >= 0 ? matches[parentIndex] : undefined - routeId = parentMatch?.routeId as string | undefined - parentGlobalNotFound = parentMatch?.globalNotFound ?? false - childMatchId = - parentIndex >= 0 ? (matches[parentIndex + 1]?.id as string) : undefined + const parentIndex = matches.findIndex((match) => match.routeId === routeId) + const parentMatch = matches[parentIndex]! + parentGlobalNotFound = !!parentMatch._notFound + parentNotFoundError = parentMatch.error + childRouteId = matches[parentIndex + 1]?.routeId } else { - // Subscribe directly to the match store from the pool instead of - // the two-level byId → matchStore pattern. - const parentMatchStore = matchId - ? router.stores.matchStores.get(matchId) - : undefined + const parentMatchStore = router.stores.getMatchStore(routeId) // eslint-disable-next-line react-hooks/rules-of-hooks - ;[routeId, parentGlobalNotFound] = useStore( + ;[parentGlobalNotFound, parentNotFoundError] = useStore( parentMatchStore, - (match): OutletMatchSelection => [ - match?.routeId as string | undefined, - match?.globalNotFound ?? false, - ], + (match): OutletMatchSelection => [!!match!._notFound, match!.error], outletMatchSelectionEqual, ) // eslint-disable-next-line react-hooks/rules-of-hooks - childMatchId = useStore(router.stores.matchesId, (ids) => { - const index = ids.findIndex((id) => id === matchId) - return ids[index + 1] + childRouteId = useStore(router.stores.ids, (ids) => { + return ids[ids.indexOf(routeId) + 1] }) } - const route = routeId ? router.routesById[routeId] : undefined - - const pendingElement = router.options.defaultPendingComponent ? ( - - ) : null - if (parentGlobalNotFound) { - if (!route) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'Invariant failed: Could not resolve route for Outlet render', - ) - } - - invariant() - } - return renderRouteNotFound(router, route, undefined) + return renderRouteNotFound( + router, + router.routesById[routeId], + parentNotFoundError, + ) } - if (!childMatchId) { + if (!childRouteId) { return null } - const nextMatch = + const nextMatch = if (routeId === rootRouteId) { return ( - {nextMatch} + + {nextMatch} + ) } diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx index 62d0fc42ae..3be4173996 100644 --- a/packages/react-router/src/Matches.tsx +++ b/packages/react-router/src/Matches.tsx @@ -4,12 +4,13 @@ import * as React from 'react' import { useStore } from '@tanstack/react-store' import { rootRouteId } from '@tanstack/router-core' import { isServer } from '@tanstack/router-core/isServer' -import { CatchBoundary, ErrorComponent } from './CatchBoundary' +import { CatchBoundary } from './CatchBoundary' import { useRouter } from './useRouter' import { useStructuralSharing } from './useMatch' -import { Transitioner } from './Transitioner' +import { useLayoutEffect } from './utils' +import { Transitioner, settleOwner } from './Transitioner' import { matchContext } from './matchContext' -import { Match } from './Match' +import { Match, renderPending } from './Match' import { SafeFragment } from './SafeFragment' import type { StructuralSharingOption, @@ -48,23 +49,19 @@ export function Matches() { const router = useRouter() const rootRoute: AnyRoute = router.routesById[rootRouteId] - const PendingComponent = - rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent - - const pendingElement = PendingComponent ? : null + const pendingElement = renderPending(router, rootRoute) // Do not render a root Suspense during SSR or hydrating from SSR const ResolvedSuspense = - (isServer ?? router.isServer) || - (typeof document !== 'undefined' && router.ssr) - ? SafeFragment - : React.Suspense + (isServer ?? router.isServer) || router.ssr ? SafeFragment : React.Suspense const inner = ( - + <> {!(isServer ?? router.isServer) && } - - + + + + ) return router.options.InnerWrap ? ( @@ -76,26 +73,33 @@ export function Matches() { function MatchesInner() { const router = useRouter() - const _isServer = isServer ?? router.isServer - const matchId = _isServer - ? router.stores.firstId.get() - : // eslint-disable-next-line react-hooks/rules-of-hooks - useStore(router.stores.firstId, (id) => id) - const resetKey = _isServer - ? router.stores.loadedAt.get() - : // eslint-disable-next-line react-hooks/rules-of-hooks - useStore(router.stores.loadedAt, (loadedAt) => loadedAt) + const acknowledgement = router._rendered! + const matches = + (isServer ?? router.isServer) + ? router.stores.matches.get() + : // eslint-disable-next-line react-hooks/rules-of-hooks + useStore( + router.stores.matches, + (value) => acknowledgement[0 /* offered */] ?? value, + ) + const match = matches[0] + const routeId = match?.routeId + + useLayoutEffect(() => { + if (acknowledgement[0 /* offered */] === matches) { + settleOwner(acknowledgement, true) + } + }, [acknowledgement, matches]) - const matchComponent = matchId ? : null + const matchComponent = routeId ? : null return ( - + {router.options.disableGlobalCatchBoundary ? ( matchComponent ) : ( resetKey} - errorComponent={ErrorComponent} + getResetKey={() => match} onCatch={ process.env.NODE_ENV !== 'production' ? (error) => { @@ -143,7 +147,11 @@ export function useMatchRoute() { if (!(isServer ?? router.isServer)) { // eslint-disable-next-line react-hooks/rules-of-hooks - useStore(router.stores.matchRouteDeps, (d) => d) + useStore(router.stores.location, (location) => location.href) + // eslint-disable-next-line react-hooks/rules-of-hooks + useStore(router.stores.resolvedLocation, (location) => location?.href) + // eslint-disable-next-line react-hooks/rules-of-hooks + useStore(router.stores.status, (status) => status) } return React.useCallback( @@ -255,20 +263,8 @@ export function useMatches< } /** - * Read the full array of active route matches or select a derived subset. - * - * Useful for debugging, breadcrumbs, or aggregating metadata across matches. - * - * @returns The array of matches (or the selected value). - * @link https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchesHook - */ - -/** - * Read the full array of active route matches or select a derived subset. - * - * Useful for debugging, breadcrumbs, or aggregating metadata across matches. - * - * @link https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchesHook + * Read the presented route matches above the current match, or select a + * derived value from them. */ export function useParentMatches< TRouter extends AnyRouter = RegisteredRouter, @@ -278,13 +274,13 @@ export function useParentMatches< opts?: UseMatchesBaseOptions & StructuralSharingOption, ): UseMatchesResult { - const contextMatchId = React.useContext(matchContext) + const contextRouteId = React.useContext(matchContext) return useMatches({ select: (matches: Array>) => { matches = matches.slice( 0, - matches.findIndex((d) => d.id === contextMatchId), + matches.findIndex((d) => d.routeId === contextRouteId), ) return opts?.select ? opts.select(matches) : matches }, @@ -293,8 +289,8 @@ export function useParentMatches< } /** - * Read the array of active route matches that are children of the current - * match (or selected parent) in the match tree. + * Read the presented route matches below the current match, or select a + * derived value from them. */ export function useChildMatches< TRouter extends AnyRouter = RegisteredRouter, @@ -304,12 +300,12 @@ export function useChildMatches< opts?: UseMatchesBaseOptions & StructuralSharingOption, ): UseMatchesResult { - const contextMatchId = React.useContext(matchContext) + const contextRouteId = React.useContext(matchContext) return useMatches({ select: (matches: Array>) => { matches = matches.slice( - matches.findIndex((d) => d.id === contextMatchId) + 1, + matches.findIndex((d) => d.routeId === contextRouteId) + 1, ) return opts?.select ? opts.select(matches) : matches }, diff --git a/packages/react-router/src/RouterProvider.tsx b/packages/react-router/src/RouterProvider.tsx index 4846ab17fc..81c3fb4ece 100644 --- a/packages/react-router/src/RouterProvider.tsx +++ b/packages/react-router/src/RouterProvider.tsx @@ -50,8 +50,8 @@ export function RouterContextProvider< } /** - * Top-level component that renders the active route matches and provides the - * router to the React tree via context. + * Renders the current match presentation and provides the router to the React + * tree via context. * * Accepts the same options as `createRouter` via props to update the router * instance after creation. diff --git a/packages/react-router/src/Scripts.tsx b/packages/react-router/src/Scripts.tsx index 5285fbd8db..a1b189d4c6 100644 --- a/packages/react-router/src/Scripts.tsx +++ b/packages/react-router/src/Scripts.tsx @@ -1,5 +1,5 @@ import { useStore } from '@tanstack/react-store' -import { deepEqual } from '@tanstack/router-core' +import { _getAssetMatches, deepEqual } from '@tanstack/router-core' import { isServer } from '@tanstack/router-core/isServer' import { Asset } from './Asset' import { useRouter } from './useRouter' @@ -17,23 +17,38 @@ export const Scripts = () => { const router = useRouter() const nonce = router.options.ssr?.nonce - const getAssetScripts = (matches: Array) => { - const assetScripts: Array = [] + const getScripts = (matches: Array) => { + matches = _getAssetMatches(matches) + const scripts = matches + .flatMap((match) => match.scripts ?? []) + .filter(Boolean) + .map( + ({ children, ...script }) => + ({ + tag: 'script', + attrs: { + ...script, + suppressHydrationWarning: true, + nonce, + }, + children, + }) satisfies RouterManagedTag, + ) as Array const manifest = router.ssr?.manifest if (!manifest) { - return [] + return scripts } for (const match of matches) { - const scripts = manifest.routes[match.routeId]?.scripts + const manifestScripts = manifest.routes[match.routeId]?.scripts - if (!scripts) { + if (!manifestScripts) { continue } - for (const asset of scripts) { - assetScripts.push({ + for (const asset of manifestScripts) { + scripts.push({ tag: 'script', attrs: { ...asset.attrs, nonce }, children: asset.children, @@ -44,64 +59,35 @@ export const Scripts = () => { } } - return assetScripts + return scripts } - const getScripts = (matches: Array): Array => - ( - matches - .map((match) => match.scripts!) - .flat(1) - .filter(Boolean) as Array - ).map( - ({ children, ...script }) => - ({ - tag: 'script', - attrs: { - ...script, - suppressHydrationWarning: true, - nonce, - }, - children, - }) satisfies RouterManagedTag, - ) - if (isServer ?? router.isServer) { const activeMatches = router.stores.matches.get() - const assetScripts = getAssetScripts(activeMatches) const scripts = getScripts(activeMatches) - return renderScripts(router, scripts, assetScripts) + return renderScripts(router, scripts) } - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const assetScripts = useStore( - router.stores.matches, - getAssetScripts, - deepEqual, - ) // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static const scripts = useStore(router.stores.matches, getScripts, deepEqual) - return renderScripts(router, scripts, assetScripts) + return renderScripts(router, scripts) } function renderScripts( router: ReturnType, - scripts: Array, - assetScripts: Array, + scripts: Array, ) { - const allScripts = [...scripts, ...assetScripts] as Array - if ((isServer ?? router.isServer) && router.serverSsr) { const serverBufferedScript = router.serverSsr.takeBufferedScripts() if (serverBufferedScript) { - allScripts.unshift(serverBufferedScript) + scripts.unshift(serverBufferedScript) } } return ( <> - {allScripts.map((asset, i) => ( + {scripts.map((asset, i) => ( ))} diff --git a/packages/react-router/src/Transitioner.tsx b/packages/react-router/src/Transitioner.tsx index bf945571a6..341d09ffcc 100644 --- a/packages/react-router/src/Transitioner.tsx +++ b/packages/react-router/src/Transitioner.tsx @@ -1,43 +1,65 @@ 'use client' import * as React from 'react' -import { batch, useStore } from '@tanstack/react-store' import { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core' -import { useLayoutEffect, usePrevious } from './utils' +import { useLayoutEffect } from './utils' import { useRouter } from './useRouter' +import type { AnyRouter } from '@tanstack/router-core' + +export function settleOwner( + owner: NonNullable, + rendered: boolean, +) { + const settle = owner[1 /* settle */] + owner.length = 0 + settle?.(rendered) +} export function Transitioner() { const router = useRouter() - const mountLoadForRouter = React.useRef({ router, mounted: false }) - - const [isTransitioning, setIsTransitioning] = React.useState(false) - // Track pending state changes - const isLoading = useStore(router.stores.isLoading, (value) => value) - const hasPending = useStore(router.stores.hasPending, (value) => value) - - const previousIsLoading = usePrevious(isLoading) - - const isAnyPending = isLoading || isTransitioning || hasPending - const previousIsAnyPending = usePrevious(isAnyPending) - - const isPagePending = isLoading || hasPending - const previousIsPagePending = usePrevious(isPagePending) - - router.startTransition = (fn: () => void) => { - setIsTransitioning(true) - React.startTransition(() => { - fn() - setIsTransitioning(false) + const acknowledgement = (router._rendered ??= []) + const mounted = + process.env.NODE_ENV !== 'production' + ? // eslint-disable-next-line react-hooks/rules-of-hooks + React.useRef(false) + : undefined + + router.startTransition = (fn, expected) => + new Promise((resolve, reject) => { + settleOwner(acknowledgement, false) + acknowledgement.push(expected, resolve) + React.startTransition(() => { + try { + fn() + } catch (cause) { + if (acknowledgement[1 /* settle */] === resolve) { + acknowledgement.length = 0 + } + reject(cause) + } + }) }) + if (process.env.NODE_ENV !== 'production') { + ;( + router as typeof router & { _cancelTransition?: () => void } + )._cancelTransition = () => settleOwner(acknowledgement, false) } - // Subscribe to location changes - // and try to load the new location - React.useEffect(() => { + // Subscribe before canonicalizing so the initial URL has exactly one load. + useLayoutEffect(() => { const unsub = router.history.subscribe(router.load) + if (mounted?.current) { + return unsub + } + if (mounted) { + mounted.current = true + } + + router.updateLatestLocation() + const location = router.latestLocation const nextLocation = router.buildLocation({ - to: router.latestLocation.pathname, + to: location.pathname, search: true, params: true, hash: true, @@ -46,86 +68,41 @@ export function Transitioner() { }) // Check if the current URL matches the canonical form. - // Compare publicHref (browser-facing URL) for consistency with - // the server-side redirect check in router.beforeLoad. + // Compare publicHref (browser-facing URL) consistently with server + // canonicalization. if ( - trimPathRight(router.latestLocation.publicHref) !== + trimPathRight(location.publicHref) !== trimPathRight(nextLocation.publicHref) ) { - router.commitLocation({ ...nextLocation, replace: true }) - } - - return () => { - unsub() + router.commitLocation({ + ...nextLocation, + replace: true, + ignoreBlocker: true, + }) + return unsub } - }, [router, router.history]) - // Try to load the initial location - useLayoutEffect(() => { + const resolvedLocation = router.stores.resolvedLocation.get() if ( - // if we are hydrating from SSR, loading is triggered in ssr-client - (typeof window !== 'undefined' && router.ssr) || - (mountLoadForRouter.current.router === router && - mountLoadForRouter.current.mounted) + resolvedLocation?.href === location.href && + resolvedLocation.state.__TSR_key === location.state.__TSR_key ) { - return - } - mountLoadForRouter.current = { router, mounted: true } - - const tryLoad = async () => { - try { - await router.load() - } catch (err) { - console.error(err) - } - } - - tryLoad() - }, [router]) - - useLayoutEffect(() => { - // The router was loading and now it's not - if (previousIsLoading && !isLoading) { - router.emit({ - type: 'onLoad', // When the new URL has committed, when the new matches have been loaded into state.matches - ...getLocationChangeInfo( - router.stores.location.get(), - router.stores.resolvedLocation.get(), - ), - }) - } - }, [previousIsLoading, router, isLoading]) - - useLayoutEffect(() => { - // emit onBeforeRouteMount - if (previousIsPagePending && !isPagePending) { - router.emit({ - type: 'onBeforeRouteMount', - ...getLocationChangeInfo( - router.stores.location.get(), - router.stores.resolvedLocation.get(), - ), + acknowledgement.push(router.stores.matches.get(), (rendered) => { + if (rendered) { + router.emit({ + type: 'onRendered', + ...getLocationChangeInfo(resolvedLocation, resolvedLocation), + }) + } }) + } else if (!router._tx) { + router.load().catch(console.error) } - }, [isPagePending, previousIsPagePending, router]) - - useLayoutEffect(() => { - if (previousIsAnyPending && !isAnyPending) { - const changeInfo = getLocationChangeInfo( - router.stores.location.get(), - router.stores.resolvedLocation.get(), - ) - router.emit({ - type: 'onResolved', - ...changeInfo, - }) - batch(() => { - router.stores.status.set('idle') - router.stores.resolvedLocation.set(router.stores.location.get()) - }) - } - }, [isAnyPending, previousIsAnyPending, router]) + return unsub + // `mounted` exists only in development and is a stable ref when present. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router, router.history]) return null } diff --git a/packages/react-router/src/headContentUtils.tsx b/packages/react-router/src/headContentUtils.tsx index 5ed35bd1ce..d61ca829db 100644 --- a/packages/react-router/src/headContentUtils.tsx +++ b/packages/react-router/src/headContentUtils.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { useStore } from '@tanstack/react-store' import { + _getAssetMatches, appendUniqueUserTags, deepEqual, escapeHtml, @@ -22,6 +23,7 @@ function buildTagsFromMatches( matches: Array, assetCrossOrigin?: AssetCrossOriginConfig, ): Array { + matches = _getAssetMatches(matches) const routeMeta = matches .map((match) => match.meta) .filter((meta) => meta !== undefined) @@ -187,7 +189,7 @@ function buildTagsFromMatches( } /** - * Build the list of head/link/meta/script tags to render for active matches. + * Build the head/link/meta/script tags from the renderable presented prefix. * Used internally by `HeadContent`. */ export const useTags = (assetCrossOrigin?: AssetCrossOriginConfig) => { @@ -204,226 +206,11 @@ export const useTags = (assetCrossOrigin?: AssetCrossOriginConfig) => { } // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const routeMeta = useStore( - router.stores.matches, - (matches) => { - return matches - .map((match) => match.meta) - .filter((meta) => meta !== undefined) - }, - deepEqual, + const selectTags = React.useCallback( + (matches: Array) => + buildTagsFromMatches(router, nonce, matches, assetCrossOrigin), + [assetCrossOrigin, nonce, router], ) - - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const meta: Array = React.useMemo(() => { - const resultMeta: Array = [] - const metaByAttribute: Record = {} - let title: RouterManagedTag | undefined - for (let i = routeMeta.length - 1; i >= 0; i--) { - const metas = routeMeta[i]! - for (let j = metas.length - 1; j >= 0; j--) { - const m = metas[j] - if (!m) continue - - if (m.title) { - if (!title) { - title = { - tag: 'title', - children: m.title, - } - } - } else if ('script:ld+json' in m) { - // Handle JSON-LD structured data - // Content is HTML-escaped to prevent XSS when injected via dangerouslySetInnerHTML - try { - const json = JSON.stringify(m['script:ld+json']) - resultMeta.push({ - tag: 'script', - attrs: { - type: 'application/ld+json', - }, - children: escapeHtml(json), - }) - } catch { - // Skip invalid JSON-LD objects - } - } else { - const attribute = m.name ?? m.property - if (attribute) { - if (metaByAttribute[attribute]) { - continue - } else { - metaByAttribute[attribute] = true - } - } - - resultMeta.push({ - tag: 'meta', - attrs: { - ...m, - nonce, - }, - }) - } - } - } - - if (title) { - resultMeta.push(title) - } - - if (nonce) { - resultMeta.push({ - tag: 'meta', - attrs: { - property: 'csp-nonce', - content: nonce, - }, - }) - } - resultMeta.reverse() - - return resultMeta - }, [routeMeta, nonce]) - - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const links = useStore( - router.stores.matches, - (matches) => { - const constructed = matches - .flatMap((match) => match.links ?? []) - .filter((link) => link !== undefined) - .map((link) => ({ - tag: 'link', - attrs: { - ...link, - nonce, - }, - })) satisfies Array - - return constructed - }, - deepEqual, - ) - - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const manifestCssTags = useStore( - router.stores.matches, - (matches) => { - const manifest = router.ssr?.manifest - const tags: Array = [] - - if (!manifest) { - return tags - } - - matches.forEach((match) => { - manifest.routes[match.routeId]?.css?.forEach((link) => { - const resolvedLink = resolveManifestCssLink(link) - tags.push({ - tag: 'link', - attrs: { - rel: 'stylesheet', - ...resolvedLink, - crossOrigin: - getAssetCrossOrigin(assetCrossOrigin, 'stylesheet') ?? - resolvedLink.crossOrigin, - suppressHydrationWarning: true, - nonce, - }, - }) - }) - }) - - if (manifest.inlineStyle) { - tags.push({ - tag: 'style', - attrs: { - ...manifest.inlineStyle.attrs, - nonce, - }, - children: manifest.inlineStyle.children, - inlineCss: true, - }) - } - - return tags - }, - deepEqual, - ) - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const preloadLinks = useStore( - router.stores.matches, - (matches) => { - const preloadLinks: Array = [] - const manifest = router.ssr?.manifest - - if (!manifest) { - return preloadLinks - } - - matches.forEach((match) => { - manifest.routes[match.routeId]?.preloads?.forEach((preload) => { - preloadLinks.push({ - tag: 'link', - attrs: { - ...getScriptPreloadAttrs(manifest, preload, assetCrossOrigin), - nonce, - }, - }) - }) - }) - - return preloadLinks - }, - deepEqual, - ) - - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const styles = useStore( - router.stores.matches, - (matches) => { - return matches - .flatMap((match) => match.styles ?? []) - .filter((style) => style !== undefined) - .map(({ children, ...attrs }) => ({ - tag: 'style', - attrs: { - ...attrs, - nonce, - }, - children: children as string | undefined, - })) satisfies Array - }, - deepEqual, - ) - - // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const headScripts: Array = useStore( - router.stores.matches, - (matches) => { - return matches - .flatMap((match) => match.headScripts ?? []) - .filter((script) => script !== undefined) - .map(({ children, ...script }) => ({ - tag: 'script', - attrs: { - ...script, - nonce, - }, - children: children as string | undefined, - })) satisfies Array - }, - deepEqual, - ) - - const tags: Array = [] - appendUniqueUserTags(tags, meta) - tags.push(...preloadLinks) - appendUniqueUserTags(tags, links) - tags.push(...manifestCssTags) - appendUniqueUserTags(tags, styles) - appendUniqueUserTags(tags, headScripts) - return tags + return useStore(router.stores.matches, selectTags, deepEqual) } diff --git a/packages/react-router/src/lazyRouteComponent.tsx b/packages/react-router/src/lazyRouteComponent.tsx index b4fe8703c5..7c387e0e31 100644 --- a/packages/react-router/src/lazyRouteComponent.tsx +++ b/packages/react-router/src/lazyRouteComponent.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { isModuleNotFoundError } from '@tanstack/router-core' +import { isServer } from '@tanstack/router-core/isServer' import { reactUse } from './utils' import type { AsyncRouteComponent } from './route' @@ -24,42 +25,24 @@ export function lazyRouteComponent< let loadPromise: Promise | undefined let comp: T[TKey] | T['default'] let error: any - let reload: boolean const load = () => { if (!loadPromise) { + error = undefined loadPromise = importer() .then((res) => { - loadPromise = undefined + // Keep browser preload behavior unchanged; SSR can reuse the import. + if (!(isServer ?? typeof window === 'undefined')) { + loadPromise = undefined + } comp = res[exportName ?? 'default'] }) .catch((err) => { + loadPromise = undefined // We don't want an error thrown from preload in this case, because // there's nothing we want to do about module not found during preload. // Record the error, the rest is handled during the render path. error = err - // If the load fails due to module not found, it may mean a new version of - // the build was deployed and the user's browser is still using an old version. - // If this happens, the old version in the user's browser would have an outdated - // URL to the lazy module. - // In that case, we want to attempt one window refresh to get the latest. - if (isModuleNotFoundError(error)) { - if ( - error instanceof Error && - typeof window !== 'undefined' && - typeof sessionStorage !== 'undefined' - ) { - // Again, we want to reload one time on module not found error and not enter - // a reload loop if there is some other issue besides an old deploy. - // That's why we store our reload attempt in sessionStorage. - // Use error.message as key because it contains the module path that failed. - const storageKey = `tanstack_router_reload:${error.message}` - if (!sessionStorage.getItem(storageKey)) { - sessionStorage.setItem(storageKey, '1') - reload = true - } - } - } }) } @@ -67,15 +50,23 @@ export function lazyRouteComponent< } const lazyComp = function Lazy(props: any) { - // Now that we're out of preload and into actual render path, - if (reload) { - // If it was a module loading error, - // throw eternal suspense while we wait for window to reload - window.location.reload() - throw new Promise(() => {}) - } if (error) { - // Otherwise, just throw the error + // A missing module can mean that a newer deployment replaced the URL. + // Reload only for the error that is still current at render time, so a + // successful retry cannot leave a stale reload request armed. + if ( + isModuleNotFoundError(error) && + !(isServer ?? typeof window === 'undefined') && + typeof sessionStorage !== 'undefined' + ) { + const storageKey = `tanstack_router_reload:${error.message}` + if (!sessionStorage.getItem(storageKey)) { + sessionStorage.setItem(storageKey, '1') + window.location.reload() + // Suspend forever while the document reloads. + throw new Promise(() => {}) + } + } throw error } diff --git a/packages/react-router/src/ssr/RouterClient.tsx b/packages/react-router/src/ssr/RouterClient.tsx index 3271dcd63f..69abc97f0b 100644 --- a/packages/react-router/src/ssr/RouterClient.tsx +++ b/packages/react-router/src/ssr/RouterClient.tsx @@ -3,16 +3,11 @@ import { Await } from '../awaited' import { RouterProvider } from '../RouterProvider' import type { AnyRouter } from '@tanstack/router-core' -let hydrationPromise: Promise>> | undefined +let hydrationPromise: Promise | undefined export function RouterClient(props: { router: AnyRouter }) { - if (!hydrationPromise) { - if (!props.router.stores.matchesId.get().length) { - hydrationPromise = hydrate(props.router) - } else { - hydrationPromise = Promise.resolve() - } - } + hydrationPromise ??= hydrate(props.router).finally(() => window.$_TSR!.h()) + return ( stream.cancel().catch(() => {}) }, + { + signal: request.signal, + onAbort: () => stream.cancel().catch(() => {}), + }, ) return createSsrStreamResponse( router, new Response(responseStream as any, { - status: router.stores.statusCode.get(), + status: + router._serverResult?.type === 'render' + ? router._serverResult.status + : 200, headers: responseHeaders, }), ) @@ -177,7 +183,7 @@ export const renderRouterToStream = async ({ const responseStream = transformPipeableStreamWithRouter( router, reactAppPassthrough, - { onAbort: abortPipeable }, + { signal: request.signal, onAbort: abortPipeable }, ) responseAttached = true @@ -199,7 +205,10 @@ export const renderRouterToStream = async ({ return createSsrStreamResponse( router, new Response(responseStream as any, { - status: router.stores.statusCode.get(), + status: + router._serverResult?.type === 'render' + ? router._serverResult.status + : 200, headers: responseHeaders, }), ) diff --git a/packages/react-router/src/ssr/renderRouterToString.tsx b/packages/react-router/src/ssr/renderRouterToString.tsx index e9fe4ec779..5e299cc215 100644 --- a/packages/react-router/src/ssr/renderRouterToString.tsx +++ b/packages/react-router/src/ssr/renderRouterToString.tsx @@ -21,7 +21,10 @@ export const renderRouterToString = async ({ } return new Response(`${html}`, { - status: router.stores.statusCode.get(), + status: + router._serverResult?.type === 'render' + ? router._serverResult.status + : 200, headers: responseHeaders, }) } catch (error) { diff --git a/packages/react-router/src/useMatch.tsx b/packages/react-router/src/useMatch.tsx index 6c1d03421d..35d6673962 100644 --- a/packages/react-router/src/useMatch.tsx +++ b/packages/react-router/src/useMatch.tsx @@ -20,12 +20,7 @@ import type { ThrowOrOptional, } from '@tanstack/router-core' -const dummyStore = { - get() {}, - subscribe() { - return { unsubscribe() {} } - }, -} as any +const dummyMatch = {} export function useStructuralSharing< TRouter extends AnyRouter, @@ -147,16 +142,15 @@ export function useMatch< >, ): ThrowOrOptional, TThrow> { const router = useRouter() - const nearestMatchId = React.useContext( + const nearestRouteId = React.useContext( opts.from ? dummyMatchContext : matchContext, ) - const matchStore = opts.from - ? router.stores.getRouteMatchStore(opts.from) - : router.stores.matchStores.get(nearestMatchId!) + const routeId = opts.from ?? nearestRouteId + const matchStore = router.stores.getMatchStore(routeId!) if (isServer ?? router.isServer) { - const match = matchStore?.get() + const match = matchStore.get() if (!match) { if (opts.shouldThrow ?? true) { if (process.env.NODE_ENV !== 'production') { @@ -179,12 +173,12 @@ export function useMatch< useStructuralSharing(opts, router) // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static - const matchSelection = useStore(matchStore ?? dummyStore, (match) => - match ? selector(match as any) : dummyStore, + const matchSelection = useStore(matchStore, (match) => + match ? selector(match as any) : dummyMatch, ) - if (matchSelection !== dummyStore) { - return matchSelection + if (matchSelection !== dummyMatch) { + return matchSelection as any } if (opts.shouldThrow ?? true) { diff --git a/packages/react-router/src/utils.ts b/packages/react-router/src/utils.ts index f3cbf54613..ca0653e350 100644 --- a/packages/react-router/src/utils.ts +++ b/packages/react-router/src/utils.ts @@ -1,5 +1,6 @@ 'use client' import * as React from 'react' +import { isServer } from '@tanstack/router-core/isServer' // Safe version of React.use() that will not cause compilation errors against // React 18 with Webpack, which statically analyzes imports and fails when it @@ -27,7 +28,9 @@ export function useStableCallback) => any>( } export const useLayoutEffect = - typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect + (isServer ?? typeof window === 'undefined') + ? React.useEffect + : React.useLayoutEffect /** * Taken from https://www.developerway.com/posts/implementing-advanced-use-previous-hook#part3 diff --git a/packages/react-router/tests/Matches.test.tsx b/packages/react-router/tests/Matches.test.tsx index dd3ca3dfa6..33153a4e11 100644 --- a/packages/react-router/tests/Matches.test.tsx +++ b/packages/react-router/tests/Matches.test.tsx @@ -1,6 +1,14 @@ import { afterEach, describe, expect, test } from 'vitest' -import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' import { createMemoryHistory } from '@tanstack/history' +import { createControlledPromise } from '@tanstack/router-core' import { Link, Outlet, @@ -126,11 +134,11 @@ test('when filtering useMatches by loaderData', async () => { test('should show pendingComponent of root route', async () => { const root = createRootRoute({ - pendingComponent: () =>
, + pendingComponent: () =>
, loader: async () => { await new Promise((r) => setTimeout(r, 50)) }, - component: () =>
, + component: () =>
, }) const router = createRouter({ @@ -145,6 +153,134 @@ test('should show pendingComponent of root route', async () => { expect(await rendered.findByTestId('root-content')).toBeInTheDocument() }) +test('useMatchRoute follows superseding pending locations', async () => { + const aGate = createControlledPromise() + const bGate = createControlledPromise() + + function Layout() { + const matchRoute = useMatchRoute() + return ( +
+ + {String(Boolean(matchRoute({ to: '/a', pending: true })))} + + + {String(Boolean(matchRoute({ to: '/b', pending: true })))} + + + {String(Boolean(matchRoute({ to: '/b', pending: false })))} + + +
+ ) + } + + const root = createRootRoute({ component: Layout }) + const index = createRoute({ + getParentRoute: () => root, + path: '/', + }) + const a = createRoute({ + getParentRoute: () => root, + path: '/a', + loader: () => aGate, + }) + const b = createRoute({ + getParentRoute: () => root, + path: '/b', + loader: () => bGate, + }) + const router = createRouter({ + routeTree: root.addChildren([index, a, b]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + render() + + await waitFor(() => { + expect(screen.getByTestId('pending-a')).toHaveTextContent('false') + expect(router.stores.status.get()).toBe('idle') + expect(router.stores.resolvedLocation.get()?.pathname).toBe('/') + }) + + let navigationA!: Promise + act(() => { + navigationA = router.navigate({ to: '/a' }) + }) + await waitFor(() => { + expect(screen.getByTestId('pending-a')).toHaveTextContent('true') + expect(screen.getByTestId('resolved-b')).toHaveTextContent('false') + }) + + let navigationB!: Promise + act(() => { + navigationB = router.navigate({ to: '/b' }) + }) + await waitFor(() => { + expect(screen.getByTestId('pending-a')).toHaveTextContent('false') + expect(screen.getByTestId('pending-b')).toHaveTextContent('true') + expect(screen.getByTestId('resolved-b')).toHaveTextContent('false') + }) + + await act(async () => { + aGate.resolve() + bGate.resolve() + await Promise.allSettled([navigationA, navigationB]) + }) + await waitFor(() => { + expect(screen.getByTestId('pending-b')).toHaveTextContent('false') + expect(screen.getByTestId('resolved-b')).toHaveTextContent('true') + }) +}) + +test('legacy notFoundRoute drops a stale parent layout after navigation', async () => { + let legacyLoads = 0 + const root = createRootRoute({ component: Outlet }) + const parent = createRoute({ + getParentRoute: () => root, + path: '/parent', + component: () => ( +
+ Parent layout + +
+ ), + }) + const known = createRoute({ + getParentRoute: () => parent, + path: '/known', + }) + const legacyNotFound = createRoute({ + getParentRoute: () => root, + path: '/404', + loader: () => { + legacyLoads++ + return 'not found' + }, + component: () =>
Legacy not found
, + staleTime: Infinity, + gcTime: Infinity, + }) + const router = createRouter({ + routeTree: root.addChildren([parent.addChildren([known])]), + history: createMemoryHistory({ initialEntries: ['/parent/missing'] }), + notFoundRoute: legacyNotFound, + }) + + const rendered = render() + expect(await rendered.findByText('Parent layout')).toBeInTheDocument() + expect(await rendered.findByText('Legacy not found')).toBeInTheDocument() + expect(legacyLoads).toBe(1) + + await act(async () => { + await router.navigate({ to: '/missing' } as any) + }) + + expect(rendered.queryByText('Parent layout')).not.toBeInTheDocument() + expect(await rendered.findByText('Legacy not found')).toBeInTheDocument() + expect(legacyLoads).toBe(1) + rendered.unmount() +}) + describe('matching on different param types', () => { const testCases = [ { diff --git a/packages/react-router/tests/Scripts.test.tsx b/packages/react-router/tests/Scripts.test.tsx index b7605c9402..893cef01bc 100644 --- a/packages/react-router/tests/Scripts.test.tsx +++ b/packages/react-router/tests/Scripts.test.tsx @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from 'vitest' +import { afterEach, describe, expect, test, vi } from 'vitest' import { act, cleanup, @@ -9,11 +9,14 @@ import { } from '@testing-library/react' import { createPortal } from 'react-dom' import ReactDOMServer from 'react-dom/server' +import { hydrate } from '@tanstack/router-core/ssr/client' +import { dehydrateSsrMatchId } from '../../router-core/src/ssr/ssr-match-id' import { HeadContent, Link, Outlet, + RouterContextProvider, RouterProvider, createBrowserHistory, createMemoryHistory, @@ -57,6 +60,7 @@ afterEach(() => { cleanup() browserHistories.splice(0).forEach((history) => history.destroy()) window.history.replaceState(null, 'root', '/') + delete window.$_TSR }) describe('ssr scripts', () => { @@ -158,15 +162,12 @@ describe('ssr scripts', () => { { src: 'script3.js' }, ]) - const { container } = await act(() => - render(), - ) - expect(await screen.findByTestId('root')).toBeInTheDocument() - expect(await screen.findByTestId('index')).toBeInTheDocument() - - expect(container.innerHTML).toEqual( - `
root
index
`, + const html = ReactDOMServer.renderToString( + , ) + expect(html).toContain('') + expect(html).toContain('') + expect(html).not.toContain('script2.js') }) }) @@ -337,6 +338,119 @@ describe('scripts with async/defer attributes', () => { }) describe('ssr HeadContent', () => { + test('renders descendant assets during a data-only hydration handoff', async () => { + const rootRoute = createRootRoute({}) + const dataOnlyRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/report', + ssr: 'data-only', + loader: () => 'report', + }) + const childRoute = createRoute({ + getParentRoute: () => dataOnlyRoute, + path: '/details', + loader: () => 'details', + head: () => ({ + meta: [{ name: 'data-only-child', content: 'visible' }], + links: [{ rel: 'preload', href: '/data-only-head-link.js' }], + styles: [ + { + id: 'data-only-route-style', + children: '.data-only-child { color: green }', + }, + ], + scripts: [ + { + id: 'data-only-head-script', + type: 'application/json', + children: '{"source":"head"}', + }, + ], + }), + scripts: () => [ + { + id: 'data-only-body-script', + type: 'application/json', + children: '{"source":"body"}', + }, + ], + }) + const router = createRouter({ + history: createMemoryHistory({ + initialEntries: ['/report/details'], + }), + routeTree: rootRoute.addChildren([ + dataOnlyRoute.addChildren([childRoute]), + ]), + }) + const matches = router.matchRoutes(router.latestLocation) + window.$_TSR = { + router: { + dehydratedData: {}, + manifest: { + routes: { + [childRoute.id]: { + css: ['/data-only-manifest.css'], + preloads: ['/data-only-manifest.js'], + scripts: [ + { + attrs: { + id: 'data-only-manifest-script', + type: 'application/json', + }, + children: '{"source":"manifest"}', + }, + ], + }, + }, + }, + matches: matches.map((match, index) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success', + ssr: index === 1 ? 'data-only' : true, + l: index ? (index === 1 ? 'report' : 'details') : undefined, + u: Date.now(), + })), + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + } + + await hydrate(router) + + expect(router.state.matches.map((match) => match.status)).toEqual([ + 'success', + 'pending', + 'success', + ]) + render( + + + + , + ) + + expect( + document.querySelector('meta[name="data-only-child"]'), + ).not.toBeNull() + expect( + document.querySelector('link[href="/data-only-head-link.js"]'), + ).not.toBeNull() + expect(document.querySelector('#data-only-route-style')).not.toBeNull() + expect(document.querySelector('#data-only-head-script')).not.toBeNull() + expect(document.querySelector('#data-only-body-script')).not.toBeNull() + expect( + document.querySelector('link[href="/data-only-manifest.css"]'), + ).not.toBeNull() + expect( + document.querySelector('link[href="/data-only-manifest.js"]'), + ).not.toBeNull() + expect(document.querySelector('#data-only-manifest-script')).not.toBeNull() + }) + test('derives title, dedupes meta, and allows non-loader HeadContent', async () => { const rootRoute = createRootRoute({ loader: () => @@ -839,6 +953,130 @@ describe('ssr HeadContent', () => { ), ).toHaveLength(1) }) + + test('does not render retained descendant assets past a terminal parent boundary', async () => { + let failParent = false + const childHeadScript = '{"source":"child-head"}' + const childBodyScript = '{"source":"child-body"}' + const childManifestScript = '{"source":"child-manifest"}' + const childPreload = '/terminal-child-preload.js' + const childHeadLink = '/terminal-child-head-link.js' + const rootRoute = createRootRoute({ + component: () => ( + <> + {createPortal(, document.head)} + + + + ), + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + shouldReload: true, + loader: () => { + if (failParent) { + throw new Error('parent failed') + } + }, + component: Outlet, + errorComponent: () =>
Parent error
, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + head: () => ({ + meta: [{ name: 'terminal-child', content: 'visible' }], + links: [{ rel: 'preload', href: childHeadLink }], + styles: [{ children: '.terminal-child { color: red }' }], + scripts: [{ type: 'application/ld+json', children: childHeadScript }], + }), + scripts: () => [ + { type: 'application/ld+json', children: childBodyScript }, + ], + component: () =>
Child content
, + }) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + }) + router.ssr = { + manifest: { + routes: { + [childRoute.id]: { + preloads: [childPreload], + scripts: [ + { + attrs: { type: 'application/ld+json' }, + children: childManifestScript, + }, + ], + }, + }, + }, + } + + await router.load() + await act(() => render()) + + await waitFor(() => { + expect( + document.head.querySelector('meta[name="terminal-child"]'), + ).not.toBeNull() + expect( + document.head.querySelector(`link[href="${childPreload}"]`), + ).not.toBeNull() + expect( + document.head.querySelector(`link[href="${childHeadLink}"]`), + ).not.toBeNull() + expect(document.head.textContent).toContain( + '.terminal-child { color: red }', + ) + expect(document.documentElement.textContent).toContain(childHeadScript) + expect(document.documentElement.textContent).toContain(childBodyScript) + expect(document.documentElement.textContent).toContain( + childManifestScript, + ) + }) + + failParent = true + await act(() => router.invalidate()) + await screen.findByText('Parent error') + + expect(router.state.matches).toHaveLength(3) + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + }) + expect(router.state.matches[2]).toMatchObject({ + routeId: childRoute.id, + meta: [{ name: 'terminal-child', content: 'visible' }], + scripts: [{ type: 'application/ld+json', children: childBodyScript }], + }) + await waitFor(() => { + expect( + document.head.querySelector('meta[name="terminal-child"]'), + ).toBeNull() + expect( + document.head.querySelector(`link[href="${childPreload}"]`), + ).toBeNull() + expect( + document.head.querySelector(`link[href="${childHeadLink}"]`), + ).toBeNull() + expect(document.head.textContent).not.toContain( + '.terminal-child { color: red }', + ) + expect(document.documentElement.textContent).not.toContain( + childHeadScript, + ) + expect(document.documentElement.textContent).not.toContain( + childBodyScript, + ) + expect(document.documentElement.textContent).not.toContain( + childManifestScript, + ) + }) + }) }) describe('data script rendering', () => { diff --git a/packages/react-router/tests/ancestor-loader-child-pending-min.test.tsx b/packages/react-router/tests/ancestor-loader-child-pending-min.test.tsx new file mode 100644 index 0000000000..09945bb9af --- /dev/null +++ b/packages/react-router/tests/ancestor-loader-child-pending-min.test.tsx @@ -0,0 +1,157 @@ +import * as React from 'react' +import { act, cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + Outlet, + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(() => { + cleanup() + vi.useRealTimers() +}) + +test('a child fallback revealed after a fresh ancestor loader keeps its own pendingMinMs', async () => { + vi.useFakeTimers() + + const parentLoader = createControlledPromise() + const childLoader = createControlledPromise() + const rootRoute = createRootRoute({ + component: () => , + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Index
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => parentLoader, + component: () => , + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Child pending
, + loader: () => childLoader, + component: () =>
Child content
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPendingMs: 0, + }) + + await router.load() + render() + expect(screen.getByText('Index')).toBeInTheDocument() + + let navigation!: Promise + await act(async () => { + navigation = router.navigate({ to: '/parent/child' }) + await vi.advanceTimersByTimeAsync(25) + }) + + expect(screen.queryByText('Child pending')).not.toBeInTheDocument() + + await act(async () => { + parentLoader.resolve() + await vi.advanceTimersByTimeAsync(0) + }) + expect(screen.getByText('Child pending')).toBeInTheDocument() + expect(screen.queryByText('Child content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(25) + childLoader.resolve() + await Promise.resolve() + }) + + // The minimum is measured from the child's first visible frame, not from + // the navigation start or the ancestor's completion. + await act(async () => { + await vi.advanceTimersByTimeAsync(74) + }) + expect(screen.getByText('Child pending')).toBeInTheDocument() + expect(screen.queryByText('Child content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + await navigation + }) + expect(screen.queryByText('Child pending')).not.toBeInTheDocument() + expect(screen.getByText('Child content')).toBeInTheDocument() +}) + +test('advancing from a parent loader to a parallel child loader does not restart pendingMs', async () => { + vi.useFakeTimers() + + const parentLoader = createControlledPromise() + const childLoader = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Index
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => parentLoader, + component: Outlet, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + pendingMs: 1_000, + pendingMinMs: 0, + pendingComponent: () =>
Child pending
, + loader: () => childLoader, + component: () =>
Child content
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + render() + + let navigation!: Promise + await act(async () => { + navigation = router.navigate({ to: '/parent/child' }) + await vi.advanceTimersByTimeAsync(900) + }) + expect(screen.queryByText('Child pending')).not.toBeInTheDocument() + + await act(async () => { + parentLoader.resolve() + await vi.advanceTimersByTimeAsync(99) + }) + expect(screen.queryByText('Child pending')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + }) + expect(screen.getByText('Child pending')).toBeInTheDocument() + + await act(async () => { + childLoader.resolve() + await navigation + }) + expect(screen.getByText('Child content')).toBeInTheDocument() +}) diff --git a/packages/react-router/tests/component-preload-retry-pending-min.test.tsx b/packages/react-router/tests/component-preload-retry-pending-min.test.tsx new file mode 100644 index 0000000000..dcdd94af67 --- /dev/null +++ b/packages/react-router/tests/component-preload-retry-pending-min.test.tsx @@ -0,0 +1,150 @@ +import * as React from 'react' +import { act } from 'react' +import { afterEach, expect, test, vi } from 'vitest' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { createControlledPromise } from '@tanstack/router-core' +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + useRouter, +} from '../src' +import type { ErrorComponentProps } from '../src' + +afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + cleanup() +}) + +test('delayed component preload reveals pending UI', async () => { + const componentGate = createControlledPromise() + const Page = Object.assign(() =>
Page content
, { + preload: () => componentGate, + }) + const rootRoute = createRootRoute({}) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + pendingMs: 10, + pendingComponent: () =>
Loading page...
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render() + + expect(await screen.findByText('Loading page...')).toBeInTheDocument() + componentGate.resolve() + expect(await screen.findByText('Page content')).toBeInTheDocument() + expect(screen.queryByText('Loading page...')).not.toBeInTheDocument() +}) + +/** + * A component-only route can fail while preloading its code, then retry from + * its error UI through invalidate(). The retry is a fresh pending generation: + * its fallback must remain visible until the retried component preload is + * ready and pendingMinMs has elapsed. + */ +test('component preload retry remains pending through pendingMinMs', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + const retryChunk = createControlledPromise() + let preloadAttempt = 0 + let retryInvalidation!: Promise + let retrySettled = false + + const Page = Object.assign( + () =>
Page content
, + { + preload: vi.fn(() => { + preloadAttempt++ + return preloadAttempt === 1 + ? Promise.reject(new Error('initial chunk request failed')) + : retryChunk + }), + }, + ) + + function RetryError({ reset }: ErrorComponentProps) { + const router = useRouter() + return ( + + ) + } + + const rootRoute = createRootRoute({}) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + errorComponent: RetryError, + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () => ( +
Loading page...
+ ), + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render() + + expect( + await screen.findByRole('button', { name: 'Retry chunk' }), + ).toBeInTheDocument() + expect(Page.preload).toHaveBeenCalledTimes(1) + + vi.useFakeTimers() + fireEvent.click(screen.getByRole('button', { name: 'Retry chunk' })) + + // Let pendingMs: 0 publish the retry lane. + await act(async () => { + await vi.advanceTimersByTimeAsync(0) + }) + expect(Page.preload).toHaveBeenCalledTimes(2) + expect(screen.getByTestId('page-pending')).toBeInTheDocument() + expect( + screen.queryByRole('button', { name: 'Retry chunk' }), + ).not.toBeInTheDocument() + + await act(async () => { + retryChunk.resolve() + await Promise.resolve() + }) + + expect.soft(retrySettled).toBe(false) + expect.soft(screen.queryByTestId('page-pending')).toBeInTheDocument() + expect.soft(screen.queryByTestId('page-content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(99) + }) + expect(screen.getByTestId('page-pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + await retryInvalidation + }) + + expect(screen.getByTestId('page-content')).toBeInTheDocument() + expect(screen.queryByTestId('page-pending')).not.toBeInTheDocument() +}) diff --git a/packages/react-router/tests/component-preload-retry.test.tsx b/packages/react-router/tests/component-preload-retry.test.tsx new file mode 100644 index 0000000000..ff33ec4bea --- /dev/null +++ b/packages/react-router/tests/component-preload-retry.test.tsx @@ -0,0 +1,128 @@ +import * as React from 'react' +import { afterEach, expect, test, vi } from 'vitest' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { createControlledPromise } from '@tanstack/router-core' +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + lazyRouteComponent, + useRouter, +} from '../src' +import type { ErrorComponentProps } from '../src' + +afterEach(() => { + cleanup() + vi.restoreAllMocks() + vi.unstubAllGlobals() + sessionStorage.clear() +}) + +test('a successful server component download is reused', async () => { + vi.stubGlobal('window', undefined) + const importer = vi.fn().mockResolvedValue({ default: () => null }) + const Page = lazyRouteComponent(importer) + + await Page.preload?.() + await Page.preload?.() + expect(importer).toHaveBeenCalledTimes(1) +}) + +test('concurrent component preloads share the import', async () => { + const componentImport = createControlledPromise<{ + default: () => null + }>() + const importer = vi.fn(() => componentImport) + const Page = lazyRouteComponent(importer) + + const first = Page.preload?.() + const second = Page.preload?.() + expect(importer).toHaveBeenCalledOnce() + + componentImport.resolve({ default: () => null }) + await Promise.all([first, second]) +}) + +test('a failed component download is retried from the route error UI', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + const PageContent = () =>
Page content
+ const importer = vi + .fn<() => Promise<{ default: typeof PageContent }>>() + .mockRejectedValueOnce(new Error('component download failed')) + .mockResolvedValue({ default: PageContent }) + const Page = lazyRouteComponent(importer) + + function RouteError({ reset }: ErrorComponentProps) { + const router = useRouter() + return ( + + ) + } + + const rootRoute = createRootRoute() + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + errorComponent: RouteError, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render() + + const retryButton = await screen.findByRole('button', { name: 'Retry' }) + expect(importer).toHaveBeenCalledTimes(1) + + fireEvent.click(retryButton) + + expect(await screen.findByText('Page content')).toBeInTheDocument() + expect(importer).toHaveBeenCalledTimes(2) + expect( + screen.queryByRole('button', { name: 'Retry' }), + ).not.toBeInTheDocument() +}) + +test('renders after retrying a module download that failed during preload', async () => { + const PageContent = () =>
Page content
+ const importer = vi + .fn<() => Promise<{ default: typeof PageContent }>>() + .mockRejectedValueOnce( + new TypeError( + 'Failed to fetch dynamically imported module: /assets/page.js', + ), + ) + .mockResolvedValue({ default: PageContent }) + const Page = lazyRouteComponent(importer) + + await Page.preload?.() + + const rootRoute = createRootRoute() + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render() + + expect(await screen.findByText('Page content')).toBeInTheDocument() + expect(importer).toHaveBeenCalledTimes(2) +}) diff --git a/packages/react-router/tests/errorComponent.test.tsx b/packages/react-router/tests/errorComponent.test.tsx index 0779c2e8c4..b5ee4b6709 100644 --- a/packages/react-router/tests/errorComponent.test.tsx +++ b/packages/react-router/tests/errorComponent.test.tsx @@ -1,12 +1,13 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' -import ReactDOMServer from 'react-dom/server' import { + HeadContent, Link, Outlet, RouterProvider, createBrowserHistory, + createControlledPromise, createLazyRoute, createMemoryHistory, createRootRoute, @@ -14,6 +15,11 @@ import { createRouter, notFound, } from '../src' +import { + RouterServer, + createRequestHandler, + renderRouterToString, +} from '../src/ssr/server' import type { ErrorComponentProps, RouterHistory } from '../src' function MyErrorComponent(props: ErrorComponentProps) { @@ -267,6 +273,109 @@ describe.each([true, false])( }, ) +test('global catch boundary resets when a background child generation recovers', async () => { + const refresh = createControlledPromise() + let loaderCalls = 0 + const rootRoute = createRootRoute({ component: Outlet }) + const childRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: { + staleReloadMode: 'background', + handler: () => (++loaderCalls === 1 ? 1 : refresh), + }, + component: () => { + const revision = childRoute.useLoaderData() + if (revision === 1) { + throw new Error('stale child render failed') + } + return
Recovered child revision {revision}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history, + }) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) + + render() + expect( + await screen.findByText('stale child render failed'), + ).toBeInTheDocument() + + const invalidation = router.invalidate() + await vi.waitFor(() => expect(loaderCalls).toBe(2)) + expect(screen.getByText('stale child render failed')).toBeInTheDocument() + expect(screen.queryByText(/Recovered child revision/)).not.toBeInTheDocument() + refresh.resolve(2) + await invalidation + + expect( + await screen.findByText('Recovered child revision 2'), + ).toBeInTheDocument() + expect( + screen.queryByText('stale child render failed'), + ).not.toBeInTheDocument() +}) + +test('ancestor route errorComponent resets when a background child generation recovers', async () => { + const refresh = createControlledPromise() + let loaderCalls = 0 + const rootRoute = createRootRoute({ + component: Outlet, + errorComponent: ({ error }) =>
Ancestor error: {error.message}
, + }) + const childRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: { + staleReloadMode: 'background', + handler: () => (++loaderCalls === 1 ? 1 : refresh), + }, + component: () => { + const revision = childRoute.useLoaderData() + if (revision === 1) { + throw new Error('stale child render failed') + } + return
Recovered child revision {revision}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history, + }) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) + + let invalidation: Promise | undefined + try { + render() + expect( + await screen.findByText('Ancestor error: stale child render failed'), + ).toBeInTheDocument() + + invalidation = router.invalidate({ + filter: (match) => match.routeId === childRoute.id, + }) + await vi.waitFor(() => expect(loaderCalls).toBe(2)) + expect( + screen.getByText('Ancestor error: stale child render failed'), + ).toBeInTheDocument() + refresh.resolve(2) + await invalidation + + expect( + await screen.findByText('Recovered child revision 2'), + ).toBeInTheDocument() + } finally { + refresh.resolve(2) + if (invalidation) { + await Promise.allSettled([invalidation]) + } + } +}) + test('errorComponent receives primitive errors thrown from beforeLoad', async () => { const rootRoute = createRootRoute() const indexRoute = createRoute({ @@ -313,8 +422,47 @@ test('errorComponent receives primitive errors thrown from beforeLoad', async () expect(screen.queryByText('About route content')).not.toBeInTheDocument() }) +test.each(['beforeLoad', 'loader'] as const)( + 'a Promise synchronously thrown from %s renders the route error UI', + async (hook) => { + const thrown = Promise.resolve('not route data') + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + beforeLoad: + hook === 'beforeLoad' + ? () => { + throw thrown + } + : undefined, + loader: + hook === 'loader' + ? () => { + throw thrown + } + : undefined, + errorComponent: ({ error }) => ( +
+ {error instanceof Error && error.cause === thrown + ? 'Promise route error' + : 'Wrong route error'} +
+ ), + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + + expect(await screen.findByText('Promise route error')).toBeInTheDocument() + expect(screen.queryByText('Wrong route error')).not.toBeInTheDocument() + }, +) + test('SSR errorComponent receives primitive errors thrown from beforeLoad', async () => { - const history = createMemoryHistory({ initialEntries: ['/about'] }) const rootRoute = createRootRoute({ component: function Root() { return @@ -330,20 +478,301 @@ test('SSR errorComponent receives primitive errors thrown from beforeLoad', asyn errorComponent: ({ error }) =>
Error: {String(error)}
, }) - const router = createRouter({ - routeTree: rootRoute.addChildren([aboutRoute]), - history, + const handler = createRequestHandler({ + request: new Request('http://localhost/about'), + createRouter: () => + createRouter({ + routeTree: rootRoute.addChildren([aboutRoute]), + isServer: true, + }), }) - router.isServer = true - await router.load() + const response = await handler(({ router, responseHeaders }) => + renderRouterToString({ + router, + responseHeaders, + children: , + }), + ) - expect(router.state.statusCode).toBe(500) - const html = ReactDOMServer.renderToString() + expect(response.status).toBe(500) + const html = await response.text() expect(html).toContain('Error:') expect(html).toContain('primitive error thrown') }) +test('a later fresh ancestor loader failure owns the reachable boundary', async () => { + const parentStarted = createControlledPromise() + const childStarted = createControlledPromise() + const parentGate = createControlledPromise() + const childGate = createControlledPromise() + const childSettled = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + component: Outlet, + loader: async () => { + parentStarted.resolve() + await parentGate + throw new Error('later parent failure') + }, + errorComponent: () =>
Parent error boundary
, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childStarted.resolve() + await childGate + childSettled.resolve() + throw new Error('first child failure') + }, + errorComponent: () =>
Child error boundary
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + render() + + let navigation!: Promise + await act(async () => { + navigation = router.navigate({ to: '/parent/child' }) + await Promise.all([parentStarted, childStarted]) + }) + await act(async () => { + childGate.resolve() + await childSettled + parentGate.resolve() + await navigation + }) + + expect(screen.getByText('Parent error boundary')).toBeInTheDocument() + expect(screen.queryByText('Child error boundary')).not.toBeInTheDocument() +}) + +test('a fresh ancestor failure waits for lazy options before rendering its error', async () => { + const parentStarted = createControlledPromise() + const childStarted = createControlledPromise() + const parentGate = createControlledPromise() + const childGate = createControlledPromise() + const childSettled = createControlledPromise() + const lazyParentOptions = createLazyRoute('/parent')({ + component: () => ( + <> +
Lazy parent shell
+ + + ), + }) + const parentChunk = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: async () => { + parentStarted.resolve() + await parentGate + throw new Error('later parent failure') + }, + errorComponent: () =>
Parent error boundary
, + }).lazy(() => parentChunk) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childStarted.resolve() + await childGate + childSettled.resolve() + throw new Error('first child failure') + }, + errorComponent: () =>
Child error boundary
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + render() + + let navigation: Promise | undefined + try { + await act(async () => { + navigation = router.navigate({ to: '/parent/child' }) + await Promise.all([parentStarted, childStarted]) + }) + await act(async () => { + childGate.resolve() + await childSettled + parentGate.resolve() + await Promise.resolve() + }) + + expect(parentChunk.status).toBe('pending') + expect(screen.getByText('Home')).toBeInTheDocument() + expect(screen.queryByText('Parent error boundary')).not.toBeInTheDocument() + expect(screen.queryByText('Child error boundary')).not.toBeInTheDocument() + expect(screen.queryByText('Lazy parent shell')).not.toBeInTheDocument() + } finally { + await act(async () => { + childGate.resolve() + parentGate.resolve() + parentChunk.resolve(lazyParentOptions) + await navigation + }) + } + + expect(screen.getByText('Parent error boundary')).toBeInTheDocument() + expect(screen.queryByText('Lazy parent shell')).not.toBeInTheDocument() + expect(screen.queryByText('Child error boundary')).not.toBeInTheDocument() +}) + +test('SSR renders a later fresh ancestor loader failure', async () => { + const parentStarted = createControlledPromise() + const childStarted = createControlledPromise() + const parentGate = createControlledPromise() + const childGate = createControlledPromise() + const childSettled = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + component: Outlet, + loader: async () => { + parentStarted.resolve() + await parentGate + throw new Error('later parent failure') + }, + errorComponent: () =>
Parent error boundary
, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childStarted.resolve() + await childGate + childSettled.resolve() + throw new Error('first child failure') + }, + errorComponent: () =>
Child error boundary
, + }) + const handler = createRequestHandler({ + request: new Request('http://localhost/parent/child'), + createRouter: () => + createRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + ]), + isServer: true, + }), + }) + + const responsePromise = handler(({ router, responseHeaders }) => + renderRouterToString({ + router, + responseHeaders, + children: , + }), + ) + await Promise.all([parentStarted, childStarted]) + childGate.resolve() + await childSettled + parentGate.resolve() + const response = await responsePromise + const html = await response.text() + + expect(response.status).toBe(500) + expect(html).toContain('Parent error boundary') + expect(html).not.toContain('Child error boundary') +}) + +// https://github.com/TanStack/router/issues/4684 +test('#4684: SSR renders head content when beforeLoad throws', async () => { + const rootRoute = createRootRoute({ + head: () => ({ + links: [{ rel: 'stylesheet', href: '/global.css' }], + }), + shellComponent: function RootDocument({ children }) { + return ( + + + + + {children} + + ) + }, + component: Outlet, + }) + const failingRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/fail', + beforeLoad: () => { + throw new Error('beforeLoad failed') + }, + head: ({ match }) => ({ + meta: [{ title: match.error ? 'Error title' : 'Success title' }], + }), + component: function FailingRoute() { + return
Route content
+ }, + errorComponent: ({ error }) =>
Error UI: {error.message}
, + }) + + const handler = createRequestHandler({ + request: new Request('http://localhost/fail'), + createRouter: () => + createRouter({ + routeTree: rootRoute.addChildren([failingRoute]), + isServer: true, + }), + }) + + const response = await handler(({ router, responseHeaders }) => + renderRouterToString({ + router, + responseHeaders, + children: , + }), + ) + + expect(response.status).toBe(500) + const html = await response.text() + const serverDocument = new DOMParser().parseFromString(html, 'text/html') + + expect(serverDocument.body.textContent).toContain( + 'Error UI: beforeLoad failed', + ) + expect(serverDocument.head.querySelector('title')?.textContent).toBe( + 'Error title', + ) + expect( + serverDocument.head.querySelector( + 'link[rel="stylesheet"][href="/global.css"]', + ), + ).not.toBeNull() +}) + describe('notFoundComponent is rendered when an error is thrown in params.parse', () => { test('displays notFoundComponent when error is thrown in params.parse', async () => { const history = createMemoryHistory({ initialEntries: ['/'] }) @@ -423,13 +852,11 @@ describe('notFoundComponent is rendered when an error is thrown in params.parse' render() - await act(() => router.latestLoadPromise) - expect(rootLoader).toHaveBeenCalledTimes(1) - const linkToRottenPizza = await screen.findByRole('link', { name: 'link to rotten pizza', }) + expect(rootLoader).toHaveBeenCalledTimes(1) expect(linkToRottenPizza).toBeInTheDocument() await act(() => fireEvent.mouseOver(linkToRottenPizza)) await act(() => fireEvent.click(linkToRottenPizza)) diff --git a/packages/react-router/tests/hydration-capped-boundary-pending.test.tsx b/packages/react-router/tests/hydration-capped-boundary-pending.test.tsx new file mode 100644 index 0000000000..57d99c8af5 --- /dev/null +++ b/packages/react-router/tests/hydration-capped-boundary-pending.test.tsx @@ -0,0 +1,264 @@ +import * as React from 'react' +import { act } from '@testing-library/react' +import { hydrateRoot } from 'react-dom/client' +import { renderToString } from 'react-dom/server' +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { dehydrateSsrMatchId } from '../../router-core/src/ssr/ssr-match-id' +import { hydrate } from '../src/ssr/client' +import { + Outlet, + RouterProvider, + createRootRoute, + createRoute, + createRouter, + notFound, +} from '../src' +import type { TsrSsrGlobal } from '../src/ssr/client' + +declare global { + interface Window { + $_TSR?: TsrSsrGlobal + } +} + +const testCleanups: Array<() => void | Promise> = [] + +afterEach(async () => { + while (testCleanups.length) { + await testCleanups.pop()!() + } + vi.restoreAllMocks() + window.$_TSR = undefined + document.body.innerHTML = '' +}) + +describe('hydrating a server-capped boundary lane', () => { + test('recovers a /404 payload against a missing browser URL', async () => { + function MissingPage() { + return
Missing page
+ } + + const makeRouteTree = () => { + const rootRoute = createRootRoute({ + component: Outlet, + notFoundComponent: MissingPage, + }) + const notFoundRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/404', + component: MissingPage, + }) + return rootRoute.addChildren([notFoundRoute]) + } + + const serverRouter = createRouter({ + routeTree: makeRouteTree(), + history: createMemoryHistory({ initialEntries: ['/404'] }), + }) + serverRouter.isServer = true + await serverRouter.load() + const serverMatches = serverRouter.stores.matches.get() + const serverHtml = renderToString() + expect(serverHtml).toContain('Missing page') + + const clientRouter = createRouter({ + routeTree: makeRouteTree(), + history: createMemoryHistory({ initialEntries: ['/missing'] }), + }) + window.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + matches: serverMatches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + u: match.updatedAt, + s: match.status, + l: match.loaderData, + e: match.error, + ssr: match.ssr, + ...(match._notFound ? { g: true } : {}), + })), + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(clientRouter) + + const container = document.createElement('div') + container.innerHTML = serverHtml + document.body.appendChild(container) + let root!: ReturnType + await act(async () => { + root = hydrateRoot(container, , { + onRecoverableError: () => {}, + }) + testCleanups.push(async () => { + await act(() => root.unmount()) + }) + await Promise.resolve() + }) + + expect(container).toHaveTextContent('Missing page') + expect(clientRouter.state.resolvedLocation?.pathname).toBe('/missing') + expect(clientRouter.state.matches).toHaveLength(1) + expect(clientRouter.state.matches[0]).toMatchObject({ _notFound: true }) + }) + + test.each([ + ['error', 'parent'], + ['notFound', 'parent'], + ['error', 'root'], + ['notFound', 'root'], + ] as const)( + 'keeps the server-rendered %s %s boundary visible', + async (outcome, boundary) => { + const childLoader = vi.fn(() => 'child data') + const boundaryCommits = vi.fn() + + function BoundaryError() { + React.useEffect(() => { + boundaryCommits() + }, []) + return
Boundary error
+ } + + function BoundaryNotFound() { + React.useEffect(() => { + boundaryCommits() + }, []) + return
Boundary not found
+ } + + const makeRouteTree = () => { + const boundaryOptions = { + beforeLoad: () => { + throw outcome === 'notFound' + ? notFound() + : new Error('server route failure') + }, + pendingComponent: () => ( +
Boundary pending
+ ), + errorComponent: BoundaryError, + notFoundComponent: BoundaryNotFound, + } + const rootRoute = createRootRoute({ + component: Outlet, + ...(boundary === 'root' ? boundaryOptions : {}), + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + component: Outlet, + ...(boundary === 'parent' ? boundaryOptions : {}), + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + component: () =>
Child
, + }) + return { + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + ]), + } + } + + const serverRouter = createRouter({ + ...makeRouteTree(), + history: createMemoryHistory({ + initialEntries: ['/parent/child'], + }), + }) + serverRouter.isServer = true + await serverRouter.load() + + const serverMatches = serverRouter.stores.matches.get() + expect(serverMatches).toHaveLength(3) + const serverBoundary = serverMatches[boundary === 'root' ? 0 : 1]! + if (outcome === 'notFound' && boundary === 'root') { + expect(serverBoundary).toMatchObject({ + status: 'success', + _notFound: true, + }) + } else { + expect(serverBoundary.status).toBe(outcome) + } + const serverHtml = renderToString( + , + ) + expect(serverHtml).toContain( + outcome === 'notFound' ? 'Boundary not found' : 'Boundary error', + ) + expect(boundaryCommits).not.toHaveBeenCalled() + + const clientRouter = createRouter({ + ...makeRouteTree(), + history: createMemoryHistory({ + initialEntries: ['/parent/child'], + }), + }) + + window.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + matches: serverMatches + .slice(0, boundary === 'root' ? 1 : 2) + .map((match) => ({ + i: dehydrateSsrMatchId(match.id), + u: match.updatedAt, + s: match.status, + l: match.loaderData, + e: match.error, + ssr: match.ssr, + ...(match._notFound ? { g: true } : {}), + })), + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(clientRouter) + + const container = document.createElement('div') + container.innerHTML = serverHtml + document.body.appendChild(container) + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}) + let root!: ReturnType + await act(async () => { + root = hydrateRoot(container, ) + testCleanups.push(async () => { + await act(() => root.unmount()) + }) + await Promise.resolve() + }) + + // A shorter dehydrated lane means SPA mode only for an actual shell. + // Here it is shorter because the server already rendered a terminal + // boundary, so hydration must not replace that boundary with pending UI. + expect(boundaryCommits).toHaveBeenCalledTimes(1) + expect(container).toHaveTextContent( + outcome === 'notFound' ? 'Boundary not found' : 'Boundary error', + ) + expect(container).not.toHaveTextContent('Boundary pending') + expect(consoleError.mock.calls.flat().join(' ')).not.toMatch( + /hydration|did not match/i, + ) + expect(childLoader).not.toHaveBeenCalled() + }, + ) +}) diff --git a/packages/react-router/tests/hydration-terminal-lane.test.tsx b/packages/react-router/tests/hydration-terminal-lane.test.tsx new file mode 100644 index 0000000000..35784ea916 --- /dev/null +++ b/packages/react-router/tests/hydration-terminal-lane.test.tsx @@ -0,0 +1,98 @@ +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, test, vi } from 'vitest' +import { hydrate } from '@tanstack/router-core/ssr/client' +import { dehydrateSsrMatchId } from '../../router-core/src/ssr/ssr-match-id' +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' +import type { AnyRouteMatch } from '@tanstack/router-core' +import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client' + +function bootstrap( + matches: Array<{ + match: AnyRouteMatch + status: AnyRouteMatch['status'] + ssr: AnyRouteMatch['ssr'] + data?: unknown + error?: unknown + }>, +): void { + window.$_TSR = { + router: { + manifest: undefined, + matches: matches.map(({ match, status, ssr, data, error }) => ({ + i: dehydrateSsrMatchId(match.id), + l: data, + e: error, + s: status, + ssr, + u: Date.now(), + })), + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + } as TsrSsrGlobal +} + +afterEach(() => { + cleanup() + delete window.$_TSR +}) + +describe('hydration terminal lane', () => { + test('keeps server data while loading only the missing client suffix', async () => { + const parentLoader = vi.fn(() => 'client-parent') + const childLoader = vi.fn(() => 'client-child') + const rootRoute = createRootRoute({ component: Outlet }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: parentLoader, + component: () => ( + <> +
{parentRoute.useLoaderData()}
+ + + ), + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + ssr: false, + loader: childLoader, + component: () =>
{childRoute.useLoaderData()}
, + }) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + }) + const matches = router.matchRoutes(router.state.location) + bootstrap([ + { match: matches[0]!, status: 'success', ssr: true }, + { + match: matches[1]!, + status: 'success', + ssr: true, + data: 'server-parent', + }, + { match: matches[2]!, status: 'pending', ssr: false }, + ]) + + await hydrate(router) + render() + + expect(await screen.findByText('server-parent')).toBeInTheDocument() + expect(await screen.findByText('client-child')).toBeInTheDocument() + expect(screen.queryByText('client-parent')).not.toBeInTheDocument() + expect(parentLoader).not.toHaveBeenCalled() + expect(childLoader).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/react-router/tests/issue-2182-root-pending.test.tsx b/packages/react-router/tests/issue-2182-root-pending.test.tsx new file mode 100644 index 0000000000..9cf3f82cb7 --- /dev/null +++ b/packages/react-router/tests/issue-2182-root-pending.test.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import { act, cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRouter, +} from '../src' + +afterEach(() => { + cleanup() + vi.useRealTimers() +}) + +// https://github.com/TanStack/router/issues/2182 +test('root pending fallback remains visible through pendingMinMs', async () => { + vi.useFakeTimers() + + const loaderGate = createControlledPromise() + const rootRoute = createRootRoute({ + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Pending
, + loader: () => loaderGate, + component: () =>
Loaded
, + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + + await act(async () => { + await vi.advanceTimersByTimeAsync(0) + }) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + expect(screen.queryByTestId('root-content')).not.toBeInTheDocument() + + loaderGate.resolve('loaded') + + await act(async () => { + await vi.advanceTimersByTimeAsync(99) + }) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + expect(screen.queryByTestId('root-content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + }) + expect(screen.queryByTestId('root-pending')).not.toBeInTheDocument() + expect(screen.getByTestId('root-content')).toBeInTheDocument() +}) diff --git a/packages/react-router/tests/issue-2905-root-beforeload-pending.test.tsx b/packages/react-router/tests/issue-2905-root-beforeload-pending.test.tsx new file mode 100644 index 0000000000..2821571c42 --- /dev/null +++ b/packages/react-router/tests/issue-2905-root-beforeload-pending.test.tsx @@ -0,0 +1,59 @@ +import * as React from 'react' +import { act, cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + RouterProvider, + createMemoryHistory, + createRootRoute, + createRouter, +} from '../src' + +afterEach(() => { + cleanup() + vi.useRealTimers() +}) + +// https://github.com/TanStack/router/issues/2905 +test('#2905: a root pendingComponent renders while root beforeLoad is pending', async () => { + vi.useFakeTimers() + + const rootRoute = createRootRoute({ + beforeLoad: async () => { + await new Promise((resolve) => setTimeout(resolve, 3_000)) + }, + pendingComponent: () =>
Root pending
, + component: () =>
Root
, + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + await act(async () => { + await vi.advanceTimersByTimeAsync(0) + }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(999) + }) + expect(screen.queryByTestId('root-pending')).not.toBeInTheDocument() + expect(screen.queryByTestId('root-content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + }) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + expect(screen.queryByTestId('root-content')).not.toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1_999) + }) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + }) + expect(screen.queryByTestId('root-pending')).not.toBeInTheDocument() + expect(screen.getByTestId('root-content')).toBeInTheDocument() +}) diff --git a/packages/react-router/tests/issue-4467-lazy-route-pending.test.tsx b/packages/react-router/tests/issue-4467-lazy-route-pending.test.tsx new file mode 100644 index 0000000000..ef85b2f235 --- /dev/null +++ b/packages/react-router/tests/issue-4467-lazy-route-pending.test.tsx @@ -0,0 +1,144 @@ +import * as React from 'react' +import { act, cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, onTestFinished, test, vi } from 'vitest' + +import { + Outlet, + RouterProvider, + createControlledPromise, + createLazyRoute, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(cleanup) + +// https://github.com/TanStack/router/issues/4467 +test('default pending component renders while lazy route options load', async () => { + const rootRoute = createRootRoute({ + component: Outlet, + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>

Index page

, + }) + const lazyPageOptions = createLazyRoute('/page')({ + component: () =>

Page

, + }) + const lazyOptions = createControlledPromise() + const loadLazyOptions = vi.fn(() => lazyOptions) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + }).lazy(loadLazyOptions) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, pageRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPendingMs: 0, + defaultPendingMinMs: 0, + defaultPendingComponent: () =>

Loading page

, + }) + let navigationPromise: Promise | undefined + + onTestFinished(async () => { + await act(async () => { + if (lazyOptions.status === 'pending') { + lazyOptions.resolve(lazyPageOptions) + } + await navigationPromise + }) + }) + + render() + + expect( + await screen.findByRole('heading', { name: 'Index page' }), + ).toBeInTheDocument() + + act(() => { + navigationPromise = router.navigate({ to: '/page' }) + }) + + expect(await screen.findByRole('status')).toHaveTextContent('Loading page') + expect( + screen.queryByRole('heading', { name: 'Page' }), + ).not.toBeInTheDocument() + expect(lazyOptions.status).toBe('pending') + expect(loadLazyOptions).toHaveBeenCalledTimes(1) + + await act(async () => { + lazyOptions.resolve(lazyPageOptions) + await navigationPromise + }) + + expect(screen.getByRole('heading', { name: 'Page' })).toBeInTheDocument() + expect(screen.queryByRole('status')).not.toBeInTheDocument() + expect(loadLazyOptions).toHaveBeenCalledTimes(1) +}) + +test('a lazy pending component is offered while the eager loader is still pending', async () => { + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>

Index page

, + }) + const loader = createControlledPromise() + const lazyPageOptions = createLazyRoute('/page')({ + pendingComponent: () =>

Loading lazy page

, + component: () =>

Page

, + }) + const lazyOptions = createControlledPromise() + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => loader, + }).lazy(() => lazyOptions) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, pageRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPendingMs: 0, + defaultPendingMinMs: 0, + defaultPendingComponent: () =>

Loading default

, + }) + let navigation: Promise | undefined + + onTestFinished(async () => { + await act(async () => { + lazyOptions.resolve(lazyPageOptions) + loader.resolve() + await navigation + }) + }) + + render() + expect( + await screen.findByRole('heading', { name: 'Index page' }), + ).toBeInTheDocument() + + act(() => { + navigation = router.navigate({ to: '/page' }) + }) + expect(await screen.findByRole('status')).toHaveTextContent('Loading default') + + await act(async () => { + lazyOptions.resolve(lazyPageOptions) + }) + + expect(await screen.findByRole('status')).toHaveTextContent( + 'Loading lazy page', + ) + expect( + screen.queryByRole('heading', { name: 'Page' }), + ).not.toBeInTheDocument() + + await act(async () => { + loader.resolve() + await navigation + }) + + expect(screen.getByRole('heading', { name: 'Page' })).toBeInTheDocument() +}) diff --git a/packages/react-router/tests/issue-4476-react-query-cancellation.test.tsx b/packages/react-router/tests/issue-4476-react-query-cancellation.test.tsx new file mode 100644 index 0000000000..039608ffb2 --- /dev/null +++ b/packages/react-router/tests/issue-4476-react-query-cancellation.test.tsx @@ -0,0 +1,129 @@ +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { + QueryClient, + QueryClientProvider, + useQuery, +} from '@tanstack/react-query' +import { afterEach, expect, onTestFinished, test, vi } from 'vitest' +import { + Link, + Outlet, + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(cleanup) + +// https://github.com/TanStack/router/issues/4476 +test('#4476: pending navigation keeps the query observer mounted and its fetchQuery signal alive', async () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: 0 } }, + }) + const queryGate = createControlledPromise() + const queryKey = ['issue-4476'] as const + const routeError = vi.fn() + const pageTwoBeforeLoad = vi.fn() + const pendingComponentRendered = vi.fn() + let querySignal: AbortSignal | undefined + + const rootRoute = createRootRoute({ + component: () => ( + <> + + Page two + + + + ), + }) + function PageOneComponent() { + const query = useQuery({ + queryKey, + queryFn: () => Promise.resolve(3), + }) + return
Page one: {query.data}
+ } + const pageOneRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: PageOneComponent, + }) + const pageTwoRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page-two', + beforeLoad: async ({ preload }) => { + pageTwoBeforeLoad(preload) + if (preload) { + return + } + const data = await queryClient.fetchQuery({ + queryKey, + queryFn: ({ signal }) => { + querySignal = signal + return queryGate + }, + }) + return { data } + }, + errorComponent: ({ error }) => { + routeError(error) + return
{error.name}
+ }, + component: () => { + const { data } = pageTwoRoute.useRouteContext() + return
Page two: {data}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageOneRoute, pageTwoRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPreload: 'intent', + defaultPreloadDelay: 0, + defaultPreloadStaleTime: 0, + defaultPendingMs: 0, + defaultPendingComponent: () => { + pendingComponentRendered() + return
Loading page two
+ }, + }) + + onTestFinished(() => { + queryGate.resolve(10) + queryClient.clear() + }) + + render( + + + , + ) + expect(await screen.findByText('Page one: 3')).toBeInTheDocument() + + const link = screen.getByRole('link', { name: 'Page two' }) + fireEvent.mouseOver(link) + await waitFor(() => expect(pageTwoBeforeLoad).toHaveBeenCalledWith(true)) + fireEvent.click(link) + + await waitFor(() => expect(querySignal).toBeDefined()) + expect(screen.getByTestId('page-one')).toBeInTheDocument() + expect(screen.getByTestId('page-two-pending')).toBeInTheDocument() + expect(querySignal?.aborted).toBe(false) + queryGate.resolve(10) + + expect(await screen.findByText('Page two: 10')).toBeInTheDocument() + expect(routeError).not.toHaveBeenCalled() + expect(screen.queryByTestId('page-one')).not.toBeInTheDocument() + expect(screen.queryByTestId('page-two-pending')).not.toBeInTheDocument() + expect(screen.queryByTestId('page-two-error')).not.toBeInTheDocument() + expect(router.state.location.pathname).toBe('/page-two') +}) diff --git a/packages/react-router/tests/issue-4759-pending-frame.test.tsx b/packages/react-router/tests/issue-4759-pending-frame.test.tsx new file mode 100644 index 0000000000..7b8b74161c --- /dev/null +++ b/packages/react-router/tests/issue-4759-pending-frame.test.tsx @@ -0,0 +1,100 @@ +import * as React from 'react' +import { + afterEach, + beforeEach, + describe, + expect, + onTestFinished, + test, + vi, +} from 'vitest' +import { act, cleanup, render, screen } from '@testing-library/react' +import { + RouterProvider, + createBrowserHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' +import type { RouterHistory } from '../src' + +let history: RouterHistory + +beforeEach(() => { + history = createBrowserHistory() + expect(window.location.pathname).toBe('/') +}) + +afterEach(() => { + history.destroy() + window.history.replaceState(null, 'root', '/') + vi.resetAllMocks() + cleanup() +}) + +// Repro for https://github.com/TanStack/router/issues/4759 +// +// JSDOM cannot observe browser paints. This unit reduction verifies the event +// ordering behind the issue: pending DOM must be published before the first +// macrotask when pendingMs is 0. +describe('issue #4759: pendingMs 0 publishes pending DOM before a macrotask', () => { + test('pending fallback is committed on mount without waiting for a macrotask', async () => { + vi.useFakeTimers() + let resolveLoader!: (value: string) => void + const loaderPromise = new Promise((resolve) => { + resolveLoader = resolve + }) + + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: () => loaderPromise, + component: () =>
loaded
, + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history, + }) + let resolveRendered!: () => void + const rendered = new Promise((resolve) => { + resolveRendered = resolve + }) + const unsubscribe = router.subscribe('onRendered', (event) => { + if (event.toLocation.pathname === '/') { + resolveRendered() + } + }) + + onTestFinished(() => { + unsubscribe() + resolveLoader('done') + vi.useRealTimers() + }) + + render( +
+ ( +
pending...
+ )} + /> +
, + ) + + // Fake timers keep the first macrotask frozen. An implementation that + // publishes pending state with setTimeout cannot satisfy this assertion. + await act(async () => {}) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + // Sanity: the load still completes normally afterwards. + resolveLoader('done') + await act(() => rendered) + expect(screen.getByTestId('loaded')).toBeInTheDocument() + expect(screen.queryByTestId('pending')).not.toBeInTheDocument() + }) +}) diff --git a/packages/react-router/tests/issue-5778-router-provider-context-preload.test.tsx b/packages/react-router/tests/issue-5778-router-provider-context-preload.test.tsx new file mode 100644 index 0000000000..e179a1502e --- /dev/null +++ b/packages/react-router/tests/issue-5778-router-provider-context-preload.test.tsx @@ -0,0 +1,113 @@ +import * as React from 'react' +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { afterEach, expect, test } from 'vitest' +import { + Link, + Outlet, + RouterProvider, + createMemoryHistory, + createRootRouteWithContext, + createRoute, + createRouter, +} from '../src' + +afterEach(cleanup) + +// https://github.com/TanStack/router/issues/5778 +test('#5778: intent preload sees a RouterProvider context update before the first navigation', async () => { + const seen: Array<{ + route: 'auth' | 'foo' + foo: string + cause: string + preload: boolean + }> = [] + const rootRoute = createRootRouteWithContext<{ foo: string }>()({ + component: () => ( + <> + + Foo + + + + ), + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const authRoute = createRoute({ + getParentRoute: () => rootRoute, + id: '_authenticated', + beforeLoad: ({ context, cause, preload }) => { + seen.push({ route: 'auth', foo: context.foo, cause, preload }) + }, + component: () => , + }) + const fooRoute = createRoute({ + getParentRoute: () => authRoute, + path: '/foo', + beforeLoad: ({ context, cause, preload }) => { + seen.push({ route: 'foo', foo: context.foo, cause, preload }) + }, + component: () =>
Foo page
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + authRoute.addChildren([fooRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + context: { foo: null! }, + defaultPreload: 'intent', + defaultPreloadDelay: 0, + }) + + function App() { + const [foo, setFoo] = React.useState('foo') + return ( + <> + {foo} + + + + ) + } + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + expect(screen.getByLabelText('Current context')).toHaveTextContent('foo') + + fireEvent.click(screen.getByRole('button', { name: 'Update context' })) + await waitFor(() => + expect(screen.getByLabelText('Current context')).toHaveTextContent('baz'), + ) + + expect(seen).toEqual([]) + const link = screen.getByRole('link', { name: 'Foo' }) + fireEvent.mouseEnter(link) + await waitFor(() => expect(seen).toHaveLength(2)) + expect(seen).toEqual([ + { route: 'auth', foo: 'baz', cause: 'preload', preload: true }, + { route: 'foo', foo: 'baz', cause: 'preload', preload: true }, + ]) + expect(screen.getByText('Home')).toBeInTheDocument() + expect(screen.queryByText('Foo page')).not.toBeInTheDocument() + expect(router.state.location.pathname).toBe('/') + + fireEvent.click(link) + expect(await screen.findByText('Foo page')).toBeInTheDocument() + expect(router.state.location.pathname).toBe('/foo') +}) diff --git a/packages/react-router/tests/issue-6107-lazy-chunk-error-component.test.tsx b/packages/react-router/tests/issue-6107-lazy-chunk-error-component.test.tsx new file mode 100644 index 0000000000..22440fe119 --- /dev/null +++ b/packages/react-router/tests/issue-6107-lazy-chunk-error-component.test.tsx @@ -0,0 +1,89 @@ +import * as React from 'react' +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + Link, + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(() => { + vi.restoreAllMocks() + cleanup() +}) + +// https://github.com/TanStack/router/issues/6107 +test('#6107: lazy chunk hover failure is non-fatal and navigation renders defaultErrorComponent', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + const chunkError = new TypeError( + 'Failed to fetch dynamically imported module: /assets/posts.lazy.js', + ) + const defaultErrorRendered = vi.fn() + let lazyCalls = 0 + + const rootRoute = createRootRoute({ + component: () => ( + <> + + Posts + + + + ), + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Index
, + }) + const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + }).lazy(() => { + lazyCalls++ + return Promise.reject(chunkError) + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, postsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPreloadDelay: 0, + defaultErrorComponent: ({ error }) => { + defaultErrorRendered(error) + return
{error.message}
+ }, + }) + const preloadRoute = vi.spyOn(router, 'preloadRoute') + + render() + expect(await screen.findByText('Index')).toBeInTheDocument() + + const link = screen.getByRole('link', { name: 'Posts' }) + fireEvent.mouseEnter(link) + await waitFor(() => expect(preloadRoute).toHaveBeenCalledTimes(1)) + await preloadRoute.mock.results[0]!.value + expect(lazyCalls).toBeGreaterThanOrEqual(1) + expect(screen.getByText('Index')).toBeInTheDocument() + expect(screen.queryByTestId('default-error')).not.toBeInTheDocument() + expect(defaultErrorRendered).not.toHaveBeenCalled() + + const callsAfterPreload = lazyCalls + fireEvent.click(link) + expect(await screen.findByTestId('default-error')).toHaveTextContent( + chunkError.message, + ) + expect(lazyCalls).toBeGreaterThan(callsAfterPreload) + expect(defaultErrorRendered).toHaveBeenCalledWith(chunkError) + expect(screen.queryByText('Index')).not.toBeInTheDocument() + expect(router.state.location.pathname).toBe('/posts') + expect(router.state.status).toBe('idle') +}) diff --git a/packages/react-router/tests/issue-6371-search-default-normalization-abort.test.tsx b/packages/react-router/tests/issue-6371-search-default-normalization-abort.test.tsx new file mode 100644 index 0000000000..7698d117a6 --- /dev/null +++ b/packages/react-router/tests/issue-6371-search-default-normalization-abort.test.tsx @@ -0,0 +1,169 @@ +import { afterEach, expect, onTestFinished, test, vi } from 'vitest' +import { act, cleanup, render, screen } from '@testing-library/react' +import { + Outlet, + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + useLocation, +} from '../src' + +afterEach(() => { + cleanup() +}) + +test('#6371: initial search defaults produce one live canonical loader', async () => { + const loaderGate = createControlledPromise() + const canonicalLocation = createControlledPromise() + const abortedLoaderData = 'discarded aborted loader' + const loaderSignals: Array = [] + const loaderLocations: Array = [] + const errorComponentRendered = vi.fn() + const loader = vi.fn( + ({ + abortController, + location, + }: { + abortController: AbortController + location: { href: string } + }) => { + const signal = abortController.signal + loaderSignals.push(signal) + loaderLocations.push(location.href) + + return new Promise((resolve, reject) => { + const onAbort = () => { + resolve(abortedLoaderData) + } + + if (signal.aborted) { + onAbort() + return + } + + signal.addEventListener('abort', onAbort, { once: true }) + loaderGate.then((data) => { + signal.removeEventListener('abort', onAbort) + resolve(data) + }, reject) + }) + }, + ) + + const PendingLocation = () => { + const href = useLocation({ select: (location) => location.href }) + return
{href}
+ } + + const rootRoute = createRootRoute({ + component: () => , + }) + const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + validateSearch: (search: Record) => ({ + page: typeof search.page === 'number' ? search.page : 1, + }), + loader, + component: () => ( +
{aboutRoute.useLoaderData()}
+ ), + errorComponent: ({ error }) => { + errorComponentRendered(error) + return
{error.message}
+ }, + }) + const history = createMemoryHistory({ initialEntries: ['/about'] }) + const unsubscribeHistory = history.subscribe(() => { + if (history.location.href === '/about?page=1') { + canonicalLocation.resolve() + } + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([aboutRoute]), + history, + defaultPendingMs: 0, + defaultPendingMinMs: 0, + defaultPendingComponent: PendingLocation, + }) + + onTestFinished(async () => { + unsubscribeHistory() + await act(() => { + canonicalLocation.resolve() + loaderGate.resolve('about data') + }) + }) + + render() + + await act(async () => { + await canonicalLocation + }) + + expect(loader).toHaveBeenCalledTimes(1) + expect(loaderLocations).toEqual(['/about?page=1']) + expect(loaderSignals).toHaveLength(1) + expect(loaderSignals[0]?.aborted).toBe(false) + + expect( + await screen.findByText('/about?page=1', { + selector: '[data-testid="pending-location"]', + }), + ).toBeInTheDocument() + + await act(() => { + loaderGate.resolve('about data') + }) + + expect(await screen.findByTestId('about-data')).toHaveTextContent( + 'about data', + ) + expect(loader).toHaveBeenCalledTimes(1) + expect(loaderSignals[0]?.aborted).toBe(false) + expect(errorComponentRendered).not.toHaveBeenCalled() + expect(screen.queryByTestId('about-error')).not.toBeInTheDocument() +}) + +test('initial canonicalization bypasses existing navigation blockers', async () => { + const loader = vi.fn( + ({ location }: { location: { href: string } }) => location.href, + ) + const rootRoute = createRootRoute({ + component: () => , + }) + const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + validateSearch: (search: Record) => ({ + page: typeof search.page === 'number' ? search.page : 1, + }), + loader, + component: () =>
{aboutRoute.useLoaderData()}
, + }) + const history = createMemoryHistory({ initialEntries: ['/about'] }) + const blockerFn = vi.fn(() => true) + const unblock = history.block({ + blockerFn, + enableBeforeUnload: false, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([aboutRoute]), + history, + }) + + onTestFinished(unblock) + + render() + + expect(await screen.findByText('/about?page=1')).toBeInTheDocument() + expect(loader).toHaveBeenCalledTimes(1) + expect(history.location.href).toBe('/about?page=1') + expect(router.latestLocation.href).toBe('/about?page=1') + expect(router.state.location.href).toBe('/about?page=1') + expect(router.state.resolvedLocation?.href).toBe('/about?page=1') + expect(blockerFn).not.toHaveBeenCalled() +}) diff --git a/packages/react-router/tests/issue-7051-force-pending-suspense.test.tsx b/packages/react-router/tests/issue-7051-force-pending-suspense.test.tsx new file mode 100644 index 0000000000..5640cb7477 --- /dev/null +++ b/packages/react-router/tests/issue-7051-force-pending-suspense.test.tsx @@ -0,0 +1,195 @@ +import { act } from 'react' +import { afterEach, expect, test, vi } from 'vitest' +import { cleanup, render, screen } from '@testing-library/react' +import { + Outlet, + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(() => { + vi.clearAllMocks() + cleanup() +}) + +// Ported from PR #7051. A forced-pending reload must keep showing its pending +// fallback until fresh content commits instead of exposing the error boundary. +test('invalidate({ forcePending: true }) keeps rendering the pending fallback instead of the error boundary', async () => { + const history = createMemoryHistory({ + initialEntries: ['/force-pending'], + }) + const errorComponentRendered = vi.fn() + let shouldSuspendReload = false + const reloadGate = createControlledPromise() + + const rootRoute = createRootRoute({ + component: () => , + }) + + const forcePendingRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/force-pending', + pendingMs: 0, + pendingMinMs: 10, + loader: async () => { + if (shouldSuspendReload) { + await reloadGate + } + + return 'done' + }, + component: () => ( +
+ {forcePendingRoute.useLoaderData()} +
+ ), + pendingComponent: () => ( +
Pending...
+ ), + errorComponent: ({ error }) => { + errorComponentRendered(error) + return
{String(error)}
+ }, + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([forcePendingRoute]), + history, + }) + + render() + + await act(() => router.load()) + expect(await screen.findByTestId('force-pending-route')).toHaveTextContent( + 'done', + ) + + shouldSuspendReload = true + let invalidation!: Promise + act(() => { + invalidation = router.invalidate({ forcePending: true }) + }) + + expect( + await screen.findByTestId('force-pending-fallback'), + ).toBeInTheDocument() + expect(errorComponentRendered).not.toHaveBeenCalled() + expect(screen.queryByTestId('force-pending-error')).not.toBeInTheDocument() + + act(() => { + reloadGate.resolve() + }) + + await act(() => invalidation) + expect(await screen.findByTestId('force-pending-route')).toHaveTextContent( + 'done', + ) + expect(screen.queryByTestId('force-pending-fallback')).not.toBeInTheDocument() + expect(screen.queryByTestId('force-pending-error')).not.toBeInTheDocument() + expect(errorComponentRendered).not.toHaveBeenCalled() + expect(router.state.location.pathname).toBe('/force-pending') + expect(router.state.status).toBe('idle') +}) + +test('regular navigation keeps the current pending fallback while its loader is aborted', async () => { + const firstLoaderAborted = createControlledPromise() + const secondLoaderStarted = createControlledPromise() + const secondLoaderGate = createControlledPromise() + const firstErrorComponentRendered = vi.fn() + let firstSignal: AbortSignal | undefined + + const rootRoute = createRootRoute({ + component: () => , + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home page
, + }) + const firstRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/first', + pendingMs: 0, + loader: async ({ abortController }) => { + firstSignal = abortController.signal + await new Promise((_resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => { + firstLoaderAborted.resolve() + reject(new DOMException('Aborted', 'AbortError')) + }, + { once: true }, + ) + }) + return 'first' + }, + component: () => ( +
{firstRoute.useLoaderData()}
+ ), + pendingComponent: () => ( +
Pending first route
+ ), + errorComponent: ({ error }) => { + firstErrorComponentRendered(error) + return
{String(error)}
+ }, + }) + const secondRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/second', + loader: async () => { + secondLoaderStarted.resolve() + await secondLoaderGate + return 'second' + }, + component: () => ( +
{secondRoute.useLoaderData()}
+ ), + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPreload: false, + }) + + render() + expect(await screen.findByTestId('home-page')).toBeInTheDocument() + + act(() => { + void router.navigate({ to: '/first' }) + }) + expect(await screen.findByTestId('first-pending')).toBeInTheDocument() + expect(firstSignal?.aborted).toBe(false) + + let secondNavigation!: Promise + act(() => { + secondNavigation = router.navigate({ to: '/second' }) + }) + await act(async () => { + await Promise.all([firstLoaderAborted, secondLoaderStarted]) + }) + + expect(firstSignal?.aborted).toBe(true) + expect(screen.getByTestId('first-pending')).toBeInTheDocument() + expect(screen.queryByTestId('first-error')).not.toBeInTheDocument() + expect(firstErrorComponentRendered).not.toHaveBeenCalled() + expect(router.state.location.pathname).toBe('/second') + expect(router.state.status).toBe('pending') + + act(() => { + secondLoaderGate.resolve() + }) + await act(() => secondNavigation) + + expect(await screen.findByTestId('second-page')).toHaveTextContent('second') + expect(screen.queryByTestId('first-pending')).not.toBeInTheDocument() + expect(screen.queryByTestId('first-error')).not.toBeInTheDocument() + expect(firstErrorComponentRendered).not.toHaveBeenCalled() + expect(router.state.location.pathname).toBe('/second') + expect(router.state.status).toBe('idle') +}) diff --git a/packages/react-router/tests/issue-7367-pending-min-redirect.test.tsx b/packages/react-router/tests/issue-7367-pending-min-redirect.test.tsx new file mode 100644 index 0000000000..666f1d3e4b --- /dev/null +++ b/packages/react-router/tests/issue-7367-pending-min-redirect.test.tsx @@ -0,0 +1,78 @@ +import * as React from 'react' +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' + +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + redirect, +} from '../src' +import { sleep } from './utils' + +afterEach(() => { + vi.restoreAllMocks() + cleanup() +}) + +// https://github.com/TanStack/router/issues/7367 +// Root route shows a spinner immediately (pendingMs: 0) while beforeLoad +// decides where to send the user, keeps it up for pendingMinMs, and then +// redirects. This used to crash in MatchInnerImpl (white screen) because the +// redirected match was rendered/thrown after its loadPromise was cleared. +test('immediate pending spinner (pendingMs: 0 + pendingMinMs) with root beforeLoad redirect renders the target without render errors', async () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + let hasRedirected = false + + const rootRoute = createRootRoute({ + component: () => , + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
loading
, + errorComponent: ({ error }) => ( +
{String(error)}
+ ), + beforeLoad: async () => { + await sleep(50) + if (!hasRedirected) { + hasRedirected = true + throw redirect({ to: '/welcome', replace: true }) + } + }, + }) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Index
, + }) + + const welcomeRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/welcome', + component: () =>
Welcome
, + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, welcomeRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + + // pendingMs: 0 — the spinner must show right away. + expect(await screen.findByTestId('pending')).toBeInTheDocument() + + // The redirect must complete: the target renders, no error boundary output + // and no render crash. + expect( + await screen.findByTestId('welcome-page', undefined, { timeout: 5_000 }), + ).toBeInTheDocument() + expect(screen.queryByTestId('pending')).not.toBeInTheDocument() + expect(screen.queryByTestId('root-error')).not.toBeInTheDocument() + expect(router.state.location.pathname).toBe('/welcome') + expect(consoleError).not.toHaveBeenCalled() +}) diff --git a/packages/react-router/tests/issue-7635-error-head-after-navigation.test.tsx b/packages/react-router/tests/issue-7635-error-head-after-navigation.test.tsx new file mode 100644 index 0000000000..56aed7b375 --- /dev/null +++ b/packages/react-router/tests/issue-7635-error-head-after-navigation.test.tsx @@ -0,0 +1,100 @@ +import { createPortal } from 'react-dom' +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + HeadContent, + Link, + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(() => { + cleanup() + document.head.innerHTML = '' +}) + +// https://github.com/TanStack/router/issues/7635 +test('#7635: a parent beforeLoad error replaces the previous child title', async () => { + const appError = new Error('App beforeLoad failed') + const appErrorRendered = vi.fn() + const childHead = vi.fn(() => ({ + meta: [{ title: 'Child success title' }], + })) + + const rootRoute = createRootRoute({ + component: () => ( + <> + {createPortal(, document.head)} + + Fail app load + + + + ), + }) + const appRoute = createRoute({ + getParentRoute: () => rootRoute, + id: '_app', + validateSearch: (search: Record) => ({ + fail: search.fail === true || search.fail === 'true', + }), + beforeLoad: ({ search }) => { + if (search.fail) { + throw appError + } + }, + head: ({ match }) => ({ + meta: [ + { + title: match.error ? 'App error title' : 'App success title', + }, + ], + }), + component: Outlet, + errorComponent: ({ error }) => { + appErrorRendered(error) + return
{error.message}
+ }, + }) + const childRoute = createRoute({ + getParentRoute: () => appRoute, + path: '/child', + head: childHead, + component: () =>
Child content
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([appRoute.addChildren([childRoute])]), + history: createMemoryHistory({ + initialEntries: ['/child?fail=false'], + }), + }) + + render() + + expect(await screen.findByTestId('child-content')).toBeInTheDocument() + await waitFor(() => expect(document.title).toBe('Child success title')) + expect(childHead).toHaveBeenCalled() + childHead.mockClear() + + fireEvent.click(screen.getByRole('link', { name: 'Fail app load' })) + + expect(await screen.findByTestId('app-error')).toHaveTextContent( + appError.message, + ) + expect(appErrorRendered).toHaveBeenCalledWith(appError) + expect(screen.queryByTestId('child-content')).not.toBeInTheDocument() + await waitFor(() => expect(document.title).toBe('App error title')) + expect(childHead).not.toHaveBeenCalled() + expect(router.state.location.href).toBe('/child?fail=true') + expect(router.state.status).toBe('idle') +}) diff --git a/packages/react-router/tests/issue-7638-invalidate-transition-error.test.tsx b/packages/react-router/tests/issue-7638-invalidate-transition-error.test.tsx new file mode 100644 index 0000000000..9f1c0eec3f --- /dev/null +++ b/packages/react-router/tests/issue-7638-invalidate-transition-error.test.tsx @@ -0,0 +1,185 @@ +import * as React from 'react' +import { afterEach, expect, test, vi } from 'vitest' +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react' +import { + Outlet, + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + useRouter, +} from '../src' +import type { ErrorComponentProps } from '../src' + +afterEach(() => { + cleanup() +}) + +// https://github.com/TanStack/router/issues/7638 +// router.invalidate() called inside React.startTransition while a nested +// route is showing its errorComponent must complete the reload and land back +// on the error UI without crashing React with +// "Rendered more hooks than during the previous render." +function setup({ failVia }: { failVia: 'render' | 'loader' }) { + const rootRoute = createRootRoute({ component: () => }) + const parentAction = vi.fn() + const secondChildLoad = createControlledPromise() + let invalidation: Promise | undefined + + const testRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/test', + component: function TestComponent() { + testRoute.useLoaderData() + const router = useRouter() + const [isPending, startTransition] = React.useTransition() + return ( +
+ + + +
+ ) + }, + }) + + let childLoaderCalls = 0 + const childLoader = vi.fn(async () => { + childLoaderCalls++ + if (childLoaderCalls === 2) { + await secondChildLoad + } + if (failVia === 'loader') { + throw new Error('loader error') + } + return 'data' + }) + + const testIndexRoute = createRoute({ + getParentRoute: () => testRoute, + path: '/', + loader: childLoader, + component: function ChildComponent() { + if (failVia === 'render') { + throw new Error('render error') + } + return
child content
+ }, + }) + + let errorRenders = 0 + const router = createRouter({ + routeTree: rootRoute.addChildren([testRoute.addChildren([testIndexRoute])]), + history: createMemoryHistory({ initialEntries: ['/test'] }), + defaultErrorComponent: (props: ErrorComponentProps) => { + errorRenders++ + return
error: {props.error.message}
+ }, + }) + + return { + router, + childLoader, + parentAction, + secondChildLoad, + getErrorRenders: () => errorRenders, + getInvalidation: () => invalidation, + } +} + +test.each(['render', 'loader'] as const)( + 'invalidate() inside startTransition through a nested %s-error route does not crash', + async (failVia) => { + // Error boundaries log caught errors through console.error, and so does a + // hooks-order crash. Capture instead of polluting the test output, then + // inspect the captured calls for the crash signature. + const { + router, + childLoader, + parentAction, + secondChildLoad, + getErrorRenders, + getInvalidation, + } = setup({ failVia }) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + try { + render() + + expect(await screen.findByTestId('error-ui')).toHaveTextContent( + `error: ${failVia} error`, + ) + const initialErrorRenders = getErrorRenders() + expect(childLoader).toHaveBeenCalledTimes(1) + consoleError.mockClear() + + fireEvent.click(screen.getByTestId('invalidate')) + + await waitFor(() => { + expect(childLoader).toHaveBeenCalledTimes(2) + expect(screen.getByTestId('invalidate')).toHaveTextContent('pending') + expect(screen.getByTestId('invalidate')).toBeDisabled() + }) + expect(secondChildLoad.status).toBe('pending') + + const invalidation = getInvalidation() + if (!invalidation) { + throw new Error('invalidate action did not return its promise') + } + + await act(async () => { + secondChildLoad.resolve() + await invalidation + }) + + await waitFor(() => { + expect(screen.getByTestId('error-ui')).toHaveTextContent( + `error: ${failVia} error`, + ) + expect(getErrorRenders()).toBeGreaterThan(initialErrorRenders) + expect(screen.getByTestId('invalidate')).toHaveTextContent('invalidate') + expect(screen.getByTestId('invalidate')).toBeEnabled() + }) + + fireEvent.click(screen.getByTestId('parent-action')) + expect(parentAction).toHaveBeenCalledTimes(1) + + const hooksCrash = consoleError.mock.calls.find((call) => + call.some((arg) => + String(arg?.message ?? arg).includes('Rendered more hooks'), + ), + ) + expect(hooksCrash).toBeUndefined() + } finally { + if (secondChildLoad.status === 'pending') { + await act(async () => { + secondChildLoad.resolve() + await getInvalidation()?.catch(() => undefined) + }) + } + consoleError.mockRestore() + } + }, +) diff --git a/packages/react-router/tests/loaders.test.tsx b/packages/react-router/tests/loaders.test.tsx index d9b5968d72..6859b963e2 100644 --- a/packages/react-router/tests/loaders.test.tsx +++ b/packages/react-router/tests/loaders.test.tsx @@ -647,7 +647,7 @@ test('reproducer #4546', async () => { } }) -test('clears pendingTimeout when match resolves', async () => { +test('does not show pending UI when loaders finish before their pending delays', async () => { const defaultPendingComponentOnMountMock = vi.fn() const nestedPendingComponentOnMountMock = vi.fn() const fooPendingComponentOnMountMock = vi.fn() @@ -716,7 +716,6 @@ test('clears pendingTimeout when match resolves', async () => { }) render() - await act(() => router.latestLoadPromise) const linkToFoo = await screen.findByTestId('link-to-foo') fireEvent.click(linkToFoo) const fooElement = await screen.findByText('Nested Foo page') @@ -730,20 +729,32 @@ test('clears pendingTimeout when match resolves', async () => { expect(fooPendingComponentOnMountMock).not.toHaveBeenCalled() }) -test('throw abortError from loader upon initial load with basepath', async () => { - window.history.replaceState(null, 'root', '/app') +// https://github.com/TanStack/router/pull/7673 +test('#7673: a spontaneous loader AbortError renders the boundary without executing the route component', async () => { + history.replace('/app') + history.flush() const rootRoute = createRootRoute({}) + const abortError = new DOMException('Aborted', 'AbortError') + const routeComponentRendered = vi.fn() + const renderedError = vi.fn() + let routeSignal: AbortSignal | undefined const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - loader: async () => { - return Promise.reject(new DOMException('Aborted', 'AbortError')) + loader: async ({ abortController }): Promise<{ value: string }> => { + routeSignal = abortController.signal + return Promise.reject(abortError) + }, + component: () => { + routeComponentRendered() + const data = indexRoute.useLoaderData() + return
{data.value}
+ }, + errorComponent: ({ error }) => { + renderedError(error) + return
indexErrorComponent
}, - component: () =>
Index route content
, - errorComponent: () => ( -
indexErrorComponent
- ), }) const routeTree = rootRoute.addChildren([indexRoute]) @@ -751,13 +762,21 @@ test('throw abortError from loader upon initial load with basepath', async () => render() - const indexElement = await screen.findByText('Index route content') - expect(indexElement).toBeInTheDocument() - expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() - expect(window.location.pathname.startsWith('/app')).toBe(true) + expect(await screen.findByTestId('index-error')).toBeInTheDocument() + expect(screen.queryByTestId('index-content')).not.toBeInTheDocument() + expect(routeComponentRendered).not.toHaveBeenCalled() + expect(renderedError).toHaveBeenCalledWith(abortError) + expect(routeSignal?.aborted).toBe(false) + expect( + router.state.matches.find((match) => match.routeId === indexRoute.id), + ).toMatchObject({ + status: 'error', + error: abortError, + }) + expect(window.location.pathname).toBe('/app') }) -test('cancelMatches after pending timeout', async () => { +test('navigating away from a pending route aborts its loader', async () => { function getPendingComponent(onMount: () => void) { const PendingComponent = () => { useEffect(() => { @@ -770,6 +789,7 @@ test('cancelMatches after pending timeout', async () => { } const onAbortMock = vi.fn() const fooPendingComponentOnMountMock = vi.fn() + let fooSignal: AbortSignal | undefined const rootRoute = createRootRoute({ component: () => (
@@ -787,17 +807,18 @@ test('cancelMatches after pending timeout', async () => { const fooRoute = createRoute({ getParentRoute: () => rootRoute, path: '/foo', - pendingMs: WAIT_TIME * 20, + pendingMs: 0, loader: async ({ abortController }) => { + fooSignal = abortController.signal await new Promise((resolve) => { - const timer = setTimeout(() => { - resolve() - }, WAIT_TIME * 40) - abortController.signal.addEventListener('abort', () => { - onAbortMock() - clearTimeout(timer) - resolve() - }) + abortController.signal.addEventListener( + 'abort', + () => { + onAbortMock() + resolve() + }, + { once: true }, + ) }) }, pendingComponent: getPendingComponent(fooPendingComponentOnMountMock), @@ -811,18 +832,22 @@ test('cancelMatches after pending timeout', async () => { const routeTree = rootRoute.addChildren([fooRoute, barRoute]) const router = createRouter({ routeTree, history }) render() - await act(() => router.latestLoadPromise) const fooLink = await screen.findByTestId('link-to-foo') fireEvent.click(fooLink) - await sleep(WAIT_TIME * 30) const pendingElement = await screen.findByText('Pending...') expect(pendingElement).toBeInTheDocument() - const barLink = await screen.findByTestId('link-to-bar') - fireEvent.click(barLink) - const barElement = await screen.findByText('Bar page') + expect(fooSignal?.aborted).toBe(false) + await act(() => router.navigate({ to: '/bar' })) + const barElement = screen.getByText('Bar page') expect(barElement).toBeInTheDocument() + expect(fooPendingComponentOnMountMock).toHaveBeenCalled() - expect(onAbortMock).toHaveBeenCalled() + expect(onAbortMock).toHaveBeenCalledTimes(1) + expect(fooSignal?.aborted).toBe(true) + expect(screen.queryByText('Pending...')).not.toBeInTheDocument() + expect(screen.queryByText('Foo page')).not.toBeInTheDocument() + expect(router.state.location.href).toBe('/bar') + expect(router.state.status).toBe('idle') }) test('reproducer for #6388 - rapid navigation between parameterized routes should not trigger errorComponent', async () => { @@ -912,30 +937,25 @@ test('reproducer for #6388 - rapid navigation between parameterized routes shoul }) render() - await act(() => router.latestLoadPromise) - - const pendingComponent = screen.findByTestId('pending-component') expect(await screen.findByTestId('home-page')).toBeInTheDocument() - const param1Link = await screen.findByTestId('link-to-param-1') fireEvent.click(param1Link) - expect(await pendingComponent).toBeInTheDocument() + expect(await screen.findByTestId('pending-component')).toBeInTheDocument() const param2Link = await screen.findByTestId('link-to-param-2') fireEvent.click(param2Link) - expect(await pendingComponent).toBeInTheDocument() + expect(await screen.findByTestId('pending-component')).toBeInTheDocument() fireEvent.click(param1Link) - expect(await pendingComponent).toBeInTheDocument() + expect(await screen.findByTestId('pending-component')).toBeInTheDocument() - await act(() => router.latestLoadPromise) + const paramPage = await screen.findByTestId('param-page') expect(onAbortMock).toHaveBeenCalled() expect(errorComponentRenderCount).not.toHaveBeenCalled() expect(screen.queryByTestId('error-component')).not.toBeInTheDocument() - expect(await pendingComponent).not.toBeInTheDocument() + expect(screen.queryByTestId('pending-component')).not.toBeInTheDocument() - const paramPage = await screen.findByTestId('param-page') expect(paramPage).toBeInTheDocument() expect(paramPage).toHaveTextContent('Param Component 1 Done') expect(loaderCompleteMock).toHaveBeenCalled() diff --git a/packages/react-router/tests/not-found.test.tsx b/packages/react-router/tests/not-found.test.tsx index 3754785d35..f27057c7ab 100644 --- a/packages/react-router/tests/not-found.test.tsx +++ b/packages/react-router/tests/not-found.test.tsx @@ -1,17 +1,24 @@ import { afterEach, beforeEach, expect, test } from 'vitest' -import { cleanup, render, screen } from '@testing-library/react' +import { act, cleanup, render, screen } from '@testing-library/react' import { Link, Outlet, RouterProvider, createBrowserHistory, + createControlledPromise, + createLazyRoute, createRootRoute, createRoute, createRouter, notFound, rootRouteId, } from '../src' +import { + RouterServer, + createRequestHandler, + renderRouterToString, +} from '../src/ssr/server' import type { NotFoundRouteProps, RouterHistory } from '../src' let history: RouterHistory @@ -27,6 +34,179 @@ afterEach(() => { cleanup() }) +test('navigating to an actively preloaded missing URL renders the global not-found boundary', async () => { + const missingLoaderStarted = createControlledPromise() + const missingLoader = createControlledPromise() + const rootRoute = createRootRoute({ + component: Outlet, + notFoundComponent: () =>
Missing URL boundary
, + loader: ({ location }) => { + if (location.pathname === '/missing') { + missingLoaderStarted.resolve() + return missingLoader + } + return + }, + shouldReload: true, + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history, + }) + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + + const preload = router.preloadRoute({ to: '/missing' } as any) + await missingLoaderStarted + const navigation = router.navigate({ to: '/missing' } as any) + missingLoader.resolve() + await act(() => Promise.all([preload, navigation])) + + expect(screen.getByText('Missing URL boundary')).toBeInTheDocument() + expect(screen.queryByText('Home')).not.toBeInTheDocument() +}) + +test('a lazy route notFoundComponent handles an eager beforeLoad failure', async () => { + const rootRoute = createRootRoute({ + component: Outlet, + notFoundComponent: () =>
Root not found
, + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const failingRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/lazy-not-found', + beforeLoad: () => { + throw notFound() + }, + }).lazy(() => + Promise.resolve( + createLazyRoute('/lazy-not-found')({ + notFoundComponent: () =>
Lazy route not found
, + }), + ), + ) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, failingRoute]), + history, + }) + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + + await act(() => router.navigate({ to: '/lazy-not-found' })) + + expect(screen.getByText('Lazy route not found')).toBeInTheDocument() + expect(screen.queryByText('Root not found')).not.toBeInTheDocument() +}) + +test('SSR uses a lazy route notFoundComponent for an eager beforeLoad failure', async () => { + const rootRoute = createRootRoute({ + component: Outlet, + notFoundComponent: () =>
Root not found
, + }) + const failingRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/lazy-not-found', + beforeLoad: () => { + throw notFound() + }, + }).lazy(() => + Promise.resolve( + createLazyRoute('/lazy-not-found')({ + notFoundComponent: () =>
Lazy route not found
, + }), + ), + ) + const handler = createRequestHandler({ + request: new Request('http://localhost/lazy-not-found'), + createRouter: () => + createRouter({ + routeTree: rootRoute.addChildren([failingRoute]), + isServer: true, + }), + }) + + const response = await handler(({ router, responseHeaders }) => + renderRouterToString({ + router, + responseHeaders, + children: , + }), + ) + + expect(response.status).toBe(404) + const html = await response.text() + expect(html).toContain('Lazy route not found') + expect(html).not.toContain('Root not found') +}) + +test.each(['client', 'server'] as const)( + 'a lazy child boundary handles a fuzzy URL miss on the %s', + async (environment) => { + const rootRoute = createRootRoute({ + component: Outlet, + notFoundComponent: () =>
Root fuzzy boundary
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + component: Outlet, + notFoundComponent: () =>
Parent fuzzy boundary
, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + }).lazy(() => + Promise.resolve( + createLazyRoute('/parent/child')({ + notFoundComponent: () =>
Lazy child fuzzy boundary
, + }), + ), + ) + const routeTree = rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + ]) + + if (environment === 'client') { + const router = createRouter({ routeTree, history }) + render() + await act(() => router.navigate({ to: '/parent/child/missing' as any })) + + expect(screen.getByText('Lazy child fuzzy boundary')).toBeInTheDocument() + expect( + screen.queryByText('Parent fuzzy boundary'), + ).not.toBeInTheDocument() + return + } + + const response = await createRequestHandler({ + request: new Request('http://localhost/parent/child/missing'), + createRouter: () => createRouter({ routeTree, isServer: true }), + })(({ router, responseHeaders }) => + renderRouterToString({ + router, + responseHeaders, + children: , + }), + ) + + expect(response.status).toBe(404) + const html = await response.text() + expect(html).toContain('Lazy child fuzzy boundary') + expect(html).not.toContain('Parent fuzzy boundary') + }, +) + test.each([ { notFoundMode: 'fuzzy' as const, diff --git a/packages/react-router/tests/on-rendered-same-href-state.test.tsx b/packages/react-router/tests/on-rendered-same-href-state.test.tsx new file mode 100644 index 0000000000..76e7a0cb9d --- /dev/null +++ b/packages/react-router/tests/on-rendered-same-href-state.test.tsx @@ -0,0 +1,66 @@ +import { act } from 'react' +import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + Outlet, + RouterProvider, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +const testCleanups: Array<() => void> = [] + +afterEach(() => { + while (testCleanups.length) { + testCleanups.pop()!() + } + cleanup() +}) + +test('onRendered fires for a same-href navigation with a new history key', async () => { + const rootRoute = createRootRoute({ component: () => }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Index
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + expect(await screen.findByText('Index')).toBeInTheDocument() + await waitFor(() => { + expect(router.state.status).toBe('idle') + expect(router.state.resolvedLocation?.href).toBe('/') + }) + const initialHistoryKey = router.state.resolvedLocation?.state.__TSR_key + expect(initialHistoryKey).toBeDefined() + + const onRendered = vi.fn() + const unsubscribe = router.subscribe('onRendered', onRendered) + testCleanups.push(unsubscribe) + await act(() => + router.navigate({ + to: '/', + state: { sameHrefState: true } as any, + }), + ) + await waitFor(() => expect(onRendered).toHaveBeenCalledTimes(1)) + + const event = onRendered.mock.calls[0]![0] + expect(event.fromLocation?.state.sameHrefState).toBeUndefined() + expect(event.toLocation.state.sameHrefState).toBe(true) + expect(event.fromLocation?.href).toBe('/') + expect(event.toLocation.href).toBe('/') + expect(event.hrefChanged).toBe(false) + expect(event.fromLocation?.state.__TSR_key).toBe(initialHistoryKey) + expect(event.toLocation.state.__TSR_key).toBeDefined() + expect(event.toLocation.state.__TSR_key).not.toBe(initialHistoryKey) + expect(router.state.resolvedLocation?.state.__TSR_key).toBe( + event.toLocation.state.__TSR_key, + ) +}) diff --git a/packages/react-router/tests/pending-fallback-promise-replacement.test.tsx b/packages/react-router/tests/pending-fallback-promise-replacement.test.tsx new file mode 100644 index 0000000000..0f5ca3703d --- /dev/null +++ b/packages/react-router/tests/pending-fallback-promise-replacement.test.tsx @@ -0,0 +1,138 @@ +import * as React from 'react' +import { act } from 'react' +import { afterEach, expect, test, vi } from 'vitest' +import { cleanup, render, screen } from '@testing-library/react' +import { createControlledPromise } from '@tanstack/router-core' +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' +import type { AnyRouter } from '../src' + +afterEach(() => { + vi.useRealTimers() + cleanup() +}) + +test.each(['child', 'root'] as const)( + 'a mounted %s pending fallback follows an overlapping load generation', + async (routeLevel) => { + const firstReload = createControlledPromise() + const secondReload = createControlledPromise() + const reloads = [firstReload, secondReload] + let loaderCall = 0 + + const routeOptions = { + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Loading...
, + loader: () => { + const generation = ++loaderCall + const gate = reloads[generation - 2] + return gate ? gate.then(() => ({ generation })) : { generation } + }, + } + + const makeRouter = (): AnyRouter => { + if (routeLevel === 'root') { + const rootRoute = createRootRoute({ + ...routeOptions, + component: () => ( +
+ Generation {rootRoute.useLoaderData().generation} +
+ ), + }) + return createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + } + + const rootRoute = createRootRoute({ + component: () => , + }) + const pageRoute = createRoute({ + ...routeOptions, + getParentRoute: () => rootRoute, + path: '/page', + component: () => ( +
+ Generation {pageRoute.useLoaderData().generation} +
+ ), + }) + return createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + } + const router = makeRouter() + + render() + expect(await screen.findByText('Generation 1')).toBeInTheDocument() + + vi.useFakeTimers() + + let firstInvalidation!: Promise + await act(async () => { + firstInvalidation = router.invalidate({ forcePending: true }) + await vi.advanceTimersByTimeAsync(0) + }) + + expect(loaderCall).toBe(2) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(25) + }) + + let secondInvalidation!: Promise + await act(async () => { + secondInvalidation = router.invalidate({ forcePending: true }) + await vi.advanceTimersByTimeAsync(0) + }) + + expect(loaderCall).toBe(3) + + await act(async () => { + firstReload.resolve() + await Promise.resolve() + }) + + // Completing the superseded generation cannot release the currently + // mounted fallback or restore its stale loader data. + expect(screen.getByTestId('pending')).toBeInTheDocument() + expect(screen.queryByText('Generation 2')).not.toBeInTheDocument() + + let secondSettled = false + void secondInvalidation.then(() => { + secondSettled = true + }) + await act(async () => { + secondReload.resolve() + await Promise.resolve() + }) + + expect(secondSettled).toBe(false) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(74) + }) + expect(secondSettled).toBe(false) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + await Promise.all([firstInvalidation, secondInvalidation]) + }) + + expect(screen.getByText('Generation 3')).toBeInTheDocument() + expect(screen.queryByTestId('pending')).not.toBeInTheDocument() + }, +) diff --git a/packages/react-router/tests/preloaded-mount-resolution.test.tsx b/packages/react-router/tests/preloaded-mount-resolution.test.tsx new file mode 100644 index 0000000000..1679546c79 --- /dev/null +++ b/packages/react-router/tests/preloaded-mount-resolution.test.tsx @@ -0,0 +1,155 @@ +import { afterEach, beforeEach, expect, onTestFinished, test, vi } from 'vitest' +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { createMemoryHistory } from '@tanstack/history' +import { + Outlet, + RouterProvider, + createControlledPromise, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +/** + * A load that settles before RouterProvider mounts (or completes within the + * mount effect's batch) gives the Transitioner no isLoading flip to observe. + * The router status must still resolve to 'idle' with resolvedLocation set, + * and onRendered must fire — otherwise consumers waiting on those signals + * deadlock forever (this hung the memory-client benchmark for 6 hours). + * + * Uses a raw createRoot without the act() test environment: act-driven + * flushing re-renders between the isLoading toggles and masks the race. + * + * Note: vitest's jsdom scheduler still observes the flip more often than the + * benchmark's environment, so this test pins the CONTRACT; the deterministic + * regression guard for the original hang is the memory-client:react + * benchmark (benchmarks/memory/client/scenarios/mount-unmount), which CI runs. + */ + +let prevActEnv: unknown + +beforeEach(() => { + prevActEnv = (globalThis as any).IS_REACT_ACT_ENVIRONMENT + ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = false +}) + +afterEach(() => { + ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = prevActEnv +}) + +test('mounting after a settled load still resolves status and fires onRendered', async () => { + const lifecycle: Array<'layout' | 'rendered'> = [] + const Home = () => { + React.useLayoutEffect(() => { + lifecycle.push('layout') + }, []) + return
Home
+ } + const rootRoute = createRootRoute({ + component: () => , + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: () => 'home data', + component: Home, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + let resolveRendered!: () => void + const rendered = new Promise((resolve) => { + resolveRendered = resolve + }) + const onRendered = vi.fn(() => { + lifecycle.push('rendered') + resolveRendered() + }) + const onResolved = vi.fn() + const onLoad = vi.fn() + const unsubscribers = [ + router.subscribe('onRendered', onRendered), + router.subscribe('onResolved', onResolved), + router.subscribe('onLoad', onLoad), + ] + const unsubscribe = () => unsubscribers.forEach((fn) => fn()) + const container = document.createElement('div') + document.body.appendChild(container) + const reactRoot = createRoot(container) + let renderedTimeout: ReturnType | undefined + + onTestFinished(() => { + clearTimeout(renderedTimeout) + unsubscribe() + reactRoot.unmount() + container.remove() + }) + + // Load fully settles before the provider mounts — the exact shape of the + // memory benchmark's mount/unmount cycle. + await router.load() + expect(router.state.status).toBe('idle') + expect(router.state.resolvedLocation?.pathname).toBe('/') + expect(onLoad).toHaveBeenCalledTimes(1) + expect(onResolved).toHaveBeenCalledTimes(1) + expect(onRendered).not.toHaveBeenCalled() + + reactRoot.render() + await Promise.race([ + rendered, + new Promise((_, reject) => { + renderedTimeout = setTimeout(() => { + reject(new Error('Timed out waiting for onRendered')) + }, 2000) + }), + ]) + + expect(container.querySelector('[data-testid="home"]')).not.toBeNull() + expect(onRendered).toHaveBeenCalledTimes(1) + expect(lifecycle).toEqual(['layout', 'rendered']) + expect(router.state.status).toBe('idle') + expect(router.state.resolvedLocation?.pathname).toBe('/') +}) + +test('mounting during a load keeps the existing generation', async () => { + const gate = createControlledPromise() + const beforeLoad = vi.fn() + const loader = vi.fn(() => gate) + const rootRoute = createRootRoute({ component: () => }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + beforeLoad, + loader, + component: () =>
Home
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + const load = router.load() + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + const container = document.createElement('div') + document.body.appendChild(container) + const reactRoot = createRoot(container) + + onTestFinished(() => { + reactRoot.unmount() + container.remove() + }) + + reactRoot.render() + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledOnce()) + gate.resolve() + await load + await vi.waitFor(() => { + expect(container.querySelector('[data-testid="home"]')).not.toBeNull() + }) + + expect(beforeLoad).toHaveBeenCalledOnce() + expect(loader).toHaveBeenCalledOnce() +}) diff --git a/packages/react-router/tests/public-presentation-lane-contract.test.tsx b/packages/react-router/tests/public-presentation-lane-contract.test.tsx new file mode 100644 index 0000000000..862fffc93f --- /dev/null +++ b/packages/react-router/tests/public-presentation-lane-contract.test.tsx @@ -0,0 +1,244 @@ +import { act, cleanup, render, screen, waitFor } from '@testing-library/react' +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createControlledPromise } from '@tanstack/router-core' +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(() => { + cleanup() + vi.useRealTimers() +}) + +describe('public presentation lane contracts', () => { + test('visible pending UI publishes every matched route and its loading state', async () => { + const parentGate = createControlledPromise() + + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const parentRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + pendingMs: 0, + pendingComponent: () =>
Loading parent
, + loader: () => parentGate, + component: Outlet, + }) + const childRoute = createRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => 'child data', + component: () =>
Child content
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + await waitFor(() => expect(router.state.status).toBe('idle')) + + let navigation!: Promise + await act(async () => { + navigation = router.navigate({ to: '/parent/child' }) + await Promise.resolve() + }) + + expect(await screen.findByText('Loading parent')).toBeInTheDocument() + expect(screen.queryByText('Child content')).not.toBeInTheDocument() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ status: 'pending', isFetching: 'loader' }) + + await act(async () => { + parentGate.resolve('parent data') + await navigation + }) + + expect(screen.getByText('Child content')).toBeInTheDocument() + expect(router.state.status).toBe('idle') + }) + + test('same-boundary takeover republishes successor search without restarting pendingMinMs', async () => { + const firstGate = createControlledPromise() + const secondGate = createControlledPromise() + + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + validateSearch: (search: Record) => ({ + revision: Number(search.revision), + }), + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Loading page
, + beforeLoad: ({ search }) => + search.revision === 1 ? firstGate : secondGate, + component: () => { + const search = pageRoute.useSearch() + return
Page revision {search.revision}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, pageRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + await waitFor(() => expect(router.state.status).toBe('idle')) + + vi.useFakeTimers() + vi.setSystemTime(0) + + let successorSettled = false + let settledAtOriginalDeadline = false + let renderedAtOriginalDeadline = false + try { + await act(async () => { + void router.navigate({ + to: '/page', + search: { revision: 1 }, + }) + await vi.advanceTimersByTimeAsync(0) + }) + expect(screen.getByText('Loading page')).toBeInTheDocument() + expect(router.state.matches.at(-1)?.search).toMatchObject({ revision: 1 }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(25) + }) + + let secondNavigation!: Promise + await act(async () => { + secondNavigation = router.navigate({ + to: '/page', + search: { revision: 2 }, + }) + await vi.advanceTimersByTimeAsync(0) + }) + + expect(screen.getByText('Loading page')).toBeInTheDocument() + expect(router.state.location.search).toMatchObject({ revision: 2 }) + expect(router.state.matches.at(-1)?.search).toMatchObject({ revision: 2 }) + + void secondNavigation.then(() => { + successorSettled = true + }) + await act(async () => { + secondGate.resolve() + await Promise.resolve() + }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(74) + }) + expect(successorSettled).toBe(false) + expect(screen.getByText('Loading page')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + await Promise.resolve() + }) + + settledAtOriginalDeadline = successorSettled + renderedAtOriginalDeadline = + screen.queryByText('Page revision 2') !== null + } finally { + // Finish a faulty implementation too, so a deadline assertion cannot + // strand this router and contaminate the following test. + firstGate.resolve() + secondGate.resolve() + await act(async () => { + await vi.advanceTimersByTimeAsync(1_000) + await Promise.resolve() + }) + } + + expect({ + settled: settledAtOriginalDeadline, + rendered: renderedAtOriginalDeadline, + }).toEqual({ settled: true, rendered: true }) + expect(screen.getByText('Page revision 2')).toBeInTheDocument() + expect(screen.queryByText('Loading page')).not.toBeInTheDocument() + }) + + test('a reentrant navigation from onResolved suppresses the stale onRendered event', async () => { + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home
, + }) + const firstRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/first', + component: () =>
First
, + }) + const secondRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/second', + component: () =>
Second
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + expect(await screen.findByText('Home')).toBeInTheDocument() + await waitFor(() => expect(router.state.status).toBe('idle')) + + const renderedPaths: Array = [] + let successor: Promise | undefined + const unsubscribeResolved = router.subscribe('onResolved', (event) => { + if (event.toLocation.pathname === '/first') { + successor = router.navigate({ to: '/second' }) + } + }) + const unsubscribeRendered = router.subscribe('onRendered', (event) => { + if (event.toLocation.pathname !== '/') { + renderedPaths.push(event.toLocation.pathname) + } + }) + + try { + await act(() => router.navigate({ to: '/first' })) + await act(async () => { + await successor + }) + + expect(screen.getByText('Second')).toBeInTheDocument() + expect(screen.queryByText('First')).not.toBeInTheDocument() + expect(renderedPaths).toEqual(['/second']) + } finally { + unsubscribeResolved() + unsubscribeRendered() + } + }) +}) diff --git a/packages/react-router/tests/react-render-owner-contract.test.tsx b/packages/react-router/tests/react-render-owner-contract.test.tsx new file mode 100644 index 0000000000..0797fa5264 --- /dev/null +++ b/packages/react-router/tests/react-render-owner-contract.test.tsx @@ -0,0 +1,101 @@ +import { act } from 'react' +import { cleanup, render, screen, waitFor } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' +import { + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRouter, +} from '../src' + +const testCleanups: Array<() => void> = [] + +afterEach(() => { + while (testCleanups.length) { + testCleanups.pop()!() + } + cleanup() +}) + +test('a suspended same-membership publication cannot acknowledge its successor', async () => { + const firstRenderStarted = createControlledPromise() + const firstRenderGate = createControlledPromise() + let signaledFirstRender = false + + const rootRoute = createRootRoute({ + validateSearch: (search: Record) => ({ + revision: Number(search.revision), + }), + component: () => { + const revision = rootRoute.useSearch().revision + if (revision === 1 && firstRenderGate.status === 'pending') { + if (!signaledFirstRender) { + signaledFirstRender = true + firstRenderStarted.resolve() + } + throw firstRenderGate + } + return
Root revision {revision}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/?revision=0'] }), + }) + + render() + expect(await screen.findByText('Root revision 0')).toBeInTheDocument() + await waitFor(() => expect(router.state.status).toBe('idle')) + const initialIds = router.state.matches.map((match) => match.routeId) + + const renderedRevisions: Array = [] + const unsubscribe = router.subscribe('onRendered', (event) => { + renderedRevisions.push( + Number((event.toLocation.search as Record).revision), + ) + }) + testCleanups.push(unsubscribe) + + let firstNavigation!: Promise + await act(async () => { + firstNavigation = router.navigate({ + to: '/', + search: { revision: 1 }, + }) + await firstRenderStarted + }) + + const firstSettled = vi.fn() + void firstNavigation.then(firstSettled) + expect(router.state.matches.map((match) => match.routeId)).toEqual(initialIds) + expect(router.state.matches[0]?.search.revision).toBe(1) + expect(screen.getByText('Root revision 0')).toBeInTheDocument() + expect(firstSettled).not.toHaveBeenCalled() + expect(renderedRevisions).toEqual([]) + + try { + await act(() => + router.navigate({ + to: '/', + search: { revision: 2 }, + }), + ) + await firstNavigation + + expect(screen.getByText('Root revision 2')).toBeInTheDocument() + expect(router.state.matches.map((match) => match.routeId)).toEqual( + initialIds, + ) + expect(renderedRevisions).toEqual([2]) + expect(firstSettled).toHaveBeenCalledOnce() + } finally { + await act(async () => { + firstRenderGate.resolve() + await Promise.resolve() + }) + } + + expect(screen.getByText('Root revision 2')).toBeInTheDocument() + expect(renderedRevisions).toEqual([2]) +}) diff --git a/packages/react-router/tests/redirect-chain-first-load.test.tsx b/packages/react-router/tests/redirect-chain-first-load.test.tsx new file mode 100644 index 0000000000..b3f4618a28 --- /dev/null +++ b/packages/react-router/tests/redirect-chain-first-load.test.tsx @@ -0,0 +1,110 @@ +import * as React from 'react' +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, test, vi } from 'vitest' + +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + redirect, +} from '../src' +import { sleep } from './utils' + +afterEach(() => { + vi.restoreAllMocks() + cleanup() +}) + +// A chain of async layout beforeLoad redirects during the very first load +// (search-stripping self-redirect -> layout redirect -> child redirect) used +// to leave a match rendering with a nulled loadPromise, crashing +// MatchInnerImpl with an uncaught `undefined`. Pending UI is enabled for +// every match (defaultPendingMs: 0) to force pending publication mid-chain. +// The production auto-code-splitting reproduction for issue #7457 lives in +// e2e/react-router/issue-7457. +test('chained layout beforeLoad redirects on first load render the final target without throwing from MatchInner', async () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + const rootRoute = createRootRoute({ component: () => }) + + const userLayout = createRoute({ + id: 'user', + getParentRoute: () => rootRoute, + validateSearch: (search: Record): { flag?: boolean } => ({ + flag: search.flag === true || search.flag === 'true' ? true : undefined, + }), + beforeLoad: async ({ search }) => { + if (search.flag) { + await sleep(20) + throw redirect({ + to: '.', + replace: true, + search: (prev: any) => ({ ...prev, flag: undefined }), + }) + } + }, + component: () => , + }) + + const dashboardLayout = createRoute({ + id: 'dashboard', + getParentRoute: () => userLayout, + beforeLoad: async () => { + await sleep(30) + throw redirect({ to: '/intro', replace: true }) + }, + component: () => , + }) + + const homeRoute = createRoute({ + getParentRoute: () => dashboardLayout, + path: '/home', + component: () =>
Home
, + }) + + const introLayout = createRoute({ + getParentRoute: () => userLayout, + path: '/intro', + beforeLoad: ({ location }) => { + if (location.pathname !== '/intro/step') { + throw redirect({ to: '/intro/step', replace: true }) + } + }, + component: () => , + }) + + const introIndexRoute = createRoute({ + getParentRoute: () => introLayout, + path: '/', + component: () =>
Intro index
, + }) + + const introStepRoute = createRoute({ + getParentRoute: () => introLayout, + path: 'step', + component: () =>
Intro step
, + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([ + userLayout.addChildren([ + dashboardLayout.addChildren([homeRoute]), + introLayout.addChildren([introIndexRoute, introStepRoute]), + ]), + ]), + defaultPendingMs: 0, + defaultPendingComponent: () =>
loading
, + history: createMemoryHistory({ initialEntries: ['/home?flag=true'] }), + }) + + render() + + expect( + await screen.findByTestId('intro-step', undefined, { timeout: 5_000 }), + ).toBeInTheDocument() + expect(router.state.location.pathname).toBe('/intro/step') + expect(consoleError).not.toHaveBeenCalled() +}) diff --git a/packages/react-router/tests/redirect.test.tsx b/packages/react-router/tests/redirect.test.tsx index cc15f0da36..0c254fb098 100644 --- a/packages/react-router/tests/redirect.test.tsx +++ b/packages/react-router/tests/redirect.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { + act, cleanup, configure, fireEvent, @@ -8,13 +9,13 @@ import { } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { createControlledPromise } from '@tanstack/router-core' import { Link, Outlet, RouterProvider, createBrowserHistory, - createMemoryHistory, createRootRoute, createRoute, createRouter, @@ -45,6 +46,114 @@ const WAIT_TIME = 100 describe('redirect', () => { describe('SPA', () => { configure({ reactStrictMode: true }) + + test('allows a same-location redirect to settle after a side effect', async () => { + let firstLoad = true + const loader = vi.fn(() => { + if (firstLoad) { + firstLoad = false + throw redirect({ to: '/' }) + } + }) + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader, + component: () =>
Index page
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history, + }) + + render() + + expect(await screen.findByText('Index page')).toBeInTheDocument() + expect(window.location.pathname).toBe('/') + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.status).toBe('idle') + }) + + test('renders a root error after too many same-location redirects', async () => { + const loader = vi.fn(() => { + throw redirect({ to: '/' }) + }) + const rootRoute = createRootRoute({ + errorComponent: ({ error }) => ( +
Root: {error.message}
+ ), + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader, + errorComponent: ({ error }) => ( +
Index: {error.message}
+ ), + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history, + }) + + render() + + expect(await screen.findByTestId('root-error')).toHaveTextContent( + 'Root: Too many redirects', + ) + expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() + expect(window.location.pathname).toBe('/') + expect(loader).toHaveBeenCalledTimes(21) + expect(router.state.status).toBe('idle') + }) + + test('renders a root error after too many alternating redirects', async () => { + const indexLoader = vi.fn(() => { + throw redirect({ to: '/other' }) + }) + const otherLoader = vi.fn(() => { + throw redirect({ to: '/' }) + }) + const rootRoute = createRootRoute({ + errorComponent: ({ error }) => ( +
Root: {error.message}
+ ), + }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: indexLoader, + errorComponent: ({ error }) => ( +
Index: {error.message}
+ ), + }) + const otherRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/other', + loader: otherLoader, + errorComponent: ({ error }) => ( +
Other: {error.message}
+ ), + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, otherRoute]), + history, + }) + + render() + + expect(await screen.findByTestId('root-error')).toHaveTextContent( + 'Root: Too many redirects', + ) + expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() + expect(screen.queryByTestId('other-error')).not.toBeInTheDocument() + expect(window.location.pathname).toBe('/') + expect(indexLoader).toHaveBeenCalledTimes(11) + expect(otherLoader).toHaveBeenCalledTimes(10) + expect(router.state.status).toBe('idle') + }) + test('when `redirect` is thrown in `beforeLoad`', async () => { const nestedLoaderMock = vi.fn() const nestedFooLoaderMock = vi.fn() @@ -115,6 +224,7 @@ describe('redirect', () => { test('when root `beforeLoad` redirects while root pendingComponent is showing and the target route is lazy', async () => { let hasRedirected = false + const beforeLoad = createControlledPromise() const consoleError = vi .spyOn(console, 'error') .mockImplementation(() => {}) @@ -124,7 +234,7 @@ describe('redirect', () => { pendingMs: 0, pendingComponent: () =>
loading
, beforeLoad: async () => { - await sleep(WAIT_TIME) + await beforeLoad if (!hasRedirected) { hasRedirected = true throw redirect({ to: '/posts' }) @@ -150,6 +260,14 @@ describe('redirect', () => { render() + try { + expect(await screen.findByTestId('pending')).toBeInTheDocument() + } finally { + await act(() => { + beforeLoad.resolve() + }) + } + // The lazy target route adds the async boundary that exposes the stale // redirected-match render path this regression is guarding. expect(await screen.findByTestId('lazy-route-page')).toBeInTheDocument() @@ -311,116 +429,4 @@ describe('redirect', () => { expect(window.location.pathname).toBe('/final') }) }) - - describe('SSR', () => { - test('when `redirect` is thrown in `beforeLoad`', async () => { - const rootRoute = createRootRoute() - - const indexRoute = createRoute({ - path: '/', - getParentRoute: () => rootRoute, - beforeLoad: () => { - throw redirect({ - to: '/about', - }) - }, - }) - - const aboutRoute = createRoute({ - path: '/about', - getParentRoute: () => rootRoute, - component: () => { - return 'About' - }, - }) - - const router = createRouter({ - routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), - // Mock server mode - isServer: true, - history: createMemoryHistory({ - initialEntries: ['/'], - }), - }) - - await router.load() - - expect(router.state.redirect).toBeDefined() - expect(router.state.redirect).toBeInstanceOf(Response) - const redirectResponse = router.state.redirect! - - expect(redirectResponse.options).toEqual({ - _fromLocation: expect.objectContaining({ - hash: '', - href: '/', - pathname: '/', - search: {}, - searchStr: '', - }), - to: '/about', - href: '/about', - statusCode: 307, - }) - }) - - test('when `redirect` is thrown in `loader`', async () => { - const rootRoute = createRootRoute() - - const indexRoute = createRoute({ - path: '/', - getParentRoute: () => rootRoute, - loader: () => { - throw redirect({ - to: '/about', - }) - }, - }) - - const aboutRoute = createRoute({ - path: '/about', - getParentRoute: () => rootRoute, - component: () => { - return 'About' - }, - }) - - const router = createRouter({ - history: createMemoryHistory({ - initialEntries: ['/'], - }), - routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), - // Mock server mode - isServer: true, - }) - - await router.load() - - const currentRedirect = router.state.redirect - - expect(currentRedirect).toBeDefined() - expect(currentRedirect).toBeInstanceOf(Response) - const redirectResponse = currentRedirect! - expect(redirectResponse.status).toEqual(307) - expect(redirectResponse.headers.get('Location')).toEqual('/about') - expect(redirectResponse.options).toEqual({ - _fromLocation: { - external: false, - hash: '', - href: '/', - publicHref: '/', - pathname: '/', - search: {}, - searchStr: '', - state: { - __TSR_index: 0, - __TSR_key: redirectResponse.options._fromLocation!.state.__TSR_key, - key: redirectResponse.options._fromLocation!.state.key, - }, - }, - href: '/about', - to: '/about', - statusCode: 307, - }) - }) - }) }) diff --git a/packages/react-router/tests/renderRouterToStream.test.tsx b/packages/react-router/tests/renderRouterToStream.test.tsx index 74e180a552..f42456b7be 100644 --- a/packages/react-router/tests/renderRouterToStream.test.tsx +++ b/packages/react-router/tests/renderRouterToStream.test.tsx @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test, vi } from 'vitest' +import { afterEach, describe, expect, onTestFinished, test, vi } from 'vitest' import { attachRouterServerSsrUtils } from '@tanstack/router-core/ssr/server' import { createMemoryHistory, createRootRoute, createRouter } from '../src' @@ -52,6 +52,35 @@ function unwrapResponse( } describe('renderRouterToStream - pipeable sync errors', () => { + test('request abort cancels readable rendering without consuming the response body', async () => { + const cancel = vi.fn() + const stream = Object.assign(new ReadableStream({ cancel }), { + allReady: Promise.resolve(), + }) + reactDomServerMocks.renderToReadableStream = vi.fn(() => stream) + + const router = await buildRouter() + const controller = new AbortController() + onTestFinished(() => { + router.serverSsr?.cleanup() + }) + + const response = unwrapResponse( + await renderRouterToStream({ + request: new Request('http://localhost/', { + signal: controller.signal, + }), + router, + responseHeaders: new Headers(), + children: null, + }), + ) + + expect(response.body).not.toBeNull() + controller.abort(new Error('request-gone')) + await vi.waitFor(() => expect(cancel).toHaveBeenCalledOnce()) + }) + test('sync onError before pipeable is assigned still aborts pipeable', async () => { const abort = vi.fn() reactDomServerMocks.renderToPipeableStream.mockImplementationOnce( @@ -197,7 +226,7 @@ describe('renderRouterToStream - pipeable sync errors', () => { } }) - test('request abort aborts pipeable and errors body', async () => { + test('request abort cancels pipeable rendering before the response body is consumed', async () => { const abort = vi.fn() reactDomServerMocks.renderToPipeableStream.mockImplementationOnce( (_children, opts) => { @@ -220,13 +249,14 @@ describe('renderRouterToStream - pipeable sync errors', () => { }), ) + expect(response.body).not.toBeNull() controller.abort(new Error('request-gone')) + await vi.waitFor(() => expect(abort).toHaveBeenCalledOnce()) const terminated = await Promise.race([ expectBodyRejects(response, 'request-gone').then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 2000)), ]) expect(terminated).toBe(true) - expect(abort).toHaveBeenCalledOnce() } finally { router.serverSsr?.cleanup() } diff --git a/packages/react-router/tests/root-pending-min.test.tsx b/packages/react-router/tests/root-pending-min.test.tsx new file mode 100644 index 0000000000..0216ae4fec --- /dev/null +++ b/packages/react-router/tests/root-pending-min.test.tsx @@ -0,0 +1,186 @@ +import * as React from 'react' +import { act, cleanup, render, screen } from '@testing-library/react' +import { hydrateRoot } from 'react-dom/client' +import { renderToString } from 'react-dom/server' +import { afterEach, expect, test, vi } from 'vitest' +import { dehydrateSsrMatchId } from '../../router-core/src/ssr/ssr-match-id' +import { hydrate } from '../src/ssr/client' +import { + RouterProvider, + createControlledPromise, + createMemoryHistory, + createRootRoute, + createRouter, +} from '../src' + +const testCleanups: Array<() => void | Promise> = [] + +afterEach(async () => { + while (testCleanups.length) { + await testCleanups.pop()!() + } + cleanup() + vi.useRealTimers() + delete window.$_TSR +}) + +test('a post-hydration root reload keeps its fallback through pendingMinMs', async () => { + const reloadGate = createControlledPromise() + const rootLoader = vi.fn(() => reloadGate.then(() => ({ generation: 2 }))) + const rootRoute = createRootRoute({ + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Pending
, + loader: rootLoader, + component: () => ( +
+ Generation {rootRoute.useLoaderData().generation} +
+ ), + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + const rootMatch = router.matchRoutes(router.latestLocation)[0]! + // This is the same public bootstrap shape produced by the server. Calling + // hydrate() ensures router.ssr and the active match are established through + // the real client hydration path rather than by mutating router stores. + window.$_TSR = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + matches: [ + { + i: dehydrateSsrMatchId(rootMatch.id), + s: 'success', + ssr: true, + l: { generation: 1 }, + u: Date.now(), + }, + ], + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(router) + expect(router.ssr).toBeDefined() + + render() + expect(screen.getByTestId('root-content')).toHaveTextContent('Generation 1') + expect(rootLoader).not.toHaveBeenCalled() + + vi.useFakeTimers() + + let invalidation!: Promise + await act(async () => { + invalidation = router.invalidate({ forcePending: true }) + await vi.advanceTimersByTimeAsync(0) + }) + + expect(rootLoader).toHaveBeenCalledTimes(1) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + expect(screen.getByTestId('root-content')).not.toBeVisible() + + await act(async () => { + reloadGate.resolve() + await Promise.resolve() + }) + + await act(async () => { + await vi.advanceTimersByTimeAsync(99) + }) + expect(screen.getByTestId('root-pending')).toBeInTheDocument() + + await act(async () => { + await vi.advanceTimersByTimeAsync(1) + await invalidation + }) + expect(screen.queryByTestId('root-pending')).not.toBeInTheDocument() + expect(screen.getByTestId('root-content')).toHaveTextContent('Generation 2') +}) + +test('root route hydration preserves component state across its Suspense boundary', async () => { + const mounts = vi.fn() + const unmounts = vi.fn() + const initializers = vi.fn(() => 'preserved') + + const rootRoute = createRootRoute({ + pendingComponent: () =>
Root pending
, + component: function RootComponent() { + const [value] = React.useState(initializers) + React.useEffect(() => { + mounts() + return unmounts + }, []) + return
{value}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + // Model the server render and the client router produced by hydrate(). The + // outer root boundary is intentionally absent from both trees, while the + // route's own pending boundary is present in both. + router.ssr = { manifest: { routes: {} } } + router.isServer = true + const html = renderToString() + router.isServer = false + expect(html).toContain('') + expect(html).toContain('preserved') + + const container = document.createElement('div') + container.innerHTML = html + document.body.appendChild(container) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + let root!: ReturnType + await act(async () => { + root = hydrateRoot(container, ) + testCleanups.push(async () => { + await act(() => root.unmount()) + consoleError.mockRestore() + container.remove() + }) + await Promise.resolve() + }) + + expect(container).toHaveTextContent('preserved') + // One initializer belongs to the server render and one to client + // hydration. The stable boundary must preserve that hydrated client + // instance instead of creating a third one. + expect(initializers).toHaveBeenCalledTimes(2) + expect(mounts).toHaveBeenCalledTimes(1) + expect(unmounts).not.toHaveBeenCalled() + expect(consoleError).not.toHaveBeenCalled() +}) + +test('server rendering uses the root pending boundary for route component suspension', async () => { + const gate = createControlledPromise() + const rootRoute = createRootRoute({ + pendingComponent: () =>
Server root pending
, + component: () => { + throw gate + }, + }) + const router = createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + router.isServer = true + await router.load() + + const html = renderToString() + + // renderToString cannot wait for Suspense, but the root route's stable + // boundary contains the suspension and emits its fallback. Streaming SSR + // can wait for the same boundary instead. + expect(html).toContain('Server root pending') +}) diff --git a/packages/react-router/tests/routeContext.test.tsx b/packages/react-router/tests/routeContext.test.tsx index d11f86420e..e00228e280 100644 --- a/packages/react-router/tests/routeContext.test.tsx +++ b/packages/react-router/tests/routeContext.test.tsx @@ -141,7 +141,7 @@ describe('context function', () => { }) test('when loader deps change', async () => { - const mockContextFn = vi.fn() + let generation = 0 const rootRoute = createRootRoute() const indexRoute = createRoute({ @@ -153,14 +153,21 @@ describe('context function', () => { path: '/', loaderDeps: ({ search }) => ({ foo: search.foo }), context: ({ deps }) => { - mockContextFn(deps) + return { + generation: ++generation, + deps: JSON.stringify(deps), + } }, component: () => { const navigate = indexRoute.useNavigate() + const context = indexRoute.useRouteContext() return (

Index page

search: {JSON.stringify(indexRoute.useSearch())}

+

+ context: {context.generation}:{context.deps} +

} ``` -For synchronous invalidation (wait until loaders finish): +Use `await router.invalidate({ sync: true })` when the next step requires refreshed loader data. -```tsx -await router.invalidate({ sync: true }) -``` +Treat the mutation and invalidation as one workflow. The mutation must persist before invalidation starts, and the loader must read from the same authoritative store. Verify create, update, and delete through the rendered route, including a fresh reload; local component state can hide a stale loader or non-persistent write. ### Error Handling @@ -361,16 +376,13 @@ export const Route = createFileRoute('/posts')({ }, }) -// CORRECT — loaders run in the browser, use fetch or API calls +// CORRECT for an SPA — use a client-safe API helper export const Route = createFileRoute('/posts')({ - loader: async () => { - const res = await fetch('/api/posts') - return res.json() - }, + loader: () => fetchPosts(), }) ``` -Do NOT put database queries, filesystem access, or server-only code in loaders unless you are using TanStack Start server functions. +Do NOT put database queries, filesystem access, or server-only code directly in loaders. In TanStack Start, put that work in a server function and call the function from the loader. Do not use a relative `fetch('/api/...')` in an SSR loader. ### MEDIUM: Not understanding staleTime default is 0 diff --git a/packages/router-core/skills/router-core/navigation/SKILL.md b/packages/router-core/skills/router-core/navigation/SKILL.md index 0e09ddbcec..ba1f0fd2e6 100644 --- a/packages/router-core/skills/router-core/navigation/SKILL.md +++ b/packages/router-core/skills/router-core/navigation/SKILL.md @@ -1,14 +1,15 @@ --- -name: router-core/navigation +name: navigation description: >- Link component, useNavigate, Navigate component, router.navigate, ToOptions/NavigateOptions/LinkOptions, from/to relative navigation, activeOptions/activeProps, preloading (intent/viewport/render), preloadDelay, navigation blocking (useBlocker, Block), createLink, linkOptions helper, scroll restoration, MatchRoute. -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core sources: diff --git a/packages/router-core/skills/router-core/not-found-and-errors/SKILL.md b/packages/router-core/skills/router-core/not-found-and-errors/SKILL.md index b4fde048f8..2a69f16888 100644 --- a/packages/router-core/skills/router-core/not-found-and-errors/SKILL.md +++ b/packages/router-core/skills/router-core/not-found-and-errors/SKILL.md @@ -1,13 +1,14 @@ --- -name: router-core/not-found-and-errors +name: not-found-and-errors description: >- notFound() function, notFoundComponent, defaultNotFoundComponent, notFoundMode (fuzzy/root), errorComponent, CatchBoundary, CatchNotFound, isNotFound, NotFoundRoute (deprecated), route masking (mask option, createRouteMask, unmaskOnReload). -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core sources: diff --git a/packages/router-core/skills/router-core/path-params/SKILL.md b/packages/router-core/skills/router-core/path-params/SKILL.md index 32964d06cc..b4819f04b5 100644 --- a/packages/router-core/skills/router-core/path-params/SKILL.md +++ b/packages/router-core/skills/router-core/path-params/SKILL.md @@ -1,13 +1,14 @@ --- -name: router-core/path-params +name: path-params description: >- Dynamic path segments ($paramName), splat routes ($ / _splat), optional params ({-$paramName}), prefix/suffix patterns ({$param}.ext), useParams, params.parse/stringify, pathParamsAllowedCharacters, i18n locale patterns. -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core sources: diff --git a/packages/router-core/skills/router-core/search-params/SKILL.md b/packages/router-core/skills/router-core/search-params/SKILL.md index 5b509e5a50..0960297d00 100644 --- a/packages/router-core/skills/router-core/search-params/SKILL.md +++ b/packages/router-core/skills/router-core/search-params/SKILL.md @@ -1,13 +1,14 @@ --- -name: router-core/search-params +name: search-params description: >- validateSearch, search param validation with Zod/Valibot/ArkType adapters, fallback(), search middlewares (retainSearchParams, stripSearchParams), custom serialization (parseSearch, stringifySearch), search param inheritance, loaderDeps for cache keys, reading and writing search params. -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core sources: diff --git a/packages/router-core/skills/router-core/ssr/SKILL.md b/packages/router-core/skills/router-core/ssr/SKILL.md index de3fc9a6d7..d1e0524f13 100644 --- a/packages/router-core/skills/router-core/ssr/SKILL.md +++ b/packages/router-core/skills/router-core/ssr/SKILL.md @@ -1,5 +1,5 @@ --- -name: router-core/ssr +name: ssr description: >- Non-streaming and streaming SSR, RouterClient/RouterServer, renderRouterToString/renderRouterToStream, createRequestHandler, @@ -7,9 +7,10 @@ description: >- components, head route option (meta/links/styles/scripts), ScriptOnce, automatic loader dehydration/hydration, memory history on server, data serialization, document head management. -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core - router-core/data-loading diff --git a/packages/router-core/skills/router-core/type-safety/SKILL.md b/packages/router-core/skills/router-core/type-safety/SKILL.md index 7b7c53bfa8..ce9204677e 100644 --- a/packages/router-core/skills/router-core/type-safety/SKILL.md +++ b/packages/router-core/skills/router-core/type-safety/SKILL.md @@ -1,14 +1,15 @@ --- -name: router-core/type-safety +name: type-safety description: >- Full type inference philosophy (never cast, never annotate inferred values), Register module declaration, from narrowing on hooks and Link, strict:false for shared components, getRouteApi for code-split typed access, addChildren with object syntax for TS perf, LinkProps and ValidateLinkOptions type utilities, as const satisfies pattern. -type: sub-skill -library: tanstack-router -library_version: '1.166.2' +metadata: + type: sub-skill + library: tanstack-router + library_version: '1.171.15' requires: - router-core sources: @@ -489,4 +490,10 @@ const search = Route.useSearch() If a build error mentions `react-router-dom`, `next/`, `pages/_app`, or duplicate `/` routes, fix the import — don't paper over with type assertions. +### 6. CRITICAL: Treating typecheck as proof of runtime schema propagation + +Types can say a field exists while a database projection, API serializer, or server function omits it. When adding or renaming a field, trace the value through storage, validation, handler output, loader data, and rendered UI. Do not cast the response to the desired type. +Add a runtime assertion against the real handler or serialized response, such as `expect(await getOrder({ data: { id } })).toMatchObject({ totalCents: 2599 })`. +Then run the route-level test and production build. The type test remains necessary, but it is not the runtime contract test. + See also: router-core (Register setup), router-core/navigation (from narrowing), router-core/code-splitting (getRouteApi). diff --git a/packages/router-core/src/Matches.ts b/packages/router-core/src/Matches.ts index 852d186b67..41fd4018b5 100644 --- a/packages/router-core/src/Matches.ts +++ b/packages/router-core/src/Matches.ts @@ -9,7 +9,7 @@ import type { RouteIds, } from './routeInfo' import type { AnyRouter, RegisteredRouter, SSROption } from './router' -import type { Constrain, ControlledPromise } from './utils' +import type { Constrain } from './utils' export type AnyMatchAndValue = { match: any; value: any } @@ -131,47 +131,32 @@ export interface RouteMatch< pathname: string params: TAllParams _strictParams: TAllParams - status: 'pending' | 'success' | 'error' | 'redirected' | 'notFound' + status: 'pending' | 'success' | 'error' | 'notFound' isFetching: false | 'beforeLoad' | 'loader' error: unknown paramsError: unknown searchError: unknown updatedAt: number - _nonReactive: { - /** @internal */ - beforeLoadPromise?: ControlledPromise - /** @internal */ - loaderPromise?: ControlledPromise - /** @internal */ - pendingTimeout?: ReturnType - loadPromise?: ControlledPromise - displayPendingPromise?: Promise - minPendingPromise?: ControlledPromise - dehydrated?: boolean - /** @internal */ - error?: unknown - } loaderData?: TLoaderData + /** @internal Exclusive end of the SSR-verified asset prefix. */ + _assetEnd?: number /** @internal */ - __routeContext?: Record + _ctx?: Record /** @internal */ __beforeLoadContext?: Record context: TAllContext search: TFullSearchSchema _strictSearch: TFullSearchSchema - fetchCount: number abortController: AbortController cause: 'preload' | 'enter' | 'stay' loaderDeps: TLoaderDeps preload: boolean invalid: boolean headers?: Record - globalNotFound?: boolean + _notFound?: boolean staticData: StaticDataRouteOption /** This attribute is not reactive */ ssr?: SSROption - _forcePending?: boolean - _displayPending?: boolean } export interface PreValidationErrorHandlingRouteMatch< diff --git a/packages/router-core/src/await-signal.ts b/packages/router-core/src/await-signal.ts new file mode 100644 index 0000000000..c3506e2abd --- /dev/null +++ b/packages/router-core/src/await-signal.ts @@ -0,0 +1,27 @@ +export function waitForReason( + value: T | PromiseLike, + signal: AbortSignal, + onLate?: (value: T) => void, +): Promise { + const promise = Promise.resolve(value) + if (signal.aborted) { + if (!onLate) { + return Promise.race([Promise.reject(signal.reason), promise]) + } + void promise.then(onLate, () => {}) + return Promise.reject(signal.reason) + } + return new Promise((resolve, reject) => { + const abort = () => reject(signal.reason) + signal.addEventListener('abort', abort, { once: true }) + promise + .then((result) => { + if (signal.aborted) { + onLate?.(result) + } else { + resolve(result) + } + }, reject) + .finally(() => signal.removeEventListener('abort', abort)) + }) +} diff --git a/packages/router-core/src/index.ts b/packages/router-core/src/index.ts index fd673ca410..a296629ffa 100644 --- a/packages/router-core/src/index.ts +++ b/packages/router-core/src/index.ts @@ -103,6 +103,7 @@ export { resolveManifestCssLink, } from './manifest' export { isMatch } from './Matches' +export { _getAssetMatches, _getRenderedMatches } from './load-client' export type { AnyMatchAndValue, FindValueByIndex, @@ -242,7 +243,6 @@ export { SearchParamError, PathParamError, getInitialRouterState, - getMatchedRoutes, trailingSlashOptions, } from './router' @@ -274,9 +274,7 @@ export type { InjectedHtmlEntry, EmitFn, LoadFn, - GetMatchFn, SubscribeFn, - UpdateMatchFn, CommitLocationFn, GetMatchRoutesFn, MatchRoutesFn, diff --git a/packages/router-core/src/isServer/client.ts b/packages/router-core/src/isServer/client.ts index f2f86b6ec7..e12407f409 100644 --- a/packages/router-core/src/isServer/client.ts +++ b/packages/router-core/src/isServer/client.ts @@ -1 +1,2 @@ export const isServer = false +export const loadServerRoute: never = undefined as never diff --git a/packages/router-core/src/isServer/development.ts b/packages/router-core/src/isServer/development.ts index 3632fabf05..e4f13879ad 100644 --- a/packages/router-core/src/isServer/development.ts +++ b/packages/router-core/src/isServer/development.ts @@ -1,2 +1,3 @@ // Development/test mode - returns undefined so fallback to router.isServer is used export const isServer: boolean | undefined = undefined +export { loadServerRoute } from '../load-server' diff --git a/packages/router-core/src/isServer/server.ts b/packages/router-core/src/isServer/server.ts index cfa181440a..d13401a9e9 100644 --- a/packages/router-core/src/isServer/server.ts +++ b/packages/router-core/src/isServer/server.ts @@ -1 +1,2 @@ export const isServer = process.env.NODE_ENV === 'test' ? undefined : true +export { loadServerRoute } from '../load-server' diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index 55d5a79ce8..7ec148b8f4 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -671,13 +671,15 @@ export interface LinkOptionsProps { /** * The preloading strategy for this link * - `false` - No preloading - * - `'intent'` - Preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there. + * - `'intent'` - Preload the linked route when the user focuses, hovers over, or touches the link * - `'viewport'` - Preload the linked route when it enters the viewport + * - `'render'` - Preload the linked route as soon as it renders */ preload?: false | 'intent' | 'viewport' | 'render' /** - * When a preload strategy is set, this delays the preload by this many milliseconds. - * If the user exits the link before this delay, the preload will be cancelled. + * When the intent preload strategy is set, this delays focus and hover + * preloading by this many milliseconds. Touch intent preloads immediately. + * If focus or hover exits before this delay, the preload will be cancelled. */ preloadDelay?: number /** diff --git a/packages/router-core/src/load-client.ts b/packages/router-core/src/load-client.ts new file mode 100644 index 0000000000..83a7697499 --- /dev/null +++ b/packages/router-core/src/load-client.ts @@ -0,0 +1,2651 @@ +// Keep this filename free of a secondary extension so declaration generation +// can rewrite relative imports for both ESM and CJS. +import { isNotFound } from './not-found' +import { isRedirect } from './redirect' +import { getLocationChangeInfo, runRouteLifecycle } from './router' +import { hydrateSsrMatchId } from './ssr/ssr-match-id' +import type { GLOBAL_SEROVAL, GLOBAL_TSR } from './ssr/constants' +import type { AnySerializationAdapter } from './ssr/serializer/transformer' +import type { TsrSsrGlobal } from './ssr/types' +import type { ParsedLocation } from './location' +import type { AnyRouteMatch } from './Matches' +import type { NotFoundError } from './not-found' +import type { + AnyRoute, + BeforeLoadContextOptions, + LoaderFnContext, + RouteContextOptions, + RouteLoaderFn, +} from './route' +import type { AnyRedirect } from './redirect' +import type { AnyRouter } from './router' + +type RouteComponentType = + | 'component' + | 'pendingComponent' + | 'errorComponent' + | 'notFoundComponent' + +export function replaceRouteChunk( + route: AnyRoute, + lazyFn: AnyRoute['lazyFn'], +): void { + route.lazyFn = lazyFn ?? route.lazyFn + route._lazy = undefined +} + +function preloadComponent( + route: AnyRoute, + type: RouteComponentType, +): Promise | undefined { + return (route.options[type] as any)?.preload?.() +} + +function loadComponents( + route: AnyRoute, + onPendingReady?: () => void, +): Promise | undefined { + const component = preloadComponent(route, 'component') + const pending = preloadComponent(route, 'pendingComponent') + const pendingReady = + onPendingReady && pending ? pending.then(onPendingReady) : pending + if (onPendingReady && !pending) { + onPendingReady() + } + if (component && pendingReady) { + return Promise.all([component, pendingReady]).then(() => {}) + } + return component ?? pendingReady +} + +export function loadRouteChunk( + route: AnyRoute, + // `false` waits only for lazy route options, before a boundary is selected. + componentType?: 'errorComponent' | 'notFoundComponent' | false, + onPendingReady?: () => void, +): Promise | undefined { + const afterLazy = () => + componentType === false + ? undefined + : componentType + ? preloadComponent(route, componentType) + : loadComponents(route, onPendingReady) + const current = route._lazy + if (current) { + return current === true ? afterLazy() : current.then(afterLazy) + } + if (!route.lazyFn) { + return afterLazy() + } + + const promise = route.lazyFn().then( + (lazyRoute) => { + // HMR clears the owner before an obsolete import can settle. + if (process.env.NODE_ENV === 'production' || route._lazy === promise) { + const { id: _id, ...options } = lazyRoute.options + Object.assign(route.options, options) + route._lazy = true + } + }, + (error) => { + if (process.env.NODE_ENV === 'production' || route._lazy === promise) { + route._lazy = undefined + } + throw error + }, + ) + route._lazy = promise + return promise.then(afterLazy) +} + +/** Return the structural lane through the first terminal render boundary. */ +export function _getRenderedMatches( + matches: Array, +): Array { + const end = + matches.findIndex( + (match) => match.status !== 'success' || match._notFound, + ) + 1 + return end && end < matches.length ? matches.slice(0, end) : matches +} + +/** Return the lane whose document assets belong to the current presentation. */ +export function _getAssetMatches( + matches: Array, +): Array { + let end = matches.length + for (let index = 0; index < end; index++) { + const match = matches[index]! + // `_assetEnd` is only ever set on hydration presentation clones that are + // `status: 'pending'`, `ssr: 'data-only'`, error-free, and not not-found + // (see hydrate.ts), and commits clear it — so its presence alone is the guard. + if (match._assetEnd !== undefined) { + end = Math.min(end, Math.max(index + 1, match._assetEnd)) + continue + } + if (match.status !== 'success' || match._notFound) { + end = index + 1 + break + } + } + // `end` only ever shrinks to `index + 1 >= 1`, so no zero guard is needed. + return end < matches.length ? matches.slice(0, end) : matches +} + +declare const lanePhase: unique symbol + +type LanePhase = 'matched' | 'contextualized' | 'reduced' | 'projected' + +/** + * Lane matches carry their lane's phase so functions can demand evidence of + * pipeline position (e.g. `commitMatches` only accepts a projected lane's + * matches). The brand is phantom — it never exists at runtime. + */ +type LaneMatches = Array & { + readonly [lanePhase]?: TPhase +} + +type Lane = [ + location: ParsedLocation, + matches: LaneMatches, + background?: Array, + backgroundSettlement?: Promise, +] & { readonly [lanePhase]?: TPhase } + +type MatchedLane = Lane<'matched'> +type ContextualizedLane = Lane<'contextualized'> +type ReducedLane = Lane<'reduced'> +type ProjectedLane = Lane<'projected'> + +const SUCCESS = 0 +const ERROR = 1 +const NOT_FOUND = 2 +// Control outcomes stay contiguous so the hot path can test them together. +const REDIRECTED = 3 +const CANCELED = 4 + +type LoaderOutcome = + | [kind: typeof SUCCESS, data: unknown] + | [kind: typeof ERROR, error: unknown] + | [kind: typeof NOT_FOUND, error: NotFoundError] + | [kind: typeof REDIRECTED, redirect: AnyRedirect] + | [kind: typeof CANCELED] + +type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number] + +export type LoaderFlight = [ + outcome: Promise, + controller: AbortController, + leases: number, +] + +type WorkMatch = AnyRouteMatch & { + _flight?: LoaderFlight +} + +declare const matchPhase: unique symbol + +/** + * A match whose loader outcome has been applied by `settleInto`, which is the + * sole granter of this brand (phantom, zero-runtime). Consumers that require + * it — e.g. `cacheLoaderMatch` — can only be reached after settlement, so the + * compiler enforces the loader→settle→cache ordering. Sources that arrive + * already settled (dehydrated server data) must cast at a named boundary. + */ +type SettledMatch = WorkMatch & { readonly [matchPhase]: 'settled' } + +export type LoadTransaction = [ + controller: AbortController, + redirects: number, + location: ParsedLocation, + matches: Array, + startedAt: number, + done: Promise, + /** + * Dev-only HMR refresh mode. Presence is the mode flag; a refresh always + * carries the presentation it started from and its optional hydration + * handoff. While a publication awaits acknowledgement, its rollback lives + * with the transaction that owns the publication. + */ + refresh?: [ + presentation: Array, + handoff: NonNullable | undefined, + rollback?: () => boolean, + ], +] + +export type PendingSession = [ + owner: LoadTransaction, + boundary: number, + /** Pending reveal time until acknowledged, then minimum-visible-until time. */ + deadline: number, + timer?: ReturnType, + ack?: Promise, + component?: unknown, +] + +type CoordinatorRouter = AnyRouter & { + /** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */ + _preloads?: Map> + _refreshNextLoad?: boolean + _cancelTransition?: () => void +} + +type PublicationCheckpoint = { + previousMatches: Array + previousPresentation: Array + previousCache: Map + commitPromise: CoordinatorRouter['_commitPromise'] + published: boolean +} + +type LoaderTask = [ + index: number, + outcome: Promise, + chunkFailure: Promise, + candidate?: WorkMatch, +] + +type BackgroundLoaderTask = [ + index: number, + outcome: Promise, + chunkFailure: Promise, + candidate: WorkMatch, +] + +type ExecuteLaneOptions = [ + controller: AbortController, + redirects: number, + isCurrent: () => boolean, + base: Array, + preload?: boolean, + sync?: boolean, + forceStaleReload?: boolean, + resolvedPrefix?: number, + onReady?: () => void, +] + +type ControlOutcome = + | [kind: typeof REDIRECTED, redirect: AnyRedirect] + | [kind: typeof CANCELED] + +type LaneResult = ProjectedLane | ControlOutcome + +function isControl( + result: Lane | ControlOutcome, +): result is ControlOutcome { + return typeof result[0 /* location or kind */] === 'number' +} + +export function waitFor( + value: T | PromiseLike, + signal: AbortSignal, +): Promise { + if (signal.aborted) { + return Promise.race([Promise.reject(signal), value]) + } + return new Promise((resolve, reject) => { + const abort = () => reject(signal) + signal.addEventListener('abort', abort, { once: true }) + Promise.resolve(value) + .then(resolve, reject) + .finally(() => signal.removeEventListener('abort', abort)) + }) +} + +export function getRoute(router: AnyRouter, match: WorkMatch): AnyRoute { + return (router.routesById as Record)[match.routeId]! +} + +function normalize( + value: unknown, + rejected: boolean, + routeId?: string, +): LoaderOutcome { + if (isRedirect(value)) { + return [REDIRECTED, value] + } + if (isNotFound(value)) { + value.routeId ||= routeId + return [NOT_FOUND, value] + } + if (rejected && typeof (value as any)?.then === 'function') { + value = new Error('A Promise was thrown', { cause: value }) + } + return rejected ? [ERROR, value] : [SUCCESS, value] +} + +function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome { + let outcome = normalize(cause, true, route.id) + if (outcome[0 /* kind */] !== ERROR) { + return outcome + } + try { + route.options.onError?.(outcome[1 /* error */]) + } catch (onErrorCause) { + outcome = normalize(onErrorCause, true, route.id) + } + return outcome +} + +function normalizeLaneError( + route: AnyRoute, + cause: unknown, + options: ExecuteLaneOptions, +): LoaderOutcome { + if ( + options[0 /* controller */].signal.aborted || + !options[2 /* isCurrent */]() + ) { + options[0 /* controller */].abort() + return [CANCELED] + } + return normalizeError(route, cause) +} + +export function navigateFrom(router: AnyRouter, location: ParsedLocation) { + return (opts: any) => + router.navigate({ + ...opts, + _fromLocation: location, + }) +} + +async function contextualize( + router: AnyRouter, + lane: MatchedLane, + options: ExecuteLaneOptions, + end: number, + planSuccessfulLane: () => void, +): Promise { + const [location, matches] = lane + const signal = options[0 /* controller */].signal + const preload = !!options[4 /* preload */] + for (let index = options[7 /* resolvedPrefix */] ?? 0; index < end; index++) { + const match = matches[index]! + const route = getRoute(router, match) + + match.abortController = options[0 /* controller */] + // Contextualization is serial, so the previous match already contains the + // complete parent context for this route. + const parentContext = + matches[index - 1]?.context ?? router.options.context ?? {} + const common = { + params: match.params, + location, + navigate: navigateFrom(router, location), + buildLocation: router.buildLocation, + cause: preload ? ('preload' as const) : match.cause, + abortController: options[0 /* controller */], + preload, + matches, + routeId: route.id, + } + let context = parentContext + try { + let routeContext = match._ctx + if (!routeContext && route.options.context) { + routeContext = match._ctx = + route.options.context({ + ...common, + deps: match.loaderDeps, + context: parentContext, + } satisfies RouteContextOptions) || {} + } + context = { + ...parentContext, + ...routeContext, + } + match.context = context + } catch (cause) { + releaseFlight(router, match) + return [index, normalizeLaneError(route, cause, options)] + } + if (signal.aborted || !options[2 /* isCurrent */]()) { + options[0 /* controller */].abort() + return [index, [CANCELED]] + } + const validationError = match.paramsError ?? match.searchError + if (validationError !== undefined) { + releaseFlight(router, match) + return [index, normalizeLaneError(route, validationError, options)] + } + const beforeLoad = route.options.beforeLoad + if (!beforeLoad) { + continue + } + + const beforeLoadContext: BeforeLoadContextOptions< + any, + any, + any, + any, + any, + any, + any, + any, + any + > = { + ...common, + search: match.search, + context, + ...router.options.additionalContext, + } + + const previousStatus = match.status + if (previousStatus === 'success') { + match.status = 'pending' + } + options[8 /* onReady */]?.() + try { + setFetching(router, match, 'beforeLoad', options[0 /* controller */]) + const result = await waitFor(beforeLoad(beforeLoadContext), signal) + if (!options[2 /* isCurrent */]()) { + options[0 /* controller */].abort() + return [index, [CANCELED]] + } + const outcome = normalize(result, false, route.id) + if (outcome[0 /* kind */] !== SUCCESS) { + releaseFlight(router, match) + return [index, outcome] + } + match.context = { + ...context, + ...result, + } + } catch (cause) { + releaseFlight(router, match) + return [index, normalizeLaneError(route, cause, options)] + } finally { + if (previousStatus === 'success' && match.status === 'pending') { + match.status = 'success' + } + setFetching(router, match, false, options[0 /* controller */]) + } + } + + // Let a synchronous lane claim predecessor flights before this frame yields. + planSuccessfulLane() + return +} + +function releaseOwnedFlight( + router: AnyRouter, + match: WorkMatch, + flight?: LoaderFlight, +): AbortController | undefined { + if (!flight || --flight[2 /* leases */]) { + return + } + if (router._flights?.get(match.id) === flight) { + const current = router._tx + if ( + current && + !current[0 /* controller */].signal.aborted && + !(process.env.NODE_ENV !== 'production' && current[6 /* refresh */]) && + !current[3 /* matches */].includes(match) && + current[3 /* matches */].some((candidate) => candidate.id === match.id) && + current[3 /* matches */].some( + (candidate) => candidate.isFetching === 'beforeLoad', + ) + ) { + // Keep work discoverable only while the current lane is still running + // beforeLoad. Loader planning performs the matching zero-owner sweep. + return + } + router._flights.delete(match.id) + } + return flight[1 /* controller */] +} + +function releaseFlight(router: AnyRouter, match: WorkMatch): void { + const flight = match._flight + match._flight = undefined + releaseOwnedFlight(router, match, flight)?.abort() +} +/** + * Not passing in a `next` ownership recipient + * is equivalent to discarding the match resources + */ +function transferMatchResources( + router: AnyRouter, + previous: Array, + next?: Array, +): void { + const abort: Array = [] + for (const match of previous as Array) { + if (!next?.includes(match)) { + const flight = match._flight + match._flight = undefined + const controller = releaseOwnedFlight(router, match, flight) + if (controller) { + abort.push(controller) + } + } + } + for (const controller of abort) { + controller.abort() + } +} + +function transferPredecessorResources( + router: AnyRouter, + previous: Array, + next: Array, +): void { + const abort: Array = [] + for (const match of previous as Array) { + if (!next.includes(match)) { + const flight = match._flight + match._flight = undefined + if ( + flight?.[2 /* leases */] === 1 && + router._flights?.get(match.id) === flight && + !( + process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */] + ) && + next.some((candidate) => candidate.id === match.id) + ) { + // The successor has not made its same-ID reload decision yet. + flight[2 /* leases */] = 0 + } else { + const controller = releaseOwnedFlight(router, match, flight) + if (controller) { + abort.push(controller) + } + } + } + } + for (const controller of abort) { + controller.abort() + } +} + +function releaseUnownedFlights(router: AnyRouter): void { + const abort: Array = [] + for (const [id, flight] of router._flights ?? []) { + if (!flight[2 /* leases */]) { + router._flights!.delete(id) + abort.push(flight[1 /* controller */]) + } + } + for (const controller of abort) { + controller.abort() + } +} + +function acquireMatchResources(matches: Array): void { + for (const match of matches as Array) { + const flight = match._flight + if (flight) { + flight[2 /* leases */]++ + } + } +} + +function setFetching( + router: AnyRouter, + match: WorkMatch, + value: AnyRouteMatch['isFetching'], + owner?: AbortController, +): void { + match.isFetching = value + if (owner && router._tx?.[0 /* controller */] !== owner) { + return + } + const store = router.stores.byRoute.get(match.routeId) + const presented = store?.get() + if (presented?.id === match.id) { + store!.set({ ...presented, isFetching: value }) + } +} + +function getLoaderContext( + router: AnyRouter, + lane: ContextualizedLane, + match: WorkMatch, + route: AnyRoute, + controller: AbortController, + parentMatchPromise: Promise | undefined, + preload: boolean, +): LoaderFnContext { + const location = lane[0 /* location */] + return { + params: match.params, + location, + navigate: navigateFrom(router, location), + cause: preload ? ('preload' as const) : match.cause, + abortController: controller, + preload, + deps: match.loaderDeps, + parentMatchPromise: parentMatchPromise as any, + context: match.context, + route, + ...router.options.additionalContext, + } +} + +async function loadResource( + router: AnyRouter, + lane: ContextualizedLane, + match: WorkMatch, + route: AnyRoute, + loader: RouteLoaderFn | undefined, + parentMatchPromise: Promise | undefined, + preload: boolean, + owner: AbortController, +): Promise { + const signal = owner.signal + if (signal.aborted) { + return [CANCELED] + } + if (!loader) { + return [SUCCESS, undefined] + } + + let flight = match._flight + setFetching(router, match, 'loader', owner) + try { + if (!flight) { + const controller = new AbortController() + flight = [ + Promise.resolve() + .then(() => + loader( + getLoaderContext( + router, + lane, + match, + route, + controller, + parentMatchPromise, + preload, + ), + ), + ) + .then( + (value) => normalize(value, false, route.id), + (cause) => normalize(cause, true, route.id), + ) + .then((result): LoaderOutcome => { + // The registry controls discovery; leases keep current consumers + // sharing the same terminal outcome. + if ( + result[0 /* kind */] !== SUCCESS && + router._flights?.get(match.id) === flight + ) { + router._flights!.delete(match.id) + if (!flight![2 /* leases */]) { + controller.abort() + } + } + return result[0 /* kind */] === ERROR && flight![2 /* leases */] + ? normalizeError(route, result[1 /* error */]) + : result + }), + controller, + 1, + ] + ;(router._flights ??= new Map()).set(match.id, flight) + } + match._flight = flight + match.abortController = flight[1 /* controller */] + return await waitFor(flight[0 /* outcome */], signal) + } catch (cause) { + if (cause !== signal) { + throw cause + } + releaseFlight(router, match) + return [CANCELED] + } finally { + setFetching(router, match, false, owner) + } +} + +function settleInto( + match: WorkMatch, + result: LoaderOutcome, + preload: boolean, +): asserts match is SettledMatch { + if (result[0 /* kind */] === SUCCESS) { + match.loaderData = result[1 /* data */] + match.error = undefined + match.status = 'success' + match.invalid = false + match.updatedAt = Date.now() + match.preload = preload + } else if (result[0 /* kind */] !== REDIRECTED) { + // Reduction installs only the selected terminal failure. Every other + // settled attempt remains a renderable, stale match in that lane. + match.status = 'success' + match.error = undefined + match.invalid = true + } +} + +export function cacheLoaderMatch( + router: CoordinatorRouter, + match: SettledMatch, + planned: AnyRouteMatch | undefined, +): void { + const current = router._cache.get(match.id) as WorkMatch | undefined + if ( + current !== planned || + router._committed.some( + (candidate) => + candidate.id === match.id && + (candidate as WorkMatch)._flight === match._flight, + ) + ) { + return + } + const cached = { + ...match, + _notFound: undefined, + context: {}, + } as WorkMatch + if (cached._flight) { + cached._flight[2 /* leases */]++ + } + router._cache.set(match.id, cached) + if (current) { + releaseFlight(router, current) + } +} + +function getParentSnapshot( + match: WorkMatch, + outcome: LoaderOutcome, +): WorkMatch { + if (outcome[0 /* kind */] === ERROR || outcome[0 /* kind */] === NOT_FOUND) { + return { + ...match, + status: outcome[0 /* kind */] === ERROR ? 'error' : 'notFound', + error: outcome[1 /* error */], + _flight: undefined, + } + } + return match +} + +function createLoaderTask( + router: AnyRouter, + lane: ContextualizedLane, + index: number, + tasks: Array, + semanticParent: Promise | undefined, + options: ExecuteLaneOptions, +): Promise { + const match = lane[1 /* matches */][index]! + const route = getRoute(router, match) + const preload = !!options[4 /* preload */] + const plannedCacheMatch = preload ? router._cache.get(match.id) : undefined + let configured + let reload = false + let reloadFailure: LoaderOutcome | undefined + try { + if (match.status === 'success') { + configured = route.options.shouldReload + if (typeof configured === 'function') { + configured = configured( + getLoaderContext( + router, + lane, + match, + route, + options[0 /* controller */], + semanticParent, + preload, + ), + ) + } + if (!options[2 /* isCurrent */]()) { + options[0 /* controller */].abort() + reloadFailure = [CANCELED] + } + } + if (!reloadFailure) { + if (match.status !== 'success') { + reload = true + } else { + const staleAge = + options[4 /* preload */] || match.preload + ? (route.options.preloadStaleTime ?? + router.options.defaultPreloadStaleTime ?? + 30_000) + : (route.options.staleTime ?? router.options.defaultStaleTime ?? 0) + reload = !!( + match.invalid || + configured || + (configured === undefined && + Date.now() - match.updatedAt >= staleAge && + (options[6 /* forceStaleReload */] || + match.cause === 'enter' || + options[3 /* base */].some( + (candidate) => + candidate.routeId === match.routeId && + candidate.id !== match.id, + ))) + ) + } + } + } catch (cause) { + match.invalid = true + releaseFlight(router, match) + reloadFailure = normalizeLaneError(route, cause, options) + } + const routeLoader = route.options.loader + const loader = + typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler + let donor = + (!preload || route.options.preload !== false) && + routeLoader && + !(process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */]) + ? router._flights?.get(match.id) + : undefined + if (donor === match._flight || reloadFailure) { + donor = undefined + } else if (donor && !reload && !preload && configured === undefined) { + // Normal cache policy accepts an already-running generation even when this + // lane itself would not have started another loader. + reload = true + } else if (!reload) { + donor = undefined + } + const background = !!( + routeLoader && + reload && + match.status === 'success' && + !preload && + !options[5 /* sync */] && + ((typeof routeLoader === 'function' + ? undefined + : routeLoader?.staleReloadMode) ?? + router.options.defaultStaleReloadMode) !== 'blocking' + ) + const loaded = reload && (!preload || route.options.preload !== false) + const blocking = + loaded && !background && (match.status !== 'success' || !!routeLoader) + const onLazyReady = + route.lazyFn && route._lazy !== true ? options[8 /* onReady */] : undefined + if (loaded && !routeLoader) { + match.invalid = false + match.updatedAt = Date.now() + } + if (donor) { + donor[2 /* leases */]++ + } + if (blocking) { + const acceptedFlight = match._flight + match._flight = donor + releaseOwnedFlight(router, match, acceptedFlight)?.abort() + // A successful route without a loader has no blocking work to present. It + // still gets a task so its chunk and derived assets participate in the + // lane, but putting it back into pending would hide an already-rendered + // ancestor while only a descendant is loading. + if (match.status === 'success') { + match.status = 'pending' + } + options[8 /* onReady */]?.() + } + if (!loaded) { + match.isFetching = false + } + const rawOutcome = reloadFailure + ? Promise.resolve(reloadFailure) + : !blocking + ? Promise.resolve([SUCCESS, match.loaderData]) + : loadResource( + router, + lane, + match, + route, + loader, + semanticParent, + preload, + options[0 /* controller */], + ) + const outcome = rawOutcome.then((result) => { + if (blocking) { + settleInto(match, result, preload) + if (result[0 /* kind */] === SUCCESS) { + if ( + preload && + routeLoader && + !options[0 /* controller */].signal.aborted + ) { + cacheLoaderMatch(router, match, plannedCacheMatch) + } + // A route is renderable only after both its data and normal component + // chunk are ready. Its loader data is already available to descendants. + match.status = 'pending' + } + } + return result + }) + + const rawChunkFailure = waitFor( + Promise.resolve().then(() => loadRouteChunk(route, undefined, onLazyReady)), + options[0 /* controller */].signal, + ).then( + () => undefined, + (cause): IndexedOutcome => [ + index, + normalizeLaneError(route, cause, options), + ], + ) + const chunkFailure = rawChunkFailure.then((failure) => + outcome.then((result) => { + if ( + blocking && + !failure && + result[0 /* kind */] === SUCCESS && + match.status === 'pending' && + options[2 /* isCurrent */]() + ) { + match.status = 'success' + options[8 /* onReady */]?.() + } + return failure + }), + ) + tasks.push([index, outcome, chunkFailure]) + if (!background) { + return outcome.then((result) => getParentSnapshot(match, result)) + } + const candidate: WorkMatch = { + ...match, + status: 'pending', + preload: false, + _flight: donor, + } + match.invalid = false + match.isFetching = 'loader' + const backgroundOutcome = loadResource( + router, + lane, + candidate, + route, + loader, + semanticParent, + false, + options[0 /* controller */], + ).then((result) => { + match.isFetching = false + settleInto(candidate, result, false) + return result + }) + ;(lane[2 /* background */] ??= []).push([ + index, + backgroundOutcome, + chunkFailure, + candidate, + ]) + return backgroundOutcome.then((result) => + getParentSnapshot(candidate, result), + ) +} + +async function getNotFoundBoundary( + router: AnyRouter, + matches: Array, + indexed: IndexedOutcome | undefined, + signal: AbortSignal, + fallback = 0, +): Promise { + const cause = indexed?.[1 /* outcome */][1 /* error or redirect */] as + | NotFoundError + | undefined + let index = cause?.routeId + ? matches.findIndex((match) => match.routeId === cause.routeId) + : (indexed?.[0 /* index */] ?? matches.length - 1) + if (index < 0) { + index = 0 + } + for (let i = index; i >= 0; i--) { + const route = getRoute(router, matches[i]!) + const loading = loadRouteChunk(route, false) + if (loading) { + try { + await waitFor(loading, signal) + } catch (cause) { + if (cause === signal) { + throw cause + } + } + } + if (route.options.notFoundComponent) { + return i + } + } + return cause?.routeId ? index : fallback +} + +function discardBackground(router: AnyRouter, lane: Lane): void { + if (lane[2 /* background */]) { + transferMatchResources( + router, + lane[2 /* background */].map((task) => task[3 /* candidate */]), + ) + lane[2 /* background */] = undefined + } +} + +async function settleTasks( + tasks: Array, + serialFailure?: IndexedOutcome, + redirectTasks?: Array, + gate?: number | Promise, +): Promise { + let loaderFailure: IndexedOutcome | undefined + + try { + await Promise.all( + tasks.map((task) => + task[1 /* outcome */].then(async (outcome) => { + const taskIndex = task[0 /* index */] + if (gate && taskIndex >= (await gate)) { + return + } + if (outcome[0 /* kind */] >= REDIRECTED) { + throw [taskIndex, outcome] as IndexedOutcome + } + if (!loaderFailure && outcome[0 /* kind */] !== SUCCESS) { + loaderFailure = [taskIndex, outcome] + // Every started descendant must settle before an ordinary failure + // wins because a redirect from any of them remains control flow. + await Promise.all( + (redirectTasks ?? []).map((nextTask) => { + if (nextTask[0 /* index */] <= taskIndex) { + return + } + return nextTask[1 /* outcome */].then((nextOutcome) => { + if (nextOutcome[0 /* kind */] === REDIRECTED) { + throw [ + nextTask[0 /* index */], + nextOutcome, + ] as IndexedOutcome + } + }) + }), + ) + } + }), + ), + ) + } catch (cause) { + return cause as IndexedOutcome + } + return serialFailure ?? loaderFailure +} + +async function reduceLane( + router: AnyRouter, + lane: ContextualizedLane, + tasks: Array, + controller: AbortController, + redirects: number, + settlement: Promise, + onReady?: () => void, +): Promise { + const matches = lane[1 /* matches */] + let failure = await settlement + let redirectLimitExceeded = false + const plannedBoundary = matches.findIndex((match) => match._notFound) + const boundaryOf = (found: IndexedOutcome) => + found[1 /* outcome */][0 /* kind */] === NOT_FOUND + ? getNotFoundBoundary(router, matches, found, controller.signal) + : found[0 /* index */] + let readinessEnd = plannedBoundary < 0 ? matches.length : plannedBoundary + + if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) { + readinessEnd = 0 + } else if (failure) { + readinessEnd = failure[2 /* boundary */] ??= await boundaryOf(failure) + for (const task of tasks) { + if (task[0 /* index */] >= readinessEnd) { + break + } + const outcome = await task[1 /* outcome */] + // Presence means a loader previously succeeded, even with `undefined`. + if ( + outcome[0 /* kind */] !== SUCCESS && + outcome[0 /* kind */] < REDIRECTED && + !('loaderData' in matches[task[0 /* index */]]!) + ) { + failure = [task[0 /* index */], outcome] + readinessEnd = failure[2 /* boundary */] = await boundaryOf(failure) + break + } + } + } + + for (const task of tasks) { + if (task[0 /* index */] >= readinessEnd) { + break + } + const chunkFailure = await task[2 /* chunkFailure */] + if (!chunkFailure) { + continue + } + failure = chunkFailure + break + } + + if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) { + const outcome = failure![1 /* outcome */] + if ( + outcome[0 /* kind */] !== REDIRECTED || + outcome[1 /* redirect */].options.reloadDocument || + redirects < 20 + ) { + discardBackground(router, lane) + return outcome as ControlOutcome + } + redirectLimitExceeded = true + failure = [0, [ERROR, new Error('Too many redirects')]] + } + + const boundary = failure + ? (failure[2 /* boundary */] ?? (await boundaryOf(failure))) + : plannedBoundary + if (boundary >= 0) { + const outcome = failure?.[1 /* outcome */] + const kind = outcome?.[0 /* kind */] + const match = matches[boundary]! + const cause = outcome?.[1 /* error or redirect */] + const install = () => { + if (outcome) { + match._notFound = undefined + if (kind === ERROR) { + match.status = 'error' + } else { + ;(cause as NotFoundError).routeId = match.routeId + if (match.routeId === router.routeTree.id) { + match.status = 'success' + match._notFound = true + } else { + match.status = 'notFound' + } + } + match.error = cause + match.isFetching = false + } + } + install() + try { + await waitFor( + outcome + ? Promise.resolve().then(() => + loadRouteChunk( + getRoute(router, match), + kind === ERROR ? 'errorComponent' : 'notFoundComponent', + ), + ) + : Promise.all([ + loadRouteChunk(getRoute(router, match)), + loadRouteChunk(getRoute(router, match), 'notFoundComponent'), + ]), + controller.signal, + ) + } catch (cause) { + if (cause === controller.signal) { + discardBackground(router, lane) + return [CANCELED] + } + } + if (!outcome) { + match.status = 'success' + onReady?.() + } else if (redirectLimitExceeded) { + controller.abort() + await Promise.all([ + ...tasks.map((task) => task[1 /* outcome */]), + ...tasks.map((task) => task[2 /* chunkFailure */]), + ...(lane[2 /* background */] ?? []).map( + (task) => task[1 /* outcome */], + ), + ]) + discardBackground(router, lane) + transferMatchResources(router, matches) + install() + } + } + + return lane as ReducedLane +} + +export async function projectLane( + router: AnyRouter, + lane: ReducedLane, + signal: AbortSignal, + start = 0, + end = lane[1 /* matches */].length, +): Promise { + const matches = lane[1 /* matches */] + for (let index = start; index < end; index++) { + const match = matches[index]! + const routeOptions = getRoute(router, match).options + if (routeOptions.head || routeOptions.scripts) { + try { + const context = { + ssr: router.options.ssr, + matches, + match, + params: match.params, + loaderData: match.loaderData, + } + const [head, scripts] = await waitFor( + Promise.all([ + routeOptions.head?.(context), + routeOptions.scripts?.(context), + ]), + signal, + ) + match.meta = head?.meta + match.links = head?.links + match.headScripts = head?.scripts + match.styles = head?.styles + match.scripts = scripts + } catch (cause) { + if (cause === signal) { + break + } + console.error(cause) + } + } + if (match.status !== 'success' || match._notFound) { + break + } + } + return lane as ProjectedLane +} + +async function executeClientLane( + router: AnyRouter, + location: ParsedLocation, + matches: Array, + options: ExecuteLaneOptions, +): Promise { + const matched = [location, matches as Array] as MatchedLane + let plannedBoundary = matches.findIndex((match) => match._notFound) + if (router.options.notFoundMode !== 'root' && plannedBoundary >= 0) { + const boundary = await getNotFoundBoundary( + router, + matched[1 /* matches */], + undefined, + options[0 /* controller */].signal, + plannedBoundary, + ) + if (boundary !== plannedBoundary) { + matches[plannedBoundary]!._notFound = undefined + matches[boundary]!._notFound = true + } + plannedBoundary = boundary + } + let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1 + const tasks: Array = [] + const start = options[7 /* resolvedPrefix */] ?? 0 + let semanticParent = start + ? Promise.resolve(matched[1 /* matches */][start - 1]!) + : undefined + const planSuccessfulLane = () => { + for (let index = start; index < end; index++) { + if (options[0 /* controller */].signal.aborted) { + break + } + semanticParent = createLoaderTask( + router, + matched as ContextualizedLane, + index, + tasks, + semanticParent, + options, + ) + } + } + // From here on `matched` is contextualized: `contextualize` communicates + // through mutation plus a failure return, so the phase brand is asserted at + // the two use sites below rather than granted by a (byte-costing) return. + const failure = await contextualize( + router, + matched, + options, + end, + planSuccessfulLane, + ) + if (failure) { + options[5 /* sync */] = true + end = failure[0 /* index */] + if (failure[1 /* outcome */][0 /* kind */] === NOT_FOUND) { + failure[2 /* boundary */] = await getNotFoundBoundary( + router, + matched[1 /* matches */], + failure, + options[0 /* controller */].signal, + ) + end = Math.min(end, failure[2 /* boundary */] + 1) + } else if (failure[1 /* outcome */][0 /* kind */] >= REDIRECTED) { + end = 0 + } + planSuccessfulLane() + } + if (options[2 /* isCurrent */]() && !options[4 /* preload */]) { + releaseUnownedFlights(router) + } + let reduced: ReducedLane | ControlOutcome + try { + const reduction = reduceLane( + router, + matched as ContextualizedLane, + tasks, + options[0 /* controller */], + options[1 /* redirects */], + settleTasks(tasks, failure, matched[2 /* background */]), + options[8 /* onReady */], + ) + if (matched[2 /* background */]?.length) { + matched[3 /* backgroundSettlement */] = settleTasks( + matched[2 /* background */], + undefined, + undefined, + reduction.then( + (foreground) => + isControl(foreground) + ? 0 + : _getRenderedMatches(foreground[1 /* matches */]).length, + () => 0, + ), + ) + } + reduced = await reduction + } catch (cause) { + discardBackground(router, matched) + throw cause + } + if (isControl(reduced)) { + return reduced + } + return projectLane( + router, + reduced, + options[0 /* controller */].signal, + options[7 /* resolvedPrefix */] === reduced[1 /* matches */].length + ? options[7 /* resolvedPrefix */] + : 0, + ) +} + +/** + * Finds the first route that should show pending UI and its two timing values. + * A fallback already on screen remains selected after its route loads, so we + * do not jump to a child fallback. Matches put back into pending by invalidation + * skip pendingMs, and a route without a usable fallback blocks pending UI for deeper routes. + */ +function pendingConfig( + router: AnyRouter, + matches: Array, +): + | [delay: number, boundary: number, min: number, component: unknown] + | undefined + | void { + const presented = router.stores.matches.get() + for (let index = 0; index < matches.length; index++) { + const match = matches[index]! + const success = match.status === 'success' + const visible = + success && + presented[index]?.id === match.id && + presented[index]?.status === 'pending' + if (success && !visible) { + continue + } + const route = getRoute(router, match as WorkMatch) + const delay = + visible || match.invalid + ? 0 + : (route.options.pendingMs ?? router.options.defaultPendingMs) + const component = + route.options.pendingComponent ?? + (router.options as any).defaultPendingComponent + return component && typeof delay === 'number' && delay !== Infinity + ? [ + delay, + index, + route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0, + component, + ] + : undefined + } +} + +/** + * Waits for `pendingMs`, then presents the complete lane. Rendering applies the + * selected boundary cutoff while retaining every match's structural state. + * A replacement load for the same match keeps the timer; choosing a different + * match resets it. `pendingMinMs` starts after the fallback renders. + */ +function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void { + if (router._tx !== tx) { + return + } + let session = router._pending + let tookOver = false + const sessionMatchId = + session?.[0 /* owner */][3 /* matches */][session[1 /* boundary */]]?.id + if (session?.[0 /* owner */] !== tx) { + if ( + session && + tx[3 /* matches */][session[1 /* boundary */]]?.id === sessionMatchId + ) { + session[0 /* owner */] = tx + tookOver = true + } else { + clearTimeout(session?.[3 /* timer */]) + router._pending = session = undefined + } + } + const config = pendingConfig(router, tx[3 /* matches */]) + if (!config) { + return + } + const [delay, boundary, min, component] = config + const matchId = tx[3 /* matches */][boundary]!.id + if ( + !session || + session[1 /* boundary */] !== boundary || + sessionMatchId !== matchId + ) { + // Hydration and redirects can preserve pending presentation without a session. + // Do not delay it again; conservatively start pendingMinMs from now. + clearTimeout(session?.[3 /* timer */]) + const presented = router.stores.matches.get()[boundary] + const visible = presented?.id === matchId && presented.status === 'pending' + router._pending = session = [ + tx, + boundary, + visible ? Date.now() + min : tx[4 /* startedAt */] + delay, + undefined, + visible ? Promise.resolve(true) : undefined, + component, + ] + } + if ( + session[4 /* ack */] && + !tookOver && + session[5 /* component */] === component + ) { + return + } + session[5 /* component */] = component + if (!session[4 /* ack */]) { + clearTimeout(session[3 /* timer */]) + const remaining = session[2 /* deadline */] - Date.now() + if (remaining > 0) { + session[3 /* timer */] = setTimeout(() => { + offerPending(router, tx) + }, remaining) + return + } + session[2 /* deadline */] = 0 + } + const offered = tx[3 /* matches */].map((match) => ({ + ...match, + _flight: undefined, + })) + offered[boundary]!.status = 'pending' + const ack = router + .startTransition(() => router.stores.setMatches(offered), offered) + .then((rendered) => { + if ( + rendered && + router._pending === session && + session[4 /* ack */] === ack && + !session[2 /* deadline */] + ) { + session[2 /* deadline */] = Date.now() + min + } + return rendered + }) + session[4 /* ack */] = ack +} + +/** + * Cancels pending UI timing when its load ends. The ownership check prevents + * an older, superseded load from clearing pending UI that a newer load took over. + */ +function finishPending(router: CoordinatorRouter, tx: LoadTransaction): void { + const session = router._pending + if (session?.[0 /* owner */] === tx) { + clearTimeout(session[3 /* timer */]) + router._pending = undefined + } +} + +function publishMatches( + router: CoordinatorRouter, + matches: Array, +): void { + router._committed = matches + router.stores.setMatches(matches) +} + +function discardLane(router: AnyRouter, lane: ProjectedLane): void { + transferMatchResources(router, lane[1 /* matches */]) + discardBackground(router, lane) +} + +function commitMatches( + router: CoordinatorRouter, + tx: LoadTransaction, + matches: LaneMatches<'projected'>, + resolvedPrefix?: number, +): void { + const previous = router._committed + const previousCached = router._cache + for (const match of matches) { + match.preload = false + if (resolvedPrefix) { + match._assetEnd = undefined + } + } + const cut = _getRenderedMatches(matches).length + const cached = new Map() + const now = Date.now() + for (const match of [...previous, ...previousCached.values()]) { + // Rendered-prefix ids and settled successes anywhere in the lane are + // authoritative: retaining an older same-id generation would shadow them + // at the next planning pass. Unsettled beyond-boundary matches are not — + // they must not evict a newer same-id preload. + if ( + match.status !== 'success' || + matches.some( + (candidate, index) => + candidate.id === match.id && + (index < cut || candidate.status === 'success'), + ) + ) { + continue + } + const work = match as WorkMatch + const route = getRoute(router, work) + if ( + !route.options.loader || + now - match.updatedAt >= + (match.preload + ? (route.options.preloadGcTime ?? + router.options.defaultPreloadGcTime ?? + 300_000) + : (route.options.gcTime ?? router.options.defaultGcTime ?? 300_000)) + ) { + continue + } + cached.set( + match.id, + previousCached.get(match.id) === match + ? match + : ({ + ...match, + _flight: undefined, + isFetching: false, + context: {}, + } as WorkMatch), + ) + } + // The lane becomes committed before publication can synchronously reenter. + tx[3 /* matches */] = [] + router._cache = cached + publishMatches(router, matches) + transferMatchResources( + router, + [...previousCached.values(), ...previous], + [...matches, ...cached.values()], + ) + runRouteLifecycle(router, previous, matches, () => router._tx === tx) +} + +function commitRefreshMatches( + router: CoordinatorRouter, + tx: LoadTransaction, + matches: LaneMatches<'projected'>, + checkpoint: PublicationCheckpoint, +): void { + const previous = router._committed + const previousCached = router._cache + for (const match of matches) { + match.preload = false + } + const cached = new Map() + // Delay releasing the previous owners until the HMR render is acknowledged. + // Old generations must not become reusable cache entries after refresh. + tx[3 /* matches */] = [] + router._cache = cached + checkpoint.previousMatches = previous + checkpoint.previousCache = previousCached + checkpoint.published = true + publishMatches(router, matches) + if (!checkpoint.published || router._tx !== tx) { + return + } + runRouteLifecycle(router, previous, matches, () => router._tx === tx) +} + +function settlePublication( + router: CoordinatorRouter, + checkpoint: PublicationCheckpoint, +): void { + if (!checkpoint.published) { + return + } + checkpoint.published = false + transferMatchResources( + router, + [...checkpoint.previousCache.values(), ...checkpoint.previousMatches], + [...router._cache.values(), ...router._committed], + ) +} + +function rollbackPublication( + router: CoordinatorRouter, + tx: LoadTransaction, + lane: ProjectedLane, + checkpoint: PublicationCheckpoint, +): boolean { + if ( + !checkpoint.published || + router._tx !== tx || + router._committed !== lane[1 /* matches */] + ) { + settlePublication(router, checkpoint) + return false + } + + const discarded = [...router._cache.values(), ...router._committed] + const restored = [ + ...checkpoint.previousCache.values(), + ...checkpoint.previousMatches, + ] + router._cache = checkpoint.previousCache + router._committed = checkpoint.previousMatches + checkpoint.published = false + + for (const match of discarded as Array) { + if ( + !restored.includes(match) && + match._flight && + router._flights?.get(match.id) === match._flight + ) { + router._flights.delete(match.id) + } + } + + finishPending(router, tx) + router.batch(() => { + router.stores.status.set('idle') + router.stores.setMatches(checkpoint.previousPresentation) + }) + tx[0 /* controller */].abort() + transferMatchResources(router, discarded, restored) + discardBackground(router, lane) + if (router._tx === tx && router._commitPromise === checkpoint.commitPromise) { + router._commitPromise?.resolve() + router._commitPromise = undefined + } + return true +} + +async function transitionRefresh( + router: CoordinatorRouter, + tx: LoadTransaction, + lane: ProjectedLane, + changeInfo: ReturnType, +): Promise { + const refresh = tx[6 /* refresh */]! + const checkpoint: PublicationCheckpoint = { + previousMatches: router._committed, + previousPresentation: refresh[0 /* presentation */], + previousCache: router._cache, + commitPromise: router._commitPromise, + published: false, + } + const commit = () => { + finishPending(router, tx) + refresh[2 /* rollback */] = rollback + commitRefreshMatches(router, tx, lane[1 /* matches */], checkpoint) + if (!checkpoint.published || router._tx !== tx) { + return + } + router.emit({ type: 'onLoad', ...changeInfo }) + if (router._tx === tx) { + router.emit({ type: 'onBeforeRouteMount', ...changeInfo }) + } + } + const rollback = () => { + if (refresh[2 /* rollback */] === rollback) { + refresh[2 /* rollback */] = undefined + } + const restored = rollbackPublication(router, tx, lane, checkpoint) + router._cancelTransition?.() + return restored + } + try { + const rendered = await router.startTransition(commit, lane[1 /* matches */]) + if (refresh[2 /* rollback */] === rollback) { + refresh[2 /* rollback */] = undefined + } + if (checkpoint.published) { + const handoff = refresh[1 /* handoff */] + if (handoff && router._handoff === handoff) { + handoff[1 /* finish */]() + } + if (router._tx === tx) { + tx[6 /* refresh */] = undefined + } + } + settlePublication(router, checkpoint) + return rendered + } catch (cause) { + if (rollback()) { + return + } + throw cause + } +} + +async function awaitCurrent( + router: CoordinatorRouter, + owner?: LoadTransaction, +): Promise { + let current = router._tx + while (current && current !== owner) { + await current[5 /* done */] + if (router._tx === current) { + return + } + current = router._tx + } +} + +async function followRedirect( + router: CoordinatorRouter, + tx: LoadTransaction, + redirect: AnyRedirect, +): Promise { + await router.navigate({ + ...redirect.options, + replace: true, + ignoreBlocker: true, + _redirects: tx[1 /* redirects */] + 1, + } as any) +} + +function restoreCommitted( + router: CoordinatorRouter, + tx: LoadTransaction, +): void { + finishPending(router, tx) + tx[0 /* controller */].abort() + transferMatchResources(router, tx[3 /* matches */]) + tx[3 /* matches */] = [] + if (router._tx !== tx) { + return + } + router.batch(() => { + router.stores.status.set('idle') + router.stores.setMatches(router._committed) + }) + if (router._tx === tx) { + router._commitPromise?.resolve() + router._commitPromise = undefined + } +} + +async function runBackground( + router: CoordinatorRouter, + tx: LoadTransaction, + base: Array, + tasks: Array, + settlement: Promise, +): Promise { + const next = base.map((match) => ({ ...match })) + acquireMatchResources(next) + for (const task of tasks) { + releaseFlight(router, next[task[0 /* index */]]!) + next[task[0 /* index */]] = task[3 /* candidate */] + } + // Phase jump: the clones inherit beforeLoad context from the committed + // foreground lane, which already ran `contextualize` for these matches. + const lane = [tx[2 /* location */], next] as ContextualizedLane + let reduced: ReducedLane | ControlOutcome + try { + reduced = await reduceLane( + router, + lane, + tasks, + tx[0 /* controller */], + tx[1 /* redirects */], + settlement, + ) + } catch (cause) { + transferMatchResources(router, next) + throw cause + } + if (isControl(reduced)) { + transferMatchResources(router, next) + if ( + reduced[0 /* kind */] === REDIRECTED && + router._tx === tx && + router._committed === base + ) { + await followRedirect(router, tx, reduced[1 /* redirect */]) + } + return + } + const projected = await projectLane( + router, + reduced, + tx[0 /* controller */].signal, + ) + if (router._tx !== tx || router._committed !== base) { + transferMatchResources(router, projected[1 /* matches */]) + return + } + for (const match of projected[1 /* matches */] as Array) { + const cached = router._cache.get(match.id) as WorkMatch | undefined + if (cached?._flight && cached._flight === match._flight) { + router._cache.delete(match.id) + releaseFlight(router, cached) + } + } + publishMatches(router, projected[1 /* matches */]) + transferMatchResources(router, base, projected[1 /* matches */]) +} + +async function runClientTransaction( + router: CoordinatorRouter, + tx: LoadTransaction, + forceStaleReload: boolean, + onReady?: () => void, + sync?: boolean, + resolvedPrefix?: number, +): Promise { + const options: ExecuteLaneOptions = [ + tx[0 /* controller */], + tx[1 /* redirects */], + () => router._tx === tx && !!tx[3 /* matches */].length, + router._committed, + undefined, + sync, + forceStaleReload, + resolvedPrefix, + onReady, + ] + const result = await executeClientLane( + router, + tx[2 /* location */], + tx[3 /* matches */], + options, + ) + + if (isControl(result)) { + if (result[0 /* kind */] === REDIRECTED && router._tx === tx) { + finishPending(router, tx) + transferMatchResources(router, tx[3 /* matches */]) + tx[3 /* matches */] = [] + if (router._tx === tx) { + if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) { + router._refreshNextLoad = true + } + await followRedirect(router, tx, result[1 /* redirect */]) + } + } else { + restoreCommitted(router, tx) + } + return + } + const pending = router._pending + if (pending?.[0 /* owner */] === tx) { + /** + * Loading finished, so cancel any pending reveal. If the fallback rendered, + * wait out the rest of `pendingMinMs` before replacing it. If it never + * rendered, there is no minimum wait; if another load took it over, that + * load owns the deadline. + */ + clearTimeout(pending[3 /* timer */]) + if (pending[4 /* ack */]) { + const signal = tx[0 /* controller */].signal + let rendered = false + try { + rendered = await waitFor(pending[4 /* ack */], signal) + } catch (cause) { + if (cause !== signal) { + throw cause + } + } + if ( + rendered && + router._pending === pending && + pending[0 /* owner */] === tx + ) { + const remaining = pending[2 /* deadline */] - Date.now() + if (remaining > 0) { + try { + await waitFor( + new Promise((resolve) => { + pending[3 /* timer */] = setTimeout(resolve, remaining) + }), + signal, + ) + } catch {} + clearTimeout(pending[3 /* timer */]) + } + } + } + } + if (router._tx !== tx) { + finishPending(router, tx) + discardLane(router, result) + return + } + const toLocation = tx[2 /* location */] + const changeInfo = getLocationChangeInfo( + toLocation, + router.stores.resolvedLocation.get(), + ) + const background = result[2 /* background */] + await router.startViewTransition(async () => { + if (router._tx !== tx) { + discardLane(router, result) + return + } + const commit = () => { + finishPending(router, tx) + commitMatches(router, tx, result[1 /* matches */], resolvedPrefix) + if (router._tx !== tx) { + return + } + router.emit({ type: 'onLoad', ...changeInfo }) + if (router._tx === tx) { + router.emit({ type: 'onBeforeRouteMount', ...changeInfo }) + } + } + const rendered = + process.env.NODE_ENV !== 'production' && tx[6 /* refresh */] + ? await transitionRefresh(router, tx, result, changeInfo) + : await router.startTransition(commit, result[1 /* matches */]) + if ( + process.env.NODE_ENV !== 'production' && + tx[6 /* refresh */] && + rendered === undefined + ) { + return + } + if (router._tx !== tx) { + discardBackground(router, result) + return + } + if (background?.length) { + // Publish refreshes only after the foreground render acknowledgement. + // Otherwise a fast refresh can replace the acknowledged generation + // before the framework commits it and strand the navigation. + runBackground( + router, + tx, + result[1 /* matches */], + background, + result[3 /* backgroundSettlement */]!, + ).catch(console.error) + } + router.batch(() => { + router.stores.resolvedLocation.set(toLocation) + router.stores.status.set('idle') + if (router._tx === tx) { + router.emit({ type: 'onResolved', ...changeInfo }) + } + if (rendered && router._tx === tx) { + router.emit({ type: 'onRendered', ...changeInfo }) + } + }) + if (router._tx !== tx) { + return + } + router._commitPromise?.resolve() + router._commitPromise = undefined + }) +} + +export async function loadClientRoute( + router: CoordinatorRouter, + opts?: { sync?: boolean }, +): Promise { + let rematerialize = false + if (process.env.NODE_ENV !== 'production') { + router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.() + rematerialize = !!router._refreshNextLoad || !!router._tx?.[6 /* refresh */] + } + const refreshPresentation = rematerialize + ? router.stores.matches.get() + : undefined + const previousOwner = router._tx + const resolvedLocation = router.stores.resolvedLocation.get() + const previousLocation = resolvedLocation ?? router.stores.location.get() + const location = router.latestLocation + const pendingLocation = router._pendingLocation as + | (ParsedLocation & { _redirects?: number }) + | undefined + const redirects = + pendingLocation?.href === location.href + ? (pendingLocation._redirects ?? 0) + : 0 + const handoff = router._handoff + const hydrationController = rematerialize + ? undefined + : handoff?.[0 /* claim */]() + const preflight = new AbortController() + const previousPreflight = router._preflight + router._preflight = preflight + if (!rematerialize && !hydrationController) { + handoff?.[1 /* finish */]() + } + previousPreflight?.abort() + // The preflight controller is not exposed to route hooks. Every replacement + // aborts its predecessor, so a live signal is the sole authority here. + if (preflight.signal.aborted) { + await awaitCurrent(router, previousOwner) + return + } + + const changeInfo = getLocationChangeInfo(location, resolvedLocation) + router.emit({ type: 'onBeforeNavigate', ...changeInfo }) + if (!preflight.signal.aborted) { + router.emit({ type: 'onBeforeLoad', ...changeInfo }) + } + if (preflight.signal.aborted) { + await awaitCurrent(router, previousOwner) + return + } + const sameHref = previousLocation.href === location.href + let matches: Array + let controller = preflight + try { + matches = + process.env.NODE_ENV !== 'production' && rematerialize + ? router.matchRoutes(location, { + _controller: preflight, + _rematerialize: true, + }) + : router.matchRoutes(location, { _controller: preflight }) + acquireMatchResources(matches) + } catch (cause) { + preflight.abort() + if (!isRedirect(cause)) { + if (process.env.NODE_ENV !== 'production' && rematerialize) { + router._refreshNextLoad = undefined + } + await awaitCurrent(router) + router._commitPromise?.resolve() + router._commitPromise = undefined + return + } + await router.navigate({ + ...cause.options, + replace: true, + ignoreBlocker: true, + }) + await awaitCurrent(router, previousOwner) + return + } + const resolvedPrefix = hydrationController + ? handoff![1 /* finish */](matches) + : undefined + if (resolvedPrefix) { + controller = hydrationController! + } else { + hydrationController?.abort() + } + if (preflight.signal.aborted) { + transferMatchResources(router, matches) + await awaitCurrent(router, previousOwner) + return + } + router._preflight = undefined + + const tx: LoadTransaction = [ + controller, + redirects, + location, + matches, + Date.now(), + Promise.resolve() + .then(() => + runClientTransaction( + router, + tx, + sameHref, + () => offerPending(router, tx), + opts?.sync, + resolvedPrefix, + ), + ) + .catch(() => { + if (router._tx === tx) { + restoreCommitted(router, tx) + } + }), + ] + if (process.env.NODE_ENV !== 'production' && rematerialize) { + // `refreshPresentation` is always captured when `rematerialize` is set. + tx[6 /* refresh */] = [refreshPresentation!, handoff] + router._refreshNextLoad = undefined + } + router._tx = tx + if (previousOwner) { + for (const match of router.stores.matches.get() as Array) { + if (router._tx !== tx) { + break + } + if (match.isFetching) { + setFetching(router, match, false) + } + } + previousOwner[0 /* controller */].abort() + transferPredecessorResources( + router, + previousOwner[3 /* matches */], + tx[3 /* matches */], + ) + } + if (router._tx !== tx) { + transferMatchResources(router, tx[3 /* matches */]) + tx[3 /* matches */] = [] + await awaitCurrent(router, tx) + return + } + router.batch(() => { + router.stores.status.set('pending') + router.stores.location.set(location) + }) + offerPending(router, tx) + try { + await tx[5 /* done */] + } finally { + await awaitCurrent(router, tx) + } +} + +export async function refreshClientRoute( + router: CoordinatorRouter, +): Promise { + router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.() + const pending = router._tx + if ( + pending && + !pending[6 /* refresh */] && + router.stores.status.get() === 'pending' + ) { + await pending[5 /* done */] + if (router._tx !== pending) { + await awaitCurrent(router, pending) + } + } + // Existing owners remain alive for rollback but cannot donate stale work. + router._flights?.clear() + router.clearCache() + router._refreshNextLoad = true + await loadClientRoute(router, { sync: true }) +} + +export async function preloadClientRoute( + router: CoordinatorRouter, + opts: any, + redirects = 0, +): Promise | undefined> { + if (redirects > 20) { + return + } + if ( + process.env.NODE_ENV !== 'production' && + (router._refreshNextLoad || router._tx?.[6 /* refresh */]) + ) { + return + } + const location = opts._builtLocation ?? router.buildLocation(opts) + const base = router._committed + const controller = new AbortController() + let matches: Array + try { + matches = router.matchRoutes(location, { + _controller: controller, + }) + acquireMatchResources(matches) + } catch (cause) { + controller.abort() + if (!isNotFound(cause)) { + console.error(cause) + } + return + } + ;(router._preloads ??= new Map()).set(controller, matches) + let active: boolean + try { + let result: LaneResult + try { + result = await executeClientLane(router, location, matches, [ + controller, + redirects, + // Preload lanes run to completion even when unrelated navigations commit: + // finished work seeds the cache. + () => true, + base, + true, + ]) + } finally { + active = router._preloads.delete(controller) + transferMatchResources(router, matches) + controller.abort() + } + if (!isControl(result)) { + return result[1 /* matches */] + } + if ( + active && + result[0 /* kind */] === REDIRECTED && + !result[1 /* redirect */].options.reloadDocument + ) { + return preloadClientRoute( + router, + { + ...result[1 /* redirect */].options, + _fromLocation: location, + }, + redirects + 1, + ) + } + } catch (cause) { + if (!isNotFound(cause)) { + console.error(cause) + } + } + return +} + +// --- SSR hydration (client entry via @tanstack/router-core/ssr/client) --- + +declare global { + interface Window { + [GLOBAL_TSR]?: TsrSsrGlobal + [GLOBAL_SEROVAL]?: any + } +} + +export async function hydrate(router: AnyRouter): Promise { + if (process.env.NODE_ENV !== 'production' && !window.$_TSR) { + throw new Error( + 'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', + ) + } + const tsr = window.$_TSR! + + const adapters = router.options.serializationAdapters as + | Array + | undefined + if (adapters?.length) { + tsr.t = new Map( + adapters.map((adapter) => [adapter.key, adapter.fromSerializable]), + ) + tsr.buffer.forEach((script) => script()) + } + tsr.initialized = true + + const dehydratedRouter = tsr.router + if (process.env.NODE_ENV !== 'production' && !dehydratedRouter) { + throw new Error( + 'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', + ) + } + router.ssr = { manifest: dehydratedRouter!.manifest } + router.options.ssr = { + nonce: ( + document.querySelector('meta[property="csp-nonce"]') as + | HTMLMetaElement + | undefined + )?.content, + } + + const dehydratedMatches = dehydratedRouter!.matches + + const controller = new AbortController() + const previousPreflight = router._preflight + router._preflight = controller + previousPreflight?.abort() + // Route context can abort this controller itself. Only a new slot owner + // supersedes hydration. + const isCurrent = () => router._preflight === controller + + let location!: AnyRouter['latestLocation'] + let candidates!: Array + let handoffHistoryHref!: string + let handoffHistoryState: unknown + try { + await waitFor( + router.options.hydrate?.(dehydratedRouter!.dehydratedData), + controller.signal, + ) + if (!isCurrent()) { + return + } + // Hydration trusts transported context and beforeLoad. The raw history + // entry owns the handoff; route structure is verified after rematching. + const historyLocation = router.history.location + handoffHistoryHref = historyLocation.href + handoffHistoryState = historyLocation.state + router.updateLatestLocation() + location = router.latestLocation + router.stores.location.set(location) + candidates = router.matchRoutes(location, { + _controller: controller, + }) + } catch (cause) { + if (isCurrent()) { + router._preflight = undefined + } + controller.abort(cause) + if (cause !== controller.signal) { + throw cause + } + } + if (!isCurrent()) { + return + } + const committed: Array = [] + let pendingBoundary: number | undefined + let verifiedAssetEnd = 0 + const retryFrom = (index: number) => { + // The failing route's identity is still verified, but no descendant is. + verifiedAssetEnd = Math.min(verifiedAssetEnd, index + 1) + const removed = committed.splice(index) + for (const match of removed) { + if ( + getRoute(router, match).options.loader && + (match.status === 'success' || + (!match.invalid && 'loaderData' in match)) + ) { + cacheLoaderMatch( + router, + // Phase jump: dehydrated server data is already past the loader + // phase — the guard above verified a settled success (or transported + // loaderData), so this clone is settled without a client settleInto. + { + ...match, + status: 'success', + error: undefined, + preload: true, + } as SettledMatch, + router._cache.get(match.id), + ) + } + } + transferMatchResources(router, removed) + } + + // A longer server lane is valid only when the local match already caps the + // branch at a global not-found boundary. Otherwise no transported work is + // safe to attach to the shorter client lane. + const shared = + dehydratedMatches.length > candidates.length + ? candidates.findIndex((match) => match._notFound) + 1 + : dehydratedMatches.length + let isTerminal = false + for (let index = 0; index < shared; index++) { + const candidate = candidates[index]! + const dehydrated = dehydratedMatches[index]! + if ( + typeof dehydrated.i !== 'string' || + hydrateSsrMatchId(dehydrated.i) !== candidate.id + ) { + pendingBoundary ??= index + break + } + verifiedAssetEnd = index + 1 + const route = getRoute(router, candidate) + if ( + 'l' in dehydrated || + (dehydrated.s === 'success' && + dehydrated.e === undefined && + route.options.loader) + ) { + candidate.loaderData = dehydrated.l + } + candidate.status = dehydrated.s + candidate.ssr = dehydrated.ssr + route.options.ssr = candidate.ssr + candidate.updatedAt = dehydrated.u + candidate.error = dehydrated.e + candidate._notFound ||= dehydrated.g + const terminal = + candidate.status === 'error' || + candidate.status === 'notFound' || + candidate._notFound + if (terminal) { + isTerminal = true + committed.push(candidate) + if (candidate.ssr === false || candidate.ssr === 'data-only') { + pendingBoundary ??= index + } + break + } + if (candidate.status === 'pending') { + pendingBoundary ??= index + break + } + + committed.push(candidate) + if (candidate.ssr === 'data-only') { + pendingBoundary ??= index + } + } + if ( + !isTerminal && + committed.length === shared && + shared < candidates.length + ) { + pendingBoundary = shared + } + + // Hooks observe structural membership. Execution remains limited to + // `committed`, the accepted server prefix. + const chunks = committed.map(async (match) => { + try { + const route = getRoute(router, match) + if (match._notFound) { + await Promise.all([ + loadRouteChunk(route), + loadRouteChunk(route, 'notFoundComponent'), + ]) + } else { + await loadRouteChunk( + route, + match.status === 'error' + ? 'errorComponent' + : match.status === 'notFound' + ? 'notFoundComponent' + : undefined, + ) + } + return true + } catch { + return false + } + }) + let chunkFailure = 0 + try { + while ( + chunkFailure < chunks.length && + (await waitFor(chunks[chunkFailure]!, controller.signal)) + ) { + chunkFailure++ + } + } catch { + return + } + if (!isCurrent()) { + return + } + if (chunkFailure < committed.length) { + retryFrom(chunkFailure) + } + + // The first pending match is already visible, so prepare its route context + // without granting its beforeLoad or loader any hydration authority. + const contextEnd = Math.max( + pendingBoundary === committed.length + ? committed.length + 1 + : committed.length, + // `chunks.length` keeps the pre-retry committed length, so a smaller + // `chunkFailure` is the exclusive bound of the verified context prefix. + chunkFailure < chunks.length ? chunkFailure : verifiedAssetEnd, + ) + for (let index = 0; index < contextEnd; index++) { + const match = candidates[index]! + const route = getRoute(router, match) + const parentContext = + candidates[index - 1]?.context ?? router.options.context ?? {} + let routeContext + if (route.options.context) { + try { + routeContext = match._ctx = + route.options.context({ + deps: match.loaderDeps, + params: match.params, + context: parentContext, + location, + navigate: navigateFrom(router, location), + buildLocation: router.buildLocation, + cause: match.cause, + abortController: controller, + preload: false, + matches: candidates, + routeId: route.id, + }) || {} + } catch { + if (!isCurrent()) { + return + } + if ( + match.status !== 'error' && + match.status !== 'notFound' && + !match._notFound + ) { + retryFrom(index) + break + } + } + if (!isCurrent()) { + return + } + } + match.context = { + ...parentContext, + ...routeContext, + ...(committed[index] && dehydratedMatches[index]!.b), + } + } + + await projectLane( + router, + [location, candidates] as any, + controller.signal, + 0, + verifiedAssetEnd, + ) + if (!isCurrent()) { + return + } + const needsClientLoad = + pendingBoundary !== undefined || committed.length < shared + const committedMatches = + isTerminal && committed.length === shared ? candidates : committed + let presented = needsClientLoad ? candidates : committedMatches + let dataOnlyAssetEnd: number | undefined + if (needsClientLoad && pendingBoundary !== undefined) { + const boundary = presented[pendingBoundary]! + // A verified descendant proves this data-only boundary was nonterminal. + dataOnlyAssetEnd = + boundary.ssr === 'data-only' && verifiedAssetEnd > pendingBoundary + 1 + ? verifiedAssetEnd + : undefined + presented = presented.slice() + presented[pendingBoundary] = { + ...boundary, + status: 'pending', + ssr: boundary.ssr === 'data-only' ? 'data-only' : false, + _assetEnd: dataOnlyAssetEnd, + } + } + + const claim = () => { + const historyLocation = router.history.location + return needsClientLoad && + !router._tx && + historyLocation.href === handoffHistoryHref && + historyLocation.state === handoffHistoryState && + router._committed === committedMatches && + committedMatches.length && + !controller.signal.aborted + ? controller + : undefined + } + const handoff: NonNullable = [ + claim, + (matches) => { + if (router._handoff !== handoff) { + return + } + // `finish` is single-use. Consume the slot before validating or moving + // resources so reentrant work cannot claim the same handoff. + router._handoff = undefined + const prefix = committedMatches.length + if ( + !matches || + !claim() || + committedMatches.some((match, index) => match.id !== matches[index]?.id) + ) { + controller.abort() + return + } + let handoffAssetEnd = dataOnlyAssetEnd + if (handoffAssetEnd !== undefined) { + for (let index = prefix; index < handoffAssetEnd; index++) { + if (candidates[index]?.id !== matches[index]?.id) { + handoffAssetEnd = index > pendingBoundary! + 1 ? index : undefined + break + } + } + } + const clones = committedMatches.map((match) => ({ ...match })) + if (handoffAssetEnd !== undefined) { + clones[pendingBoundary!]!._assetEnd = handoffAssetEnd + } + transferMatchResources(router, matches.splice(0, prefix, ...clones)) + for (let index = prefix; index < matches.length; index++) { + const match = matches[index]! + const hydrated = candidates[index] + if (hydrated?.id === match.id && hydrated._ctx) { + match._ctx = hydrated._ctx + } + match.abortController = controller + } + return prefix + }, + ] + router._committed = committedMatches + router._handoff = handoff + router._preflight = undefined + router.batch(() => { + router.stores.setMatches(presented) + router.stores.status.set('idle') + if (!needsClientLoad) { + router.stores.resolvedLocation.set(router.stores.location.get()) + } + }) +} diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts deleted file mode 100644 index f901a0c97d..0000000000 --- a/packages/router-core/src/load-matches.ts +++ /dev/null @@ -1,1278 +0,0 @@ -import { isServer } from '@tanstack/router-core/isServer' -import { invariant } from './invariant' -import { createControlledPromise, isPromise } from './utils' -import { isNotFound } from './not-found' -import { rootRouteId } from './root' -import { isRedirect } from './redirect' -import type { NotFoundError } from './not-found' -import type { ParsedLocation } from './location' -import type { - AnyRoute, - BeforeLoadContextOptions, - LoaderFnContext, - SsrContextOptions, -} from './route' -import type { AnyRouteMatch, MakeRouteMatch } from './Matches' -import type { AnyRouter, SSROption, UpdateMatchFn } from './router' - -/** - * An object of this shape is created when calling `loadMatches`. - * It contains everything we need for all other functions in this file - * to work. (It's basically the function's argument, plus a few mutable states) - */ -type InnerLoadContext = { - /** the calling router instance */ - router: AnyRouter - location: ParsedLocation - /** mutable state, scoped to a `loadMatches` call */ - firstBadMatchIndex?: number - /** mutable state, scoped to a `loadMatches` call */ - rendered?: boolean - serialError?: unknown - updateMatch: UpdateMatchFn - matches: Array - preload?: boolean - forceStaleReload?: boolean - onReady?: () => Promise - sync?: boolean -} - -const triggerOnReady = (inner: InnerLoadContext): void | Promise => { - if (!inner.rendered) { - inner.rendered = true - return inner.onReady?.() - } -} - -const hasForcePendingActiveMatch = (router: AnyRouter): boolean => { - return router.stores.matchesId.get().some((matchId) => { - return router.stores.matchStores.get(matchId)?.get()._forcePending - }) -} - -const resolvePreload = (inner: InnerLoadContext, matchId: string): boolean => { - return !!(inner.preload && !inner.router.stores.matchStores.has(matchId)) -} - -/** - * Builds the accumulated context from router options and all matches up to (and optionally including) the given index. - * Merges __routeContext and __beforeLoadContext from each match. - */ -const buildMatchContext = ( - inner: InnerLoadContext, - index: number, - includeCurrentMatch: boolean = true, -): Record => { - const context: Record = { - ...(inner.router.options.context ?? {}), - } - const end = includeCurrentMatch ? index : index - 1 - for (let i = 0; i <= end; i++) { - const innerMatch = inner.matches[i] - if (!innerMatch) continue - const m = inner.router.getMatch(innerMatch.id) - if (!m) continue - Object.assign(context, m.__routeContext, m.__beforeLoadContext) - } - return context -} - -const getNotFoundBoundaryIndex = ( - inner: InnerLoadContext, - err: NotFoundError, -): number | undefined => { - if (!inner.matches.length) { - return undefined - } - - const requestedRouteId = err.routeId - const matchedRootIndex = inner.matches.findIndex( - (m) => m.routeId === inner.router.routeTree.id, - ) - const rootIndex = matchedRootIndex >= 0 ? matchedRootIndex : 0 - - let startIndex = requestedRouteId - ? inner.matches.findIndex((match) => match.routeId === requestedRouteId) - : (inner.firstBadMatchIndex ?? inner.matches.length - 1) - - if (startIndex < 0) { - startIndex = rootIndex - } - - for (let i = startIndex; i >= 0; i--) { - const match = inner.matches[i]! - const route = inner.router.looseRoutesById[match.routeId]! - if (route.options.notFoundComponent) { - return i - } - } - - // If no boundary component is found, preserve explicit routeId targeting behavior, - // otherwise default to root for untargeted notFounds. - return requestedRouteId ? startIndex : rootIndex -} - -const handleRedirectAndNotFound = ( - inner: InnerLoadContext, - match: AnyRouteMatch | undefined, - err: unknown, -): void => { - if (!isRedirect(err) && !isNotFound(err)) return - - if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) { - throw err - } - - // in case of a redirecting match during preload, the match does not exist - if (match) { - match._nonReactive.beforeLoadPromise?.resolve() - match._nonReactive.loaderPromise?.resolve() - match._nonReactive.beforeLoadPromise = undefined - match._nonReactive.loaderPromise = undefined - - match._nonReactive.error = err - - inner.updateMatch(match.id, (prev) => ({ - ...prev, - status: isRedirect(err) - ? 'redirected' - : isNotFound(err) - ? 'notFound' - : prev.status === 'pending' - ? 'success' - : prev.status, - context: buildMatchContext(inner, match.index), - isFetching: false, - error: err, - })) - - if (isNotFound(err) && !err.routeId) { - // Stamp the throwing match's routeId so that the finalization step in - // loadMatches knows where the notFound originated. The actual boundary - // resolution (walking up to the nearest notFoundComponent) is deferred to - // the finalization step, where firstBadMatchIndex is stable and - // headMaxIndex can be capped correctly. - err.routeId = match.routeId - } - - match._nonReactive.loadPromise?.resolve() - } - - if (isRedirect(err)) { - inner.rendered = true - err.options._fromLocation = inner.location - err.redirectHandled = true - err = inner.router.resolveRedirect(err) - } - - throw err -} - -const shouldSkipLoader = ( - inner: InnerLoadContext, - matchId: string, -): boolean => { - const match = inner.router.getMatch(matchId) - if (!match) { - return true - } - // upon hydration, we skip the loader if the match has been dehydrated on the server - if (!(isServer ?? inner.router.isServer) && match._nonReactive.dehydrated) { - return true - } - - if ((isServer ?? inner.router.isServer) && match.ssr === false) { - return true - } - - return false -} - -const syncMatchContext = ( - inner: InnerLoadContext, - matchId: string, - index: number, -): void => { - const nextContext = buildMatchContext(inner, index) - - inner.updateMatch(matchId, (prev) => { - return { - ...prev, - context: nextContext, - } - }) -} - -const handleSerialError = ( - inner: InnerLoadContext, - index: number, - err: any, -): void => { - const { id: matchId, routeId } = inner.matches[index]! - const route = inner.router.looseRoutesById[routeId]! - - // Much like suspense, we use a promise here to know if - // we've been outdated by a new loadMatches call and - // should abort the current async operation - if (err instanceof Promise) { - throw err - } - - inner.firstBadMatchIndex ??= index - handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err) - - try { - route.options.onError?.(err) - } catch (errorHandlerErr) { - err = errorHandlerErr - handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err) - } - - inner.updateMatch(matchId, (prev) => { - prev._nonReactive.beforeLoadPromise?.resolve() - prev._nonReactive.beforeLoadPromise = undefined - prev._nonReactive.loadPromise?.resolve() - - return { - ...prev, - error: err, - status: 'error', - isFetching: false, - updatedAt: Date.now(), - abortController: new AbortController(), - } - }) - - if (!inner.preload && !isRedirect(err) && !isNotFound(err)) { - inner.serialError ??= err - } -} - -const isBeforeLoadSsr = ( - inner: InnerLoadContext, - matchId: string, - index: number, - route: AnyRoute, -): void | Promise => { - const existingMatch = inner.router.getMatch(matchId)! - const parentMatchId = inner.matches[index - 1]?.id - const parentMatch = parentMatchId - ? inner.router.getMatch(parentMatchId)! - : undefined - - // in SPA mode, only SSR the root route - if (inner.router.isShell()) { - existingMatch.ssr = route.id === rootRouteId - return - } - - if (parentMatch?.ssr === false) { - existingMatch.ssr = false - return - } - - const parentOverride = (tempSsr: SSROption) => { - if (tempSsr === true && parentMatch?.ssr === 'data-only') { - return 'data-only' - } - return tempSsr - } - - const defaultSsr = inner.router.options.defaultSsr ?? true - - if (route.options.ssr === undefined) { - existingMatch.ssr = parentOverride(defaultSsr) - return - } - - if (typeof route.options.ssr !== 'function') { - existingMatch.ssr = parentOverride(route.options.ssr) - return - } - const { search, params } = existingMatch - - const ssrFnContext: SsrContextOptions = { - search: makeMaybe(search, existingMatch.searchError), - params: makeMaybe(params, existingMatch.paramsError), - location: inner.location, - matches: inner.matches.map((match) => ({ - index: match.index, - pathname: match.pathname, - fullPath: match.fullPath, - staticData: match.staticData, - id: match.id, - routeId: match.routeId, - search: makeMaybe(match.search, match.searchError), - params: makeMaybe(match.params, match.paramsError), - ssr: match.ssr, - })), - } - - const tempSsr = route.options.ssr(ssrFnContext) - if (isPromise(tempSsr)) { - return tempSsr.then((ssr) => { - existingMatch.ssr = parentOverride(ssr ?? defaultSsr) - }) - } - - existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr) - return -} - -const setupPendingTimeout = ( - inner: InnerLoadContext, - matchId: string, - route: AnyRoute, - match: AnyRouteMatch, -): void => { - if (match._nonReactive.pendingTimeout !== undefined) return - - const pendingMs = - route.options.pendingMs ?? inner.router.options.defaultPendingMs - const shouldPending = !!( - inner.onReady && - !(isServer ?? inner.router.isServer) && - !resolvePreload(inner, matchId) && - (route.options.loader || - route.options.beforeLoad || - routeNeedsPreload(route)) && - typeof pendingMs === 'number' && - pendingMs !== Infinity && - (route.options.pendingComponent ?? - (inner.router.options as any)?.defaultPendingComponent) - ) - - if (shouldPending) { - const pendingTimeout = setTimeout(() => { - // Update the match and prematurely resolve the loadMatches promise so that - // the pending component can start rendering - triggerOnReady(inner) - }, pendingMs) - match._nonReactive.pendingTimeout = pendingTimeout - } -} - -const preBeforeLoadSetup = ( - inner: InnerLoadContext, - matchId: string, - route: AnyRoute, -): void | Promise => { - const existingMatch = inner.router.getMatch(matchId)! - - // If we are in the middle of a load, either of these will be present - // (not to be confused with `loadPromise`, which is always defined) - if ( - !existingMatch._nonReactive.beforeLoadPromise && - !existingMatch._nonReactive.loaderPromise - ) - return - - setupPendingTimeout(inner, matchId, route, existingMatch) - - const then = () => { - const match = inner.router.getMatch(matchId)! - if ( - match.preload && - (match.status === 'redirected' || match.status === 'notFound') - ) { - handleRedirectAndNotFound(inner, match, match.error) - } - } - - // Wait for the previous beforeLoad to resolve before we continue - return existingMatch._nonReactive.beforeLoadPromise - ? existingMatch._nonReactive.beforeLoadPromise.then(then) - : then() -} - -const executeBeforeLoad = ( - inner: InnerLoadContext, - matchId: string, - index: number, - route: AnyRoute, -): void | Promise => { - const match = inner.router.getMatch(matchId)! - - // explicitly capture the previous loadPromise - let prevLoadPromise = match._nonReactive.loadPromise - match._nonReactive.loadPromise = createControlledPromise(() => { - prevLoadPromise?.resolve() - prevLoadPromise = undefined - }) - - const { paramsError, searchError } = match - - if (paramsError) { - handleSerialError(inner, index, paramsError) - } - - if (searchError) { - handleSerialError(inner, index, searchError) - } - - setupPendingTimeout(inner, matchId, route, match) - - const abortController = new AbortController() - - let isPending = false - const pending = () => { - if (isPending) return - isPending = true - inner.updateMatch(matchId, (prev) => ({ - ...prev, - isFetching: 'beforeLoad', - fetchCount: prev.fetchCount + 1, - abortController, - // Note: We intentionally don't update context here. - // Context should only be updated after beforeLoad resolves to avoid - // components seeing incomplete context during async beforeLoad execution. - })) - } - - const resolve = () => { - match._nonReactive.beforeLoadPromise?.resolve() - match._nonReactive.beforeLoadPromise = undefined - inner.updateMatch(matchId, (prev) => ({ - ...prev, - isFetching: false, - })) - } - - // if there is no `beforeLoad` option, just mark as pending and resolve - // Context will be updated later in loadRouteMatch after loader completes - if (!route.options.beforeLoad) { - inner.router.batch(() => { - pending() - resolve() - }) - return - } - - match._nonReactive.beforeLoadPromise = createControlledPromise() - - // Build context from all parent matches, excluding current match's __beforeLoadContext - // (since we're about to execute beforeLoad for this match) - const context = { - ...buildMatchContext(inner, index, false), - ...match.__routeContext, - } - const { search, params, cause } = match - const preload = resolvePreload(inner, matchId) - const beforeLoadFnContext: BeforeLoadContextOptions< - any, - any, - any, - any, - any, - any, - any, - any, - any - > = { - search, - abortController, - params, - preload, - context, - location: inner.location, - navigate: (opts: any) => - inner.router.navigate({ - ...opts, - _fromLocation: inner.location, - }), - buildLocation: inner.router.buildLocation, - cause: preload ? 'preload' : cause, - matches: inner.matches, - routeId: route.id, - ...inner.router.options.additionalContext, - } - - const updateContext = (beforeLoadContext: any) => { - if (beforeLoadContext === undefined) { - inner.router.batch(() => { - pending() - resolve() - }) - return - } - if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) { - pending() - handleSerialError(inner, index, beforeLoadContext) - } - - inner.router.batch(() => { - pending() - inner.updateMatch(matchId, (prev) => ({ - ...prev, - __beforeLoadContext: beforeLoadContext, - })) - resolve() - }) - } - - let beforeLoadContext - try { - beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext) - if (isPromise(beforeLoadContext)) { - pending() - return beforeLoadContext - .catch((err) => { - handleSerialError(inner, index, err) - }) - .then(updateContext) - } - } catch (err) { - pending() - handleSerialError(inner, index, err) - } - - updateContext(beforeLoadContext) - return -} - -const handleBeforeLoad = ( - inner: InnerLoadContext, - index: number, -): void | Promise => { - const { id: matchId, routeId } = inner.matches[index]! - const route = inner.router.looseRoutesById[routeId]! - - const serverSsr = () => { - // on the server, determine whether SSR the current match or not - if (isServer ?? inner.router.isServer) { - const maybePromise = isBeforeLoadSsr(inner, matchId, index, route) - if (isPromise(maybePromise)) return maybePromise.then(queueExecution) - } - return queueExecution() - } - - const execute = () => executeBeforeLoad(inner, matchId, index, route) - - const queueExecution = () => { - if (shouldSkipLoader(inner, matchId)) return - const result = preBeforeLoadSetup(inner, matchId, route) - return isPromise(result) ? result.then(execute) : execute() - } - - return serverSsr() -} - -const executeHead = ( - inner: InnerLoadContext, - matchId: string, - route: AnyRoute, -): void | Promise< - Pick< - AnyRouteMatch, - 'meta' | 'links' | 'headScripts' | 'headers' | 'scripts' | 'styles' - > -> => { - const match = inner.router.getMatch(matchId) - // in case of a redirecting match during preload, the match does not exist - if (!match) { - return - } - if (!route.options.head && !route.options.scripts && !route.options.headers) { - return - } - const assetContext = { - ssr: inner.router.options.ssr, - matches: inner.matches, - match, - params: match.params, - loaderData: match.loaderData, - } - - return Promise.all([ - route.options.head?.(assetContext), - route.options.scripts?.(assetContext), - route.options.headers?.(assetContext), - ]).then(([headFnContent, scripts, headers]) => { - const meta = headFnContent?.meta - const links = headFnContent?.links - const headScripts = headFnContent?.scripts - const styles = headFnContent?.styles - - return { - meta, - links, - headScripts, - headers, - scripts, - styles, - } - }) -} - -const getLoaderContext = ( - inner: InnerLoadContext, - matchPromises: Array>, - matchId: string, - index: number, - route: AnyRoute, -): LoaderFnContext => { - const parentMatchPromise = matchPromises[index - 1] as any - const { params, loaderDeps, abortController, cause } = - inner.router.getMatch(matchId)! - - const context = buildMatchContext(inner, index) - - const preload = resolvePreload(inner, matchId) - - return { - params, - deps: loaderDeps, - preload: !!preload, - parentMatchPromise, - abortController, - context, - location: inner.location, - navigate: (opts) => - inner.router.navigate({ - ...opts, - _fromLocation: inner.location, - }), - cause: preload ? 'preload' : cause, - route, - ...inner.router.options.additionalContext, - } -} - -const runLoader = async ( - inner: InnerLoadContext, - matchPromises: Array>, - matchId: string, - index: number, - route: AnyRoute, -): Promise => { - try { - // If the Matches component rendered - // the pending component and needs to show it for - // a minimum duration, we''ll wait for it to resolve - // before committing to the match and resolving - // the loadPromise - - const match = inner.router.getMatch(matchId)! - - // Actually run the loader and handle the result - try { - if (!(isServer ?? inner.router.isServer) || match.ssr === true) { - loadRouteChunk(route) - } - - // Kick off the loader! - const routeLoader = route.options.loader - const loader = - typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler - const loaderResult = loader?.( - getLoaderContext(inner, matchPromises, matchId, index, route), - ) - const loaderResultIsPromise = !!loader && isPromise(loaderResult) - - const willLoadSomething = !!( - loaderResultIsPromise || - route._lazyPromise || - route._componentsPromise || - route.options.head || - route.options.scripts || - route.options.headers || - match._nonReactive.minPendingPromise - ) - - if (willLoadSomething) { - inner.updateMatch(matchId, (prev) => ({ - ...prev, - isFetching: 'loader', - })) - } - - if (loader) { - const loaderData = loaderResultIsPromise - ? await loaderResult - : loaderResult - - handleRedirectAndNotFound( - inner, - inner.router.getMatch(matchId), - loaderData, - ) - if (loaderData !== undefined) { - inner.updateMatch(matchId, (prev) => ({ - ...prev, - loaderData, - })) - } - } - - // Lazy option can modify the route options, - // so we need to wait for it to resolve before - // we can use the options - if (route._lazyPromise) await route._lazyPromise - const pendingPromise = match._nonReactive.minPendingPromise - if (pendingPromise) await pendingPromise - - // Last but not least, wait for the components - // to be preloaded before we resolve the match - if (route._componentsPromise) await route._componentsPromise - inner.updateMatch(matchId, (prev) => ({ - ...prev, - error: undefined, - context: buildMatchContext(inner, index), - status: 'success', - isFetching: false, - updatedAt: Date.now(), - })) - } catch (e) { - let error = e - - if ((error as any)?.name === 'AbortError') { - if (match.abortController.signal.aborted) { - match._nonReactive.loaderPromise?.resolve() - match._nonReactive.loaderPromise = undefined - return - } - inner.updateMatch(matchId, (prev) => ({ - ...prev, - status: prev.status === 'pending' ? 'success' : prev.status, - isFetching: false, - context: buildMatchContext(inner, index), - })) - return - } - - const pendingPromise = match._nonReactive.minPendingPromise - if (pendingPromise) await pendingPromise - - if (isNotFound(e)) { - await (route.options.notFoundComponent as any)?.preload?.() - } - - handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), e) - - try { - route.options.onError?.(e) - } catch (onErrorError) { - error = onErrorError - handleRedirectAndNotFound( - inner, - inner.router.getMatch(matchId), - onErrorError, - ) - } - if (!isRedirect(error) && !isNotFound(error)) { - await loadRouteChunk(route, ['errorComponent']) - } - - inner.updateMatch(matchId, (prev) => ({ - ...prev, - error, - context: buildMatchContext(inner, index), - status: 'error', - isFetching: false, - })) - } - } catch (err) { - const match = inner.router.getMatch(matchId) - // in case of a redirecting match during preload, the match does not exist - if (match) { - match._nonReactive.loaderPromise = undefined - } - handleRedirectAndNotFound(inner, match, err) - } -} - -const loadRouteMatch = async ( - inner: InnerLoadContext, - matchPromises: Array>, - index: number, -): Promise => { - async function handleLoader( - preload: boolean, - prevMatch: AnyRouteMatch, - previousRouteMatchId: string | undefined, - match: AnyRouteMatch, - route: AnyRoute, - ) { - const age = Date.now() - prevMatch.updatedAt - - const staleAge = preload - ? (route.options.preloadStaleTime ?? - inner.router.options.defaultPreloadStaleTime ?? - 30_000) // 30 seconds for preloads by default - : (route.options.staleTime ?? inner.router.options.defaultStaleTime ?? 0) - - const shouldReloadOption = route.options.shouldReload - - // Default to reloading the route all the time - // Allow shouldReload to get the last say, - // if provided. - const shouldReload = - typeof shouldReloadOption === 'function' - ? shouldReloadOption( - getLoaderContext(inner, matchPromises, matchId, index, route), - ) - : shouldReloadOption - - // If the route is successful and still fresh, just resolve - const { status, invalid } = match - const staleMatchShouldReload = - age >= staleAge && - (!!inner.forceStaleReload || - match.cause === 'enter' || - (previousRouteMatchId !== undefined && - previousRouteMatchId !== match.id)) - loaderShouldRunAsync = - status === 'success' && - (invalid || (shouldReload ?? staleMatchShouldReload)) - if (preload && route.options.preload === false) { - // Do nothing - } else if ( - loaderShouldRunAsync && - !inner.sync && - shouldReloadInBackground - ) { - loaderIsRunningAsync = true - ;(async () => { - try { - await runLoader(inner, matchPromises, matchId, index, route) - const match = inner.router.getMatch(matchId)! - match._nonReactive.loaderPromise?.resolve() - match._nonReactive.loadPromise?.resolve() - match._nonReactive.loaderPromise = undefined - match._nonReactive.loadPromise = undefined - } catch (err) { - if (isRedirect(err)) { - await inner.router.navigate(err.options) - } - } - })() - } else if (status !== 'success' || loaderShouldRunAsync) { - await runLoader(inner, matchPromises, matchId, index, route) - } else { - syncMatchContext(inner, matchId, index) - } - } - - const { id: matchId, routeId } = inner.matches[index]! - let loaderShouldRunAsync = false - let loaderIsRunningAsync = false - const route = inner.router.looseRoutesById[routeId]! - const routeLoader = route.options.loader - const shouldReloadInBackground = - ((typeof routeLoader === 'function' - ? undefined - : routeLoader?.staleReloadMode) ?? - inner.router.options.defaultStaleReloadMode) !== 'blocking' - - if (shouldSkipLoader(inner, matchId)) { - const match = inner.router.getMatch(matchId) - if (!match) { - return inner.matches[index]! - } - - syncMatchContext(inner, matchId, index) - - if (isServer ?? inner.router.isServer) { - return inner.router.getMatch(matchId)! - } - } else { - const prevMatch = inner.router.getMatch(matchId)! // This is where all of the stale-while-revalidate magic happens - const activeIdAtIndex = inner.router.stores.matchesId.get()[index] - const activeAtIndex = - (activeIdAtIndex && - inner.router.stores.matchStores.get(activeIdAtIndex)) || - null - const previousRouteMatchId = - activeAtIndex?.routeId === routeId - ? activeIdAtIndex - : inner.router.stores.matches.get().find((d) => d.routeId === routeId) - ?.id - const preload = resolvePreload(inner, matchId) - - // there is a loaderPromise, so we are in the middle of a load - if (prevMatch._nonReactive.loaderPromise) { - // do not block if we already have stale data we can show - // but only if the ongoing load is not a preload since error handling is different for preloads - // and we don't want to swallow errors - if ( - prevMatch.status === 'success' && - !inner.sync && - !prevMatch.preload && - shouldReloadInBackground - ) { - return prevMatch - } - await prevMatch._nonReactive.loaderPromise - const match = inner.router.getMatch(matchId)! - const error = match._nonReactive.error || match.error - if (error) { - handleRedirectAndNotFound(inner, match, error) - } - - if (match.status === 'pending') { - await handleLoader( - preload, - prevMatch, - previousRouteMatchId, - match, - route, - ) - } - } else { - const nextPreload = - preload && !inner.router.stores.matchStores.has(matchId) - const match = inner.router.getMatch(matchId)! - match._nonReactive.loaderPromise = createControlledPromise() - if (nextPreload !== match.preload) { - inner.updateMatch(matchId, (prev) => ({ - ...prev, - preload: nextPreload, - })) - } - - await handleLoader(preload, prevMatch, previousRouteMatchId, match, route) - } - } - const match = inner.router.getMatch(matchId)! - if (!loaderIsRunningAsync) { - match._nonReactive.loaderPromise?.resolve() - match._nonReactive.loadPromise?.resolve() - match._nonReactive.loadPromise = undefined - } - - clearTimeout(match._nonReactive.pendingTimeout) - match._nonReactive.pendingTimeout = undefined - if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined - match._nonReactive.dehydrated = undefined - - const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false - if (nextIsFetching !== match.isFetching || match.invalid !== false) { - inner.updateMatch(matchId, (prev) => ({ - ...prev, - isFetching: nextIsFetching, - invalid: false, - })) - return inner.router.getMatch(matchId)! - } else { - return match - } -} - -export async function loadMatches(arg: { - router: AnyRouter - location: ParsedLocation - matches: Array - preload?: boolean - forceStaleReload?: boolean - onReady?: () => Promise - updateMatch: UpdateMatchFn - sync?: boolean -}): Promise> { - const inner: InnerLoadContext = arg - const matchPromises: Array> = [] - - // make sure the pending component is immediately rendered when hydrating a match that is not SSRed - // the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached - if ( - !(isServer ?? inner.router.isServer) && - hasForcePendingActiveMatch(inner.router) - ) { - triggerOnReady(inner) - } - - let beforeLoadNotFound: NotFoundError | undefined - - // Execute all beforeLoads one by one - for (let i = 0; i < inner.matches.length; i++) { - try { - const beforeLoad = handleBeforeLoad(inner, i) - if (isPromise(beforeLoad)) await beforeLoad - } catch (err) { - if (isRedirect(err)) { - throw err - } - if (isNotFound(err)) { - beforeLoadNotFound = err - } else { - if (!inner.preload) throw err - } - break - } - - if (inner.serialError || inner.firstBadMatchIndex != null) { - break - } - } - - // Execute loaders once, with max index adapted for beforeLoad notFound handling. - const baseMaxIndexExclusive = inner.firstBadMatchIndex ?? inner.matches.length - - const boundaryIndex = - beforeLoadNotFound && !inner.preload - ? getNotFoundBoundaryIndex(inner, beforeLoadNotFound) - : undefined - - const maxIndexExclusive = - beforeLoadNotFound && inner.preload - ? 0 - : boundaryIndex !== undefined - ? Math.min(boundaryIndex + 1, baseMaxIndexExclusive) - : baseMaxIndexExclusive - - let firstNotFound: NotFoundError | undefined - let firstUnhandledRejection: unknown - - for (let i = 0; i < maxIndexExclusive; i++) { - matchPromises.push(loadRouteMatch(inner, matchPromises, i)) - } - - try { - await Promise.all(matchPromises) - } catch { - const settled = await Promise.allSettled(matchPromises) - - for (const result of settled) { - if (result.status !== 'rejected') continue - - const reason = result.reason - if (isRedirect(reason)) { - throw reason - } - if (isNotFound(reason)) { - firstNotFound ??= reason - } else { - firstUnhandledRejection ??= reason - } - } - - if (firstUnhandledRejection !== undefined) { - throw firstUnhandledRejection - } - } - - const notFoundToThrow = - firstNotFound ?? - (beforeLoadNotFound && !inner.preload ? beforeLoadNotFound : undefined) - - let headMaxIndex = - inner.firstBadMatchIndex !== undefined - ? inner.firstBadMatchIndex - : inner.matches.length - 1 - - if (!notFoundToThrow && beforeLoadNotFound && inner.preload) { - return inner.matches - } - - if (notFoundToThrow) { - // Determine once which matched route will actually render the - // notFoundComponent, then pass this precomputed index through the remaining - // finalization steps. - // This can differ from the throwing route when routeId targets an ancestor - // boundary (or when bubbling resolves to a parent/root boundary). - const renderedBoundaryIndex = getNotFoundBoundaryIndex( - inner, - notFoundToThrow, - ) - - if (renderedBoundaryIndex === undefined) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'Invariant failed: Could not find match for notFound boundary', - ) - } - - invariant() - } - const boundaryMatch = inner.matches[renderedBoundaryIndex]! - - const boundaryRoute = inner.router.looseRoutesById[boundaryMatch.routeId]! - const defaultNotFoundComponent = (inner.router.options as any) - ?.defaultNotFoundComponent - - // Ensure a notFoundComponent exists on the boundary route - if (!boundaryRoute.options.notFoundComponent && defaultNotFoundComponent) { - boundaryRoute.options.notFoundComponent = defaultNotFoundComponent - } - - notFoundToThrow.routeId = boundaryMatch.routeId - - const boundaryIsRoot = boundaryMatch.routeId === inner.router.routeTree.id - - inner.updateMatch(boundaryMatch.id, (prev) => ({ - ...prev, - ...(boundaryIsRoot - ? // For root boundary, use globalNotFound so the root component's - // shell still renders and handles the not-found display, - // instead of replacing the entire root shell via status='notFound'. - { status: 'success' as const, globalNotFound: true, error: undefined } - : // For non-root boundaries, set status:'notFound' so MatchInner - // renders the notFoundComponent directly. - { status: 'notFound' as const, error: notFoundToThrow }), - isFetching: false, - })) - - headMaxIndex = renderedBoundaryIndex - - // Ensure the rendering boundary route chunk (and its lazy components, including - // lazy notFoundComponent) is loaded before we continue to head execution/render. - await loadRouteChunk(boundaryRoute, ['notFoundComponent']) - } else if (!inner.preload) { - // Clear stale root global-not-found state on normal navigations that do not - // throw notFound. This must live here (instead of only in runLoader success) - // because the root loader may be skipped when data is still fresh. - const rootMatch = inner.matches[0]! - // `rootMatch` is the next match for this navigation. If it is not global - // not-found, then any currently stored root global-not-found is stale. - if (!rootMatch.globalNotFound) { - // `currentRootMatch` is the current store state (from the previous - // navigation/load). Update only when a stale flag is actually present. - const currentRootMatch = inner.router.getMatch(rootMatch.id) - if (currentRootMatch?.globalNotFound) { - inner.updateMatch(rootMatch.id, (prev) => ({ - ...prev, - globalNotFound: false, - error: undefined, - })) - } - } - } - - // When a serial error occurred (e.g. beforeLoad threw a regular Error), - // the erroring route's lazy chunk wasn't loaded because loaders were skipped. - // We need to load it so the code-split errorComponent is available for rendering. - if (inner.serialError && inner.firstBadMatchIndex !== undefined) { - const errorRoute = - inner.router.looseRoutesById[ - inner.matches[inner.firstBadMatchIndex]!.routeId - ]! - await loadRouteChunk(errorRoute, ['errorComponent']) - } - - // serially execute heads once after loaders/notFound handling, ensuring - // all head functions get a chance even if one throws. - for (let i = 0; i <= headMaxIndex; i++) { - const match = inner.matches[i]! - const { id: matchId, routeId } = match - const route = inner.router.looseRoutesById[routeId]! - try { - const headResult = executeHead(inner, matchId, route) - if (headResult) { - const head = await headResult - inner.updateMatch(matchId, (prev) => ({ - ...prev, - ...head, - })) - } - } catch (err) { - console.error(`Error executing head for route ${routeId}:`, err) - } - } - - const readyPromise = triggerOnReady(inner) - if (isPromise(readyPromise)) { - await readyPromise - } - - if (notFoundToThrow) { - throw notFoundToThrow - } - - if (inner.serialError && !inner.preload && !inner.onReady) { - throw inner.serialError - } - - return inner.matches -} - -export type RouteComponentType = - | 'component' - | 'errorComponent' - | 'pendingComponent' - | 'notFoundComponent' - -function preloadRouteComponents( - route: AnyRoute, - componentTypesToLoad: Array, -): Promise | undefined { - const preloads = componentTypesToLoad - .map((type) => (route.options[type] as any)?.preload?.()) - .filter(Boolean) - - if (preloads.length === 0) return undefined - - return Promise.all(preloads) as any as Promise -} - -export function loadRouteChunk( - route: AnyRoute, - componentTypesToLoad: Array = componentTypes, -) { - if (!route._lazyLoaded && route._lazyPromise === undefined) { - if (route.lazyFn) { - route._lazyPromise = route.lazyFn().then((lazyRoute) => { - // explicitly don't copy over the lazy route's id - const { id: _id, ...options } = lazyRoute.options - Object.assign(route.options, options) - route._lazyLoaded = true - route._lazyPromise = undefined // gc promise, we won't need it anymore - }) - } else { - route._lazyLoaded = true - } - } - - const runAfterLazy = () => - route._componentsLoaded - ? undefined - : componentTypesToLoad === componentTypes - ? (() => { - if (route._componentsPromise === undefined) { - const componentsPromise = preloadRouteComponents( - route, - componentTypes, - ) - - if (componentsPromise) { - route._componentsPromise = componentsPromise.then(() => { - route._componentsLoaded = true - route._componentsPromise = undefined // gc promise, we won't need it anymore - }) - } else { - route._componentsLoaded = true - } - } - - return route._componentsPromise - })() - : preloadRouteComponents(route, componentTypesToLoad) - - return route._lazyPromise - ? route._lazyPromise.then(runAfterLazy) - : runAfterLazy() -} - -function makeMaybe( - value: TValue, - error: TError, -): { status: 'success'; value: TValue } | { status: 'error'; error: TError } { - if (error) { - return { status: 'error' as const, error } - } - return { status: 'success' as const, value } -} - -export function routeNeedsPreload(route: AnyRoute) { - for (const componentType of componentTypes) { - if ((route.options[componentType] as any)?.preload) { - return true - } - } - return false -} - -export const componentTypes: Array = [ - 'component', - 'errorComponent', - 'pendingComponent', - 'notFoundComponent', -] as const diff --git a/packages/router-core/src/load-server.ts b/packages/router-core/src/load-server.ts new file mode 100644 index 0000000000..083d7ffb40 --- /dev/null +++ b/packages/router-core/src/load-server.ts @@ -0,0 +1,849 @@ +// Keep this filename free of a secondary extension so declaration generation +// can rewrite relative imports for both ESM and CJS. +import { isNotFound } from './not-found' +import { isRedirect, redirect } from './redirect' +import { rootRouteId } from './root' +import { loadRouteChunk } from './load-client' +import { waitForReason } from './await-signal' +import { getLocationChangeInfo, runRouteLifecycle } from './router' +import type { ParsedLocation } from './location' +import type { AnyRouteMatch } from './Matches' +import type { NotFoundError } from './not-found' +import type { + AnyRoute, + BeforeLoadContextOptions, + LoaderFnContext, + RouteContextOptions, + SsrContextOptions, +} from './route' +import type { AnyRedirect } from './redirect' +import type { AnyRouter, SSROption } from './router' + +declare const serverLanePhase: unique symbol + +type ServerLane = { + readonly [serverLanePhase]: TPhase + location: ParsedLocation + matches: Array +} + +type MatchedLane = ServerLane<'matched'> + +type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number] + +type ContextualizedLane = ServerLane<'contextualized'> & { + end: number + failure?: IndexedOutcome +} + +type ReducedLane = ServerLane<'reduced'> + +const SUCCESS = 0 +const ERROR = 1 +const NOT_FOUND = 2 +const REDIRECTED = 3 +const SKIPPED = 4 + +type LoaderOutcome = + | [typeof SUCCESS, data: unknown] + | [typeof ERROR, error: unknown] + | [typeof NOT_FOUND, error: NotFoundError] + | [typeof REDIRECTED, redirect: AnyRedirect] + | [typeof SKIPPED] + +type LoaderTask = { + index: number + outcome: Promise + match: Promise +} + +export type ServerLoadResult = + | { + type: 'render' + status: 200 | 404 | 500 + matches: Array + } + | { type: 'redirect'; redirect: AnyRedirect } + +function getRoute(router: AnyRouter, match: AnyRouteMatch): AnyRoute { + return router.routesById[match.routeId] +} + +function normalize(value: unknown, rejected: boolean): LoaderOutcome { + if (isRedirect(value)) { + return [REDIRECTED, value] + } + if (isNotFound(value)) { + return [NOT_FOUND, value] + } + if (rejected && typeof (value as any)?.then === 'function') { + value = new Error('A Promise was thrown', { cause: value }) + } + return rejected ? [ERROR, value] : [SUCCESS, value] +} + +function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome { + let outcome = normalize(cause, true) + if (outcome[0] !== ERROR) { + return outcome + } + try { + route.options.onError?.(outcome[1]) + } catch (onErrorCause) { + outcome = normalize(onErrorCause, true) + } + return outcome +} + +function maybe( + value: TValue, + cause: unknown, +): { status: 'success'; value: TValue } | { status: 'error'; error: unknown } { + if (cause !== undefined) { + return { status: 'error', error: cause } + } + return { status: 'success', value } +} + +function navigateFrom(router: AnyRouter, location: ParsedLocation) { + return (options: any) => + router.navigate({ + ...options, + _fromLocation: location, + }) +} + +function waitFor(value: Promise, signal?: AbortSignal): Promise { + return signal ? waitForReason(value, signal) : value +} + +async function resolveSsr( + router: AnyRouter, + lane: MatchedLane, + index: number, +): Promise { + const match = lane.matches[index]! + const route = getRoute(router, match) + const parentSsr = lane.matches[index - 1]?.ssr + + if (router.isShell()) { + return route.id === rootRouteId + } + if (parentSsr === false) { + return false + } + + const inherit = (value: SSROption): SSROption => { + return value === true && parentSsr === 'data-only' ? 'data-only' : value + } + const defaultSsr = router.options.defaultSsr ?? true + const inheritedDefault = inherit(defaultSsr) + // A functional override can fail. Establish the inherited policy first so + // the selected error boundary retains the route's actual renderability. + match.ssr = inheritedDefault + const option = route.options.ssr + if (option === undefined) { + return inheritedDefault + } + if (typeof option !== 'function') { + return inherit(option) + } + + const context: SsrContextOptions = { + search: maybe(match.search, match.searchError), + params: maybe(match.params, match.paramsError), + location: lane.location, + matches: lane.matches.map((candidate) => ({ + index: candidate.index, + pathname: candidate.pathname, + fullPath: candidate.fullPath, + staticData: candidate.staticData, + id: candidate.id, + routeId: candidate.routeId, + search: maybe(candidate.search, candidate.searchError), + params: maybe(candidate.params, candidate.paramsError), + ssr: candidate.ssr, + })), + } + return inherit((await option(context)) ?? defaultSsr) +} + +function stampNotFound( + match: AnyRouteMatch, + outcome: LoaderOutcome, +): LoaderOutcome { + if (outcome[0] === NOT_FOUND && !outcome[1].routeId) { + outcome[1].routeId = match.routeId + } + return outcome +} + +async function contextualize( + router: AnyRouter, + lane: MatchedLane, + signal?: AbortSignal, +): Promise { + const globalBoundary = lane.matches.findIndex((match) => match._notFound) + let end = globalBoundary < 0 ? lane.matches.length : globalBoundary + 1 + let failure: IndexedOutcome | undefined + let parentContext: Record = { + ...(router.options.context ?? {}), + } + + for (let index = 0; index < end; index++) { + const match = lane.matches[index]! + const route = getRoute(router, match) + try { + match.ssr = await resolveSsr(router, lane, index) + } catch (cause) { + signal?.throwIfAborted() + failure = [index, stampNotFound(match, normalizeError(route, cause))] + end = index + } + signal?.throwIfAborted() + if (failure?.[1][0] === REDIRECTED) { + break + } + + match.__beforeLoadContext = undefined + let context = parentContext + try { + let routeContext + if (route.options.context) { + const routeContextOptions: RouteContextOptions< + any, + any, + any, + any, + any + > = { + deps: match.loaderDeps, + params: match.params, + context: parentContext, + location: lane.location, + navigate: navigateFrom(router, lane.location), + buildLocation: router.buildLocation, + cause: match.cause, + abortController: match.abortController, + preload: false, + matches: lane.matches, + routeId: route.id, + } + routeContext = route.options.context(routeContextOptions) ?? undefined + } + context = { + ...parentContext, + ...routeContext, + } + match.context = context + } catch (cause) { + signal?.throwIfAborted() + if (!failure) { + failure = [index, stampNotFound(match, normalizeError(route, cause))] + } + end = index + break + } + signal?.throwIfAborted() + if (failure) { + break + } + const validationError = match.paramsError ?? match.searchError + if (validationError !== undefined) { + failure = [ + index, + stampNotFound(match, normalizeError(route, validationError)), + ] + end = index + break + } + signal?.throwIfAborted() + + if (match.ssr === false || !route.options.beforeLoad) { + parentContext = context + continue + } + + const abortController = match.abortController + const options: BeforeLoadContextOptions< + any, + any, + any, + any, + any, + any, + any, + any, + any + > = { + search: match.search, + abortController, + params: match.params, + preload: false, + context, + location: lane.location, + navigate: navigateFrom(router, lane.location), + buildLocation: router.buildLocation, + cause: match.cause, + matches: lane.matches, + routeId: route.id, + ...router.options.additionalContext, + } + + try { + const beforeLoadContext = await route.options.beforeLoad(options) + signal?.throwIfAborted() + const outcome = stampNotFound(match, normalize(beforeLoadContext, false)) + if (outcome[0] !== SUCCESS) { + failure = [index, outcome] + end = index + break + } + match.__beforeLoadContext = beforeLoadContext + match.context = { + ...context, + ...beforeLoadContext, + } + parentContext = match.context + } catch (cause) { + signal?.throwIfAborted() + failure = [index, stampNotFound(match, normalizeError(route, cause))] + end = index + break + } + } + + return { + location: lane.location, + matches: lane.matches, + end, + failure, + } as ContextualizedLane +} + +function getLoaderContext( + router: AnyRouter, + lane: ContextualizedLane, + match: AnyRouteMatch, + route: AnyRoute, + index: number, + tasks: Array, +): LoaderFnContext { + return { + params: match.params, + deps: match.loaderDeps, + preload: false, + parentMatchPromise: tasks[index - 1]?.match, + abortController: match.abortController, + context: match.context, + location: lane.location, + navigate: navigateFrom(router, lane.location), + cause: match.cause, + route, + ...router.options.additionalContext, + } +} + +function createLoaderTask( + router: AnyRouter, + lane: ContextualizedLane, + index: number, + tasks: Array, + signal?: AbortSignal, +): LoaderTask { + const match = lane.matches[index]! + const route = getRoute(router, match) + let outcome: Promise + + if (match.ssr === false) { + outcome = Promise.resolve([SKIPPED]) + } else { + const routeLoader = route.options.loader + const loader = + typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler + if (!loader) { + outcome = Promise.resolve([SUCCESS, undefined]) + } else { + outcome = Promise.resolve() + .then(() => + loader(getLoaderContext(router, lane, match, route, index, tasks)), + ) + .then( + (result) => normalize(result, false), + (cause) => normalize(cause, true), + ) + .then((result): LoaderOutcome => { + if ( + result[0] !== REDIRECTED && + (signal?.aborted || match.abortController.signal.reason === lane) + ) { + return [SKIPPED] + } + if (result[0] === ERROR) { + result = normalizeError(route, result[1]) + } + return stampNotFound(match, result) + }) + } + } + + const parentMatch = outcome.then((result) => { + const snapshot = { ...match } + if (result[0] === SUCCESS) { + snapshot.loaderData = result[1] + snapshot.status = 'success' + snapshot.error = undefined + snapshot.invalid = false + snapshot.isFetching = false + } else if (result[0] === ERROR) { + snapshot.status = 'error' + snapshot.error = result[1] + } else if (result[0] === NOT_FOUND) { + snapshot.status = 'notFound' + snapshot.error = result[1] + } + return snapshot + }) + + return { index, outcome, match: parentMatch } +} + +async function getNotFoundBoundary( + router: AnyRouter, + matches: Array, + indexed: IndexedOutcome | undefined, + signal?: AbortSignal, + fallback = 0, +): Promise { + const cause = indexed?.[1][1] as NotFoundError | undefined + let index = cause?.routeId + ? matches.findIndex((match) => match.routeId === cause.routeId) + : (indexed?.[0] ?? matches.length - 1) + if (index < 0) { + index = 0 + } + for (let candidate = index; candidate >= 0; candidate--) { + const route = getRoute(router, matches[candidate]!) + const loading = loadRouteChunk(route, false) + if (loading) { + try { + await loading + } catch { + signal?.throwIfAborted() + } + } + signal?.throwIfAborted() + if (route.options.notFoundComponent) { + return candidate + } + } + return cause?.routeId ? index : fallback +} + +function abortMatches( + matches: Array, + start = 0, + reason?: unknown, +): void { + for (let index = start; index < matches.length; index++) { + matches[index]!.abortController.abort(reason) + } +} + +function resolveServerRedirect( + router: AnyRouter, + location: ParsedLocation, + value: AnyRedirect, +): ServerLoadResult { + value.options._fromLocation = location + return { type: 'redirect', redirect: router.resolveRedirect(value) } +} + +async function applyFailure( + router: AnyRouter, + lane: ContextualizedLane, + indexed: IndexedOutcome | undefined, + signal?: AbortSignal, +): Promise<{ status: 200 | 404 | 500; boundary?: number; kind?: number }> { + if (!indexed) { + const boundary = lane.matches.findIndex((match) => match._notFound) + if (boundary >= 0) { + abortMatches(lane.matches, boundary + 1) + return { status: 404, boundary, kind: NOT_FOUND } + } + return { status: 200 } + } + + const [index, outcome] = indexed + if (outcome[0] === ERROR) { + const match = lane.matches[index]! + match._notFound = undefined + match.status = 'error' + match.error = outcome[1] + match.isFetching = false + abortMatches(lane.matches, index + 1) + return { status: 500, boundary: index, kind: ERROR } + } + + const boundary = + indexed[2] ?? + (await getNotFoundBoundary(router, lane.matches, indexed, signal)) + const match = lane.matches[boundary]! + const cause = outcome[1] as NotFoundError + cause.routeId = match.routeId + match._notFound = undefined + if (match.routeId === router.routeTree.id) { + match.status = 'success' + match._notFound = true + match.error = cause + } else { + match.status = 'notFound' + match.error = cause + } + match.isFetching = false + abortMatches(lane.matches, boundary + 1) + return { status: 404, boundary, kind: NOT_FOUND } +} + +async function loadNormalChunks( + router: AnyRouter, + lane: ContextualizedLane, + end: number, + signal?: AbortSignal, +): Promise { + const chunks: Array> = [] + for (let index = 0; index < lane.matches.length; index++) { + const match = lane.matches[index]! + if (index >= end || match.ssr !== true || match.status !== 'success') { + continue + } + const route = getRoute(router, match) + try { + const loading = loadRouteChunk(route) + if (loading) { + const chunk = loading.then( + () => { + signal?.throwIfAborted() + return undefined + }, + (cause) => { + signal?.throwIfAborted() + return [ + index, + stampNotFound(match, normalizeError(route, cause)), + ] as IndexedOutcome + }, + ) + // Route-order reduction can return before later chunks settle. + void chunk.catch(() => {}) + chunks.push(chunk) + } + } catch (cause) { + signal?.throwIfAborted() + chunks.push([index, stampNotFound(match, normalizeError(route, cause))]) + } + } + for (const chunk of chunks) { + const indexed = Array.isArray(chunk) ? chunk : await chunk + if (indexed) { + return indexed + } + } + return undefined +} + +async function projectLane( + router: AnyRouter, + lane: ReducedLane, + signal?: AbortSignal, +): Promise { + for (const match of lane.matches) { + const routeOptions = getRoute(router, match).options + if (routeOptions.head || routeOptions.scripts || routeOptions.headers) { + const context = { + ssr: router.options.ssr, + matches: lane.matches, + match, + params: match.params, + loaderData: match.loaderData, + } + try { + const [head, scripts, headers] = await Promise.all([ + routeOptions.head?.(context), + routeOptions.scripts?.(context), + routeOptions.headers?.(context), + ]) + signal?.throwIfAborted() + match.meta = head?.meta + match.links = head?.links + match.headScripts = head?.scripts + match.styles = head?.styles + match.scripts = scripts + match.headers = headers + } catch (cause) { + signal?.throwIfAborted() + console.error(cause) + } + } + if (match.ssr === false || match.status !== 'success' || match._notFound) { + break + } + } +} + +async function executeServerLane( + router: AnyRouter, + location: ParsedLocation, + matchedMatches: Array, + signal?: AbortSignal, +): Promise { + const matched = { + location, + matches: matchedMatches.map((match) => ({ + ...match, + __beforeLoadContext: undefined, + context: {}, + isFetching: false, + abortController: new AbortController(), + })), + } as MatchedLane + const abortLane = () => abortMatches(matched.matches, 0, signal?.reason) + if (signal?.aborted) { + abortLane() + signal.throwIfAborted() + } + signal?.addEventListener('abort', abortLane, { once: true }) + + try { + const plannedGlobalBoundary = matched.matches.findIndex( + (match) => match._notFound, + ) + if (router.options.notFoundMode !== 'root' && plannedGlobalBoundary >= 0) { + const boundary = await getNotFoundBoundary( + router, + matched.matches, + undefined, + signal, + plannedGlobalBoundary, + ) + if (boundary !== plannedGlobalBoundary) { + matched.matches[plannedGlobalBoundary]!._notFound = undefined + matched.matches[boundary]!._notFound = true + } + } + const lane = await contextualize(router, matched, signal) + signal?.throwIfAborted() + + let loaderEnd = lane.end + if (lane.failure?.[1][0] === REDIRECTED) { + loaderEnd = 0 + } else if (lane.failure?.[1][0] === NOT_FOUND) { + lane.failure[2] = await getNotFoundBoundary( + router, + lane.matches, + lane.failure, + signal, + ) + loaderEnd = Math.min(loaderEnd, lane.failure[2] + 1) + } + + const tasks: Array = [] + for (let index = 0; index < loaderEnd; index++) { + const task = createLoaderTask(router, lane, index, tasks, signal) + tasks.push(task) + } + + let loaderFailure: IndexedOutcome | undefined + let control = lane.failure?.[1][0] === REDIRECTED ? lane.failure : undefined + try { + await Promise.all( + tasks.map((task) => + task.outcome.then((loadedOutcome) => { + const match = lane.matches[task.index]! + const outcome = loadedOutcome + if (outcome[0] === SUCCESS) { + match.loaderData = outcome[1] + match.status = 'success' + match.error = undefined + match.invalid = false + match.isFetching = false + match.updatedAt = Date.now() + } else if (outcome[0] === REDIRECTED) { + control = [task.index, outcome] + throw control + } else { + // A selective-SSR skip must stay pending for hydration. Every + // settled server attempt is otherwise renderable unless + // reduction selects it as the lane's terminal failure. + if (match.ssr !== false) { + match.status = 'success' + match.error = undefined + match.invalid = true + match.isFetching = false + } + if (!loaderFailure && outcome[0] !== SKIPPED) { + loaderFailure = [task.index, outcome] + } + } + }), + ), + ) + } catch (cause) { + if (!Array.isArray(cause)) { + throw cause + } + control = cause as IndexedOutcome + } + signal?.throwIfAborted() + + if (control?.[1][0] === REDIRECTED) { + abortMatches(lane.matches, 0, lane) + return resolveServerRedirect(router, location, control[1][1]) + } + + let failure = lane.failure ?? loaderFailure + const plannedBoundary = lane.matches.findIndex((match) => match._notFound) + let readinessEnd: number + if (failure) { + const outcomeEnd = (failure[2] ??= + failure[1][0] === NOT_FOUND + ? await getNotFoundBoundary(router, lane.matches, failure, signal) + : failure[0]) + for (const task of tasks) { + if (task.index >= outcomeEnd) { + break + } + const outcome = await task.outcome + // Presence means a loader previously succeeded, even with `undefined`. + if ( + outcome[0] !== SUCCESS && + outcome[0] < REDIRECTED && + !('loaderData' in lane.matches[task.index]!) + ) { + failure = [task.index, outcome] + failure[2] = + outcome[0] === NOT_FOUND + ? await getNotFoundBoundary(router, lane.matches, failure, signal) + : task.index + break + } + } + readinessEnd = failure[2] + } else { + readinessEnd = plannedBoundary < 0 ? lane.matches.length : plannedBoundary + } + const requiredFailure = await loadNormalChunks( + router, + lane, + readinessEnd, + signal, + ) + signal?.throwIfAborted() + if (requiredFailure) { + if (requiredFailure[1][0] === REDIRECTED) { + abortMatches(lane.matches) + return resolveServerRedirect(router, location, requiredFailure[1][1]) + } + failure = requiredFailure + } + + const terminal = await applyFailure(router, lane, failure, signal) + if (terminal.boundary !== undefined) { + const match = lane.matches[terminal.boundary]! + if (match.ssr === true) { + const route = getRoute(router, match) + try { + if (terminal.kind === ERROR) { + await loadRouteChunk(route, 'errorComponent') + } else if (match._notFound) { + await Promise.all([ + loadRouteChunk(route), + loadRouteChunk(route, 'notFoundComponent'), + ]) + } else { + await loadRouteChunk(route, 'notFoundComponent') + } + } catch {} + signal?.throwIfAborted() + } + } + + signal?.throwIfAborted() + await projectLane( + router, + { + location: lane.location, + matches: lane.matches, + } as ReducedLane, + signal, + ) + signal?.throwIfAborted() + router.serverSsr?.onCleanup(abortLane) + return { type: 'render', status: terminal.status, matches: lane.matches } + } finally { + signal?.removeEventListener('abort', abortLane) + } +} + +type ServerLoadOptions = NonNullable[0]> & { + _signal?: AbortSignal +} + +export async function loadServerRoute( + router: AnyRouter, + opts?: ServerLoadOptions, +): Promise { + router.updateLatestLocation() + const next = router.latestLocation + const previous = router._committed + let result: ServerLoadResult + try { + const canonical = router.buildLocation({ + to: next.pathname, + search: true, + params: true, + hash: true, + state: true, + _includeValidateSearch: true, + }) + if (next.publicHref !== canonical.publicHref) { + const href = canonical.publicHref || '/' + throw canonical.external + ? redirect({ href }) + : redirect({ href, _builtLocation: canonical }) + } + + const fromLocation = router.stores.resolvedLocation.get() + const changeInfo = getLocationChangeInfo(next, fromLocation) + router.emit({ type: 'onBeforeNavigate', ...changeInfo }) + router.emit({ type: 'onBeforeLoad', ...changeInfo }) + opts?._signal?.throwIfAborted() + result = await waitFor( + executeServerLane(router, next, router.matchRoutes(next), opts?._signal), + opts?._signal, + ) + opts?._signal?.throwIfAborted() + } catch (cause) { + opts?._signal?.throwIfAborted() + if (!isRedirect(cause)) { + throw cause + } + result = resolveServerRedirect(router, next, cause) + } + + router._serverResult = result + router.batch(() => { + router.stores.location.set(next) + router.stores.status.set('idle') + if (result.type === 'render') { + router.stores.setMatches(result.matches) + router.stores.resolvedLocation.set(next) + } + }) + if (result.type === 'render') { + router._committed = result.matches + runRouteLifecycle(router, previous, result.matches) + } + router._commitPromise?.resolve() + router._commitPromise = undefined +} diff --git a/packages/router-core/src/redirect.ts b/packages/router-core/src/redirect.ts index b10fac6d7a..2ac387daeb 100644 --- a/packages/router-core/src/redirect.ts +++ b/packages/router-core/src/redirect.ts @@ -21,7 +21,6 @@ export type Redirect< */ _builtLocation?: ParsedLocation } - redirectHandled?: boolean } export type RedirectOptions< diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index 2de7dc57f0..eae2f15e30 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -700,10 +700,6 @@ export interface Route< THandlers > isRoot: TParentRoute extends AnyRoute ? true : false - /** @internal */ - _componentsPromise?: Promise - /** @internal */ - _componentsLoaded?: boolean lazyFn?: () => Promise< LazyRoute< Route< @@ -729,9 +725,7 @@ export interface Route< > > /** @internal */ - _lazyPromise?: Promise - /** @internal */ - _lazyLoaded?: boolean + _lazy?: Promise | true rank: number to: TrimPathRight init: (opts: { originalIndex: number }) => void @@ -1711,10 +1705,7 @@ export class BaseRoute< > > /** @internal */ - _lazyPromise?: Promise - /** @internal */ - _componentsPromise?: Promise - + _lazy?: Promise | true constructor( options?: RouteOptions< TRegister, diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 2197dab737..5b0cbfb6be 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -1,8 +1,7 @@ import { createBrowserHistory, parseHref } from '@tanstack/history' -import { isServer } from '@tanstack/router-core/isServer' +import { isServer, loadServerRoute } from '@tanstack/router-core/isServer' import { DEFAULT_PROTOCOL_ALLOWLIST, - createControlledPromise, decodePath, deepEqual, encodePathLikeUrl, @@ -35,8 +34,14 @@ import { isNotFound } from './not-found' import { setupScrollRestoration } from './scroll-restoration' import { defaultParseSearch, defaultStringifySearch } from './searchParams' import { rootRouteId } from './root' -import { isRedirect, redirect } from './redirect' -import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches' +import { isRedirect } from './redirect' +import { + loadClientRoute, + loadRouteChunk, + preloadClientRoute, + refreshClientRoute, + replaceRouteChunk, +} from './load-client' import { composeRewrites, executeRewriteInput, @@ -51,6 +56,12 @@ import type { } from './new-process-route-tree' import type { SearchParser, SearchSerializer } from './searchParams' import type { AnyRedirect, ResolvedRedirect } from './redirect' +import type { + LoadTransaction, + LoaderFlight, + PendingSession, +} from './load-client' +import type { ServerLoadResult } from './load-server' import type { HistoryAction, HistoryLocation, @@ -62,7 +73,6 @@ import type { import type { Awaitable, Constrain, - ControlledPromise, NoInfer, NonNullableUpdater, PickAsRequired, @@ -75,8 +85,6 @@ import type { AnyRouteWithContext, LoaderStaleReloadMode, MakeRemountDepsOptionsUnion, - RouteContextOptions, - RouteLike, RouteMask, SearchMiddleware, SearchMiddlewareMeta, @@ -106,7 +114,6 @@ import type { } from './manifest' import type { AnySchema, AnyValidator } from './validators' import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link' -import type { NotFoundError } from './not-found' import type { AnySerializationAdapter, ValidateSerializableInput, @@ -263,9 +270,9 @@ export interface RouterOptions< */ defaultPreloadStaleTime?: number /** - * The default `defaultPreloadGcTime` a route should use if no preloadGcTime is provided. + * The default `preloadGcTime` a route should use if none is provided. * - * @default 1_800_000 `(30 minutes)` + * @default 300_000 `(5 minutes)` * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property) * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading) */ @@ -297,7 +304,7 @@ export interface RouterOptions< /** * The default `gcTime` a route should use if no gcTime is provided. * - * @default 1_800_000 `(30 minutes)` + * @default 300_000 `(5 minutes)` * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property) * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options) */ @@ -544,14 +551,10 @@ export interface RouterState< in out TRouteMatch = MakeRouteMatchUnion, > { status: 'pending' | 'idle' - loadedAt: number isLoading: boolean - isTransitioning: boolean matches: Array location: ParsedLocation> resolvedLocation?: ParsedLocation> - statusCode: number - redirect?: AnyRedirect } export interface BuildNextOptions { @@ -619,9 +622,21 @@ export type SubscribeFn = ( ) => () => void export interface MatchRoutesOpts { - preload?: boolean throwOnError?: boolean - dest?: BuildNextOptions + /** @internal */ + _controller?: AbortController + /** @internal */ + _rematerialize?: boolean +} + +function routeNeedsLoad(route: AnyRoute): unknown { + return ( + route.options.loader || + route.options.beforeLoad || + route.lazyFn || + (route.options.component as any)?.preload || + (route.options.pendingComponent as any)?.preload + ) } export type InferRouterContext = @@ -748,6 +763,7 @@ export type EmitFn = (routerEvent: RouterEvent) => void export type LoadFn = (opts?: { sync?: boolean action?: { type: HistoryAction } + _signal?: AbortSignal }) => Promise export type CommitLocationFn = ({ @@ -756,7 +772,10 @@ export type CommitLocationFn = ({ ...next }: ParsedLocation & CommitLocationOptions) => Promise -export type StartTransitionFn = (fn: () => void) => void +export type StartTransitionFn = ( + fn: () => void, + expected: Array, +) => Promise export interface MatchRoutesFn { ( @@ -775,13 +794,6 @@ export interface MatchRoutesFn { ): Array } -export type GetMatchFn = (matchId: string) => AnyRouteMatch | undefined - -export type UpdateMatchFn = ( - id: string, - updater: (match: AnyRouteMatch) => AnyRouteMatch, -) => void - export type LoadRouteChunkFn = (route: AnyRoute) => Promise> export type ResolveRedirect = (err: AnyRedirect) => ResolvedRedirect @@ -896,18 +908,61 @@ export function getLocationChangeInfo( location: ParsedLocation, resolvedLocation?: ParsedLocation, ) { - const fromLocation = resolvedLocation - const toLocation = location - const pathChanged = fromLocation?.pathname !== toLocation.pathname - const hrefChanged = fromLocation?.href !== toLocation.href - const hashChanged = fromLocation?.hash !== toLocation.hash - return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged } + return { + fromLocation: resolvedLocation, + toLocation: location, + pathChanged: resolvedLocation?.pathname !== location.pathname, + hrefChanged: resolvedLocation?.href !== location.href, + hashChanged: resolvedLocation?.hash !== location.hash, + } } -export const locationHistoryActions = new WeakMap< - ParsedLocation, - HistoryAction ->() +/** + * Return only state owned by the application, excluding volatile history + * bookkeeping. Mask payloads (`__tempLocation`/`__tempKey`) are kept: they + * distinguish otherwise-identical locations. + */ +export function _getUserHistoryState({ + key: _key, + __TSR_key: _tsrKey, + __TSR_index: _tsrIndex, + __hashScrollIntoViewOptions: _hashScroll, + ...state +}: ParsedHistoryState): HistoryState { + return state +} + +/** Run route lifecycle callbacks in leave/enter/stay phases. */ +export function runRouteLifecycle( + router: AnyRouter, + previous: Array, + matches: Array, + isCurrent?: () => boolean, +): void { + for (const match of previous) { + if (isCurrent?.() === false) { + return + } + if (!matches.some((candidate) => candidate.routeId === match.routeId)) { + ;(router.routesById as Record)[ + match.routeId + ]!.options.onLeave?.(match) + } + } + for (const match of matches) { + if (isCurrent?.() === false) { + return + } + const route = (router.routesById as Record)[ + match.routeId + ]! + route.options[ + previous.some((candidate) => candidate.routeId === match.routeId) + ? 'onStay' + : 'onEnter' + ]?.(match) + } +} type LightweightRouteMatchResult = { matchedRoutes: ReadonlyArray @@ -956,6 +1011,46 @@ declare global { | undefined } +export interface RouterCore< + in out TRouteTree extends AnyRoute, + in out TTrailingSlashOption extends TrailingSlashOption, + in out TDefaultStructuralSharingOption extends boolean, + in out TRouterHistory extends RouterHistory = RouterHistory, + in out TDehydrated extends Record = Record, +> { + shouldViewTransition?: boolean | ViewTransitionOptions + /** Current client load transaction and owner of navigation writes. */ + _tx?: LoadTransaction + /** Joinable in-flight loader generations keyed by match ID. */ + _flights?: Map + /** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */ + _preloads?: Map> + /** Owns cancellable work before a client transaction publishes. */ + _preflight?: AbortController + /** Transfers one reconstructed SSR prefix into its initial client load. */ + _handoff?: HydrationHandoff + /** Pending-boundary reveal and minimum-visible timing state. */ + _pending?: PendingSession + /** Result of the latest server load, used to render or redirect. */ + _serverResult?: ServerLoadResult + /** Framework publication waiting for an exact render acknowledgement. */ + _rendered?: [ + offered?: Array, + settle?: (rendered: boolean) => void, + ] + /** Development-only HMR reload for a route and its descendants. */ + _refreshRoute: (() => Promise) | undefined + /** Development-only replacement for a route's lazy chunk owner. */ + _replaceRouteChunk: + | ((route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void) + | undefined +} + +export type HydrationHandoff = [ + claim: () => AbortController | undefined, + finish: (matches?: Array) => number | undefined, +] + /** * Core, framework-agnostic router engine that powers TanStack Router. * @@ -978,14 +1073,17 @@ export class RouterCore< )}` _scroll: { next: boolean + // True until the current PUSH/REPLACE renders, so its hash owns window scroll. + hash?: boolean restoring?: boolean restoration?: boolean reset?: boolean } = { next: true } - shouldViewTransition?: boolean | ViewTransitionOptions = undefined - isViewTransitionTypesSupported?: boolean = undefined subscribers = new Set>() - viewTransitionPromise?: ControlledPromise + /** Accepted off-screen loader generations keyed by match ID. */ + _cache = new Map() + /** Accepted semantic lane, excluding temporary pending presentation. */ + _committed: Array = [] // Must build in constructor stores!: RouterStores @@ -1006,7 +1104,7 @@ export class RouterCore< rewrite?: LocationRewrite origin?: string latestLocation!: ParsedLocation> - pendingBuiltLocation?: ParsedLocation> + _pendingLocation?: ParsedLocation> basepath!: string routeTree!: TRouteTree routesById!: RoutesById @@ -1051,24 +1149,19 @@ export class RouterCore< options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST, }) - if (typeof document !== 'undefined') { + if (!(isServer ?? typeof document === 'undefined')) { self.__TSR_ROUTER__ = this } } - // This is a default implementation that can optionally be overridden - // by the router provider once rendered. We provide this so that the - // router can be used in a non-react environment if necessary - startTransition: StartTransitionFn = (fn) => fn() - + startTransition: StartTransitionFn = async (fn) => { + fn() + return false + } isShell() { return !!this.options.isShell } - isPrerendering() { - return !!this.options.isPrerendering - } - update: UpdateFn< TRouteTree, TTrailingSlashOption, @@ -1094,7 +1187,8 @@ export class RouterCore< ...newOptions, } - this.isServer = this.options.isServer ?? typeof document === 'undefined' + this.isServer = + this.options.isServer ?? isServer ?? typeof document === 'undefined' this.protocolAllowlist = new Set(this.options.protocolAllowlist) @@ -1168,17 +1262,13 @@ export class RouterCore< if (!this.stores && this.latestLocation) { const config = this.getStoreConfig(this) this.batch = config.batch - this.stores = createRouterStores( - getInitialRouterState(this.latestLocation), - config, - ) + this.stores = createRouterStores(this.latestLocation, config) if (!(isServer ?? this.isServer)) { setupScrollRestoration(this) } } - let needsLocationUpdate = false const nextBasepath = this.options.basepath ?? '/' const nextRewriteOption = this.options.rewrite const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath @@ -1211,21 +1301,9 @@ export class RouterCore< this.updateLatestLocation() } - needsLocationUpdate = true - } - - if (needsLocationUpdate && this.stores) { - this.stores.location.set(this.latestLocation) - } - - if ( - typeof window !== 'undefined' && - 'CSS' in window && - typeof window.CSS?.supports === 'function' - ) { - this.isViewTransitionTypesSupported = window.CSS.supports( - 'selector(:active-view-transition-type(a))', - ) + if (this.stores) { + this.stores.location.set(this.latestLocation) + } } } @@ -1296,11 +1374,15 @@ export class RouterCore< } emit: EmitFn = (routerEvent) => { - this.subscribers.forEach((listener) => { + for (const listener of this.subscribers) { if (listener.eventType === routerEvent.type) { - listener.fn(routerEvent) + try { + listener.fn(routerEvent) + } catch (e) { + console.error(e) + } } - }) + } } /** @@ -1408,10 +1490,6 @@ export class RouterCore< return branch } - get looseRoutesById() { - return this.routesById as Record - } - matchRoutes: MatchRoutesFn = ( pathnameOrNext: string | ParsedLocation, locationSearchOrOpts?: AnySchema | MatchRoutesOpts, @@ -1430,16 +1508,6 @@ export class RouterCore< return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts) } - private getParentContext(parentMatch?: AnyRouteMatch) { - const parentMatchId = parentMatch?.id - - const parentContext = !parentMatchId - ? ((this.options.context as any) ?? undefined) - : (parentMatch.context ?? this.options.context ?? undefined) - - return parentContext - } - private matchRoutesInternal( next: ParsedLocation, opts?: MatchRoutesOpts, @@ -1466,18 +1534,19 @@ export class RouterCore< } } - const globalNotFoundRouteId = isGlobalNotFound + const _notFoundRouteId = isGlobalNotFound ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes) : undefined const matches = new Array(matchedRoutes.length) - // Snapshot of active match state keyed by routeId, used to stabilise - // params/search across navigations. - const previousActiveMatchesByRouteId = new Map() - for (const store of this.stores.matchStores.values()) { - if (store.routeId) { - previousActiveMatchesByRouteId.set(store.routeId, store.get()) - } + const committed = this._committed + const previousAt = (route: AnyRoute, index: number) => { + const match = committed[index] + return match?.routeId === route.id + ? match + : route === this.options.notFoundRoute + ? committed.find((candidate) => candidate.routeId === route.id) + : undefined } for (let index = 0; index < matchedRoutes.length; index++) { @@ -1509,7 +1578,6 @@ export class RouterCore< ...strictSearch, } strictMatchSearch = { ...parentStrictSearch, ...strictSearch } - searchError = undefined } catch (err: any) { let searchParamError = err if (!(err instanceof SearchParamError)) { @@ -1527,19 +1595,25 @@ export class RouterCore< searchError = searchParamError } } - // This is where we need to call route.options.loaderDeps() to get any additional // deps that the route's loader function might need to run. We need to do this // before we create the match so that we can pass the deps to the route's // potential key function which is used to uniquely identify the route match in state - const loaderDeps = - route.options.loaderDeps?.({ - search: preMatchSearch, - }) ?? '' - - const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : '' - + let loaderDeps: any = '' + let loaderDepsHash = '' + try { + loaderDeps = + route.options.loaderDeps?.({ + search: preMatchSearch, + }) ?? '' + loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) || '' : '' + } catch (cause) { + if (opts?.throwOnError) { + throw cause + } + searchError ??= cause + } const { interpolatedPath, usedParams } = interpolatePath({ path: route.fullPath, params: routeParams, @@ -1547,12 +1621,9 @@ export class RouterCore< server: this.isServer, }) - // Waste not, want not. If we already have a match for this route, - // reuse it. This is important for layout routes, which might stick - // around between navigation actions that only change leaf routes. - - // Existing matches are matches that are already loaded along with - // pending matches that are still loading + // Seed planning from the accepted same-ID cache generation first, then + // from the committed generation for this route. Presentation stores are + // deliberately not a semantic reuse authority. const matchId = // route.id for disambiguation route.id + @@ -1561,13 +1632,16 @@ export class RouterCore< // explicit deps loaderDepsHash - const existingMatch = this.getMatch(matchId) - - const previousMatch = previousActiveMatchesByRouteId.get(route.id) + const previousMatch = previousAt(route, index) + const existingMatch = + process.env.NODE_ENV !== 'production' && opts?._rematerialize + ? undefined + : (this._cache.get(matchId) ?? + (previousMatch?.id === matchId ? previousMatch : undefined)) const strictParams = existingMatch?._strictParams ?? usedParams - let paramsError: unknown = undefined + let paramsError: unknown if (!existingMatch) { try { @@ -1603,15 +1677,10 @@ export class RouterCore< ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : nullReplaceEqualDeep(existingMatch.search, preMatchSearch), _strictSearch: strictMatchSearch, + searchError, } } else { - const status = - route.options.loader || - route.options.beforeLoad || - route.lazyFn || - routeNeedsPreload(route) - ? 'pending' - : 'success' + const status = routeNeedsLoad(route) ? 'pending' : 'success' match = { id: matchId, @@ -1626,96 +1695,42 @@ export class RouterCore< ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : preMatchSearch, _strictSearch: strictMatchSearch, - searchError: undefined, + searchError, status, isFetching: false, error: undefined, paramsError, - __routeContext: undefined, - _nonReactive: { - loadPromise: createControlledPromise(), - }, - __beforeLoadContext: undefined, context: {}, - abortController: new AbortController(), - fetchCount: 0, + abortController: opts?._controller ?? new AbortController(), cause, loaderDeps: previousMatch ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps) : loaderDeps, invalid: false, preload: false, - links: undefined, - scripts: undefined, - headScripts: undefined, - meta: undefined, staticData: route.options.staticData || {}, fullPath: route.fullPath, } } - if (!opts?.preload) { - // If we have a global not found, mark the right match as global not found - match.globalNotFound = globalNotFoundRouteId === route.id - } - - // update the searchError if there is one - match.searchError = searchError - - const parentContext = this.getParentContext(parentMatch) - - match.context = { - ...parentContext, - ...match.__routeContext, - ...match.__beforeLoadContext, + // If we have a global not found, mark the right match as global not found + const _notFound = _notFoundRouteId === route.id + if (match._notFound && !_notFound) { + match.error = undefined } + match._notFound = _notFound matches[index] = match } for (let index = 0; index < matches.length; index++) { const match = matches[index]! - const route = this.looseRoutesById[match.routeId]! - const existingMatch = this.getMatch(match.id) - - // Update the match's params - const previousMatch = previousActiveMatchesByRouteId.get(match.routeId) - match.params = previousMatch - ? nullReplaceEqualDeep(previousMatch.params, routeParams) - : routeParams - - if (!existingMatch) { - const parentMatch = matches[index - 1] - const parentContext = this.getParentContext(parentMatch) - - // Update the match's context - - if (route.options.context) { - const contextFnContext: RouteContextOptions = - { - deps: match.loaderDeps, - params: match.params, - context: parentContext ?? {}, - location: next, - navigate: (opts: any) => - this.navigate({ ...opts, _fromLocation: next }), - buildLocation: this.buildLocation, - cause: match.cause, - abortController: match.abortController, - preload: !!match.preload, - matches, - routeId: route.id, - } - // Get the route context - match.__routeContext = - route.options.context(contextFnContext) ?? undefined - } - - match.context = { - ...parentContext, - ...match.__routeContext, - ...match.__beforeLoadContext, - } + match.params = + match.cause === 'stay' + ? nullReplaceEqualDeep(match.params, routeParams) + : routeParams + if (opts?._controller) { + match.context = {} } } @@ -1723,25 +1738,38 @@ export class RouterCore< } getMatchedRoutes: GetMatchRoutesFn = (pathname) => { - return getMatchedRoutes({ - pathname, - routesById: this.routesById, - processedTree: this.processedTree, - }) + const routeParams: Record = Object.create(null) + const match = findRouteMatch( + trimPathRight(pathname), + this.processedTree, + true, + ) + if (match) { + Object.assign(routeParams, match.rawParams) + } + return { + matchedRoutes: match?.branch || [this.routesById[rootRouteId]!], + routeParams, + foundRoute: match?.route, + } } /** * Lightweight route matching for buildLocation. * Only computes fullPath, accumulated search, and params - skipping expensive - * operations like AbortController, ControlledPromise, loaderDeps, and full match objects. + * operations like AbortController, loaderDeps, and full match objects. */ private matchRoutesLightweight( location: ParsedLocation, ): LightweightRouteMatchResult { - const lastStateMatchId = last(this.stores.matchesId.get()) + const lastRouteId = last(this.stores.ids.get()) + const lastStateMatch = lastRouteId + ? this.stores.byRoute.get(lastRouteId)!.get() + : undefined + const lastStateMatchId = lastStateMatch?.id const cached = this.lightweightCache.get(location) - if (cached && cached[0] === lastStateMatchId) { - return cached[1] + if (cached && cached[0 /* lastMatchId */] === lastStateMatchId) { + return cached[1 /* result */] } const { matchedRoutes, routeParams } = this.getMatchedRoutes( @@ -1772,8 +1800,6 @@ export class RouterCore< } // Determine params: reuse from state if possible, otherwise parse - const lastStateMatch = - lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get() const canReuseParams = lastStateMatch && lastStateMatch.routeId === lastRoute.id && @@ -1808,37 +1834,6 @@ export class RouterCore< return result } - cancelMatch = (id: string) => { - const match = this.getMatch(id) - - if (!match) return - - match.abortController.abort() - clearTimeout(match._nonReactive.pendingTimeout) - match._nonReactive.pendingTimeout = undefined - } - - cancelMatches = () => { - this.stores.pendingIds.get().forEach((matchId) => { - this.cancelMatch(matchId) - }) - - this.stores.matchesId.get().forEach((matchId) => { - if (this.stores.pendingMatchStores.has(matchId)) { - return - } - - const match = this.stores.matchStores.get(matchId)?.get() - if (!match) { - return - } - - if (match.status === 'pending' || match.isFetching === 'loader') { - this.cancelMatch(matchId) - } - }) - } - /** * Build the next ParsedLocation from navigation options without committing. * Resolves `to`/`from`, params/search/hash/state, applies search validation @@ -1854,7 +1849,7 @@ export class RouterCore< ): ParsedLocation => { // We allow the caller to override the current location const currentLocation = - dest._fromLocation || this.pendingBuiltLocation || this.latestLocation + dest._fromLocation || this._pendingLocation || this.latestLocation // Use lightweight matching - only computes what buildLocation needs // (fullPath, search, params) without creating full match objects @@ -1909,15 +1904,7 @@ export class RouterCore< : sourcePath // Resolve the next params - const nextParams = - dest.params === false || dest.params === null - ? Object.create(null) - : (dest.params ?? true) === true - ? fromParams - : Object.assign( - fromParams, - functionalUpdate(dest.params as any, fromParams), - ) + const nextParams = resolveNextParams(dest.params, fromParams) const destRoute = this.routesByPath[ trimPathRight(nextTo) as keyof typeof this.routesByPath @@ -2015,12 +2002,12 @@ export class RouterCore< nextSearch = validatedSearch } - nextSearch = applySearchMiddleware({ - search: nextSearch, + nextSearch = applySearchMiddleware( + nextSearch, dest, destRoutes, - _includeValidateSearch: opts._includeValidateSearch, - }) + opts._includeValidateSearch, + ) // Replace the equal deep nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch) @@ -2120,12 +2107,7 @@ export class RouterCore< // If mask has a params function, call it with the matched params as context // Otherwise, use the matched params or the provided params value - const nextParams = - maskParams === false || maskParams === null - ? Object.create(null) - : (maskParams ?? true) === true - ? params - : Object.assign(params, functionalUpdate(maskParams, params)) + const nextParams = resolveNextParams(maskParams, params) maskedDest = { from: opts.from, @@ -2154,7 +2136,7 @@ export class RouterCore< return buildWithMatches(opts) } - commitLocationPromise: undefined | ControlledPromise + _commitPromise: (Promise & { resolve: () => void }) | undefined /** * Commit a previously built location to history (push/replace), optionally @@ -2166,37 +2148,26 @@ export class RouterCore< ...next }) => { let historyAction: HistoryAction | undefined - const isSameState = () => { - // the following props are ignored but may still be provided when navigating, - // temporarily add the previous values to the next state so they don't affect - // the comparison - const ignoredProps = [ - 'key', // TODO: Remove in v2 - use __TSR_key instead - '__TSR_key', - '__TSR_index', - '__hashScrollIntoViewOptions', - ] as const - ignoredProps.forEach((prop) => { - ;(next.state as any)[prop] = this.latestLocation.state[prop] - }) - const isEqual = deepEqual(next.state, this.latestLocation.state) - ignoredProps.forEach((prop) => { - delete next.state[prop] - }) - return isEqual - } - - const isSameUrl = - trimPathRight(this.latestLocation.href) === trimPathRight(next.href) + const isSameLocation = + trimPathRight(this.latestLocation.href) === trimPathRight(next.href) && + deepEqual( + _getUserHistoryState(next.state), + _getUserHistoryState(this.latestLocation.state), + ) - let previousCommitPromise = this.commitLocationPromise - this.commitLocationPromise = createControlledPromise(() => { + const previousCommitPromise = this._commitPromise + let resolve!: () => void + const commitPromise = new Promise((done) => { + resolve = done + }) as Promise & { resolve: () => void } + commitPromise.resolve = () => { + resolve() previousCommitPromise?.resolve() - previousCommitPromise = undefined - }) + } + this._commitPromise = commitPromise // Don't commit to history if nothing changed - if (isSameUrl && isSameState()) { + if (isSameLocation) { this.load() } else { let { @@ -2248,21 +2219,17 @@ export class RouterCore< nextHistory.state, { ignoreBlocker }, ) + + // Without a subscribed adapter the history commit cannot trigger the + // load itself. The same-location branch already loads directly. + if (!this.history.subscribers.size) { + this.load({ action: { type: historyAction } }) + } } this._scroll.next = next.resetScroll ?? true - if (!this.history.subscribers.size) { - this.load( - historyAction - ? { - action: { type: historyAction }, - } - : undefined, - ) - } - - return this.commitLocationPromise + return this._commitPromise } /** Convenience helper: build a location from options, then commit it. */ @@ -2272,9 +2239,11 @@ export class RouterCore< hashScrollIntoView, viewTransition, ignoreBlocker, + _redirects, href, ...rest - }: BuildNextOptions & CommitLocationOptions = {}) => { + }: BuildNextOptions & + CommitLocationOptions & { _redirects?: number } = {}) => { if (href) { const currentIndex = this.history.location.state.__TSR_index @@ -2298,8 +2267,12 @@ export class RouterCore< ...(rest as any), _includeValidateSearch: true, }) + if (_redirects) { + ;(location as typeof location & { _redirects?: number })._redirects = + _redirects + } - this.pendingBuiltLocation = location as ParsedLocation< + this._pendingLocation = location as ParsedLocation< FullSearchSchema > @@ -2315,8 +2288,8 @@ export class RouterCore< // Clear pending location after commit starts // We do this on next microtask to allow synchronous navigate calls to chain queueMicrotask(() => { - if (this.pendingBuiltLocation === location) { - this.pendingBuiltLocation = undefined + if (this._pendingLocation === location) { + this._pendingLocation = undefined } }) @@ -2412,254 +2385,17 @@ export class RouterCore< }) } - latestLoadPromise: undefined | Promise - - beforeLoad = () => { - // Cancel any pending matches - this.cancelMatches() - this.updateLatestLocation() - - if (isServer ?? this.isServer) { - // for SPAs on the initial load, this is handled by the Transitioner - const nextLocation = this.buildLocation({ - to: this.latestLocation.pathname, - search: true, - params: true, - hash: true, - state: true, - _includeValidateSearch: true, - }) - - // Check if location changed - origin check is unnecessary since buildLocation - // always uses this.origin when constructing URLs - if (this.latestLocation.publicHref !== nextLocation.publicHref) { - const href = this.getParsedLocationHref(nextLocation) - if (nextLocation.external) { - throw redirect({ href }) - } else { - throw redirect({ href, _builtLocation: nextLocation }) - } - } - } - - // Match the routes - const pendingMatches = this.matchRoutes(this.latestLocation) - - const nextCachedMatches = this.stores.cachedMatches - .get() - .filter((d) => !pendingMatches.some((e) => e.id === d.id)) - - // Ingest the new matches - this.batch(() => { - this.stores.status.set('pending') - this.stores.statusCode.set(200) - this.stores.isLoading.set(true) - this.stores.location.set(this.latestLocation) - this.stores.setPending(pendingMatches) - // If a cached match moved to pending matches, remove it from cached matches - this.stores.setCached(nextCachedMatches) - }) - } - load: LoadFn = async (opts): Promise => { - const historyAction = opts?.action?.type - let redirect: AnyRedirect | undefined - let notFound: NotFoundError | undefined - let loadPromise: Promise - const previousLocation = - this.stores.resolvedLocation.get() ?? this.stores.location.get() - - // eslint-disable-next-line prefer-const - loadPromise = new Promise((resolve) => { - this.startTransition(async () => { - try { - this.beforeLoad() - if (historyAction) { - locationHistoryActions.set(this.latestLocation, historyAction) - } else { - locationHistoryActions.delete(this.latestLocation) - } - const next = this.latestLocation - const prevLocation = this.stores.resolvedLocation.get() - const locationChangeInfo = getLocationChangeInfo(next, prevLocation) - - if (!this.stores.redirect.get()) { - this.emit({ - type: 'onBeforeNavigate', - ...locationChangeInfo, - }) - } - - this.emit({ - type: 'onBeforeLoad', - ...locationChangeInfo, - }) - - await loadMatches({ - router: this, - sync: opts?.sync, - forceStaleReload: previousLocation.href === next.href, - matches: this.stores.pendingMatches.get(), - location: next, - updateMatch: this.updateMatch, - // eslint-disable-next-line @typescript-eslint/require-await - onReady: async () => { - // Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition) - this.startTransition(() => { - this.startViewTransition(async () => { - // this.viewTransitionPromise = createControlledPromise() - - // Commit the pending matches. If a previous match was - // removed, place it in the cachedMatches - // - // exitingMatches uses match.id (routeId + params + loaderDeps) so - // navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry. - let exitingMatches: Array | null = null - - // Lifecycle-hook identity uses routeId only so that navigating between - // different params/deps of the same route fires onStay (not onLeave+onEnter). - let hookExitingMatches: Array | null = null - let hookEnteringMatches: Array | null = null - let hookStayingMatches: Array | null = null - - this.batch(() => { - const pendingMatches = this.stores.pendingMatches.get() - const mountPending = pendingMatches.length - const currentMatches = this.stores.matches.get() - - exitingMatches = mountPending - ? currentMatches.filter( - (match) => - !this.stores.pendingMatchStores.has(match.id), - ) - : null - - // Lifecycle-hook identity: routeId only (route presence in tree) - // Build routeId sets from pools to avoid derived stores. - const pendingRouteIds = new Set() - for (const s of this.stores.pendingMatchStores.values()) { - if (s.routeId) pendingRouteIds.add(s.routeId) - } - const activeRouteIds = new Set() - for (const s of this.stores.matchStores.values()) { - if (s.routeId) activeRouteIds.add(s.routeId) - } - - hookExitingMatches = mountPending - ? currentMatches.filter( - (match) => !pendingRouteIds.has(match.routeId), - ) - : null - hookEnteringMatches = mountPending - ? pendingMatches.filter( - (match) => !activeRouteIds.has(match.routeId), - ) - : null - hookStayingMatches = mountPending - ? pendingMatches.filter((match) => - activeRouteIds.has(match.routeId), - ) - : currentMatches - - this.stores.isLoading.set(false) - this.stores.loadedAt.set(Date.now()) - /** - * When committing new matches, cache any exiting matches that are still usable. - * Routes that resolved with `status: 'error'` or `status: 'notFound'` are - * deliberately excluded from `cachedMatches` so that subsequent invalidations - * or reloads re-run their loaders instead of reusing the failed/not-found data. - */ - if (mountPending) { - this.stores.setMatches(pendingMatches) - this.stores.setPending([]) - this.stores.setCached([ - ...this.stores.cachedMatches.get(), - ...exitingMatches!.filter( - (d) => - d.status !== 'error' && - d.status !== 'notFound' && - d.status !== 'redirected', - ), - ]) - this.clearExpiredCache() - } - }) - - // - for (const [matches, hook] of [ - [hookExitingMatches, 'onLeave'], - [hookEnteringMatches, 'onEnter'], - [hookStayingMatches, 'onStay'], - ] as const) { - if (!matches) continue - for (const match of matches as Array) { - this.looseRoutesById[match.routeId]!.options[hook]?.( - match, - ) - } - } - }) - }) - }, - }) - } catch (err) { - if (isRedirect(err)) { - redirect = err - if (!(isServer ?? this.isServer)) { - this.navigate({ - ...redirect.options, - replace: true, - ignoreBlocker: true, - }) - } - } else if (isNotFound(err)) { - notFound = err - } - - const nextStatusCode = redirect - ? redirect.status - : notFound - ? 404 - : this.stores.matches.get().some((d) => d.status === 'error') - ? 500 - : 200 - - this.batch(() => { - this.stores.statusCode.set(nextStatusCode) - this.stores.redirect.set(redirect) - }) - } - - if (this.latestLoadPromise === loadPromise) { - this.commitLocationPromise?.resolve() - this.latestLoadPromise = undefined - this.commitLocationPromise = undefined - } - - resolve() - }) - }) - - this.latestLoadPromise = loadPromise - - await loadPromise - - while ( - (this.latestLoadPromise as any) && - loadPromise !== this.latestLoadPromise - ) { - await this.latestLoadPromise + if (isServer ?? this.isServer) { + return loadServerRoute(this, opts) } - let newStatusCode: number | undefined = undefined - if (this.hasNotFoundMatch()) { - newStatusCode = 404 - } else if (this.stores.matches.get().some((d) => d.status === 'error')) { - newStatusCode = 500 - } - if (newStatusCode !== undefined) { - this.stores.statusCode.set(newStatusCode) + this.updateLatestLocation() + if (opts?.action) { + this._scroll.hash = + opts.action.type === 'PUSH' || opts.action.type === 'REPLACE' } + await loadClientRoute(this, opts) } startViewTransition = (fn: () => Promise) => { @@ -2674,9 +2410,8 @@ export class RouterCore< // Attempt to start a view transition (or just apply the changes if we can't) if ( shouldViewTransition && - typeof document !== 'undefined' && - 'startViewTransition' in document && - typeof document.startViewTransition === 'function' + !(isServer ?? typeof document === 'undefined') && + typeof (document as any).startViewTransition === 'function' ) { // lib.dom.ts doesn't support viewTransition types variant yet. // TODO: Fix this when dom types are updated @@ -2684,7 +2419,7 @@ export class RouterCore< if ( typeof shouldViewTransition === 'object' && - this.isViewTransitionTypesSupported + window.CSS?.supports?.('selector(:active-view-transition-type(a))') ) { const next = this.latestLocation const prevLocation = this.stores.resolvedLocation.get() @@ -2697,8 +2432,7 @@ export class RouterCore< : shouldViewTransition.types if (resolvedViewTransitionTypes === false) { - fn() - return + return fn() } startViewTransitionParams = { @@ -2709,58 +2443,21 @@ export class RouterCore< startViewTransitionParams = fn } - document.startViewTransition(startViewTransitionParams) - } else { - fn() + return (document as any).startViewTransition(startViewTransitionParams) + .updateCallbackDone } - } - - updateMatch: UpdateMatchFn = (id, updater) => { - this.startTransition(() => { - const pendingMatch = this.stores.pendingMatchStores.get(id) - if (pendingMatch) { - pendingMatch.set(updater) - return - } - - const activeMatch = this.stores.matchStores.get(id) - if (activeMatch) { - activeMatch.set(updater) - return - } - - const cachedMatch = this.stores.cachedMatchStores.get(id) - if (cachedMatch) { - const next = updater(cachedMatch.get()) - if (next.status === 'redirected') { - const deleted = this.stores.cachedMatchStores.delete(id) - if (deleted) { - this.stores.cachedIds.set((prev) => - prev.filter((matchId) => matchId !== id), - ) - } - } else { - cachedMatch.set(next) - } - } - }) - } - - getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => { - return ( - this.stores.cachedMatchStores.get(matchId)?.get() ?? - this.stores.pendingMatchStores.get(matchId)?.get() ?? - this.stores.matchStores.get(matchId)?.get() - ) + return fn() } /** - * Invalidate the current matches and optionally force them back into a pending state. + * Invalidate selected match generations and optionally force current matches + * back into a pending state. + * + * - Marks committed and cached matches whose IDs are selected as invalid. + * - Retires selected active preloads so older work cannot publish fresh data. * - * - Marks all matches that pass the optional `filter` as `invalid: true`. - * - If `forcePending` is true, or a match is currently in `'error'` or `'notFound'` status, - * its status is reset to `'pending'` and its `error` cleared so that the loader is re-run - * on the next `load()` call (eg. after HMR or a manual invalidation). + * The next load decides when to publish pending UI, so invalidation does not + * mutate the currently rendered status. */ invalidate: InvalidateFn< RouterCore< @@ -2771,44 +2468,79 @@ export class RouterCore< TDehydrated > > = (opts) => { + const committedMatches = this._committed + const filter = opts?.filter + const preloads = this._preloads + const invalidIds = new Set( + [ + ...committedMatches, + ...this._cache.values(), + ...[...(preloads?.values() ?? [])].flat(), + ...(this._tx?.[3 /* matches */] ?? []), + ] + .filter( + (match) => !filter || filter(match as MakeRouteMatchUnion), + ) + .map((match) => match.id), + ) + const discardedPreloads: Array = [] + for (const [controller, matches] of preloads ?? []) { + if (matches.some((match) => invalidIds.has(match.id))) { + preloads!.delete(controller) + discardedPreloads.push(controller) + } + } const invalidate = (d: MakeRouteMatch) => { - if (opts?.filter?.(d as MakeRouteMatchUnion) ?? true) { - return { + if (invalidIds.has(d.id)) { + const route = this.routesById[d.routeId] as AnyRoute + const next = { ...d, invalid: true, - ...(opts?.forcePending || - d.status === 'error' || - d.status === 'notFound' + ...((opts?.forcePending || + d.status === 'error' || + d.status === 'notFound') && + routeNeedsLoad(route) ? ({ status: 'pending', error: undefined } as const) : undefined), } + // Invalidation replaces this owner; it does not create another lease. + ;(d as AnyRouteMatch & { _flight?: LoaderFlight })._flight = undefined + return next } return d } - this.batch(() => { - this.stores.setMatches(this.stores.matches.get().map(invalidate)) - this.stores.setCached(this.stores.cachedMatches.get().map(invalidate)) - this.stores.setPending(this.stores.pendingMatches.get().map(invalidate)) - }) + this._committed = committedMatches.map(invalidate) + // Cache entries are settled successes. Retiring matching active preload + // owners makes an in-place stale mark sufficient while preserving data. + for (const [id, match] of this._cache) { + if (invalidIds.has(id)) { + match.invalid = true + if (opts?.forcePending) { + match.status = 'pending' + } + } + } + // The superseding load must not discover any same-ID generation selected + // for replacement. Existing owners release it in their normal order. + for (const id of invalidIds) { + this._flights?.delete(id) + } + for (const controller of discardedPreloads) { + controller.abort() + } this.shouldViewTransition = false return this.load({ sync: opts?.sync }) } - getParsedLocationHref = (location: ParsedLocation) => { - // For redirects and external use, we need publicHref (with rewrite output applied) - // href is the internal path after rewrite input, publicHref is user-facing - return location.publicHref || '/' - } - resolveRedirect = (redirect: AnyRedirect): AnyRedirect => { const locationHeader = redirect.headers.get('Location') if (!redirect.options.href || redirect.options._builtLocation) { const location = redirect.options._builtLocation ?? this.buildLocation(redirect.options) - const href = this.getParsedLocationHref(location) + const href = location.publicHref || '/' redirect.options.href = href redirect.headers.set('Location', href) } else if (locationHeader) { @@ -2845,115 +2577,64 @@ export class RouterCore< } clearCache: ClearCacheFn = (opts) => { + const cached = this._cache + const preloads = this._preloads const filter = opts?.filter - if (filter !== undefined) { - this.stores.setCached( - this.stores.cachedMatches - .get() - .filter((m) => !filter(m as MakeRouteMatchUnion)), - ) - } else { - this.stores.setCached([]) + const discarded: Array = [] + const discardedIds: Array = [] + for (const [id, match] of cached) { + if (!filter || filter(match as MakeRouteMatchUnion)) { + discardedIds.push(id) + discarded.push(match) + } } - } - - clearExpiredCache = () => { - const now = Date.now() - // This is where all of the garbage collection magic happens - const filter = (d: MakeRouteMatch) => { - const route = this.looseRoutesById[d.routeId]! - - if (!route.options.loader) { - return true + const abort: Array = [] + for (const [controller, matches] of preloads ?? []) { + if (!filter || matches.some(filter as any)) { + abort.push(controller) + discarded.push(...matches) } + } - // If the route was preloaded, use the preloadGcTime - // otherwise, use the gcTime - const gcTime = - (d.preload - ? (route.options.preloadGcTime ?? this.options.defaultPreloadGcTime) - : (route.options.gcTime ?? this.options.defaultGcTime)) ?? - 5 * 60 * 1000 - - const isError = d.status === 'error' - if (isError) return true - - const gcEligible = now - d.updatedAt >= gcTime - return gcEligible + // Run every public filter before changing authority, then prune both maps + // before releasing a public loader + // signal, whose abort listeners can synchronously reenter the router. + for (const id of discardedIds) { + cached.delete(id) + } + for (const controller of abort) { + preloads!.delete(controller) + } + // clearCache is a force-retirement boundary, so a final discarded lease + // must not use the normal zero-owner handoff window. + for (const match of discarded as Array< + AnyRouteMatch & { _flight?: LoaderFlight } + >) { + const flight = match._flight + match._flight = undefined + if (flight && !--flight[2 /* leases */]) { + if (this._flights?.get(match.id) === flight) { + this._flights.delete(match.id) + } + abort.push(flight[1 /* controller */]) + } + } + for (const controller of abort) { + controller.abort() } - this.clearCache({ filter }) } - loadRouteChunk = loadRouteChunk + loadRouteChunk: ( + route: AnyRoute, + componentType?: 'errorComponent' | 'notFoundComponent' | false, + ) => Promise | undefined = loadRouteChunk preloadRoute: PreloadRouteFn< TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory - > = async (opts) => { - const next = opts._builtLocation ?? this.buildLocation(opts as any) - - let matches = this.matchRoutes(next, { - throwOnError: true, - preload: true, - dest: opts, - }) - - const activeMatchIds = new Set([ - ...this.stores.matchesId.get(), - ...this.stores.pendingIds.get(), - ]) - - const loadedMatchIds = new Set([ - ...activeMatchIds, - ...this.stores.cachedIds.get(), - ]) - - // If the matches are already loaded, we need to add them to the cached matches. - const matchesToCache = matches.filter( - (match) => !loadedMatchIds.has(match.id), - ) - if (matchesToCache.length) { - const cachedMatches = this.stores.cachedMatches.get() - this.stores.setCached([...cachedMatches, ...matchesToCache]) - } - - try { - matches = await loadMatches({ - router: this, - matches, - location: next, - preload: true, - updateMatch: (id, updater) => { - // Don't update the match if it's currently loaded - if (activeMatchIds.has(id)) { - matches = matches.map((d) => (d.id === id ? updater(d) : d)) - } else { - this.updateMatch(id, updater) - } - }, - }) - - return matches - } catch (err) { - if (isRedirect(err)) { - if (err.options.reloadDocument) { - return undefined - } - - return await this.preloadRoute({ - ...err.options, - _fromLocation: next, - }) - } - if (!isNotFound(err)) { - // Preload errors are not fatal, but we should still log them - console.error(err) - } - return undefined - } - } + > = (opts) => preloadClientRoute(this, opts) matchRoute: MatchRouteFn< TRouteTree, @@ -2971,12 +2652,12 @@ export class RouterCore< } const next = this.buildLocation(matchLocation as any) - if (opts?.pending && this.stores.status.get() !== 'pending') { + const isPending = this.stores.status.get() === 'pending' + if (opts?.pending && !isPending) { return false } - const pending = - opts?.pending === undefined ? !this.stores.isLoading.get() : opts.pending + const pending = opts?.pending ?? !isPending const baseLocation = pending ? this.latestLocation @@ -3016,11 +2697,19 @@ export class RouterCore< serverSsr?: ServerSsr serverSsrLifecycle?: RouterSsrLifecycle +} - hasNotFoundMatch = () => { - return this.stores.matches - .get() - .some((d) => d.status === 'notFound' || d.globalNotFound) +/** + * In non-production environments, + * augment the RouterCore class with a `_refreshRoute` method + * dedicated to HMR. + */ +if (process.env.NODE_ENV !== 'production') { + RouterCore.prototype._replaceRouteChunk = replaceRouteChunk + RouterCore.prototype._refreshRoute = async function () { + this._serverResult = undefined + this.updateLatestLocation() + await refreshClientRoute(this) } } @@ -3057,14 +2746,11 @@ export function getInitialRouterState( location: ParsedLocation, ): RouterState { return { - loadedAt: 0, isLoading: false, - isTransitioning: false, status: 'idle', resolvedLocation: undefined, location, matches: [], - statusCode: 200, } } @@ -3096,56 +2782,12 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown { return {} } -/** - * Build the matched route chain and extract params for a pathname. - * Falls back to the root route if no specific route is found. - */ -export function getMatchedRoutes({ - pathname, - routesById, - processedTree, -}: { - pathname: string - routesById: Record - processedTree: ProcessedTree -}) { - const routeParams: Record = Object.create(null) - const trimmedPath = trimPathRight(pathname) - - let foundRoute: TRouteLike | undefined = undefined - const match = findRouteMatch(trimmedPath, processedTree, true) - if (match) { - foundRoute = match.route - Object.assign(routeParams, match.rawParams) // Copy params, because they're cached - } - - const matchedRoutes = match?.branch || [routesById[rootRouteId]!] - - return { matchedRoutes, routeParams, foundRoute } -} - -/** - * TODO: once caches are persisted across requests on the server, - * we can cache the built middleware chain using `last(destRoutes)` as the key - */ -function applySearchMiddleware({ - search, - dest, - destRoutes, - _includeValidateSearch, -}: { - search: any - dest: { search?: unknown } - destRoutes: ReadonlyArray - _includeValidateSearch: boolean | undefined -}) { - const middleware = buildMiddlewareChain(destRoutes) - return middleware(search, dest, _includeValidateSearch ?? false) -} - -function buildMiddlewareChain(destRoutes: ReadonlyArray) { - let dest: BuildNextOptions - let includeValidateSearch: boolean | undefined +function applySearchMiddleware( + search: any, + dest: BuildNextOptions, + destRoutes: ReadonlyArray, + includeValidateSearch: boolean | undefined, +) { const middlewares = [] as Array> for (const route of destRoutes) { @@ -3238,15 +2880,7 @@ function buildMiddlewareChain(destRoutes: ReadonlyArray) { return (middlewares[index]! as any)({ search: currentSearch, next, meta }) } - return function middleware( - search: any, - nextDest: BuildNextOptions, - _includeValidateSearch: boolean, - ) { - dest = nextDest - includeValidateSearch = _includeValidateSearch - return applyNext(0, search) - } + return applyNext(0, search) } function findGlobalNotFoundRouteId( @@ -3254,26 +2888,41 @@ function findGlobalNotFoundRouteId( routes: ReadonlyArray, ) { if (notFoundMode !== 'root') { + let fallback for (let i = routes.length - 1; i >= 0; i--) { const route = routes[i]! - if (route.children) { + if (route.options.notFoundComponent) { return route.id } + fallback ||= route.children && route.id + } + if (fallback) { + return fallback } } return rootRouteId } +function resolveNextParams( + spec: unknown, + base: Record, +): Record { + return spec === false || spec === null + ? Object.create(null) + : (spec ?? true) === true + ? base + : Object.assign(base, functionalUpdate(spec as any, base)) +} + function extractStrictParams( route: AnyRoute, accumulatedParams: Record, ) { const parseParams = route.options.params?.parse ?? route.options.parseParams if (parseParams) { - const result = parseParams(accumulatedParams as Record) - if (result === false) { - throw new Error('Route params.parse returned false for a matched route') - } - Object.assign(accumulatedParams, result) + Object.assign( + accumulatedParams, + parseParams(accumulatedParams as Record), + ) } } diff --git a/packages/router-core/src/scroll-restoration.ts b/packages/router-core/src/scroll-restoration.ts index 6287a6fce0..5e4ae0d3ee 100644 --- a/packages/router-core/src/scroll-restoration.ts +++ b/packages/router-core/src/scroll-restoration.ts @@ -1,5 +1,4 @@ import { isServer } from '@tanstack/router-core/isServer' -import { locationHistoryActions } from './router' import type { AnyRouter } from './router' import type { ParsedLocation } from './location' @@ -133,7 +132,6 @@ export function getElementScrollRestorationEntry( let ignoreScroll = false const windowScrollTarget = 'window' -type ScrollTarget = typeof windowScrollTarget | Element function getElement(selector: string | (() => Element | null | undefined)) { try { @@ -148,8 +146,8 @@ function getScrollToTopElements( scrollToTopSelectors: NonNullable< AnyRouter['options']['scrollToTopSelectors'] >, -): Array { - const elements: Array = [] +) { + const elements = new Set() for (const selector of scrollToTopSelectors) { if (selector === windowScrollTarget) { @@ -158,7 +156,7 @@ function getScrollToTopElements( const element = getElement(selector) if (element) { - elements.push(element) + elements.add(element) } } @@ -180,46 +178,21 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { const getKey = router.options.getScrollRestorationKey || defaultGetScrollRestorationKey - const trackedScrollEntries = new Map() - const setTrackedScrollEntry = ( - target: ScrollTarget, - scrollX: number, - scrollY: number, - ) => { - const entry = - trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry) - entry.scrollX = scrollX - entry.scrollY = scrollY - trackedScrollEntries.set(target, entry) - } - - const onScroll = (event: Event) => { - if (ignoreScroll || !scroll.restoring) { - return - } - - if (event.target === document) { - setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY) - } else { - const target = event.target as Element - setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop) - } - } + const trackedScrollTargets = new Set() // Snapshot the current page's tracked scroll targets before navigation or unload. const snapshotCurrentScrollTargets = (restoreKey: string) => { - if (!scroll.restoring) { - return - } - const keyEntry = (scrollRestorationCache[restoreKey] ||= {} as ScrollRestorationByElement) - for (const [target, position] of trackedScrollEntries) { - if (target === windowScrollTarget) { - keyEntry[windowScrollTarget] = position - } else if (target.isConnected) { - keyEntry[getScrollRestorationSelector(target)] = position + for (const target of trackedScrollTargets) { + if (target === document) { + keyEntry[windowScrollTarget] = { scrollX, scrollY } + } else if ((target as Element).isConnected) { + keyEntry[getScrollRestorationSelector(target as Element)] = { + scrollX: (target as Element).scrollLeft, + scrollY: (target as Element).scrollTop, + } } } } @@ -230,12 +203,21 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { history.scrollRestoration = 'manual' - document.addEventListener('scroll', onScroll, true) + document.addEventListener( + 'scroll', + (event) => { + if (ignoreScroll) { + return + } + trackedScrollTargets.add(event.target as Document | Element) + }, + true, + ) router.subscribe('onBeforeLoad', (event) => { if (event.fromLocation) { snapshotCurrentScrollTargets(getKey(event.fromLocation)) } - trackedScrollEntries.clear() + trackedScrollTargets.clear() }) addEventListener('pagehide', () => { snapshotCurrentScrollTargets( @@ -258,12 +240,11 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { const behavior = router.options.scrollRestorationBehavior const scrollToTopSelectors = router.options.scrollToTopSelectors const shouldResetScroll = scroll.next - let scrollToTopElements: Array | undefined - trackedScrollEntries.clear() - - if (!shouldResetScroll) { - scroll.next = true - } + const hashNavigation = scroll.hash + let scrollToTopElements: Set | undefined + trackedScrollTargets.clear() + scroll.next = true + scroll.hash = false if ( typeof router.options.scrollRestoration === 'function' && @@ -295,7 +276,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { if (shouldResetScroll && scrollToTopSelectors) { scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors) - if (scrollToTopElements.includes(element)) { + if (scrollToTopElements.has(element)) { continue } } @@ -321,11 +302,12 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { let windowRestored = false if (shouldResetScroll) { - const action = locationHistoryActions.get(event.toLocation) + if (!hash && scrollToTopSelectors) { + scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors) + } + const skipWindowRestore = - hash && - hashScrollIntoViewOptions && - (action === 'PUSH' || action === 'REPLACE') + hash && hashScrollIntoViewOptions && hashNavigation const elementEntries = scroll.restoring ? scrollRestorationCache[cacheKey] @@ -351,21 +333,23 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) { if (element) { element.scrollLeft = scrollX element.scrollTop = scrollY + scrollToTopElements?.delete(element) } } } } - if (!windowRestored && !hash) { + if (!hash) { const scrollOptions = { top: 0, left: 0, behavior, } - scrollTo(scrollOptions) - if (scrollToTopSelectors) { - scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors) + if (!windowRestored) { + scrollTo(scrollOptions) + } + if (scrollToTopElements) { for (const element of scrollToTopElements) { element.scrollTo(scrollOptions) } diff --git a/packages/router-core/src/ssr/createRequestHandler.ts b/packages/router-core/src/ssr/createRequestHandler.ts index 32377b2bea..a945effd01 100644 --- a/packages/router-core/src/ssr/createRequestHandler.ts +++ b/packages/router-core/src/ssr/createRequestHandler.ts @@ -1,11 +1,15 @@ import { createMemoryHistory } from '@tanstack/history' +import { _getRenderedMatches } from '../load-client' import { mergeHeaders } from './headers' import { attachRouterServerSsrUtils, getNormalizedURL, getOrigin, } from './ssr-server' -import { normalizeSsrResponse } from './handlerCallback' +import { + bindSsrResponseToRequest, + disposeSsrResponseDetached, +} from './handlerCallback' import type { HandlerCallback } from './handlerCallback' import type { AnyHeaders } from './headers' import type { AnyRouter } from '../router' @@ -15,6 +19,79 @@ export type RequestHandler = ( cb: HandlerCallback, ) => Promise +type RequestWaiter = ((reason: unknown) => void) | undefined + +const requestWaiters = new WeakMap>() + +function removeRequestWaiter( + waiters: Array, + index: number, + reject: (reason: unknown) => void, +) { + if (waiters[index] !== reject) { + return + } + if (index !== waiters.length - 1) { + waiters[index] = undefined + return + } + + waiters.pop() + while (waiters.length && waiters[waiters.length - 1] === undefined) { + waiters.pop() + } +} + +export function waitForRequest( + value: T | PromiseLike, + signal: AbortSignal, + onLate?: (value: T) => void, +): Promise { + const promise = Promise.resolve(value) + if (signal.aborted) { + void promise.then(onLate, () => {}) + return Promise.reject(signal.reason) + } + + return new Promise((resolve, reject) => { + let waiters = requestWaiters.get(signal) + let index: number + if (waiters) { + index = waiters.push(reject) - 1 + } else { + const newWaiters: Array = [reject] + waiters = newWaiters + index = 0 + requestWaiters.set(signal, newWaiters) + signal.addEventListener( + 'abort', + () => { + requestWaiters.delete(signal) + for (const rejectWaiter of newWaiters) { + rejectWaiter?.(signal.reason) + } + newWaiters.length = 0 + }, + { once: true }, + ) + } + void promise.then( + (result) => { + removeRequestWaiter(waiters, index, reject) + if (signal.aborted) { + onLate?.(result) + } else { + resolve(result) + } + }, + (error) => { + removeRequestWaiter(waiters, index, reject) + reject(error) + }, + ) + }) +} + export function createRequestHandler({ createRouter, request, @@ -25,13 +102,14 @@ export function createRequestHandler({ getRouterManifest?: () => ServerManifest | Promise }): RequestHandler { return async (cb) => { + request.signal.throwIfAborted() const router = createRouter() let responseOwnsCleanup = false try { attachRouterServerSsrUtils({ router, - manifest: await getRouterManifest?.(), + manifest: await waitForRequest(getRouterManifest?.(), request.signal), }) // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR. @@ -50,20 +128,41 @@ export function createRequestHandler({ origin: router.options.origin ?? origin, }) - await router.load() + await router.load({ + _signal: request.signal, + }) + request.signal.throwIfAborted() + + const result = router._serverResult + if (result?.type === 'redirect') { + return result.redirect + } - await router.serverSsr?.dehydrate() + await waitForRequest(router.serverSsr?.dehydrate(), request.signal) + request.signal.throwIfAborted() const responseHeaders = getRequestHeaders({ router, }) - const response = await cb({ - request, + request.signal.throwIfAborted() + const response = await waitForRequest( + cb({ + request, + router, + responseHeaders, + }), + request.signal, + (late) => { + disposeSsrResponseDetached(late, request.signal.reason) + }, + ) + const ssrResponse = bindSsrResponseToRequest( router, - responseHeaders, - }) - const ssrResponse = normalizeSsrResponse(response) + response, + request.signal, + ) + request.signal.throwIfAborted() responseOwnsCleanup = ssrResponse.serverSsrCleanup === 'stream' return ssrResponse.response } finally { @@ -79,16 +178,10 @@ export function createRequestHandler({ function getRequestHeaders(opts: { router: AnyRouter }): Headers { const matchHeaders: Array = [] - for (const match of opts.router.stores.matches.get()) { + for (const match of _getRenderedMatches(opts.router.stores.matches.get())) { matchHeaders.push(match.headers) } - // Handle Redirects - const redirect = opts.router.stores.redirect.get() - if (redirect) { - matchHeaders.push(redirect.headers) - } - return mergeHeaders( { 'Content-Type': 'text/html; charset=UTF-8', diff --git a/packages/router-core/src/ssr/handlerCallback.ts b/packages/router-core/src/ssr/handlerCallback.ts index 22aa843063..368c8a2a61 100644 --- a/packages/router-core/src/ssr/handlerCallback.ts +++ b/packages/router-core/src/ssr/handlerCallback.ts @@ -30,6 +30,40 @@ export function normalizeSsrResponse( : { response: result, serverSsrCleanup: 'none' } } +export function disposeSsrResponse( + response: SsrResponse, + reason?: unknown, +): Promise { + if (response.serverSsrCleanup !== 'stream') { + return Promise.resolve() + } + try { + return Promise.resolve(response.dispose(reason)) + } catch (error) { + return Promise.reject(error) + } +} + +export function disposeSsrResponseDetached( + result: HandlerCallbackResult, + reason?: unknown, + onError: (error: unknown) => void = console.error, +): void { + const ssrResponse = normalizeSsrResponse(result) + if (ssrResponse.serverSsrCleanup === 'stream') { + void disposeSsrResponse(ssrResponse, reason).catch(onError) + return + } + + if (ssrResponse.response.body) { + try { + void ssrResponse.response.body.cancel(reason).catch(onError) + } catch (error) { + onError(error) + } + } +} + export function createSsrStreamResponse( router: TRouter, response: Response, @@ -43,29 +77,63 @@ export function createSsrStreamResponse( response, serverSsrCleanup: 'stream', async dispose(reason?: unknown) { - if (disposed) return + if (disposed) { + return + } disposed = true + // Sever router ownership before asking user/renderer stream machinery to + // cancel. A custom stream is allowed to ignore cancellation forever. + router.serverSsr?.cleanup() + try { await response.body!.cancel(reason) } catch { - // ignore; fallback cleanup below still releases router SSR state + // Cleanup above already released router SSR state. } - - router.serverSsr?.cleanup() }, } } +export function bindSsrResponseToRequest( + router: AnyRouter | undefined, + result: HandlerCallbackResult, + signal: AbortSignal, +): SsrResponse { + const ssrResponse = normalizeSsrResponse(result) + if (ssrResponse.serverSsrCleanup !== 'stream') { + if (signal.aborted) { + disposeSsrResponseDetached(result, signal.reason) + } + return ssrResponse + } + + const failed = (error: unknown) => { + router?.serverSsr?.cleanup() + console.error(error) + } + const abort = () => { + disposeSsrResponseDetached(ssrResponse, signal.reason, failed) + } + if (signal.aborted) { + abort() + return ssrResponse + } + + signal.addEventListener('abort', abort, { once: true }) + router?.serverSsr?.onCleanup(() => { + signal.removeEventListener('abort', abort) + }) + return ssrResponse +} + export async function replaceSsrResponse( result: HandlerCallbackResult, response: Response, reason?: unknown, ): Promise { const ssrResponse = normalizeSsrResponse(result) - if (ssrResponse.serverSsrCleanup === 'stream') { - await ssrResponse.dispose(reason) - } + await disposeSsrResponse(ssrResponse, reason) return { response, serverSsrCleanup: 'none' } } @@ -74,9 +142,7 @@ export async function stripSsrResponseBody( reason?: unknown, ): Promise { const ssrResponse = normalizeSsrResponse(result) - if (ssrResponse.serverSsrCleanup === 'stream') { - await ssrResponse.dispose(reason) - } + await disposeSsrResponse(ssrResponse, reason) return { response: new Response(null, ssrResponse.response), serverSsrCleanup: 'none', diff --git a/packages/router-core/src/ssr/server.ts b/packages/router-core/src/ssr/server.ts index df5b838676..93973999cb 100644 --- a/packages/router-core/src/ssr/server.ts +++ b/packages/router-core/src/ssr/server.ts @@ -1,8 +1,11 @@ -export { createRequestHandler } from './createRequestHandler' +export { createRequestHandler, waitForRequest } from './createRequestHandler' export type { RequestHandler } from './createRequestHandler' export { + bindSsrResponseToRequest, createSsrStreamResponse, defineHandlerCallback, + disposeSsrResponse, + disposeSsrResponseDetached, isSsrResponse, normalizeSsrResponse, replaceSsrResponse, diff --git a/packages/router-core/src/ssr/ssr-client.ts b/packages/router-core/src/ssr/ssr-client.ts index 2aa5358ac0..6b0921c8db 100644 --- a/packages/router-core/src/ssr/ssr-client.ts +++ b/packages/router-core/src/ssr/ssr-client.ts @@ -1,310 +1 @@ -import { invariant } from '../invariant' -import { isNotFound } from '../not-found' -import { createControlledPromise } from '../utils' -import { hydrateSsrMatchId } from './ssr-match-id' -import type { GLOBAL_SEROVAL, GLOBAL_TSR } from './constants' -import type { DehydratedMatch, TsrSsrGlobal } from './types' -import type { AnyRouteMatch } from '../Matches' -import type { AnyRouter } from '../router' -import type { RouteContextOptions } from '../route' -import type { AnySerializationAdapter } from './serializer/transformer' - -declare global { - interface Window { - [GLOBAL_TSR]?: TsrSsrGlobal - [GLOBAL_SEROVAL]?: any - } -} - -function hydrateMatch( - match: AnyRouteMatch, - deyhydratedMatch: DehydratedMatch, -): void { - match.id = deyhydratedMatch.i - match.__beforeLoadContext = deyhydratedMatch.b - match.loaderData = deyhydratedMatch.l - match.status = deyhydratedMatch.s - match.ssr = deyhydratedMatch.ssr - match.updatedAt = deyhydratedMatch.u - match.error = deyhydratedMatch.e - // Only hydrate global-not-found when a defined value is present in the - // dehydrated payload. If omitted, preserve the value computed from the - // current client location (important for SPA fallback HTML served at unknown - // URLs, where dehydrated matches may come from `/` but client matching marks - // root as globalNotFound). - if (deyhydratedMatch.g !== undefined) { - match.globalNotFound = deyhydratedMatch.g - } -} - -export async function hydrate(router: AnyRouter): Promise { - if (!window.$_TSR) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', - ) - } - - invariant() - } - - const serializationAdapters = router.options.serializationAdapters as - | Array - | undefined - - if (serializationAdapters?.length) { - const fromSerializableMap = new Map() - serializationAdapters.forEach((adapter) => { - fromSerializableMap.set(adapter.key, adapter.fromSerializable) - }) - window.$_TSR.t = fromSerializableMap - window.$_TSR.buffer.forEach((script) => script()) - } - window.$_TSR.initialized = true - - if (!window.$_TSR.router) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', - ) - } - - invariant() - } - - const dehydratedRouter = window.$_TSR.router - dehydratedRouter.matches.forEach((dehydratedMatch) => { - dehydratedMatch.i = hydrateSsrMatchId(dehydratedMatch.i) - }) - if (dehydratedRouter.lastMatchId) { - dehydratedRouter.lastMatchId = hydrateSsrMatchId( - dehydratedRouter.lastMatchId, - ) - } - const { manifest, dehydratedData, lastMatchId } = dehydratedRouter - - router.ssr = { - manifest, - } - const meta = document.querySelector('meta[property="csp-nonce"]') as - | HTMLMetaElement - | undefined - const nonce = meta?.content - router.options.ssr = { - nonce, - } - - // Allow the user to handle custom hydration data before matching routes. - // This lets hydration install router config that affects matching, e.g. rewrites. - await router.options.hydrate?.(dehydratedData) - - // Hydrate the router state - const matches = router.matchRoutes(router.stores.location.get()) - - // kick off loading the route chunks - const routeChunkPromise = Promise.all( - matches.map((match) => - router.loadRouteChunk(router.looseRoutesById[match.routeId]!), - ), - ) - - function setMatchForcePending(match: AnyRouteMatch) { - // usually the minPendingPromise is created in the Match component if a pending match is rendered - // however, this might be too late if the match synchronously resolves - const route = router.looseRoutesById[match.routeId]! - const pendingMinMs = - route.options.pendingMinMs ?? router.options.defaultPendingMinMs - if (pendingMinMs) { - const minPendingPromise = createControlledPromise() - match._nonReactive.minPendingPromise = minPendingPromise - match._forcePending = true - - setTimeout(() => { - minPendingPromise.resolve() - // We've handled the minPendingPromise, so we can delete it - router.updateMatch(match.id, (prev) => { - prev._nonReactive.minPendingPromise = undefined - return { - ...prev, - _forcePending: undefined, - } - }) - }, pendingMinMs) - } - } - - function setRouteSsr(match: AnyRouteMatch) { - const route = router.looseRoutesById[match.routeId] - if (route) { - route.options.ssr = match.ssr - } - } - // Right after hydration and before the first render, we need to rehydrate each match - // First step is to reyhdrate loaderData and __beforeLoadContext - let firstNonSsrMatchIndex: number | undefined = undefined - matches.forEach((match) => { - const dehydratedMatch = dehydratedRouter.matches.find( - (d) => d.i === match.id, - ) - if (!dehydratedMatch) { - match._nonReactive.dehydrated = false - match.ssr = false - setRouteSsr(match) - return - } - - hydrateMatch(match, dehydratedMatch) - setRouteSsr(match) - - match._nonReactive.dehydrated = match.ssr !== false - - if (match.ssr === 'data-only' || match.ssr === false) { - if (firstNonSsrMatchIndex === undefined) { - firstNonSsrMatchIndex = match.index - setMatchForcePending(match) - } - } - }) - - router.stores.setMatches(matches) - - // now that all necessary data is hydrated: - // 1) fully reconstruct the route context - // 2) execute `head()` and `scripts()` for each match - const activeMatches = router.stores.matches.get() - const location = router.stores.location.get() - await Promise.all( - activeMatches.map(async (match) => { - try { - const route = router.looseRoutesById[match.routeId]! - - const parentMatch = activeMatches[match.index - 1] - const parentContext = parentMatch?.context ?? router.options.context - - // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed - // so run it again and merge route context - if (route.options.context) { - const contextFnContext: RouteContextOptions = - { - deps: match.loaderDeps, - params: match.params, - context: parentContext ?? {}, - location, - navigate: (opts: any) => - router.navigate({ - ...opts, - _fromLocation: location, - }), - buildLocation: router.buildLocation, - cause: match.cause, - abortController: match.abortController, - preload: false, - matches, - routeId: route.id, - } - match.__routeContext = - route.options.context(contextFnContext) ?? undefined - } - - match.context = { - ...parentContext, - ...match.__routeContext, - ...match.__beforeLoadContext, - } - - const assetContext = { - ssr: router.options.ssr, - matches: activeMatches, - match, - params: match.params, - loaderData: match.loaderData, - } - const headFnContent = await route.options.head?.(assetContext) - - const scripts = await route.options.scripts?.(assetContext) - - match.meta = headFnContent?.meta - match.links = headFnContent?.links - match.headScripts = headFnContent?.scripts - match.styles = headFnContent?.styles - match.scripts = scripts - } catch (err) { - if (isNotFound(err)) { - match.error = { isNotFound: true } - console.error( - `NotFound error during hydration for routeId: ${match.routeId}`, - err, - ) - } else { - match.error = err as any - console.error( - `Error during hydration for route ${match.routeId}:`, - err, - ) - throw err - } - } - }), - ) - - const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId - const hasSsrFalseMatches = matches.some((m) => m.ssr === false) - // all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load() - if (!hasSsrFalseMatches && !isSpaMode) { - matches.forEach((match) => { - // remove the dehydrated flag since we won't run router.load() which would remove it - match._nonReactive.dehydrated = undefined - }) - // Mark the current location as resolved so that later load cycles - // (e.g. preloads, invalidations) don't mistakenly detect a href change - // (resolvedLocation defaults to undefined and router.load() is skipped - // in the normal SSR hydration path). - router.stores.resolvedLocation.set(router.stores.location.get()) - return routeChunkPromise - } - - // schedule router.load() to run after the next tick so we can store the promise in the match before loading starts - const loadPromise = Promise.resolve() - .then(() => router.load()) - .catch((err) => { - console.error('Error during router hydration:', err) - }) - - // in SPA mode we need to keep the first match below the root route pending until router.load() is finished - // this will prevent that other pending components are rendered but hydration is not blocked - if (isSpaMode) { - const match = matches[1] - if (!match) { - if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'Invariant failed: Expected to find a match below the root match in SPA mode.', - ) - } - - invariant() - } - setMatchForcePending(match) - - match._displayPending = true - match._nonReactive.displayPendingPromise = loadPromise - - loadPromise.then(() => { - router.batch(() => { - // ensure router is not in status 'pending' anymore - // this usually happens in Transitioner but if loading synchronously resolves, - // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false - if (router.stores.status.get() === 'pending') { - router.stores.status.set('idle') - router.stores.resolvedLocation.set(router.stores.location.get()) - } - // hide the pending component once the load is finished - router.updateMatch(match.id, (prev) => ({ - ...prev, - _displayPending: undefined, - displayPendingPromise: undefined, - })) - }) - }) - } - return routeChunkPromise -} +export { hydrate } from '../load-client' diff --git a/packages/router-core/src/ssr/ssr-match-id.ts b/packages/router-core/src/ssr/ssr-match-id.ts index 498c3a0c7e..6c7e2e9eaf 100644 --- a/packages/router-core/src/ssr/ssr-match-id.ts +++ b/packages/router-core/src/ssr/ssr-match-id.ts @@ -1,7 +1,16 @@ export function dehydrateSsrMatchId(id: string): string { - return id.replaceAll('/', '\0') + return id + .replaceAll('~', '~~') + .replaceAll('\0', '~0') + .replaceAll('\uFFFD', '~r') + .replaceAll('/', '\0') } export function hydrateSsrMatchId(id: string): string { - return id.replaceAll('\0', '/').replaceAll('\uFFFD', '/') + return id + .replaceAll('\0', '/') + .replaceAll('\uFFFD', '/') + .replace(/~([~0r])/g, (_, code) => + code === '0' ? '\0' : code === 'r' ? '\uFFFD' : code, + ) } diff --git a/packages/router-core/src/ssr/ssr-server.ts b/packages/router-core/src/ssr/ssr-server.ts index 31db2f5e41..30d3bc4890 100644 --- a/packages/router-core/src/ssr/ssr-server.ts +++ b/packages/router-core/src/ssr/ssr-server.ts @@ -8,6 +8,7 @@ import { import { decodePath } from '../utils' import { createLRUCache } from '../lru-cache' import { rootRouteId } from '../root' +import { _getRenderedMatches } from '../load-client' import minifiedTsrBootStrapScript from './tsrScript?script-string' import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants' import { dehydrateSsrMatchId } from './ssr-match-id' @@ -51,7 +52,7 @@ export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch { dehydratedMatch[shorthand] = match[key] } } - if (match.globalNotFound) { + if (match._notFound) { dehydratedMatch.g = true } return dehydratedMatch @@ -389,7 +390,7 @@ export function attachRouterServerSsrUtils({ if (!manifest) return manifest const requestAssets = getRequestAssets?.() - const matches = router.stores.matches.get() + const matches = _getRenderedMatches(router.stores.matches.get()) const hasAssets = hasRequestAssets(requestAssets) if (!hasAssets && !manifest.inlineCss) { @@ -495,8 +496,9 @@ export function attachRouterServerSsrUtils({ invariant() } - let matchesToDehydrate = router.stores.matches.get() - if (router.isShell()) { + let matchesToDehydrate = _getRenderedMatches(router.stores.matches.get()) + const isShell = router.isShell() + if (isShell) { // In SPA mode we only want to dehydrate the root match matchesToDehydrate = matchesToDehydrate.slice(0, 1) } @@ -540,11 +542,10 @@ export function attachRouterServerSsrUtils({ manifest: manifestToDehydrate, matches, } - const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id - if (lastMatchId) { - dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId) - } const dehydratedData = await router.options.dehydrate?.() + if (cleanupStarted) { + return + } if (dehydratedData) { dehydratedRouter.dehydratedData = dehydratedData } diff --git a/packages/router-core/src/ssr/transformStreamWithRouter.ts b/packages/router-core/src/ssr/transformStreamWithRouter.ts index 4393f955b3..03c6ecf2e1 100644 --- a/packages/router-core/src/ssr/transformStreamWithRouter.ts +++ b/packages/router-core/src/ssr/transformStreamWithRouter.ts @@ -4,6 +4,8 @@ import { TSR_SCRIPT_BARRIER_ID } from './constants' import type { AnyRouter } from '../router' export type TransformStreamWithRouterOptions = { + /** The request lifetime that owns this response stream. */ + signal?: AbortSignal /** Timeout for serialization to complete after app render finishes (default: 60000ms) */ timeoutMs?: number /** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */ @@ -196,6 +198,22 @@ function createAbortNotifier(opts?: TransformStreamWithRouterOptions) { } } +function listenToAbort( + signal: AbortSignal | undefined, + onAbort: (reason?: unknown) => void, +) { + if (!signal) { + return + } + if (signal.aborted) { + onAbort(signal.reason) + return + } + const listener = () => onAbort(signal.reason) + signal.addEventListener('abort', listener, { once: true }) + return () => signal.removeEventListener('abort', listener) +} + export function transformStreamWithRouter( router: AnyRouter, appStream: ReadableStream, @@ -224,6 +242,7 @@ function makeFastPathStream( let controller: ReadableStreamDefaultController | undefined let state: MergeState = MergeState.ReadingBody let lifetimeTimeoutHandle: ReturnType | undefined + let stopListeningToAbort: (() => void) | undefined let stopListeningToInjectedHtml: (() => void) | undefined const readerState = createReaderState(appStream) const notifyAbort = createAbortNotifier(opts) @@ -251,6 +270,8 @@ function makeFastPathStream( clearTimeout(lifetimeTimeoutHandle) lifetimeTimeoutHandle = undefined } + stopListeningToAbort?.() + stopListeningToAbort = undefined try { stopListeningToInjectedHtml?.() } catch { @@ -360,6 +381,11 @@ function makeFastPathStream( }, }) + stopListeningToAbort = listenToAbort(opts?.signal, (reason) => { + safeError(reason) + cleanup(reason) + }) + return stream } @@ -380,6 +406,7 @@ function makeMainStream( let stopListeningToSerializationFinished: (() => void) | undefined let serializationTimeoutHandle: ReturnType | undefined let lifetimeTimeoutHandle: ReturnType | undefined + let stopListeningToAbort: (() => void) | undefined let cleanedUp = false let controller: ReadableStreamDefaultController | undefined @@ -509,6 +536,8 @@ function makeMainStream( } stopListeningToInjectedHtml = undefined stopListeningToSerializationFinished = undefined + stopListeningToAbort?.() + stopListeningToAbort = undefined if (serializationTimeoutHandle !== undefined) { clearTimeout(serializationTimeoutHandle) @@ -763,7 +792,14 @@ function makeMainStream( if (cleanedUp || isDone()) return stream } - // Transform the appStream + stopListeningToAbort = listenToAbort(opts?.signal, (reason) => { + safeError(reason) + cleanup(reason) + }) + if (cleanedUp || isDone()) + return stream + + // Transform the appStream ;(async () => { try { while (true) { diff --git a/packages/router-core/src/ssr/types.ts b/packages/router-core/src/ssr/types.ts index 70395cc9a0..27bf111843 100644 --- a/packages/router-core/src/ssr/types.ts +++ b/packages/router-core/src/ssr/types.ts @@ -15,7 +15,6 @@ export interface DehydratedMatch { export interface DehydratedRouter { manifest: Manifest | undefined dehydratedData?: any - lastMatchId?: string matches: Array } diff --git a/packages/router-core/src/stores.ts b/packages/router-core/src/stores.ts index c2ab8311c3..8c2beeb2e7 100644 --- a/packages/router-core/src/stores.ts +++ b/packages/router-core/src/stores.ts @@ -1,11 +1,9 @@ -import { createLRUCache } from './lru-cache' import { arraysEqual, functionalUpdate } from './utils' import type { AnyRoute } from './route' import type { RouterState } from './router' import type { FullSearchSchema } from './routeInfo' import type { ParsedLocation } from './location' -import type { AnyRedirect } from './redirect' import type { AnyRouteMatch } from './Matches' export interface RouterReadableStore { @@ -34,12 +32,9 @@ export type StoreConfig = { createMutableStore: MutableStoreFactory createReadonlyStore: ReadonlyStoreFactory batch: RouterBatchFn - init?: (stores: RouterStores) => void } -type MatchStore = RouterWritableStore & { - routeId?: string -} +type MatchStore = RouterWritableStore type ReadableStore = RouterReadableStore /** SSR non-reactive createMutableStore */ @@ -71,272 +66,115 @@ export function createNonReactiveReadonlyStore( export interface RouterStores { status: RouterWritableStore['status']> - loadedAt: RouterWritableStore - isLoading: RouterWritableStore - isTransitioning: RouterWritableStore location: RouterWritableStore>> resolvedLocation: RouterWritableStore< ParsedLocation> | undefined > - statusCode: RouterWritableStore - redirect: RouterWritableStore - matchesId: RouterWritableStore> - pendingIds: RouterWritableStore> - /** @internal */ - cachedIds: RouterWritableStore> + ids: RouterWritableStore> matches: ReadableStore> - pendingMatches: ReadableStore> - cachedMatches: ReadableStore> - firstId: ReadableStore - hasPending: ReadableStore - matchRouteDeps: ReadableStore<{ - locationHref: string - resolvedLocationHref: string | undefined - status: RouterState['status'] - }> __store: RouterReadableStore> - matchStores: Map - pendingMatchStores: Map - cachedMatchStores: Map + byRoute: Map /** - * Get a computed store that resolves a routeId to its current match state. - * Returns the same cached store instance for repeated calls with the same key. - * The computed depends on matchesId + the individual match store, so - * subscribers are only notified when the resolved match state changes. + * Get the stable atom for a route's presented match. The atom remains in the + * pool when the route leaves and contains `undefined` until it re-enters. */ - getRouteMatchStore: ( + getMatchStore: ( routeId: string, ) => RouterReadableStore setMatches: (nextMatches: Array) => void - setPending: (nextMatches: Array) => void - setCached: (nextMatches: Array) => void } export function createRouterStores( - initialState: RouterState, + initialLocation: RouterState['location'], config: StoreConfig, ): RouterStores { - const { createMutableStore, createReadonlyStore, batch, init } = config + const { createMutableStore, createReadonlyStore, batch } = config // non reactive utilities - const matchStores = new Map() - const pendingMatchStores = new Map() - const cachedMatchStores = new Map() + const byRoute = new Map() // atoms - const status = createMutableStore(initialState.status) - const loadedAt = createMutableStore(initialState.loadedAt) - const isLoading = createMutableStore(initialState.isLoading) - const isTransitioning = createMutableStore(initialState.isTransitioning) - const location = createMutableStore(initialState.location) - const resolvedLocation = createMutableStore(initialState.resolvedLocation) - const statusCode = createMutableStore(initialState.statusCode) - const redirect = createMutableStore(initialState.redirect) - const matchesId = createMutableStore>([]) - const pendingIds = createMutableStore>([]) - const cachedIds = createMutableStore>([]) + const status = createMutableStore['status']>('idle') + const location = createMutableStore(initialLocation) + const resolvedLocation = + createMutableStore['resolvedLocation']>(undefined) + const ids = createMutableStore>([]) // 1st order derived stores const matches = createReadonlyStore(() => - readPoolMatches(matchStores, matchesId.get()), - ) - const pendingMatches = createReadonlyStore(() => - readPoolMatches(pendingMatchStores, pendingIds.get()), - ) - const cachedMatches = createReadonlyStore(() => - readPoolMatches(cachedMatchStores, cachedIds.get()), - ) - const firstId = createReadonlyStore(() => matchesId.get()[0]) - const hasPending = createReadonlyStore(() => - matchesId.get().some((matchId) => { - const store = matchStores.get(matchId) - return store?.get().status === 'pending' - }), + ids.get().map((id) => byRoute.get(id)!.get()!), ) - const matchRouteDeps = createReadonlyStore(() => ({ - locationHref: location.get().href, - resolvedLocationHref: resolvedLocation.get()?.href, - status: status.get(), - })) // compatibility "big" state store const __store = createReadonlyStore(() => ({ status: status.get(), - loadedAt: loadedAt.get(), - isLoading: isLoading.get(), - isTransitioning: isTransitioning.get(), + isLoading: status.get() === 'pending', matches: matches.get(), location: location.get(), resolvedLocation: resolvedLocation.get(), - statusCode: statusCode.get(), - redirect: redirect.get(), })) - // Per-routeId computed store cache. - // Each entry resolves routeId → match state through the signal graph, - // giving consumers a single store to subscribe to instead of the - // two-level byRouteId → matchStore pattern. - // - // 64 max size is arbitrary, this is only for active matches anyway so - // it should be plenty. And we already have a 32 limit due to route - // matching bitmask anyway. - const matchStoreByRouteIdCache = createLRUCache< - string, - RouterReadableStore - >(64) - - function getRouteMatchStore( - routeId: string, - ): RouterReadableStore { - let cached = matchStoreByRouteIdCache.get(routeId) - if (!cached) { - cached = createReadonlyStore(() => { - // Reading matchesId.get() tracks it as a dependency. - // When matchesId changes (navigation), this computed re-evaluates. - const ids = matchesId.get() - for (const id of ids) { - const matchStore = matchStores.get(id) - if (matchStore && matchStore.routeId === routeId) { - // Reading matchStore.get() tracks it as a dependency. - // When the match store's state changes, this re-evaluates. - return matchStore.get() - } - } - return undefined - }) - matchStoreByRouteIdCache.set(routeId, cached) + function getMatchStore(routeId: string): MatchStore { + let matchStore = byRoute.get(routeId) + if (!matchStore) { + matchStore = createMutableStore(undefined) + byRoute.set(routeId, matchStore) } - return cached + return matchStore } const store = { // atoms status, - loadedAt, - isLoading, - isTransitioning, location, resolvedLocation, - statusCode, - redirect, - matchesId, - pendingIds, - cachedIds, + ids, // derived matches, - pendingMatches, - cachedMatches, - firstId, - hasPending, - matchRouteDeps, // non-reactive state - matchStores, - pendingMatchStores, - cachedMatchStores, + byRoute, // compatibility "big" state __store, - // per-key computed stores - getRouteMatchStore, + // stable per-route presentation atoms + getMatchStore, // methods setMatches, - setPending, - setCached, } - // initialize the active matches - setMatches(initialState.matches as Array) - init?.(store) - // setters to update non-reactive utilities in sync with the reactive stores function setMatches(nextMatches: Array) { - reconcileMatchPool( - nextMatches, - matchStores, - matchesId, - createMutableStore, - batch, - ) - } - - function setPending(nextMatches: Array) { - reconcileMatchPool( - nextMatches, - pendingMatchStores, - pendingIds, - createMutableStore, - batch, - ) - } - - function setCached(nextMatches: Array) { - reconcileMatchPool( - nextMatches, - cachedMatchStores, - cachedIds, - createMutableStore, - batch, - ) - } - - return store -} - -function readPoolMatches( - pool: Map, - ids: Array, -): Array { - const matches: Array = [] - for (const id of ids) { - const matchStore = pool.get(id) - if (matchStore) { - matches.push(matchStore.get()) - } - } - return matches -} - -function reconcileMatchPool( - nextMatches: Array, - pool: Map, - idStore: RouterWritableStore>, - createMutableStore: MutableStoreFactory, - batch: RouterBatchFn, -): void { - const nextIds = nextMatches.map((d) => d.id) - const nextIdSet = new Set(nextIds) - - batch(() => { - for (const id of pool.keys()) { - if (!nextIdSet.has(id)) { - pool.delete(id) + const previousIds = ids.get() + const nextIds = nextMatches.map((match) => match.routeId) + + batch(() => { + // Publish lane membership first so framework trees reconcile departures + // before observers of a leaving route receive its tombstone. + if (!arraysEqual(previousIds, nextIds)) { + ids.set(nextIds) } - } - for (const nextMatch of nextMatches) { - const existing = pool.get(nextMatch.id) - if (!existing) { - const matchStore = createMutableStore(nextMatch) as MatchStore - matchStore.routeId = nextMatch.routeId - pool.set(nextMatch.id, matchStore) - continue + for (const id of previousIds) { + if (!nextIds.includes(id)) { + byRoute.get(id)!.set(() => undefined) + } } - existing.routeId = nextMatch.routeId - if (existing.get() !== nextMatch) { - existing.set(nextMatch) + for (const nextMatch of nextMatches) { + const matchStore = getMatchStore(nextMatch.routeId) + if (matchStore.get() !== nextMatch) { + matchStore.set(nextMatch) + } } - } + }) + } - if (!arraysEqual(idStore.get(), nextIds)) { - idStore.set(nextIds) - } - }) + return store } diff --git a/packages/router-core/tests/background-assets-stale.test.ts b/packages/router-core/tests/background-assets-stale.test.ts new file mode 100644 index 0000000000..1c39969185 --- /dev/null +++ b/packages/router-core/tests/background-assets-stale.test.ts @@ -0,0 +1,79 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +describe('background decorative asset failure', () => { + beforeEach(() => { + vi.useFakeTimers() + vi.setSystemTime(0) + }) + + afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + }) + + test('commits fresh loader data while preserving the previous projected assets', async () => { + const projectionError = new Error('head projection failed') + const log = vi.spyOn(console, 'error').mockImplementation(() => {}) + let resolveStaleReload!: (data: { title: string }) => void + let loaderCalls = 0 + const loader = () => { + loaderCalls += 1 + if (loaderCalls === 1) { + return { title: 'old' } + } + + return new Promise<{ title: string }>((resolve) => { + resolveStaleReload = resolve + }) + } + + const rootRoute = new BaseRootRoute({}) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + loader, + head: ({ loaderData }) => { + // The loader always resolves before head runs in this test, so + // loaderData is never undefined here. + if (loaderData!.title === 'fresh') { + throw projectionError + } + + return { + meta: [{ title: loaderData!.title }], + } + }, + staleTime: 0, + gcTime: 60_000, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([fooRoute]), + history: createMemoryHistory(), + }) + + await router.navigate({ to: '/foo' }) + const matchId = router.state.matches.find( + (match) => match.routeId === fooRoute.id, + )!.id + const getMatch = () => + router.state.matches.find((match) => match.id === matchId) + + expect(getMatch()?.loaderData).toEqual({ title: 'old' }) + expect(getMatch()?.meta).toEqual([{ title: 'old' }]) + + await vi.advanceTimersByTimeAsync(1) + await router.load() + await vi.waitFor(() => expect(loaderCalls).toBe(2)) + + resolveStaleReload({ title: 'fresh' }) + await vi.waitFor(() => + expect(getMatch()?.loaderData).toEqual({ title: 'fresh' }), + ) + + expect(getMatch()?.meta).toEqual([{ title: 'old' }]) + expect(log).toHaveBeenCalledWith(projectionError) + }) +}) diff --git a/packages/router-core/tests/background-trim-abort.test.ts b/packages/router-core/tests/background-trim-abort.test.ts new file mode 100644 index 0000000000..aa992df111 --- /dev/null +++ b/packages/router-core/tests/background-trim-abort.test.ts @@ -0,0 +1,185 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +test('a background boundary keeps the matched branch and its lifecycle stable', async () => { + let parentLoads = 0 + const parentError = new Error('parent background reload failed') + const childOnEnter = vi.fn() + const childOnLeave = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + if (++parentLoads === 2) { + throw parentError + } + return `parent data ${parentLoads}` + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + return 'child data' + }, + onEnter: childOnEnter, + onLeave: childOnLeave, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + await router.invalidate() + + await expect.poll(() => router.state.matches[1]?.status).toBe('error') + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + status: 'error', + error: parentError, + }) + expect(router.state.matches[2]).toMatchObject({ + status: 'success', + loaderData: 'child data', + }) + expect(childOnEnter).toHaveBeenCalledTimes(1) + expect(childOnLeave).not.toHaveBeenCalled() + + await router.invalidate() + await expect.poll(() => router.state.matches[1]?.status).toBe('success') + expect(childOnEnter).toHaveBeenCalledTimes(1) + expect(childOnLeave).not.toHaveBeenCalled() +}) + +test('a background notFound stays private until its parent boundary is ready', async () => { + const boundaryReady = createControlledPromise() + const ParentNotFound = Object.assign(() => null, { + preload: () => boundaryReady, + }) + let childLoads = 0 + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + notFoundComponent: ParentNotFound, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + if (++childLoads > 1) { + throw notFound() + } + return 'initial child data' + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + await router.invalidate({ + filter: (match) => match.routeId === childRoute.id, + }) + await vi.waitFor(() => expect(childLoads).toBe(2)) + + expect(router.state.matches.map((match) => match.status)).toEqual([ + 'success', + 'success', + 'success', + ]) + + boundaryReady.resolve() + await vi.waitFor(() => { + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]?.status).toBe('notFound') + expect(router.state.matches[2]?.status).toBe('success') + }) +}) + +test('foreground supersession aborts every loader in a background batch', async () => { + let parentLoads = 0 + let childLoads = 0 + const backgroundSignals: Array = [] + + const waitForAbort = (signal: AbortSignal) => + new Promise((_resolve, reject) => { + signal.addEventListener( + 'abort', + () => { + const error = new Error('aborted') + error.name = 'AbortError' + reject(error) + }, + { once: true }, + ) + }) + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: ({ abortController }) => { + if (++parentLoads > 1) { + backgroundSignals.push(abortController.signal) + return waitForAbort(abortController.signal) + } + return 'initial parent data' + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: ({ abortController }) => { + if (++childLoads > 1) { + backgroundSignals.push(abortController.signal) + return waitForAbort(abortController.signal) + } + return 'initial child data' + }, + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + otherRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + const revalidation = router.invalidate() + await vi.waitFor(() => expect(backgroundSignals).toHaveLength(2)) + + await router.navigate({ to: '/other' }) + await revalidation + + expect(backgroundSignals[0]).not.toBe(backgroundSignals[1]) + expect(backgroundSignals.every((signal) => signal.aborted)).toBe(true) + expect(router.state.matches.at(-1)?.routeId).toBe(otherRoute.id) +}) diff --git a/packages/router-core/tests/blocked-navigation-current-load.test.ts b/packages/router-core/tests/blocked-navigation-current-load.test.ts new file mode 100644 index 0000000000..e307f35934 --- /dev/null +++ b/packages/router-core/tests/blocked-navigation-current-load.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * A history blocker can discard a navigation commit AFTER + * buildAndCommitLocation set router._pendingLocation (it is cleared a + * microtask later). A continuation of the still-current in-flight load that + * resumes inside that window must not treat the merely-built location as + * ownership loss: a blocked commit never starts a replacement load, so + * abandoning the pass would leave the router with status 'pending', + * isLoading true, and a populated pending pool forever. + */ + +describe('blocked navigation does not cancel the current load', () => { + test('loader continuation resuming while a blocked navigation is built still commits', async () => { + const loaderGate = createControlledPromise() + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const slowRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/slow', + loader: () => loaderGate, + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, slowRoute, otherRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + + // Framework transitioners turn committed history changes into router + // loads. A blocked commit never reaches this subscriber. + const unsubscribe = router.history.subscribe(router.load) + let unblock: (() => void) | undefined + + try { + const navigation = router.navigate({ to: '/slow' }) + + // Discard every later navigation commit. The blocker is async, so the + // discarded commit still sets _pendingLocation for one microtask. + unblock = router.history.block({ blockerFn: async () => true }) + + // Same tick: settle the loader, then issue a navigation the blocker + // discards. The loader continuation resumes inside the window where + // _pendingLocation still points at /other. + loaderGate.resolve('slow data') + const blockedNavigation = router.navigate({ to: '/other' }) + + await Promise.all([navigation, blockedNavigation]) + + expect(router.state.location.pathname).toBe('/slow') + expect(router.history.location.pathname).toBe('/slow') + expect( + router.state.matches.find((m) => m.routeId === slowRoute.id), + ).toMatchObject({ + status: 'success', + loaderData: 'slow data', + }) + } finally { + unblock?.() + unsubscribe() + } + }) +}) diff --git a/packages/router-core/tests/boundary-component-chunk.test.ts b/packages/router-core/tests/boundary-component-chunk.test.ts new file mode 100644 index 0000000000..b8e8322ca6 --- /dev/null +++ b/packages/router-core/tests/boundary-component-chunk.test.ts @@ -0,0 +1,262 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * boundary component preloads should not be blocked by an unrelated + * normal route component preload. A route load starts the normal component + * preload before the loader settles; if that loader throws, the error boundary + * asks to load only the route's errorComponent. + * + * This test uses a real client router load. It keeps the normal route component + * preload pending, resolves the errorComponent preload, and expects the route + * error state to be committed without waiting for the normal component chunk. + */ + +const pendingGates: Array>> = [] +const pendingLoads: Array> = [] + +afterEach(async () => { + for (const gate of pendingGates) { + gate.resolve() + } + + await Promise.allSettled(pendingLoads) + + pendingGates.length = 0 + pendingLoads.length = 0 +}) + +describe('route boundary component preloads', () => { + test('errorComponent preload resolves without waiting for a pending route component preload', async () => { + const componentGate = createControlledPromise() + const errorComponentGate = createControlledPromise() + const routeError = new Error('loader failed') + let errorComponentPreloadCalls = 0 + pendingGates.push(componentGate, errorComponentGate) + + const SlowRouteComponent = Object.assign(() => null, { + preload: () => componentGate, + }) + const ErrorBoundary = Object.assign(() => null, { + preload: () => { + errorComponentPreloadCalls++ + return errorComponentGate + }, + }) + + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/chunked', + loader: () => { + throw routeError + }, + component: SlowRouteComponent as any, + errorComponent: ErrorBoundary as any, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/chunked'] }), + }) + + const loadPromise = router.load() + pendingLoads.push(loadPromise) + + await vi.waitFor(() => expect(errorComponentPreloadCalls).toBe(1)) + errorComponentGate.resolve() + + // The route component chunk (componentGate) is still pending: the error + // state and the public load promise must settle without waiting for it. + await loadPromise + expect(componentGate.status).toBe('pending') + const match = router.state.matches.find((item) => item.routeId === route.id) + expect(match?.status).toBe('error') + expect(match?.error).toBe(routeError) + + componentGate.resolve() + }) + + test('an error boundary ignores a late normal component failure', async () => { + const componentGate = createControlledPromise() + const errorComponentGate = createControlledPromise() + const routeError = new Error('loader failed') + const componentError = new Error('component chunk failed') + const onError = vi.fn() + let errorComponentPreloadCalls = 0 + pendingGates.push(errorComponentGate) + + const SlowRouteComponent = Object.assign(() => null, { + preload: () => componentGate, + }) + const ErrorBoundary = Object.assign(() => null, { + preload: () => { + errorComponentPreloadCalls++ + return errorComponentGate + }, + }) + + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/chunked', + loader: () => { + throw routeError + }, + onError, + component: SlowRouteComponent as any, + errorComponent: ErrorBoundary as any, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/chunked'] }), + }) + + const loadPromise = router.load() + pendingLoads.push(loadPromise) + + await vi.waitFor(() => expect(errorComponentPreloadCalls).toBe(1)) + errorComponentGate.resolve() + await loadPromise + + const match = router.state.matches.find((item) => item.routeId === route.id) + expect(match?.status).toBe('error') + expect(match?.error).toBe(routeError) + expect(onError).toHaveBeenCalledOnce() + + componentGate.reject(componentError) + await expect(componentGate).rejects.toBe(componentError) + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(onError).toHaveBeenCalledOnce() + expect(match?.error).toBe(routeError) + }) + + test('global notFound does not wait for component chunks below its boundary', async () => { + const hiddenComponentGate = createControlledPromise() + const notFoundPreload = vi.fn(() => Promise.resolve()) + pendingGates.push(hiddenComponentGate) + + const NotFoundBoundary = Object.assign(() => null, { + preload: notFoundPreload, + }) + const HiddenComponent = Object.assign(() => null, { + preload: () => hiddenComponentGate, + }) + + const rootRoute = new BaseRootRoute({}) + const layoutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + id: '_layout', + notFoundComponent: NotFoundBoundary as any, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/child', + component: HiddenComponent as any, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([layoutRoute.addChildren([childRoute])]), + history: createMemoryHistory({ + initialEntries: ['/child/does-not-exist'], + }), + notFoundMode: 'fuzzy', + }) + + const loading = router.load() + pendingLoads.push(loading) + await loading + + expect(hiddenComponentGate.status).toBe('pending') + expect(notFoundPreload).toHaveBeenCalledOnce() + expect(router.state.matches.find((match) => match._notFound)?.routeId).toBe( + layoutRoute.id, + ) + }) + + test('a late normal component chunk cannot replace a selected not-found boundary', async () => { + const componentGate = createControlledPromise() + const notFoundGate = createControlledPromise() + const notFoundPreload = vi.fn(() => notFoundGate) + pendingGates.push(componentGate, notFoundGate) + + const ParentComponent = Object.assign(() => null, { + preload: () => componentGate, + }) + const NotFoundBoundary = Object.assign(() => null, { + preload: notFoundPreload, + }) + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => 'ready', + component: ParentComponent as any, + notFoundComponent: NotFoundBoundary as any, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + beforeLoad: () => { + throw notFound({ routeId: parentRoute.id }) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + const loading = router.load() + pendingLoads.push(loading) + await vi.waitFor(() => expect(notFoundPreload).toHaveBeenCalledOnce()) + + componentGate.resolve() + await Promise.resolve() + notFoundGate.resolve() + await loading + + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'notFound', + loaderData: 'ready', + }) + }) + + test('a required ancestor lazy chunk failure wins over a deeper loader notFound', async () => { + const chunkError = new Error('ancestor lazy chunk failed') + const rootRoute = new BaseRootRoute({}) + const ancestorRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/ancestor', + errorComponent: () => null, + }).lazy(() => Promise.reject(chunkError)) + const childRoute = new BaseRoute({ + getParentRoute: () => ancestorRoute, + path: '/child', + loader: () => { + throw notFound() + }, + notFoundComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + ancestorRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/ancestor/child'] }), + }) + + await router.load() + + expect( + router.state.matches.find((match) => match.routeId === ancestorRoute.id), + ).toMatchObject({ status: 'error', error: chunkError }) + }) +}) diff --git a/packages/router-core/tests/build-location.test.ts b/packages/router-core/tests/build-location.test.ts index 131fccf095..ce0d5c3c98 100644 --- a/packages/router-core/tests/build-location.test.ts +++ b/packages/router-core/tests/build-location.test.ts @@ -6,8 +6,23 @@ import { retainSearchParams, stripSearchParams, } from '../src' +import { _getUserHistoryState } from '../src/router' import { createTestRouter } from './routerTestUtils' +test('_getUserHistoryState removes volatile router bookkeeping but keeps mask payloads', () => { + expect( + _getUserHistoryState({ + key: 'legacy-key', + __TSR_key: 'key', + __TSR_index: 1, + __hashScrollIntoViewOptions: true, + __tempLocation: {} as any, + __tempKey: 'temp-key', + user: 'state', + } as any), + ).toEqual({ user: 'state', __tempLocation: {}, __tempKey: 'temp-key' }) +}) + describe('buildLocation - params function receives parsed params', () => { test('prev params should contain parsed params from route params.parse', async () => { const rootRoute = new BaseRootRoute({}) diff --git a/packages/router-core/tests/callbacks.test.ts b/packages/router-core/tests/callbacks.test.ts index 1673626f7e..7a1b062d4f 100644 --- a/packages/router-core/tests/callbacks.test.ts +++ b/packages/router-core/tests/callbacks.test.ts @@ -200,10 +200,6 @@ describe('callbacks', () => { )?.id expect(page2MatchId).toBeDefined() expect(page2MatchId).not.toBe(page1MatchId) - expect( - router.stores.cachedIds.get().some((id) => id === page1MatchId), - ).toBe(true) - await router.navigate({ to: '/foo', search: { page: '1' } }) expect(loader).toHaveBeenCalledTimes(2) expect(router.state.matches.some((d) => d.id === page1MatchId)).toBe(true) @@ -225,10 +221,6 @@ describe('callbacks', () => { )?.id expect(post2MatchId).toBeDefined() expect(post2MatchId).not.toBe(post1MatchId) - expect( - router.stores.cachedIds.get().some((id) => id === post1MatchId), - ).toBe(true) - await router.navigate({ to: '/posts/$postId', params: { postId: '1' } }) expect(loader).toHaveBeenCalledTimes(2) expect(router.state.matches.some((d) => d.id === post1MatchId)).toBe(true) diff --git a/packages/router-core/tests/chunk-failure-lifecycle.test.ts b/packages/router-core/tests/chunk-failure-lifecycle.test.ts new file mode 100644 index 0000000000..bf9fa369d4 --- /dev/null +++ b/packages/router-core/tests/chunk-failure-lifecycle.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * primary route chunk failures should go through the same route-error + * lifecycle as loader and beforeLoad failures. Skipping normalization means + * route onError callbacks, redirects, and notFound values from lazy/chunk + * loading can behave differently from other route failures. + * + * This test uses a real router navigation to a lazy route whose chunk rejects. + * The match reaches error state with the chunk error, but the route's onError + * callback should also receive that error as part of normal route failure + * handling. + */ + +describe('route chunk failure lifecycle', () => { + test('a rejected lazy chunk is retried on the next load instead of replaying forever', async () => { + let lazyCalls = 0 + const chunkError = new Error('lazy chunk failed once') + + const rootRoute = new BaseRootRoute({}) + const lazyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/lazy', + loader: () => 'loaded', + }).lazy(() => { + lazyCalls++ + if (lazyCalls === 1) { + return Promise.reject(chunkError) + } + return Promise.resolve({ options: {} } as any) + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([lazyRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.navigate({ to: '/lazy' }) + expect( + router.state.matches.find((match) => match.routeId === lazyRoute.id) + ?.status, + ).toBe('error') + + await router.invalidate() + expect(lazyCalls).toBe(2) + expect( + router.state.matches.find((match) => match.routeId === lazyRoute.id) + ?.status, + ).toBe('success') + }) + + test('calls route onError when lazy route chunk rejects during navigation', async () => { + const chunkError = new Error('lazy chunk failed') + let capturedError: unknown + + const rootRoute = new BaseRootRoute({}) + const lazyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/lazy', + onError: (error) => { + capturedError = error + }, + }).lazy(() => Promise.reject(chunkError)) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([lazyRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.navigate({ to: '/lazy' }) + + const lazyMatch = router.state.matches.find( + (match) => match.routeId === lazyRoute.id, + ) + expect(lazyMatch?.status).toBe('error') + expect(lazyMatch?.error).toBe(chunkError) + expect(capturedError).toBe(chunkError) + }) +}) diff --git a/packages/router-core/tests/client-lane-adversarial.test.ts b/packages/router-core/tests/client-lane-adversarial.test.ts new file mode 100644 index 0000000000..acfe3fede4 --- /dev/null +++ b/packages/router-core/tests/client-lane-adversarial.test.ts @@ -0,0 +1,547 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + redirect, +} from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +afterEach(() => { + vi.useRealTimers() +}) + +function abortAwareGate(signal: AbortSignal): Promise { + return new Promise((_resolve, reject) => { + signal.addEventListener( + 'abort', + () => { + const error = new Error('aborted') + error.name = 'AbortError' + reject(error) + }, + { once: true }, + ) + }) +} + +describe('adversarial client lane ownership', () => { + test('a navigation started by a pre-load listener supersedes the emitting load before it can continue', async () => { + const firstBeforeLoad = vi.fn() + const secondBeforeLoad = vi.fn() + const firstLoader = vi.fn(() => 'first data') + const secondLoader = vi.fn(() => 'second data') + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/first', + beforeLoad: firstBeforeLoad, + loader: firstLoader, + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/second', + beforeLoad: secondBeforeLoad, + loader: secondLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const beforeLoadLocations: Array = [] + router.subscribe('onBeforeLoad', (event) => { + beforeLoadLocations.push(event.toLocation.pathname) + }) + router.subscribe('onBeforeNavigate', (event) => { + if (event.toLocation.pathname === '/first') { + void router.navigate({ to: '/second' }) + } + }) + + await router.navigate({ to: '/first' }) + + expect(router.state.location.pathname).toBe('/second') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: secondRoute.id, + status: 'success', + }) + expect(beforeLoadLocations).toEqual(['/second']) + expect(firstBeforeLoad).not.toHaveBeenCalled() + expect(firstLoader).not.toHaveBeenCalled() + expect(secondBeforeLoad).toHaveBeenCalledTimes(1) + expect(secondLoader).toHaveBeenCalledTimes(1) + }) + + test.each(['onBeforeNavigate', 'onBeforeLoad'] as const)( + 'a throwing %s listener cannot interrupt later listeners or navigation finalization', + async (eventType) => { + const listenerError = new Error(`${eventType} listener failed`) + const laterListener = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetLoader = vi.fn(() => 'target data') + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader: targetLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + router.subscribe(eventType, () => { + throw listenerError + }) + router.subscribe(eventType, laterListener) + + await router.navigate({ to: '/target' }) + + expect(laterListener).toHaveBeenCalledTimes(1) + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + expect(targetLoader).toHaveBeenCalledTimes(1) + }, + ) + + test('superseding a published pending lane diffs lifecycle hooks from the last final lane', async () => { + const aOnLeave = vi.fn() + const bOnEnter = vi.fn() + const bOnLeave = vi.fn() + const cOnEnter = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const aRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/a', + onLeave: aOnLeave, + }) + const bRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/b', + pendingMs: 0, + pendingComponent: () => null, + loader: ({ abortController }) => abortAwareGate(abortController.signal), + onEnter: bOnEnter, + onLeave: bOnLeave, + }) + const cRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/c', + onEnter: cOnEnter, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([aRoute, bRoute, cRoute]), + history: createMemoryHistory({ initialEntries: ['/a'] }), + }) + + await router.load() + aOnLeave.mockClear() + + const bNavigation = router.navigate({ to: '/b' }) + await vi.waitFor(() => + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: bRoute.id, + status: 'pending', + }), + ) + + await router.navigate({ to: '/c' }) + await bNavigation + + expect(router.state.matches.at(-1)?.routeId).toBe(cRoute.id) + expect(aOnLeave).toHaveBeenCalledTimes(1) + expect(bOnEnter).not.toHaveBeenCalled() + expect(bOnLeave).not.toHaveBeenCalled() + expect(cOnEnter).toHaveBeenCalledTimes(1) + }) + + test('keeps a hidden child matched without projecting it below a parent error', async () => { + const parentError = new Error('parent failed') + const childHead = vi.fn(() => ({ + meta: [{ title: 'unreachable child' }], + })) + const childOnEnter = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw parentError + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + throw notFound() + }, + notFoundComponent: () => null, + head: childHead, + onEnter: childOnEnter, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + error: parentError, + }) + expect(childHead).not.toHaveBeenCalled() + expect(childOnEnter).toHaveBeenCalledTimes(1) + }) + + test('a redirect aborts the discarded loader generation', async () => { + let redirectSignal: AbortSignal | undefined + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + loader: ({ abortController }) => { + redirectSignal = abortController.signal + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/source' }) + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + expect(redirectSignal?.aborted).toBe(true) + }) + + test('keeps successful descendant data behind an ancestor boundary', async () => { + const parentError = new Error('parent failed') + const childData = { value: 'child data' } + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw parentError + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => childData, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + error: parentError, + }) + expect(router.state.matches[2]).toMatchObject({ + routeId: childRoute.id, + status: 'success', + loaderData: childData, + }) + }) + + test('a terminal preload retains reusable descendant loader data', async () => { + const parentError = new Error('preload parent failed') + const childLoader = vi.fn(() => 'child data') + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw parentError + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/parent/child' }) + await router.navigate({ to: '/parent/child' }) + + expect(childLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches[2]).toMatchObject({ + routeId: childRoute.id, + status: 'success', + loaderData: 'child data', + }) + }) + + test('navigation started by route context cannot be overwritten by the stale matching pass', async () => { + const bLoaderGate = createControlledPromise() + const bLoader = vi.fn(() => bLoaderGate) + let retainedLoaderSignal: AbortSignal | undefined + let abandonedContextSignal: AbortSignal | undefined + let redirected = false + + const rootRoute = new BaseRootRoute({ + staleTime: Infinity, + loader: ({ abortController }) => { + retainedLoaderSignal = abortController.signal + return 'root data' + }, + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const aRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/a', + context: ({ navigate, abortController }) => { + abandonedContextSignal = abortController.signal + if (!redirected) { + redirected = true + void navigate({ to: '/b' }) + } + return { fromA: true } + }, + }) + const bRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/b', + loader: bLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, aRoute, bRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const navigation = router.navigate({ to: '/a' }) + await vi.waitFor(() => expect(bLoader).toHaveBeenCalledTimes(1)) + + bLoaderGate.resolve() + await navigation + + expect(router.state.location.pathname).toBe('/b') + expect(router.state.matches.at(-1)?.routeId).toBe(bRoute.id) + expect(abandonedContextSignal?.aborted).toBe(true) + expect(retainedLoaderSignal?.aborted).toBe(false) + }) + + test('a context-failure lane stays live until its terminal generation is replaced', async () => { + const contextError = new Error('context failed after starting work') + + let contextSignal: AbortSignal | undefined + let contextWorkAborted = false + const safeLoader = vi.fn(() => 'safe data') + + const rootRoute = new BaseRootRoute({}) + const brokenRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/broken', + context: ({ abortController }) => { + contextSignal = abortController.signal + abortController.signal.addEventListener( + 'abort', + () => { + contextWorkAborted = true + }, + { once: true }, + ) + throw contextError + }, + }) + const safeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/safe', + loader: safeLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([brokenRoute, safeRoute]), + history: createMemoryHistory({ initialEntries: ['/broken'] }), + }) + + await router.load() + expect(contextSignal?.aborted).toBe(false) + expect(contextWorkAborted).toBe(false) + + await router.navigate({ to: '/safe' }) + expect(router.state.location.pathname).toBe('/safe') + expect(safeLoader).toHaveBeenCalledTimes(1) + expect(contextSignal?.aborted).toBe(true) + expect(contextWorkAborted).toBe(true) + }) + + test.each( + ([false, true] as const).flatMap((isServer) => [ + { + isServer, + thrownType: 'AbortSignal', + createThrownValue: (signal: AbortSignal) => signal, + }, + { + isServer, + thrownType: 'AbortError', + createThrownValue: () => + new DOMException('The operation was aborted.', 'AbortError'), + }, + ]), + )( + 'treats a user-thrown $thrownType in beforeLoad as an ordinary route error (isServer=$isServer)', + async ({ isServer, createThrownValue }) => { + let matchSignal: AbortSignal | undefined + let thrownValue: unknown + const rootRoute = new BaseRootRoute({}) + const brokenRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/broken', + beforeLoad: ({ abortController }) => { + matchSignal = abortController.signal + thrownValue = createThrownValue(matchSignal) + throw thrownValue + }, + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([brokenRoute]), + history: createMemoryHistory({ initialEntries: ['/broken'] }), + isServer, + }) + + if (isServer) { + const response = await loadServerResponse(router, '/broken') + expect(response.status).toBe(500) + } else { + await router.load() + } + + const match = router.state.matches.at(-1) + expect(matchSignal?.aborted).toBe(isServer) + expect(match).toMatchObject({ + routeId: brokenRoute.id, + status: 'error', + }) + expect(match?.error).toBe(thrownValue) + }, + ) + + test('a planning failure becomes the successor lane instead of stranding navigation', async () => { + const preflightError = new Error('next loaderDeps failed') + const pendingGate = createControlledPromise() + const brokenPreflightReached = createControlledPromise() + let pendingSignal: AbortSignal | undefined + const pendingStarted = createControlledPromise() + + const rootRoute = new BaseRootRoute({}) + const aRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/a', + }) + const pendingRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/pending', + pendingMs: 0, + pendingComponent: () => null, + loader: ({ abortController }) => { + pendingSignal = abortController.signal + pendingStarted.resolve() + return Promise.race([ + pendingGate, + abortAwareGate(abortController.signal), + ]) + }, + }) + const brokenRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/broken', + loaderDeps: (): Record => { + brokenPreflightReached.resolve() + throw preflightError + }, + loader: () => 'never runs', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([aRoute, pendingRoute, brokenRoute]), + history: createMemoryHistory({ initialEntries: ['/a'] }), + }) + + await router.load() + const pendingNavigation = router.navigate({ to: '/pending' }) + await pendingStarted + + const brokenNavigation = router.navigate({ to: '/broken' }) + await brokenPreflightReached + + await vi.waitFor(() => expect(pendingSignal?.aborted).toBe(true)) + + pendingGate.resolve() + await Promise.all([pendingNavigation, brokenNavigation]) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: brokenRoute.id, + status: 'error', + error: preflightError, + }) + expect(router.state.location.pathname).toBe('/broken') + }) +}) diff --git a/packages/router-core/tests/error-boundary-cache-generation.test.ts b/packages/router-core/tests/error-boundary-cache-generation.test.ts new file mode 100644 index 0000000000..447dace6ea --- /dev/null +++ b/packages/router-core/tests/error-boundary-cache-generation.test.ts @@ -0,0 +1,81 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('cache retention across an error-boundary commit', () => { + test('a superseded generation cannot shadow newer beyond-boundary data', async () => { + let parentFails = false + const parentGate = createControlledPromise() + const parentLoader = vi.fn(async () => { + if (parentFails) { + await parentGate + throw new Error('parent boom') + } + }) + let childVersion = 0 + let wantChildReload = false + const childLoader = vi.fn(() => ({ version: ++childVersion })) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/p', + loader: parentLoader, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: 'c', + shouldReload: () => wantChildReload, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/p/c'] }), + defaultStaleReloadMode: 'blocking', + }) + + await router.load() + expect(childLoader).toHaveBeenCalledTimes(1) + + // Same-destination refresh where the child settles a newer generation + // before the parent fails at the boundary. + parentFails = true + wantChildReload = true + const refresh = router.navigate({ to: '/p/c' }) + await vi.waitFor(() => expect(childLoader).toHaveBeenCalledTimes(2)) + parentGate.resolve() + await refresh + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ status: 'error' }) + + // Leave and return with reloads declined: the presented child data must + // be the newer generation, not the superseded one. + parentFails = false + wantChildReload = false + await router.navigate({ to: '/' }) + await router.navigate({ to: '/p/c' }) + + const child = router.state.matches.find( + (match) => match.routeId === childRoute.id, + ) + expect(childLoader).toHaveBeenCalledTimes(2) + expect(child).toMatchObject({ + status: 'success', + loaderData: { version: 2 }, + }) + }) +}) diff --git a/packages/router-core/tests/fatal-load-rejection.test.ts b/packages/router-core/tests/fatal-load-rejection.test.ts new file mode 100644 index 0000000000..bac78891b0 --- /dev/null +++ b/packages/router-core/tests/fatal-load-rejection.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + redirect, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * A genuinely fatal router rejection (not a route outcome — for example, + * redirect resolution throwing while finalizing a serial beforeLoad + * redirect) must settle without publishing matches whose load + * promises never settled: the serial redirect capped the loader prefix at 0, + * so the loader phase never ran for ancestor matches and their pending + * loadPromise would hang Suspense forever. + */ + +describe('fatal load rejection', () => { + test('fatal rejection during a serial-redirect-capped pass settles the lane', async () => { + const boom = new Error('resolveRedirect failed') + const errorComponentGate = createControlledPromise() + const errorComponentStarted = createControlledPromise() + const errorComponentPreload = vi.fn(() => { + errorComponentStarted.resolve() + return errorComponentGate + }) + const ErrorComponent = Object.assign(() => null, { + preload: errorComponentPreload, + }) + + const rootRoute = new BaseRootRoute({ + // Never runs: the serial redirect caps the loader prefix at 0. Its + // loadPromise (created for the beforeLoad phase) must still settle. + loader: () => 'root data', + errorComponent: ErrorComponent, + }) + const badRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/bad', + beforeLoad: () => { + throw redirect({ + to: '/bad', + search: () => { + throw boom + }, + }) + }, + }) + const safeLoader = vi.fn(() => 'safe data') + const safeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/safe', + loader: safeLoader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([badRoute, safeRoute]), + history: createMemoryHistory({ initialEntries: ['/bad'] }), + }) + + const load = router.load() + const outcome = await Promise.race([ + load.then(() => 'load-settled' as const), + errorComponentStarted.then(() => 'error-preload-started' as const), + ]) + if (outcome === 'error-preload-started') { + errorComponentGate.resolve() + } + expect(outcome).toBe('load-settled') + await load + expect(errorComponentPreload).not.toHaveBeenCalled() + expect(router.state.status).toBe('idle') + + errorComponentGate.resolve() + await router.navigate({ to: '/safe' }) + expect(router.state.location.pathname).toBe('/safe') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: safeRoute.id, + status: 'success', + loaderData: 'safe data', + }) + expect(safeLoader).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/router-core/tests/granular-stores.test.ts b/packages/router-core/tests/granular-stores.test.ts index 54d2a4efab..8d5b98755a 100644 --- a/packages/router-core/tests/granular-stores.test.ts +++ b/packages/router-core/tests/granular-stores.test.ts @@ -31,324 +31,33 @@ function createRouter() { }) } -function createLoaderRouter({ - initialEntries = ['/posts/123'], - staleTime = 0, -}: { - initialEntries?: Array - staleTime?: number -} = {}) { - let resolveLoader: (() => void) | undefined - let callCount = 0 - - const rootRoute = new BaseRootRoute({}) - - const indexRoute = new BaseRoute({ - getParentRoute: () => rootRoute, - path: '/', - }) - - const aboutRoute = new BaseRoute({ - getParentRoute: () => rootRoute, - path: '/about', - }) - - const postRoute = new BaseRoute({ - getParentRoute: () => rootRoute, - path: '/posts/$postId', - staleTime, - loader: () => { - callCount += 1 - - if (callCount === 1) { - return { version: 'initial' } - } - - return new Promise<{ version: string }>((resolve) => { - resolveLoader = () => resolve({ version: 'reloaded' }) - }) - }, - }) - - const routeTree = rootRoute.addChildren([indexRoute, aboutRoute, postRoute]) - - return { - router: createTestRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries, - }), - }), - resolveLoader: () => resolveLoader?.(), - } -} - describe('granular stores', () => { - test('keeps pool stores correct across active/pending/cached transitions', async () => { + test('keeps ordered route state current across match generations', async () => { const router = createRouter() await router.navigate({ to: '/posts/123' }) - const activeMatches = router.state.matches - - // Active pool contains all active matches with correct routeIds - expect(router.stores.matchesId.get()).toEqual( - activeMatches.map((match) => match.id), - ) - activeMatches.forEach((match) => { - const store = router.stores.matchStores.get(match.id) - expect(store).toBeDefined() - expect(store!.routeId).toBe(match.routeId) - // getRouteMatchStore resolves to the same state - expect(router.stores.getRouteMatchStore(match.routeId).get()).toBe( - store!.get(), - ) - }) - - const pendingMatches = [...activeMatches].reverse().map((match, index) => ({ - ...match, - id: `${match.id}__pending_${index}`, - })) - const cachedMatches = [...activeMatches].map((match, index) => ({ - ...match, - id: `${match.id}__cached_${index}`, - })) - - router.stores.setPending(pendingMatches) - router.stores.setCached(cachedMatches) - - expect(router.stores.matchesId.get()).toEqual( - activeMatches.map((match) => match.id), - ) - expect(router.stores.pendingIds.get()).toEqual( - pendingMatches.map((match) => match.id), - ) - expect(router.stores.cachedIds.get()).toEqual( - cachedMatches.map((match) => match.id), - ) - - // Pending pool has correct routeIds - pendingMatches.forEach((match) => { - const pendingStore = router.stores.pendingMatchStores.get(match.id) - expect(pendingStore).toBeDefined() - expect(pendingStore!.routeId).toBe(match.routeId) - // Pending match is NOT in the active pool - expect(router.stores.matchStores.get(match.id)).toBeUndefined() - // Active pool still has a match for this routeId - expect( - router.stores.getRouteMatchStore(match.routeId).get(), - ).toBeDefined() - }) - - const nextActiveMatches = activeMatches.map((match, index) => ({ - ...match, - id: `${match.id}__active_next_${index}`, - })) - router.stores.setMatches(nextActiveMatches) - - expect(router.stores.matchesId.get()).toEqual( - nextActiveMatches.map((match) => match.id), - ) - nextActiveMatches.forEach((match) => { - const store = router.stores.matchStores.get(match.id) - expect(store).toBeDefined() - expect(store!.routeId).toBe(match.routeId) - expect(router.stores.getRouteMatchStore(match.routeId).get()).toBe( - store!.get(), - ) - }) - }) - - test('match store updates are isolated to the touched active match', async () => { - const router = createRouter() - await router.navigate({ to: '/posts/123' }) - - const rootMatch = router.state.matches[0] - const leafMatch = router.state.matches[1] - - expect(rootMatch).toBeDefined() - expect(leafMatch).toBeDefined() - - if (!rootMatch || !leafMatch) { - throw new Error('Expected root and leaf matches to exist') - } - - const rootStore = router.stores.matchStores.get(rootMatch.id) - const leafStore = router.stores.matchStores.get(leafMatch.id) - - expect(rootStore).toBeDefined() - expect(leafStore).toBeDefined() - - if (!rootStore || !leafStore) { - throw new Error('Expected root and leaf match stores to exist') - } - - const rootBefore = rootStore.get() - const leafBefore = leafStore.get() + expect(router.state.location.pathname).toBe('/posts/123') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + '__root__', + '/posts/$postId', + ]) + expect(router.state.matches.at(-1)?.params).toMatchObject({ postId: '123' }) - router.updateMatch(leafMatch.id, (prev) => ({ - ...prev, - status: 'pending', - })) + await router.navigate({ to: '/posts/456' }) - expect(rootStore.get()).toBe(rootBefore) - expect(leafStore.get()).not.toBe(leafBefore) - expect(leafStore.get().status).toBe('pending') - }) - - test('getRouteMatchStore caches store instances and clears when route is inactive', async () => { - const router = createRouter() - await router.navigate({ to: '/posts/123' }) - - const postsStore = router.stores.getRouteMatchStore('/posts/$postId') - - expect(router.stores.getRouteMatchStore('/posts/$postId')).toBe(postsStore) - expect(postsStore.get()?.routeId).toBe('/posts/$postId') + expect(router.state.location.pathname).toBe('/posts/456') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + '__root__', + '/posts/$postId', + ]) + expect(router.state.matches.at(-1)?.params).toMatchObject({ postId: '456' }) await router.navigate({ to: '/about' }) - expect(router.stores.getRouteMatchStore('/posts/$postId')).toBe(postsStore) - expect(postsStore.get()).toBeUndefined() - }) - - test('no-op match pool reconciliation preserves ids and match state references', async () => { - const router = createRouter() - await router.navigate({ to: '/posts/123' }) - - const activeMatches = router.state.matches - const activeIdsBefore = router.stores.matchesId.get() - const activeStoresBefore = activeMatches.map((match) => - router.stores.matchStores.get(match.id), - ) - const activeStatesBefore = activeStoresBefore.map((store) => store?.get()) - - router.stores.setMatches(activeMatches) - - expect(router.stores.matchesId.get()).toBe(activeIdsBefore) - expect(router.stores.matches.get()).toBe(activeMatches) - for (let i = 0; i < activeMatches.length; i++) { - const match = activeMatches[i]! - const store = router.stores.matchStores.get(match.id) - expect(store).toBe(activeStoresBefore[i]) - expect(store?.get()).toBe(activeStatesBefore[i]) - } - - const pendingMatches = activeMatches.map((match) => ({ - ...match, - id: `${match.id}__pending`, - status: 'pending' as const, - })) - - router.stores.setPending(pendingMatches) - - const pendingIdsBefore = router.stores.pendingIds.get() - const pendingStoresBefore = pendingMatches.map((match) => - router.stores.pendingMatchStores.get(match.id), - ) - const pendingStatesBefore = pendingStoresBefore.map((store) => store?.get()) - - router.stores.setPending(pendingMatches) - - expect(router.stores.pendingIds.get()).toBe(pendingIdsBefore) - for (let i = 0; i < pendingMatches.length; i++) { - const match = pendingMatches[i]! - const store = router.stores.pendingMatchStores.get(match.id) - expect(store).toBe(pendingStoresBefore[i]) - expect(store?.get()).toBe(pendingStatesBefore[i]) - } - }) - - test('updateMatch prefers the pending pool when active and pending share an id', async () => { - const { router, resolveLoader } = createLoaderRouter() - - await router.load() - - const activeLeaf = router.state.matches[1] - - expect(activeLeaf).toBeDefined() - - if (!activeLeaf) { - throw new Error('Expected active leaf match to exist') - } - - const activeStore = router.stores.matchStores.get(activeLeaf.id) - - expect(activeStore).toBeDefined() - - if (!activeStore) { - throw new Error('Expected active leaf store to exist') - } - - const activeBefore = activeStore.get() - - const reloadPromise = router.load() - await Promise.resolve() - - const pendingStore = router.stores.pendingMatchStores.get(activeLeaf.id) - - expect(pendingStore).toBeDefined() - expect(router.stores.matchStores.get(activeLeaf.id)).toBe(activeStore) - - if (!pendingStore) { - throw new Error('Expected pending leaf store to exist') - } - - router.updateMatch(activeLeaf.id, (prev) => ({ - ...prev, - status: 'error', - error: new Error('pending-only-update'), - })) - - expect(activeStore.get()).toBe(activeBefore) - expect(activeStore.get().status).toBe('success') - expect(pendingStore.get().status).toBe('error') - expect(pendingStore.get().error).toEqual(new Error('pending-only-update')) - - resolveLoader() - await reloadPromise - }) - - test('supports duplicate ids across pools without cross-pool contamination', async () => { - const router = createRouter() - await router.navigate({ to: '/posts/123' }) - - const activeLeaf = router.state.matches[1]! - const duplicatedId = activeLeaf.id - const pendingDuplicate = { - ...activeLeaf, - status: 'pending' as const, - } - const cachedDuplicate = { - ...activeLeaf, - status: 'success' as const, - } - - router.stores.setPending([pendingDuplicate]) - router.stores.setCached([cachedDuplicate]) - - router.stores.setMatches( - router.state.matches.map((match) => - match.id === duplicatedId - ? { - ...match, - status: 'error' as const, - error: new Error('active-only-update'), - } - : match, - ), - ) - - expect(router.stores.matchStores.get(duplicatedId)?.get().status).toBe( - 'error', - ) - expect( - router.stores.getRouteMatchStore(activeLeaf.routeId).get()?.status, - ).toBe('error') - // Pending pool has its own store for this id - expect( - router.stores.pendingMatchStores.get(duplicatedId)?.get().status, - ).toBe('pending') - expect(router.stores.pendingMatches.get()[0]?.status).toBe('pending') - expect(router.stores.cachedMatches.get()[0]?.status).toBe('success') - expect(router.getMatch(duplicatedId)?.status).toBe('success') + expect(router.state.location.pathname).toBe('/about') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + '__root__', + '/about', + ]) }) }) diff --git a/packages/router-core/tests/hmr-refresh-lifecycle.test.ts b/packages/router-core/tests/hmr-refresh-lifecycle.test.ts new file mode 100644 index 0000000000..ad7c743bf3 --- /dev/null +++ b/packages/router-core/tests/hmr-refresh-lifecycle.test.ts @@ -0,0 +1,466 @@ +import { + afterEach, + beforeEach, + describe, + expect, + onTestFinished, + test, + vi, +} from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + redirect, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +beforeEach(() => { + vi.spyOn(window, 'scrollTo').mockImplementation(() => {}) +}) + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('HMR route refresh', () => { + test('reloads retained routes with onStay lifecycle semantics', async () => { + let generation = 1 + const loader = vi.fn(() => generation) + const onEnter = vi.fn() + const onLeave = vi.fn() + const order: Array = [] + const onStay = vi.fn(() => order.push('onStay')) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader, + onEnter, + onLeave, + onStay, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const unsubLoad = router.subscribe('onLoad', () => order.push('onLoad')) + const unsubMount = router.subscribe('onBeforeRouteMount', () => + order.push('onBeforeRouteMount'), + ) + generation = 2 + await router._refreshRoute!() + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe(2) + expect(onEnter).toHaveBeenCalledTimes(1) + expect(onLeave).not.toHaveBeenCalled() + expect(onStay).toHaveBeenCalledTimes(1) + expect(order).toEqual(['onStay', 'onLoad', 'onBeforeRouteMount']) + unsubLoad() + unsubMount() + }) + + test('retires refresh mode after an acknowledged publication', async () => { + const otherLoader = vi.fn(() => 'other data') + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + loader: otherLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, otherRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + await router._refreshRoute!() + await router.preloadRoute({ to: '/other' }) + + expect(router._tx?.[6]).toBeUndefined() + expect(otherLoader).toHaveBeenCalledOnce() + }) + + test('passes rematerialization to a reentrant preflight load', async () => { + let generation = 1 + const loader = vi.fn(() => generation) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + generation = 2 + let reenter = true + let reentrantLoad: Promise | undefined + const unsubscribe = router.subscribe('onBeforeNavigate', () => { + if (reenter) { + reenter = false + reentrantLoad = router.load({ sync: true }) + } + }) + onTestFinished(unsubscribe) + + await router._refreshRoute!() + await reentrantLoad + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe(2) + }) + + test('passes rematerialization to a load that supersedes refresh work', async () => { + let generation = 1 + let reenter = false + let reentrantLoad: Promise | undefined + let router!: ReturnType + const loader = vi.fn(() => { + if (reenter) { + reenter = false + reentrantLoad = router.load({ sync: true }) + } + return generation + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + loader, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + generation = 2 + reenter = true + await router._refreshRoute!() + await reentrantLoad + + expect(loader).toHaveBeenCalledTimes(3) + expect(router.state.matches.at(-1)?.loaderData).toBe(2) + }) + + test('passes rematerialization through a refresh redirect', async () => { + let generation = 1 + let shouldRedirect = false + const rootLoader = vi.fn(() => generation) + const rootRoute = new BaseRootRoute({ + staleTime: Infinity, + loader: rootLoader, + }) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + beforeLoad: () => { + if (shouldRedirect) { + throw redirect({ to: '/target' }) + } + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/source'] }), + }) + + await router.load() + generation = 2 + shouldRedirect = true + await router._refreshRoute!() + + expect(router.state.location.pathname).toBe('/target') + expect(rootLoader).toHaveBeenCalledTimes(2) + expect(router.state.matches[0]?.loaderData).toBe(2) + }) + + test('restores the committed presentation when publication fails', async () => { + let generation = 1 + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => generation, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const previousMatches = router.state.matches + generation = 2 + const startTransition = router.startTransition + router.startTransition = async (fn) => { + fn() + router.clearCache() + throw new Error('render failed') + } + + await router._refreshRoute!() + + expect(router.state.status).toBe('idle') + expect(router.state.matches).toHaveLength(previousMatches.length) + expect(router.state.matches.at(-1)?.loaderData).toBe(1) + + router.startTransition = startTransition + generation = 3 + await router._refreshRoute!() + expect(router.state.matches.at(-1)?.loaderData).toBe(3) + }) + + test('keeps the rendered generation alive until refresh is acknowledged', async () => { + let generation = 1 + const signals: Array = [] + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: ({ abortController }) => { + signals.push(abortController.signal) + return generation + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const renderedSignal = signals[0]! + generation = 2 + const startTransition = router.startTransition + router.startTransition = async (fn) => { + fn() + throw new Error('render failed') + } + + await router._refreshRoute!() + + expect(renderedSignal.aborted).toBe(false) + expect(signals[1]?.aborted).toBe(true) + expect(router.state.matches.at(-1)?.loaderData).toBe(1) + + router.startTransition = startTransition + generation = 3 + await router._refreshRoute!() + expect(renderedSignal.aborted).toBe(true) + expect(router.state.matches.at(-1)?.loaderData).toBe(3) + }) + + test('rolls overlapping refreshes back to the last acknowledged generation', async () => { + let generation = 1 + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => generation, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + let transitionCount = 0 + let supersedeFirst!: () => void + router.startTransition = (fn) => { + transitionCount++ + fn() + if (transitionCount === 1) { + return new Promise((resolve) => { + supersedeFirst = () => resolve(false) + }) + } + return Promise.reject(new Error('replacement render failed')) + } + ;( + router as typeof router & { _cancelTransition?: () => void } + )._cancelTransition = () => supersedeFirst() + + generation = 2 + const firstRefresh = router._refreshRoute!() + await vi.waitFor(() => expect(transitionCount).toBe(1)) + + generation = 3 + const secondRefresh = router._refreshRoute!() + await Promise.all([firstRefresh, secondRefresh]) + + expect(router.state.status).toBe('idle') + expect(router.state.matches.at(-1)?.loaderData).toBe(1) + }) + + test('does not resolve a superseding navigation promise during rollback', async () => { + let generation = 1 + const destinationGate = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => generation, + }) + const destinationRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/destination', + loader: () => destinationGate, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, destinationRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + let transitionCount = 0 + let cancelRefresh!: () => void + router.startTransition = (fn) => { + transitionCount++ + fn() + if (transitionCount === 1) { + return new Promise((resolve) => { + cancelRefresh = () => resolve(false) + }) + } + return Promise.resolve(true) + } + ;( + router as typeof router & { _cancelTransition?: () => void } + )._cancelTransition = () => cancelRefresh() + + generation = 2 + const refresh = router._refreshRoute!() + await vi.waitFor(() => expect(transitionCount).toBe(1)) + + const navigationSettled = vi.fn() + const navigation = router + .navigate({ to: '/destination' }) + .then(navigationSettled) + await vi.waitFor(() => + expect(router.state.location.pathname).toBe('/destination'), + ) + expect(navigationSettled).not.toHaveBeenCalled() + + destinationGate.resolve() + await Promise.all([refresh, navigation]) + expect(navigationSettled).toHaveBeenCalledOnce() + expect(router.state.matches.at(-1)?.routeId).toBe(destinationRoute.id) + }) + + test('waits for an ordinary pending navigation before refreshing', async () => { + const destinationGate = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + }) + const destinationRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/destination', + pendingMs: 0, + pendingMinMs: 0, + pendingComponent: () => null, + loader: () => destinationGate, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, destinationRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const navigation = router.navigate({ to: '/destination' }) + await vi.waitFor(() => { + expect(router.state.status).toBe('pending') + expect(router.state.matches.at(-1)?.routeId).toBe(destinationRoute.id) + }) + destinationRoute.options.onStay = () => { + throw new Error('refreshed destination failed') + } + const refreshSettled = vi.fn() + const refresh = router._refreshRoute!().then(refreshSettled) + await Promise.resolve() + expect(refreshSettled).not.toHaveBeenCalled() + + destinationGate.resolve() + await Promise.all([navigation, refresh]) + + expect(router.state.status).toBe('idle') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: destinationRoute.id, + status: 'success', + }) + expect(refreshSettled).toHaveBeenCalledOnce() + }) + + test('rolls back when an HMR lifecycle callback throws', async () => { + let generation = 1 + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => generation, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + generation = 2 + pageRoute.options.onStay = () => { + throw new Error('lifecycle failed') + } + + await router._refreshRoute!() + + expect(router.state.status).toBe('idle') + expect(router.state.matches.at(-1)?.loaderData).toBe(1) + + pageRoute.options.onStay = undefined + generation = 3 + await router._refreshRoute!() + expect(router.state.matches.at(-1)?.loaderData).toBe(3) + }) + + test('does not adopt a preload created by HMR preflight hooks', async () => { + let generation = 1 + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => generation, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + generation = 2 + const unsubscribe = router.subscribe('onBeforeLoad', () => { + void router.preloadRoute({ to: '/page' }) + }) + onTestFinished(unsubscribe) + + await router._refreshRoute!() + + expect(router.state.matches.at(-1)?.loaderData).toBe(2) + }) +}) diff --git a/packages/router-core/tests/hydrate.test.ts b/packages/router-core/tests/hydrate.test.ts index efac230e49..7ecec4a4e5 100644 --- a/packages/router-core/tests/hydrate.test.ts +++ b/packages/router-core/tests/hydrate.test.ts @@ -1,35 +1,82 @@ +import { runInNewContext } from 'node:vm' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createMemoryHistory } from '@tanstack/history' -import { BaseRootRoute, BaseRoute, notFound } from '../src' +import { + BaseRootRoute, + BaseRoute, + _getAssetMatches, + createSerializationAdapter, + isNotFound, + notFound, +} from '../src' import { hydrate } from '../src/ssr/client' -import { createTestRouter } from './routerTestUtils' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' import { dehydrateSsrMatchId } from '../src/ssr/ssr-match-id' -import type { LocationRewrite } from '../src' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouteMatch, AnyRouter, LocationRewrite } from '../src' +import type { ServerManifest } from '../src/manifest' import type { TsrSsrGlobal } from '../src/ssr/types' -import type { AnyRouteMatch } from '../src' -import type { Manifest } from '../src/manifest' -const testManifest: Manifest = { +const testManifest: ServerManifest = { routes: {}, } +function createMockBootstrap( + router?: NonNullable, +): TsrSsrGlobal { + return { + router, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } +} + +async function dehydrateToBootstrap( + router: AnyRouter, + manifest: ServerManifest = testManifest, +): Promise { + attachRouterServerSsrUtils({ router, manifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { + currentScript: { + remove() {}, + }, + }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + describe('hydrate', () => { let mockWindow: { $_TSR?: TsrSsrGlobal } let mockRouter: any let mockHead: any beforeEach(() => { - // Reset global window mock mockWindow = {} - ;(global as any).window = mockWindow + vi.stubGlobal('window', mockWindow) - // Reset mock head function mockHead = vi.fn() const history = createMemoryHistory({ initialEntries: ['/'] }) - const rootRoute = new BaseRootRoute({}) - const indexRoute = new BaseRoute({ getParentRoute: () => rootRoute, path: '/', @@ -38,378 +85,582 @@ describe('hydrate', () => { head: mockHead, }) - const otherRoute = new BaseRoute({ - getParentRoute: () => indexRoute, - path: '/other', - component: () => 'Other', + mockRouter = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history, + isServer: false, }) - - const routeTree = rootRoute.addChildren([ - indexRoute.addChildren([otherRoute]), - ]) - - mockRouter = createTestRouter({ routeTree, history, isServer: true }) }) afterEach(() => { - vi.resetAllMocks() - delete (global as any).window + vi.restoreAllMocks() + vi.unstubAllGlobals() }) - it('should throw error if window.$_TSR is not available', async () => { - await expect(hydrate(mockRouter)).rejects.toThrow( + it.each([ + [ + 'window.$_TSR', + () => {}, 'Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!', - ) - }) + ], + [ + 'window.$_TSR.router', + () => { + mockWindow.$_TSR = createMockBootstrap() + }, + 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', + ], + ])( + 'throws a diagnostic when %s is unavailable', + async (_, setup, message) => { + setup() + + await expect(hydrate(mockRouter)).rejects.toThrow(message) + }, + ) + + it('clears hydration preflight when the custom hydrate hook rejects', async () => { + const error = new Error('custom hydration failed') + mockWindow.$_TSR = createMockBootstrap({ + manifest: testManifest, + dehydratedData: {}, + matches: [], + }) + mockRouter.options.hydrate = () => Promise.reject(error) - it('should throw error if window.$_TSR.router is not available', async () => { - mockWindow.$_TSR = { - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - // router is missing - } as any + await expect(hydrate(mockRouter)).rejects.toBe(error) - await expect(hydrate(mockRouter)).rejects.toThrow( - 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!', - ) + expect(mockRouter._preflight).toBeUndefined() }) - it('should initialize serialization adapters when provided', async () => { - const mockSerializer = { - key: 'testAdapter', - fromSerializable: vi.fn(), - toSerializable: vi.fn(), - test: vi.fn().mockReturnValue(true), - '~types': { - input: {}, - output: {}, - extends: {}, - }, + it('clears hydration preflight when location reconstruction rejects', async () => { + const error = new Error('location parsing failed') + mockWindow.$_TSR = createMockBootstrap({ + manifest: testManifest, + dehydratedData: {}, + matches: [], + }) + mockRouter.options.hydrate = () => { + mockRouter.options.parseSearch = () => { + throw error + } } - mockRouter.options.serializationAdapters = [mockSerializer] + await expect(hydrate(mockRouter)).rejects.toBe(error) - const mockMatches = [{ id: '/', routeId: '/', index: 0, _nonReactive: {} }] - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches + expect(mockRouter._preflight).toBeUndefined() + }) - const mockBuffer = [vi.fn(), vi.fn()] - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: [], + it('round-trips the manifest and matches without running client loaders', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverProductRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/products/$productId', + loader: ({ params }) => ({ + productId: params.productId, + source: 'server', + }), + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverProductRoute]), + history: createMemoryHistory({ initialEntries: ['/products/42'] }), + isServer: true, + }) + const manifest: ServerManifest = { + routes: { + [serverRootRoute.id]: { preloads: ['/assets/root.js'] }, + [serverProductRoute.id]: { preloads: ['/assets/product.js'] }, }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: mockBuffer, - initialized: false, } - await hydrate(mockRouter) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter, manifest) + + const serverMatches = serverRouter.state.matches + const clientLoader = vi.fn(() => ({ + productId: 'client', + source: 'client', + })) + const clientRootRoute = new BaseRootRoute({}) + const clientProductRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/products/$productId', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientProductRoute]), + history: createMemoryHistory({ initialEntries: ['/products/42'] }), + isServer: false, + }) + + await hydrate(clientRouter) - expect(mockWindow.$_TSR.t).toBeInstanceOf(Map) - expect(mockWindow.$_TSR.t?.get('testAdapter')).toBe( - mockSerializer.fromSerializable, + expect(clientRouter.ssr?.manifest).toEqual(manifest) + expect( + clientRouter.state.matches.map((match) => ({ + id: match.id, + routeId: match.routeId, + status: match.status, + loaderData: match.loaderData, + ssr: match.ssr, + })), + ).toEqual( + serverMatches.map((match) => ({ + id: match.id, + routeId: match.routeId, + status: match.status, + loaderData: match.loaderData, + ssr: match.ssr, + })), ) - expect(mockBuffer[0]).toHaveBeenCalled() - expect(mockBuffer[1]).toHaveBeenCalled() - expect(mockWindow.$_TSR.initialized).toBe(true) + expect(clientRouter.state.matches.at(-1)?.loaderData).toEqual({ + productId: '42', + source: 'server', + }) + expect(clientLoader).not.toHaveBeenCalled() }) - it('should handle empty serialization adapters', async () => { - mockRouter.options.serializationAdapters = [] - - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: [], - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) + it('restores undefined loader data without serializing it', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/page', + loader: () => undefined, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: true, + }) - expect(mockWindow.$_TSR.t).toBeUndefined() - expect(mockWindow.$_TSR.initialized).toBe(true) - }) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + expect(mockWindow.$_TSR.router?.matches[1]).not.toHaveProperty('l') - it('should set manifest in router.ssr', async () => { - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: [], - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + const clientLoader = vi.fn(() => undefined) + const clientRootRoute = new BaseRootRoute({}) + const clientRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/page', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) - await hydrate(mockRouter) + await hydrate(clientRouter) - expect(mockRouter.ssr).toEqual({ - manifest: testManifest, + expect(clientLoader).not.toHaveBeenCalled() + const match = clientRouter.state.matches[1] + expect(match).toMatchObject({ + routeId: clientRoute.id, + status: 'success', }) + expect(match).toHaveProperty('loaderData', undefined) }) - it('should hydrate matches', async () => { - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, + it('preserves successful undefined loader data when selecting a later failure', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + loader: () => undefined, + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + expect(mockWindow.$_TSR.router?.matches[1]).not.toHaveProperty('l') + + const parentError = new Error('parent reload failed') + const childError = new Error('child guard failed') + const clientRootRoute = new BaseRootRoute({}) + const clientParentRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/parent', + loader: () => { + throw parentError }, - { - id: '/other', - routeId: '/other', - index: 1, - ssr: undefined, - _nonReactive: {}, + errorComponent: () => null, + }) + const clientChildRoute = new BaseRoute({ + getParentRoute: () => clientParentRoute, + path: '/child', + beforeLoad: () => { + throw childError }, - ] + errorComponent: () => null, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([ + clientParentRoute.addChildren([clientChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) - const dehydratedMatches = [ - { - i: '/', - l: { indexData: 'server-data' }, - s: 'success' as const, - ssr: true, - u: Date.now(), - }, - ] + await hydrate(clientRouter) + await clientRouter.invalidate() - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches + expect( + clientRouter.state.matches.find( + (match) => match.routeId === clientParentRoute.id, + ), + ).toMatchObject({ status: 'success', error: undefined }) + expect( + clientRouter.state.matches.find( + (match) => match.routeId === clientChildRoute.id, + ), + ).toMatchObject({ status: 'error', error: childError }) + }) - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: dehydratedMatches, - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + it.each([ + ['mismatched', dehydrateSsrMatchId('/different-match')], + ['missing', undefined], + ['non-string', 42], + ])( + 'does not attach dehydrated data with a %s match identity', + async (_, identity) => { + const serverRootRoute = new BaseRootRoute({}) + const serverProductRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/products/$productId', + loader: () => 'server data', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverProductRoute]), + history: createMemoryHistory({ initialEntries: ['/products/42'] }), + isServer: true, + }) + + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + mockWindow.$_TSR.router!.matches[1]!.i = identity as string + + const clientLoader = vi.fn(() => 'client data') + const clientRootRoute = new BaseRootRoute({}) + const clientProductRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/products/$productId', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientProductRoute]), + history: createMemoryHistory({ initialEntries: ['/products/42'] }), + isServer: false, + }) + + await hydrate(clientRouter) + + const pendingMatch = clientRouter.state.matches.at(-1) + expect(pendingMatch).toMatchObject({ + routeId: clientProductRoute.id, + status: 'pending', + }) + expect(pendingMatch).not.toHaveProperty('loaderData') + expect(clientLoader).not.toHaveBeenCalled() + + await clientRouter.load() + + expect(clientRouter.state.matches.at(-1)).toMatchObject({ + routeId: clientProductRoute.id, + status: 'success', + loaderData: 'client data', + }) + expect(clientLoader).toHaveBeenCalledTimes(1) + }, + ) + + it('keeps an earlier data-only boundary when a descendant id mismatches', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr: 'data-only', + loader: () => 'server parent data', + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + loader: () => 'server child data', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) - await hydrate(mockRouter) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + mockWindow.$_TSR.router!.matches[2]!.i = dehydrateSsrMatchId( + '/different-child-match', + ) + + const childContext = vi.fn(() => ({ source: 'client child context' })) + const clientRootRoute = new BaseRootRoute({}) + const clientParentLoader = vi.fn(() => 'client parent data') + const clientParentRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/parent', + ssr: 'data-only', + loader: clientParentLoader, + }) + const clientChildRoute = new BaseRoute({ + getParentRoute: () => clientParentRoute, + path: '/child', + context: childContext, + loader: () => 'client child data', + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([ + clientParentRoute.addChildren([clientChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) + + await hydrate(clientRouter) - const { id, loaderData, ssr, status } = mockMatches[0] as AnyRouteMatch - expect(id).toBe('/') - expect(loaderData).toEqual({ indexData: 'server-data' }) - expect(status).toBe('success') - expect(ssr).toBe(true) + expect(clientRouter.state.matches[1]).toMatchObject({ + routeId: clientParentRoute.id, + status: 'pending', + loaderData: 'server parent data', + }) + expect(childContext).not.toHaveBeenCalled() + expect(clientParentLoader).not.toHaveBeenCalled() + expect( + _getAssetMatches(clientRouter.state.matches).map( + (match) => match.routeId, + ), + ).toEqual([clientRootRoute.id, clientParentRoute.id]) + + await clientRouter.load() + + expect(childContext).toHaveBeenCalledTimes(1) + expect(clientParentLoader).not.toHaveBeenCalled() + expect(clientRouter.state.matches[1]).toMatchObject({ + routeId: clientParentRoute.id, + status: 'success', + loaderData: 'server parent data', + }) + expect(clientRouter.state.matches[2]).toMatchObject({ + routeId: clientChildRoute.id, + status: 'success', + loaderData: 'client child data', + }) }) - it('should hydrate globalNotFound when dehydrated flag is present', async () => { - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, - }, - ] + it('rejects a longer non-terminal server lane before attaching its data', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + loader: () => 'server data', + }) + const serverIndexRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverIndexRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + isServer: true, + }) - const dehydratedMatches = [ - { - i: '/', - s: 'success' as const, - ssr: true, - u: Date.now(), - g: true as const, - }, - ] + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + expect(mockWindow.$_TSR.router?.matches).toHaveLength(3) + + const clientLoader = vi.fn(() => 'client data') + const clientRootRoute = new BaseRootRoute({}) + const clientParentRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/parent', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientParentRoute]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + isServer: false, + }) - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches + await hydrate(clientRouter) - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: dehydratedMatches, - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + expect(clientRouter.state.resolvedLocation).toBeUndefined() + expect(clientRouter.state.matches.at(-1)).not.toHaveProperty('loaderData') + expect(clientLoader).not.toHaveBeenCalled() - await hydrate(mockRouter) + await clientRouter.load() - expect((mockMatches[0] as AnyRouteMatch).globalNotFound).toBe(true) + expect(clientRouter.state.matches.at(-1)).toMatchObject({ + routeId: clientParentRoute.id, + status: 'success', + loaderData: 'client data', + }) + expect(clientLoader).toHaveBeenCalledTimes(1) }) - it('should leave globalNotFound undefined when dehydrated flag is omitted', async () => { - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, - }, - ] + it('round-trips adapted loader data through the bootstrap protocol', async () => { + class AdaptedValue { + constructor(readonly value: string) {} + } - const dehydratedMatches = [ - { - i: '/', - s: 'success' as const, - ssr: true, - u: Date.now(), - }, - ] + const adapter = createSerializationAdapter({ + key: 'adapted-value', + test: (value: unknown): value is AdaptedValue => + value instanceof AdaptedValue, + toSerializable: (value: AdaptedValue) => value.value, + fromSerializable: (value: string) => new AdaptedValue(value), + }) - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches + const serverRootRoute = new BaseRootRoute({}) + const serverIndexRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/', + loader: () => new AdaptedValue('server loader data'), + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverIndexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + isServer: true, + }) + serverRouter.options.serializationAdapters = [adapter] - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: dehydratedMatches, - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) - await hydrate(mockRouter) + const clientLoader = vi.fn(() => new AdaptedValue('client loader data')) + const clientRootRoute = new BaseRootRoute({}) + const clientIndexRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientIndexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + isServer: false, + }) + clientRouter.options.serializationAdapters = [adapter] + + await hydrate(clientRouter) - expect((mockMatches[0] as AnyRouteMatch).globalNotFound).toBeUndefined() + const loaderData = clientRouter.state.matches.at(-1)?.loaderData + expect(loaderData).toBeInstanceOf(AdaptedValue) + expect(loaderData).toEqual(new AdaptedValue('server loader data')) + expect(clientLoader).not.toHaveBeenCalled() }) - it('should preserve existing globalNotFound when dehydrated flag is omitted', async () => { - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, - globalNotFound: true, + it('round-trips a server root notFound lane as _notFound', async () => { + const serverRootRoute = new BaseRootRoute({ + notFoundComponent: () => 'Not Found', + }) + const serverMissingRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/missing', + loader: () => { + throw notFound({ data: { source: 'server loader' } }) }, - ] + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverMissingRoute]), + history: createMemoryHistory({ initialEntries: ['/missing'] }), + isServer: true, + }) - const dehydratedMatches = [ - { - i: '/', - s: 'success' as const, - ssr: true, - u: Date.now(), - }, - ] + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches + expect(serverRouter.state.matches).toHaveLength(2) + expect(serverRouter.state.matches[0]).toMatchObject({ + routeId: serverRootRoute.id, + status: 'success', + _notFound: true, + }) + expect(isNotFound(serverRouter.state.matches[0]?.error)).toBe(true) - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: dehydratedMatches, - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + const clientLoader = vi.fn(() => 'client data') + const clientRootRoute = new BaseRootRoute({ + notFoundComponent: () => 'Not Found', + }) + const clientMissingRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/missing', + loader: clientLoader, + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientMissingRoute]), + history: createMemoryHistory({ initialEntries: ['/missing'] }), + isServer: false, + }) - await hydrate(mockRouter) + await hydrate(clientRouter) - expect((mockMatches[0] as AnyRouteMatch).globalNotFound).toBe(true) + expect(clientRouter.state.matches).toHaveLength(2) + expect(clientRouter.state.matches[0]).toMatchObject({ + routeId: clientRootRoute.id, + status: 'success', + _notFound: true, + }) + expect(clientRouter.state.matches[1]).toMatchObject({ + routeId: clientMissingRoute.id, + status: 'pending', + }) + expect(isNotFound(clientRouter.state.matches[0]?.error)).toBe(true) + expect(clientRouter.state.matches[0]?.error).toEqual( + expect.objectContaining({ data: { source: 'server loader' } }), + ) + expect(clientLoader).not.toHaveBeenCalled() }) - it('should decode dehydrated match ids before hydration lookup and SPA-mode checks', async () => { - const loadSpy = vi.spyOn(mockRouter, 'load') - - const mockMatches = [ - { - id: '/', - routeId: '/', - index: 0, - ssr: undefined, - _nonReactive: {}, - }, - ] - - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches - - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: dehydrateSsrMatchId('/'), - matches: [ - { - i: dehydrateSsrMatchId('/'), - l: { indexData: 'server-data' }, - s: 'success', - ssr: true, - u: Date.now(), - }, - ], - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } - - await hydrate(mockRouter) + it('preserves a client-matched _notFound when a shell payload omits the flag', async () => { + const serverRootRoute = new BaseRootRoute({ + notFoundComponent: () => 'Not Found', + }) + const serverKnownRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/known', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverKnownRoute]), + history: createMemoryHistory({ initialEntries: ['/known'] }), + isServer: true, + isShell: true, + }) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) - expect(loadSpy).not.toHaveBeenCalled() - expect((mockRouter.state.matches[0] as AnyRouteMatch).id).toBe('/') - }) + const clientRootRoute = new BaseRootRoute({ + notFoundComponent: () => 'Not Found', + }) + const clientKnownRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/known', + }) + const clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientKnownRoute]), + history: createMemoryHistory({ initialEntries: ['/does-not-exist'] }), + isServer: false, + }) - it('should run custom hydration before matching routes', async () => { - const history = createMemoryHistory({ initialEntries: ['/public'] }) + await hydrate(clientRouter) - const rootRoute = new BaseRootRoute({}) - const internalRoute = new BaseRoute({ - getParentRoute: () => rootRoute, - path: '/internal', - component: () => 'Internal', + expect(clientRouter.state.matches).toHaveLength(1) + expect(clientRouter.state.matches[0]).toMatchObject({ + routeId: clientRootRoute.id, + status: 'success', + _notFound: true, }) + }) + it('applies round-tripped custom hydration before matching a rewritten location', async () => { const rewrite: LocationRewrite = { input: ({ url }) => { if (url.pathname === '/public') { @@ -417,104 +668,109 @@ describe('hydrate', () => { } return url }, + output: ({ url }) => { + if (url.pathname === '/internal') { + url.pathname = '/public' + } + return url + }, } - mockRouter = createTestRouter({ - routeTree: rootRoute.addChildren([internalRoute]), - history, + const serverRootRoute = new BaseRootRoute({}) + const serverInternalRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/internal', + loader: () => 'server internal data', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverInternalRoute]), + history: createMemoryHistory({ initialEntries: ['/public'] }), + rewrite, + dehydrate: () => ({ rewrite: true }), isServer: true, - hydrate: (dehydrated: { rewrite?: boolean }) => { - if (dehydrated.rewrite) { - mockRouter.update({ rewrite }) - } - }, }) - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: { rewrite: true }, - lastMatchId: '/internal/internal', - matches: [ - { - i: '__root__/', - s: 'success', - ssr: true, - u: Date.now(), - }, - { - i: '/internal/internal', - s: 'success', - ssr: true, - u: Date.now(), - }, - ], - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) - await hydrate(mockRouter) + const clientLoader = vi.fn(() => 'client internal data') + const clientRootRoute = new BaseRootRoute({}) + const clientInternalRoute = new BaseRoute({ + getParentRoute: () => clientRootRoute, + path: '/internal', + loader: clientLoader, + }) + let clientRouter: AnyRouter + const customHydrate = vi.fn((dehydrated: { rewrite?: boolean }) => { + if (dehydrated.rewrite) { + clientRouter.update({ rewrite }) + } + }) + clientRouter = createTestRouter({ + routeTree: clientRootRoute.addChildren([clientInternalRoute]), + history: createMemoryHistory({ initialEntries: ['/public'] }), + hydrate: customHydrate, + isServer: false, + }) + + await hydrate(clientRouter) - expect(mockRouter.state.location.pathname).toBe('/internal') - expect(mockRouter.state.location.publicHref).toBe('/public') + expect(customHydrate).toHaveBeenCalledOnce() + expect(customHydrate).toHaveBeenCalledWith({ rewrite: true }) + expect(clientRouter.state.location.pathname).toBe('/internal') + expect(clientRouter.state.location.publicHref).toBe('/public') expect( - mockRouter.state.matches.map((match: AnyRouteMatch) => match.routeId), - ).toEqual(['__root__', '/internal']) + clientRouter.state.matches.map((match: AnyRouteMatch) => ({ + routeId: match.routeId, + status: match.status, + loaderData: match.loaderData, + })), + ).toEqual([ + { + routeId: clientRootRoute.id, + status: 'success', + loaderData: undefined, + }, + { + routeId: clientInternalRoute.id, + status: 'success', + loaderData: 'server internal data', + }, + ]) + expect(clientLoader).not.toHaveBeenCalled() }) - it('should handle errors during route context hydration', async () => { + it('logs and swallows a notFound thrown by a hydrated route head', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) mockHead.mockImplementation(() => { - throw notFound() - }) - - const mockMatches = [ - { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} }, - ] - - mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches) - mockRouter.state.matches = mockMatches - - mockWindow.$_TSR = { - router: { - manifest: testManifest, - dehydratedData: {}, - lastMatchId: '/', - matches: [ - { - i: '/', - l: { data: 'test' }, - s: 'success', - ssr: true, - u: Date.now(), - }, - ], - }, - h: vi.fn(), - e: vi.fn(), - c: vi.fn(), - p: vi.fn(), - buffer: [], - initialized: false, - } + throw notFound({ data: { source: 'index head' } }) + }) + const matched = mockRouter.matchRoutes(mockRouter.stores.location.get()) - await hydrate(mockRouter) + mockWindow.$_TSR = createMockBootstrap({ + manifest: testManifest, + dehydratedData: {}, + matches: matched.map((match: AnyRouteMatch) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + }) - const match = mockRouter.state.matches[0] as AnyRouteMatch - expect(match.error).toEqual({ isNotFound: true }) + await hydrate(mockRouter) + const match = mockRouter.state.matches.find( + (candidate: AnyRouteMatch) => candidate.routeId === '/', + ) as AnyRouteMatch | undefined + expect(match).toBeDefined() + expect(match?.status).toBe('success') + expect(match?.error).toBeUndefined() + expect(mockHead).toHaveBeenCalledOnce() expect(consoleSpy).toHaveBeenCalledWith( - 'NotFound error during hydration for routeId: /', expect.objectContaining({ isNotFound: true, + data: { source: 'index head' }, }), ) - - consoleSpy.mockRestore() }) }) diff --git a/packages/router-core/tests/hydrated-stay-match-data.test.ts b/packages/router-core/tests/hydrated-stay-match-data.test.ts new file mode 100644 index 0000000000..5f8333310a --- /dev/null +++ b/packages/router-core/tests/hydrated-stay-match-data.test.ts @@ -0,0 +1,160 @@ +import { runInNewContext } from 'node:vm' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouter } from '../src' +import type { TsrSsrGlobal } from '../src/ssr/types' +import type { ServerManifest } from '../src/manifest' + +const testManifest: ServerManifest = { routes: {} } + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +describe('hydrated stay match data preservation', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + vi.stubGlobal('window', mockWindow) + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it('keeps server loader data while refreshing context on client navigation', async () => { + const serverRootBeforeLoad = vi.fn(() => ({ auth: 'server' })) + const serverRootLoader = vi.fn(() => ({ root: 'server' })) + const serverARouteLoader = vi.fn(() => 'a server data') + const serverRootRoute = new BaseRootRoute({ + beforeLoad: serverRootBeforeLoad, + loader: serverRootLoader, + }) + const serverARoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/a', + ssr: false, + loader: serverARouteLoader, + }) + const serverBRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/b', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverARoute, serverBRoute]), + history: createMemoryHistory({ initialEntries: ['/a'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverRootBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverRootLoader).toHaveBeenCalledTimes(1) + expect(serverARouteLoader).not.toHaveBeenCalled() + expect(serverRouter.state.matches[0]).toMatchObject({ + routeId: serverRootRoute.id, + status: 'success', + context: { auth: 'server' }, + loaderData: { root: 'server' }, + }) + const rootBeforeLoad = vi.fn(() => ({ auth: 'client' })) + const rootLoader = vi.fn(() => ({ root: 'client' })) + const history = createMemoryHistory({ initialEntries: ['/a'] }) + + const rootRoute = new BaseRootRoute({ + beforeLoad: rootBeforeLoad, + loader: rootLoader, + }) + const aRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/a', + ssr: false, + loader: () => 'a client data', + }) + const bRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/b', + loader: () => 'b client data', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([aRoute, bRoute]), + history, + isServer: false, + }) + + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + // Hydration adopts the server root before client loads can re-enter beforeLoad. + expect(rootBeforeLoad).not.toHaveBeenCalled() + expect(rootLoader).not.toHaveBeenCalled() + expect( + router.state.matches.find((m) => m.routeId === rootRoute.id), + ).toMatchObject({ + context: { auth: 'server' }, + loaderData: { root: 'server' }, + }) + + await router.load() + + expect( + router.state.matches.find((m) => m.routeId === aRoute.id)?.loaderData, + ).toBe('a client data') + expect(router.state.location.pathname).toBe('/a') + expect(router.state.resolvedLocation?.pathname).toBe('/a') + expect(rootLoader).not.toHaveBeenCalled() + expect( + router.state.matches.find((m) => m.routeId === rootRoute.id), + ).toMatchObject({ + context: { auth: 'server' }, + loaderData: { root: 'server' }, + }) + + // The root stay match refreshes beforeLoad context without rerunning its loader. + await router.navigate({ to: '/b' }) + + expect( + router.state.matches.find((m) => m.routeId === bRoute.id)?.loaderData, + ).toBe('b client data') + expect(router.state.location.pathname).toBe('/b') + expect(router.state.resolvedLocation?.pathname).toBe('/b') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + bRoute.id, + ]) + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(rootLoader).not.toHaveBeenCalled() + expect( + router.state.matches.find((m) => m.routeId === rootRoute.id), + ).toMatchObject({ + context: { auth: 'client' }, + loaderData: { root: 'server' }, + }) + }) +}) diff --git a/packages/router-core/tests/hydration-asset-context-order.test.ts b/packages/router-core/tests/hydration-asset-context-order.test.ts new file mode 100644 index 0000000000..64ea28971e --- /dev/null +++ b/packages/router-core/tests/hydration-asset-context-order.test.ts @@ -0,0 +1,99 @@ +import { runInNewContext } from 'node:vm' +import { afterEach, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouteMatch, AnyRouter } from '../src' +import type { ServerManifest } from '../src/manifest' +import type { TsrSsrGlobal } from '../src/ssr/types' + +const testManifest: ServerManifest = { routes: {} } + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() +}) + +test('hydration reconstructs every match context before ancestor head reads the lane', async () => { + const serverBeforeLoad = vi.fn(() => ({ + user: 'server authenticated user', + })) + const serverRootRoute = new BaseRootRoute({}) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/child', + beforeLoad: serverBeforeLoad, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverChildRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverRouter.state.matches.at(-1)?.context).toMatchObject({ + user: 'server authenticated user', + }) + let childContextSeenByRootHead: unknown + const rootHead = vi.fn(({ matches }: { matches: Array }) => { + childContextSeenByRootHead = matches[1]?.context + return { meta: [{ title: 'hydrated' }] } + }) + const clientBeforeLoad = vi.fn(() => ({ user: 'client fallback' })) + const rootRoute = new BaseRootRoute({ + head: rootHead, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + beforeLoad: clientBeforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + + vi.stubGlobal('window', { $_TSR: bootstrap }) + + await hydrate(router) + + expect(childContextSeenByRootHead).toMatchObject({ + user: 'server authenticated user', + }) + expect(clientBeforeLoad).not.toHaveBeenCalled() + expect(rootHead).toHaveBeenCalledTimes(1) + expect(router.state.location.pathname).toBe('/child') + expect(router.state.resolvedLocation?.pathname).toBe('/child') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: childRoute.id, + context: { user: 'server authenticated user' }, + }) + expect(router.state.matches[0]?.meta).toEqual([{ title: 'hydrated' }]) +}) diff --git a/packages/router-core/tests/hydration-boundary-chunks.test.ts b/packages/router-core/tests/hydration-boundary-chunks.test.ts new file mode 100644 index 0000000000..fe760e074a --- /dev/null +++ b/packages/router-core/tests/hydration-boundary-chunks.test.ts @@ -0,0 +1,235 @@ +import { runInNewContext } from 'node:vm' +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, isNotFound, notFound } from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouter } from '../src' +import type { TsrSsrGlobal } from '../src/ssr/types' +import type { ServerManifest } from '../src/manifest' + +const testManifest: ServerManifest = { routes: {} } + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +describe('hydration route chunks below a server boundary', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + vi.stubGlobal('window', mockWindow) + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + test('hydrates an error boundary without requiring its normal component chunk', async () => { + const serverError = new Error('server beforeLoad failed') + const serverBeforeLoad = vi.fn(() => { + throw serverError + }) + const serverRootRoute = new BaseRootRoute({}) + const serverAppRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/app', + beforeLoad: serverBeforeLoad, + errorComponent: () => null, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverAppRoute]), + history: createMemoryHistory({ initialEntries: ['/app'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverRouter.state.location.pathname).toBe('/app') + expect(serverRouter.state.matches.map((match) => match.routeId)).toEqual([ + serverRootRoute.id, + serverAppRoute.id, + ]) + expect(serverRouter.state.matches.at(-1)).toMatchObject({ + status: 'error', + error: serverError, + }) + const transportedError = bootstrap.router?.matches.at(-1)?.e + expect(transportedError).not.toBe(serverError) + expect(transportedError).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + + const normalChunkError = new Error('normal component chunk unavailable') + const normalComponentPreload = vi.fn(() => Promise.reject(normalChunkError)) + const errorComponentPreload = vi.fn(() => Promise.resolve()) + const clientBeforeLoad = vi.fn(() => { + throw new Error('client beforeLoad should not run') + }) + const NormalComponent = Object.assign(() => null, { + preload: normalComponentPreload, + }) + const ErrorComponent = Object.assign(() => null, { + preload: errorComponentPreload, + }) + + const rootRoute = new BaseRootRoute({}) + const appRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/app', + beforeLoad: clientBeforeLoad, + component: NormalComponent, + errorComponent: ErrorComponent, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([appRoute]), + history: createMemoryHistory({ initialEntries: ['/app'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await expect(hydrate(router)).resolves.toBeUndefined() + + const appMatch = router.state.matches.find( + (match) => match.routeId === appRoute.id, + ) + expect(appMatch?.status).toBe('error') + expect(appMatch?.error).toBe(transportedError) + expect(appMatch?.error).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + expect(router.state.location.pathname).toBe('/app') + expect(router.state.resolvedLocation?.pathname).toBe('/app') + expect(clientBeforeLoad).not.toHaveBeenCalled() + expect(normalComponentPreload).not.toHaveBeenCalled() + expect(errorComponentPreload).toHaveBeenCalledTimes(1) + }) + + test.each([ + { ssr: undefined, hydratedStatus: 'notFound', resolved: true }, + { + ssr: 'data-only' as const, + hydratedStatus: 'pending', + resolved: false, + }, + ])( + 'hydrates an ancestor notFound boundary with ssr=$ssr without loading an omitted descendant chunk', + async ({ ssr, hydratedStatus, resolved }) => { + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr, + notFoundComponent: () => 'not found', + }) + const serverNotFound = notFound({ routeId: serverParentRoute.id }) + const serverChildLoader = vi.fn(() => { + throw serverNotFound + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + loader: serverChildLoader, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverChildLoader).toHaveBeenCalledTimes(1) + expect(serverRouter.state.matches.map((match) => match.routeId)).toEqual([ + serverRootRoute.id, + serverParentRoute.id, + serverChildRoute.id, + ]) + expect(serverRouter.state.matches[1]?.status).toBe('notFound') + expect(isNotFound(serverRouter.state.matches[1]?.error)).toBe(true) + const childChunkError = new Error('omitted child chunk unavailable') + const childComponentPreload = vi.fn(() => Promise.reject(childChunkError)) + const ChildComponent = Object.assign(() => null, { + preload: childComponentPreload, + }) + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr, + notFoundComponent: () => 'not found', + }) + const childLoader = vi.fn(() => { + throw new Error('client child loader should not run') + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + component: ChildComponent, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await expect(hydrate(router)).resolves.toBeUndefined() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]?.status).toBe(hydratedStatus) + expect(isNotFound(router.state.matches[1]?.error)).toBe(true) + expect(router.state.matches[2]?.status).toBe('pending') + expect(router.state.location.pathname).toBe('/parent/child') + expect(router.state.resolvedLocation?.pathname).toBe( + resolved ? '/parent/child' : undefined, + ) + expect(childComponentPreload).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + + if (ssr === 'data-only') { + await router.load() + + expect(router.state.resolvedLocation?.pathname).toBe('/parent/child') + expect(router.state.matches[1]?.status).toBe('notFound') + expect(isNotFound(router.state.matches[1]?.error)).toBe(true) + expect(childComponentPreload).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + } + }, + ) +}) diff --git a/packages/router-core/tests/hydration-currentness.test.ts b/packages/router-core/tests/hydration-currentness.test.ts new file mode 100644 index 0000000000..3257b0e201 --- /dev/null +++ b/packages/router-core/tests/hydration-currentness.test.ts @@ -0,0 +1,675 @@ +import { runInNewContext } from 'node:vm' +import { + afterEach, + beforeEach, + describe, + expect, + onTestFinished, + test, + vi, +} from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + _getAssetMatches, + createControlledPromise, +} from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { dehydrateSsrMatchId } from '../src/ssr/ssr-match-id' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouteMatch, AnyRouter, NavigateFn } from '../src' +import type { DehydratedRouter, TsrSsrGlobal } from '../src/ssr/types' +import type { ServerManifest } from '../src/manifest' + +const testManifest: ServerManifest = { routes: {} } + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +// These tests install the hydration protocol directly so client-only hooks can +// be paused at deterministic ownership boundaries. +function installHydrationPayload( + mockWindow: { $_TSR?: TsrSsrGlobal }, + matches: DehydratedRouter['matches'], +) { + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + matches, + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } +} + +/** + * Hydration asset work is another private lane generation. If a public + * navigation commits while an old hydration head is pending, the hydration + * continuation must not execute more old-lane hooks or mark its captured + * location as resolved. + */ +describe('hydration asset currentness', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + vi.stubGlobal('window', mockWindow) + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + test('shell hydration preserves the complete lane and its first pending boundary', async () => { + const childLoader = vi.fn(() => 'client data') + const rootRoute = new BaseRootRoute({}) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + ssr: false, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + routeId: childRoute.id, + status: 'pending', + ssr: false, + }) + expect(router.state.resolvedLocation).toBeUndefined() + expect(childLoader).not.toHaveBeenCalled() + }) + + test('includes an identity-verified ssr:false boundary in a data-only asset prefix', async () => { + const serverChildLoader = vi.fn(() => 'server child data') + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr: 'data-only', + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + ssr: false, + loader: serverChildLoader, + }) + const serverTailRoute = new BaseRoute({ + getParentRoute: () => serverChildRoute, + path: '/tail', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([ + serverChildRoute.addChildren([serverTailRoute]), + ]), + ]), + history: createMemoryHistory({ + initialEntries: ['/parent/child/tail'], + }), + isServer: true, + }) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + expect(serverChildLoader).not.toHaveBeenCalled() + + const childContext = vi.fn(() => ({ assetSource: 'child context' })) + const childHead = vi.fn(({ match }: { match: AnyRouteMatch }) => ({ + meta: [ + { + name: 'ssr-false-boundary', + content: (match.context as { assetSource?: string }).assetSource, + }, + ], + })) + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr: 'data-only', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + ssr: false, + loader: () => 'client child data', + context: childContext, + head: childHead, + }) + const tailRoute = new BaseRoute({ + getParentRoute: () => childRoute, + path: '/tail', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute.addChildren([tailRoute])]), + ]), + history: createMemoryHistory({ + initialEntries: ['/parent/child/tail'], + }), + isServer: false, + }) + + await hydrate(router) + + expect( + _getAssetMatches(router.state.matches).map((match) => match.routeId), + ).toEqual([rootRoute.id, parentRoute.id, childRoute.id]) + expect(childContext).toHaveBeenCalledOnce() + expect(childHead).toHaveBeenCalledOnce() + expect(router.state.matches[2]?.meta).toEqual([ + { name: 'ssr-false-boundary', content: 'child context' }, + ]) + }) + + test('preserves a verified data-only asset prefix through its client continuation', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr: 'data-only', + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + ssr: false, + loader: () => 'server child data', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + mockWindow.$_TSR = await dehydrateToBootstrap(serverRouter) + + const childLoaderResult = createControlledPromise() + let continuationAssetEnd: number | undefined + let router!: AnyRouter + const childLoader = vi.fn(() => { + continuationAssetEnd = router._tx?.[3][1]?._assetEnd + return childLoaderResult + }) + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr: 'data-only', + pendingComponent: () => null, + pendingMs: 0, + pendingMinMs: 0, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + ssr: false, + loader: childLoader, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) + + await hydrate(router) + const load = router.load() + + await vi.waitFor(() => { + expect(childLoader).toHaveBeenCalledOnce() + expect(router.state.status).toBe('pending') + }) + expect(continuationAssetEnd).toBe(3) + expect( + _getAssetMatches(router.state.matches).map((match) => match.routeId), + ).toEqual([rootRoute.id, parentRoute.id, childRoute.id]) + expect(router.state.matches[1]?._assetEnd).toBe(3) + + childLoaderResult.resolve('client child data') + await load + + expect(router.state.matches[1]?._assetEnd).toBeUndefined() + expect(router.state.matches[2]).toMatchObject({ + status: 'success', + loaderData: 'client child data', + }) + }) + + test('settles when navigation supersedes a pending custom hydrate hook', async () => { + const hydrateGate = createControlledPromise() + const hydrateStarted = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const oldRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/old', + }) + const newRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/new', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([oldRoute, newRoute]), + history: createMemoryHistory({ initialEntries: ['/old'] }), + isServer: false, + }) + router.options.hydrate = () => { + hydrateStarted.resolve() + return hydrateGate + } + const oldMatches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + oldMatches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + const hydration = hydrate(router) + onTestFinished(async () => { + hydrateGate.resolve() + await hydration + }) + + await hydrateStarted + await router.navigate({ to: '/new' }) + await expect(hydration).resolves.toBeUndefined() + + expect(router.state.resolvedLocation?.pathname).toBe('/new') + expect(router.state.matches.at(-1)?.routeId).toBe(newRoute.id) + }) + + test('does not publish an old hydration lane after a newer navigation commits', async () => { + const oldChunkGate = createControlledPromise() + const oldChunkStarted = createControlledPromise() + const OldComponent = Object.assign(() => null, { + preload: () => { + oldChunkStarted.resolve() + return oldChunkGate + }, + }) + const rootRoute = new BaseRootRoute({}) + const oldRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/old', + component: OldComponent, + }) + const newRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/new', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([oldRoute, newRoute]), + history: createMemoryHistory({ initialEntries: ['/old'] }), + isServer: false, + }) + const oldMatches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + oldMatches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + const hydration = hydrate(router) + onTestFinished(async () => { + oldChunkGate.resolve() + await hydration + }) + + await oldChunkStarted + await router.navigate({ to: '/new' }) + + expect(router.state.resolvedLocation?.pathname).toBe('/new') + expect(router.state.matches.at(-1)?.routeId).toBe(newRoute.id) + + await hydration + + expect(router.state.location.pathname).toBe('/new') + expect(router.state.resolvedLocation?.pathname).toBe('/new') + expect(router.state.matches.at(-1)?.routeId).toBe(newRoute.id) + }) + + test('an aborted hydration handoff falls back to a fresh client continuation', async () => { + let hydrationController: AbortController | undefined + const childLoader = vi.fn(() => 'client data') + const rootRoute = new BaseRootRoute({ + context: ({ abortController }: { abortController: AbortController }) => { + hydrationController ??= abortController + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + ssr: false, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + expect(hydrationController).toBeDefined() + + const unsubscribe = router.subscribe('onBeforeLoad', () => { + hydrationController!.abort() + }) + onTestFinished(unsubscribe) + + await router.load() + + expect(router.state.resolvedLocation?.pathname).toBe('/child') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: childRoute.id, + status: 'success', + loaderData: 'client data', + }) + expect(childLoader).toHaveBeenCalledTimes(1) + }) + + test('failed HMR restores a partial hydration presentation and handoff', async () => { + let hydrationController: AbortController | undefined + const childLoader = vi.fn(() => 'client child data') + const rootRoute = new BaseRootRoute({ + context: ({ abortController }: { abortController: AbortController }) => { + hydrationController ??= abortController + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + ssr: false, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + const handoff = router._handoff + const startTransition = router.startTransition + router.startTransition = async (fn) => { + fn() + throw new Error('HMR render failed') + } + + await router._refreshRoute!() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + status: 'pending', + ssr: false, + }) + expect(router._handoff).toBe(handoff) + expect(hydrationController?.signal.aborted).toBe(false) + + router.startTransition = startTransition + await router.load() + expect(router.state.matches[1]).toMatchObject({ + status: 'success', + loaderData: 'client child data', + }) + expect(childLoader).toHaveBeenCalledTimes(2) + }) + + test('a synchronous context navigation prevents stale hydration assets from running', async () => { + const oldHead = vi.fn(() => ({ meta: [{ title: 'Old route' }] })) + const oldScripts = vi.fn(() => [ + { children: 'window.oldHydrationLaneRan = true' }, + ]) + const newHead = vi.fn(() => ({ meta: [{ title: 'New route' }] })) + + let navigation: Promise | undefined + let navigateDuringHydration = false + const navigationStarted = vi.fn() + const oldContext = vi.fn(({ navigate }: { navigate: NavigateFn }) => { + if (navigateDuringHydration) { + navigateDuringHydration = false + navigationStarted() + navigation = navigate({ to: '/new' }) + } + return { source: 'old' } + }) + const rootRoute = new BaseRootRoute({}) + const oldRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/old', + context: oldContext, + head: oldHead, + scripts: oldScripts, + }) + const newRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/new', + head: newHead, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([oldRoute, newRoute]), + history: createMemoryHistory({ initialEntries: ['/old'] }), + isServer: false, + }) + const oldMatches = router.matchRoutes(router.stores.location.get()) + + installHydrationPayload( + mockWindow, + oldMatches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + navigateDuringHydration = true + await hydrate(router) + expect(navigationStarted).toHaveBeenCalled() + expect(navigation).toBeDefined() + await navigation + + expect(router.state.location.pathname).toBe('/new') + expect(router.state.resolvedLocation?.pathname).toBe('/new') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: newRoute.id, + status: 'success', + meta: [{ title: 'New route' }], + }) + expect(oldContext).toHaveBeenCalled() + expect(oldHead).not.toHaveBeenCalled() + expect(oldScripts).not.toHaveBeenCalled() + expect(newHead).toHaveBeenCalled() + }) + + test('an adopted data-only error does not run its server-omitted descendants', async () => { + const serverError = new Error('server failed') + const serverBeforeLoad = vi.fn(() => { + throw serverError + }) + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr: 'data-only', + beforeLoad: serverBeforeLoad, + }) + const serverChildLoader = vi.fn() + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + loader: serverChildLoader, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverChildLoader).not.toHaveBeenCalled() + expect(serverRouter.state.matches.map((match) => match.routeId)).toEqual([ + serverRootRoute.id, + serverParentRoute.id, + serverChildRoute.id, + ]) + expect(serverRouter.state.matches[1]).toMatchObject({ + routeId: serverParentRoute.id, + status: 'error', + ssr: 'data-only', + error: serverError, + }) + const transportedError = bootstrap.router?.matches.at(-1)?.e + expect(transportedError).not.toBe(serverError) + expect(transportedError).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + + const parentBeforeLoad = vi.fn() + const childBeforeLoad = vi.fn() + const childLoader = vi.fn() + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr: 'data-only', + beforeLoad: parentBeforeLoad, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + beforeLoad: childBeforeLoad, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'pending', + error: transportedError, + }) + expect(parentBeforeLoad).not.toHaveBeenCalled() + expect(childBeforeLoad).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + expect( + _getAssetMatches(router.state.matches).map((match) => match.routeId), + ).toEqual([rootRoute.id, parentRoute.id]) + + await router.load() + + expect(router.state.location.pathname).toBe('/parent/child') + expect(router.state.resolvedLocation?.pathname).toBe('/parent/child') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + status: 'error', + error: transportedError, + }) + expect(router.state.matches[1]?.error).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + expect(parentBeforeLoad).not.toHaveBeenCalled() + expect(childBeforeLoad).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + }) +}) diff --git a/packages/router-core/tests/hydration-terminal-error-child-head.test.ts b/packages/router-core/tests/hydration-terminal-error-child-head.test.ts new file mode 100644 index 0000000000..0e607f7a1c --- /dev/null +++ b/packages/router-core/tests/hydration-terminal-error-child-head.test.ts @@ -0,0 +1,175 @@ +import { runInNewContext } from 'node:vm' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouter } from '../src' +import type { TsrSsrGlobal } from '../src/ssr/types' +import type { ServerManifest } from '../src/manifest' + +const testManifest: ServerManifest = { routes: {} } + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + expect(context.$_TSR).toBeDefined() + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +// This terminal-prefix case is an internal hydration protocol invariant, not +// an issue #7635 reproduction. The user-visible same-route navigation +// regression is covered in react-router, where the rendered boundary and +// document title can be observed directly. +describe('hydrated terminal error prefix', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + vi.stubGlobal('window', mockWindow) + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it('adopts the terminal prefix without running omitted child work', async () => { + const serverError = new Error('App beforeLoad failed') + const serverBeforeLoad = vi.fn(() => { + throw serverError + }) + const serverChildLoader = vi.fn(() => ({ child: 'server data' })) + const serverRootRoute = new BaseRootRoute({}) + const serverAppRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/app', + beforeLoad: serverBeforeLoad, + errorComponent: () => 'App error', + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverAppRoute, + path: '/child', + loader: serverChildLoader, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverAppRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/app/child'] }), + isServer: true, + }) + + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverChildLoader).not.toHaveBeenCalled() + expect(serverRouter.state.matches.map((match) => match.routeId)).toEqual([ + serverRootRoute.id, + serverAppRoute.id, + serverChildRoute.id, + ]) + expect(serverRouter.state.matches[1]).toMatchObject({ + routeId: serverAppRoute.id, + status: 'error', + error: serverError, + }) + const transportedError = bootstrap.router?.matches.at(-1)?.e + expect(transportedError).not.toBe(serverError) + expect(transportedError).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + + const appHead = vi.fn(({ match }: any) => ({ + meta: [{ title: match.error ? 'App error title' : 'App success title' }], + })) + const childHead = vi.fn(() => ({ + meta: [{ title: 'Child success title' }], + })) + const childLoader = vi.fn(() => ({ child: 'data' })) + + const rootRoute = new BaseRootRoute({}) + const appRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/app', + head: appHead, + errorComponent: () => 'App error', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => appRoute, + path: '/child', + loader: childLoader, + head: childHead, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([appRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/app/child'] }), + isServer: false, + }) + + // Route matching confirms that the location has a child suffix which the + // terminal server payload omits. + const matches = router.matchRoutes(router.stores.location.get()) + expect(matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + appRoute.id, + childRoute.id, + ]) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + // Hydration reconstructs the full matched branch while adopting only the + // terminal prefix represented by the payload. + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + appRoute.id, + childRoute.id, + ]) + expect(router.state.matches[2]?.status).toBe('pending') + + const committedApp = router.state.matches.find( + (match) => match.routeId === appRoute.id, + ) + expect(router.state.location.pathname).toBe('/app/child') + expect(router.state.isLoading).toBe(false) + expect(committedApp?.status).toBe('error') + expect(committedApp?.error).toBe(transportedError) + + // No callback for the omitted suffix may run while adopting the prefix. + expect(childLoader).not.toHaveBeenCalled() + expect(childHead).not.toHaveBeenCalled() + + // Core projects the error match's metadata; document ownership is covered + // by the framework-level test. + expect(appHead).toHaveBeenCalledOnce() + expect(appHead).toHaveBeenCalledWith( + expect.objectContaining({ + match: expect.objectContaining({ + routeId: appRoute.id, + status: 'error', + error: transportedError, + }), + }), + ) + expect(committedApp?.meta).toEqual([{ title: 'App error title' }]) + }) +}) diff --git a/packages/router-core/tests/invalidate-pre-rematch-failure.test.ts b/packages/router-core/tests/invalidate-pre-rematch-failure.test.ts new file mode 100644 index 0000000000..084fbe8d31 --- /dev/null +++ b/packages/router-core/tests/invalidate-pre-rematch-failure.test.ts @@ -0,0 +1,63 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * A planning failure is a route error generation and leaves the router usable + * for a later retry. + */ +test('a fatal pre-rematch failure publishes an error and permits retry', async () => { + const boom = new Error('loaderDeps failed during invalidation rematch') + let failLoaderDeps = false + const loaderSignals: Array = [] + + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loaderDeps: () => { + if (failLoaderDeps) { + throw boom + } + return {} + }, + loader: ({ abortController }) => { + loaderSignals.push(abortController.signal) + return 'target data' + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory({ initialEntries: ['/target'] }), + }) + + await router.load() + const activeSignal = loaderSignals[0] + expect(router.state.matches.at(-1)?.status).toBe('success') + + failLoaderDeps = true + await router.invalidate({ forcePending: true }) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'error', + error: boom, + }) + expect(loaderSignals).toHaveLength(1) + expect(activeSignal?.aborted).toBe(true) + + failLoaderDeps = false + await router.invalidate() + const retrySignal = loaderSignals[1] + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'target data', + }) + expect(loaderSignals).toHaveLength(2) + expect(retrySignal).not.toBe(activeSignal) + expect(activeSignal?.aborted).toBe(true) + expect(retrySignal?.aborted).toBe(false) +}) diff --git a/packages/router-core/tests/issue-3179-preload-cached-cause.test.ts b/packages/router-core/tests/issue-3179-preload-cached-cause.test.ts new file mode 100644 index 0000000000..c465961dc2 --- /dev/null +++ b/packages/router-core/tests/issue-3179-preload-cached-cause.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +// https://github.com/TanStack/router/issues/3179 +test('#3179: preloading the active route does not replay its load callbacks', async () => { + const calls: Array<{ + phase: 'beforeLoad' | 'loader' + cause: string + preload: boolean + }> = [] + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + staleTime: 0, + preloadStaleTime: 0, + beforeLoad: ({ cause, preload }) => { + calls.push({ phase: 'beforeLoad', cause, preload }) + }, + loader: ({ cause, preload }) => { + calls.push({ phase: 'loader', cause, preload }) + }, + }) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), + history: createMemoryHistory({ initialEntries: ['/about'] }), + }) + + await router.load() + + // First hover Home from About, then click it. This is the sequence from + // the issue that used to cache the navigation's flags on the match. + await router.preloadRoute({ to: '/' }) + expect(calls).toEqual([ + { phase: 'beforeLoad', cause: 'preload', preload: true }, + { phase: 'loader', cause: 'preload', preload: true }, + ]) + + calls.length = 0 + await router.navigate({ to: '/' }) + expect(calls).toEqual([ + { phase: 'beforeLoad', cause: 'enter', preload: false }, + { phase: 'loader', cause: 'enter', preload: false }, + ]) + + // beforeLoad is contextualization, not loader cache data, so a later preload + // reruns it with fresh flags while retaining the accepted loader generation. + calls.length = 0 + await router.preloadRoute({ to: '/' }) + expect(calls).toEqual([ + { phase: 'beforeLoad', cause: 'preload', preload: true }, + ]) +}) diff --git a/packages/router-core/tests/issue-3293-on-enter-after-loader.test.ts b/packages/router-core/tests/issue-3293-on-enter-after-loader.test.ts new file mode 100644 index 0000000000..a1be910314 --- /dev/null +++ b/packages/router-core/tests/issue-3293-on-enter-after-loader.test.ts @@ -0,0 +1,108 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +// Existing-behavior controls for https://github.com/TanStack/router/issues/3293 +test('#3293 existing behavior: direct load runs onEnter after beforeLoad and loader', async () => { + const beforeLoadGate = createControlledPromise<{ ready: true }>() + const loaderGate = createControlledPromise<{ someData: 42 }>() + const beforeLoad = vi.fn(() => beforeLoadGate) + const loader = vi.fn(() => loaderGate) + const onEnter = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + beforeLoad, + loader, + onEnter, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([aboutRoute]), + history: createMemoryHistory({ initialEntries: ['/about'] }), + }) + + const load = router.load() + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + expect(loader).not.toHaveBeenCalled() + expect(onEnter).not.toHaveBeenCalled() + + beforeLoadGate.resolve({ ready: true }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + expect(loader).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ ready: true }), + }), + ) + expect(onEnter).not.toHaveBeenCalled() + + loaderGate.resolve({ someData: 42 }) + await load + + expect(onEnter).toHaveBeenCalledTimes(1) + expect(onEnter).toHaveBeenCalledWith( + expect.objectContaining({ + status: 'success', + context: expect.objectContaining({ ready: true }), + loaderData: { someData: 42 }, + }), + ) +}) + +test('#3293 existing behavior: uncached SPA navigation runs onEnter after beforeLoad and loader', async () => { + const beforeLoadGate = createControlledPromise<{ ready: true }>() + const loaderGate = createControlledPromise<{ someData: 42 }>() + const beforeLoad = vi.fn(() => beforeLoadGate) + const loader = vi.fn(() => loaderGate) + const onEnter = vi.fn() + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + beforeLoad, + loader, + onEnter, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + expect(onEnter).not.toHaveBeenCalled() + + const navigation = router.navigate({ to: '/about' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + expect(loader).not.toHaveBeenCalled() + expect(onEnter).not.toHaveBeenCalled() + + beforeLoadGate.resolve({ ready: true }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + expect(loader).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ ready: true }), + }), + ) + expect(onEnter).not.toHaveBeenCalled() + + loaderGate.resolve({ someData: 42 }) + await navigation + + expect(onEnter).toHaveBeenCalledTimes(1) + expect(onEnter).toHaveBeenCalledWith( + expect.objectContaining({ + status: 'success', + context: expect.objectContaining({ ready: true }), + loaderData: { someData: 42 }, + }), + ) +}) diff --git a/packages/router-core/tests/issue-3920-on-before-navigate-after-initial-redirect.test.ts b/packages/router-core/tests/issue-3920-on-before-navigate-after-initial-redirect.test.ts new file mode 100644 index 0000000000..f0de5df8dd --- /dev/null +++ b/packages/router-core/tests/issue-3920-on-before-navigate-after-initial-redirect.test.ts @@ -0,0 +1,40 @@ +import { createMemoryHistory } from '@tanstack/history' +import { expect, test } from 'vitest' +import { BaseRootRoute, BaseRoute, redirect } from '../src' +import { createTestRouter } from './routerTestUtils' + +// https://github.com/TanStack/router/issues/3920 +test('onBeforeNavigate fires after the initial load redirects', async () => { + const rootRoute = new BaseRootRoute({}) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + beforeLoad: () => { + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const thirdRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/third', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([sourceRoute, targetRoute, thirdRoute]), + history: createMemoryHistory({ initialEntries: ['/source'] }), + }) + + await router.load() + expect(router.state.location.pathname).toBe('/target') + + const navigatedPaths: Array = [] + router.subscribe('onBeforeNavigate', (event) => { + navigatedPaths.push(event.toLocation.pathname) + }) + + await router.navigate({ to: '/third' }) + + expect(navigatedPaths).toEqual(['/third']) +}) diff --git a/packages/router-core/tests/issue-3928-rapid-reload-abort.test.ts b/packages/router-core/tests/issue-3928-rapid-reload-abort.test.ts new file mode 100644 index 0000000000..a16258be0b --- /dev/null +++ b/packages/router-core/tests/issue-3928-rapid-reload-abort.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +type LoaderInvocation = { + resolve: (value: string) => void + signal: AbortSignal +} + +const createAbortableInvocation = ( + abortController: AbortController, + onAbort?: () => void, +) => { + let resolve!: (value: string) => void + const promise = new Promise((promiseResolve, reject) => { + const handleAbort = () => { + onAbort?.() + reject(new DOMException('signal is aborted without reason', 'AbortError')) + } + resolve = (value) => { + abortController.signal.removeEventListener('abort', handleAbort) + promiseResolve(value) + } + if (abortController.signal.aborted) { + handleAbort() + } else { + abortController.signal.addEventListener('abort', handleAbort, { + once: true, + }) + } + }) + return { + invocation: { resolve, signal: abortController.signal }, + promise, + } +} + +// https://github.com/TanStack/router/issues/3928 +describe('issue #3928: rapid reloads of a reused parent', () => { + test('one parent flight can serve rapid successors without clearing the latest child load', async () => { + const indexAbort = vi.fn() + const rootInvocations = new Map() + const indexInvocations = new Map() + + const rootRoute = new BaseRootRoute({ + shouldReload: true, + // Search is deliberately not part of this loader's key. Its active + // same-ID flight may serve successors while the child keys each filter. + loader: ({ abortController, location }) => { + const { invocation, promise } = + createAbortableInvocation(abortController) + const search = location.search as Record + const filter = typeof search.filter === 'string' ? search.filter : '' + rootInvocations.set(filter, invocation) + return promise + }, + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + validateSearch: (search: Record) => ({ + filter: typeof search.filter === 'string' ? search.filter : '', + }), + loaderDeps: ({ search }: { search: { filter: string } }) => ({ + filter: search.filter, + }), + loader: ({ + deps, + abortController, + }: { + deps: { filter: string } + abortController: AbortController + }) => { + const { invocation, promise } = createAbortableInvocation( + abortController, + indexAbort, + ) + indexInvocations.set(deps.filter, invocation) + return promise + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const initialLoad = router.load() + await vi.waitFor(() => { + expect(rootInvocations.has('')).toBe(true) + expect(indexInvocations.has('')).toBe(true) + }) + rootInvocations.get('')!.resolve('root:initial') + indexInvocations.get('')!.resolve('') + await initialLoad + + const navA = router.navigate({ to: '/', search: { filter: 'a' } }) + await vi.waitFor(() => { + expect(rootInvocations.has('a')).toBe(true) + expect(indexInvocations.has('a')).toBe(true) + }) + + const navAB = router.navigate({ to: '/', search: { filter: 'ab' } }) + await vi.waitFor(() => { + expect(indexInvocations.has('ab')).toBe(true) + expect(indexInvocations.get('a')!.signal.aborted).toBe(true) + expect(rootInvocations.get('a')!.signal.aborted).toBe(false) + }) + + const navABC = router.navigate({ to: '/', search: { filter: 'abc' } }) + await vi.waitFor(() => { + expect(indexInvocations.has('abc')).toBe(true) + expect(indexInvocations.get('ab')!.signal.aborted).toBe(true) + expect(rootInvocations.get('a')!.signal.aborted).toBe(false) + }) + + rootInvocations.get('a')!.resolve('root:a') + indexInvocations.get('abc')!.resolve('abc') + await Promise.all([navA, navAB, navABC]) + + expect(indexAbort).toHaveBeenCalledTimes(2) + expect(rootInvocations.size).toBe(2) + expect(rootInvocations.get('a')!.signal.aborted).toBe(false) + expect(indexInvocations.get('abc')!.signal.aborted).toBe(false) + expect(router.state.location.search).toEqual({ filter: 'abc' }) + expect(router.state.matches[0]).toMatchObject({ + status: 'success', + loaderData: 'root:a', + }) + expect(router.state.matches[1]).toMatchObject({ + status: 'success', + loaderData: 'abc', + }) + expect(router.state.matches.some((match) => match.status === 'error')).toBe( + false, + ) + }) +}) diff --git a/packages/router-core/tests/issue-4078-loader-notfound-root-boundary.test.ts b/packages/router-core/tests/issue-4078-loader-notfound-root-boundary.test.ts new file mode 100644 index 0000000000..c33521597b --- /dev/null +++ b/packages/router-core/tests/issue-4078-loader-notfound-root-boundary.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it, onTestFinished } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + rootRouteId, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +// Existing Core attribution coverage related to: +// https://github.com/TanStack/router/issues/4078 +// https://github.com/TanStack/router/issues/2255 +// Throwing an untargeted notFound() in a child loader used to always render the +// defaultNotFoundComponent, even when __root__ defined a notFoundComponent +// (#4078). #2255 is the same asymmetry: a path-mismatch notFound renders the +// root component + notFoundComponent, but a loader-thrown notFound did not. +// +// At Router Core's boundary, root attribution is represented by the root match +// with _notFound. These assertions do not distinguish the +// configured root notFoundComponent from the router default; that reported +// rendering behavior requires framework-level coverage. +describe('#4078 / #2255 existing Core root-boundary attribution', () => { + const setup = (initialEntries: Array) => { + const loaderStarted = createControlledPromise() + const loaderResponse = createControlledPromise() + const rootRoute = new BaseRootRoute({ + component: () => 'Root', + notFoundComponent: () => 'Root not found', + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + loader: async () => { + loaderStarted.resolve() + await loaderResponse + throw notFound() + }, + component: () => 'About', + }) + + return { + router: createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), + history: createMemoryHistory({ initialEntries }), + }), + loaderStarted, + loaderResponse, + } + } + + const getRootBoundaryProjection = ( + router: ReturnType['router'], + ) => { + const rootMatch = router.state.matches.find( + (match) => match.routeId === rootRouteId, + ) + + return { + routeId: rootMatch?.routeId, + _notFound: rootMatch?._notFound, + } + } + + it('an async loader notFound navigation selects the root boundary', async () => { + const { router, loaderStarted, loaderResponse } = setup(['/']) + await router.load() + + const navigation = router.navigate({ to: '/about' }) + onTestFinished(() => { + loaderResponse.resolve() + }) + await loaderStarted + expect(loaderResponse.status).toBe('pending') + loaderResponse.resolve() + + await navigation + + expect(router.state.location.pathname).toBe('/about') + expect(getRootBoundaryProjection(router)).toEqual({ + routeId: rootRouteId, + _notFound: true, + }) + }) + + it('matches the root-boundary projection of an unmatched URL (#2255 parity)', async () => { + const unmatched = setup(['/missing']) + await unmatched.router.load() + const unmatchedProjection = getRootBoundaryProjection(unmatched.router) + expect(unmatchedProjection).toEqual({ + routeId: rootRouteId, + _notFound: true, + }) + + const loaderNotFound = setup(['/']) + await loaderNotFound.router.load() + const navigation = loaderNotFound.router.navigate({ to: '/about' }) + await loaderNotFound.loaderStarted + loaderNotFound.loaderResponse.resolve() + await navigation + + expect(getRootBoundaryProjection(loaderNotFound.router)).toEqual( + unmatchedProjection, + ) + }) +}) diff --git a/packages/router-core/tests/issue-4444-param-parse-error-lazy-child.test.ts b/packages/router-core/tests/issue-4444-param-parse-error-lazy-child.test.ts new file mode 100644 index 0000000000..9a996f9e4a --- /dev/null +++ b/packages/router-core/tests/issue-4444-param-parse-error-lazy-child.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, PathParamError } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * Issue #4444: when a route's params.parse throws and one of its children is + * lazily loaded, the router used to stay "pending" forever: the lazy child's + * chunk was never requested, its match never settled, and framework + * transitioners could never reach idle. + * + * Desired behavior: the param parse error is a serial failure — the failing + * route commits status 'error' for its error boundary, descendants below the + * boundary are trimmed out of the lane with their load promises settled, and + * router.load() resolves with no match left pending. + */ +describe('issue #4444: param parse error on a route with a lazy child', () => { + test('load settles, commits the error boundary, and leaves no match stuck pending', async () => { + const rootRoute = new BaseRootRoute({}) + const langRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/lang/$lang', + params: { + parse: ({ lang }: { lang: string }) => { + if (lang !== 'en') { + throw new Error(`Unsupported language: ${lang}`) + } + return { lang } + }, + stringify: ({ lang }: { lang: string }) => ({ lang }), + }, + errorComponent: () => null, + }) + const lazyChildRoute = new BaseRoute({ + getParentRoute: () => langRoute, + path: '/lazy', + }) + const lazyFn = vi.fn(() => + Promise.resolve({ + options: { + id: lazyChildRoute.id, + component: () => null, + }, + }), + ) + lazyChildRoute.lazy(lazyFn) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + langRoute.addChildren([lazyChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/lang/es/lazy'] }), + }) + + // The reported bug: this load never reached a settled render state. + await router.load() + expect(router.state.status).toBe('idle') + + // The failing route commits the parse error for its error boundary… + const langMatch = router.state.matches.find( + (match) => match.routeId === langRoute.id, + ) + expect(langMatch?.status).toBe('error') + expect(langMatch?.error).toBeInstanceOf(PathParamError) + + // …while the structurally matched lazy child remains unresolved and hidden + // below that boundary without starting its chunk. + expect( + router.state.matches.find((match) => match.routeId === lazyChildRoute.id), + ).toMatchObject({ status: 'pending', isFetching: false }) + expect(lazyFn).not.toHaveBeenCalled() + expect(router.state.isLoading).toBe(false) + }) +}) diff --git a/packages/router-core/tests/issue-4572-preload-root-beforeload-flags.test.ts b/packages/router-core/tests/issue-4572-preload-root-beforeload-flags.test.ts new file mode 100644 index 0000000000..23495e5001 --- /dev/null +++ b/packages/router-core/tests/issue-4572-preload-root-beforeload-flags.test.ts @@ -0,0 +1,173 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * Issue #4572: an intent preload used to re-trigger active beforeLoad + * callbacks with stale navigation flags. Direct preloadRoute calls provide + * the core-level reduction of the reported Link hover behavior. + * + * A completed beforeLoad result is never cached. Every new preload therefore + * contextualizes its full route lane with fresh preload flags. + */ + +function createPreloadFixture() { + const rootBeforeLoad = vi.fn() + const indexBeforeLoad = vi.fn() + const aboutBeforeLoad = vi.fn() + const bagBeforeLoad = vi.fn() + const nestedBeforeLoad = vi.fn() + + const rootRoute = new BaseRootRoute({ beforeLoad: rootBeforeLoad }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + beforeLoad: indexBeforeLoad, + }) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + beforeLoad: aboutBeforeLoad, + }) + const bagRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '_bag', + beforeLoad: bagBeforeLoad, + }) + const nestedRoute = new BaseRoute({ + getParentRoute: () => bagRoute, + path: '/nested', + beforeLoad: nestedBeforeLoad, + }) + + return { + rootBeforeLoad, + indexBeforeLoad, + aboutBeforeLoad, + bagBeforeLoad, + nestedBeforeLoad, + nestedRoute, + router: createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + aboutRoute, + bagRoute.addChildren([nestedRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }), + } +} + +test('#4572: sibling preload contextualizes the full target lane with preload flags', async () => { + const { + rootBeforeLoad, + indexBeforeLoad, + aboutBeforeLoad, + bagBeforeLoad, + nestedBeforeLoad, + router, + } = createPreloadFixture() + + await router.load() + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(indexBeforeLoad).toHaveBeenCalledTimes(1) + expect(rootBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'enter', preload: false }), + ) + + await router.preloadRoute({ to: '/about' }) + expect(rootBeforeLoad).toHaveBeenCalledTimes(2) + expect(indexBeforeLoad).toHaveBeenCalledTimes(1) + expect(aboutBeforeLoad).toHaveBeenCalledTimes(1) + expect(aboutBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'preload', preload: true }), + ) + expect(rootBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ cause: 'preload', preload: true }), + ) + expect(bagBeforeLoad).not.toHaveBeenCalled() + expect(nestedBeforeLoad).not.toHaveBeenCalled() +}) + +test('#4572: active-route preload reruns beforeLoad with preload flags', async () => { + const { + rootBeforeLoad, + indexBeforeLoad, + aboutBeforeLoad, + bagBeforeLoad, + nestedBeforeLoad, + router, + } = createPreloadFixture() + + await router.load() + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(indexBeforeLoad).toHaveBeenCalledTimes(1) + expect(rootBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'enter', preload: false }), + ) + + rootBeforeLoad.mockClear() + indexBeforeLoad.mockClear() + await router.preloadRoute({ to: '/' }) + expect(rootBeforeLoad).toHaveBeenCalledOnce() + expect(indexBeforeLoad).toHaveBeenCalledOnce() + expect(rootBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'preload', preload: true }), + ) + expect(indexBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'preload', preload: true }), + ) + expect(aboutBeforeLoad).not.toHaveBeenCalled() + expect(bagBeforeLoad).not.toHaveBeenCalled() + expect(nestedBeforeLoad).not.toHaveBeenCalled() +}) + +test('#4572: active nested preload reruns its full pathless lane', async () => { + const { + rootBeforeLoad, + indexBeforeLoad, + aboutBeforeLoad, + bagBeforeLoad, + nestedBeforeLoad, + nestedRoute, + router, + } = createPreloadFixture() + + await router.load() + await router.navigate({ to: '/_bag/nested' }) + expect(rootBeforeLoad).toHaveBeenCalledTimes(2) + expect(rootBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ cause: 'stay', preload: false }), + ) + expect(indexBeforeLoad).toHaveBeenCalledTimes(1) + expect(aboutBeforeLoad).not.toHaveBeenCalled() + expect(bagBeforeLoad).toHaveBeenCalledTimes(1) + expect(bagBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'enter', preload: false }), + ) + expect(nestedBeforeLoad).toHaveBeenCalledTimes(1) + expect(nestedBeforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'enter', preload: false }), + ) + expect( + router.state.matches.some((match) => match.routeId === nestedRoute.id), + ).toBe(true) + rootBeforeLoad.mockClear() + indexBeforeLoad.mockClear() + aboutBeforeLoad.mockClear() + bagBeforeLoad.mockClear() + nestedBeforeLoad.mockClear() + + await router.preloadRoute({ to: '/_bag/nested' }) + expect(rootBeforeLoad).toHaveBeenCalledOnce() + expect(indexBeforeLoad).not.toHaveBeenCalled() + expect(aboutBeforeLoad).not.toHaveBeenCalled() + expect(bagBeforeLoad).toHaveBeenCalledOnce() + expect(nestedBeforeLoad).toHaveBeenCalledOnce() + for (const beforeLoad of [rootBeforeLoad, bagBeforeLoad, nestedBeforeLoad]) { + expect(beforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ cause: 'preload', preload: true }), + ) + } +}) diff --git a/packages/router-core/tests/issue-4684-head-on-beforeload-error.test.ts b/packages/router-core/tests/issue-4684-head-on-beforeload-error.test.ts new file mode 100644 index 0000000000..6bf84e75ce --- /dev/null +++ b/packages/router-core/tests/issue-4684-head-on-beforeload-error.test.ts @@ -0,0 +1,84 @@ +import { createMemoryHistory } from '@tanstack/history' +import { describe, expect, test } from 'vitest' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +// Existing Core projection coverage related to: +// https://github.com/TanStack/router/issues/4684 +// +// When beforeLoad throws, head() must still execute for the retained lane — +// the root head owns global stylesheets/title that the errorComponent page +// depends on, and the failing route's own head can provide error-specific +// head content. Rendering those projections through HeadContent is a framework +// concern; this suite asserts the Core-owned match projections. +describe('#4684 existing Core behavior: head executes when beforeLoad throws', () => { + const setup = (isServer: boolean) => { + const beforeLoadError = new Error('beforeLoad failed') + const rootHead = () => ({ + meta: [{ title: 'Root title' }], + links: [{ rel: 'stylesheet', href: '/global.css' }], + }) + const rootRoute = new BaseRootRoute({ head: rootHead }) + const failingRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/fail', + beforeLoad: () => { + throw beforeLoadError + }, + head: ({ match }) => ({ + meta: [{ title: match.error ? 'Error title' : 'Success title' }], + }), + errorComponent: () => 'error', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([failingRoute]), + history: createMemoryHistory({ initialEntries: ['/fail'] }), + isServer, + }) + + return { router, rootRoute, failingRoute, beforeLoadError } + } + + test('server load projects root and failing-route heads for the error lane', async () => { + const { router, rootRoute, failingRoute, beforeLoadError } = setup(true) + + const response = await loadServerResponse(router, '/fail') + + expect(response.status).toBe(500) + + const rootMatch = router.state.matches.find( + (match) => match.routeId === rootRoute.id, + ) + const failingMatch = router.state.matches.find( + (match) => match.routeId === failingRoute.id, + ) + expect(rootMatch?.meta).toEqual([{ title: 'Root title' }]) + expect(rootMatch?.links).toEqual([ + { rel: 'stylesheet', href: '/global.css' }, + ]) + expect(failingMatch?.status).toBe('error') + expect(failingMatch?.error).toBe(beforeLoadError) + expect(failingMatch?.meta).toEqual([{ title: 'Error title' }]) + }) + + test('client load projects root and failing-route heads for the error lane', async () => { + const { router, rootRoute, failingRoute, beforeLoadError } = setup(false) + + await router.load() + + const rootMatch = router.state.matches.find( + (match) => match.routeId === rootRoute.id, + ) + const failingMatch = router.state.matches.find( + (match) => match.routeId === failingRoute.id, + ) + expect(rootMatch?.meta).toEqual([{ title: 'Root title' }]) + expect(rootMatch?.links).toEqual([ + { rel: 'stylesheet', href: '/global.css' }, + ]) + expect(failingMatch?.status).toBe('error') + expect(failingMatch?.error).toBe(beforeLoadError) + expect(failingMatch?.meta).toEqual([{ title: 'Error title' }]) + }) +}) diff --git a/packages/router-core/tests/issue-4696-parent-context-search-normalization.test.ts b/packages/router-core/tests/issue-4696-parent-context-search-normalization.test.ts new file mode 100644 index 0000000000..20ef368318 --- /dev/null +++ b/packages/router-core/tests/issue-4696-parent-context-search-normalization.test.ts @@ -0,0 +1,102 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +// Existing-behavior coverage for https://github.com/TanStack/router/issues/4696 +test('#4696 existing behavior: normalized child search keeps reused parent context', async () => { + const rootLoader = vi.fn( + ({ context }: { context: Record }) => context, + ) + const dashboardBeforeLoad = vi.fn( + ({ context }: { context: Record }) => { + if (!context.isAuthenticated) { + throw new Error('Authentication context was lost') + } + }, + ) + const rootRoute = new BaseRootRoute({ + beforeLoad: async () => { + await Promise.resolve() + return { + initialData: { + user: { email: 'mr.user@gmail.com', role: 'user' }, + }, + initializationError: undefined, + isAuthenticated: true, + isAdmin: false, + } + }, + loader: rootLoader, + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const dashboardRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/dashboard', + validateSearch: () => ({ page: 0 }), + beforeLoad: dashboardBeforeLoad, + }) + const history = createMemoryHistory({ initialEntries: ['/'] }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, dashboardRoute]), + history, + }) + + await router.load() + expect(rootLoader).toHaveBeenCalledTimes(1) + expect(rootLoader.mock.calls[0]?.[0].context).toMatchObject({ + isAuthenticated: true, + }) + expect(router.state.location.pathname).toBe('/') + router.update({ + history: createMemoryHistory({ + initialEntries: ['/dashboard?page=0'], + }), + }) + await router.load() + expect(router.state.location.pathname).toBe('/dashboard') + expect(rootLoader).toHaveBeenCalledTimes(1) + expect(dashboardBeforeLoad).toHaveBeenCalledTimes(1) + expect(dashboardBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ isAuthenticated: true }), + }), + ) + expect( + router.state.matches.find((match) => match.routeId === rootRoute.id) + ?.loaderData, + ).toMatchObject({ isAuthenticated: true }) + + dashboardBeforeLoad.mockClear() + router.update({ + history: createMemoryHistory({ initialEntries: ['/dashboard'] }), + }) + await router.load() + + expect(dashboardBeforeLoad).toHaveBeenCalledTimes(1) + expect(dashboardBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ isAuthenticated: true }), + }), + ) + expect(rootLoader).toHaveBeenCalledTimes(1) + expect(router.state.location.pathname).toBe('/dashboard') + const rootMatch = router.state.matches.find( + (match) => match.routeId === rootRoute.id, + ) + const dashboardMatch = router.state.matches.find( + (match) => match.routeId === dashboardRoute.id, + ) + expect(rootMatch?.loaderData).toMatchObject({ + isAuthenticated: true, + isAdmin: false, + }) + expect(dashboardMatch).toMatchObject({ + status: 'success', + search: { page: 0 }, + searchError: undefined, + }) +}) diff --git a/packages/router-core/tests/issue-5106-hydrated-notfound-boundary.test.ts b/packages/router-core/tests/issue-5106-hydrated-notfound-boundary.test.ts new file mode 100644 index 0000000000..5ee450ee35 --- /dev/null +++ b/packages/router-core/tests/issue-5106-hydrated-notfound-boundary.test.ts @@ -0,0 +1,274 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, isNotFound } from '../src' +import { hydrate } from '../src/ssr/client' +import { dehydrateSsrMatchId } from '../src/ssr/ssr-match-id' +import { createTestRouter } from './routerTestUtils' +import type { TsrSsrGlobal } from '../src/ssr/types' +import type { Manifest } from '../src/manifest' + +const testManifest: Manifest = { routes: {} } + +// The child-owned case is supplemental Core state coverage for +// https://github.com/TanStack/router/issues/5106. The reported rendered React +// hydration symptom is not observable at this layer. The ancestor-owned case +// is a generic terminal-prefix invariant, not an issue #5106 reproduction. +describe('hydrated notFound boundary coverage', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + ;(global as any).window = mockWindow + }) + + afterEach(() => { + delete (global as any).window + vi.restoreAllMocks() + }) + + it('#5106 existing behavior: adopts a child-owned boundary without invoking its loaders during hydration', async () => { + const postsLoader = vi.fn(() => 'posts-data') + const postLoader = vi.fn(() => 'post-data') + const history = createMemoryHistory({ + initialEntries: ['/posts/i-do-not-exist'], + }) + + const rootRoute = new BaseRootRoute({}) + const postsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + loader: postsLoader, + component: () => 'Posts', + }) + const postRoute = new BaseRoute({ + getParentRoute: () => postsRoute, + path: '/$postId', + loader: postLoader, + component: () => 'Post', + notFoundComponent: () => 'Post not found', + }) + const safeLoader = vi.fn(() => 'safe-data') + const safeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/safe', + loader: safeLoader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + postsRoute.addChildren([postRoute]), + safeRoute, + ]), + history, + isServer: false, + }) + + const matches = router.matchRoutes(router.stores.location.get()) + expect(matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + postsRoute.id, + postRoute.id, + ]) + + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + // This synthetic payload models a child-owned boundary, so its + // terminal prefix includes the child match. + matches: [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'success' as const, + l: 'posts-data', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[2]!.id), + s: 'notFound' as const, + e: { isNotFound: true }, + ssr: true, + u: Date.now(), + }, + ], + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(router) + + const stateMatches = router.state.matches + expect(router.state.location.pathname).toBe('/posts/i-do-not-exist') + expect(router.state.isLoading).toBe(false) + expect(stateMatches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + postsRoute.id, + postRoute.id, + ]) + + // The parent keeps the data and status supplied by the payload. + expect(stateMatches[1]!.status).toBe('success') + expect(stateMatches[1]!.loaderData).toBe('posts-data') + + // The child is adopted as the notFound boundary. + expect(stateMatches[2]!.status).toBe('notFound') + expect(isNotFound(stateMatches[2]!.error)).toBe(true) + + // Hydration does not invoke either route loader represented by the + // payload. + expect(postsLoader).not.toHaveBeenCalled() + expect(postLoader).not.toHaveBeenCalled() + expect(safeLoader).not.toHaveBeenCalled() + + // Supplemental public liveness coverage: a later client navigation still + // completes. This does not exercise React's rendered hydration boundary. + await router.navigate({ to: '/safe' }) + expect(router.state.location.pathname).toBe('/safe') + expect(router.state.isLoading).toBe(false) + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + safeRoute.id, + ]) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: safeRoute.id, + status: 'success', + loaderData: 'safe-data', + }) + expect(safeLoader).toHaveBeenCalledTimes(1) + expect(postsLoader).not.toHaveBeenCalled() + expect(postLoader).not.toHaveBeenCalled() + }) + + it('generic terminal-prefix invariant: adopts an ancestor-owned boundary without presenting the omitted child', async () => { + const postsLoader = vi.fn(() => 'posts-data') + const postLoader = vi.fn(() => 'post-data') + const history = createMemoryHistory({ + initialEntries: ['/posts/i-do-not-exist'], + }) + + const rootRoute = new BaseRootRoute({}) + const postsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + loader: postsLoader, + component: () => 'Posts', + notFoundComponent: () => 'Posts not found', + }) + // The child has no notFoundComponent, so the synthetic payload below + // represents the parent as the selected boundary and omits the child. + const postRoute = new BaseRoute({ + getParentRoute: () => postsRoute, + path: '/$postId', + loader: postLoader, + component: () => 'Post', + }) + const safeLoader = vi.fn(() => 'safe-data') + const safeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/safe', + loader: safeLoader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + postsRoute.addChildren([postRoute]), + safeRoute, + ]), + history, + isServer: false, + }) + + const matches = router.matchRoutes(router.stores.location.get()) + expect(matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + postsRoute.id, + postRoute.id, + ]) + + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + // The represented terminal lane is capped at the parent boundary. + matches: [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'notFound' as const, + l: 'posts-data', + e: { isNotFound: true }, + ssr: true, + u: Date.now(), + }, + ], + }, + h: vi.fn(), + e: vi.fn(), + c: vi.fn(), + p: vi.fn(), + buffer: [], + initialized: false, + } + + await hydrate(router) + + // Hydration reconstructs the complete branch but executes only the + // represented terminal prefix. + const stateMatches = router.state.matches + expect(router.state.location.pathname).toBe('/posts/i-do-not-exist') + expect(router.state.isLoading).toBe(false) + expect(stateMatches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + postsRoute.id, + postRoute.id, + ]) + expect(stateMatches[1]!.routeId).toBe(postsRoute.id) + expect(stateMatches[1]!.status).toBe('notFound') + expect(isNotFound(stateMatches[1]!.error)).toBe(true) + // The boundary keeps the parent loader data supplied by the payload. + expect(stateMatches[1]!.loaderData).toBe('posts-data') + expect(stateMatches[2]).toMatchObject({ + routeId: postRoute.id, + status: 'pending', + }) + + // Neither the omitted child nor the represented parent runs client-side. + expect(postLoader).not.toHaveBeenCalled() + expect(postsLoader).not.toHaveBeenCalled() + expect(safeLoader).not.toHaveBeenCalled() + + // A later client navigation remains usable after adopting the prefix. + await router.navigate({ to: '/safe' }) + expect(router.state.location.pathname).toBe('/safe') + expect(router.state.isLoading).toBe(false) + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + safeRoute.id, + ]) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: safeRoute.id, + status: 'success', + loaderData: 'safe-data', + }) + expect(safeLoader).toHaveBeenCalledTimes(1) + expect(postLoader).not.toHaveBeenCalled() + expect(postsLoader).not.toHaveBeenCalled() + }) +}) diff --git a/packages/router-core/tests/issue-5427-hydration-root-global-not-found.test.ts b/packages/router-core/tests/issue-5427-hydration-root-global-not-found.test.ts new file mode 100644 index 0000000000..0a6119b349 --- /dev/null +++ b/packages/router-core/tests/issue-5427-hydration-root-global-not-found.test.ts @@ -0,0 +1,136 @@ +import { createMemoryHistory } from '@tanstack/history' +import { describe, expect, onTestFinished, test, vi } from 'vitest' +import { BaseRootRoute, BaseRoute } from '../src' +import { hydrate } from '../src/ssr/client' +import { dehydrateMatch } from '../src/ssr/ssr-server' +import { createTestRouter } from './routerTestUtils' +import type { TsrSsrGlobal } from '../src/ssr/types' + +function createRouter(initialEntry: string, isServer: boolean) { + const rootRoute = new BaseRootRoute({ + notFoundComponent: () => 'Root not found', + }) + const notFoundRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/404', + component: () => 'Not found route', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([notFoundRoute]), + history: createMemoryHistory({ initialEntries: [initialEntry] }), + isServer, + }) + + return { rootRoute, router } +} + +function createLayoutRouter(initialEntry: string, isServer: boolean) { + const rootRoute = new BaseRootRoute({}) + const layoutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + id: '_layout', + notFoundComponent: () => 'Layout not found', + }) + const notFoundRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/404', + }) + const notFoundIndexRoute = new BaseRoute({ + getParentRoute: () => notFoundRoute, + path: '/', + }) + const agentsRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/agents', + }) + const skillAgentRoute = new BaseRoute({ + getParentRoute: () => agentsRoute, + path: '/skill-agent', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + layoutRoute.addChildren([ + notFoundRoute.addChildren([notFoundIndexRoute]), + agentsRoute.addChildren([skillAgentRoute]), + ]), + ]), + history: createMemoryHistory({ initialEntries: [initialEntry] }), + isServer, + }) + + return { layoutRoute, router } +} + +describe('issue #5427: root-only global not-found hydration', () => { + test('resolves the client URL when the dehydrated /404 lane has a child match', async () => { + const { router: serverRouter } = createRouter('/404', true) + await serverRouter.load() + + const bootstrap: TsrSsrGlobal = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + matches: serverRouter.state.matches.map(dehydrateMatch), + }, + h: () => {}, + e: () => {}, + c: () => {}, + p: () => {}, + buffer: [], + } + const previousBootstrap = window.$_TSR + window.$_TSR = bootstrap + onTestFinished(() => { + window.$_TSR = previousBootstrap + }) + + const { rootRoute, router } = createRouter('/missing', false) + + await hydrate(router) + await vi.waitFor(() => { + expect(router.state.resolvedLocation?.pathname).toBe('/missing') + }) + + expect(router.state.matches).toHaveLength(1) + expect(router.state.matches[0]).toMatchObject({ + routeId: rootRoute.id, + _notFound: true, + }) + }) + + test('resolves a fuzzy client URL capped by an ancestor layout boundary', async () => { + const { router: serverRouter } = createLayoutRouter('/404', true) + await serverRouter.load() + expect(serverRouter.state.matches).toHaveLength(4) + + const bootstrap: TsrSsrGlobal = { + router: { + manifest: { routes: {} }, + dehydratedData: {}, + matches: serverRouter.state.matches.map(dehydrateMatch), + }, + h: () => {}, + e: () => {}, + c: () => {}, + p: () => {}, + buffer: [], + } + const previousBootstrap = window.$_TSR + window.$_TSR = bootstrap + onTestFinished(() => { + window.$_TSR = previousBootstrap + }) + + const { layoutRoute, router } = createLayoutRouter('/agents/missing', false) + + await hydrate(router) + + expect(router.state.resolvedLocation?.pathname).toBe('/agents/missing') + expect(router.state.matches).toHaveLength(3) + expect(router.state.matches[1]).toMatchObject({ + routeId: layoutRoute.id, + _notFound: true, + }) + }) +}) diff --git a/packages/router-core/tests/issue-6107-preload-chunk-failure.test.ts b/packages/router-core/tests/issue-6107-preload-chunk-failure.test.ts new file mode 100644 index 0000000000..32539b3676 --- /dev/null +++ b/packages/router-core/tests/issue-6107-preload-chunk-failure.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * Issue #6107: going offline and hovering a link (preload intent) made the + * failed dynamic import surface as an unhandled rejection in the console, and + * the error never reached the route's error component. + * + * Desired behavior: + * - a preload whose lazy chunk rejects resolves non-fatally, and + * - the failed chunk is evicted, so the subsequent real navigation retries + * the import and commits the chunk error onto the match (status 'error'), + * which is what framework error components render. + */ +describe('issue #6107: failed dynamic import of a lazy route chunk', () => { + test('preloadRoute failure is non-fatal and navigation retries and commits the chunk error', async () => { + const chunkError = new TypeError( + 'Failed to fetch dynamically imported module: /assets/posts.lazy-abc123.js', + ) + const lazyRoute = vi.fn(() => Promise.reject(chunkError)) + const rootRoute = new BaseRootRoute({}) + const postsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + }).lazy(lazyRoute) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([postsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + await router.preloadRoute({ to: '/posts' }) + expect(lazyRoute).toHaveBeenCalled() + const lazyCallsAfterPreload = lazyRoute.mock.calls.length + + await router.navigate({ to: '/posts' }) + const postsMatch = router.state.matches.find( + (match) => match.routeId === postsRoute.id, + ) + expect(lazyRoute.mock.calls.length).toBeGreaterThan(lazyCallsAfterPreload) + expect(postsMatch?.status).toBe('error') + expect(postsMatch?.error).toBe(chunkError) + }) +}) diff --git a/packages/router-core/tests/issue-6221-head-waits-for-loader.test.ts b/packages/router-core/tests/issue-6221-head-waits-for-loader.test.ts new file mode 100644 index 0000000000..a9f56ba8d3 --- /dev/null +++ b/packages/router-core/tests/issue-6221-head-waits-for-loader.test.ts @@ -0,0 +1,162 @@ +import { createMemoryHistory } from '@tanstack/history' +import { describe, expect, onTestFinished, test, vi } from 'vitest' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +// https://github.com/TanStack/router/issues/6221 +// +// head() must never compute from loaderData older than the data the lane +// commits. Two reported shapes: +// 1. Revisiting a route whose previous visit ended in notFound — the head +// must see the fresh successful loaderData, not run before the loader. +// 2. Revisiting a cached success match that gets a stale reload — the head +// title must not lag one loaderData run behind. +describe('issue #6221: head does not run before loader data is ready', () => { + test('existing-behavior control: head sees fresh loaderData after a notFound visit', async () => { + let authed = false + const articleResponse = createControlledPromise<{ title: string }>() + const successfulLoadStarted = createControlledPromise() + + const articleLoader = async () => { + if (!authed) { + await Promise.resolve() + throw notFound() + } + + successfulLoadStarted.resolve() + return articleResponse + } + + const rootRoute = new BaseRootRoute({}) + const dashboardRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/dashboard', + }) + const articleRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/article', + loader: articleLoader, + notFoundComponent: () => 'Not found', + head: ({ loaderData }) => ({ + meta: [{ title: loaderData?.title ?? 'Generic title' }], + }), + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([dashboardRoute, articleRoute]), + history: createMemoryHistory({ initialEntries: ['/article'] }), + }) + const backLoadFinished = createControlledPromise() + let backLoadStarted = false + let unsubscribe: (() => void) | undefined + + try { + await router.load() + const notFoundMatch = router.state.matches.find( + (match) => match.routeId === articleRoute.id, + ) + expect(notFoundMatch?.status).toBe('notFound') + expect(notFoundMatch?.meta).toEqual([{ title: 'Generic title' }]) + + // Model the reported auth redirect and browser Back without relying on a + // wall-clock loader delay. + authed = true + await router.navigate({ to: '/dashboard' }) + unsubscribe = router.history.subscribe(() => { + backLoadStarted = true + void router.load().then( + () => backLoadFinished.resolve(), + (error) => backLoadFinished.reject(error), + ) + }) + router.history.back() + await successfulLoadStarted + expect(articleResponse.status).toBe('pending') + + articleResponse.resolve({ title: 'Article 123' }) + await backLoadFinished + + const articleMatch = router.state.matches.find( + (match) => match.routeId === articleRoute.id, + ) + expect(articleMatch?.status).toBe('success') + expect(articleMatch?.loaderData).toEqual({ title: 'Article 123' }) + expect(articleMatch?.meta).toEqual([{ title: 'Article 123' }]) + } finally { + articleResponse.resolve({ title: 'Article 123' }) + if (backLoadStarted) { + await backLoadFinished.catch(() => undefined) + } + unsubscribe?.() + } + }) + + test('head title does not lag one loaderData run behind on revisits', async () => { + const secondLoaderStarted = createControlledPromise() + const secondLoaderResponse = createControlledPromise<{ author: string }>() + let version = 0 + const quoteLoader = () => { + version += 1 + if (version === 2) { + secondLoaderStarted.resolve() + return secondLoaderResponse + } + + return { author: 'author-1' } + } + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const quoteRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/quote', + loader: quoteLoader, + staleTime: 0, + head: ({ loaderData }) => ({ + meta: [{ title: `Quote by ${loaderData?.author ?? '...'}` }], + }), + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, quoteRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const getQuoteMatch = () => + router.state.matches.find((match) => match.routeId === quoteRoute.id) + onTestFinished(() => { + secondLoaderResponse.resolve({ author: 'author-2' }) + }) + + await router.load() + + await router.navigate({ to: '/quote' }) + expect(getQuoteMatch()?.loaderData).toEqual({ author: 'author-1' }) + expect(getQuoteMatch()?.meta).toEqual([{ title: 'Quote by author-1' }]) + + await router.navigate({ to: '/' }) + const revisit = router.navigate({ to: '/quote' }) + await secondLoaderStarted + await revisit + + // The cached generation remains internally consistent while its stale + // loader is pending in the background. + expect(secondLoaderResponse.status).toBe('pending') + expect(getQuoteMatch()?.loaderData).toEqual({ author: 'author-1' }) + expect(getQuoteMatch()?.meta).toEqual([{ title: 'Quote by author-1' }]) + + secondLoaderResponse.resolve({ author: 'author-2' }) + await vi.waitFor(() => { + expect(getQuoteMatch()?.loaderData).toEqual({ author: 'author-2' }) + expect(getQuoteMatch()?.meta).toEqual([{ title: 'Quote by author-2' }]) + }) + }) +}) diff --git a/packages/router-core/tests/issue-6351-fuzzy-notfound-layout.test.ts b/packages/router-core/tests/issue-6351-fuzzy-notfound-layout.test.ts new file mode 100644 index 0000000000..9fec0992a4 --- /dev/null +++ b/packages/router-core/tests/issue-6351-fuzzy-notfound-layout.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * https://github.com/TanStack/router/issues/6351 + * + * A fuzzy (unmatched-URL) 404 must be attributed to the nearest matched + * ancestor that can actually render it: the deepest matched route with + * children that defines a `notFoundComponent`. Attributing it to the + * deepest matched route with children regardless of whether it has a + * `notFoundComponent` skips pathless-layout/ancestor `notFoundComponent`s + * and makes the framework fall back to `defaultNotFoundComponent`. + * + * When no matched route defines a `notFoundComponent`, the previous + * behavior (deepest matched route with children) is preserved, and + * `notFoundMode: 'root'` still attributes the 404 to the root route. + * This Core suite asserts boundary ownership; rendered fallback output needs + * framework-level coverage. + */ + +function setup(opts: { + layoutNotFoundComponent?: () => unknown + agentsNotFoundComponent?: () => unknown + agentsContext?: () => Record + agentsBeforeLoad?: () => Record + agentsLoader?: () => null + notFoundMode?: 'fuzzy' | 'root' + isServer?: boolean +}) { + const rootRoute = new BaseRootRoute({}) + const layoutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + id: '_authenticated', + notFoundComponent: opts.layoutNotFoundComponent, + }) + const agentsRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/agents', + notFoundComponent: opts.agentsNotFoundComponent, + context: opts.agentsContext, + beforeLoad: opts.agentsBeforeLoad, + loader: opts.agentsLoader, + }) + const skillAgentRoute = new BaseRoute({ + getParentRoute: () => agentsRoute, + path: '/skill-agent', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + layoutRoute.addChildren([agentsRoute.addChildren([skillAgentRoute])]), + ]), + history: createMemoryHistory({ initialEntries: ['/agents/skill-agen'] }), + notFoundMode: opts.notFoundMode, + isServer: opts.isServer, + }) + + return { router, rootRoute, layoutRoute, agentsRoute } +} + +function _notFoundRouteIds(router: { + state: { matches: Array<{ routeId: string; _notFound?: boolean }> } +}) { + return router.state.matches + .filter((match) => match._notFound) + .map((match) => match.routeId) +} + +describe('issue #6351: fuzzy notFound honors pathless layout boundaries', () => { + test('lands on the pathless layout when the deeper route has no boundary', async () => { + const { router, rootRoute, layoutRoute, agentsRoute } = setup({ + layoutNotFoundComponent: () => 'layout not found', + }) + + await router.load() + + expect(router.state.location.pathname).toBe('/agents/skill-agen') + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + layoutRoute.id, + agentsRoute.id, + ]) + expect(_notFoundRouteIds(router)).toEqual([layoutRoute.id]) + }) + + test('boundary-enabled control: prefers the deeper matched route', async () => { + const { router, agentsRoute } = setup({ + layoutNotFoundComponent: () => 'layout not found', + agentsNotFoundComponent: () => 'agents not found', + }) + + await router.load() + + expect(_notFoundRouteIds(router)).toEqual([agentsRoute.id]) + }) + + test.each(['client', 'server'] as const)( + 'fuzzy ancestor 404 skips hidden descendant lifecycle callbacks on the %s', + async (environment) => { + const context = vi.fn(() => ({})) + const beforeLoad = vi.fn(() => ({})) + const loader = vi.fn(() => null) + const { router } = setup({ + layoutNotFoundComponent: () => 'layout not found', + agentsContext: context, + agentsBeforeLoad: beforeLoad, + agentsLoader: loader, + notFoundMode: 'fuzzy', + isServer: environment === 'server', + }) + + await router.load() + + expect(context).not.toHaveBeenCalled() + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + }, + ) +}) + +describe('generic global notFound attribution controls', () => { + test('fuzzy mode falls back to the deepest matched route with children when no boundary exists', async () => { + const { router, agentsRoute } = setup({}) + + await router.load() + + expect(_notFoundRouteIds(router)).toEqual([agentsRoute.id]) + }) + + test('root mode attributes the notFound to root even when a layout boundary exists', async () => { + const { router, rootRoute } = setup({ + layoutNotFoundComponent: () => 'layout not found', + notFoundMode: 'root', + }) + + await router.load() + + expect(_notFoundRouteIds(router)).toEqual([rootRoute.id]) + }) +}) diff --git a/packages/router-core/tests/issue-6371-search-default-normalization-abort.test.ts b/packages/router-core/tests/issue-6371-search-default-normalization-abort.test.ts new file mode 100644 index 0000000000..6f039b45da --- /dev/null +++ b/packages/router-core/tests/issue-6371-search-default-normalization-abort.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, trimPathRight } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * Repro for https://github.com/TanStack/router/issues/6371 + * + * Entering a route directly via URL without search params, while + * validateSearch supplies defaults, makes the framework Transitioner commit a + * canonical replace-navigation (URL with the defaulted search) right after + * the mount load already started. The canonical pass may reuse that loader + * generation, and the URL-only replacement must not abort it or surface an + * AbortError. + */ + +describe('issue #6371: search default normalization aborts the mount load silently', () => { + test('canonical replace after validateSearch defaults does not surface AbortError', async () => { + const invocations: Array<{ resolve: () => void; signal: AbortSignal }> = [] + + const rootRoute = new BaseRootRoute({}) + const aboutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/about', + validateSearch: (search: Record) => ({ + page: typeof search.page === 'number' ? search.page : 1, + }), + loader: ({ abortController }: { abortController: AbortController }) => + new Promise((resolve, reject) => { + invocations.push({ + resolve: () => resolve('about data'), + signal: abortController.signal, + }) + abortController.signal.addEventListener('abort', () => { + const abortError = new Error('signal is aborted without reason') + abortError.name = 'AbortError' + reject(abortError) + }) + }), + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([aboutRoute]), + history: createMemoryHistory({ initialEntries: ['/about'] }), + }) + + // Mount load for the raw URL (no search params in the href yet). + const initialLoad = router.load() + await vi.waitFor(() => expect(invocations.length).toBe(1)) + + // What the framework Transitioner does on mount: rebuild the canonical + // location with validated search and replace if the public href differs. + const nextLocation = router.buildLocation({ + to: router.latestLocation.pathname, + search: true, + params: true, + hash: true, + state: true, + _includeValidateSearch: true, + } as any) + + expect(nextLocation.publicHref).toContain('page=1') + expect(trimPathRight(router.latestLocation.publicHref)).not.toBe( + trimPathRight(nextLocation.publicHref), + ) + + // In core (no history subscribers) commitLocation starts the second load. + void router.commitLocation({ ...nextLocation, replace: true } as any) + + await Promise.resolve() + expect(invocations).toHaveLength(1) + expect(invocations[0]!.signal.aborted).toBe(false) + + for (const match of router.state.matches) { + expect(match.status).not.toBe('error') + expect((match.error as Error | undefined)?.name).not.toBe('AbortError') + } + + invocations[0]!.resolve() + // Awaiting the superseded mount load also awaits the superseding chain. + await initialLoad + + expect(invocations).toHaveLength(1) + + const aboutMatch = router.state.matches.find( + (match) => match.routeId === aboutRoute.id, + )! + expect(aboutMatch.status).toBe('success') + expect(aboutMatch.error).toBeUndefined() + expect(aboutMatch.loaderData).toBe('about data') + expect(aboutMatch.search).toEqual({ page: 1 }) + + expect(router.state.location.search).toEqual({ page: 1 }) + expect(router.latestLocation.publicHref).toContain('page=1') + }) +}) diff --git a/packages/router-core/tests/issue-7379-head-matches-direct-load.test.ts b/packages/router-core/tests/issue-7379-head-matches-direct-load.test.ts new file mode 100644 index 0000000000..1768988e47 --- /dev/null +++ b/packages/router-core/tests/issue-7379-head-matches-direct-load.test.ts @@ -0,0 +1,81 @@ +import { createMemoryHistory } from '@tanstack/history' +import { describe, expect, test } from 'vitest' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +// https://github.com/TanStack/router/issues/7379 +// +// On a direct (initial) load, head({ matches }) must observe loaderData and +// context on every ancestor match so a breadcrumb-style title can be built. +describe('issue #7379: head({ matches }) has loaderData and context on direct load', () => { + test('initial load executes head after loaders with populated matches', async () => { + const seenMatches: Array< + Array<{ routeId: string; loaderData: unknown; context: unknown }> + > = [] + + const rootRoute = new BaseRootRoute({}) + const nestedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/nested', + beforeLoad: async () => { + await Promise.resolve() + return { section: 'nested-section' } + }, + loader: async () => { + await Promise.resolve() + return { crumb: 'Nested' } + }, + }) + const evenRoute = new BaseRoute({ + getParentRoute: () => nestedRoute, + path: 'even', + loader: async () => { + await Promise.resolve() + return { crumb: 'Even' } + }, + head: ({ matches }) => { + seenMatches.push( + matches.map((match) => ({ + routeId: match.routeId, + loaderData: match.loaderData, + context: match.context, + })), + ) + const crumbs = matches + .map((match) => match.loaderData?.crumb) + .filter(Boolean) + return { + meta: [{ title: [...crumbs, 'Company Inc.'].join(' - ') }], + } + }, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([nestedRoute.addChildren([evenRoute])]), + history: createMemoryHistory({ initialEntries: ['/nested/even'] }), + }) + + // Direct load: this is the first and only load of the router. + await router.load() + + expect(seenMatches.length).toBeGreaterThan(0) + const seen = seenMatches.at(-1)! + + const seenNested = seen.find((entry) => entry.routeId === nestedRoute.id) + const seenEven = seen.find((entry) => entry.routeId === evenRoute.id) + + // loaderData is present on every match handed to head(). + expect(seenNested?.loaderData).toEqual({ crumb: 'Nested' }) + expect(seenEven?.loaderData).toEqual({ crumb: 'Even' }) + + // context (incl. beforeLoad context) is present as well. + expect(seenNested?.context).toMatchObject({ section: 'nested-section' }) + expect(seenEven?.context).toMatchObject({ section: 'nested-section' }) + + // The breadcrumb title is complete on the committed match. + const evenMatch = router.state.matches.find( + (match) => match.routeId === evenRoute.id, + ) + expect(evenMatch?.meta).toEqual([{ title: 'Nested - Even - Company Inc.' }]) + }) +}) diff --git a/packages/router-core/tests/issue-7602-parent-beforeload-context-child-loader.test.ts b/packages/router-core/tests/issue-7602-parent-beforeload-context-child-loader.test.ts new file mode 100644 index 0000000000..41549b58fd --- /dev/null +++ b/packages/router-core/tests/issue-7602-parent-beforeload-context-child-loader.test.ts @@ -0,0 +1,108 @@ +import { expect, onTestFinished, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +// https://github.com/TanStack/router/issues/7602 +test('#7602: browser Back republishes a cached child with fresh parent beforeLoad context', async () => { + const loaderContexts: Array<{ + number?: number + generation?: number + }> = [] + const reloadGate = createControlledPromise() + let loaderRuns = 0 + let beforeLoadRuns = 0 + + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reproRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/repro', + beforeLoad: async () => { + await Promise.resolve() + return { number: 42, generation: ++beforeLoadRuns } + }, + }) + const reproIndexRoute = new BaseRoute({ + getParentRoute: () => reproRoute, + path: '/', + loader: async ({ + context, + }: { + context: { number?: number; generation?: number } + }) => { + loaderRuns++ + loaderContexts.push({ + number: context.number, + generation: context.generation, + }) + if (loaderRuns === 2) { + await reloadGate + } + return { visit: loaderRuns } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + homeRoute, + reproRoute.addChildren([reproIndexRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/repro'] }), + }) + const getChildMatch = () => + router.state.matches.find((match) => match.routeId === reproIndexRoute.id) + // RouterProvider's Transitioner installs this subscription in applications. + // It is what turns a browser Back history event into a router load. + const unsubscribe = router.history.subscribe(router.load) + onTestFinished(() => { + reloadGate.resolve() + unsubscribe() + }) + + await router.load() + expect(router.state.location.pathname).toBe('/repro') + expect(getChildMatch()).toMatchObject({ + routeId: reproIndexRoute.id, + status: 'success', + loaderData: { visit: 1 }, + context: { number: 42, generation: 1 }, + }) + expect(loaderContexts).toEqual([{ number: 42, generation: 1 }]) + + await router.navigate({ to: '/' }) + expect(router.state.location.pathname).toBe('/') + expect(router.state.isLoading).toBe(false) + expect(getChildMatch()).toBeUndefined() + + router.history.back() + await vi.waitFor(() => expect(loaderContexts).toHaveLength(2)) + + // The cached success is visible while its stale loader reloads. It must + // never be published with the parent's context contribution missing. + expect(router.state.location.pathname).toBe('/repro') + expect(getChildMatch()).toMatchObject({ + status: 'success', + loaderData: { visit: 1 }, + context: { number: 42, generation: 2 }, + }) + expect(loaderContexts).toEqual([ + { number: 42, generation: 1 }, + { number: 42, generation: 2 }, + ]) + + reloadGate.resolve() + await vi.waitFor(() => + expect(getChildMatch()?.loaderData).toEqual({ visit: 2 }), + ) + expect(router.state.location.pathname).toBe('/repro') + expect(router.state.isLoading).toBe(false) + expect(getChildMatch()?.context).toMatchObject({ + number: 42, + generation: 2, + }) + expect(beforeLoadRuns).toBe(2) + expect(loaderRuns).toBe(2) +}) diff --git a/packages/router-core/tests/issue-7759-in-flight-preload-eviction.test.ts b/packages/router-core/tests/issue-7759-in-flight-preload-eviction.test.ts new file mode 100644 index 0000000000..66f76aaa33 --- /dev/null +++ b/packages/router-core/tests/issue-7759-in-flight-preload-eviction.test.ts @@ -0,0 +1,43 @@ +import { expect, onTestFinished, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('issue #7759: evicting an in-flight preload finishes without an internal error', async () => { + let signalLoaderStarted!: () => void + let resolveLoader!: (value: string) => void + + const loaderStarted = new Promise((resolve) => { + signalLoaderStarted = resolve + }) + const loaderResult = new Promise((resolve) => { + resolveLoader = resolve + }) + + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader: () => { + signalLoaderStarted() + return loaderResult + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory(), + }) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + onTestFinished(() => { + consoleError.mockRestore() + }) + + const preload = router.preloadRoute({ to: '/target' }) + await loaderStarted + + router.clearCache() + resolveLoader('loaded') + + await preload + expect(consoleError).not.toHaveBeenCalled() +}) diff --git a/packages/router-core/tests/load-client-wait-for.test.ts b/packages/router-core/tests/load-client-wait-for.test.ts new file mode 100644 index 0000000000..5ef9febb59 --- /dev/null +++ b/packages/router-core/tests/load-client-wait-for.test.ts @@ -0,0 +1,209 @@ +import { describe, expect, test, vi } from 'vitest' +import { waitForReason } from '../src/await-signal' +import { waitFor } from '../src/load-client' +import { waitForRequest } from '../src/ssr/createRequestHandler' + +describe('waitFor', () => { + test('observes a rejected value when the signal is already aborted', async () => { + const controller = new AbortController() + const error = new Error('late failure') + controller.abort() + + await expect(waitFor(undefined, controller.signal)).rejects.toBe( + controller.signal, + ) + await expect( + waitFor(Promise.reject(error), controller.signal), + ).rejects.toBe(controller.signal) + + await Promise.resolve() + }) + + test('preserves reason-based cancellation and observes a late value', async () => { + const controller = new AbortController() + const reason = new Error('request canceled') + const late = Promise.resolve('late response') + const onLate = vi.fn() + controller.abort(reason) + + await expect(waitForReason(late, controller.signal, onLate)).rejects.toBe( + reason, + ) + await vi.waitFor(() => expect(onLate).toHaveBeenCalledWith('late response')) + }) +}) + +describe('waitForRequest', () => { + test('shares one abort listener across concurrent and sequential waits', async () => { + const controller = new AbortController() + const addEventListener = vi.spyOn(controller.signal, 'addEventListener') + let resolveFirst!: (value: string) => void + let resolveSecond!: (value: string) => void + let resolveThird!: (value: string) => void + const first = new Promise((resolve) => { + resolveFirst = resolve + }) + const second = new Promise((resolve) => { + resolveSecond = resolve + }) + const third = new Promise((resolve) => { + resolveThird = resolve + }) + + const firstResult = waitForRequest(first, controller.signal) + const secondResult = waitForRequest(second, controller.signal) + + expect(addEventListener).toHaveBeenCalledTimes(1) + resolveFirst('first') + resolveSecond('second') + await expect(firstResult).resolves.toBe('first') + await expect(secondResult).resolves.toBe('second') + + const thirdResult = waitForRequest(third, controller.signal) + expect(addEventListener).toHaveBeenCalledTimes(1) + + const reason = new Error('request canceled') + controller.abort(reason) + await expect(thirdResult).rejects.toBe(reason) + resolveThird('third') + await Promise.resolve() + }) + + test('rejects all active waits and observes their late values', async () => { + const controller = new AbortController() + const reason = new Error('request canceled') + const onFirstLate = vi.fn() + const onSecondLate = vi.fn() + let resolveFirst!: (value: string) => void + let resolveSecond!: (value: string) => void + const first = new Promise((resolve) => { + resolveFirst = resolve + }) + const second = new Promise((resolve) => { + resolveSecond = resolve + }) + const firstResult = waitForRequest(first, controller.signal, onFirstLate) + const secondResult = waitForRequest(second, controller.signal, onSecondLate) + + controller.abort(reason) + await expect(firstResult).rejects.toBe(reason) + await expect(secondResult).rejects.toBe(reason) + + resolveFirst('first') + resolveSecond('second') + await vi.waitFor(() => { + expect(onFirstLate).toHaveBeenCalledWith('first') + expect(onSecondLate).toHaveBeenCalledWith('second') + }) + }) + + test('aborts remaining waits after an out-of-order rejection', async () => { + const controller = new AbortController() + const reason = new Error('request canceled') + const failure = new Error('wait failed') + const onLate = vi.fn() + let resolveFirst!: (value: string) => void + let rejectSecond!: (error: Error) => void + let rejectThird!: (error: Error) => void + const first = new Promise((resolve) => { + resolveFirst = resolve + }) + const second = new Promise((_, reject) => { + rejectSecond = reject + }) + const third = new Promise((_, reject) => { + rejectThird = reject + }) + const firstResult = waitForRequest(first, controller.signal, onLate) + const secondResult = waitForRequest(second, controller.signal) + const thirdResult = waitForRequest(third, controller.signal) + + rejectSecond(failure) + await expect(secondResult).rejects.toBe(failure) + + controller.abort(reason) + await expect(firstResult).rejects.toBe(reason) + await expect(thirdResult).rejects.toBe(reason) + + resolveFirst('late') + rejectThird(new Error('late failure')) + await vi.waitFor(() => expect(onLate).toHaveBeenCalledWith('late')) + }) + + test('isolates waits for different request signals', async () => { + const firstController = new AbortController() + const secondController = new AbortController() + const firstReason = new Error('first request canceled') + const firstAddEventListener = vi.spyOn( + firstController.signal, + 'addEventListener', + ) + const secondAddEventListener = vi.spyOn( + secondController.signal, + 'addEventListener', + ) + let resolveFirst!: (value: string) => void + let resolveSecond!: (value: string) => void + const first = new Promise((resolve) => { + resolveFirst = resolve + }) + const second = new Promise((resolve) => { + resolveSecond = resolve + }) + const firstResult = waitForRequest(first, firstController.signal) + const secondResult = waitForRequest(second, secondController.signal) + + expect(firstAddEventListener).toHaveBeenCalledOnce() + expect(secondAddEventListener).toHaveBeenCalledOnce() + + firstController.abort(firstReason) + await expect(firstResult).rejects.toBe(firstReason) + + resolveSecond('second result') + await expect(secondResult).resolves.toBe('second result') + resolveFirst('late first result') + await Promise.resolve() + }) + + test('observes fulfilled and rejected values when already aborted', async () => { + const controller = new AbortController() + const reason = new Error('request canceled') + const lateError = new Error('late failure') + const onLate = vi.fn() + controller.abort(reason) + + await expect( + waitForRequest(Promise.resolve('late'), controller.signal, onLate), + ).rejects.toBe(reason) + await expect( + waitForRequest(Promise.reject(lateError), controller.signal), + ).rejects.toBe(reason) + + await vi.waitFor(() => expect(onLate).toHaveBeenCalledWith('late')) + }) + + test('handles an abort during listener registration', async () => { + const controller = new AbortController() + const reason = new Error('request canceled') + const onLate = vi.fn() + const addEventListener = controller.signal.addEventListener.bind( + controller.signal, + ) + vi.spyOn(controller.signal, 'addEventListener').mockImplementation( + (type, listener, options) => { + addEventListener(type, listener, options) + controller.abort(reason) + }, + ) + let resolve!: (value: string) => void + const value = new Promise((resolveValue) => { + resolve = resolveValue + }) + + const result = waitForRequest(value, controller.signal, onLate) + await expect(result).rejects.toBe(reason) + + resolve('late') + await vi.waitFor(() => expect(onLate).toHaveBeenCalledWith('late')) + }) +}) diff --git a/packages/router-core/tests/load-route-chunk.test.ts b/packages/router-core/tests/load-route-chunk.test.ts new file mode 100644 index 0000000000..73179f9f32 --- /dev/null +++ b/packages/router-core/tests/load-route-chunk.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('router.loadRouteChunk exposes a failed lazy import and remains retryable', async () => { + const chunkError = new TypeError('Failed to fetch lazy route') + const component = () => null + let fail = true + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/route', + }).lazy(() => { + if (fail) { + fail = false + return Promise.reject(chunkError) + } + return Promise.resolve({ options: { component } } as any) + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await expect(router.loadRouteChunk(route as any)).rejects.toBe(chunkError) + await router.loadRouteChunk(route as any) + + expect(route.options.component).toBe(component) +}) diff --git a/packages/router-core/tests/load.test.ts b/packages/router-core/tests/load.test.ts index 1ea6fca30e..53a6444f0b 100644 --- a/packages/router-core/tests/load.test.ts +++ b/packages/router-core/tests/load.test.ts @@ -3,14 +3,13 @@ import { createMemoryHistory } from '@tanstack/history' import { BaseRootRoute, BaseRoute, + createControlledPromise, notFound, redirect, rootRouteId, } from '../src' -import { createTestRouter } from './routerTestUtils' -import { loadMatches } from '../src/load-matches' +import { createTestRouter, loadServerResponse } from './routerTestUtils' import type { - AnyRouter, LoaderStaleReloadMode, RootRouteOptions, RouterCore, @@ -70,14 +69,10 @@ describe('redirect resolution', () => { isServer: true, }) - await router.load() + const response = await loadServerResponse(router, initialPath) - expect(router.state.redirect).toEqual( - expect.objectContaining({ - options: expect.objectContaining({ href: '/undefined' }), - }), - ) - expect(router.state.redirect?.headers.get('Location')).toBe('/undefined') + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/undefined') }, ) }) @@ -153,12 +148,7 @@ describe('beforeLoad skip or exec', () => { test('exec on regular nav', async () => { const beforeLoad = vi.fn(() => Promise.resolve({ hello: 'world' })) const router = setup({ beforeLoad }) - const navigation = router.navigate({ to: '/foo' }) - expect(beforeLoad).toHaveBeenCalledTimes(1) - expect(router.stores.pendingMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) - await navigation + await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') expect(router.state.matches).toEqual( expect.arrayContaining([ @@ -181,7 +171,6 @@ describe('beforeLoad skip or exec', () => { await router.navigate({ to: '/foo' }) - expect(router.state.statusCode).toBe(500) expect(router.state.matches).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -202,10 +191,9 @@ describe('beforeLoad skip or exec', () => { await router.navigate({ to: '/foo' }) - expect(router.state.statusCode).toBe(500) - expect(router.state.matches.find((d) => d.id === '/foo/foo')?.error).toBe( - thrown, - ) + const match = router.state.matches.find((d) => d.id === '/foo/foo') + expect(match?.status).toBe('error') + expect(match?.error).toBe(thrown) expect(thrown).toEqual({ type: 'domain-error' }) }) @@ -213,12 +201,10 @@ describe('beforeLoad skip or exec', () => { const beforeLoad = vi.fn() const router = setup({ beforeLoad }) await router.preloadRoute({ to: '/foo' }) - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) await sleep(10) await router.navigate({ to: '/foo' }) + expect(router.state.location.pathname).toBe('/foo') expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -227,11 +213,9 @@ describe('beforeLoad skip or exec', () => { const router = setup({ beforeLoad }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) await router.navigate({ to: '/foo' }) + expect(router.state.location.pathname).toBe('/foo') expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -274,16 +258,11 @@ describe('beforeLoad skip or exec', () => { beforeLoad, }) await router.preloadRoute({ to: '/foo' }) - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) await sleep(10) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) + expect(router.state.matches.at(-1)?.status).toBe('success') expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -297,15 +276,10 @@ describe('beforeLoad skip or exec', () => { }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) + expect(router.state.matches.at(-1)?.status).toBe('success') expect(beforeLoad).toHaveBeenCalledTimes(2) }) @@ -421,12 +395,7 @@ describe('loader skip or exec', () => { test('exec on regular nav', async () => { const loader = vi.fn(() => Promise.resolve({ hello: 'world' })) const router = setup({ loader }) - const navigation = router.navigate({ to: '/foo' }) - expect(loader).toHaveBeenCalledTimes(1) - expect(router.stores.pendingMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) - await navigation + await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') expect(router.state.matches).toEqual( expect.arrayContaining([ @@ -445,25 +414,21 @@ describe('loader skip or exec', () => { const loader = vi.fn() const router = setup({ loader }) await router.preloadRoute({ to: '/foo' }) - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) await sleep(10) await router.navigate({ to: '/foo' }) - expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.location.pathname).toBe('/foo') + expect(loader).toHaveBeenCalledTimes(1) }) test('skip if resolved preload (success) within staleTime duration', async () => { const loader = vi.fn() const router = setup({ loader, staleTime: 1000 }) await router.preloadRoute({ to: '/foo' }) - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) await sleep(10) await router.navigate({ to: '/foo' }) + expect(router.state.location.pathname).toBe('/foo') expect(loader).toHaveBeenCalledTimes(1) }) @@ -472,11 +437,9 @@ describe('loader skip or exec', () => { const router = setup({ loader }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) await router.navigate({ to: '/foo' }) + expect(router.state.location.pathname).toBe('/foo') expect(loader).toHaveBeenCalledTimes(1) }) @@ -495,19 +458,35 @@ describe('loader skip or exec', () => { expect(loader).toHaveBeenCalledTimes(2) }) - test('skip if pending preload (notFound)', async () => { + test('navigation shares a pending preload loader notFound', async () => { + const loaderGate = createControlledPromise() const loader: Loader = vi.fn(async ({ preload }) => { - await sleep(100) + await loaderGate if (preload) throw notFound() }) const router = setup({ loader, }) - router.preloadRoute({ to: '/foo' }) + const preload = router.preloadRoute({ to: '/foo' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + const navigation = router.navigate({ to: '/foo' }) await Promise.resolve() - await router.navigate({ to: '/foo' }) + expect(loader).toHaveBeenCalledOnce() - expect(loader).toHaveBeenCalledTimes(1) + loaderGate.resolve() + await Promise.all([preload, navigation]) + + expect(router.state.location.pathname).toBe('/foo') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: '/foo', + status: 'notFound', + error: { + isNotFound: true, + routeId: '/foo', + }, + }) + expect(loader).toHaveBeenCalledOnce() }) test('exec if rejected preload (redirect)', async () => { @@ -519,20 +498,15 @@ describe('loader skip or exec', () => { loader, }) await router.preloadRoute({ to: '/foo' }) - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) await sleep(10) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/foo') - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) + expect(router.state.matches.at(-1)?.status).toBe('success') expect(loader).toHaveBeenCalledTimes(2) }) - test('skip if pending preload (redirect)', async () => { + test('exec if pending preload redirects', async () => { const loader: Loader = vi.fn(async ({ preload }) => { await sleep(100) if (preload) throw redirect({ to: '/bar' }) @@ -542,40 +516,13 @@ describe('loader skip or exec', () => { }) router.preloadRoute({ to: '/foo' }) await Promise.resolve() - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) await router.navigate({ to: '/foo' }) expect(router.state.location.pathname).toBe('/bar') - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) + expect(router.state.matches.at(-1)?.status).toBe('success') expect(loader).toHaveBeenCalledTimes(1) }) - test('updateMatch removes redirected matches from cachedMatches', async () => { - const loader = vi.fn() - const router = setup({ loader }) - - await router.preloadRoute({ to: '/foo' }) - expect(router.stores.cachedMatches.get()).toEqual( - expect.arrayContaining([expect.objectContaining({ id: '/foo/foo' })]), - ) - - router.updateMatch('/foo/foo', (prev) => ({ - ...prev, - status: 'redirected', - })) - - expect( - router.stores.cachedMatches.get().some((d) => d.id === '/foo/foo'), - ).toBe(false) - expect( - router.stores.cachedMatches.get().some((d) => d.status === 'redirected'), - ).toBe(false) - }) - test('exec if rejected preload (error)', async () => { const loader: Loader = vi.fn(async ({ preload }) => { if (preload) throw new Error('error') @@ -591,19 +538,33 @@ describe('loader skip or exec', () => { expect(loader).toHaveBeenCalledTimes(2) }) - test('skip if pending preload (error)', async () => { + test('navigation shares a pending preload loader error', async () => { + const loaderGate = createControlledPromise() + const failure = new Error('preload failed') const loader: Loader = vi.fn(async ({ preload }) => { - await sleep(100) - if (preload) throw new Error('error') + await loaderGate + if (preload) throw failure }) const router = setup({ loader, }) - router.preloadRoute({ to: '/foo' }) + const preload = router.preloadRoute({ to: '/foo' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + const navigation = router.navigate({ to: '/foo' }) await Promise.resolve() - await router.navigate({ to: '/foo' }) + expect(loader).toHaveBeenCalledOnce() - expect(loader).toHaveBeenCalledTimes(1) + loaderGate.resolve() + await Promise.all([preload, navigation]) + + expect(router.state.location.pathname).toBe('/foo') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: '/foo', + status: 'error', + error: failure, + }) + expect(loader).toHaveBeenCalledOnce() }) }) @@ -698,22 +659,13 @@ describe('stale loader reload triggers', () => { const getMatchById = ( router: RouterCore, id: string, - ) => - router.state.matches.find((match) => match.id === id) ?? - router.stores.pendingMatches.get().find((match) => match.id === id) ?? - router.stores.cachedMatches.get().find((match) => match.id === id) + ) => router.state.matches.find((match) => match.id === id) const hasActiveMatch = ( router: RouterCore, id: string, ) => router.state.matches.some((match) => match.id === id) - const hasPendingMatch = ( - router: RouterCore, - id: string, - ) => - router.stores.pendingMatches.get().some((match) => match.id === id) ?? false - const setup = ({ loader, staleTime, @@ -783,24 +735,30 @@ describe('stale loader reload triggers', () => { await router.navigate({ to: '/bar' }) await vi.advanceTimersByTimeAsync(1) - const revisit = router.navigate({ to: '/foo' }) - await Promise.resolve() + let revisitSettled = false + const revisit = router.navigate({ to: '/foo' }).then(() => { + revisitSettled = true + }) - expect(loader).toHaveBeenCalledTimes(2) + await vi.waitFor(() => { + expect(loader).toHaveBeenCalledTimes(2) + }) + expect(revisitSettled).toBe(false) + expect(router.state.status).toBe('pending') + expect(router.state.isLoading).toBe(true) expect(hasActiveMatch(router, '/bar/bar')).toBe(true) expect(hasActiveMatch(router, '/foo/foo')).toBe(false) - expect(hasPendingMatch(router, '/foo/foo')).toBe(true) - expect(getMatchById(router, '/foo/foo')?.loaderData).toEqual({ - value: 'first', - }) resolveStaleReload() await revisit + expect(revisitSettled).toBe(true) + expect(router.state.status).toBe('idle') + expect(router.state.isLoading).toBe(false) expect(loader).toHaveBeenCalledTimes(2) expect(hasActiveMatch(router, '/foo/foo')).toBe(true) - expect(hasPendingMatch(router, '/foo/foo')).toBe(false) expect(router.state.location.pathname).toBe('/foo') + expect(router.state.resolvedLocation?.pathname).toBe('/foo') expect(getMatchById(router, '/foo/foo')?.loaderData).toEqual({ value: 'second', }) @@ -818,27 +776,28 @@ describe('stale loader reload triggers', () => { await router.navigate({ to: '/bar' }) await vi.advanceTimersByTimeAsync(1) - const revisit = router.navigate({ to: '/foo' }) - - expect(loader).toHaveBeenCalledTimes(2) - + let revisitSettled = false + const revisit = router.navigate({ to: '/foo' }).then(() => { + revisitSettled = true + }) await revisit - const backgroundReloadPromise = getMatchById(router, '/foo/foo') - ?._nonReactive.loaderPromise - expect(backgroundReloadPromise).toBeDefined() + expect(revisitSettled).toBe(true) + expect(router.state.status).toBe('idle') + expect(router.state.isLoading).toBe(false) + expect(loader).toHaveBeenCalledTimes(2) expect(hasActiveMatch(router, '/foo/foo')).toBe(true) - expect(hasPendingMatch(router, '/foo/foo')).toBe(false) expect(router.state.location.pathname).toBe('/foo') + expect(router.state.resolvedLocation?.pathname).toBe('/foo') expect(getMatchById(router, '/foo/foo')?.loaderData).toEqual({ value: 'first', }) resolveStaleReload() - await backgroundReloadPromise - - expect(getMatchById(router, '/foo/foo')?.loaderData).toEqual({ - value: 'second', + await vi.waitFor(() => { + expect(getMatchById(router, '/foo/foo')?.loaderData).toEqual({ + value: 'second', + }) }) } @@ -900,7 +859,7 @@ describe('stale loader reload triggers', () => { expect(loader).toHaveBeenCalledTimes(2) }) - test('reloads a stale preloaded loader when switching to a different match id of the same route', async () => { + test('reuses a fresh preloaded loader when switching to a different match id of the same route', async () => { const rootRoute = new BaseRootRoute({}) const rootLoader = vi.fn(() => ({ ok: true })) const childLoader = vi.fn(() => ({ ok: true })) @@ -958,8 +917,8 @@ describe('stale loader reload triggers', () => { search: { page: '2' }, }) - expect(rootLoader).toHaveBeenCalledTimes(3) - expect(childLoader).toHaveBeenCalledTimes(3) + expect(rootLoader).toHaveBeenCalledTimes(2) + expect(childLoader).toHaveBeenCalledTimes(2) }) test('skips stale ancestor loader when only a child path param changes', async () => { @@ -1154,24 +1113,23 @@ describe('stale loader reload triggers', () => { }) }) -test('cancelMatches after pending timeout', async () => { - const WAIT_TIME = 5 - const onAbortMock = vi.fn() +test('navigating away from a pending route aborts its loader', async () => { + let loaderSignal: AbortSignal | undefined const rootRoute = new BaseRootRoute({}) const fooRoute = new BaseRoute({ getParentRoute: () => rootRoute, path: '/foo', - pendingMs: WAIT_TIME * 20, + pendingMs: 0, loader: async ({ abortController }) => { + loaderSignal = abortController.signal await new Promise((resolve) => { - const timer = setTimeout(() => { - resolve() - }, WAIT_TIME * 40) - abortController.signal.addEventListener('abort', () => { - onAbortMock() - clearTimeout(timer) - resolve() - }) + abortController.signal.addEventListener( + 'abort', + () => { + resolve() + }, + { once: true }, + ) }) }, pendingComponent: {}, @@ -1184,26 +1142,36 @@ test('cancelMatches after pending timeout', async () => { const router = createTestRouter({ routeTree, history: createMemoryHistory() }) await router.load() - router.navigate({ to: '/foo' }) - await sleep(WAIT_TIME * 30) + const fooNavigation = router.navigate({ to: '/foo' }) - // At this point, pending timeout should have triggered - const fooMatch = router.getMatch('/foo/foo') - expect(fooMatch).toBeDefined() + await vi.waitFor(() => { + expect(loaderSignal).toBeDefined() + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: fooRoute.id, + status: 'pending', + }) + }) - // Navigate away, which should cancel the pending match await router.navigate({ to: '/bar' }) - await router.latestLoadPromise + await fooNavigation expect(router.state.location.pathname).toBe('/bar') - - // Verify that abort was called and pending timeout was cleared - expect(onAbortMock).toHaveBeenCalled() - const cancelledFooMatch = router.getMatch('/foo/foo') - expect(cancelledFooMatch?._nonReactive.pendingTimeout).toBeUndefined() + expect(router.state.resolvedLocation?.pathname).toBe('/bar') + expect(router.state.status).toBe('idle') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: barRoute.id, + status: 'success', + }) + expect( + router.state.matches.some((match) => match.routeId === fooRoute.id), + ).toBe(false) + expect(loaderSignal?.aborted).toBe(true) }) describe('head execution', () => { + const getTitle = (match: { meta?: unknown }) => + (match.meta as Array<{ title?: string }> | undefined)?.[0]?.title + const setupBeforeLoadNotFoundHierarchy = (throwAtIndex: 1 | 2 | 3) => { const loaderResolvers: Array<(() => void) | undefined> = [] @@ -1280,14 +1248,12 @@ describe('head execution', () => { const heads = routes.map( (route) => route.options.head as ReturnType, ) - return { router, routes, loaders, heads, loaderResolvers, - throwAtIndex, } } @@ -1303,26 +1269,27 @@ describe('head execution', () => { await Promise.resolve() await Promise.resolve() - for (let i = 0; i < routes.length; i++) { - const loader = loaders[i]! - const expectedCalls = i < throwAtIndex ? 1 : 0 - expect(loader).toHaveBeenCalledTimes(expectedCalls) - } - expect(loadResolved).toBe(false) + await vi.waitFor(() => { + loaders.forEach((loader, index) => { + expect(loader).toHaveBeenCalledTimes(index < throwAtIndex ? 1 : 0) + }) + }) for (let i = 0; i < throwAtIndex; i++) { - expect(loaderResolvers[i]).toBeDefined() loaderResolvers[i]!() } await loadPromise - for (let i = 0; i < heads.length; i++) { - const head = heads[i]! - const expectedCalls = i <= throwAtIndex ? 1 : 0 - expect(head).toHaveBeenCalledTimes(expectedCalls) - } + const titles = router.state.matches.map(getTitle) + expect(titles.slice(0, throwAtIndex + 1)).toEqual( + ['Root', 'Level 1', 'Level 2', 'Level 3'].slice(0, throwAtIndex + 1), + ) + expect(titles.slice(throwAtIndex + 1).every((title) => !title)).toBe(true) + heads.forEach((head, index) => { + expect(head).toHaveBeenCalledTimes(index <= throwAtIndex ? 1 : 0) + }) for (let i = 0; i < throwAtIndex; i++) { const route = routes[i]! @@ -1343,7 +1310,7 @@ describe('head execution', () => { }) }) - test('executes head once when loader throws notFound', async () => { + test('projects head when loader throws notFound', async () => { const head = vi.fn(() => ({ meta: [{ title: 'Test' }] })) const rootRoute = new BaseRootRoute({}) const testRoute = new BaseRoute({ @@ -1362,16 +1329,16 @@ describe('head execution', () => { await router.load() - expect(head).toHaveBeenCalledTimes(1) const match = router.state.matches.find((m) => m.routeId === testRoute.id) expect(match?.status).toBe('notFound') + expect(match?.meta).toEqual([{ title: 'Test' }]) + expect(head).toHaveBeenCalledTimes(1) }) test('propagates sync beforeLoad non-notFound error running ancestor loaders and heads', async () => { const beforeLoadError = new Error('beforeLoad-sync-error') const rootLoader = vi.fn(() => ({ level: 0 })) const rootHead = vi.fn(() => ({ meta: [{ title: 'Root' }] })) - const rootRoute = new BaseRootRoute({ loader: rootLoader, head: rootHead, @@ -1379,7 +1346,6 @@ describe('head execution', () => { const childLoader = vi.fn(() => ({ level: 1 })) const childHead = vi.fn(() => ({ meta: [{ title: 'Child' }] })) - const childRoute = new BaseRoute({ getParentRoute: () => rootRoute, path: '/test', @@ -1396,23 +1362,17 @@ describe('head execution', () => { history: createMemoryHistory({ initialEntries: ['/test'] }), }) - const location = router.latestLocation - const matches = router.matchRoutes(location) - router.stores.setPending(matches) + await router.load() - await expect( - loadMatches({ - router, - location, - matches, - updateMatch: router.updateMatch, - }), - ).rejects.toBe(beforeLoadError) + expect( + router.state.matches.find((match) => match.routeId === childRoute.id) + ?.error, + ).toBe(beforeLoadError) + expect(router.state.matches[0]?.loaderData).toEqual({ level: 0 }) + expect(router.state.matches.map(getTitle)).toEqual(['Root', 'Child']) expect(rootLoader).toHaveBeenCalledTimes(1) - expect(childLoader).toHaveBeenCalledTimes(0) - // Head functions still run for ancestors up to the erroring match so that - // SSR produces valid content (e.g. charset, viewport, stylesheets). + expect(childLoader).not.toHaveBeenCalled() expect(rootHead).toHaveBeenCalledTimes(1) expect(childHead).toHaveBeenCalledTimes(1) }) @@ -1421,7 +1381,6 @@ describe('head execution', () => { const beforeLoadError = new Error('beforeLoad-async-error') const rootLoader = vi.fn(() => ({ level: 0 })) const rootHead = vi.fn(() => ({ meta: [{ title: 'Root' }] })) - const rootRoute = new BaseRootRoute({ loader: rootLoader, head: rootHead, @@ -1429,7 +1388,6 @@ describe('head execution', () => { const childLoader = vi.fn(() => ({ level: 1 })) const childHead = vi.fn(() => ({ meta: [{ title: 'Child' }] })) - const childRoute = new BaseRoute({ getParentRoute: () => rootRoute, path: '/test', @@ -1447,23 +1405,17 @@ describe('head execution', () => { history: createMemoryHistory({ initialEntries: ['/test'] }), }) - const location = router.latestLocation - const matches = router.matchRoutes(location) - router.stores.setPending(matches) + await router.load() - await expect( - loadMatches({ - router, - location, - matches, - updateMatch: router.updateMatch, - }), - ).rejects.toBe(beforeLoadError) + expect( + router.state.matches.find((match) => match.routeId === childRoute.id) + ?.error, + ).toBe(beforeLoadError) + expect(router.state.matches[0]?.loaderData).toEqual({ level: 0 }) + expect(router.state.matches.map(getTitle)).toEqual(['Root', 'Child']) expect(rootLoader).toHaveBeenCalledTimes(1) - expect(childLoader).toHaveBeenCalledTimes(0) - // Head functions still run for ancestors up to the erroring match so that - // SSR produces valid content (e.g. charset, viewport, stylesheets). + expect(childLoader).not.toHaveBeenCalled() expect(rootHead).toHaveBeenCalledTimes(1) expect(childHead).toHaveBeenCalledTimes(1) }) @@ -1476,40 +1428,47 @@ describe('head execution', () => { name: string throwAtIndex: ThrowAtIndex parentFailures: ParentFailureMap - expectedErrorKind: 'notFound' | 'redirect' | 'error' + expectedErrorKind: 'notFound' | 'redirect' expectedErrorSource?: string - expectedErrorRouteIndex?: 0 | 1 | 2 | 3 - expectedLoaderMaxIndex: number - expectedRenderedHeadMaxIndex: number - withDefaultNotFoundComponent?: boolean + expectedBoundaryIndex?: 0 | 1 | 2 | 3 + expectedLoaderCount: number + expectedHeadTitles: Array beforeLoadNotFoundFactory?: ( routes: readonly [any, any, any, any], ) => ReturnType - expectRootNotFoundComponentAssigned?: boolean } const setupScenario = ({ throwAtIndex, parentFailures, beforeLoadNotFoundFactory, - withDefaultNotFoundComponent, + skipRootLoaderOnReload = false, }: { throwAtIndex: ThrowAtIndex parentFailures: ParentFailureMap beforeLoadNotFoundFactory?: Scenario['beforeLoadNotFoundFactory'] - withDefaultNotFoundComponent?: boolean + skipRootLoaderOnReload?: boolean }) => { const makeHead = (label: string) => vi.fn(() => ({ meta: [{ title: label }] })) const makeLoader = (index: number) => - vi.fn(() => { - const failure = parentFailures[index as 0 | 1 | 2] - if (failure === 'notFound') { - throw notFound({ data: { source: `loader-${index}` } }) - } - if (failure === 'redirect') { - throw redirect({ to: '/redirect-target' }) + vi.fn(({ location }: { location: { pathname: string } }) => { + // Keep failures scoped to the route under test. A root loader which + // redirects unconditionally would also redirect the destination and + // describe an infinite redirect loop rather than precedence between + // the original lane's settled outcomes. + if (location.pathname !== '/redirect-target') { + const failure = parentFailures[index as 0 | 1 | 2] + if (failure === 'notFound') { + throw notFound({ data: { source: `loader-${index}` } }) + } + if (failure === 'redirect') { + throw redirect({ to: '/redirect-target' }) + } + if (failure === 'error') { + throw new Error(`loader-${index}-error`) + } } return { level: index } }) @@ -1517,6 +1476,9 @@ describe('head execution', () => { const rootRoute = new BaseRootRoute({ loader: makeLoader(0), head: makeHead('Root'), + ...(skipRootLoaderOnReload + ? { staleTime: Infinity, shouldReload: () => false } + : {}), }) const level1Route = new BaseRoute({ @@ -1551,14 +1513,12 @@ describe('head execution', () => { ]) const routes = [rootRoute, level1Route, level2Route, level3Route] as const - - ;([0, 1, 2] as const).forEach((index) => { - if (parentFailures[index] === 'error') { - ;(routes[index].options as any).shouldReload = () => { - throw new Error(`loader-${index}-error`) - } - } - }) + const loaders = routes.map( + (route) => route.options.loader as ReturnType, + ) + const heads = routes.map( + (route) => route.options.head as ReturnType, + ) const throwRoute = routes[throwAtIndex]! throwRoute.options.beforeLoad = () => { @@ -1573,18 +1533,8 @@ describe('head execution', () => { history: createMemoryHistory({ initialEntries: ['/level-1/level-2/level-3'], }), - ...(withDefaultNotFoundComponent - ? { defaultNotFoundComponent: () => null } - : {}), }) - const loaders = routes.map( - (route) => route.options.loader as ReturnType, - ) - const heads = routes.map( - (route) => route.options.head as ReturnType, - ) - return { router, routes, @@ -1593,24 +1543,6 @@ describe('head execution', () => { } } - const runLoadMatchesAndCapture = async (router: AnyRouter) => { - const location = router.latestLocation - const matches = router.matchRoutes(location) - router.stores.setPending(matches) - - try { - await loadMatches({ - router, - location, - matches, - updateMatch: router.updateMatch, - }) - return { error: undefined, matches } - } catch (error) { - return { error, matches } - } - } - const scenarios = [ { name: 'throws beforeLoad notFound when parent loaders succeed', @@ -1618,35 +1550,39 @@ describe('head execution', () => { parentFailures: {} as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'beforeLoad-3', - expectedLoaderMaxIndex: 2, - expectedRenderedHeadMaxIndex: 3, + expectedBoundaryIndex: 3, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root', 'Level 1', 'Level 2', 'Level 3'], }, { - name: 'uses parent loader notFound when parent loader throws notFound', + name: 'promotes a fresh parent loader notFound above a serial child notFound', throwAtIndex: 3 as const, parentFailures: { 1: 'notFound' } as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'loader-1', - expectedLoaderMaxIndex: 2, - expectedRenderedHeadMaxIndex: 1, + expectedBoundaryIndex: 1, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root', 'Level 1'], }, { - name: 'uses first parent loader notFound when multiple parent loaders throw notFound', + name: 'promotes the first fresh parent loader failure above a serial child notFound', throwAtIndex: 3 as const, parentFailures: { 1: 'notFound', 2: 'notFound' } as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'loader-1', - expectedLoaderMaxIndex: 2, - expectedRenderedHeadMaxIndex: 1, + expectedBoundaryIndex: 1, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root', 'Level 1'], }, { - name: 'uses parent loader notFound when root loader throws notFound', + name: 'promotes a fresh root loader notFound above a serial child notFound', throwAtIndex: 2 as const, parentFailures: { 0: 'notFound' } as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'loader-0', - expectedLoaderMaxIndex: 1, - expectedRenderedHeadMaxIndex: 0, + expectedBoundaryIndex: 0, + expectedLoaderCount: 2, + expectedHeadTitles: ['Root'], }, { name: 'uses explicit routeId from beforeLoad notFound to target ancestor boundary', @@ -1654,9 +1590,9 @@ describe('head execution', () => { parentFailures: {} as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'beforeLoad-explicit-level1', - expectedErrorRouteIndex: 1, - expectedLoaderMaxIndex: 1, - expectedRenderedHeadMaxIndex: 1, + expectedBoundaryIndex: 1, + expectedLoaderCount: 2, + expectedHeadTitles: ['Root', 'Level 1'], beforeLoadNotFoundFactory: (routes) => notFound({ routeId: routes[1].id as never, @@ -1669,8 +1605,9 @@ describe('head execution', () => { parentFailures: {} as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'beforeLoad-invalid-route', - expectedLoaderMaxIndex: 0, - expectedRenderedHeadMaxIndex: 0, + expectedBoundaryIndex: 0, + expectedLoaderCount: 1, + expectedHeadTitles: ['Root'], beforeLoadNotFoundFactory: () => notFound({ routeId: '/does-not-exist' as never, @@ -1683,38 +1620,23 @@ describe('head execution', () => { parentFailures: {} as ParentFailureMap, expectedErrorKind: 'notFound' as const, expectedErrorSource: 'beforeLoad-non-exact-route', - expectedLoaderMaxIndex: 0, - expectedRenderedHeadMaxIndex: 0, + expectedBoundaryIndex: 0, + expectedLoaderCount: 1, + expectedHeadTitles: ['Root'], beforeLoadNotFoundFactory: (routes) => notFound({ routeId: `${routes[1].id}/` as never, data: { source: 'beforeLoad-non-exact-route' }, }), }, - { - name: 'assigns defaultNotFoundComponent on root when unknown routeId falls back to root', - throwAtIndex: 3 as const, - parentFailures: {} as ParentFailureMap, - expectedErrorKind: 'notFound' as const, - expectedErrorSource: 'beforeLoad-invalid-route-default', - expectedLoaderMaxIndex: 0, - expectedRenderedHeadMaxIndex: 0, - withDefaultNotFoundComponent: true, - expectRootNotFoundComponentAssigned: true, - beforeLoadNotFoundFactory: () => - notFound({ - routeId: '/does-not-exist' as never, - data: { source: 'beforeLoad-invalid-route-default' }, - }), - }, { name: 'prioritizes redirect when parent loader throws redirect', throwAtIndex: 3 as const, parentFailures: { 0: 'redirect' } as ParentFailureMap, expectedErrorKind: 'redirect' as const, expectedErrorSource: undefined, - expectedLoaderMaxIndex: 2, - expectedRenderedHeadMaxIndex: -1, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root'], }, { name: 'prioritizes redirect over root-loader notFound when both appear in settled loaders', @@ -1722,17 +1644,18 @@ describe('head execution', () => { parentFailures: { 0: 'notFound', 1: 'redirect' } as ParentFailureMap, expectedErrorKind: 'redirect' as const, expectedErrorSource: undefined, - expectedLoaderMaxIndex: 2, - expectedRenderedHeadMaxIndex: -1, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root'], }, { - name: 'propagates regular loader error when mixed with loader notFound in settled loaders', + name: 'promotes the first fresh parent failure when later loaders fail differently', throwAtIndex: 3 as const, parentFailures: { 1: 'notFound', 2: 'error' } as ParentFailureMap, - expectedErrorKind: 'error' as const, - expectedErrorSource: 'loader-2-error', - expectedLoaderMaxIndex: 1, - expectedRenderedHeadMaxIndex: -1, + expectedErrorKind: 'notFound' as const, + expectedErrorSource: 'loader-1', + expectedBoundaryIndex: 1, + expectedLoaderCount: 3, + expectedHeadTitles: ['Root', 'Level 1'], }, ] satisfies Array @@ -1741,60 +1664,68 @@ describe('head execution', () => { throwAtIndex: scenario.throwAtIndex, parentFailures: scenario.parentFailures, beforeLoadNotFoundFactory: scenario.beforeLoadNotFoundFactory, - withDefaultNotFoundComponent: scenario.withDefaultNotFoundComponent, }) - const { error, matches } = await runLoadMatchesAndCapture(router) - - for (let i = 0; i < routes.length; i++) { - const loader = loaders[i]! - const expectedCalls = i <= scenario.expectedLoaderMaxIndex ? 1 : 0 - expect(loader).toHaveBeenCalledTimes(expectedCalls) - } - - for (let i = 0; i < heads.length; i++) { - const head = heads[i]! - const expectedCalls = i <= scenario.expectedRenderedHeadMaxIndex ? 1 : 0 - expect(head).toHaveBeenCalledTimes(expectedCalls) - } + await router.load() + const matches = router.state.matches if (scenario.expectedErrorKind === 'redirect') { - expect(error).toEqual( + expect(router.state.location.pathname).toBe('/redirect-target') + expect(matches.at(-1)).toEqual( expect.objectContaining({ - redirectHandled: true, - options: expect.objectContaining({ - to: '/redirect-target', - }), + pathname: '/redirect-target', + status: 'success', }), ) - return - } - - if (scenario.expectedErrorKind === 'error') { - expect(error).toBeInstanceOf(Error) - expect((error as Error).message).toBe(scenario.expectedErrorSource) - return - } - - expect(error).toEqual( - expect.objectContaining({ - isNotFound: true, - data: { source: scenario.expectedErrorSource }, - }), - ) + expect(matches.some((match) => match.error !== undefined)).toBe(false) + expect(matches.some((match) => match._notFound)).toBe(false) + } else { + const boundary = matches.find( + (match) => match.status === 'notFound' || match._notFound, + )! + expect(boundary.routeId).toBe( + routes[scenario.expectedBoundaryIndex!]!.id, + ) - if (scenario.expectedErrorRouteIndex !== undefined) { - expect((error as { routeId?: string }).routeId).toBe( - routes[scenario.expectedErrorRouteIndex]!.id, + expect(boundary.error).toEqual( + expect.objectContaining({ + isNotFound: true, + data: { source: scenario.expectedErrorSource }, + }), ) - } - if (scenario.expectRootNotFoundComponentAssigned) { - expect(routes[0].options.notFoundComponent).toBeTypeOf('function') + if (scenario.expectedBoundaryIndex === 0) { + expect(boundary.status).toBe('success') + expect(boundary._notFound).toBe(true) + } else { + expect(boundary.status).toBe('notFound') + expect(boundary.error).toEqual( + expect.objectContaining({ routeId: boundary.routeId }), + ) + } } + + loaders.forEach((loader, index) => { + const originalLaneCalls = loader.mock.calls.filter( + ([options]) => + options.location.pathname === '/level-1/level-2/level-3', + ) + expect(originalLaneCalls).toHaveLength( + index < scenario.expectedLoaderCount ? 1 : 0, + ) + }) + heads.forEach((head, index) => { + expect(head).toHaveBeenCalledTimes( + index < scenario.expectedHeadTitles.length ? 1 : 0, + ) + }) + expect( + matches.map(getTitle).filter((title) => title !== undefined), + ).toEqual(scenario.expectedHeadTitles) + expect(router.state.status).toBe('idle') }) - test('sets globalNotFound on root match when beforeLoad notFound targets root boundary', async () => { + test('sets _notFound on root match when beforeLoad notFound targets root boundary', async () => { const { router, routes } = setupScenario({ throwAtIndex: 3, parentFailures: {}, @@ -1805,28 +1736,27 @@ describe('head execution', () => { }), }) - const { error, matches } = await runLoadMatchesAndCapture(router) + await router.load() - expect(error).toEqual( + const rootMatch = router.state.matches.find( + (m) => m.routeId === routes[0].id, + ) + + expect(rootMatch?._notFound).toBe(true) + expect(rootMatch?.status).toBe('success') + expect(rootMatch?.error).toEqual( expect.objectContaining({ isNotFound: true, data: { source: 'beforeLoad-root-explicit' }, }), ) - - const rootMatch = router.stores.pendingMatches - .get() - .find((m) => m.routeId === routes[0].id) - - expect(rootMatch?.globalNotFound).toBe(true) - expect(rootMatch?.status).toBe('success') - expect(rootMatch?.error).toBeUndefined() }) - test('clears stale root globalNotFound on subsequent successful load', async () => { - const { router, routes } = setupScenario({ + test('clears stale root _notFound when the root loader is skipped on a successful reload', async () => { + const { router, routes, loaders } = setupScenario({ throwAtIndex: 3, parentFailures: {}, + skipRootLoaderOnReload: true, beforeLoadNotFoundFactory: (innerRoutes) => notFound({ routeId: innerRoutes[0].id as never, @@ -1834,87 +1764,31 @@ describe('head execution', () => { }), }) - const first = await runLoadMatchesAndCapture(router) - expect(first.error).toEqual(expect.objectContaining({ isNotFound: true })) + await router.load() + const initialRootMatch = router.state.matches.find( + (match) => match.routeId === routes[0].id, + ) + expect(initialRootMatch?._notFound).toBe(true) + expect(initialRootMatch?.error).toEqual( + expect.objectContaining({ + isNotFound: true, + data: { source: 'beforeLoad-root-explicit' }, + }), + ) + expect(loaders[0]).toHaveBeenCalledTimes(1) const throwingRoute = routes[3] throwingRoute.options.beforeLoad = undefined - const second = await runLoadMatchesAndCapture(router) - expect(second.error).toBeUndefined() - - const rootMatch = router.stores.pendingMatches - .get() - .find((m) => m.routeId === routes[0].id) - - expect(rootMatch?.globalNotFound).toBe(false) - }) - - test('clears stale root globalNotFound when root loader is skipped', async () => { - const rootLoader = vi.fn(() => ({ level: 0 })) - const rootRoute = new BaseRootRoute({ - loader: rootLoader, - staleTime: Infinity, - shouldReload: () => false, - }) - - const childLoader = vi.fn(() => ({ level: 1 })) - const childRoute = new BaseRoute({ - getParentRoute: () => rootRoute, - path: '/test', - loader: childLoader, - staleTime: Infinity, - shouldReload: () => false, - }) - - const routeTree = rootRoute.addChildren([childRoute]) - - const router = createTestRouter({ - routeTree, - history: createMemoryHistory({ initialEntries: ['/test'] }), - }) - - const first = await runLoadMatchesAndCapture(router) - expect(first.error).toBeUndefined() - expect(rootLoader).toHaveBeenCalledTimes(1) - - const staleRootNotFound = notFound({ data: { source: 'stale-root' } }) - const currentRootMatchId = router.stores.pendingMatches - .get() - .find((m) => m.routeId === rootRoute.id)!.id - - router.updateMatch(currentRootMatchId, (prev) => ({ - ...prev, - status: 'success', - globalNotFound: true, - error: staleRootNotFound, - })) - - const location = router.latestLocation - const matches = router.matchRoutes(location) - const pendingRootMatch = matches.find((m) => m.routeId === rootRoute.id)! - pendingRootMatch.status = 'success' - pendingRootMatch.globalNotFound = false - pendingRootMatch.error = undefined - router.stores.setPending(matches) - - await expect( - loadMatches({ - router, - location, - matches, - updateMatch: router.updateMatch, - }), - ).resolves.toBe(matches) - - expect(rootLoader).toHaveBeenCalledTimes(1) + await router.load() - const rootMatch = router.stores.pendingMatches - .get() - .find((m) => m.routeId === rootRoute.id) + const rootMatch = router.state.matches.find( + (m) => m.routeId === routes[0].id, + ) - expect(rootMatch?.globalNotFound).toBe(false) - expect(rootMatch?.error).toBeUndefined() + expect(rootMatch?._notFound).toBe(false) + expect(rootMatch?.status).toBe('success') + expect(loaders[0]).toHaveBeenCalledTimes(1) }) }) }) @@ -1975,7 +1849,6 @@ describe('params.parse notFound', () => { const match = router.state.matches.find((m) => m.routeId === testRoute.id) expect(match?.status).toBe('success') - expect(router.state.statusCode).toBe(200) }) }) diff --git a/packages/router-core/tests/loader-architecture-regressions.test.ts b/packages/router-core/tests/loader-architecture-regressions.test.ts new file mode 100644 index 0000000000..d849305244 --- /dev/null +++ b/packages/router-core/tests/loader-architecture-regressions.test.ts @@ -0,0 +1,1187 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + redirect, +} from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +test.each([ + ['error', () => new Error('parent failed')], + ['notFound', () => notFound()], +] as const)( + 'a child can redirect after observing its parent loader %s', + async (expectedStatus, createFailure) => { + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw createFailure() + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async ({ parentMatchPromise }) => { + const parent = await parentMatchPromise + if (parent.status === expectedStatus) { + throw redirect({ to: '/target' }) + } + return parent.status + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ + initialEntries: ['/parent/child'], + }), + }) + + await router.load() + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + }, +) + +test.each(['client', 'server'] as const)( + 'a descendant self-abort cannot hide redirecting onError on the %s', + async (environment) => { + const parentGate = createControlledPromise() + const childGate = createControlledPromise() + const parentOnError = vi.fn() + const childOnError = vi.fn(() => { + throw redirect({ to: '/target' }) + }) + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: async () => { + await parentGate + throw new Error('parent failed') + }, + onError: parentOnError, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async ({ abortController }) => { + await childGate + abortController.abort() + throw new Error('child failed') + }, + onError: childOnError, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: environment === 'server', + }) + + const loading = + environment === 'server' + ? loadServerResponse(router, '/parent/child') + : router.load() + parentGate.resolve() + await vi.waitFor(() => expect(parentOnError).toHaveBeenCalledOnce()) + childGate.resolve() + await vi.waitFor(() => expect(childOnError).toHaveBeenCalledOnce()) + + if (environment === 'server') { + const response = await loading + expect(response).toBeDefined() + if (!response) { + return + } + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/target') + } else { + await loading + expect(router.state.location.pathname).toBe('/target') + } + }, +) + +test('superseding a load clears fetching state from the still-presented lane', async () => { + const firstGate = createControlledPromise() + const secondGate = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 0), + }), + beforeLoad: ({ search }) => (search.revision === 1 ? firstGate : undefined), + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + loader: () => secondGate, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, otherRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const firstNavigation = router.navigate({ + to: '/page', + search: { revision: 1 }, + }) + await vi.waitFor(() => + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + isFetching: 'beforeLoad', + }), + ) + + const secondNavigation = router.navigate({ to: '/other' }) + await vi.waitFor(() => { + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + isFetching: false, + }) + }) + + firstGate.resolve() + secondGate.resolve() + await Promise.all([firstNavigation, secondNavigation]) +}) + +test('an ignored preload flight is released after a navigation has planned its loaders', async () => { + const preloadFailure = createControlledPromise() + const childGate = createControlledPromise() + let preloadSignal: AbortSignal | undefined + const parentLoader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + const generation = parentLoader.mock.calls.length + if (generation === 1) { + return 'initial parent data' + } + preloadSignal = abortController.signal + return preloadFailure + }, + ) + const childLoader = vi.fn(() => childGate) + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + shouldReload: ({ preload }) => preload, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + }) + const siblingRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/sibling', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute, siblingRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/parent/sibling' }) + await vi.waitFor(() => expect(parentLoader).toHaveBeenCalledTimes(2)) + + const navigation = router.navigate({ to: '/parent/child' }) + await vi.waitFor(() => expect(childLoader).toHaveBeenCalledOnce()) + + preloadFailure.reject(new Error('discarded preload generation')) + await preload + expect(preloadSignal?.aborted).toBe(true) + + childGate.resolve('child data') + await navigation +}) + +test('an ordinary route-context failure still permits ancestor loaders', async () => { + const failure = new Error('child context failed') + const parentLoader = vi.fn(() => 'parent data') + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + context: () => { + throw failure + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + + expect(parentLoader).toHaveBeenCalledOnce() + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'success', + loaderData: 'parent data', + }) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: childRoute.id, + status: 'error', + error: failure, + }) +}) + +test('a failed route context is recomputed when the route is retried', async () => { + const failure = new Error('context failed') + let shouldFail = true + let generation = 0 + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + context: () => { + generation++ + if (shouldFail) { + throw failure + } + return { generation } + }, + loader: ({ context }) => context.generation, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: route.id, + status: 'error', + error: failure, + }) + + shouldFail = false + await router.invalidate() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: route.id, + status: 'success', + loaderData: 2, + }) +}) + +test('invalidation reruns the loader with same-id route context', async () => { + let contextGeneration = 0 + let loaderGeneration = 0 + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + context: () => ({ generation: ++contextGeneration }), + loader: ({ context }) => ({ + loader: ++loaderGeneration, + context: context.generation, + }), + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + expect(router.state.matches.at(-1)?.loaderData).toEqual({ + loader: 1, + context: 1, + }) + + await router.invalidate() + + expect(router.state.matches.at(-1)?.loaderData).toEqual({ + loader: 2, + context: 1, + }) +}) + +test("a settled preload cannot abort a navigation's beforeLoad controller", async () => { + const suspended = createControlledPromise() + let navigationSignal: AbortSignal | undefined + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + beforeLoad: ({ abortController, preload }) => { + if (!preload) { + navigationSignal = abortController.signal + } + throw suspended + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/guarded' }) + const navigation = router.navigate({ to: '/guarded' }) + + await preload + expect(navigationSignal?.aborted).toBe(false) + + await navigation +}) + +test('a fast background candidate remains private when a reentrant navigation wins', async () => { + let parentLoads = 0 + let childLoads = 0 + let activeSignal: AbortSignal | undefined + let replacementSignal: AbortSignal | undefined + let reentrantNavigation: Promise | undefined + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + reload: Number(search.reload ?? 0), + }), + shouldReload: ({ location }) => + (location.search as { reload: number }).reload === 1, + loader: ({ abortController }) => { + parentLoads++ + if (parentLoads === 1) { + activeSignal = abortController.signal + } else { + replacementSignal = abortController.signal + } + return { revision: parentLoads } + }, + onStay: (match) => { + if (match.search.reload === 1 && !reentrantNavigation) { + reentrantNavigation = router.navigate({ + to: '/parent/child', + search: { reload: 2 }, + }) + } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + shouldReload: ({ location }) => + (location.search as { reload: number }).reload === 1, + loader: () => { + childLoads++ + if (childLoads === 2) { + throw redirect({ to: '/target' }) + } + return 'child data' + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ + initialEntries: ['/parent/child?reload=0'], + }), + }) + + await router.load() + await router.navigate({ + to: '/parent/child', + search: { reload: 1 }, + }) + await reentrantNavigation + + expect(router.state.location.search).toEqual({ reload: 2 }) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ loaderData: { revision: 1 } }) + expect(parentLoads).toBe(2) + expect(activeSignal?.aborted).toBe(false) + expect(replacementSignal?.aborted).toBe(true) +}) + +test('a superseding navigation reuses a pending background loader after async beforeLoad', async () => { + const backgroundResult = createControlledPromise() + const beforeLoadStarted = createControlledPromise() + const beforeLoadGate = createControlledPromise() + const signals: Array = [] + let generation = 0 + + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + validateSearch: (search: Record) => ({ + reload: Number(search.reload ?? 0), + }), + shouldReload: ({ location }) => + (location.search as { reload: number }).reload === 1 ? true : undefined, + beforeLoad: async ({ search }) => { + if (search.reload === 2) { + beforeLoadStarted.resolve() + await beforeLoadGate + } + }, + loader: { + staleReloadMode: 'background', + handler: ({ abortController }) => { + signals.push(abortController.signal) + generation++ + return generation === 2 ? backgroundResult : generation + }, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ + initialEntries: ['/page?reload=0'], + }), + }) + + await router.load() + await router.navigate({ + to: '/page', + search: { reload: 1 }, + }) + await vi.waitFor(() => expect(generation).toBe(2)) + + const successor = router.navigate({ + to: '/page', + search: { reload: 2 }, + }) + await beforeLoadStarted + + backgroundResult.resolve(2) + beforeLoadGate.resolve() + await successor + + await vi.waitFor(() => { + expect(router.state.matches.at(-1)?.loaderData).toBe(2) + }) + expect(generation).toBe(2) + expect(signals[1]?.aborted).toBe(false) + expect(router.state.matches.at(-1)?.preload).toBe(false) +}) + +test('an ancestor beforeLoad failure releases an unconsumed background loader', async () => { + const backgroundResult = createControlledPromise() + const signals: Array = [] + let generation = 0 + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + phase: String(search.phase ?? 'initial'), + }), + beforeLoad: ({ search }) => { + if (search.phase === 'fail') { + throw new Error('parent failed') + } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + shouldReload: ({ location }) => + (location.search as { phase: string }).phase === 'refresh' + ? true + : undefined, + staleTime: Infinity, + loader: { + staleReloadMode: 'background', + handler: ({ abortController }) => { + signals.push(abortController.signal) + generation++ + return generation === 2 ? backgroundResult : generation + }, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ + initialEntries: ['/parent/child?phase=initial'], + }), + }) + + await router.load() + await router.navigate({ + to: '/parent/child', + search: { phase: 'refresh' }, + }) + await vi.waitFor(() => expect(generation).toBe(2)) + + await router.navigate({ + to: '/parent/child', + search: { phase: 'fail' }, + }) + + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ status: 'error' }) + expect(signals[1]?.aborted).toBe(true) + + backgroundResult.resolve(2) + await router.navigate({ + to: '/parent/child', + search: { phase: 'recovered' }, + }) + + expect(generation).toBe(2) + expect(router.state.matches.at(-1)?.loaderData).toBe(1) +}) + +test('reentrant navigation from onError cancels stale serial work', async () => { + const failure = new Error('stale context failed') + const parentLoader = vi.fn(() => 'parent data') + let successor: Promise | undefined + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + context: () => { + throw failure + }, + onError: () => { + successor ??= router.navigate({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/parent/child' }) + await successor + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)?.routeId).toBe(targetRoute.id) + expect(router.state.matches.some((match) => match.error === failure)).toBe( + false, + ) + expect(parentLoader).not.toHaveBeenCalled() +}) + +test('a joined descendant loader redirect still wins after an ancestor loader fails', async () => { + const childStarted = createControlledPromise() + const childRedirect = createControlledPromise() + const ancestorFailure = new Error('navigation parent failed') + let childLoads = 0 + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + mode: String(search.mode ?? ''), + }), + loaderDeps: ({ search }) => ({ mode: search.mode }), + loader: ({ location }) => { + if ((location.search as { mode: string }).mode === 'navigation') { + throw ancestorFailure + } + return 'preload parent data' + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childLoads++ + if (childLoads === 1) { + childStarted.resolve() + await childRedirect + throw redirect({ to: '/target' }) + } + return 'retried child data' + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ + to: '/parent/child', + search: { mode: 'preload' }, + }) + await childStarted + + const navigation = router.navigate({ + to: '/parent/child', + search: { mode: 'navigation' }, + }) + childRedirect.resolve() + await Promise.all([preload, navigation]) + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + expect(childLoads).toBe(1) +}) + +test('an unrelated preload redirect does not override a navigation error', async () => { + const preloadChildStarted = createControlledPromise() + const releasePreloadRedirect = createControlledPromise() + const navigationFailure = new Error('navigation parent failed') + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + mode: String(search.mode ?? ''), + }), + loaderDeps: ({ search }) => ({ mode: search.mode }), + loader: ({ deps }) => { + if (deps.mode === 'navigation') { + throw navigationFailure + } + return 'preload parent data' + }, + errorComponent: () => null, + }) + const childLoader = vi.fn(async ({ deps }: { deps: { mode: string } }) => { + if (deps.mode === 'preload') { + preloadChildStarted.resolve() + await releasePreloadRedirect + throw redirect({ to: '/target' }) + } + return 'navigation child data' + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loaderDeps: ({ search }) => ({ mode: search.mode }), + loader: childLoader, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ + to: '/parent/child', + search: { mode: 'preload' }, + }) + await preloadChildStarted + + await router.navigate({ + to: '/parent/child', + search: { mode: 'navigation' }, + }) + + expect(router.state.location).toMatchObject({ + pathname: '/parent/child', + search: { mode: 'navigation' }, + }) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'error', + error: navigationFailure, + }) + expect(childLoader).toHaveBeenCalledTimes(2) + + releasePreloadRedirect.resolve() + const preloaded = await preload + + expect(preloaded?.at(-1)?.routeId).toBe(targetRoute.id) + expect(router.state.location).toMatchObject({ + pathname: '/parent/child', + search: { mode: 'navigation' }, + }) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'error', + error: navigationFailure, + }) +}) + +test('a loaderDeps error is handled by the route error boundary', async () => { + const failure = new Error('loader deps failed') + const rootRoute = new BaseRootRoute({}) + const brokenRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/broken', + loaderDeps: (): Record => { + throw failure + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([brokenRoute]), + history: createMemoryHistory({ initialEntries: ['/broken'] }), + }) + + await router.load() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: brokenRoute.id, + status: 'error', + error: failure, + }) +}) + +test('loaderDeps redirects use the bounded navigation redirect policy', async () => { + const rootRoute = new BaseRootRoute({}) + const routes = Array.from( + { length: 23 }, + (_, index) => + new BaseRoute({ + getParentRoute: () => rootRoute, + path: `/step-${index}`, + loaderDeps: + index === 22 + ? undefined + : () => { + throw redirect({ to: `/step-${index + 1}` }) + }, + }), + ) + const router = createTestRouter({ + routeTree: rootRoute.addChildren(routes as any) as any, + history: createMemoryHistory({ initialEntries: ['/step-0'] }), + }) + + await router.load() + + expect(router.state.location.pathname).toBe('/step-20') + expect( + router.state.matches.find((match) => match.status !== 'success'), + ).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: expect.objectContaining({ + message: 'Too many redirects', + }), + }) +}) + +test('a reentrant user navigation starts a fresh redirect chain', async () => { + let navigateFresh: () => void + const rootRoute = new BaseRootRoute({}) + const chain = Array.from( + { length: 20 }, + (_, index) => + new BaseRoute({ + getParentRoute: () => rootRoute, + path: `/chain-${index}`, + beforeLoad: + index < 19 + ? () => { + throw redirect({ to: `/chain-${index + 1}` }) + } + : () => navigateFresh(), + }), + ) + const freshRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/fresh', + beforeLoad: () => { + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + ...(chain as any), + freshRoute, + targetRoute, + ]) as any, + history: createMemoryHistory({ initialEntries: ['/chain-0'] }), + }) + navigateFresh = () => { + void router.navigate({ to: '/fresh' }) + } + + await router.load() + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) +}) + +test('a superseded load settles without waiting for stale asset projection', async () => { + const slowHeadStarted = createControlledPromise() + const slowHead = createControlledPromise<{ + meta: Array<{ title: string }> + }>() + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const slowRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/slow', + head: () => { + slowHeadStarted.resolve() + return slowHead + }, + scripts: () => [{ children: 'window.route = "slow"' }], + headers: () => ({ 'x-route': 'slow' }), + }) + const fastRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/fast', + head: () => ({ meta: [{ title: 'fast' }] }), + scripts: () => [{ children: 'window.route = "fast"' }], + headers: () => ({ 'x-route': 'fast' }), + }) + const history = createMemoryHistory({ initialEntries: ['/'] }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, slowRoute, fastRoute]), + history, + }) + + await router.load() + history.push('/slow') + const slowLoad = router.load() + await slowHeadStarted + + history.push('/fast') + const fastLoad = router.load() + await fastLoad + + const settledBeforeProjection = await Promise.race([ + slowLoad.then(() => true), + new Promise((resolve) => setTimeout(() => resolve(false), 100)), + ]) + + slowHead.resolve({ meta: [{ title: 'slow' }] }) + await slowLoad + await Promise.resolve() + + expect(settledBeforeProjection).toBe(true) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: fastRoute.id, + meta: [{ title: 'fast' }], + scripts: [{ children: 'window.route = "fast"' }], + }) +}) + +test('server response headers stop at the terminal render boundary', async () => { + let failParent = false + const rootRoute = new BaseRootRoute({ + headers: () => ({ 'x-root': 'root' }), + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + if (failParent) { + throw new Error('parent failed') + } + }, + headers: () => ({ 'x-parent': 'parent' }), + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + headers: () => ({ 'x-child': 'child' }), + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + await router.load() + expect(router.state.matches.at(-1)?.headers).toEqual({ 'x-child': 'child' }) + + failParent = true + const response = await loadServerResponse(router, '/parent/child') + + expect(router.state.matches).toHaveLength(3) + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + }) + expect(response.headers.get('x-root')).toBe('root') + expect(response.headers.get('x-parent')).toBe('parent') + expect(response.headers.get('x-child')).toBeNull() +}) + +test('a superseded load settles without waiting for its normal component chunk', async () => { + const chunkStarted = createControlledPromise() + const chunk = createControlledPromise() + const onError = vi.fn() + const component = Object.assign(() => null, { + preload: () => { + chunkStarted.resolve() + return chunk + }, + }) + const rootRoute = new BaseRootRoute({}) + const slowRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/slow', + component, + onError, + }) + const fastRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/fast', + }) + const history = createMemoryHistory({ initialEntries: ['/slow'] }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([slowRoute, fastRoute]), + history, + }) + + const slowLoad = router.load() + await chunkStarted + history.push('/fast') + await router.load() + + expect( + await Promise.race([ + slowLoad.then(() => true), + new Promise((resolve) => setTimeout(() => resolve(false), 100)), + ]), + ).toBe(true) + expect(router.state.matches.at(-1)?.routeId).toBe(fastRoute.id) + + chunk.reject(new Error('obsolete chunk failed')) + await Promise.resolve() + expect(onError).not.toHaveBeenCalled() +}) + +test('a superseded load settles without waiting for its terminal boundary chunk', async () => { + const chunkStarted = createControlledPromise() + const chunk = createControlledPromise() + const loaderError = new Error('loader failed') + const onError = vi.fn() + const errorComponent = Object.assign(() => null, { + preload: () => { + chunkStarted.resolve() + return chunk + }, + }) + const rootRoute = new BaseRootRoute({}) + const slowRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/slow', + loader: () => { + throw loaderError + }, + errorComponent, + onError, + }) + const fastRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/fast', + }) + const history = createMemoryHistory({ initialEntries: ['/slow'] }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([slowRoute, fastRoute]), + history, + }) + + const slowLoad = router.load() + await chunkStarted + history.push('/fast') + await router.load() + + expect( + await Promise.race([ + slowLoad.then(() => true), + new Promise((resolve) => setTimeout(() => resolve(false), 100)), + ]), + ).toBe(true) + expect(router.state.matches.at(-1)?.routeId).toBe(fastRoute.id) + + chunk.reject(new Error('obsolete boundary chunk failed')) + await Promise.resolve() + expect(onError).toHaveBeenCalledOnce() + expect(onError).toHaveBeenCalledWith(loaderError) +}) + +test('a stale shouldReload cannot continue a superseded preload', async () => { + const loader = vi.fn(() => 'cached') + const onError = vi.fn() + let reenter = false + let winner: Promise | undefined + let router: ReturnType + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const cachedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/cached', + preloadStaleTime: 0, + shouldReload: () => { + if (reenter) { + router.history.push('/winner') + winner = router.load() + throw new Error('obsolete shouldReload failure') + } + return true + }, + loader, + onError, + }) + const winnerRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/winner', + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, cachedRoute, winnerRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/cached' }) + reenter = true + await router.preloadRoute({ to: '/cached' }) + await winner + + expect(router.state.location.pathname).toBe('/winner') + expect(router.state.matches.at(-1)?.routeId).toBe(winnerRoute.id) + expect(loader).toHaveBeenCalledOnce() + // Preload lanes run to completion despite the navigation, so the stale + // shouldReload's failure surfaces through onError (matching pre-rewrite + // behavior for preload errors) — but it cannot restart the loader or + // disturb the winning navigation. + expect(onError).toHaveBeenCalledOnce() +}) + +test('an onLeave navigation to the in-flight destination runs a fresh lane', async () => { + const beforeLoad = vi.fn() + const firstEnter = vi.fn() + const firstStay = vi.fn() + let successor: Promise | undefined + let redirected = false + let router: ReturnType + + const rootRoute = new BaseRootRoute({}) + const fromRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/from', + onLeave: () => { + if (!redirected) { + redirected = true + successor = router.navigate({ to: '/first' }) + } + }, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/first', + beforeLoad, + onEnter: firstEnter, + onStay: firstStay, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([fromRoute, firstRoute]), + history: createMemoryHistory({ initialEntries: ['/from'] }), + }) + + await router.load() + await router.navigate({ to: '/first' }) + await successor + + expect(router.state.location.pathname).toBe('/first') + expect(router.state.matches.at(-1)?.routeId).toBe(firstRoute.id) + expect(beforeLoad).toHaveBeenCalledTimes(2) + expect(firstEnter.mock.calls.length + firstStay.mock.calls.length).toBe(1) +}) diff --git a/packages/router-core/tests/loader-self-abort.test.ts b/packages/router-core/tests/loader-self-abort.test.ts new file mode 100644 index 0000000000..68916b1d68 --- /dev/null +++ b/packages/router-core/tests/loader-self-abort.test.ts @@ -0,0 +1,73 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +test.each([false, true])( + 'a loader can fulfill after aborting its controller with isServer=%s', + async (isServer) => { + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: ({ abortController }) => { + abortController.abort() + return 'accepted data' + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer, + }) + + if (isServer) { + expect((await loadServerResponse(router, '/page')).status).toBe(200) + } else { + await router.load() + } + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + loaderData: 'accepted data', + }) + }, +) + +test.each([false, true])( + 'a loader rejection remains an error after aborting its controller with isServer=%s', + async (isServer) => { + const failure = new Error('loader failed after aborting its controller') + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: ({ abortController }) => { + abortController.abort() + throw failure + }, + errorComponent: () => null, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer, + }) + + if (isServer) { + expect((await loadServerResponse(router, '/page')).status).toBe(500) + } else { + await router.load() + } + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'error', + error: failure, + }) + expect(onError).toHaveBeenCalledWith(failure) + }, +) diff --git a/packages/router-core/tests/loader-thrown-promise.test.ts b/packages/router-core/tests/loader-thrown-promise.test.ts new file mode 100644 index 0000000000..e45eede84f --- /dev/null +++ b/packages/router-core/tests/loader-thrown-promise.test.ts @@ -0,0 +1,57 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +test.each([ + ['beforeLoad', false], + ['beforeLoad', true], + ['loader', false], + ['loader', true], +] as const)( + 'a synchronously thrown %s Promise has the same meaning with isServer=%s', + async (hook, isServer) => { + const thrown = Promise.resolve('not loader data') + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad: + hook === 'beforeLoad' + ? () => { + throw thrown + } + : undefined, + loader: + hook === 'loader' + ? () => { + throw thrown + } + : undefined, + onError, + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory({ initialEntries: ['/target'] }), + isServer, + }) + + if (isServer) { + const response = await loadServerResponse(router, '/target') + expect(response.status).toBe(500) + } else { + await router.load() + } + + const error = router.state.matches.at(-1)?.error + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'error', + }) + expect(error).toBeInstanceOf(Error) + expect((error as Error).cause).toBe(thrown) + expect(onError).toHaveBeenCalledWith(error) + }, +) diff --git a/packages/router-core/tests/masked-location-state-commit.test.ts b/packages/router-core/tests/masked-location-state-commit.test.ts new file mode 100644 index 0000000000..9738b42261 --- /dev/null +++ b/packages/router-core/tests/masked-location-state-commit.test.ts @@ -0,0 +1,55 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('masked location remnants in history state', () => { + test('a same-href navigation clears an expired mask from history state', async () => { + const makeRoutes = () => { + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const realRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/real', + }) + const prettyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/pretty', + }) + return rootRoute.addChildren([indexRoute, realRoute, prettyRoute]) + } + const history = createMemoryHistory({ initialEntries: ['/'] }) + + const router1 = createTestRouter({ + routeTree: makeRoutes(), + history, + unmaskOnReload: true, + }) + await router1.load() + await router1.navigate({ to: '/real', mask: { to: '/pretty' } }) + expect(history.location.state.__tempLocation).toBeDefined() + + // A fresh router over the same history models a page reload: the + // per-session temp key no longer matches, so the mask has expired and the + // literal URL resolves. + const router2 = createTestRouter({ + routeTree: makeRoutes(), + history, + unmaskOnReload: true, + }) + await router2.load() + expect(router2.state.location.pathname).toBe('/pretty') + + // Navigating to the same href must still commit so the expired mask + // payload is dropped from history state, matching pre-lane behavior. + await router2.navigate({ to: '/pretty' }) + expect(history.location.state.__tempLocation).toBeUndefined() + }) +}) diff --git a/packages/router-core/tests/parent-match-promise.test.ts b/packages/router-core/tests/parent-match-promise.test.ts new file mode 100644 index 0000000000..5513067027 --- /dev/null +++ b/packages/router-core/tests/parent-match-promise.test.ts @@ -0,0 +1,121 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +test.each([false, true])( + 'parentMatchPromise exposes successful parent state during loading with isServer=%s', + async (isServer) => { + const seen: Array = [] + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => 'parent data', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async ({ parentMatchPromise }) => { + seen.push(await parentMatchPromise) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer, + }) + + await router.load() + + expect(seen).toHaveLength(1) + expect(seen[0]).toMatchObject({ + routeId: parentRoute.id, + loaderData: 'parent data', + status: 'success', + invalid: false, + preload: false, + isFetching: false, + }) + }, +) + +test('client preload=false parentMatchPromise exposes the skipped pending parent', async () => { + const seen: Array = [] + const parentLoader = vi.fn() + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + preload: false, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async ({ parentMatchPromise }) => { + seen.push(await parentMatchPromise) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + isServer: false, + }) + + await router.preloadRoute({ to: '/parent/child' }) + + expect(parentLoader).not.toHaveBeenCalled() + expect(seen).toHaveLength(1) + expect(seen[0]).toMatchObject({ + routeId: parentRoute.id, + status: 'pending', + invalid: false, + preload: false, + isFetching: false, + }) +}) + +test('server parentMatchPromise exposes the error selected by onError', async () => { + const original = new Error('original') + const selected = new Error('selected') + let seen: unknown + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw original + }, + onError: () => { + throw selected + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async ({ parentMatchPromise }) => { + seen = await parentMatchPromise + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + await router.load() + + expect(seen).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + error: selected, + }) +}) diff --git a/packages/router-core/tests/preflight-reentrant-context.test.ts b/packages/router-core/tests/preflight-reentrant-context.test.ts new file mode 100644 index 0000000000..415e5f0e50 --- /dev/null +++ b/packages/router-core/tests/preflight-reentrant-context.test.ts @@ -0,0 +1,101 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('synchronous navigation from route context stops abandoned descendant context work', async () => { + let redirected = false + let redirectNavigation: Promise | undefined + const childContext = vi.fn(() => ({ child: true })) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + context: ({ navigate }) => { + if (!redirected) { + redirected = true + redirectNavigation = navigate({ to: '/target' }) + } + return { parent: true } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + context: childContext, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/parent/child' }) + await redirectNavigation + + expect(router.state.location.pathname).toBe('/target') + expect(childContext).not.toHaveBeenCalled() +}) + +test('synchronous navigation from preloaded route context lets the preload finish quietly', async () => { + let redirected = false + let redirectNavigation: Promise | undefined + const childContext = vi.fn(() => ({ child: true })) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + context: ({ navigate, preload }) => { + if (preload && !redirected) { + redirected = true + redirectNavigation = navigate({ to: '/target' }) + } + return { parent: true } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + context: childContext, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/parent/child' }) + await redirectNavigation + + expect(router.state.location.pathname).toBe('/target') + // Preload lanes are not cancelled by navigations: the descendant context + // still computes (and may seed the cache), while the navigation wins the + // presented state. Only navigation lanes stop abandoned descendant work. + expect(childContext).toHaveBeenCalledOnce() +}) diff --git a/packages/router-core/tests/preflight-signal-currentness.test.ts b/packages/router-core/tests/preflight-signal-currentness.test.ts new file mode 100644 index 0000000000..23519edfec --- /dev/null +++ b/packages/router-core/tests/preflight-signal-currentness.test.ts @@ -0,0 +1,49 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, redirect } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('a route-context redirect retires its matching generation before following the redirect', async () => { + let sourceSignal: AbortSignal | undefined + const sourceBeforeLoad = vi.fn() + const sourceLoader = vi.fn() + const targetLoader = vi.fn(() => 'target') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + context: ({ abortController }) => { + sourceSignal = abortController.signal + throw redirect({ to: '/target' }) + }, + beforeLoad: sourceBeforeLoad, + loader: sourceLoader, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader: targetLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/source' }) + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'target', + }) + expect(sourceSignal?.aborted).toBe(true) + expect(sourceBeforeLoad).not.toHaveBeenCalled() + expect(sourceLoader).not.toHaveBeenCalled() + expect(targetLoader).toHaveBeenCalledOnce() +}) diff --git a/packages/router-core/tests/preload-adoption.test.ts b/packages/router-core/tests/preload-adoption.test.ts new file mode 100644 index 0000000000..94d31e2e94 --- /dev/null +++ b/packages/router-core/tests/preload-adoption.test.ts @@ -0,0 +1,367 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.useRealTimers() +}) + +/** + * Preload adoption edge cases. The happy path (navigation adopts an + * in-flight preload's successful loader run) and the control-flow + * non-leakage path are pinned in load.test.ts; this file pins the + * adoption boundary: a donor must have its loader genuinely in flight. A + * preload still in its serial phase can itself be waiting on the navigation + * through the borrow protocol, while a stale successful donor can still have + * fresh loader work pending behind its cached snapshot. + */ + +describe('preload adoption', () => { + test('navigation waits for fresh data from an in-flight stale preload revalidation', async () => { + vi.useFakeTimers() + vi.setSystemTime(0) + + const revalidationGate = createControlledPromise<{ + notifications: Array + }>() + const revalidationStarted = createControlledPromise() + let loaderCalls = 0 + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const notificationsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/notifications', + staleTime: 0, + preloadStaleTime: 0, + gcTime: 60_000, + loader: { + staleReloadMode: 'blocking', + handler: () => { + loaderCalls++ + if (loaderCalls === 1) { + return { notifications: ['old'] } + } + + revalidationStarted.resolve() + return revalidationGate + }, + }, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, notificationsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/notifications' } as any) + + expect(loaderCalls).toBe(1) + + // The cached successful preload is now stale. A second hover starts a + // genuine revalidation while the user clicks the link. + vi.setSystemTime(1) + const revalidation = router.preloadRoute({ + to: '/notifications', + } as any) + await revalidationStarted + expect(loaderCalls).toBe(2) + + let navigationSettled = false + const navigation = router.navigate({ to: '/notifications' }).then(() => { + navigationSettled = true + }) + await Promise.resolve() + + // The navigation may share the revalidation, but it must not treat the + // stale cached snapshot as the completed result while fresh work runs. + expect(revalidationGate.status).toBe('pending') + expect(navigationSettled).toBe(false) + + revalidationGate.resolve({ notifications: ['fresh'] }) + await Promise.all([revalidation, navigation]) + + // A fix may either share the fresh revalidation or run a navigation + // loader of its own; correctness only requires publishing fresh data. + expect(loaderCalls).toBeGreaterThanOrEqual(2) + expect( + router.state.matches.find( + (match) => match.routeId === notificationsRoute.id, + )?.loaderData, + ).toEqual({ notifications: ['fresh'] }) + }) + + test('navigation runs beforeLoad independently of a preload serial phase', async () => { + const beforeLoadGate = createControlledPromise() + const preloadSerialStarted = createControlledPromise() + let beforeLoadCalls = 0 + const loader = vi.fn(() => 'data') + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + beforeLoad: async () => { + beforeLoadCalls++ + if (beforeLoadCalls === 1) { + // Keep the shared lane's serial phase in flight. + preloadSerialStarted.resolve() + await beforeLoadGate + } + }, + loader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, fooRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + + // Preload is stuck in its serial phase — its loader has NOT started. + const preload = router.preloadRoute({ to: '/foo' } as any) + await preloadSerialStarted + expect(beforeLoadCalls).toBe(1) + + const navigation = router.navigate({ to: '/foo' }) + await vi.waitFor(() => expect(beforeLoadCalls).toBe(2)) + expect(loader).toHaveBeenCalledTimes(1) + + beforeLoadGate.resolve() + await Promise.all([navigation, preload]) + expect(beforeLoadCalls).toBe(2) + expect(loader).toHaveBeenCalledTimes(1) + expect( + router.state.matches.find((match) => match.routeId === fooRoute.id) + ?.status, + ).toBe('success') + }) + + test("a sibling preload shares another preload lane's in-flight loader", async () => { + const loaderGate = createControlledPromise() + const loaderStarted = createControlledPromise() + let preloadBeforeLoadCalls = 0 + const loader = vi.fn(() => { + loaderStarted.resolve() + return loaderGate + }) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + preloadStaleTime: Infinity, + beforeLoad: ({ preload }) => { + if (preload) { + preloadBeforeLoadCalls++ + } + }, + loader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, fooRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + + const first = router.preloadRoute({ to: '/foo' } as any) + await loaderStarted + + const second = router.preloadRoute({ to: '/foo' } as any) + await vi.waitFor(() => expect(preloadBeforeLoadCalls).toBe(2)) + expect(loader).toHaveBeenCalledTimes(1) + + loaderGate.resolve('once') + await Promise.all([first, second]) + expect(loader).toHaveBeenCalledTimes(1) + + await router.navigate({ to: '/foo' }) + + expect(loader).toHaveBeenCalledTimes(1) + expect( + router.state.matches.find((match) => match.routeId === fooRoute.id) + ?.loaderData, + ).toBe('once') + }) + + test('a fulfilled undefined loader result is adopted as success', async () => { + const loaderGate = createControlledPromise() + const loaderStarted = createControlledPromise() + const loader = vi.fn(() => { + loaderStarted.resolve() + return loaderGate + }) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + beforeLoad: vi.fn(), + loader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, fooRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + + const preload = router.preloadRoute({ to: '/foo' }) + await loaderStarted + const navigation = router.navigate({ to: '/foo' }) + await Promise.resolve() + expect(loaderGate.status).toBe('pending') + expect(loader).toHaveBeenCalledTimes(1) + + loaderGate.resolve(undefined) + await Promise.all([preload, navigation]) + + expect(loader).toHaveBeenCalledTimes(1) + const match = router.state.matches.find( + (candidate) => candidate.routeId === fooRoute.id, + ) + expect(match?.status).toBe('success') + expect(match?.loaderData).toBeUndefined() + }) + + test('navigation shares a failed pending preload without starving sibling work', async () => { + const preloadParentStarted = createControlledPromise() + const preloadFailureGate = createControlledPromise() + const childStarted = createControlledPromise() + const childGate = createControlledPromise() + const parentFailure = new Error('preload failed') + let parentLoads = 0 + let childLoads = 0 + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: async ({ preload }) => { + parentLoads++ + if (preload) { + preloadParentStarted.resolve() + await preloadFailureGate + throw parentFailure + } + return 'navigation data' + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childLoads++ + childStarted.resolve() + return childGate + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/parent/child' } as any) + await Promise.all([preloadParentStarted, childStarted]) + expect(parentLoads).toBe(1) + expect(childLoads).toBe(1) + + const navigation = router.navigate({ to: '/parent/child' }) + await Promise.resolve() + expect(preloadFailureGate.status).toBe('pending') + expect(parentLoads).toBe(1) + expect(childLoads).toBe(1) + + preloadFailureGate.resolve() + childGate.resolve('child data') + await Promise.all([navigation, preload]) + + expect(parentLoads).toBe(1) + expect(childLoads).toBe(1) + expect(router.state.location.pathname).toBe('/parent/child') + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'error', + error: parentFailure, + }) + expect( + router.state.matches.find((match) => match.routeId === childRoute.id), + ).toMatchObject({ + status: 'success', + loaderData: 'child data', + }) + }) + + test('a route with preload disabled does not discard its preloaded ancestor', async () => { + const parentLoader = vi.fn(() => 'parent data') + const childLoader = vi.fn(() => 'child data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + preloadStaleTime: Infinity, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + preload: false, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/parent/child' } as any) + await router.navigate({ to: '/parent/child' }) + + expect(parentLoader).toHaveBeenCalledTimes(1) + expect(childLoader).toHaveBeenCalledTimes(1) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id) + ?.loaderData, + ).toBe('parent data') + expect( + router.state.matches.find((match) => match.routeId === childRoute.id) + ?.loaderData, + ).toBe('child data') + }) +}) diff --git a/packages/router-core/tests/preload-background-parent-coherence.test.ts b/packages/router-core/tests/preload-background-parent-coherence.test.ts new file mode 100644 index 0000000000..3dcbb62824 --- /dev/null +++ b/packages/router-core/tests/preload-background-parent-coherence.test.ts @@ -0,0 +1,88 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('unrelated parent background work does not block a cacheable child preload', async () => { + const backgroundResponse = createControlledPromise<{ revision: number }>() + const childLoaderStarted = createControlledPromise() + let parentLoadCount = 0 + + const parentLoader = vi.fn(() => { + parentLoadCount++ + return parentLoadCount === 1 ? { revision: 1 } : backgroundResponse + }) + const childLoader = vi.fn(async ({ parentMatchPromise }) => { + childLoaderStarted.resolve() + const parentMatch = await parentMatchPromise + return { + parentRevision: (parentMatch.loaderData as { revision: number }).revision, + } + }) + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: parentLoader, + staleTime: Infinity, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + staleTime: Infinity, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + }) + + await router.load() + expect(parentLoader).toHaveBeenCalledTimes(1) + + // Invalidating a successful loader uses the default background revalidation + // mode. The public invalidate promise settles once that private reload has + // started; its network response can remain pending. + await router.invalidate({ + filter: (match) => match.routeId === parentRoute.id, + }) + expect(parentLoader).toHaveBeenCalledTimes(2) + + const childPreload = router.preloadRoute({ to: '/parent/child' }) + + await childLoaderStarted + expect(backgroundResponse.status).toBe('pending') + expect(parentLoader).toHaveBeenCalledTimes(2) + await childPreload + + // Loader data is deliberately reusable across context generations. The + // child preload may finish from the visible parent generation without + // waiting for unrelated background work to win publication. + expect(backgroundResponse.status).toBe('pending') + expect(childLoader).toHaveBeenCalledTimes(1) + + backgroundResponse.resolve({ revision: 2 }) + await vi.waitFor(() => + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id) + ?.loaderData, + ).toEqual({ revision: 2 }), + ) + expect(parentLoader).toHaveBeenCalledTimes(2) + + await router.navigate({ to: '/parent/child' }) + + const parentMatch = router.state.matches.find( + (match) => match.routeId === parentRoute.id, + ) + const childMatch = router.state.matches.find( + (match) => match.routeId === childRoute.id, + ) + + expect(parentLoader).toHaveBeenCalledTimes(2) + expect(childLoader).toHaveBeenCalledTimes(1) + expect(parentMatch?.loaderData).toEqual({ revision: 2 }) + expect(childMatch?.loaderData).toEqual({ parentRevision: 1 }) +}) diff --git a/packages/router-core/tests/preload-beforeload-reuse.test.ts b/packages/router-core/tests/preload-beforeload-reuse.test.ts new file mode 100644 index 0000000000..c9d70bae5d --- /dev/null +++ b/packages/router-core/tests/preload-beforeload-reuse.test.ts @@ -0,0 +1,669 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +afterEach(() => { + vi.useRealTimers() +}) + +describe('preloaded loader reuse with fresh beforeLoad context', () => { + test('reruns nested context for navigation while reusing loader data', async () => { + const parentBeforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + parent: preload ? 'preloaded parent' : 'loaded parent', + })) + const childBeforeLoad = vi.fn( + ({ + context, + preload, + }: { + context: { parent: string } + preload: boolean + }) => ({ + child: `${context.parent}:${preload}`, + }), + ) + const childLoader = vi.fn( + ({ + context, + preload, + }: { + context: { parent: string; child: string } + preload: boolean + }) => ({ context, preload }), + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + staleTime: Infinity, + beforeLoad: parentBeforeLoad, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + staleTime: Infinity, + beforeLoad: childBeforeLoad, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/parent/child' }) + await router.navigate({ to: '/parent/child' }) + + expect(parentBeforeLoad).toHaveBeenCalledTimes(2) + expect(parentBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ preload: false }), + ) + expect(childBeforeLoad).toHaveBeenCalledTimes(2) + expect(childBeforeLoad).toHaveBeenLastCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ parent: 'loaded parent' }), + preload: false, + }), + ) + expect(childLoader).toHaveBeenCalledTimes(1) + const match = router.state.matches.find( + (candidate) => candidate.routeId === childRoute.id, + ) + expect(match?.context).toEqual({ + parent: 'loaded parent', + child: 'loaded parent:false', + }) + expect(match?.loaderData).toEqual({ + context: { + parent: 'preloaded parent', + child: 'preloaded parent:true', + }, + preload: true, + }) + }) + + test('reruns beforeLoad while sharing an identical active preload loader', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + guard: preload ? 'preloaded' : 'loaded', + })) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + preloadStaleTime: Infinity, + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/guarded' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate({ to: '/guarded' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + + loaderGate.resolve('shared loader data') + await Promise.all([preload, navigation]) + + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.context).toEqual({ guard: 'loaded' }) + expect(router.state.matches.at(-1)?.loaderData).toBe('shared loader data') + }) + + test.each([ + { age: 50, expected: [false, true, false], guard: 'loaded' }, + { age: 100, expected: [false, true, false], guard: 'loaded' }, + ])( + 'reruns completed beforeLoad while retaining navigation-owned loader data at age $age', + async ({ age, expected, guard }) => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + guard: preload ? 'preloaded' : 'loaded', + })) + const loader = vi.fn(({ preload }: { preload: boolean }) => preload) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + preloadStaleTime: 100, + beforeLoad, + shouldReload: false, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/guarded' }) + await router.navigate({ to: '/' }) + vi.setSystemTime(5_000) + await router.preloadRoute({ to: '/guarded' }) + vi.setSystemTime(5_000 + age) + await router.navigate({ to: '/guarded' }) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual( + expected, + ) + expect(loader.mock.calls.map(([context]) => context.preload)).toEqual([ + false, + ]) + expect(router.state.matches.at(-1)?.context).toEqual({ guard }) + }, + ) + + test('does not cache beforeLoad-only context across an unrelated navigation', async () => { + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + guard: preload ? 'preloaded' : 'loaded', + })) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + preloadStaleTime: Infinity, + preloadGcTime: Infinity, + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, otherRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + await router.navigate({ to: '/other' }) + await router.navigate({ to: '/guarded' }) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(router.state.matches.at(-1)?.context).toEqual({ + guard: 'loaded', + }) + }) + + test('does not reuse child context under a different parent match', async () => { + const childBeforeLoad = vi.fn( + ({ + context, + preload, + }: { + context: { version: number } + preload: boolean + }) => ({ + childVersion: context.version, + }), + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + version: Number(search.version), + }), + loaderDeps: ({ search }) => ({ + version: search.version, + }), + context: ({ deps }: { deps: { version: number } }) => ({ + version: deps.version, + }), + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + preloadStaleTime: Infinity, + beforeLoad: childBeforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ + to: '/parent/child', + search: { version: 1 }, + }) + await router.navigate({ + to: '/parent/child', + search: { version: 2 }, + }) + + expect( + childBeforeLoad.mock.calls.map(([context]) => context.preload), + ).toEqual([true, false]) + expect(router.state.matches.at(-1)?.context).toEqual({ + version: 2, + childVersion: 2, + }) + }) + + test('reruns beforeLoad when the completed preload reaches its stale boundary', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const seen: Array = [] + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + preloadStaleTime: 100, + beforeLoad: ({ preload }) => { + seen.push(preload) + return { generation: preload ? 'preload' : 'navigation' } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + vi.setSystemTime(1_100) + await router.navigate({ to: '/guarded' }) + + expect(seen).toEqual([true, false]) + expect(router.state.matches.at(-1)?.context).toEqual({ + generation: 'navigation', + }) + }) + + test('reruns descendant context after an ancestor becomes stale', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const parentSeen: Array = [] + const childSeen: Array = [] + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + staleTime: Infinity, + preloadStaleTime: 100, + beforeLoad: ({ preload }) => { + parentSeen.push(preload) + return { parent: preload ? 'preloaded parent' : 'loaded parent' } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + staleTime: Infinity, + preloadStaleTime: 1_000, + beforeLoad: ({ context, preload }) => { + childSeen.push(preload) + return { child: `${context.parent}:${preload}` } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/parent/child' }) + vi.setSystemTime(1_100) + await router.navigate({ to: '/parent/child' }) + + expect(parentSeen).toEqual([true, false]) + expect(childSeen).toEqual([true, false]) + expect(router.state.matches.at(-1)?.context).toEqual({ + parent: 'loaded parent', + child: 'loaded parent:false', + }) + }) + + test('invalidation prevents reuse of completed preload context', async () => { + const seen: Array = [] + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + beforeLoad: ({ preload }) => { + seen.push(preload) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + await router.invalidate({ + filter: (match) => match.routeId === guardedRoute.id, + }) + await router.navigate({ to: '/guarded' }) + + expect(seen).toEqual([true, false]) + }) + + test('invalidation prevents adoption of context from an active preload', async () => { + const loaderGate = createControlledPromise() + let generation = 1 + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + generation, + preload, + })) + const loader = vi.fn(() => + loader.mock.calls.length === 1 ? loaderGate : 'new loader data', + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + preloadStaleTime: Infinity, + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/guarded' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + generation = 2 + await router.invalidate() + const navigation = router.navigate({ to: '/guarded' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + loaderGate.resolve('shared loader data') + await Promise.all([preload, navigation]) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.context).toEqual({ + generation: 2, + preload: false, + }) + expect(router.state.matches.at(-1)?.loaderData).toBe('new loader data') + }) + + test('preload false does not cache beforeLoad context for navigation', async () => { + const seen: Array = [] + const loader = vi.fn() + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + preload: false, + staleTime: Infinity, + beforeLoad: ({ preload }) => { + seen.push(preload) + }, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + + expect(seen).toEqual([true]) + expect(loader).not.toHaveBeenCalled() + + await router.navigate({ to: '/guarded' }) + + expect(seen).toEqual([true, false]) + expect(loader).toHaveBeenCalledTimes(1) + }) + + test('shouldReload remains scoped to loader data', async () => { + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + guard: preload ? 'preloaded' : 'loaded', + })) + const loader = vi.fn(({ preload }: { preload: boolean }) => preload) + const shouldReload = vi.fn(() => true) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + beforeLoad, + shouldReload, + loader: { + staleReloadMode: 'blocking', + handler: loader, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + await router.navigate({ to: '/guarded' }) + + expect(beforeLoad).toHaveBeenCalledTimes(2) + expect(loader).toHaveBeenCalledTimes(2) + expect(loader.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(shouldReload).toHaveBeenCalledTimes(1) + expect(shouldReload).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ guard: 'loaded' }), + preload: false, + }), + ) + }) + + test('shouldReload false does not keep stale beforeLoad context', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + guard: preload ? 'preloaded' : 'loaded', + })) + const loader = vi.fn(({ preload }: { preload: boolean }) => preload) + const shouldReload = vi.fn(() => false) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + staleTime: Infinity, + preloadStaleTime: 100, + beforeLoad, + shouldReload, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/guarded' }) + vi.setSystemTime(1_100) + await router.navigate({ to: '/guarded' }) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + ]) + expect(shouldReload).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.context).toEqual({ guard: 'loaded' }) + }) + + test.each([false, true])( + 'a beforeLoad failure clears context from the previous generation with isServer=%s', + async (isServer) => { + const failure = new Error('guard failed') + let fail = false + const rootRoute = new BaseRootRoute({}) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + context: () => ({ routeContext: true }), + beforeLoad: () => { + if (fail) { + throw failure + } + return { guard: 'accepted' } + }, + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/guarded'] }), + isServer, + }) + + await router.load() + expect(router.state.matches.at(-1)?.context).toEqual({ + routeContext: true, + guard: 'accepted', + }) + + fail = true + if (isServer) { + const response = await loadServerResponse(router, '/guarded') + expect(response.status).toBe(500) + } else { + await router.load() + } + + const failedMatch = router.state.matches.at(-1) + expect(failedMatch).toMatchObject({ + status: 'error', + error: failure, + }) + expect(failedMatch?.context).toEqual({ routeContext: true }) + }, + ) + + test.each([false, true])( + 'a validation failure clears context from the previous generation with isServer=%s', + async (isServer) => { + let fail = false + const rootRoute = new BaseRootRoute({}) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + validateSearch: () => { + if (fail) { + throw new Error('invalid search') + } + return {} + }, + context: () => ({ routeContext: true }), + beforeLoad: () => ({ guard: 'accepted' }), + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/guarded'] }), + isServer, + }) + + await router.load() + expect(router.state.matches.at(-1)?.context).toEqual({ + routeContext: true, + guard: 'accepted', + }) + fail = true + if (isServer) { + const response = await loadServerResponse(router, '/guarded') + expect(response.status).toBe(500) + } else { + await router.load() + } + + const failedMatch = router.state.matches.at(-1) + expect(failedMatch).toMatchObject({ + status: 'error', + }) + expect(failedMatch?.context).toEqual({ routeContext: true }) + }, + ) +}) diff --git a/packages/router-core/tests/preload-navigation-adoption.test.ts b/packages/router-core/tests/preload-navigation-adoption.test.ts new file mode 100644 index 0000000000..058c401f30 --- /dev/null +++ b/packages/router-core/tests/preload-navigation-adoption.test.ts @@ -0,0 +1,221 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +// A navigation that adopts an in-flight preload's loader run must reuse that +// run: it must not abort the preload's loader signal or rerun the loader. This +// is generic router adoption coverage; issue #4476 is covered separately with +// the React Query observer-unmount sequence from its reproduction. +describe('navigation adopting an in-flight preload', () => { + test('adopted preload loader runs once and its signal is not aborted', async () => { + const loaderGate = createControlledPromise() + const loaderStarted = createControlledPromise() + const beforeLoad = vi.fn() + let preloadSignal: AbortSignal | undefined + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + preloadSignal = abortController.signal + loaderStarted.resolve() + return loaderGate + }, + ) + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + beforeLoad, + loader, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, fooRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + + // Start the preload and wait until its loader is actually in flight. + const preload = router.preloadRoute({ to: '/foo' }) + await loaderStarted + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(preloadSignal).toBeDefined() + expect(preloadSignal?.aborted).toBe(false) + + // The navigation runs its own guard but shares the loader generation. + const navigation = router.navigate({ to: '/foo' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loaderGate.status).toBe('pending') + expect(loader).toHaveBeenCalledTimes(1) + expect(preloadSignal?.aborted).toBe(false) + + // Resolve the shared loader and let both settle. + loaderGate.resolve('adopted') + await Promise.all([navigation, preload]) + + // Loader ran exactly once; the adopted run's signal was never aborted. + expect(loader).toHaveBeenCalledTimes(1) + expect(preloadSignal?.aborted).toBe(false) + + // Navigation committed with the adopted loaderData. + const committed = router.state.matches.find( + (match) => match.routeId === fooRoute.id, + ) + expect(committed?.status).toBe('success') + expect(committed?.loaderData).toBe('adopted') + + // Adoption transfers loader-data lifetime ownership to the active match. + // The shared request remains alive while rendered, then aborts on unload. + await router.navigate({ to: '/' }) + expect(preloadSignal?.aborted).toBe(true) + }) + + test('reruns beforeLoad when router context changes during an active preload', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn( + ({ + context, + preload, + }: { + context: { auth: boolean } + preload: boolean + }) => ({ authorization: `${context.auth}:${preload}` }), + ) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + context: { auth: false }, + }) + + await router.load() + const preload = router.preloadRoute({ to: '/guarded' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + router.update({ ...router.options, context: { auth: true } }) + const navigation = router.navigate({ to: '/guarded' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + + loaderGate.resolve('shared') + await Promise.all([preload, navigation]) + expect(router.state.matches.at(-1)?.context).toEqual({ + auth: true, + authorization: 'true:false', + }) + }) + + test('reruns beforeLoad when user location state changes at the same href', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn( + ({ location, preload }: { location: any; preload: boolean }) => ({ + authorization: `${location.state.auth}:${preload}`, + }), + ) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, guardedRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ + to: '/guarded', + state: { auth: 'old' } as any, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate({ + to: '/guarded', + state: { auth: 'new' } as any, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + expect(beforeLoad.mock.results.map((result) => result.value)).toEqual([ + { authorization: 'old:true' }, + { authorization: 'new:false' }, + ]) + expect(loader).toHaveBeenCalledTimes(1) + + loaderGate.resolve('shared') + await Promise.all([preload, navigation]) + expect(router.state.matches.at(-1)?.context).toMatchObject({ + authorization: 'new:false', + }) + }) + + test('reruns beforeLoad when the route tree changes during an active preload', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn() + const loader = vi.fn(() => loaderGate) + const createRouteTree = () => { + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const guardedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/guarded', + beforeLoad, + loader, + }) + return rootRoute.addChildren([indexRoute, guardedRoute]) + } + const router = createTestRouter({ + routeTree: createRouteTree(), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/guarded' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + router.update({ ...router.options, routeTree: createRouteTree() }) + const navigation = router.navigate({ to: '/guarded' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + loaderGate.resolve('shared') + await Promise.all([preload, navigation]) + expect(loader).toHaveBeenCalledOnce() + }) +}) diff --git a/packages/router-core/tests/preload-public-cache-behavior.test.ts b/packages/router-core/tests/preload-public-cache-behavior.test.ts new file mode 100644 index 0000000000..2ad8017ee5 --- /dev/null +++ b/packages/router-core/tests/preload-public-cache-behavior.test.ts @@ -0,0 +1,785 @@ +import { afterEach, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() +}) + +// https://github.com/TanStack/router/issues/2980 +// Repeated child preloads must borrow a stale active parent instead of rerunning +// its loader. +test('#2980: repeated child preloads do not rerun a stale active parent loader', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + + const layoutLoader = vi.fn(() => 'layout data') + const childLoader = vi.fn((childId: string) => `child ${childId}`) + const rootRoute = new BaseRootRoute({}) + const layoutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/layout', + staleTime: 10, + loader: layoutLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '$childId', + loader: ({ params }) => childLoader(params.childId), + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([layoutRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/layout'] }), + }) + + await router.load() + expect(layoutLoader).toHaveBeenCalledTimes(1) + vi.setSystemTime(2_000) + + await router.preloadRoute({ + to: '/layout/$childId', + params: { childId: 'one' }, + }) + expect(layoutLoader).toHaveBeenCalledTimes(1) + expect(childLoader).toHaveBeenNthCalledWith(1, 'one') + + await router.preloadRoute({ + to: '/layout/$childId', + params: { childId: 'two' }, + }) + + expect(layoutLoader).toHaveBeenCalledTimes(1) + expect(childLoader).toHaveBeenCalledTimes(2) + expect(childLoader).toHaveBeenNthCalledWith(2, 'two') +}) + +// Public contract: the public stale/gc options determine whether user loader +// code runs. A read-only preload must not silently shorten a navigation-owned +// cache entry's lifetime. +test('fresh navigation data keeps its gc policy when a preload reuses it', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + + let revision = 0 + const loader = vi.fn(() => ++revision) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + gcTime: 30 * 60_000, + preloadGcTime: 30_000, + preloadStaleTime: 10 * 60_000, + staleTime: Infinity, + loader, + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, reportsRoute, otherRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/reports' }) + await router.navigate({ to: '/' }) + expect(loader).toHaveBeenCalledTimes(1) + + vi.setSystemTime(61_000) + await router.preloadRoute({ to: '/reports' }) + await router.navigate({ to: '/other' }) + await router.navigate({ to: '/reports' }) + expect(loader).toHaveBeenCalledTimes(1) + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toBe(1) + + await router.navigate({ to: '/' }) + vi.setSystemTime(1_000 + 31 * 60_000) + await router.navigate({ to: '/other' }) + await router.navigate({ to: '/reports' }) + expect(loader).toHaveBeenCalledTimes(2) + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toBe(2) +}) + +// Public contract: clearCache must remain authoritative even if an older +// preload finishes unrelated asset work after the clear. +test('clearCache during an in-flight preload cannot resurrect its borrowed data', async () => { + const headGate = createControlledPromise<{ + meta: Array<{ title: string }> + }>() + let revision = 0 + let headCalls = 0 + const loader = vi.fn(() => ++revision) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + staleTime: Infinity, + preloadStaleTime: Infinity, + loader, + head: () => { + headCalls++ + return headCalls === 1 ? { meta: [{ title: 'Reports' }] } : headGate + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/reports' }) + await router.navigate({ to: '/' }) + expect(loader).toHaveBeenCalledTimes(1) + + const preload = router.preloadRoute({ to: '/reports' }) + await vi.waitFor(() => expect(headCalls).toBe(2)) + expect(headGate.status).toBe('pending') + router.clearCache() + headGate.resolve({ meta: [{ title: 'Preloaded Reports' }] }) + await preload + + await router.navigate({ to: '/reports' }) + expect(loader).toHaveBeenCalledTimes(2) + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toBe(2) +}) + +test('clearCache cancels a brand-new in-flight preload before it can populate the cache', async () => { + const firstLoad = createControlledPromise() + const loader = vi + .fn<() => string | Promise>() + .mockImplementationOnce(() => firstLoad) + .mockReturnValue('navigation data') + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + preloadStaleTime: Infinity, + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/reports' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + router.clearCache() + firstLoad.resolve('obsolete preload data') + await preload + await router.navigate({ to: '/reports' }) + + expect(loader).toHaveBeenCalledTimes(2) + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toBe('navigation data') +}) + +test('clearCache does not reserve a discarded preload for a navigation in beforeLoad', async () => { + const navigationBeforeLoad = createControlledPromise() + let preloadSignal: AbortSignal | undefined + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => + preload ? undefined : navigationBeforeLoad, + ) + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + const generation = loader.mock.calls.length + if (generation !== 1) { + return 'fresh navigation data' + } + preloadSignal = abortController.signal + return new Promise((_resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => reject(abortController.signal.reason), + { once: true }, + ) + }) + }, + ) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + const navigation = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + router.clearCache({ + filter: (match) => match.routeId === targetRoute.id, + }) + await vi.waitFor(() => expect(preloadSignal?.aborted).toBe(true)) + await preload + + navigationBeforeLoad.resolve() + await navigation + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe('fresh navigation data') +}) + +test('clearCache does not abort loader work still used by a navigation', async () => { + const loaderGate = createControlledPromise() + let loaderSignal: AbortSignal | undefined + const beforeLoad = vi.fn() + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + loaderSignal = abortController.signal + return loaderGate + }, + ) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + const navigation = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + router.clearCache({ + filter: (match) => match.routeId === targetRoute.id, + }) + expect(loaderSignal?.aborted).toBe(false) + + loaderGate.resolve('shared loader data') + await Promise.all([preload, navigation]) + + expect(loader).toHaveBeenCalledOnce() + expect(router.state.matches.at(-1)?.loaderData).toBe('shared loader data') +}) + +test('clearCache leaves cache authority intact when its filter throws', async () => { + let firstSignal: AbortSignal | undefined + let secondSignal: AbortSignal | undefined + const rootRoute = new BaseRootRoute({}) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/first', + loader: ({ abortController }) => { + firstSignal = abortController.signal + return 'first data' + }, + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/second', + loader: ({ abortController }) => { + secondSignal = abortController.signal + return 'second data' + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/first' }) + await router.preloadRoute({ to: '/second' }) + const filterError = new Error('filter failed') + + expect(() => + router.clearCache({ + filter: (match) => { + if (match.routeId === firstRoute.id) { + return true + } + if (match.routeId === secondRoute.id) { + throw filterError + } + return false + }, + }), + ).toThrow(filterError) + expect(firstSignal?.aborted).toBe(false) + expect(secondSignal?.aborted).toBe(false) + + router.clearCache() + expect(firstSignal?.aborted).toBe(true) + expect(secondSignal?.aborted).toBe(true) +}) + +test('clearCache installs replacement authorities before abort listeners reenter', async () => { + let reentrantNavigation: Promise | undefined + let router: ReturnType + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + const generation = loader.mock.calls.length + if (generation !== 2) { + return `generation ${generation}` + } + return new Promise((_resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => { + reentrantNavigation = router.navigate({ to: '/target' }) + reject(abortController.signal.reason) + }, + { once: true }, + ) + }) + }, + ) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + staleTime: Infinity, + preloadStaleTime: 0, + loader, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/target' }) + await router.navigate({ to: '/' }) + + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + router.clearCache() + + await reentrantNavigation + await preload + expect(loader).toHaveBeenCalledTimes(3) + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)?.loaderData).toBe('generation 3') +}) + +test('clearCache detaches every discarded flight before abort listeners reenter', async () => { + let reentrantNavigation: Promise | undefined + let router: ReturnType + const firstBeforeLoad = vi.fn( + ({ abortController }: { abortController: AbortController }) => + new Promise((_resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => { + reentrantNavigation = router.navigate({ to: '/second' }) + reject(abortController.signal.reason) + }, + { once: true }, + ) + }), + ) + const secondLoader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + const generation = secondLoader.mock.calls.length + if (generation > 1) { + return `generation ${generation}` + } + return new Promise((_resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => reject(abortController.signal.reason), + { once: true }, + ) + }) + }, + ) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/first', + beforeLoad: firstBeforeLoad, + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/second', + loader: secondLoader, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const firstPreload = router.preloadRoute({ to: '/first' }) + await vi.waitFor(() => expect(firstBeforeLoad).toHaveBeenCalledOnce()) + const secondPreload = router.preloadRoute({ to: '/second' }) + await vi.waitFor(() => expect(secondLoader).toHaveBeenCalledOnce()) + + router.clearCache() + + await reentrantNavigation + await Promise.all([firstPreload, secondPreload]) + expect(secondLoader).toHaveBeenCalledTimes(2) + expect(router.state.location.pathname).toBe('/second') + expect(router.state.matches.at(-1)?.loaderData).toBe('generation 2') +}) + +test('filtered clearCache keeps shared loader work discoverable through a retained preload', async () => { + const layoutGate = createControlledPromise() + const layoutLoader = vi.fn(() => layoutGate) + const rootRoute = new BaseRootRoute({}) + const layoutRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/layout', + loader: layoutLoader, + }) + const firstBeforeLoad = vi.fn() + const firstRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/first', + beforeLoad: firstBeforeLoad, + }) + const secondBeforeLoad = vi.fn() + const secondLoader = vi.fn(() => 'second data') + const secondRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/second', + beforeLoad: secondBeforeLoad, + loader: secondLoader, + }) + const thirdBeforeLoad = vi.fn() + const thirdLoader = vi.fn(() => 'third data') + const thirdRoute = new BaseRoute({ + getParentRoute: () => layoutRoute, + path: '/third', + beforeLoad: thirdBeforeLoad, + loader: thirdLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + layoutRoute.addChildren([firstRoute, secondRoute, thirdRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const firstPreload = router.preloadRoute({ to: '/layout/first' }) + const secondPreload = router.preloadRoute({ to: '/layout/second' }) + await vi.waitFor(() => { + expect(firstBeforeLoad).toHaveBeenCalledOnce() + expect(secondBeforeLoad).toHaveBeenCalledOnce() + expect(layoutLoader).toHaveBeenCalledOnce() + expect(secondLoader).toHaveBeenCalledOnce() + }) + + router.clearCache({ + filter: (match) => match.routeId === firstRoute.id, + }) + const thirdPreload = router.preloadRoute({ to: '/layout/third' }) + await vi.waitFor(() => { + expect(thirdBeforeLoad).toHaveBeenCalledOnce() + expect(thirdLoader).toHaveBeenCalledOnce() + }) + + expect(layoutLoader).toHaveBeenCalledOnce() + layoutGate.resolve('layout data') + await Promise.all([firstPreload, secondPreload, thirdPreload]) +}) + +test('independent concurrent preloads both populate the cache', async () => { + const firstGate = createControlledPromise() + const secondGate = createControlledPromise() + const firstLoader = vi.fn(() => firstGate) + const secondLoader = vi.fn(() => secondGate) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/first', + preloadStaleTime: Infinity, + loader: firstLoader, + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/second', + preloadStaleTime: Infinity, + loader: secondLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, firstRoute, secondRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const firstPreload = router.preloadRoute({ to: '/first' }) + const secondPreload = router.preloadRoute({ to: '/second' }) + await vi.waitFor(() => { + expect(firstLoader).toHaveBeenCalledOnce() + expect(secondLoader).toHaveBeenCalledOnce() + }) + + firstGate.resolve('first data') + await firstPreload + secondGate.resolve('second data') + await secondPreload + + await router.navigate({ to: '/first' }) + await router.navigate({ to: '/second' }) + expect(firstLoader).toHaveBeenCalledOnce() + expect(secondLoader).toHaveBeenCalledOnce() +}) + +test.each([ + ['unfiltered', undefined, true], + ['filtered affected', 'target', true], + ['filtered unaffected', 'home', false], +] as const)( + '%s invalidation preserves the correct preload generation', + async (_mode, selected, shouldReload) => { + const oldLoaderGate = createControlledPromise() + let generation = 1 + const loader = vi.fn(() => + loader.mock.calls.length === 1 + ? oldLoaderGate + : `generation ${generation}`, + ) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + preloadStaleTime: Infinity, + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + generation = 2 + await (selected + ? router.invalidate({ + filter: (match) => + match.routeId === + (selected === 'target' ? targetRoute.id : homeRoute.id), + }) + : router.invalidate()) + + oldLoaderGate.resolve('generation 1') + await preload + await router.navigate({ to: '/target' }) + + expect(loader).toHaveBeenCalledTimes(shouldReload ? 2 : 1) + expect(router.state.matches.at(-1)?.loaderData).toBe( + `generation ${shouldReload ? 2 : 1}`, + ) + }, +) + +test('filtering only an active preload invalidates seeded same-ID cache data', async () => { + const replacementGate = createControlledPromise() + const loader = vi.fn(() => { + const generation = loader.mock.calls.length + return generation === 2 ? replacementGate : `generation ${generation}` + }) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 1), + }), + preloadStaleTime: 0, + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/target', search: { revision: 1 } }) + await router.navigate({ to: '/' }) + const preload = router.preloadRoute({ + to: '/target', + search: { revision: 2 }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + + await router.invalidate({ + filter: (match) => + match.routeId === targetRoute.id && + (match.search as { revision?: number }).revision === 2, + }) + replacementGate.resolve('obsolete generation 2') + await preload + await router.navigate({ to: '/target' }) + + await vi.waitFor(() => { + expect(loader).toHaveBeenCalledTimes(3) + expect(router.state.matches.at(-1)?.loaderData).toBe('generation 3') + }) +}) + +test('invalidation wins after a preload loader settles but before it publishes', async () => { + let invalidation: Promise | undefined + let generation = 0 + let router: ReturnType + const loader = vi.fn(() => { + const data = `generation ${++generation}` + if (generation === 1) { + // Schedule invalidation after the loader outcome has fulfilled while the + // preload lane's cache publication is still queued. + let turn = Promise.resolve() + for (let index = 0; index < 4; index++) { + turn = turn.then() + } + invalidation = turn.then(() => router.invalidate()) + } + return data + }) + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + preloadStaleTime: Infinity, + staleTime: Infinity, + loader, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(invalidation).toBeDefined()) + await invalidation + await preload + await router.navigate({ to: '/target' }) + + await vi.waitFor(() => { + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe('generation 2') + }) +}) + +// Preload lanes are not cancelled by unrelated navigations: an in-flight +// async beforeLoad runs to completion, its loader still executes and seeds +// the cache, and a later navigation to the target reuses that work. +test('an unrelated navigation does not cancel an in-flight preload beforeLoad', async () => { + const beforeLoadGate = createControlledPromise<{ token: string }>() + const beforeLoad = vi.fn(() => beforeLoadGate) + const loader = vi.fn(() => 'target data') + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + preloadStaleTime: Infinity, + staleTime: Infinity, + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, otherRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + + // Unrelated navigation commits while the preload's beforeLoad is pending. + await router.navigate({ to: '/other' }) + expect(router.state.location.pathname).toBe('/other') + + beforeLoadGate.resolve({ token: 'ctx' }) + await preload + // The preload completed its work despite the navigation. + expect(loader).toHaveBeenCalledTimes(1) + + // Navigating to the target consumes the seeded cache without a new load. + await router.navigate({ to: '/target' }) + expect(router.state.location.pathname).toBe('/target') + expect( + router.state.matches.find((match) => match.routeId === targetRoute.id) + ?.loaderData, + ).toBe('target data') + expect(loader).toHaveBeenCalledTimes(1) +}) diff --git a/packages/router-core/tests/preload-public-signal-lifetime.test.ts b/packages/router-core/tests/preload-public-signal-lifetime.test.ts new file mode 100644 index 0000000000..b884a3b9ad --- /dev/null +++ b/packages/router-core/tests/preload-public-signal-lifetime.test.ts @@ -0,0 +1,155 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +// Public contract: the AbortSignal supplied to loader code belongs to the +// accepted data generation. Adopting preloaded data keeps it alive; accepting +// replacement data aborts it; unloading aborts the replacement generation. +test('an adopted loader signal lives until public replacement and unload', async () => { + const replacementGate = createControlledPromise<{ generation: number }>() + let generation = 0 + let adoptedSignal: AbortSignal | undefined + let replacementSignal: AbortSignal | undefined + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + generation++ + if (generation === 1) { + adoptedSignal = abortController.signal + return { generation } + } + + replacementSignal = abortController.signal + return replacementGate + }, + ) + + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + staleTime: Infinity, + preloadStaleTime: Infinity, + loader: { + staleReloadMode: 'blocking', + handler: loader, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/reports' }) + expect(adoptedSignal?.aborted).toBe(false) + + await router.navigate({ to: '/reports' }) + expect(loader).toHaveBeenCalledTimes(1) + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toEqual({ generation: 1 }) + expect(adoptedSignal?.aborted).toBe(false) + + const replacement = router.invalidate({ forcePending: true }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + expect(replacementGate.status).toBe('pending') + + // Until replacement data is accepted, the currently rendered data still + // owns the adopted signal. + expect(adoptedSignal?.aborted).toBe(false) + expect(replacementSignal?.aborted).toBe(false) + + replacementGate.resolve({ generation: 2 }) + await replacement + expect( + router.state.matches.find((match) => match.routeId === reportsRoute.id) + ?.loaderData, + ).toEqual({ generation: 2 }) + expect(adoptedSignal?.aborted).toBe(true) + expect(replacementSignal?.aborted).toBe(false) + + await router.navigate({ to: '/' }) + expect(replacementSignal?.aborted).toBe(true) +}) + +test('a superseded preload releases its borrowed loader signal lease', async () => { + let parentSignal: AbortSignal | undefined + let navigation!: Promise + + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: ({ abortController }) => { + parentSignal = abortController.signal + return 'parent data' + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + beforeLoad: ({ navigate, preload }) => { + if (preload) { + navigation = navigate({ to: '/' }) + throw navigation + } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + homeRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + }) + + await router.load() + expect(parentSignal?.aborted).toBe(false) + + await router.preloadRoute({ to: '/parent/child' }) + await navigation + + expect(router.state.location.pathname).toBe('/') + expect(parentSignal?.aborted).toBe(true) +}) + +test('a terminal preload aborts its loader generation', async () => { + const routeError = new Error('preload failed') + let signal: AbortSignal | undefined + + const rootRoute = new BaseRootRoute({}) + const homeRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + loader: ({ abortController }) => { + signal = abortController.signal + throw routeError + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([homeRoute, reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const matches = await router.preloadRoute({ to: '/reports' }) + + expect( + matches?.find((match) => match.routeId === reportsRoute.id), + ).toMatchObject({ status: 'error', error: routeError }) + expect(signal?.aborted).toBe(true) +}) diff --git a/packages/router-core/tests/public-client-loading-contract.test.ts b/packages/router-core/tests/public-client-loading-contract.test.ts new file mode 100644 index 0000000000..c3cb0c7577 --- /dev/null +++ b/packages/router-core/tests/public-client-loading-contract.test.ts @@ -0,0 +1,698 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + redirect, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() +}) + +describe('public client loading contracts', () => { + test('blocking loading is observable through match isFetching', async () => { + const loaderGate = createControlledPromise() + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + pendingMs: 0, + pendingComponent: () => null, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const navigation = router.navigate({ to: '/target' }) + + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => + expect( + router.state.matches.find((match) => match.routeId === targetRoute.id), + ).toMatchObject({ status: 'pending', isFetching: 'loader' }), + ) + + loaderGate.resolve('target data') + await navigation + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + isFetching: false, + loaderData: 'target data', + }) + }) + + test('background loading is observable while retaining committed data', async () => { + const reloadGate = createControlledPromise<{ generation: number }>() + let loaderCalls = 0 + const loader = vi.fn(() => + ++loaderCalls === 1 ? { generation: 1 } : reloadGate, + ) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + const invalidation = router.invalidate() + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + isFetching: 'loader', + loaderData: { generation: 1 }, + }) + + reloadGate.resolve({ generation: 2 }) + await invalidation + await vi.waitFor(() => + expect(router.state.matches.at(-1)).toMatchObject({ + status: 'success', + isFetching: false, + loaderData: { generation: 2 }, + }), + ) + }) + + test('a successor does not fall back to older data while a background reload is in flight', async () => { + const backgroundResult = createControlledPromise() + const successorPlanned = createControlledPromise() + let loaderCalls = 0 + const loader = vi.fn(() => { + loaderCalls++ + if (loaderCalls === 1) { + return 'cached data' + } + if (loaderCalls === 2) { + return backgroundResult + } + return 'updated data' + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + validateSearch: (search: Record) => ({ + phase: String(search.phase ?? 'initial'), + }), + shouldReload: ({ location }) => { + const phase = (location.search as { phase: string }).phase + if (phase === 'successor') { + successorPlanned.resolve() + } + return phase === 'reload' ? true : undefined + }, + staleTime: Infinity, + loader: { + staleReloadMode: 'background', + handler: loader, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ + initialEntries: ['/page?phase=initial'], + }), + }) + + await router.load() + await router.navigate({ + to: '/page', + search: { phase: 'reload' }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + + const successor = router.navigate({ + to: '/page', + search: { phase: 'successor' }, + }) + await successorPlanned + backgroundResult.resolve('updated data') + await successor + + await vi.waitFor(() => { + expect(router.state.matches.at(-1)?.loaderData).toBe('updated data') + }) + }) + + test('a background loader can fulfill after aborting its controller', async () => { + let loaderCalls = 0 + const loader = vi.fn(({ abortController }) => { + loaderCalls++ + if (loaderCalls === 1) { + return { generation: 1 } + } + abortController.abort() + return { generation: 2 } + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + await router.invalidate() + + expect(loader).toHaveBeenCalledTimes(2) + await vi.waitFor(() => + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + isFetching: false, + loaderData: { generation: 2 }, + }), + ) + expect(router.state.status).toBe('idle') + }) + + test('a current loader can fulfill after aborting its controller', async () => { + const loader = vi.fn(({ abortController }) => { + abortController.abort() + return 'accepted data' + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + await router.load() + + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.status).toBe('idle') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + loaderData: 'accepted data', + }) + }) + + test('background redirects use the navigation redirect limit', async () => { + let loaderCalls = 0 + const loader = vi.fn(() => { + loaderCalls++ + if (loaderCalls === 1) { + return 'initial data' + } + if (loaderCalls < 30) { + throw redirect({ to: '/page' }) + } + return 'redirect limit was bypassed' + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + shouldReload: true, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + await router.load() + await router.invalidate() + + expect(loader).toHaveBeenCalledTimes(22) + await vi.waitFor(() => + expect( + router.state.matches.find((match) => match.status !== 'success'), + ).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: expect.objectContaining({ message: 'Too many redirects' }), + }), + ) + }) + + test('a blocking child observes the fresh semantic parent generation', async () => { + const parentReload = createControlledPromise<{ revision: number }>() + let parentCalls = 0 + const parentLoader = vi.fn(() => + ++parentCalls === 1 ? { revision: 1 } : parentReload, + ) + const childLoader = vi.fn(async ({ parentMatchPromise }) => { + const parent = await parentMatchPromise + return { + parentRevision: (parent.loaderData as { revision: number }).revision, + } + }) + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + staleTime: Infinity, + shouldReload: true, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: { + staleReloadMode: 'blocking', + handler: childLoader, + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent'] }), + }) + + await router.load() + + let settled = false + const navigation = router.navigate({ to: '/parent/child' }).then(() => { + settled = true + }) + await vi.waitFor(() => expect(parentLoader).toHaveBeenCalledTimes(2)) + + expect(parentReload.status).toBe('pending') + expect(settled).toBe(false) + + parentReload.resolve({ revision: 2 }) + await navigation + + await vi.waitFor(() => + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ loaderData: { revision: 2 } }), + ) + expect( + router.state.matches.find((match) => match.routeId === childRoute.id), + ).toMatchObject({ loaderData: { parentRevision: 2 } }) + }) + + test('a background descendant redirect wins after a blocking ancestor loader fails', async () => { + const childStarted = createControlledPromise() + const childRedirect = createControlledPromise() + const parentError = new Error('parent failed') + let parentLoads = 0 + let childLoads = 0 + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: { + staleReloadMode: 'blocking', + handler: async () => { + if (++parentLoads === 1) { + return 'parent data' + } + await childStarted + throw parentError + }, + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + if (++childLoads === 1) { + return 'child data' + } + childStarted.resolve() + await childRedirect + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + const invalidation = router.invalidate() + await childStarted + childRedirect.resolve() + await invalidation + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + expect( + router.state.matches.some((match) => match.error === parentError), + ).toBe(false) + }) + + test('a hidden background descendant failure cannot replace an ancestor failure', async () => { + const childStarted = createControlledPromise() + const parentError = new Error('parent failed') + const childError = new Error('child failed') + let parentLoads = 0 + let childLoads = 0 + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: { + staleReloadMode: 'blocking', + handler: async () => { + if (++parentLoads === 1) { + return 'parent data' + } + await childStarted + throw parentError + }, + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + if (++childLoads === 1) { + return 'child data' + } + childStarted.resolve() + throw childError + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + await router.invalidate() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ status: 'error', error: parentError }) + expect( + router.state.matches.find((match) => match.routeId === childRoute.id), + ).toMatchObject({ + status: 'success', + error: undefined, + isFetching: false, + loaderData: 'child data', + }) + expect( + router.state.matches.some((match) => match.error === childError), + ).toBe(false) + }) + + test('background projection stays private until its lane wins publication', async () => { + const reloadGate = createControlledPromise<{ title: string }>() + const headGate = createControlledPromise() + const freshHeadStarted = createControlledPromise() + let loaderCalls = 0 + let allowReload = true + const loader = vi.fn(() => + ++loaderCalls === 1 ? { title: 'old' } : reloadGate, + ) + const head = vi.fn(async ({ loaderData }) => { + if (loaderData?.title === 'fresh') { + freshHeadStarted.resolve() + await headGate + } + return { meta: [{ title: loaderData?.title }] } + }) + + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + staleTime: Infinity, + shouldReload: () => allowReload, + loader, + head, + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/other', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, otherRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + allowReload = false + await router.load() + expect(router.state.matches.at(-1)).toMatchObject({ + loaderData: { title: 'old' }, + meta: [{ title: 'old' }], + }) + + allowReload = true + const invalidation = router.invalidate() + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + reloadGate.resolve({ title: 'fresh' }) + await freshHeadStarted + + expect(router.state.matches.at(-1)).toMatchObject({ + loaderData: { title: 'old' }, + meta: [{ title: 'old' }], + }) + + await router.navigate({ to: '/other' }) + allowReload = false + headGate.resolve() + await invalidation + await Promise.resolve() + + await router.navigate({ to: '/page' }) + expect(router.state.matches.at(-1)).toMatchObject({ + loaderData: { title: 'old' }, + meta: [{ title: 'old' }], + isFetching: false, + }) + }) + + test('redirect depth bookkeeping is cleared after a redirect chain settles', async () => { + const hopBeforeLoad = vi.fn(({ params }) => { + const hop = Number(params.hop) + if (hop < 20) { + throw redirect({ + to: '/hop/$hop', + params: { hop: String(hop + 1) }, + } as any) + } + }) + const rootRoute = new BaseRootRoute({}) + const hopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + beforeLoad: hopBeforeLoad, + }) + const againRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/again', + beforeLoad: () => { + throw redirect({ to: '/final' }) + }, + }) + const finalRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/final', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([hopRoute, againRoute, finalRoute]), + history: createMemoryHistory({ initialEntries: ['/hop/0'] }), + }) + + await router.load() + expect(router.state.location.pathname).toBe('/hop/20') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: hopRoute.id, + status: 'success', + }) + + await router.navigate({ to: '/again' }) + expect(router.state.location.pathname).toBe('/final') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: finalRoute.id, + status: 'success', + }) + expect(router.state.status).toBe('idle') + }) + + test('a redirect-limit error aborts concurrent work in its final lane', async () => { + const finalRootAborted = createControlledPromise() + const finalRootData = createControlledPromise() + const errorComponentStarted = createControlledPromise() + const errorComponentGate = createControlledPromise() + let finalRootSignal: AbortSignal | undefined + const ErrorComponent = Object.assign(() => null, { + preload: () => { + errorComponentStarted.resolve() + return errorComponentGate + }, + }) + + const rootRoute = new BaseRootRoute({ + errorComponent: ErrorComponent as any, + loader: ({ location, abortController }) => { + if (location.pathname !== '/hop/20/child') { + return 'root data' + } + finalRootSignal = abortController.signal + finalRootSignal.addEventListener( + 'abort', + () => finalRootAborted.resolve(), + { once: true }, + ) + return finalRootData + }, + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: ({ params }) => { + throw redirect({ + to: '/hop/$hop/child', + params: { hop: String(Number(params.hop) + 1) }, + } as any) + }, + }) + const cleanupRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/cleanup', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + cleanupRoute, + ]), + history: createMemoryHistory({ + initialEntries: ['/hop/0/child'], + }), + }) + const loading = router.load() + + try { + await errorComponentStarted + expect(finalRootData.status).toBe('pending') + errorComponentGate.resolve() + await finalRootAborted + await expect(loading).resolves.toBeUndefined() + + expect(router.state.location.pathname).toBe('/hop/20/child') + const firstTerminalMatch = router.state.matches.find( + (match) => match.status !== 'success', + ) + expect(firstTerminalMatch).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + isFetching: false, + error: expect.objectContaining({ message: 'Too many redirects' }), + }) + expect(finalRootData.status).toBe('pending') + expect(finalRootSignal?.aborted).toBe(true) + expect( + router.state.matches.every((match) => match.isFetching === false), + ).toBe(true) + expect(router.state.status).toBe('idle') + expect(router.state.isLoading).toBe(false) + } finally { + finalRootData.resolve('late root data') + errorComponentGate.resolve() + await Promise.allSettled([loading]) + } + + await router.navigate({ to: '/cleanup' }) + expect(router.state.location.pathname).toBe('/cleanup') + expect( + router.state.matches.every((match) => match.status === 'success'), + ).toBe(true) + expect(router.state.status).toBe('idle') + }) + + test('a redirect is not blocked by an abort-ignoring sibling loader', async () => { + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + loader: () => new Promise(() => {}), + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.navigate({ to: '/source/child' }) + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + }) +}) diff --git a/packages/router-core/tests/public-hydration-contract.test.ts b/packages/router-core/tests/public-hydration-contract.test.ts new file mode 100644 index 0000000000..836931ee91 --- /dev/null +++ b/packages/router-core/tests/public-hydration-contract.test.ts @@ -0,0 +1,1628 @@ +import { runInNewContext } from 'node:vm' +import { + afterEach, + beforeEach, + describe, + expect, + onTestFinished, + test, + vi, +} from 'vitest' +import { createBrowserHistory, createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + redirect, +} from '../src' +import { hydrate } from '../src/ssr/client' +import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' +import { dehydrateSsrMatchId } from '../src/ssr/ssr-match-id' +import { createTestRouter } from './routerTestUtils' +import type { AnyRouteMatch, AnyRouter, LocationRewrite } from '../src' +import type { DehydratedRouter, TsrSsrGlobal } from '../src/ssr/types' +import type { ServerManifest } from '../src/manifest' + +const testManifest: ServerManifest = { routes: {} } +const browserWindow = window + +async function dehydrateToBootstrap(router: AnyRouter): Promise { + attachRouterServerSsrUtils({ router, manifest: testManifest }) + try { + await router.load() + await router.serverSsr!.dehydrate() + + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + + const context: Record = { + document: { currentScript: { remove() {} } }, + } + context.self = context + runInNewContext(script!.children!, context) + + return context.$_TSR + } finally { + router.serverSsr?.cleanup() + } +} + +function installHydrationPayload( + mockWindow: { $_TSR?: TsrSsrGlobal }, + matches: DehydratedRouter['matches'], +) { + mockWindow.$_TSR = { + router: { + manifest: testManifest, + dehydratedData: {}, + matches, + }, + h() {}, + e() {}, + c() {}, + p() {}, + buffer: [], + } +} + +describe('public hydration contracts', () => { + let mockWindow: { $_TSR?: TsrSsrGlobal } + + beforeEach(() => { + mockWindow = {} + vi.stubGlobal('window', mockWindow) + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + test('starts head and scripts together and swallows a head failure', async () => { + const error = new Error('head failed') + const headGate = createControlledPromise() + const head = vi.fn(async () => { + await headGate + throw error + }) + const scripts = vi.fn(() => [ + { children: 'window.hydrationScriptRan = true' }, + ]) + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined) + + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + head, + scripts, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + const hydration = hydrate(router) + await vi.waitFor(() => { + expect(head).toHaveBeenCalledTimes(1) + expect(scripts).toHaveBeenCalledTimes(1) + }) + + headGate.resolve() + await expect(hydration).resolves.toBeUndefined() + + expect(consoleError).toHaveBeenCalledWith(error) + expect(router.state.resolvedLocation?.pathname).toBe('/page') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + }) + }) + + test('adopts a shorter selective-SSR prefix ending at a pending client-only match', async () => { + const serverLoader = vi.fn(() => 'server data') + const serverRootRoute = new BaseRootRoute({}) + const serverClientOnlyRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/client-only', + ssr: false, + loader: serverLoader, + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverClientOnlyRoute, + path: '/child', + ssr: true, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverClientOnlyRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ + initialEntries: ['/client-only/child'], + }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + expect(bootstrap.router?.matches).toHaveLength(2) + expect(bootstrap.router?.matches.at(-1)).toMatchObject({ + s: 'pending', + ssr: false, + }) + expect(serverLoader).not.toHaveBeenCalled() + + const routeContext = vi.fn(() => ({ routeContext: true })) + const beforeLoad = vi.fn(() => ({ clientContext: true })) + const loader = vi.fn( + ({ context }) => context.clientContext && context.routeContext, + ) + const rootRoute = new BaseRootRoute({}) + const clientOnlyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/client-only', + ssr: false, + context: routeContext, + beforeLoad, + loader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => clientOnlyRoute, + path: '/child', + ssr: true, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + clientOnlyRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ + initialEntries: ['/client-only/child'], + }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + clientOnlyRoute.id, + childRoute.id, + ]) + expect(router.state.matches.slice(1)).toMatchObject([ + { routeId: clientOnlyRoute.id, status: 'pending', ssr: false }, + { routeId: childRoute.id, ssr: true }, + ]) + expect(router.state.resolvedLocation).toBeUndefined() + expect(router.state.matches[1]?.context).toEqual({ routeContext: true }) + expect(routeContext).toHaveBeenCalledTimes(1) + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + + await router.load() + + expect(beforeLoad).toHaveBeenCalledTimes(1) + expect(loader).toHaveBeenCalledTimes(1) + expect(routeContext).toHaveBeenCalledTimes(1) + expect(router.state.matches[1]).toMatchObject({ + routeId: clientOnlyRoute.id, + status: 'success', + loaderData: true, + }) + expect(router.state.resolvedLocation?.href).toBe('/client-only/child') + }) + + test('selective SSR exposes the complete accepted lane to route context and head hooks', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverClientOnlyRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/client-only', + ssr: false, + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverClientOnlyRoute, + path: '/child', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverClientOnlyRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/client-only/child'] }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + + const contextLanes: Array> = [] + const headLanes: Array> = [] + const rootRoute = new BaseRootRoute({ + context: ({ matches }) => { + contextLanes.push(matches.map((match) => match.routeId)) + return { hydrated: true } + }, + head: ({ matches }) => { + headLanes.push(matches.map((match) => match.routeId)) + return { + meta: [ + { + title: String(matches[1]?.loaderData), + }, + ], + } + }, + }) + const clientOnlyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/client-only', + ssr: false, + loader: () => 'client data', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => clientOnlyRoute, + path: '/child', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + clientOnlyRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/client-only/child'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + const fullLane = [rootRoute.id, clientOnlyRoute.id, childRoute.id] + expect(contextLanes).toEqual([fullLane]) + expect(headLanes).toEqual([fullLane]) + expect(router.state.matches.map((match) => match.routeId)).toEqual(fullLane) + expect(router.state.matches[0]?.context).toMatchObject({ hydrated: true }) + expect(router.state.matches[0]?.meta).toEqual([{ title: 'undefined' }]) + + await router.load() + + expect(router.state.matches[0]?.meta).toEqual([{ title: 'client data' }]) + }) + + test('shell hydration exposes the complete accepted lane to route context and head hooks', async () => { + const contextLanes: Array> = [] + const headLanes: Array> = [] + const rootRoute = new BaseRootRoute({ + context: ({ matches }) => { + contextLanes.push(matches.map((match) => match.routeId)) + return { hydrated: true } + }, + head: ({ matches }) => { + headLanes.push(matches.map((match) => match.routeId)) + return { meta: [{ title: 'Shell hydration' }] } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + ssr: false, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + + const fullLane = [rootRoute.id, childRoute.id] + expect(contextLanes).toEqual([fullLane]) + expect(headLanes).toEqual([fullLane]) + expect(router.state.matches.map((match) => match.routeId)).toEqual(fullLane) + expect(router.state.matches[0]?.context).toMatchObject({ hydrated: true }) + expect(router.state.matches[0]?.meta).toEqual([ + { title: 'Shell hydration' }, + ]) + }) + + test('terminal hydration exposes the full structural lane to route context and head hooks', async () => { + const contextLanes: Array> = [] + const headLanes: Array> = [] + const observeContext = ({ matches }: { matches: Array }) => { + contextLanes.push(matches.map((match) => match.routeId)) + return {} + } + const observeHead = ({ matches }: { matches: Array }) => { + headLanes.push(matches.map((match) => match.routeId)) + return { meta: [{ title: 'Terminal hydration' }] } + } + const rootRoute = new BaseRootRoute({ + context: observeContext, + head: observeHead, + }) + const boundaryRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/app', + context: observeContext, + head: observeHead, + errorComponent: () => 'App error', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => boundaryRoute, + path: '/child', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + boundaryRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/app/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'error', + e: new Error('App failed on the server'), + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + + const fullLane = [rootRoute.id, boundaryRoute.id, childRoute.id] + expect(contextLanes).toEqual([fullLane, fullLane]) + expect(headLanes).toEqual([fullLane, fullLane]) + expect(router.state.matches.map((match) => match.routeId)).toEqual(fullLane) + expect(router.state.matches[1]).toMatchObject({ + routeId: boundaryRoute.id, + status: 'error', + }) + expect(router.state.matches[2]?.status).toBe('success') + }) + + test('does not cache missing loader data when retrying a hydrated terminal boundary', async () => { + const serverError = new Error('Server boundary failed') + const chunkError = new Error('Boundary chunk failed during hydration') + const errorComponentPreload = vi + .fn<() => Promise>() + .mockRejectedValueOnce(chunkError) + .mockResolvedValue(undefined) + const ErrorComponent = Object.assign(() => 'App error', { + preload: errorComponentPreload, + }) + const beforeLoad = vi.fn() + const loader = vi.fn(() => 'client data') + const rootRoute = new BaseRootRoute({}) + const boundaryRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/app', + beforeLoad, + loader, + errorComponent: ErrorComponent, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => boundaryRoute, + path: '/child', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + boundaryRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/app/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'error', + e: serverError, + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + + expect(errorComponentPreload).toHaveBeenCalledTimes(1) + expect(beforeLoad).not.toHaveBeenCalled() + expect(router.state.resolvedLocation).toBeUndefined() + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + boundaryRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + status: 'error', + error: serverError, + }) + + await router.load() + + expect(beforeLoad).toHaveBeenCalledTimes(1) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.resolvedLocation?.href).toBe('/app/child') + expect(router.state.matches[1]).toMatchObject({ + status: 'success', + error: undefined, + loaderData: 'client data', + }) + }) + + test('retries a terminal lane after ancestor context reconstruction fails', async () => { + const serverError = new Error('Server boundary failed') + const clientError = new Error('Client boundary failed') + const contextError = new Error('Root context failed during hydration') + const context = vi + .fn<() => { clientContext: true }>() + .mockImplementationOnce(() => { + throw contextError + }) + .mockReturnValue({ clientContext: true }) + const beforeLoad = vi.fn(() => { + throw clientError + }) + const rootRoute = new BaseRootRoute({ context }) + const boundaryRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/app', + beforeLoad, + errorComponent: () => 'App error', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => boundaryRoute, + path: '/child', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + boundaryRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/app/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'error', + e: serverError, + ssr: true, + u: Date.now(), + }, + ]) + + await hydrate(router) + + expect(context).toHaveBeenCalledTimes(1) + expect(beforeLoad).not.toHaveBeenCalled() + expect(router.state.resolvedLocation).toBeUndefined() + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + boundaryRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]).toMatchObject({ + status: 'error', + error: serverError, + }) + + await router.load() + + expect(context).toHaveBeenCalledTimes(2) + expect(beforeLoad).toHaveBeenCalledTimes(1) + expect(router.state.resolvedLocation?.href).toBe('/app/child') + expect(router.state.matches[0]?.context).toMatchObject({ + clientContext: true, + }) + expect(router.state.matches[1]).toMatchObject({ + status: 'error', + error: clientError, + }) + }) + + test('does not repeat data-only assets during client continuation', async () => { + const serverRootRoute = new BaseRootRoute({}) + const serverPageRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/page', + ssr: 'data-only', + loader: () => 'server data', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverPageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + + const head = vi.fn(({ loaderData }) => ({ + meta: [{ title: String(loaderData) }], + })) + const scripts = vi.fn(({ loaderData }) => [ + { children: `window.pageData = ${JSON.stringify(loaderData)}` }, + ]) + const loader = vi.fn(() => 'client data') + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: 'data-only', + loader, + head, + scripts, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'pending', + loaderData: 'server data', + meta: [{ title: 'server data' }], + scripts: [{ children: 'window.pageData = "server data"' }], + }) + expect(head).toHaveBeenCalledTimes(1) + expect(scripts).toHaveBeenCalledTimes(1) + expect(loader).not.toHaveBeenCalled() + + await router.load() + + expect(router.state.resolvedLocation?.pathname).toBe('/page') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + loaderData: 'server data', + meta: [{ title: 'server data' }], + scripts: [{ children: 'window.pageData = "server data"' }], + }) + expect(head).toHaveBeenCalledTimes(1) + expect(scripts).toHaveBeenCalledTimes(1) + expect(loader).not.toHaveBeenCalled() + }) + + test('adopts the complete nested data-only server prefix', async () => { + const serverParentBeforeLoad = vi.fn(() => ({ source: 'server' })) + const serverParentLoader = vi.fn(() => 'server parent') + const serverChildBeforeLoad = vi.fn() + const serverChildLoader = vi.fn(() => 'server child') + const serverRootRoute = new BaseRootRoute({}) + const serverParentRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/parent', + ssr: 'data-only', + beforeLoad: serverParentBeforeLoad, + loader: serverParentLoader, + }) + const serverChildRoute = new BaseRoute({ + getParentRoute: () => serverParentRoute, + path: '/child', + beforeLoad: serverChildBeforeLoad, + loader: serverChildLoader, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([ + serverParentRoute.addChildren([serverChildRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + + expect(serverParentBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverChildBeforeLoad).toHaveBeenCalledTimes(1) + expect(serverParentLoader).toHaveBeenCalledTimes(1) + expect(serverChildLoader).toHaveBeenCalledTimes(1) + + const parentBeforeLoad = vi.fn(() => ({ source: 'client' })) + const parentLoader = vi.fn(() => 'client parent') + const childBeforeLoad = vi.fn() + const childLoader = vi.fn(() => 'client child') + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr: 'data-only', + beforeLoad: parentBeforeLoad, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + beforeLoad: childBeforeLoad, + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'pending', + loaderData: 'server parent', + }) + expect(router.state.matches[2]).toMatchObject({ + routeId: childRoute.id, + status: 'success', + loaderData: 'server child', + }) + expect(parentBeforeLoad).not.toHaveBeenCalled() + expect(childBeforeLoad).not.toHaveBeenCalled() + expect(parentLoader).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + + await router.load() + + expect(router.state.resolvedLocation?.pathname).toBe('/parent/child') + expect(router.state.matches[1]).toMatchObject({ + status: 'success', + context: { source: 'server' }, + loaderData: 'server parent', + }) + expect(router.state.matches[2]).toMatchObject({ + status: 'success', + loaderData: 'server child', + }) + expect(parentBeforeLoad).not.toHaveBeenCalled() + expect(childBeforeLoad).not.toHaveBeenCalled() + expect(parentLoader).not.toHaveBeenCalled() + expect(childLoader).not.toHaveBeenCalled() + }) + + test('keeps an ssr-false route-context error authoritative', async () => { + const serverError = new Error('server route context failed') + const serverRootRoute = new BaseRootRoute({}) + const serverPageRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/page', + ssr: false, + context: () => { + throw serverError + }, + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverPageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + const transportedError = bootstrap.router?.matches.at(-1)?.e + + expect(transportedError).toMatchObject({ + name: serverError.name, + message: serverError.message, + }) + + const context = vi.fn(() => ({ source: 'client' })) + const beforeLoad = vi.fn() + const loader = vi.fn() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + context, + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await hydrate(router) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'pending', + error: transportedError, + }) + expect(context).toHaveBeenCalledTimes(1) + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + + await router.load() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'error', + error: transportedError, + }) + expect(context).toHaveBeenCalledTimes(1) + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + }) + + test('aborts the hydrated route context generation when the route is later left', async () => { + const capturedSignals: Array = [] + const rootRoute = new BaseRootRoute({}) + const oldRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/old', + context: ({ abortController }) => { + capturedSignals.push(abortController.signal) + return { hydrated: true } + }, + }) + const newRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/new', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([oldRoute, newRoute]), + history: createMemoryHistory({ initialEntries: ['/old'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + await hydrate(router) + const hydratedSignal = capturedSignals.at(-1)! + await router.load() + const reloadedSignal = router.state.matches.find( + (match) => match.routeId === oldRoute.id, + )!.abortController.signal + expect(hydratedSignal.aborted).toBe(true) + expect(reloadedSignal).not.toBe(hydratedSignal) + expect(reloadedSignal.aborted).toBe(false) + + await router.navigate({ to: '/new' }) + + expect(reloadedSignal.aborted).toBe(true) + expect(router.state.resolvedLocation?.pathname).toBe('/new') + }) + + test('keeps hydrated route context alive when the initial load is synchronously superseded', async () => { + let capturedSignal: AbortSignal | undefined + const beforeLoadGate = createControlledPromise() + const rootRoute = new BaseRootRoute({ + context: ({ abortController }) => { + capturedSignal = abortController.signal + return { hydrated: true } + }, + }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + beforeLoad: async () => { + await beforeLoadGate + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match, index) => ({ + i: dehydrateSsrMatchId(match.id), + s: index ? ('pending' as const) : ('success' as const), + ssr: index ? false : true, + u: Date.now(), + })), + ) + + await hydrate(router) + expect(router.state.resolvedLocation).toBeUndefined() + let winningLoad: Promise | undefined + const unsubscribe = router.subscribe('onBeforeNavigate', () => { + unsubscribe() + winningLoad = router.load() + }) + + const supersededLoad = router.load() + await vi.waitFor(() => { + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'pending', + }) + }) + expect(router.state.matches.at(-1)!.abortController.signal.aborted).toBe( + false, + ) + + beforeLoadGate.resolve() + await supersededLoad + await winningLoad + + expect(capturedSignal?.aborted).toBe(false) + expect(router.state.resolvedLocation?.pathname).toBe('/page') + }) + + test('does not hand transported context to a reentrant navigation for another location', async () => { + const rootBeforeLoad = vi.fn(() => ({ source: 'client' })) + const newLoader = vi.fn( + ({ context }: { context: { source?: string } }) => context.source, + ) + const rootRoute = new BaseRootRoute({ beforeLoad: rootBeforeLoad }) + const oldRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/old', + ssr: false, + }) + const newRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/new', + loader: newLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([oldRoute, newRoute]), + history: createMemoryHistory({ initialEntries: ['/old'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { source: 'server old' }, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + let winningNavigation: Promise | undefined + const unsubscribe = router.subscribe('onBeforeNavigate', () => { + unsubscribe() + winningNavigation = router.navigate({ to: '/new' }) + }) + + await router.load() + await winningNavigation + + expect(router.state.resolvedLocation?.pathname).toBe('/new') + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(newLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.loaderData).toBe('client') + }) + + test('does not hand transported context to a reentrant history-state change', async () => { + const rootBeforeLoad = vi.fn(({ location }: { location: any }) => ({ + auth: location.state.auth, + })) + const pageLoader = vi.fn( + ({ context }: { context: { auth?: string } }) => context.auth, + ) + const rootRoute = new BaseRootRoute({ beforeLoad: rootBeforeLoad }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + loader: pageLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { auth: 'server old' }, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + let winningNavigation: Promise | undefined + const unsubscribe = router.subscribe('onBeforeNavigate', () => { + unsubscribe() + winningNavigation = router.navigate({ + to: '/page', + state: { auth: 'client new' } as any, + }) + }) + + await router.load() + await winningNavigation + + expect(router.state.resolvedLocation?.pathname).toBe('/page') + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(pageLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.loaderData).toBe('client new') + }) + + test('does not hand transported context to a replaced public URL generation', async () => { + vi.spyOn(browserWindow, 'scrollTo').mockImplementation(() => undefined) + browserWindow.history.replaceState({}, '', '/public-a') + const history = createBrowserHistory({ window: browserWindow }) + const rewrite: LocationRewrite = { + input: ({ url }) => { + if (url.pathname === '/public-a' || url.pathname === '/public-b') { + url.pathname = '/internal' + } + return url + }, + output: ({ url }) => url, + } + const beforeLoad = vi.fn(({ location }: { location: any }) => ({ + publicHref: location.publicHref, + })) + const loader = vi.fn( + ({ context: routeContext }: { context: { publicHref: string } }) => + routeContext.publicHref, + ) + const rootRoute = new BaseRootRoute({ beforeLoad }) + const internalRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/internal', + ssr: false, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([internalRoute]), + history, + rewrite, + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { publicHref: '/public-a' }, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + onTestFinished(() => { + history.destroy() + browserWindow.history.replaceState({}, '', '/') + }) + + await hydrate(router) + browserWindow.history.replaceState( + browserWindow.history.state, + '', + '/public-b', + ) + + await router.load() + + expect( + beforeLoad.mock.calls.map(([options]) => options.location.publicHref), + ).toEqual(['/public-b']) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.loaderData).toBe('/public-b') + }) + + test('keeps a hydration handoff when a provider initializes an empty context', async () => { + const rootBeforeLoad = vi.fn(() => ({ source: 'client' })) + const rootLoader = vi.fn(() => 'client root') + const pageLoader = vi.fn( + ({ context }: { context: { source?: string } }) => context.source, + ) + const rootRoute = new BaseRootRoute({ + beforeLoad: rootBeforeLoad, + loader: rootLoader, + }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + loader: pageLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { source: 'server' }, + l: 'server root', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + router.update({ + ...router.options, + context: { ...router.options.context }, + }) + await router.load() + + expect(rootBeforeLoad).not.toHaveBeenCalled() + expect(rootLoader).not.toHaveBeenCalled() + expect(pageLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches[0]).toMatchObject({ + context: { source: 'server' }, + loaderData: 'server root', + }) + expect(router.state.matches[1]).toMatchObject({ loaderData: 'server' }) + }) + + test('keeps transported hydration context across a cyclic router-context replacement', async () => { + const rootBeforeLoad = vi.fn(({ context }: { context: any }) => ({ + value: context.value, + })) + const rootLoader = vi.fn(() => 'client root') + const pageContext = vi.fn(({ context }: { context: any }) => ({ + value: context.value, + })) + const pageLoader = vi.fn(({ context }: { context: any }) => context.value) + const rootRoute = new BaseRootRoute({ + beforeLoad: rootBeforeLoad, + loader: rootLoader, + }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + context: pageContext, + loader: pageLoader, + }) + const initialValue: Record = {} + initialValue.self = initialValue + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + context: { value: initialValue }, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { value: initialValue }, + l: 'server root', + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + const nextValue: Record = {} + nextValue.self = nextValue + router.update({ + ...router.options, + context: { value: nextValue }, + }) + + await expect(router.load()).resolves.toBeUndefined() + + expect(rootBeforeLoad).not.toHaveBeenCalled() + expect(pageContext).toHaveBeenCalledTimes(1) + expect(rootLoader).not.toHaveBeenCalled() + expect(pageLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches[0]?.loaderData).toBe('server root') + expect(router.state.matches[1]?.loaderData).toBe(initialValue) + }) + + test('does not hand transported context across an invalidation', async () => { + const rootBeforeLoad = vi.fn(({ context }: { context: any }) => ({ + auth: context.auth, + })) + const pageLoader = vi.fn( + ({ context }: { context: { auth?: string } }) => context.auth, + ) + const rootRoute = new BaseRootRoute({ beforeLoad: rootBeforeLoad }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + loader: pageLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + context: { auth: 'server old' }, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { auth: 'server old' }, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + router.update({ + ...router.options, + context: { auth: 'client new' }, + }) + await router.invalidate() + + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(pageLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.loaderData).toBe('client new') + }) + + test('rejects a hydration handoff when its prefix identity changes before finish', async () => { + let generation = 'server' + const rootBeforeLoad = vi.fn(() => ({ source: 'client' })) + const pageLoader = vi.fn( + ({ context }: { context: { source?: string } }) => context.source, + ) + const rootRoute = new BaseRootRoute({ + loaderDeps: () => ({ generation }), + beforeLoad: rootBeforeLoad, + }) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + ssr: false, + loader: pageLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload(mockWindow, [ + { + i: dehydrateSsrMatchId(matches[0]!.id), + s: 'success', + b: { source: 'server' }, + ssr: true, + u: Date.now(), + }, + { + i: dehydrateSsrMatchId(matches[1]!.id), + s: 'pending', + ssr: false, + u: Date.now(), + }, + ]) + + await hydrate(router) + const unsubscribe = router.subscribe('onBeforeNavigate', () => { + unsubscribe() + generation = 'client' + }) + + await router.load() + + expect(rootBeforeLoad).toHaveBeenCalledTimes(1) + expect(pageLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.loaderData).toBe('client') + }) + + test('turns a client route-context reconstruction failure into a route error', async () => { + const contextError = new Error('client context failed') + const onError = vi.fn() + const loader = vi.fn(() => 'client data') + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + context: () => { + throw contextError + }, + loader, + onError, + errorComponent: () => 'Page error', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + l: match.routeId === pageRoute.id ? 'server data' : undefined, + ssr: true, + u: Date.now(), + })), + ) + + await expect(hydrate(router)).resolves.toBeUndefined() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + error: undefined, + loaderData: 'server data', + }) + expect(router.state.resolvedLocation).toBeUndefined() + expect(onError).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + + await router.load() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'error', + error: contextError, + loaderData: 'server data', + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith(contextError) + expect(loader).not.toHaveBeenCalled() + }) + + test('preserves route-context redirect control during hydration reconstruction', async () => { + const rootRoute = new BaseRootRoute({}) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + context: () => { + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/source'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + await expect(hydrate(router)).resolves.toBeUndefined() + + expect(router.state.location.pathname).toBe('/source') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: sourceRoute.id, + status: 'success', + }) + expect(router.state.resolvedLocation).toBeUndefined() + + await router.load() + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.resolvedLocation?.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + }) + + test('keeps a transported terminal route-context error authoritative', async () => { + const serverError = new Error('server context failed') + const serverRootRoute = new BaseRootRoute({}) + const serverPageRoute = new BaseRoute({ + getParentRoute: () => serverRootRoute, + path: '/page', + context: () => { + throw serverError + }, + errorComponent: () => 'Page error', + }) + const serverRouter = createTestRouter({ + routeTree: serverRootRoute.addChildren([serverPageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: true, + }) + const bootstrap = await dehydrateToBootstrap(serverRouter) + const transportedError = bootstrap.router?.matches.at(-1)?.e + + const clientContextError = new Error('client context failed') + const beforeLoad = vi.fn() + const loader = vi.fn( + ({ context }: { context: { recovered?: boolean } }) => context.recovered, + ) + const onError = vi.fn() + const clientContext = vi.fn((): { recovered: boolean } => { + throw clientContextError + }) + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + context: clientContext, + beforeLoad, + loader, + onError, + errorComponent: () => 'Page error', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + mockWindow.$_TSR = bootstrap + + await expect(hydrate(router)).resolves.toBeUndefined() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'error', + error: transportedError, + }) + expect(router.state.matches.at(-1)?.error).not.toBe(clientContextError) + expect(router.state.resolvedLocation?.pathname).toBe('/page') + expect(onError).not.toHaveBeenCalled() + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + + clientContext.mockImplementation(() => ({ recovered: true })) + await router.invalidate() + + expect(clientContext).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)).toMatchObject({ + status: 'success', + context: { recovered: true }, + loaderData: true, + }) + }) + + test('turns a hydrated normal-component chunk failure into a route error without rerunning loader data', async () => { + const chunkError = new Error('page component failed to load') + const componentPreload = vi.fn(() => Promise.reject(chunkError)) + const Page = Object.assign(() => 'Page', { preload: componentPreload }) + const loader = vi.fn(() => 'client data') + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + errorComponent: () => 'Page error', + loader, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + l: match.routeId === pageRoute.id ? 'server data' : undefined, + ssr: true, + u: Date.now(), + })), + ) + + await expect(hydrate(router)).resolves.toBeUndefined() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + error: undefined, + loaderData: 'server data', + }) + expect(router.state.resolvedLocation).toBeUndefined() + expect(onError).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + + await router.load() + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'error', + error: chunkError, + loaderData: 'server data', + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith(chunkError) + expect(loader).not.toHaveBeenCalled() + }) + + test('retries from the earliest failed hydration chunk regardless of rejection order', async () => { + const rootChunkError = new Error('root component failed to load') + const childChunkError = new Error('child component failed to load') + const rootChunkGate = createControlledPromise() + const rootChunkStarted = createControlledPromise() + const RootComponent = Object.assign(() => 'Root', { + preload: () => { + rootChunkStarted.resolve() + return rootChunkGate + }, + }) + const childComponentPreload = vi.fn(() => Promise.reject(childChunkError)) + const ChildComponent = Object.assign(() => 'Child', { + preload: childComponentPreload, + }) + const rootContext = vi.fn(() => ({})) + const rootRoute = new BaseRootRoute({ + component: RootComponent, + context: rootContext, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + component: ChildComponent, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + const hydration = hydrate(router) + await rootChunkStarted + await vi.waitFor(() => { + expect(childComponentPreload).toHaveBeenCalledTimes(1) + }) + rootChunkGate.reject(rootChunkError) + await expect(hydration).resolves.toBeUndefined() + + expect(rootContext).not.toHaveBeenCalled() + expect(router.state.resolvedLocation).toBeUndefined() + }) + + test('does not wait for descendant chunks after an earlier chunk fails', async () => { + const rootChunkError = new Error('root component failed to load') + const childChunkGate = createControlledPromise() + const childChunkStarted = createControlledPromise() + const RootComponent = Object.assign(() => 'Root', { + preload: () => Promise.reject(rootChunkError), + }) + const ChildComponent = Object.assign(() => 'Child', { + preload: () => { + childChunkStarted.resolve() + return childChunkGate + }, + }) + const rootContext = vi.fn(() => ({})) + const rootRoute = new BaseRootRoute({ + component: RootComponent, + context: rootContext, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + component: ChildComponent, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: false, + }) + const matches = router.matchRoutes(router.stores.location.get()) + installHydrationPayload( + mockWindow, + matches.map((match) => ({ + i: dehydrateSsrMatchId(match.id), + s: 'success' as const, + ssr: true, + u: Date.now(), + })), + ) + + let settled = false + const hydration = hydrate(router).then(() => { + settled = true + }) + try { + await childChunkStarted + await vi.waitFor(() => { + expect(settled).toBe(true) + }) + } finally { + childChunkGate.resolve() + await hydration + } + + expect(rootContext).not.toHaveBeenCalled() + expect(router.state.resolvedLocation).toBeUndefined() + }) +}) diff --git a/packages/router-core/tests/public-preload-lane-contract.test.ts b/packages/router-core/tests/public-preload-lane-contract.test.ts new file mode 100644 index 0000000000..9be86c1b03 --- /dev/null +++ b/packages/router-core/tests/public-preload-lane-contract.test.ts @@ -0,0 +1,1441 @@ +import { afterEach, describe, expect, onTestFinished, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + redirect, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.useRealTimers() +}) + +describe('public preload lane contracts', () => { + test('completed preload matches retain context without caching beforeLoad context', async () => { + let beforeLoadGeneration = 0 + const loader = vi.fn(() => 'loader data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + context: () => ({ routeContext: true }), + beforeLoad: () => ({ generation: ++beforeLoadGeneration }), + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preloaded = await router.preloadRoute({ to: '/target' }) + + expect(preloaded?.at(-1)?.context).toEqual({ + routeContext: true, + generation: 1, + }) + + await router.navigate({ to: '/target' }) + + expect(router.state.matches.at(-1)?.context).toEqual({ + routeContext: true, + generation: 2, + }) + expect(loader).toHaveBeenCalledTimes(1) + }) + + test('every active preload runs beforeLoad even for an identical location', async () => { + const gates = new Map< + number, + ReturnType> + >() + const beforeLoad = vi.fn(({ search }: { search: { version: number } }) => { + let gate = gates.get(search.version) + if (!gate) { + gate = createControlledPromise() + gates.set(search.version, gate) + } + return gate + }) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + version: Number(search.version), + }), + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + stringifySearch: () => '', + }) + + await router.load() + const first = router.preloadRoute({ + to: '/target', + search: { version: 1 }, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + const second = router.preloadRoute({ + to: '/target', + search: { version: 2 }, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + gates.get(1)!.resolve(undefined) + gates.get(2)!.resolve(undefined) + await Promise.all([first, second]) + + const third = router.preloadRoute({ + to: '/target', + search: { version: 3 }, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(3)) + const identical = router.preloadRoute({ + to: '/target', + search: { version: 3 }, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(4)) + gates.get(3)!.resolve(undefined) + await Promise.all([third, identical]) + }) + + test('navigation reruns beforeLoad while sharing an identical preload loader', async () => { + const beforeLoadGate = createControlledPromise() + const beforeLoad = vi.fn(async ({ preload }: { preload: boolean }) => { + await beforeLoadGate + return { source: preload ? 'preload' : 'navigation' } + }) + const loader = vi.fn(() => 'loader data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + beforeLoadGate.resolve() + await Promise.all([preload, navigation]) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + context: { source: 'navigation' }, + loaderData: 'loader data', + }) + }) + + test('a depth-20 navigation does not adopt a depth-0 active preload redirect', async () => { + const redirectGate = createControlledPromise() + const sharedBeforeLoad = vi.fn(async (_context: { preload: boolean }) => { + await redirectGate + throw redirect({ to: '/target' }) + }) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const hopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + beforeLoad: ({ params }) => { + const hop = Number(params.hop) + if (hop < 19) { + throw redirect({ + to: '/hop/$hop', + params: { hop: String(hop + 1) }, + } as any) + } + throw redirect({ to: '/shared' }) + }, + }) + const sharedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/shared', + beforeLoad: sharedBeforeLoad, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + hopRoute, + sharedRoute, + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + let preload: Promise | undefined + let navigation: Promise | undefined + onTestFinished(async () => { + redirectGate.resolve() + const activeWork: Array> = [] + if (preload) { + activeWork.push(preload) + } + if (navigation) { + activeWork.push(navigation) + } + await Promise.allSettled(activeWork) + }) + + await router.load() + preload = router.preloadRoute({ to: '/shared' }) + await vi.waitFor(() => expect(sharedBeforeLoad).toHaveBeenCalledOnce()) + + navigation = router.navigate({ + to: '/hop/$hop', + params: { hop: '0' }, + } as any) + await vi.waitFor(() => expect(sharedBeforeLoad).toHaveBeenCalledTimes(2)) + + redirectGate.resolve() + await Promise.all([preload, navigation]) + + expect( + sharedBeforeLoad.mock.calls.map(([context]) => context.preload), + ).toEqual([true, false]) + expect(router.state.location.pathname).toBe('/shared') + expect( + router.state.matches.find((match) => match.status !== 'success'), + ).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: expect.objectContaining({ message: 'Too many redirects' }), + }) + expect(router.state.status).toBe('idle') + }) + + test.each([ + { + difference: 'pathname', + preloadMask: { to: '/mask-a' }, + navigationMask: { to: '/mask-b' }, + preloadPathname: '/mask-a', + navigationPathname: '/mask-b', + }, + { + difference: 'unmaskOnReload', + preloadMask: { to: '/mask-a' }, + navigationMask: { to: '/mask-a', unmaskOnReload: true }, + preloadPathname: '/mask-a', + navigationPathname: '/mask-a', + }, + { + difference: 'search', + preloadMask: { to: '/mask-a', search: { source: 'preload' } }, + navigationMask: { to: '/mask-a', search: { source: 'navigation' } }, + preloadPathname: '/mask-a', + navigationPathname: '/mask-a', + }, + { + difference: 'state', + preloadMask: { to: '/mask-a', state: { source: 'preload' } }, + navigationMask: { to: '/mask-a', state: { source: 'navigation' } }, + preloadPathname: '/mask-a', + navigationPathname: '/mask-a', + }, + ])( + 'navigation reruns beforeLoad for a different explicit mask $difference on the same destination', + async ({ + preloadMask, + navigationMask, + preloadPathname, + navigationPathname, + }) => { + const beforeLoadGate = createControlledPromise() + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn( + async (context: { + location: { maskedLocation?: { pathname: string } } + preload: boolean + }) => { + if (context.preload) { + await beforeLoadGate + } + return { source: context.preload ? 'preload' : 'navigation' } + }, + ) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const maskARoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/mask-a', + }) + const maskBRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/mask-b', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + targetRoute, + maskARoute, + maskBRoute, + ]), + ...('search' in preloadMask ? { stringifySearch: () => '' } : {}), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + let preload: Promise | undefined + let navigation: Promise | undefined + try { + await router.load() + preload = router.preloadRoute({ + to: '/target', + mask: preloadMask as any, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + + beforeLoadGate.resolve() + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + navigation = router.navigate({ + to: '/target', + mask: navigationMask as any, + }) + await vi.waitFor(() => + expect( + beforeLoad.mock.calls.map(([callContext]) => ({ + preload: callContext.preload, + pathname: callContext.location.maskedLocation?.pathname, + })), + ).toEqual([ + { preload: true, pathname: preloadPathname }, + { preload: false, pathname: navigationPathname }, + ]), + ) + + loaderGate.resolve('shared loader data') + await Promise.all([preload, navigation]) + + expect(router.state.matches.at(-1)?.context).toEqual({ + source: 'navigation', + }) + expect(router.history.location.pathname).toBe(navigationPathname) + expect(loader).toHaveBeenCalledTimes(1) + } finally { + beforeLoadGate.resolve() + loaderGate.resolve('shared loader data') + const activeWork: Array> = [] + if (preload) { + activeWork.push(preload) + } + if (navigation) { + activeWork.push(navigation) + } + await Promise.allSettled(activeWork) + } + }, + ) + + test('identical active preloads await the same redirect chain', async () => { + const redirectGate = createControlledPromise() + const loaderGate = createControlledPromise() + const redirectedLoader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + beforeLoad: async () => { + await redirectGate + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader: redirectedLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const first = router.preloadRoute({ to: '/source' }) + const second = router.preloadRoute({ to: '/source' }) + let secondSettled = false + void second.then(() => { + secondSettled = true + }) + + redirectGate.resolve() + await vi.waitFor(() => expect(redirectedLoader).toHaveBeenCalledOnce()) + expect(secondSettled).toBe(false) + + loaderGate.resolve() + await Promise.all([first, second]) + expect(secondSettled).toBe(true) + }) + + test.each([ + { + name: 'route suffix', + preload: { to: '/items/one/first' }, + navigation: { to: '/items/one/second' }, + pathname: '/items/one/second', + search: {}, + }, + { + name: 'params', + preload: { to: '/items/one/first' }, + navigation: { to: '/items/two/first' }, + pathname: '/items/two/first', + search: {}, + }, + { + name: 'search', + preload: { + to: '/items/one/first', + search: { view: 'summary' }, + }, + navigation: { + to: '/items/one/first', + search: { view: 'detail' }, + }, + pathname: '/items/one/first', + search: { view: 'detail' }, + }, + ])( + 'navigation does not adopt beforeLoad from an active preload with different $name', + async ({ + preload: preloadOptions, + navigation: navigationOptions, + pathname, + search, + }) => { + const beforeLoadGate = createControlledPromise() + const beforeLoad = vi.fn(async ({ preload }: { preload: boolean }) => { + await beforeLoadGate + return { preload } + }) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const itemsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/items/$itemId', + beforeLoad, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => itemsRoute, + path: '/first', + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => itemsRoute, + path: '/second', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + itemsRoute.addChildren([firstRoute, secondRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute(preloadOptions as any) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate(navigationOptions as any) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + beforeLoadGate.resolve() + await Promise.all([preload, navigation]) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual( + [true, false], + ) + expect(router.state.location.pathname).toBe(pathname) + expect(router.state.location.search).toEqual(search) + expect(router.state.matches.at(-1)?.context).toEqual({ preload: false }) + }, + ) + + test('a different full navigation lane reruns beforeLoad but reuses active same-id loader work', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + source: preload ? 'preload' : 'navigation', + })) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + beforeLoad, + loader, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/first', + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/second', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([firstRoute, secondRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/parent/first' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate({ to: '/parent/second' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + expect(loader).toHaveBeenCalledTimes(1) + loaderGate.resolve('shared parent data') + await Promise.all([preload, navigation]) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.routeId).toBe(secondRoute.id) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + context: { source: 'navigation' }, + loaderData: 'shared parent data', + }) + }) + + test('reserved preload loader work survives preload completion while navigation beforeLoad is pending', async () => { + const loaderGate = createControlledPromise() + const navigationBeforeLoadGate = createControlledPromise() + const beforeLoad = vi.fn(async ({ preload }: { preload: boolean }) => { + if (!preload) { + await navigationBeforeLoadGate + } + return { source: preload ? 'preload' : 'navigation' } + }) + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + beforeLoad, + loader, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/first', + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/second', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([firstRoute, secondRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/parent/first' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + + const navigation = router.navigate({ to: '/parent/second' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + loaderGate.resolve('reserved parent data') + await preload + expect(loader).toHaveBeenCalledTimes(1) + + navigationBeforeLoadGate.resolve() + await navigation + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)?.routeId).toBe(secondRoute.id) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + context: { source: 'navigation' }, + loaderData: 'reserved parent data', + }) + }) + + test('a background refresh consumes reserved preload work after that preload completes', async () => { + const refreshGate = createControlledPromise() + const navigationBeforeLoadGate = createControlledPromise() + const beforeLoad = vi.fn( + async ({ + preload, + search, + }: { + preload: boolean + search: { revision: number } + }) => { + if (!preload && search.revision === 2) { + await navigationBeforeLoadGate + } + return { source: preload ? 'preload' : 'navigation' } + }, + ) + const loader = vi.fn(() => + loader.mock.calls.length === 1 ? 'initial data' : refreshGate, + ) + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 0), + }), + beforeLoad, + shouldReload: ({ location }) => + (location.search as { revision: number }).revision > 0, + loader: { + handler: loader, + staleReloadMode: 'background', + }, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/first', + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/second', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([firstRoute, secondRoute]), + ]), + history: createMemoryHistory({ + initialEntries: ['/parent/first?revision=0'], + }), + }) + + await router.load() + const preload = router.preloadRoute({ + to: '/parent/first', + search: { revision: 1 }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + + let navigationSettled = false + const navigation = router + .navigate({ + to: '/parent/second', + search: { revision: 2 }, + }) + .then(() => { + navigationSettled = true + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(3)) + + refreshGate.resolve('refreshed data') + await preload + expect(loader).toHaveBeenCalledTimes(2) + expect(navigationSettled).toBe(false) + + navigationBeforeLoadGate.resolve() + await navigation + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + false, + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.routeId).toBe(secondRoute.id) + expect( + router.state.matches.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + context: { source: 'navigation' }, + loaderData: 'refreshed data', + preload: false, + isFetching: false, + }) + }) + + test('different same-href preload lanes rerun beforeLoad but share loader work by match id', async () => { + const loaderGate = createControlledPromise() + const beforeLoad = vi.fn() + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + version: Number(search.version), + }), + beforeLoad, + loader, + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/first', + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/second', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([firstRoute, secondRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + stringifySearch: () => '', + }) + + await router.load() + const first = router.preloadRoute({ + to: '/parent/first', + search: { version: 1 }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + const second = router.preloadRoute({ + to: '/parent/first', + search: { version: 2 }, + }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + expect(loader).toHaveBeenCalledTimes(1) + loaderGate.resolve('parent data') + await Promise.all([first, second]) + + expect(beforeLoad).toHaveBeenCalledTimes(2) + expect(loader).toHaveBeenCalledTimes(1) + }) + + test('completed preloads cache loader data but not beforeLoad context', async () => { + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + source: preload ? 'preload' : 'navigation', + })) + const loader = vi.fn( + ({ context }: { context: { source: string } }) => context.source, + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + staleTime: Infinity, + preloadStaleTime: Infinity, + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/target' }) + await router.navigate({ to: '/target' }) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + context: { source: 'navigation' }, + loaderData: 'preload', + }) + }) + + test('completed preloads reuse same-id route context and loader data', async () => { + const context = ({ preload }: { preload: boolean }) => ({ + source: preload ? 'preload' : 'navigation', + }) + const loader = vi.fn( + ({ context: value }: { context: { source: string } }) => value.source, + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + staleTime: Infinity, + preloadStaleTime: Infinity, + context, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/target' }) + await router.navigate({ to: '/target' }) + + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + context: { source: 'preload' }, + loaderData: 'preload', + }) + }) + + test('navigation runs independently when an active preload fails', async () => { + const preloadGate = createControlledPromise() + const beforeLoad = vi.fn(async ({ preload }: { preload: boolean }) => { + if (preload) { + await preloadGate + throw new Error('preload failed') + } + return { source: 'navigation' } + }) + const loader = vi.fn(() => 'navigation data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(1)) + const navigation = router.navigate({ to: '/target' }) + + preloadGate.resolve() + await Promise.all([preload, navigation]) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + context: { source: 'navigation' }, + loaderData: 'navigation data', + }) + }) + + test('a later retry reuses successful loader data from the failed preload lane', async () => { + const childGate = createControlledPromise() + const childFailure = new Error('preload child failed') + const beforeLoad = vi.fn(({ preload }: { preload: boolean }) => ({ + source: preload ? 'preload' : 'navigation', + })) + let parentSignal: AbortSignal | undefined + const parentLoader = vi.fn(({ abortController }) => { + parentSignal = abortController.signal + return 'parent data' + }) + const childLoader = vi.fn(async ({ preload }: { preload: boolean }) => { + if (preload) { + await childGate + throw childFailure + } + return 'child data' + }) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + beforeLoad, + loader: parentLoader, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: childLoader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const preload = router.preloadRoute({ to: '/parent/child' }) + await vi.waitFor(() => expect(childLoader).toHaveBeenCalledTimes(1)) + + childGate.resolve() + const terminal = await preload + + expect( + terminal?.find((match) => match.routeId === parentRoute.id), + ).toMatchObject({ + status: 'success', + loaderData: 'parent data', + }) + expect( + terminal?.find((match) => match.routeId === childRoute.id), + ).toMatchObject({ + status: 'error', + error: childFailure, + }) + + await router.navigate({ to: '/parent/child' }) + + expect(beforeLoad.mock.calls.map(([context]) => context.preload)).toEqual([ + true, + false, + ]) + expect(parentLoader).toHaveBeenCalledTimes(1) + expect(parentSignal?.aborted).toBe(false) + expect(childLoader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: childRoute.id, + status: 'success', + loaderData: 'child data', + }) + }) + + test('a terminal preload stays terminal publicly while caching its successful loader generation', async () => { + const loader = vi.fn( + ({ deps }: { deps: { version: number } }) => deps.version, + ) + const rootRoute = new BaseRootRoute({ + validateSearch: (search: Record) => ({ + version: Number(search.version ?? 0), + }), + loaderDeps: ({ search }) => ({ version: search.version }), + shouldReload: false, + loader, + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad: () => { + throw notFound({ routeId: rootRoute.id as never }) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ + initialEntries: ['/?version=0'], + }), + }) + + await router.load() + const terminal = await router.preloadRoute({ + to: '/target', + search: { version: 1 }, + }) + + expect(loader).toHaveBeenCalledTimes(2) + expect(terminal).toHaveLength(2) + expect(terminal?.[0]).toMatchObject({ + routeId: rootRoute.id, + status: 'success', + _notFound: true, + error: { isNotFound: true }, + loaderData: 1, + }) + expect(terminal?.[1]).toMatchObject({ + routeId: targetRoute.id, + status: 'pending', + }) + + await router.navigate({ + to: '/target', + search: { version: 1 }, + }) + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches[0]).toMatchObject({ + status: 'success', + _notFound: true, + loaderData: 1, + }) + expect(router.state.matches[1]).toMatchObject({ + routeId: targetRoute.id, + status: 'pending', + }) + }) + + test('a terminal preload started during navigation borrows its loader generation', async () => { + const loaderGate = createControlledPromise() + const loader = vi.fn(() => + loader.mock.calls.length === 1 ? 'initial data' : loaderGate, + ) + const rootRoute = new BaseRootRoute({ + shouldReload: true, + loader, + }) + const beforeLoad = vi.fn(() => { + throw notFound({ routeId: rootRoute.id as never }) + }) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const navigation = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + const preload = router.preloadRoute({ to: '/target' }) + await vi.waitFor(() => expect(beforeLoad).toHaveBeenCalledTimes(2)) + + expect(loader).toHaveBeenCalledTimes(2) + loaderGate.resolve('navigation data') + const [, terminal] = await Promise.all([navigation, preload]) + + expect(loader).toHaveBeenCalledTimes(2) + expect(terminal?.[0]).toMatchObject({ + routeId: rootRoute.id, + _notFound: true, + loaderData: 'navigation data', + }) + }) + + test('leaving a route preserves its newer same-id preload generation', async () => { + const loader = vi.fn(() => `loader data ${loader.mock.calls.length}`) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 1), + }), + shouldReload: ({ location }) => + (location.search as { revision: number }).revision === 2, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ + initialEntries: ['/target?revision=1'], + }), + }) + + await router.load() + await router.preloadRoute({ + to: '/target', + search: { revision: 2 }, + }) + await router.navigate({ to: '/' }) + await router.navigate({ + to: '/target', + search: { revision: 1 }, + }) + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe('loader data 2') + }) + + test('an expired same-id preload does not displace fresh unloaded data', async () => { + const loader = vi.fn(() => `loader data ${loader.mock.calls.length}`) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 1), + }), + shouldReload: ({ location }) => + (location.search as { revision: number }).revision === 2, + preloadGcTime: 0, + gcTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ + initialEntries: ['/target?revision=1'], + }), + }) + + await router.load() + await router.preloadRoute({ + to: '/target', + search: { revision: 2 }, + }) + await router.navigate({ to: '/' }) + await router.navigate({ + to: '/target', + search: { revision: 1 }, + }) + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)?.loaderData).toBe('loader data 1') + }) + + test('filtered invalidation reloads every generation of the selected match id', async () => { + const loader = vi.fn(() => `loader data ${loader.mock.calls.length}`) + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 1), + }), + shouldReload: ({ location }) => + (location.search as { revision: number }).revision === 2 + ? true + : undefined, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory({ + initialEntries: ['/target?revision=1'], + }), + }) + + await router.load() + await router.preloadRoute({ + to: '/target', + search: { revision: 2 }, + }) + await router.invalidate({ + filter: (match) => (match.search as { revision?: number }).revision === 1, + }) + + expect(loader).toHaveBeenCalledTimes(3) + expect(router.state.matches.at(-1)?.loaderData).toBe('loader data 3') + }) + + test('a hidden terminal suffix does not evict a newer same-id preload', async () => { + const failingBeforeLoadStarted = createControlledPromise() + const failingBeforeLoadGate = createControlledPromise() + const loader = vi.fn(() => 'preloaded child data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + validateSearch: (search: Record) => ({ + fail: search.fail === true, + }), + beforeLoad: async ({ search }) => { + if (search.fail) { + failingBeforeLoadStarted.resolve() + await failingBeforeLoadGate + throw new Error('parent failed') + } + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + staleTime: Infinity, + preloadStaleTime: Infinity, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + const failingNavigation = router.navigate({ + to: '/parent/child', + search: { fail: true }, + }) + await failingBeforeLoadStarted + + await router.preloadRoute({ + to: '/parent/child', + search: { fail: false }, + }) + expect(loader).toHaveBeenCalledTimes(1) + + failingBeforeLoadGate.resolve() + await failingNavigation + expect(router.state.matches).toHaveLength(3) + expect(router.state.matches[1]).toMatchObject({ + routeId: parentRoute.id, + status: 'error', + }) + + await router.navigate({ + to: '/parent/child', + search: { fail: false }, + }) + + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: childRoute.id, + loaderData: 'preloaded child data', + }) + }) + + test('preload false skips speculation but performs a blocking navigation load', async () => { + const loaderGate = createControlledPromise() + const loader = vi.fn(() => loaderGate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + preload: false, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + await router.load() + await router.preloadRoute({ to: '/target' }) + expect(loader).not.toHaveBeenCalled() + + let settled = false + const navigation = router.navigate({ to: '/target' }).then(() => { + settled = true + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + expect(settled).toBe(false) + + loaderGate.resolve('target data') + await navigation + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'target data', + }) + }) + + test('preloads the target of a twenty-hop redirect chain', async () => { + const beforeLoad = vi.fn(({ params }) => { + const hop = Number(params.hop) + if (hop < 20) { + throw redirect({ + to: '/hop/$hop', + params: { hop: String(hop + 1) }, + } as any) + } + }) + const rootRoute = new BaseRootRoute({}) + const hopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([hopRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const matches = await router.preloadRoute({ + to: '/hop/$hop', + params: { hop: '0' }, + } as any) + + expect(beforeLoad).toHaveBeenCalledTimes(21) + expect(matches?.at(-1)).toMatchObject({ + routeId: hopRoute.id, + status: 'success', + params: { hop: '20' }, + }) + }) + + test('stops a preload after the twenty-hop redirect limit', async () => { + const beforeLoad = vi.fn(({ params }) => { + const hop = Number(params.hop) + if (hop < 22) { + throw redirect({ + to: '/hop/$hop', + params: { hop: String(hop + 1) }, + } as any) + } + }) + const rootRoute = new BaseRootRoute({}) + const hopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([hopRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const matches = await router.preloadRoute({ + to: '/hop/$hop', + params: { hop: '0' }, + } as any) + + expect(beforeLoad).toHaveBeenCalledTimes(21) + expect(matches?.find((match) => match.status !== 'success')).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: expect.objectContaining({ + message: 'Too many redirects', + }), + }) + }) + + test('forwards a document redirect at the redirect limit', async () => { + const beforeLoad = vi.fn(({ params }) => { + const hop = Number(params.hop) + throw redirect({ + to: '/hop/$hop', + params: { hop: String(hop + 1) }, + reloadDocument: hop === 20, + } as any) + }) + const rootRoute = new BaseRootRoute({}) + const hopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/hop/$hop', + beforeLoad, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([hopRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const matches = await router.preloadRoute({ + to: '/hop/$hop', + params: { hop: '0' }, + } as any) + + expect(beforeLoad).toHaveBeenCalledTimes(21) + expect(matches).toBeUndefined() + }) + + test('reports a chunk redirect limit at root', async () => { + const chunkError = new Error('route chunk failed') + const rootRoute = new BaseRootRoute({}) + const loopRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/loop', + onError: () => { + throw redirect({ to: '/loop' }) + }, + }).lazy(() => Promise.reject(chunkError)) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([loopRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + const matches = await router.preloadRoute({ to: '/loop' }) + + const terminal = matches?.find((match) => match.status !== 'success') + expect(terminal).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: expect.objectContaining({ message: 'Too many redirects' }), + }) + }) +}) diff --git a/packages/router-core/tests/rendered-matches.test.ts b/packages/router-core/tests/rendered-matches.test.ts new file mode 100644 index 0000000000..2a2ea362ae --- /dev/null +++ b/packages/router-core/tests/rendered-matches.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from 'vitest' +import { _getAssetMatches } from '../src' +import type { AnyRouteMatch } from '../src' + +describe('_getAssetMatches', () => { + test('does not cross an ordinary data-only pending boundary', () => { + const matches = [ + { status: 'success' }, + { status: 'pending', ssr: 'data-only' }, + { status: 'success' }, + ] as Array + + expect(_getAssetMatches(matches)).toEqual(matches.slice(0, 2)) + }) + + test('limits a hydration handoff to its verified asset prefix', () => { + const matches = [ + { status: 'success' }, + { + status: 'pending', + ssr: 'data-only', + _assetEnd: 3, + }, + { status: 'success' }, + { status: 'success' }, + ] as Array + + expect(_getAssetMatches(matches)).toEqual(matches.slice(0, 3)) + }) +}) diff --git a/packages/router-core/tests/routerTestUtils.ts b/packages/router-core/tests/routerTestUtils.ts index b71c54e347..413b810b95 100644 --- a/packages/router-core/tests/routerTestUtils.ts +++ b/packages/router-core/tests/routerTestUtils.ts @@ -5,8 +5,10 @@ import { createNonReactiveMutableStore, createNonReactiveReadonlyStore, } from '../src' +import { createRequestHandler } from '../src/ssr/createRequestHandler' import type { RouterHistory } from '@tanstack/history' import type { + AnyRouter, AnyRoute, GetStoreConfig, RouterConstructorOptions, @@ -46,3 +48,24 @@ export function createTestRouter< ) { return new RouterCore(options, getStoreConfig) } + +/** Materialize the request-local server result as the HTTP response users see. */ +export function loadServerResponse( + router: AnyRouter, + path: string, + signal?: AbortSignal, +) { + return createRequestHandler({ + createRouter: () => router, + request: new Request(`http://localhost${path}`, { signal }), + })(({ router: loadedRouter, responseHeaders }) => { + const result = loadedRouter._serverResult + return new Response(null, { + status: + result?.type === 'redirect' + ? result.redirect.status + : (result?.status ?? 500), + headers: responseHeaders, + }) + }) +} diff --git a/packages/router-core/tests/same-destination-navigation-join.test.ts b/packages/router-core/tests/same-destination-navigation-join.test.ts new file mode 100644 index 0000000000..c6d65fe525 --- /dev/null +++ b/packages/router-core/tests/same-destination-navigation-join.test.ts @@ -0,0 +1,331 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('same-destination navigation while one is in flight', () => { + function setup() { + const gate = createControlledPromise() + const beforeLoad = vi.fn() + const loader = vi.fn(() => gate) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + return { router, beforeLoad, loader, gate, targetRoute } + } + + test('a second navigation to the same destination joins the in-flight load', async () => { + const { router, beforeLoad, loader, gate, targetRoute } = setup() + await router.load() + + const first = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + const second = router.navigate({ to: '/target' }) + + gate.resolve('once') + await Promise.all([first, second]) + + expect(beforeLoad).toHaveBeenCalledTimes(2) + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'once', + }) + }) + + test('a double navigation in the same tick joins the in-flight load', async () => { + const { router, loader, gate, targetRoute } = setup() + await router.load() + + const first = router.navigate({ to: '/target' }) + const second = router.navigate({ to: '/target' }) + + gate.resolve('once') + await Promise.all([first, second]) + + expect(loader).toHaveBeenCalledTimes(1) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'once', + }) + }) + + test('a successor joining a loader error normalizes the shared generation once', async () => { + const gate = createControlledPromise() + const failure = new Error('shared failure') + const loader = vi.fn(async () => { + await gate + throw failure + }) + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + const first = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + const second = router.navigate({ to: '/target' }) + await Promise.resolve() + expect(loader).toHaveBeenCalledOnce() + + gate.resolve() + await Promise.all([first, second]) + + expect(loader).toHaveBeenCalledOnce() + expect(onError).toHaveBeenCalledOnce() + expect(onError).toHaveBeenCalledWith(failure) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'error', + error: failure, + }) + }) + + test('a navigation started by onError runs a fresh loader generation', async () => { + const gate = createControlledPromise() + const failure = new Error('first generation failed') + let successor: Promise | undefined + let router: ReturnType + const loader = vi.fn(async () => { + if (loader.mock.calls.length === 1) { + await gate + throw failure + } + return 'recovered' + }) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + loader, + onError: () => { + successor ??= router.navigate({ to: '/target' }) + }, + }) + router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + const first = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + gate.resolve() + await first + await successor + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'recovered', + }) + }) + + test('a superseded loader failure cannot poison a successor delayed in beforeLoad', async () => { + const firstResult = createControlledPromise() + const successorBeforeLoadStarted = createControlledPromise() + const successorBeforeLoadGate = createControlledPromise() + const failure = new Error('reserved generation failed') + const signals: Array = [] + const onError = vi.fn() + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + signals.push(abortController.signal) + return loader.mock.calls.length === 1 ? firstResult : 'fresh' + }, + ) + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + phase: String(search.phase ?? ''), + }), + beforeLoad: async ({ search }) => { + if (search.phase === 'successor') { + successorBeforeLoadStarted.resolve() + await successorBeforeLoadGate + } + }, + loader, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + await router.load() + + const first = router.navigate({ + to: '/target', + search: { phase: 'predecessor' }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledOnce()) + + const second = router.navigate({ + to: '/target', + search: { phase: 'successor' }, + }) + await successorBeforeLoadStarted + + expect(loader).toHaveBeenCalledOnce() + expect(signals[0]?.aborted).toBe(false) + + firstResult.reject(failure) + await vi.waitFor(() => expect(signals[0]?.aborted).toBe(true)) + + expect(onError).not.toHaveBeenCalled() + expect(loader).toHaveBeenCalledOnce() + + successorBeforeLoadGate.resolve() + await Promise.all([first, second]) + + expect(loader).toHaveBeenCalledTimes(2) + expect(onError).not.toHaveBeenCalled() + expect(signals[1]?.aborted).toBe(false) + expect(router.state.location).toMatchObject({ + pathname: '/target', + search: { phase: 'successor' }, + }) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + loaderData: 'fresh', + }) + }) + + test('invalidate reruns the loader after the navigation settles', async () => { + const { router, loader, gate } = setup() + gate.resolve('data') + await router.load() + await router.navigate({ to: '/target' }) + expect(loader).toHaveBeenCalledTimes(1) + + await router.invalidate() + + expect(loader).toHaveBeenCalledTimes(2) + }) + + test('invalidate during an in-flight navigation still reruns the loader', async () => { + const { router, loader, gate } = setup() + await router.load() + + const first = router.navigate({ to: '/target' }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(1)) + const second = router.invalidate() + + gate.resolve('data') + await Promise.all([first, second]) + + expect(loader).toHaveBeenCalledTimes(2) + }) + + test('filtered invalidate does not adopt same-id work from an active preload', async () => { + const secondGate = createControlledPromise() + const loader = vi.fn( + ({ abortController }: { abortController: AbortController }) => { + const generation = loader.mock.calls.length + return generation === 2 + ? secondGate + : `generation ${generation}:${abortController.signal.aborted}` + }, + ) + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + validateSearch: (search: Record) => ({ + revision: Number(search.revision ?? 1), + }), + shouldReload: ({ preload }) => (preload ? true : undefined), + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory({ + initialEntries: ['/target?revision=1'], + }), + }) + + await router.load() + const preload = router.preloadRoute({ + to: '/target', + search: { revision: 2 }, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(2)) + + const invalidation = router.invalidate({ + filter: (match) => + match.routeId === targetRoute.id && + (match.search as { revision: number }).revision === 1, + }) + await vi.waitFor(() => expect(loader).toHaveBeenCalledTimes(3)) + await invalidation + + expect(router.state.location.search).toEqual({ revision: 1 }) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + loaderData: 'generation 3:false', + }) + + secondGate.resolve('preload data') + await preload + }) + + test('a repeat navigation after settle reloads instead of joining', async () => { + const { router, loader, gate, targetRoute } = setup() + gate.resolve('data') + await router.load() + await router.navigate({ to: '/target' }) + + // Once the first navigation settles there is nothing to join: a repeat + // same-destination navigation keeps its refresh semantics. + await router.navigate({ to: '/target' }) + + expect(loader).toHaveBeenCalledTimes(2) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + }) +}) diff --git a/packages/router-core/tests/scroll-restoration.test.ts b/packages/router-core/tests/scroll-restoration.test.ts index 188d13bb68..c02a8f8783 100644 --- a/packages/router-core/tests/scroll-restoration.test.ts +++ b/packages/router-core/tests/scroll-restoration.test.ts @@ -2,8 +2,15 @@ import { createMemoryHistory } from '@tanstack/history' import { afterEach, describe, expect, test, vi } from 'vitest' import { BaseRootRoute, BaseRoute } from '../src' import { createTestRouter } from './routerTestUtils' +import type { ParsedLocation } from '../src' -function createRouter(options: { scrollRestoration?: boolean } = {}) { +function createRouter( + options: { + scrollRestoration?: boolean + scrollToTopSelectors?: Array Element | null | undefined)> + getScrollRestorationKey?: (location: ParsedLocation) => string + } = {}, +) { const rootRoute = new BaseRootRoute({}) const indexRoute = new BaseRoute({ getParentRoute: () => rootRoute, @@ -18,9 +25,39 @@ function createRouter(options: { scrollRestoration?: boolean } = {}) { } afterEach(() => { + document.body.replaceChildren() + window.sessionStorage.clear() vi.restoreAllMocks() + vi.unstubAllGlobals() }) +function getLocation( + router: ReturnType, + pathname: string, +) { + return { + ...router.latestLocation, + href: pathname, + pathname, + } +} + +function emitNavigation( + router: ReturnType, + type: 'onBeforeLoad' | 'onRendered', + fromLocation: ParsedLocation, + toLocation: ParsedLocation, +) { + router.emit({ + type, + fromLocation, + toLocation, + pathChanged: true, + hrefChanged: true, + hashChanged: false, + }) +} + describe('setupScrollRestoration', () => { test('sets up scroll restoration when scrollRestoration is true', () => { const windowAddEventListener = vi.spyOn(window, 'addEventListener') @@ -46,6 +83,63 @@ describe('setupScrollRestoration', () => { window.history.scrollRestoration = previousScrollRestoration }) + test('snapshots the live position when it changed after the latest scroll event', () => { + const element = document.createElement('div') + element.id = 'unit-live-snapshot-element' + document.body.append(element) + vi.stubGlobal('scrollTo', vi.fn()) + + const router = createRouter({ + scrollRestoration: true, + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-live-snapshot-source') + const destination = getLocation(router, '/unit-live-snapshot-destination') + + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + element.scrollTop = 120 + + emitNavigation(router, 'onBeforeLoad', source, destination) + element.scrollTop = 0 + emitNavigation(router, 'onRendered', source, destination) + + expect(element.scrollTop).toBe(120) + }) + + test('snapshots the live window position when it changed after the latest scroll event', () => { + const windowScrollTo = vi.fn() + vi.stubGlobal('scrollTo', windowScrollTo) + vi.stubGlobal('scrollX', 0) + vi.stubGlobal('scrollY', 80) + + const router = createRouter({ + scrollRestoration: true, + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-live-window-snapshot-source') + const destination = getLocation( + router, + '/unit-live-window-snapshot-destination', + ) + + document.dispatchEvent(new Event('scroll')) + vi.stubGlobal('scrollY', 120) + + emitNavigation(router, 'onBeforeLoad', source, destination) + emitNavigation(router, 'onRendered', source, destination) + windowScrollTo.mockClear() + + emitNavigation(router, 'onBeforeLoad', destination, source) + emitNavigation(router, 'onRendered', destination, source) + + expect(windowScrollTo).toHaveBeenCalledWith({ + top: 120, + left: 0, + behavior: undefined, + }) + }) + test.each([ ['omitted', undefined], ['false', false], @@ -81,4 +175,157 @@ describe('setupScrollRestoration', () => { window.history.scrollRestoration = previousScrollRestoration }, ) + + test('restores a configured element independently from the window', () => { + const element = document.createElement('div') + element.id = 'unit-restored-element' + element.dataset.scrollRestorationId = 'unit-restored-element' + document.body.append(element) + + const elementScrollTo = vi.fn((options: ScrollToOptions) => { + element.scrollTop = options.top ?? element.scrollTop + }) + element.scrollTo = elementScrollTo as typeof element.scrollTo + const windowScrollTo = vi.fn() + vi.stubGlobal('scrollTo', windowScrollTo) + + const router = createRouter({ + scrollRestoration: true, + scrollToTopSelectors: ['#unit-restored-element'], + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-element-source') + const destination = getLocation(router, '/unit-element-destination') + + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + + emitNavigation(router, 'onBeforeLoad', source, destination) + emitNavigation(router, 'onRendered', source, destination) + expect(element.scrollTop).toBe(0) + + elementScrollTo.mockClear() + windowScrollTo.mockClear() + vi.stubGlobal('scrollX', 0) + vi.stubGlobal('scrollY', 120) + document.dispatchEvent(new Event('scroll')) + emitNavigation(router, 'onBeforeLoad', destination, source) + emitNavigation(router, 'onRendered', destination, source) + + expect(element.scrollTop).toBe(80) + expect(elementScrollTo).not.toHaveBeenCalled() + expect(windowScrollTo).toHaveBeenCalledWith({ + top: 0, + left: 0, + behavior: undefined, + }) + }) + + test('resets an uncached configured element when the window restores', () => { + const element = document.createElement('div') + element.id = 'unit-reset-element' + document.body.append(element) + + const elementScrollTo = vi.fn((options: ScrollToOptions) => { + element.scrollTop = options.top ?? element.scrollTop + }) + element.scrollTo = elementScrollTo as typeof element.scrollTo + + const windowScrollTo = vi.fn() + vi.stubGlobal('scrollX', 0) + vi.stubGlobal('scrollY', 120) + vi.stubGlobal('scrollTo', windowScrollTo) + + const router = createRouter({ + scrollRestoration: true, + scrollToTopSelectors: ['#unit-reset-element'], + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-window-source') + const destination = getLocation(router, '/unit-window-destination') + + document.dispatchEvent(new Event('scroll')) + emitNavigation(router, 'onBeforeLoad', source, destination) + emitNavigation(router, 'onRendered', source, destination) + + elementScrollTo.mockClear() + windowScrollTo.mockClear() + element.scrollTop = 80 + + emitNavigation(router, 'onBeforeLoad', destination, source) + emitNavigation(router, 'onRendered', destination, source) + + expect(windowScrollTo).toHaveBeenCalledWith({ + top: 120, + left: 0, + behavior: undefined, + }) + expect(element.scrollTop).toBe(0) + expect(elementScrollTo).toHaveBeenCalledWith({ + top: 0, + left: 0, + behavior: undefined, + }) + }) + + test('resets a configured element when its cached selector becomes stale', () => { + const element = document.createElement('div') + element.id = 'unit-stale-selector-element' + element.dataset.scrollRestorationId = 'unit-stale-selector-source' + document.body.append(element) + + const elementScrollTo = vi.fn((options: ScrollToOptions) => { + element.scrollTop = options.top ?? element.scrollTop + }) + element.scrollTo = elementScrollTo as typeof element.scrollTo + vi.stubGlobal('scrollTo', vi.fn()) + + const router = createRouter({ + scrollRestoration: true, + scrollToTopSelectors: [() => element], + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-stale-selector-source') + const destination = getLocation(router, '/unit-stale-selector-destination') + + element.scrollTop = 80 + element.dispatchEvent(new Event('scroll', { bubbles: true })) + emitNavigation(router, 'onBeforeLoad', source, destination) + emitNavigation(router, 'onRendered', source, destination) + expect(element.scrollTop).toBe(0) + + element.dataset.scrollRestorationId = 'unit-stale-selector-current' + element.scrollTop = 40 + elementScrollTo.mockClear() + emitNavigation(router, 'onBeforeLoad', destination, source) + emitNavigation(router, 'onRendered', destination, source) + + expect(element.scrollTop).toBe(0) + expect(elementScrollTo).toHaveBeenCalledWith({ + top: 0, + left: 0, + behavior: undefined, + }) + }) + + test('does not resolve configured selectors for hash navigation without source entries', () => { + const element = document.createElement('div') + document.body.append(element) + const getElement = vi.fn(() => element) + + const router = createRouter({ + scrollRestoration: true, + scrollToTopSelectors: [getElement], + getScrollRestorationKey: (location) => location.pathname, + }) + const source = getLocation(router, '/unit-hash-source') + const destination = { + ...getLocation(router, '/unit-hash-destination'), + hash: 'section', + } + + emitNavigation(router, 'onRendered', source, destination) + + expect(getElement).not.toHaveBeenCalled() + }) }) diff --git a/packages/router-core/tests/serial-failure-foreground-prefix.test.ts b/packages/router-core/tests/serial-failure-foreground-prefix.test.ts new file mode 100644 index 0000000000..ef57474288 --- /dev/null +++ b/packages/router-core/tests/serial-failure-foreground-prefix.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * When the serial beforeLoad phase records a failure, retained ancestor + * prefix loaders belong to the foreground failure lane: a stale ancestor + * must reload in the foreground commit rather than being deferred into a + * background batch that the boundary trim would discard. + */ + +describe('serial failure keeps ancestor reloads in the foreground lane', () => { + test('a stale ancestor reloads with fresh data when a child beforeLoad fails', async () => { + let parentRuns = 0 + let shouldFail = false + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + staleTime: 0, + loader: () => `parent run ${++parentRuns}`, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + errorComponent: {}, + beforeLoad: () => { + if (shouldFail) { + throw new Error('child beforeLoad failed') + } + }, + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + }) + + await router.load() + expect(parentRuns).toBe(1) + + await router.navigate({ to: '/' }) + shouldFail = true + await router.navigate({ to: '/parent/child' }) + + const parentMatch = router.state.matches.find( + (match) => match.routeId === parentRoute.id, + ) + const childMatch = router.state.matches.find( + (match) => match.routeId === childRoute.id, + ) + + // The child committed its serial failure... + expect(childMatch?.status).toBe('error') + // ...and the stale parent reloaded in the SAME foreground lane: fresh + // loaderData is committed with the failure, not deferred to a discarded + // background batch. + expect(parentRuns).toBe(2) + expect(parentMatch?.loaderData).toBe('parent run 2') + expect(parentMatch?.status).toBe('success') + }) +}) diff --git a/packages/router-core/tests/server-async-headers-decorative-hang.test.ts b/packages/router-core/tests/server-async-headers-decorative-hang.test.ts new file mode 100644 index 0000000000..454e6a6bb8 --- /dev/null +++ b/packages/router-core/tests/server-async-headers-decorative-hang.test.ts @@ -0,0 +1,52 @@ +import { afterEach, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('a rejected projection hook is logged without failing the response', async () => { + const projectionError = new Error('scripts failed') + const log = vi.spyOn(console, 'error').mockImplementation(() => {}) + const headGate = createControlledPromise<{ + meta: Array<{ title: string }> + }>() + const headersGate = createControlledPromise>() + const head = vi.fn(() => headGate) + const scripts = vi.fn(async () => { + throw projectionError + }) + const headers = vi.fn(() => headersGate) + + const rootRoute = new BaseRootRoute({}) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + head, + scripts, + headers, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([targetRoute]), + history: createMemoryHistory({ initialEntries: ['/target'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/target') + + expect(headGate.status).toBe('pending') + expect(headersGate.status).toBe('pending') + expect(response.status).toBe(200) + expect(response.headers.get('x-response-header')).toBeNull() + expect(head).toHaveBeenCalledTimes(1) + expect(scripts).toHaveBeenCalledTimes(1) + expect(headers).toHaveBeenCalledTimes(1) + expect(log).toHaveBeenCalledTimes(1) + expect(log).toHaveBeenCalledWith(projectionError) + + headGate.resolve({ meta: [{ title: 'discarded' }] }) + headersGate.resolve({ 'x-response-header': 'discarded' }) + await Promise.all([headGate, headersGate]) +}) diff --git a/packages/router-core/tests/server-beforeload-preload-flag.test.ts b/packages/router-core/tests/server-beforeload-preload-flag.test.ts new file mode 100644 index 0000000000..4779001305 --- /dev/null +++ b/packages/router-core/tests/server-beforeload-preload-flag.test.ts @@ -0,0 +1,88 @@ +import { createMemoryHistory } from '@tanstack/history' +import { expect, test, vi } from 'vitest' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter } from './routerTestUtils' + +test('server beforeLoad receives preload false', async () => { + const seen: Array = [] + const rootRoute = new BaseRootRoute({}) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + beforeLoad: ({ preload }) => { + seen.push(preload) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + + await router.load() + + expect(seen).toEqual([false]) +}) + +test('server preloadRoute stays speculative and receives preload true', async () => { + const beforeLoad = vi.fn() + const loader = vi.fn(() => 'preloaded data') + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + beforeLoad, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, childRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + isServer: true, + }) + + await router.load() + const matches = await router.preloadRoute({ to: '/child' }) + + expect(beforeLoad).toHaveBeenCalledWith( + expect.objectContaining({ preload: true }), + ) + expect(loader).toHaveBeenCalledOnce() + expect(matches?.at(-1)).toMatchObject({ + routeId: childRoute.id, + loaderData: 'preloaded data', + }) + expect(router.state.location.pathname).toBe('/') + expect(router.state.matches.at(-1)?.routeId).toBe(indexRoute.id) +}) + +test('server child context receives its parent beforeLoad context', async () => { + const rootRoute = new BaseRootRoute({ + beforeLoad: () => ({ parent: 'ready' }), + }) + const childContext = vi.fn(({ context }) => ({ + child: `${context.parent}:child`, + })) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + context: childContext, + loader: ({ context }) => context.child, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + + await router.load() + + expect(router.state.matches.at(-1)).toMatchObject({ + context: { parent: 'ready', child: 'ready:child' }, + loaderData: 'ready:child', + }) + expect(childContext).toHaveBeenCalledTimes(1) +}) diff --git a/packages/router-core/tests/server-chunk-failure-lifecycle.test.ts b/packages/router-core/tests/server-chunk-failure-lifecycle.test.ts new file mode 100644 index 0000000000..662fab95f9 --- /dev/null +++ b/packages/router-core/tests/server-chunk-failure-lifecycle.test.ts @@ -0,0 +1,238 @@ +import { describe, expect, onTestFinished, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + redirect, +} from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +/** + * A primary route chunk failure observed by the server loader phase (a route + * component preload or lazyFn rejection) must go through the same route + * failure lifecycle as loader failures: the route's onError runs, + * redirect/notFound conversion applies, and the errorComponent boundary + * chunk is loaded. Only boundary component chunk failures commit directly + * without recursing into another boundary chunk. + * + * These tests use real server router loads and public component preload/lazy + * route APIs. + */ + +describe('server route chunk failure lifecycle', () => { + test('calls route onError once and loads the errorComponent chunk when the route component chunk rejects', async () => { + const chunkError = new Error('component chunk failed') + const capturedErrors: Array = [] + const events: Array = [] + + const FailingComponent = Object.assign(() => null, { + preload: () => Promise.reject(chunkError), + }) + const ErrorBoundary = Object.assign(() => null, { + preload: () => { + events.push('errorComponent-preload') + return Promise.resolve() + }, + }) + + const rootRoute = new BaseRootRoute({}) + const chunkedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/chunked', + onError: (error: unknown) => { + capturedErrors.push(error) + events.push('onError') + }, + component: FailingComponent as any, + errorComponent: ErrorBoundary as any, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([chunkedRoute]), + history: createMemoryHistory({ initialEntries: ['/chunked'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/chunked') + + const match = router.state.matches.find( + (item) => item.routeId === chunkedRoute.id, + ) + expect(capturedErrors).toEqual([chunkError]) + expect(match?.status).toBe('error') + expect(match?.error).toBe(chunkError) + expect(events).toEqual(['onError', 'errorComponent-preload']) + expect(response.status).toBe(500) + }) + + test('a notFound thrown from onError for a route chunk failure sets 404 instead of 500', async () => { + const chunkError = new Error('component chunk failed') + + const FailingComponent = Object.assign(() => null, { + preload: () => Promise.reject(chunkError), + }) + + const rootRoute = new BaseRootRoute({}) + const chunkedRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/chunked', + onError: () => { + throw notFound() + }, + component: FailingComponent as any, + notFoundComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([chunkedRoute]), + history: createMemoryHistory({ initialEntries: ['/chunked'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/chunked') + + const match = router.state.matches.find( + (item) => item.routeId === chunkedRoute.id, + ) + expect(response.status).toBe(404) + expect(match?.status).toBe('notFound') + expect(match?.error).toEqual(expect.objectContaining({ isNotFound: true })) + }) + + test('the first route-order chunk failure determines the response', async () => { + const rootError = new Error('root component failed') + const rootChunkGate = createControlledPromise() + const childConverted = createControlledPromise() + const RootComponent = Object.assign(() => null, { + preload: () => rootChunkGate, + }) + const ChildComponent = Object.assign(() => null, { + preload: () => Promise.reject(new Error('child component failed')), + }) + + const rootRoute = new BaseRootRoute({ + component: RootComponent as any, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + component: ChildComponent as any, + onError: () => { + childConverted.resolve() + throw redirect({ to: '/elsewhere' }) + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + + const responsePromise = loadServerResponse(router, '/child') + await childConverted + rootChunkGate.reject(rootError) + const response = await responsePromise + + expect(response.status).toBe(500) + expect(response.headers.get('Location')).toBeNull() + expect(router.state.matches).toHaveLength(2) + expect(router.state.matches[0]).toMatchObject({ + routeId: rootRoute.id, + status: 'error', + error: rootError, + }) + }) + + test('does not leave later chunk rejections unhandled after request abort', async () => { + const rootError = new Error('root component failed') + const abortError = new Error('request closed') + const rootChunkGate = createControlledPromise() + const childChunkGate = createControlledPromise() + const rootChunkStarted = createControlledPromise() + const childChunkStarted = createControlledPromise() + const RootComponent = Object.assign(() => null, { + preload: () => { + rootChunkStarted.resolve() + return rootChunkGate + }, + }) + const ChildComponent = Object.assign(() => null, { + preload: () => { + childChunkStarted.resolve() + return childChunkGate + }, + }) + const rootRoute = new BaseRootRoute({ + component: RootComponent as any, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + component: ChildComponent as any, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + const controller = new AbortController() + const unhandled: Array = [] + const onUnhandled = (error: unknown) => { + unhandled.push(error) + } + process.on('unhandledRejection', onUnhandled) + onTestFinished(() => { + process.off('unhandledRejection', onUnhandled) + }) + + const responsePromise = loadServerResponse( + router, + '/child', + controller.signal, + ) + await Promise.all([rootChunkStarted, childChunkStarted]) + + rootChunkGate.reject(rootError) + await expect(responsePromise).resolves.toMatchObject({ status: 500 }) + + controller.abort(abortError) + childChunkGate.resolve() + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(unhandled).not.toContain(abortError) + }) + + test('a required ancestor lazy chunk failure wins over a deeper loader notFound', async () => { + const chunkError = new Error('ancestor lazy chunk failed') + const rootRoute = new BaseRootRoute({}) + const ancestorRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/ancestor', + errorComponent: () => null, + }).lazy(() => Promise.reject(chunkError)) + const childRoute = new BaseRoute({ + getParentRoute: () => ancestorRoute, + path: '/child', + loader: () => { + throw notFound() + }, + notFoundComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + ancestorRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/ancestor/child'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/ancestor/child') + + expect(response.status).toBe(500) + expect( + router.state.matches.find((match) => match.routeId === ancestorRoute.id), + ).toMatchObject({ status: 'error', error: chunkError }) + }) +}) diff --git a/packages/router-core/tests/server-concurrent-error-notfound.test.ts b/packages/router-core/tests/server-concurrent-error-notfound.test.ts new file mode 100644 index 0000000000..5fc3cc63f9 --- /dev/null +++ b/packages/router-core/tests/server-concurrent-error-notfound.test.ts @@ -0,0 +1,467 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, + redirect, +} from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +const requiredPrefixModes = [ + ['fresh', 'error', false, 'child-first'], + ['fresh', 'error', true, 'child-first'], + ['fresh', 'notFound', false, 'child-first'], + ['fresh', 'notFound', true, 'child-first'], + ['retained', 'error', false, 'child-first'], + ['retained', 'notFound', false, 'child-first'], + ['retained', 'error', false, 'parent-first'], +] as const + +describe('concurrent route failure ordering', () => { + test.each(requiredPrefixModes)( + 'selects the reachable failure (parentData=%s, parent=%s, isServer=%s, order=%s)', + async (parentData, parentOutcome, isServer, settlementOrder) => { + const parentFailure = + parentOutcome === 'error' + ? new Error('parent loader failed') + : notFound() + const childFailure = + parentOutcome === 'error' + ? notFound() + : new Error('child loader failed') + const childOutcome = parentOutcome === 'error' ? 'notFound' : 'error' + const parentGate = createControlledPromise() + const childGate = createControlledPromise() + const parentStarted = createControlledPromise() + const childStarted = createControlledPromise() + const parentSettled = createControlledPromise() + const childSettled = createControlledPromise() + const settlements: Array = [] + let failing = parentData === 'fresh' + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + ssr: 'data-only', + loader: async () => { + if (!failing) { + return undefined + } + parentStarted.resolve() + await parentGate + settlements.push('parent') + parentSettled.resolve() + throw parentFailure + }, + errorComponent: () => null, + notFoundComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + if (!failing) { + return 'child data' + } + childStarted.resolve() + await childGate + settlements.push('child') + childSettled.resolve() + throw childFailure + }, + errorComponent: () => null, + notFoundComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer, + defaultStaleReloadMode: 'blocking', + }) + + if (parentData === 'retained') { + await router.load() + const parentMatch = router.state.matches.find( + (match) => match.routeId === parentRoute.id, + ) + expect(parentMatch).toMatchObject({ + routeId: parentRoute.id, + status: 'success', + loaderData: undefined, + }) + failing = true + } + + const responsePromise = isServer + ? loadServerResponse(router, '/parent/child') + : (parentData === 'retained' + ? router.invalidate() + : router.load() + ).then(() => undefined) + await Promise.all([parentStarted, childStarted]) + if (settlementOrder === 'child-first') { + childGate.resolve() + await childSettled + parentGate.resolve() + await parentSettled + } else { + parentGate.resolve() + await parentSettled + childGate.resolve() + await childSettled + } + const response = await responsePromise + + const parentWon = + parentData === 'fresh' || settlementOrder === 'parent-first' + const selectedOutcome = parentWon ? parentOutcome : childOutcome + const selectedFailure = parentWon ? parentFailure : childFailure + if (isServer) { + expect(response?.status).toBe(selectedOutcome === 'error' ? 500 : 404) + } + expect(settlements).toEqual( + settlementOrder === 'child-first' + ? ['child', 'parent'] + : ['parent', 'child'], + ) + const terminalMatch = router.state.matches.find( + (match) => match.status === 'error' || match.status === 'notFound', + ) + expect(terminalMatch).toMatchObject({ + routeId: parentWon ? parentRoute.id : childRoute.id, + status: selectedOutcome, + error: selectedFailure, + }) + expect(router.state.status).toBe('idle') + }, + ) + + test.each([false, true])( + 'recomputes chunk readiness after a notFound ancestor promotes to root (isServer=%s)', + async (isServer) => { + const chunkError = new Error('intermediate lazy chunk failed') + const childError = new Error('child loader failed') + const rootNotFound = notFound() + const ancestorGate = createControlledPromise() + const childGate = createControlledPromise() + const ancestorStarted = createControlledPromise() + const childStarted = createControlledPromise() + const ancestorSettled = createControlledPromise() + const childSettled = createControlledPromise() + const settlements: Array = [] + const intermediateLazy = vi.fn(() => Promise.reject(chunkError)) + const rootRoute = new BaseRootRoute({ + notFoundComponent: () => null, + }) + const intermediateRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/intermediate', + errorComponent: () => null, + }).lazy(intermediateLazy) + const ancestorRoute = new BaseRoute({ + getParentRoute: () => intermediateRoute, + path: '/ancestor', + loader: async () => { + ancestorStarted.resolve() + await ancestorGate + settlements.push('ancestor') + ancestorSettled.resolve() + throw rootNotFound + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => ancestorRoute, + path: '/child', + loader: async () => { + childStarted.resolve() + await childGate + settlements.push('child') + childSettled.resolve() + throw childError + }, + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + intermediateRoute.addChildren([ + ancestorRoute.addChildren([childRoute]), + ]), + ]), + history: createMemoryHistory({ + initialEntries: ['/intermediate/ancestor/child'], + }), + isServer, + }) + + const responsePromise = isServer + ? loadServerResponse(router, '/intermediate/ancestor/child') + : router.load().then(() => undefined) + await Promise.all([ancestorStarted, childStarted]) + childGate.resolve() + await childSettled + ancestorGate.resolve() + await ancestorSettled + const response = await responsePromise + + if (isServer) { + expect(response?.status).toBe(404) + } + expect(settlements).toEqual(['child', 'ancestor']) + expect(intermediateLazy).toHaveBeenCalled() + expect( + router.state.matches.find((match) => match.routeId === rootRoute.id), + ).toMatchObject({ + status: 'success', + error: rootNotFound, + }) + expect( + router.state.matches.some((match) => match.error === chunkError), + ).toBe(false) + }, + ) + + test.each([ + [false, 'beforeLoad'], + [false, 'loader'], + [true, 'beforeLoad'], + [true, 'loader'], + ] as const)( + 'keeps a resolved notFound boundary stable within one load (isServer=%s, phase=%s)', + async (isServer, phase) => { + const lazyError = new Error('lazy boundary lookup failed') + const notFoundError = notFound() + const resolveAt = phase === 'beforeLoad' ? 2 : 3 + let lazyCalls = 0 + const rootRoute = new BaseRootRoute({ + notFoundComponent: () => null, + }) + const intermediateRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/intermediate', + }).lazy(() => { + lazyCalls++ + if (lazyCalls < resolveAt) { + return Promise.reject(lazyError) + } + return Promise.resolve({ + options: { notFoundComponent: () => null }, + } as any) + }) + function throwNotFound() { + throw notFoundError + } + const childRoute = new BaseRoute({ + getParentRoute: () => intermediateRoute, + path: '/child', + beforeLoad: phase === 'beforeLoad' ? throwNotFound : undefined, + loader: phase === 'loader' ? throwNotFound : undefined, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + intermediateRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ + initialEntries: ['/intermediate/child'], + }), + isServer, + }) + + const response = isServer + ? await loadServerResponse(router, '/intermediate/child') + : await router.load().then(() => undefined) + + if (isServer) { + expect(response?.status).toBe(404) + } + expect(lazyCalls).toBe(!isServer && phase === 'loader' ? 2 : 1) + expect( + router.state.matches.find((match) => match.error === notFoundError) + ?.routeId, + ).toBe(rootRoute.id) + }, + ) + + test('a redirect aborts every generation in its discarded server lane', async () => { + const signals: Array = [] + const rootRoute = new BaseRootRoute({ + loader: ({ abortController }) => { + signals.push(abortController.signal) + return 'root data' + }, + }) + const redirectRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/redirect', + loader: ({ abortController }) => { + signals.push(abortController.signal) + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([redirectRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/redirect'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/redirect') + + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/target') + expect(signals).toHaveLength(2) + expect(signals.every((signal) => signal.aborted)).toBe(true) + }) + + test('a started descendant can redirect after its ancestor fails', async () => { + const childStarted = createControlledPromise() + const childRedirect = createControlledPromise() + const parentError = new Error('parent failed') + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: async () => { + await childStarted + throw parentError + }, + errorComponent: () => null, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: async () => { + childStarted.resolve() + await childRedirect + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + parentRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + const responsePromise = loadServerResponse(router, '/parent/child') + await childStarted + childRedirect.resolve() + const response = await responsePromise + + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/target') + }) + + test('a redirect does not abort a later server generation on the same router', async () => { + let generation = 0 + const rootRoute = new BaseRootRoute({}) + const pageRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => { + generation++ + if (generation === 2) { + throw redirect({ to: '/target' }) + } + return `data ${generation}` + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([pageRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + isServer: true, + }) + + expect((await loadServerResponse(router, '/page')).status).toBe(200) + expect((await loadServerResponse(router, '/page')).status).toBe(307) + expect((await loadServerResponse(router, '/page')).status).toBe(200) + + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: pageRoute.id, + status: 'success', + loaderData: 'data 3', + }) + }) + + test.each([false, true])( + 'an aborted child does not report its cancellation through onError (isServer=%s)', + async (isServer) => { + const childStarted = createControlledPromise() + const childLoader = createControlledPromise() + const cancellation = new Error('discarded request aborted') + const childOnError = vi.fn() + let childSignal: AbortSignal | undefined + const rootRoute = new BaseRootRoute({}) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + loader: async () => { + await childStarted + throw redirect({ to: '/target' }) + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => sourceRoute, + path: '/child', + loader: ({ abortController }) => { + childSignal = abortController.signal + abortController.signal.addEventListener( + 'abort', + () => childLoader.reject(cancellation), + { once: true }, + ) + childStarted.resolve() + return childLoader + }, + onError: childOnError, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + sourceRoute.addChildren([childRoute]), + targetRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/source/child'] }), + isServer, + }) + + const childCancellation = expect(childLoader).rejects.toBe(cancellation) + const loadPromise = isServer + ? loadServerResponse(router, '/source/child') + : router.load().then(() => undefined) + const [response] = await Promise.all([loadPromise, childCancellation]) + + expect(childSignal?.aborted).toBe(true) + expect(childOnError).not.toHaveBeenCalled() + if (isServer) { + expect(response?.status).toBe(307) + expect(response?.headers.get('Location')).toBe('/target') + } else { + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + } + }, + ) +}) diff --git a/packages/router-core/tests/server-loader-abort-error.test.ts b/packages/router-core/tests/server-loader-abort-error.test.ts new file mode 100644 index 0000000000..bc079ce239 --- /dev/null +++ b/packages/router-core/tests/server-loader-abort-error.test.ts @@ -0,0 +1,289 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createRequestHandler } from '../src/ssr/server' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +describe('loader user-thrown abort values', () => { + test.each( + ([false, true] as const).flatMap((isServer) => [ + { + isServer, + thrownType: 'AbortSignal', + createThrownValue: (signal: AbortSignal) => signal, + }, + { + isServer, + thrownType: 'AbortError', + createThrownValue: () => + new DOMException('The operation was aborted.', 'AbortError'), + }, + ]), + )( + 'treats a user-thrown $thrownType as an ordinary route error (isServer=$isServer)', + async ({ isServer, createThrownValue }) => { + let matchSignal: AbortSignal | undefined + let thrownValue: unknown + const rootRoute = new BaseRootRoute({}) + const abortingRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/aborting', + loader: ({ abortController }) => { + matchSignal = abortController.signal + thrownValue = createThrownValue(matchSignal) + throw thrownValue + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([abortingRoute]), + history: createMemoryHistory({ initialEntries: ['/aborting'] }), + isServer, + }) + + if (isServer) { + const response = await loadServerResponse(router, '/aborting') + expect(response.status).toBe(500) + } else { + await router.load() + } + + const match = router.state.matches.find( + (item) => item.routeId === abortingRoute.id, + ) + // A server match remains ordinary route failure during reduction, then + // its controller is retired when the request response is cleaned up. + expect(matchSignal?.aborted).toBe(isServer) + expect(match?.status).toBe('error') + expect(match?.error).toBe(thrownValue) + }, + ) + + test('request cancellation reaches the controller shared by route hooks', async () => { + const loaderStarted = createControlledPromise() + const controllers: Array = [] + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/work', + context: ({ abortController }) => { + controllers.push(abortController) + }, + beforeLoad: ({ abortController }) => { + controllers.push(abortController) + }, + loader: ({ abortController }) => { + controllers.push(abortController) + loaderStarted.resolve() + return new Promise((_, reject) => { + abortController.signal.addEventListener( + 'abort', + () => reject(abortController.signal.reason), + { once: true }, + ) + }) + }, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/work'] }), + isServer: true, + }) + const requestController = new AbortController() + const render = vi.fn(() => new Response('must not render')) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/work', { + signal: requestController.signal, + }), + }) + const response = handler(render) + await loaderStarted + + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + expect(controllers).toHaveLength(3) + expect( + controllers.every((controller) => controller === controllers[0]), + ).toBe(true) + expect(controllers[0]?.signal.aborted).toBe(true) + expect(controllers[0]?.signal.reason).toBe(cancellation) + expect(onError).not.toHaveBeenCalled() + expect(render).not.toHaveBeenCalled() + }) + + test('response cleanup aborts the controller retained by deferred loader data', async () => { + const deferred = createControlledPromise() + let loaderSignal: AbortSignal | undefined + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/work', + loader: ({ abortController }) => { + loaderSignal = abortController.signal + return { deferred } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/work'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/work') + + expect(response.status).toBe(200) + expect(loaderSignal?.aborted).toBe(true) + deferred.resolve('late data') + }) + + test('request cancellation thrown from route context does not call onError', async () => { + const cancellation = new Error('request disconnected in context') + const requestController = new AbortController() + const beforeLoad = vi.fn() + const loader = vi.fn() + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/work', + context: ({ abortController }) => { + requestController.abort(cancellation) + abortController.signal.throwIfAborted() + }, + beforeLoad, + loader, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/work'] }), + isServer: true, + }) + const render = vi.fn(() => new Response('must not render')) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/work', { + signal: requestController.signal, + }), + }) + + await expect(handler(render)).rejects.toBe(cancellation) + + expect(onError).not.toHaveBeenCalled() + expect(beforeLoad).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + expect(render).not.toHaveBeenCalled() + }) + + test('request cancellation during beforeLoad does not start loaders', async () => { + const beforeLoadStarted = createControlledPromise() + const loader = vi.fn() + const rootRoute = new BaseRootRoute({}) + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/work', + beforeLoad: ({ abortController }) => { + beforeLoadStarted.resolve() + return new Promise((resolve) => { + abortController.signal.addEventListener('abort', () => resolve(), { + once: true, + }) + }) + }, + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/work'] }), + isServer: true, + }) + const requestController = new AbortController() + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/work', { + signal: requestController.signal, + }), + }) + const response = handler(() => new Response('must not render')) + await beforeLoadStarted + + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + expect(loader).not.toHaveBeenCalled() + }) + + test('request cancellation does not wait for manifest lookup', async () => { + const manifestStarted = createControlledPromise() + const manifest = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const loader = vi.fn() + const route = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/work', + loader, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([route]), + history: createMemoryHistory({ initialEntries: ['/work'] }), + isServer: true, + }) + const requestController = new AbortController() + const render = vi.fn(() => new Response('must not render')) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/work', { + signal: requestController.signal, + }), + getRouterManifest: () => { + manifestStarted.resolve() + return manifest + }, + }) + + const response = handler(render) + await manifestStarted + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + expect(loader).not.toHaveBeenCalled() + expect(render).not.toHaveBeenCalled() + }) + + test('request cancellation does not wait for custom dehydration', async () => { + const dehydrateStarted = createControlledPromise() + const dehydrate = createControlledPromise() + const rootRoute = new BaseRootRoute({}) + const router = createTestRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + isServer: true, + dehydrate: () => { + dehydrateStarted.resolve() + return dehydrate + }, + }) + const requestController = new AbortController() + const render = vi.fn(() => new Response('must not render')) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/', { + signal: requestController.signal, + }), + }) + + const response = handler(render) + await dehydrateStarted + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + expect(render).not.toHaveBeenCalled() + }) +}) diff --git a/packages/router-core/tests/server-planning-redirect.test.ts b/packages/router-core/tests/server-planning-redirect.test.ts new file mode 100644 index 0000000000..95a3a50317 --- /dev/null +++ b/packages/router-core/tests/server-planning-redirect.test.ts @@ -0,0 +1,79 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, redirect } from '../src' +import { createRequestHandler } from '../src/ssr/server' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +test.each([false, true])( + 'a route-context planning redirect is control with isServer=%s', + async (isServer) => { + const rootRoute = new BaseRootRoute({}) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + context: () => { + throw redirect({ to: '/target' }) + }, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/source'] }), + isServer, + }) + + if (isServer) { + const response = await loadServerResponse(router, '/source') + + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/target') + } else { + await router.load() + + expect(router.state.location.pathname).toBe('/target') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: targetRoute.id, + status: 'success', + }) + } + }, +) + +test('the generic request handler returns a redirect without rendering', async () => { + const rootRoute = new BaseRootRoute({}) + const sourceRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/source', + loader: () => + redirect({ + to: '/target', + statusCode: 308, + headers: { 'x-redirect': 'route' }, + }), + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([sourceRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/source'] }), + isServer: true, + }) + const render = vi.fn(() => new Response('must not render')) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/source'), + }) + + const response = await handler(render) + + expect(response.status).toBe(308) + expect(response.headers.get('Location')).toBe('/target') + expect(response.headers.get('x-redirect')).toBe('route') + expect(await response.text()).toBe('') + expect(render).not.toHaveBeenCalled() +}) diff --git a/packages/router-core/tests/server-route-lifecycle.test.ts b/packages/router-core/tests/server-route-lifecycle.test.ts new file mode 100644 index 0000000000..7c90f87b60 --- /dev/null +++ b/packages/router-core/tests/server-route-lifecycle.test.ts @@ -0,0 +1,76 @@ +import { expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +test('server route lifecycle callbacks run after loaded matches are published', async () => { + const events: Array = [] + const onEnter = vi.fn((match) => { + events.push(`enter:${match.routeId}:${match.loaderData}`) + }) + const onStay = vi.fn((match) => { + events.push(`stay:${match.routeId}:${match.search.step}`) + }) + const onLeave = vi.fn((match) => { + events.push(`leave:${match.routeId}`) + }) + const barEnter = vi.fn((match) => { + events.push(`enter:${match.routeId}`) + }) + + const rootRoute = new BaseRootRoute({}) + const fooRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/foo', + beforeLoad: () => { + events.push('beforeLoad:/foo') + return { ready: true } + }, + loader: ({ context }) => { + events.push(`loader:/foo:${context.ready}`) + return 'foo data' + }, + onEnter, + onStay, + onLeave, + }) + const barRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/bar', + onEnter: barEnter, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([fooRoute, barRoute]), + history: createMemoryHistory(), + isServer: true, + }) + + expect((await loadServerResponse(router, '/foo?step=one')).status).toBe(200) + expect(events).toEqual([ + 'beforeLoad:/foo', + 'loader:/foo:true', + `enter:${fooRoute.id}:foo data`, + ]) + expect(onEnter).toHaveBeenCalledWith( + expect.objectContaining({ + routeId: fooRoute.id, + status: 'success', + loaderData: 'foo data', + context: expect.objectContaining({ ready: true }), + }), + ) + + events.length = 0 + expect((await loadServerResponse(router, '/foo?step=two')).status).toBe(200) + expect(events).toEqual([ + 'beforeLoad:/foo', + 'loader:/foo:true', + `stay:${fooRoute.id}:two`, + ]) + + events.length = 0 + expect((await loadServerResponse(router, '/bar')).status).toBe(200) + expect(events).toEqual([`leave:${fooRoute.id}`, `enter:${barRoute.id}`]) + expect(onLeave).toHaveBeenCalledTimes(1) + expect(barEnter).toHaveBeenCalledTimes(1) +}) diff --git a/packages/router-core/tests/server-serial-ssr-notfound.test.ts b/packages/router-core/tests/server-serial-ssr-notfound.test.ts new file mode 100644 index 0000000000..6f8c92b6ca --- /dev/null +++ b/packages/router-core/tests/server-serial-ssr-notfound.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, test } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, notFound } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +/** + * A notFound thrown from a route's ssr() option happens during the serial + * phase, before beforeLoad has run for that route and its descendants. It + * must cap the loader prefix like a beforeLoad notFound: the throwing route + * and its descendants must not run their loaders (their context is + * incomplete and their ssr status unresolved), and the response must be a + * 404 — a descendant loader failure must not be able to turn the outcome + * into a 200/500. + */ + +function setupRouter() { + const loaderCalls: Array = [] + + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + notFoundComponent: () => null, + ssr: () => { + throw notFound() + }, + loader: () => { + loaderCalls.push('parent') + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + loader: () => { + loaderCalls.push('child') + throw new Error('child loader failed') + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + + return { router, rootRoute, parentRoute, childRoute, loaderCalls } +} + +describe('server serial ssr() notFound', () => { + test('caps the loader prefix at the boundary and sets 404', async () => { + const { router, rootRoute, parentRoute, childRoute, loaderCalls } = + setupRouter() + + const response = await loadServerResponse(router, '/parent/child') + + const parentMatch = router.state.matches.find( + (item) => item.routeId === parentRoute.id, + ) + expect(loaderCalls).toEqual([]) + expect(response.status).toBe(404) + expect(parentMatch?.status).toBe('notFound') + expect(parentMatch?.error).toEqual( + expect.objectContaining({ isNotFound: true }), + ) + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + parentRoute.id, + childRoute.id, + ]) + expect(router.state.matches[1]?.status).toBe('notFound') + }) +}) diff --git a/packages/router-core/tests/server-ssr-false-assets.test.ts b/packages/router-core/tests/server-ssr-false-assets.test.ts new file mode 100644 index 0000000000..5ee27fa232 --- /dev/null +++ b/packages/router-core/tests/server-ssr-false-assets.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +/** + * The first `ssr: false` route contributes the assets needed for its server + * shell. Descendants inherit the client-only restriction and do not execute + * their route asset callbacks, even if they request `ssr: true` themselves. + */ +describe('server assets for ssr:false routes', () => { + test('executes assets at the client-only boundary but not below it', async () => { + const rootHead = vi.fn(() => ({ meta: [{ title: 'server root' }] })) + const rootScripts = vi.fn(() => [ + { + children: 'window.rootAssetRan = true', + }, + ]) + const rootHeaders = vi.fn(() => ({ + 'x-server-root': 'projected', + })) + const parentHead = vi.fn(() => ({ + meta: [{ title: 'client-only parent' }], + })) + const parentScripts = vi.fn(() => [ + { + children: 'window.parentAssetRan = true', + }, + ]) + const parentHeaders = vi.fn(() => ({ + 'x-client-only-parent': 'projected', + })) + const parentBeforeLoad = vi.fn(() => ({})) + const parentLoader = vi.fn(() => 'unexpected') + const childHead = vi.fn(() => ({ + meta: [{ title: 'client-only child' }], + })) + const childScripts = vi.fn(() => [ + { + children: 'window.childAssetRan = true', + }, + ]) + const childHeaders = vi.fn(() => ({ + 'x-client-only-child': 'unexpected', + })) + + const rootRoute = new BaseRootRoute({ + head: rootHead, + scripts: rootScripts, + headers: rootHeaders, + }) + const clientOnlyRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/client-only', + ssr: false, + beforeLoad: parentBeforeLoad, + loader: parentLoader, + head: parentHead, + scripts: parentScripts, + headers: parentHeaders, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => clientOnlyRoute, + path: '/child', + // A child cannot relax its parent's client-only restriction. + ssr: true, + head: childHead, + scripts: childScripts, + headers: childHeaders, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + clientOnlyRoute.addChildren([childRoute]), + ]), + history: createMemoryHistory({ + initialEntries: ['/client-only/child'], + }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/client-only/child') + + expect(response.status).toBe(200) + expect(rootHead).toHaveBeenCalledTimes(1) + expect(rootScripts).toHaveBeenCalledTimes(1) + expect(rootHeaders).toHaveBeenCalledTimes(1) + expect(response.headers.get('x-server-root')).toBe('projected') + expect(parentHead).toHaveBeenCalledTimes(1) + expect(parentScripts).toHaveBeenCalledTimes(1) + expect(parentHeaders).toHaveBeenCalledTimes(1) + expect(parentBeforeLoad).not.toHaveBeenCalled() + expect(parentLoader).not.toHaveBeenCalled() + expect(response.headers.get('x-client-only-parent')).toBe('projected') + expect( + router.state.matches.find( + (match) => match.routeId === clientOnlyRoute.id, + ), + ).toMatchObject({ + meta: [{ title: 'client-only parent' }], + scripts: [{ children: 'window.parentAssetRan = true' }], + headers: { 'x-client-only-parent': 'projected' }, + }) + expect(childHead).not.toHaveBeenCalled() + expect(childScripts).not.toHaveBeenCalled() + expect(childHeaders).not.toHaveBeenCalled() + expect(response.headers.get('x-client-only-child')).toBeNull() + }) + + test('executes assets when the root route is client-only', async () => { + const rootHead = vi.fn(() => ({ meta: [{ title: 'root shell' }] })) + const rootScripts = vi.fn(() => [ + { children: 'window.rootShellRan = true' }, + ]) + const rootHeaders = vi.fn(() => ({ 'x-root-shell': 'projected' })) + const childHead = vi.fn(() => ({ meta: [{ title: 'child' }] })) + const childScripts = vi.fn(() => [{ children: 'window.childRan = true' }]) + const childHeaders = vi.fn(() => ({ 'x-child': 'unexpected' })) + const rootRoute = new BaseRootRoute({ + ssr: false, + head: rootHead, + scripts: rootScripts, + headers: rootHeaders, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/child', + ssr: true, + head: childHead, + scripts: childScripts, + headers: childHeaders, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([childRoute]), + history: createMemoryHistory({ initialEntries: ['/child'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/child') + + expect(response.status).toBe(200) + expect(rootHead).toHaveBeenCalledTimes(1) + expect(rootScripts).toHaveBeenCalledTimes(1) + expect(rootHeaders).toHaveBeenCalledTimes(1) + expect(response.headers.get('x-root-shell')).toBe('projected') + expect(childHead).not.toHaveBeenCalled() + expect(childScripts).not.toHaveBeenCalled() + expect(childHeaders).not.toHaveBeenCalled() + expect(response.headers.get('x-child')).toBeNull() + }) + + test('projects assets for a data-only branch', async () => { + const head = vi.fn(({ loaderData }) => ({ + meta: [{ title: `report: ${loaderData}` }], + })) + const scripts = vi.fn(({ loaderData }) => [ + { children: `window.report = ${JSON.stringify(loaderData)}` }, + ]) + const headers = vi.fn(({ loaderData }) => ({ + 'x-report': String(loaderData), + })) + const rootRoute = new BaseRootRoute({}) + const reportRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/report', + ssr: 'data-only', + loader: () => 'server data', + head, + scripts, + headers, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([reportRoute]), + history: createMemoryHistory({ initialEntries: ['/report'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/report') + const match = router.state.matches.at(-1) + + expect(response.status).toBe(200) + expect(response.headers.get('x-report')).toBe('server data') + expect(match).toMatchObject({ + ssr: 'data-only', + meta: [{ title: 'report: server data' }], + scripts: [{ children: 'window.report = "server data"' }], + headers: { 'x-report': 'server data' }, + }) + expect(head).toHaveBeenCalledTimes(1) + expect(scripts).toHaveBeenCalledTimes(1) + expect(headers).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/router-core/tests/server-ssr-option-error.test.ts b/packages/router-core/tests/server-ssr-option-error.test.ts new file mode 100644 index 0000000000..f1dcf64e14 --- /dev/null +++ b/packages/router-core/tests/server-ssr-option-error.test.ts @@ -0,0 +1,178 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, redirect } from '../src' +import { createTestRouter, loadServerResponse } from './routerTestUtils' + +/** + * A functional ssr() option is user route code in the server serial phase. + * If it throws an ordinary error, the route must not be committed as a + * successful 200 response with missing loader data. It follows the same + * renderable route-error lifecycle as beforeLoad and loader failures. + */ +describe('server functional ssr() errors', () => { + test.each(['sync', 'async'] as const)( + 'commits a %s failure with route context and returns 500', + async (failureMode) => { + const boom = new Error('feature flag lookup failed') + const loader = vi.fn(() => 'reports data') + const onError = vi.fn() + const beforeLoad = vi.fn() + + const rootRoute = new BaseRootRoute({ + context: () => ({ rootContext: true }), + beforeLoad: () => ({ rootBeforeLoadContext: true }), + }) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + context: ({ context }) => ({ + reportsContext: context.rootBeforeLoadContext, + }), + ssr: () => { + if (failureMode === 'sync') { + throw boom + } + return Promise.reject(boom) + }, + beforeLoad, + loader, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/reports'] }), + isServer: true, + context: { routerContext: true }, + }) + + const response = await loadServerResponse(router, '/reports') + + const reportsMatch = router.state.matches.find( + (match) => match.routeId === reportsRoute.id, + ) + expect(loader).not.toHaveBeenCalled() + expect(beforeLoad).not.toHaveBeenCalled() + expect(response.status).toBe(500) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith(boom) + expect(reportsMatch).toMatchObject({ + status: 'error', + error: boom, + context: { + routerContext: true, + rootContext: true, + rootBeforeLoadContext: true, + reportsContext: true, + }, + }) + }, + ) + + test('retains inherited renderability and loads the lazy error boundary', async () => { + const boom = new Error('SSR policy failed') + const errorBoundaryPreload = vi.fn(() => Promise.resolve()) + const ErrorBoundary = Object.assign(() => null, { + preload: errorBoundaryPreload, + }) + const rootRoute = new BaseRootRoute({}) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + ssr: async () => { + await Promise.resolve() + throw boom + }, + }) + const lazy = vi.fn(() => + Promise.resolve({ + options: { + id: reportsRoute.id, + errorComponent: ErrorBoundary, + }, + }), + ) + reportsRoute.lazy(lazy as any) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/reports'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/reports') + + expect(response.status).toBe(500) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: reportsRoute.id, + status: 'error', + error: boom, + ssr: true, + }) + expect(lazy).toHaveBeenCalledTimes(1) + expect(errorBoundaryPreload).toHaveBeenCalledTimes(1) + }) + + test('does not run route context after an SSR policy redirect', async () => { + const context = vi.fn(() => ({ reportsContext: true })) + const loader = vi.fn(() => 'reports data') + const rootRoute = new BaseRootRoute({}) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + ssr: () => { + throw redirect({ to: '/target' }) + }, + context, + loader, + }) + const targetRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/target', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([reportsRoute, targetRoute]), + history: createMemoryHistory({ initialEntries: ['/reports'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/reports') + + expect(response.status).toBe(307) + expect(response.headers.get('Location')).toBe('/target') + expect(context).not.toHaveBeenCalled() + expect(loader).not.toHaveBeenCalled() + }) + + test('preserves the SSR policy failure when route context also throws', async () => { + const policyError = new Error('SSR policy failed') + const contextError = new Error('route context failed') + const onError = vi.fn() + const rootRoute = new BaseRootRoute({}) + const reportsRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + ssr: () => { + throw policyError + }, + context: () => { + throw contextError + }, + onError, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([reportsRoute]), + history: createMemoryHistory({ initialEntries: ['/reports'] }), + isServer: true, + }) + + const response = await loadServerResponse(router, '/reports') + + expect(response.status).toBe(500) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: reportsRoute.id, + status: 'error', + error: policyError, + }) + expect(onError).toHaveBeenCalledWith(policyError) + expect(onError).not.toHaveBeenCalledWith(contextError) + }) +}) diff --git a/packages/router-core/tests/ssr-match-id.bench.ts b/packages/router-core/tests/ssr-match-id.bench.ts new file mode 100644 index 0000000000..3f6a7c0a6e --- /dev/null +++ b/packages/router-core/tests/ssr-match-id.bench.ts @@ -0,0 +1,53 @@ +import { bench, describe, expect } from 'vitest' +import { dehydrateSsrMatchId, hydrateSsrMatchId } from '../src/ssr/ssr-match-id' + +const typicalIds = Array.from( + { length: 100 }, + (_, index) => + `/$orgId/projects/$projectId/acme/projects/project-${index}{"page":${index}}`, +) +const deepIds = Array.from( + { length: 100 }, + (_, index) => + `${Array.from({ length: 32 }, (__, depth) => `/route-${depth}`).join('')}/${index}`, +) +const reservedIds = Array.from( + { length: 100 }, + (_, index) => `~/\0/\uFFFD/~0/~r/${index}`, +) +const normalizedDeepIds = deepIds.map((id) => + dehydrateSsrMatchId(id).replaceAll('\0', '\uFFFD'), +) +let benchmarkSink = 0 + +for (const id of [...typicalIds, ...deepIds, ...reservedIds]) { + expect(hydrateSsrMatchId(dehydrateSsrMatchId(id))).toBe(id) +} + +describe('SSR match ID codec', () => { + bench('encode 100 typical match IDs', () => { + let size = 0 + for (const id of typicalIds) { + size += dehydrateSsrMatchId(id).length + } + benchmarkSink = size + }) + + bench('decode 100 normalized deep match IDs', () => { + let size = 0 + for (const id of normalizedDeepIds) { + size += hydrateSsrMatchId(id).length + } + benchmarkSink = size + }) + + bench('round-trip 100 reserved-character match IDs', () => { + let size = 0 + for (const id of reservedIds) { + size += hydrateSsrMatchId(dehydrateSsrMatchId(id)).length + } + benchmarkSink = size + }) +}) + +void benchmarkSink diff --git a/packages/router-core/tests/ssr-match-id.test.ts b/packages/router-core/tests/ssr-match-id.test.ts index 77399edb5a..65e1cc4ebb 100644 --- a/packages/router-core/tests/ssr-match-id.test.ts +++ b/packages/router-core/tests/ssr-match-id.test.ts @@ -20,7 +20,23 @@ describe('ssr match id codec', () => { expect(hydrateSsrMatchId(id)).toBe(id) }) + it('round-trips reserved and browser-normalized characters', () => { + const id = '~/\0/\uFFFD/~0/~r' + const dehydratedId = dehydrateSsrMatchId(id) + const normalize = (value: string) => value.replaceAll('\0', '\uFFFD') + + expect(hydrateSsrMatchId(normalize(dehydratedId))).toBe(id) + expect(normalize(dehydrateSsrMatchId('/r'))).not.toBe( + normalize(dehydrateSsrMatchId('\uFFFDr')), + ) + }) + it('decodes browser-normalized replacement chars back to slashes', () => { - expect(hydrateSsrMatchId('\uFFFDposts\uFFFD1')).toBe('/posts/1') + const normalized = dehydrateSsrMatchId('/posts/1').replaceAll( + '\0', + '\uFFFD', + ) + + expect(hydrateSsrMatchId(normalized)).toBe('/posts/1') }) }) diff --git a/packages/router-core/tests/ssr-server-cleanup.test.ts b/packages/router-core/tests/ssr-server-cleanup.test.ts index 0cc8832abd..a4d0b96588 100644 --- a/packages/router-core/tests/ssr-server-cleanup.test.ts +++ b/packages/router-core/tests/ssr-server-cleanup.test.ts @@ -1,8 +1,11 @@ import { createMemoryHistory } from '@tanstack/history' -import { describe, expect, test, vi } from 'vitest' +import { afterEach, describe, expect, onTestFinished, test, vi } from 'vitest' import { BaseRootRoute, BaseRoute } from '../src' import { createRequestHandler } from '../src/ssr/createRequestHandler' -import { createSsrStreamResponse } from '../src/ssr/handlerCallback' +import { + bindSsrResponseToRequest, + createSsrStreamResponse, +} from '../src/ssr/handlerCallback' import { attachRouterServerSsrUtils } from '../src/ssr/ssr-server' import { transformStreamWithRouter } from '../src/ssr/transformStreamWithRouter' import { createTestRouter } from './routerTestUtils' @@ -38,6 +41,10 @@ function deferred() { return { promise, resolve } } +afterEach(() => { + vi.restoreAllMocks() +}) + describe('serverSsr.cleanup', () => { test('onCleanup listeners run exactly once', () => { const router = buildRouter() @@ -338,6 +345,226 @@ describe('serverSsr.cleanup', () => { expect(router.serverSsr).toBeUndefined() }) + test('request abort settles while the render callback is still pending', async () => { + const router = buildRouter() + const requestController = new AbortController() + const renderStarted = deferred() + const renderResult = deferred>() + let cleanupCalls = 0 + let cancelCalls = 0 + let lateStreamResponse!: ReturnType + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/', { + signal: requestController.signal, + }), + }) + + const response = handler(({ router: requestRouter }) => { + const serverSsr = requestRouter.serverSsr! + const cleanup = serverSsr.cleanup + serverSsr.cleanup = () => { + cleanupCalls++ + cleanup() + } + lateStreamResponse = createSsrStreamResponse( + requestRouter, + new Response( + new ReadableStream({ + cancel() { + cancelCalls++ + return new Promise(() => {}) + }, + }), + ), + ) + renderStarted.resolve() + return renderResult.promise + }) + + await renderStarted.promise + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + expect(cleanupCalls).toBe(1) + expect(router.serverSsr).toBeUndefined() + + renderResult.resolve(lateStreamResponse) + await Promise.resolve() + await Promise.resolve() + expect(cleanupCalls).toBe(1) + expect(cancelCalls).toBe(1) + expect(router.serverSsr).toBeUndefined() + }) + + test('request abort cancels a plain response resolved by the callback later', async () => { + const router = buildRouter() + const requestController = new AbortController() + const callbackStarted = deferred() + const callbackResult = deferred() + const cancel = vi.fn((_reason: unknown) => new Promise(() => {})) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/', { + signal: requestController.signal, + }), + }) + + const response = handler(() => { + callbackStarted.resolve() + return callbackResult.promise + }) + + await callbackStarted.promise + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + + await expect(response).rejects.toBe(cancellation) + callbackResult.resolve(new Response(new ReadableStream({ cancel }))) + await vi.waitFor(() => { + expect(cancel).toHaveBeenCalledTimes(1) + expect(cancel).toHaveBeenCalledWith(cancellation) + }) + }) + + test.each(['throw', 'reject'] as const)( + 'reports a %s from disposal of a late render response', + async (failureMode) => { + const router = buildRouter() + const requestController = new AbortController() + const renderStarted = deferred() + const renderResult = deferred() + const cleanupError = new Error('late stream cleanup failed') + const dispose = vi.fn(() => { + if (failureMode === 'throw') { + throw cleanupError + } + return Promise.reject(cleanupError) + }) + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined) + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/', { + signal: requestController.signal, + }), + }) + onTestFinished(() => { + consoleError.mockRestore() + }) + onTestFinished(() => { + router.serverSsr?.cleanup() + }) + + const response = handler(() => { + renderStarted.resolve() + return renderResult.promise + }) + + await renderStarted.promise + const cancellation = new Error('request disconnected') + requestController.abort(cancellation) + await expect(response).rejects.toBe(cancellation) + + renderResult.resolve({ + response: new Response('stream'), + serverSsrCleanup: 'stream', + dispose, + }) + await vi.waitFor(() => { + expect(consoleError).toHaveBeenCalledWith(cleanupError) + }) + expect(dispose).toHaveBeenCalledOnce() + }, + ) + + test('request abort disposes a stream after response handoff', async () => { + const router = buildRouter() + const requestController = new AbortController() + let cleanupCalls = 0 + let cancelCalls = 0 + const handler = createRequestHandler({ + createRouter: () => router, + request: new Request('http://localhost/', { + signal: requestController.signal, + }), + }) + + const response = await handler(({ router: requestRouter }) => { + const serverSsr = requestRouter.serverSsr! + const cleanup = serverSsr.cleanup + serverSsr.cleanup = () => { + cleanupCalls++ + cleanup() + } + return createSsrStreamResponse( + requestRouter, + new Response( + new ReadableStream({ + cancel() { + cancelCalls++ + return new Promise(() => {}) + }, + }), + ), + ) + }) + + expect(response.body).not.toBeNull() + expect(cleanupCalls).toBe(0) + requestController.abort(new Error('request disconnected')) + await Promise.resolve() + + expect(cleanupCalls).toBe(1) + expect(cancelCalls).toBe(1) + expect(router.serverSsr).toBeUndefined() + }) + + test.each(['throw', 'reject'] as const)( + 'reports a custom stream disposal %s after request abort', + async (failureMode) => { + const router = buildRouter() + attachRouterServerSsrUtils({ router, manifest: undefined }) + let cleanupCalls = 0 + const cleanup = router.serverSsr!.cleanup + router.serverSsr!.cleanup = () => { + cleanupCalls++ + cleanup() + } + const cleanupError = new Error('custom stream cleanup failed') + const dispose = vi.fn(() => { + if (failureMode === 'throw') { + throw cleanupError + } + return Promise.reject(cleanupError) + }) + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined) + const requestController = new AbortController() + + bindSsrResponseToRequest( + router, + { + response: new Response('stream'), + serverSsrCleanup: 'stream', + dispose, + }, + requestController.signal, + ) + requestController.abort(new Error('request disconnected')) + + await vi.waitFor(() => { + expect(consoleError).toHaveBeenCalledWith(cleanupError) + }) + expect(dispose).toHaveBeenCalledOnce() + expect(cleanupCalls).toBe(1) + expect(router.serverSsr).toBeUndefined() + }, + ) + test('request handler defers cleanup for stream response metadata', async () => { const router = buildRouter() let cleanupCalls = 0 diff --git a/packages/router-core/tests/ssr-server-manifest.test.ts b/packages/router-core/tests/ssr-server-manifest.test.ts index f76535622f..6cdc98a87b 100644 --- a/packages/router-core/tests/ssr-server-manifest.test.ts +++ b/packages/router-core/tests/ssr-server-manifest.test.ts @@ -401,4 +401,56 @@ describe('attachRouterServerSsrUtils manifest dehydration', () => { '/assets/index.js', ]) }) + + test('omits descendant assets past a terminal parent boundary', async () => { + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/parent', + loader: () => { + throw new Error('parent failed') + }, + }) + const childRoute = new BaseRoute({ + getParentRoute: () => parentRoute, + path: '/child', + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([parentRoute.addChildren([childRoute])]), + history: createMemoryHistory({ initialEntries: ['/parent/child'] }), + isServer: true, + }) + const manifest: ServerManifest = { + inlineCss: { + styles: { + '/assets/root.css': '.root{}', + '/assets/parent.css': '.parent{}', + '/assets/child.css': '.child{}', + }, + }, + routes: { + [rootRoute.id]: { css: ['/assets/root.css'] }, + [parentRoute.id]: { css: ['/assets/parent.css'] }, + [childRoute.id]: { + css: ['/assets/child.css'], + preloads: ['/assets/child.js'], + }, + }, + } + + attachRouterServerSsrUtils({ router, manifest }) + await router.load() + + expect(router.state.matches).toHaveLength(3) + expect(router.ssr!.manifest?.inlineStyle?.children).toBe('.root{}.parent{}') + + await router.serverSsr!.dehydrate() + const script = router.serverSsr!.takeBufferedScripts() + expect(script?.children).toBeTruthy() + const dehydratedManifest = parseSerializedRouter( + script!.children!, + ).manifest! + + expect(dehydratedManifest.routes[childRoute.id]).toBeUndefined() + }) }) diff --git a/packages/router-core/tests/stay-match-abort.test.ts b/packages/router-core/tests/stay-match-abort.test.ts new file mode 100644 index 0000000000..a389d2be76 --- /dev/null +++ b/packages/router-core/tests/stay-match-abort.test.ts @@ -0,0 +1,247 @@ +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { + BaseRootRoute, + BaseRoute, + createControlledPromise, + notFound, +} from '../src' +import { createTestRouter } from './routerTestUtils' + +afterEach(() => { + vi.restoreAllMocks() +}) + +/** + * A settled success stay-match must keep its abortController un-aborted + * across navigations and invalidations: loaders can hand that signal to + * still-streaming deferred data (fetch(url, { signal }) consumed via + * ), and the documented contract only cancels the signal when the + * route unloads or the loader call becomes outdated. Only pending or + * actively-fetching matches are cancelled at load start. + */ + +function setup(reloadGate?: Promise) { + let capturedSignal: AbortSignal | undefined + let deferredRequestAbortCount = 0 + let loaderCalls = 0 + + const rootRoute = new BaseRootRoute({}) + const dashboardRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/dashboard', + staleTime: Infinity, + loader: ({ abortController }: { abortController: AbortController }) => { + loaderCalls++ + capturedSignal = abortController.signal + + // A common streaming pattern is to return immediately-available data + // alongside a request consumed later by . That request must stay + // alive while this layout is reused, then be cancelled when it unloads. + const deferredRequest = new Promise<'aborted'>((resolve) => { + abortController.signal.addEventListener( + 'abort', + () => { + deferredRequestAbortCount++ + resolve('aborted') + }, + { once: true }, + ) + }) + + const data = { critical: 'dashboard data', deferredRequest } + return reloadGate && loaderCalls > 1 ? reloadGate.then(() => data) : data + }, + }) + const settingsRoute = new BaseRoute({ + getParentRoute: () => dashboardRoute, + path: '/settings', + }) + const loginRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/login', + }) + + const router = createTestRouter({ + routeTree: rootRoute.addChildren([ + dashboardRoute.addChildren([settingsRoute]), + loginRoute, + ]), + history: createMemoryHistory({ initialEntries: ['/dashboard'] }), + }) + + return { + router, + dashboardRoute, + getSignal: () => capturedSignal, + getDeferredRequestAbortCount: () => deferredRequestAbortCount, + } +} + +describe('stay-match abort scope', () => { + test('unloading a reused match aborts the signal exposed to its loader', async () => { + const { router, getSignal, getDeferredRequestAbortCount } = setup() + + await router.load() + const dashboardLoaderSignal = getSignal() + + // Reusing the dashboard layout for a child must preserve the lifetime of + // its loader's deferred request. + await router.navigate({ to: '/dashboard/settings' }) + expect(dashboardLoaderSignal?.aborted).toBe(false) + expect(getDeferredRequestAbortCount()).toBe(0) + + // Leaving the layout altogether must now cancel that same request. + await router.navigate({ to: '/login' }) + expect(dashboardLoaderSignal?.aborted).toBe(true) + expect(getDeferredRequestAbortCount()).toBe(1) + }) + + test('background invalidation keeps the old loader signal until fresh data commits', async () => { + const reloadGate = createControlledPromise() + const { router, dashboardRoute, getSignal } = setup(reloadGate) + + await router.load() + const firstSignal = getSignal() + expect(firstSignal?.aborted).toBe(false) + + const invalidation = router.invalidate() + await vi.waitFor(() => expect(getSignal()).not.toBe(firstSignal)) + const replacementSignal = getSignal() + + // The old loader data is still the published generation while the + // background replacement is private. + expect(firstSignal?.aborted).toBe(false) + expect(replacementSignal?.aborted).toBe(false) + + reloadGate.resolve() + await invalidation + await vi.waitFor(() => expect(firstSignal?.aborted).toBe(true)) + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: dashboardRoute.id, + status: 'success', + }) + expect(replacementSignal?.aborted).toBe(false) + }) + + test.each(['beforeLoad', 'shouldReload'] as const)( + '%s failure replacing a successful stay aborts its loader signal', + async (failureStage) => { + const failure = new Error(`${failureStage} failed`) + const onAbort = vi.fn() + let shouldFail = false + let loaderSignal: AbortSignal | undefined + + const rootRoute = new BaseRootRoute({}) + const accountRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/account', + staleTime: Infinity, + beforeLoad: () => { + if (shouldFail && failureStage === 'beforeLoad') { + throw failure + } + }, + shouldReload: () => { + if (shouldFail && failureStage === 'shouldReload') { + throw failure + } + return false + }, + loader: ({ abortController }: { abortController: AbortController }) => { + loaderSignal = abortController.signal + loaderSignal.addEventListener('abort', onAbort, { once: true }) + return { user: 'Flo' } + }, + errorComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([accountRoute]), + history: createMemoryHistory({ initialEntries: ['/account'] }), + }) + + await router.load() + expect(loaderSignal?.aborted).toBe(false) + + shouldFail = true + await router.load() + + const match = router.state.matches.find( + (candidate) => candidate.routeId === accountRoute.id, + ) + expect(match?.status).toBe('error') + expect(match?.error).toBe(failure) + expect(loaderSignal?.aborted).toBe(true) + expect(onAbort).toHaveBeenCalledTimes(1) + }, + ) + + test('background notFound replacing successful data aborts its old loader signal', async () => { + let loaderCalls = 0 + let initialSignal: AbortSignal | undefined + + const rootRoute = new BaseRootRoute({}) + const accountRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/account', + staleTime: Infinity, + loader: ({ abortController }: { abortController: AbortController }) => { + loaderCalls++ + if (loaderCalls === 1) { + initialSignal = abortController.signal + return { user: 'Flo' } + } + throw notFound() + }, + notFoundComponent: () => null, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([accountRoute]), + history: createMemoryHistory({ initialEntries: ['/account'] }), + }) + + await router.load() + expect(initialSignal?.aborted).toBe(false) + + await router.invalidate() + await vi.waitFor(() => + expect(router.state.matches.at(-1)?.status).toBe('notFound'), + ) + expect(initialSignal?.aborted).toBe(true) + }) + + test('decorative background asset failure still transfers loader signal ownership', async () => { + vi.spyOn(console, 'error').mockImplementation(() => undefined) + let loaderCalls = 0 + const loaderSignals: Array = [] + + const rootRoute = new BaseRootRoute({}) + const accountRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/account', + staleTime: Infinity, + loader: ({ abortController }: { abortController: AbortController }) => { + loaderSignals.push(abortController.signal) + return { revision: ++loaderCalls } + }, + head: ({ loaderData }) => { + if (loaderData?.revision === 2) { + throw new Error('fresh head failed') + } + return { meta: [{ title: 'account' }] } + }, + }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([accountRoute]), + history: createMemoryHistory({ initialEntries: ['/account'] }), + }) + + await router.load() + await router.invalidate() + + expect(router.state.matches.at(-1)?.loaderData).toEqual({ revision: 2 }) + expect(loaderSignals).toHaveLength(2) + expect(loaderSignals[0]?.aborted).toBe(true) + expect(loaderSignals[1]?.aborted).toBe(false) + }) +}) diff --git a/packages/router-core/tests/superseded-load-await.test.ts b/packages/router-core/tests/superseded-load-await.test.ts new file mode 100644 index 0000000000..38fe3115c4 --- /dev/null +++ b/packages/router-core/tests/superseded-load-await.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test, vi } from 'vitest' +import { createMemoryHistory } from '@tanstack/history' +import { BaseRootRoute, BaseRoute, createControlledPromise } from '../src' +import { createTestRouter } from './routerTestUtils' + +/** + * Awaiting router.load() should remain a reliable synchronization point even + * when that load is superseded. If the abandoned load resolves immediately, + * callers can observe router state before the newer load has settled. + * + * This test starts a real public router.load() for /one, supersedes it with a + * real navigation to /two, and resolves only the abandoned /one loader. The + * original await should not settle until the superseding /two loader settles. + */ + +const waitForMacrotask = () => + new Promise((resolve) => { + setTimeout(resolve, 0) + }) + +describe('superseded load await', () => { + test('router.load waits for a superseding navigation load to settle', async () => { + const firstLoaderGate = createControlledPromise() + const secondLoaderGate = createControlledPromise() + let firstLoaderCalls = 0 + let secondLoaderCalls = 0 + let secondLoaderSettled = false + let supersededLoadSettled = false + + const rootRoute = new BaseRootRoute({}) + const indexRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/', + }) + const firstRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/one', + loader: async () => { + firstLoaderCalls++ + await firstLoaderGate + return 'one' + }, + }) + const secondRoute = new BaseRoute({ + getParentRoute: () => rootRoute, + path: '/two', + loader: async () => { + secondLoaderCalls++ + await secondLoaderGate + secondLoaderSettled = true + return 'two' + }, + }) + + const history = createMemoryHistory({ initialEntries: ['/'] }) + const router = createTestRouter({ + routeTree: rootRoute.addChildren([indexRoute, firstRoute, secondRoute]), + history, + }) + + await router.load() + + history.push('/one') + const supersededLoad = router.load().then(() => { + supersededLoadSettled = true + }) + await vi.waitFor(() => expect(firstLoaderCalls).toBe(1)) + + const latestNavigation = router.navigate({ to: '/two' }) + await vi.waitFor(() => expect(secondLoaderCalls).toBe(1)) + + firstLoaderGate.resolve() + await waitForMacrotask() + + expect(secondLoaderSettled).toBe(false) + expect(supersededLoadSettled).toBe(false) + + secondLoaderGate.resolve() + await Promise.all([supersededLoad, latestNavigation]) + + expect(secondLoaderSettled).toBe(true) + expect(supersededLoadSettled).toBe(true) + expect(router.state.location.pathname).toBe('/two') + expect(router.state.matches.at(-1)).toMatchObject({ + routeId: secondRoute.id, + status: 'success', + loaderData: 'two', + }) + }) +}) diff --git a/packages/router-core/tests/transformStreamWithRouter.test.ts b/packages/router-core/tests/transformStreamWithRouter.test.ts index 13b0072e24..903efeb0c9 100644 --- a/packages/router-core/tests/transformStreamWithRouter.test.ts +++ b/packages/router-core/tests/transformStreamWithRouter.test.ts @@ -436,6 +436,38 @@ describe('transformStreamWithRouter — real SSR scripts', () => { }) describe('transformStreamWithRouter — cleanup side-effects', () => { + test.each([ + ['fast', true], + ['main', false], + ] as const)( + 'request abort cleans the %s transform without consuming its response body', + async (_path, fastPath) => { + const { router, cleanupCalls } = makeRouter({ + isSerializationFinished: () => fastPath, + reserveStreamFastPath: () => fastPath, + takeBufferedHtml: () => undefined, + }) + const upstream = makeManualUpstream() + const request = new AbortController() + + const responseBody = transformStreamWithRouter( + router as any, + upstream.stream as any, + { signal: request.signal }, + ) + + expect(responseBody.locked).toBe(false) + + const reason = new Error('request-gone') + request.abort(reason) + await flush() + + expect(responseBody.locked).toBe(false) + expect(upstream.cancelled).toEqual({ value: true, reason }) + expect(cleanupCalls.count).toBe(1) + }, + ) + test('downstream cancel propagates upstream and calls serverSsr.cleanup once', async () => { // Fast path: simpler, no scanner. Verifies cancel + cleanup contract. const { router, cleanupCalls } = makeRouter({ diff --git a/packages/router-devtools-core/CHANGELOG.md b/packages/router-devtools-core/CHANGELOG.md index 485adc437f..be1a138356 100644 --- a/packages/router-devtools-core/CHANGELOG.md +++ b/packages/router-devtools-core/CHANGELOG.md @@ -1,5 +1,38 @@ # @tanstack/router-devtools-core +## 1.168.1 + +### Patch Changes + +- [#7805](https://github.com/TanStack/router/pull/7805) [`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd) - Rewrite match loading around a lane-based scheduler that tracks each navigation, preload, and background reload as an ordered unit of work. This fixes pending/redirect/retry state leaking between overlapping navigations, restores correct SSR status codes for redirects, errors, and not-found responses, and closes hydration gaps where the client re-ran work the server had already completed. + - Invalidation now retires matching active preloads so older speculative loader results cannot become fresh cache data after invalidation. + - Route `headers()` now only runs on the server, matching the documented behavior — it is no longer invoked during client-side asset projection. + - The documented default `gcTime` and `preloadGcTime` now match the existing runtime default of 5 minutes (`300_000`). + + **Removed / changed exported internals** + - `RouterState` no longer includes `loadedAt`, `isTransitioning`, `statusCode`, or `redirect`. Use `match.updatedAt` in place of `loadedAt`; subscribe to `router.state.status` / `router.state.isLoading` in place of `isTransitioning`; server response status and redirect handling are now internal to the server loader and are no longer exposed on `router.state`. + - `RouteMatch.fetchCount` has been removed, with no replacement — it was purely informational. + - `RouteMatch.status` no longer includes `'redirected'` (it remains `'pending' | 'success' | 'error' | 'notFound'`) — redirected matches are dropped from the match list instead of being rendered. + - `RouteMatch.globalNotFound` has been renamed and privatized to the internal `_notFound` field. Use `match.status === 'notFound'` instead. + - The exported React, Solid, and Vue `Match` components now accept `routeId` instead of `matchId`. + - The exported `RouterStores` adapter contract now uses route-keyed presentation stores: `matchesId` is replaced by `ids`, `matchStores` by `byRoute`, and `getRouteMatchStore()` by `getMatchStore()`. The separate `loadedAt`, `isLoading`, `isTransitioning`, `statusCode`, and `redirect` stores have been removed, along with the pending/cache stores and their setters. `StoreConfig.init` has also been removed. Read application-facing state from `router.state`; preload and cache coordination are now internal. + - Removed `RouterCore` members `getMatch()`, `updateMatch()`, `cancelMatch()`, and `cancelMatches()` — read matches from `router.state.matches` (e.g. `router.state.matches.find((m) => m.id === id)`); there is no replacement for mutating or cancelling an individual in-flight match from outside the router. + - Removed `RouterCore.hasNotFoundMatch()` — use `router.state.matches.some((m) => m.status === 'notFound')`. + - Removed `RouterCore.looseRoutesById` — use `routesById`. + - Removed `RouterCore.isPrerendering()`, `RouterCore.isViewTransitionTypesSupported`, and `RouterCore.viewTransitionPromise`, with no replacement. + - Removed `RouterCore.getParsedLocationHref()` and `RouterCore.clearExpiredCache()`, with no replacement — expired cache entries are now reconciled automatically as part of match commit. + - Removed `RouterCore.latestLoadPromise` and `RouterCore.beforeLoad()`, with no replacement. + - `RouterCore.commitLocationPromise` and `RouterCore.pendingBuiltLocation` have been replaced by the internal `_commitPromise` and `_pendingLocation` fields. + - Removed the exported `GetMatchFn` and `UpdateMatchFn` types, along with the methods they typed. + - Removed the standalone `getMatchedRoutes()` export from `@tanstack/router-core` — use the `router.getMatchedRoutes()` instance method instead. + - `RouterCore.loadRouteChunk()` no longer accepts an array of component types as its second argument. One-argument usage is unchanged; the optional second argument is now `'errorComponent'`, `'notFoundComponent'`, or `false` for internal boundary loading. + - Removed `Redirect.redirectHandled`, which was internal redirect bookkeeping. + - `MatchRoutesOpts.preload` and `MatchRoutesOpts.dest` have been removed. + - `StartTransitionFn` is now `(fn, expected) => Promise` (previously `(fn) => void`). This only affects custom framework adapters that implement `startTransition`. + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + ## 1.168.0 ### Minor Changes diff --git a/packages/router-devtools-core/package.json b/packages/router-devtools-core/package.json index f6497adc2e..127b7715b3 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.168.0", + "version": "1.168.1", "description": "Modern and scalable routing for Web applications", "author": "Tanner Linsley", "license": "MIT", @@ -25,14 +25,15 @@ ], "scripts": { "clean": "rimraf ./dist && rimraf ./coverage", - "test:eslint": "eslint ./src", + "test:eslint": "eslint ./src ./tests", + "test:unit": "vitest", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, @@ -66,6 +67,7 @@ "goober": "^2.1.16" }, "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", "solid-js": "1.9.12", "vite": "*", "vite-plugin-solid": "^2.11.10" diff --git a/packages/router-devtools-core/src/AgeTicker.tsx b/packages/router-devtools-core/src/AgeTicker.tsx index 27d179fda1..5a615be54f 100644 --- a/packages/router-devtools-core/src/AgeTicker.tsx +++ b/packages/router-devtools-core/src/AgeTicker.tsx @@ -1,6 +1,6 @@ import { clsx as cx } from 'clsx' import { useStyles } from './useStyles' -import type { AnyRouteMatch, AnyRouter } from '@tanstack/router-core' +import type { AnyRoute, AnyRouteMatch, AnyRouter } from '@tanstack/router-core' import type { Accessor } from 'solid-js' function formatTime(ms: number) { @@ -35,7 +35,9 @@ export function AgeTicker({ return null } - const route = router().looseRoutesById[match.routeId]! + const route = (router().routesById as Record)[ + match.routeId + ]! if (!route.options.loader) { return null @@ -45,7 +47,7 @@ export function AgeTicker({ const staleTime = route.options.staleTime ?? router().options.defaultStaleTime ?? 0 const gcTime = - route.options.gcTime ?? router().options.defaultGcTime ?? 30 * 60 * 1000 + route.options.gcTime ?? router().options.defaultGcTime ?? 300_000 return (
staleTime))}> diff --git a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx index 79566656b0..966eb32bc5 100644 --- a/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx +++ b/packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx @@ -282,46 +282,40 @@ export const BaseTanStackRouterDevtoolsPanel = const [history, setHistory] = createSignal>([]) const [hasHistoryOverflowed, setHasHistoryOverflowed] = createSignal(false) - let pendingMatches: Accessor> - let cachedMatches: Accessor> - // subscribable implementation - if ('subscribe' in router().stores.pendingMatches) { - const [_pendingMatches, setPending] = createSignal>( - [], - ) - pendingMatches = _pendingMatches - - const [_cachedMatches, setCached] = createSignal>([]) - cachedMatches = _cachedMatches - - type Subscribe = (fn: () => void) => { unsubscribe: () => void } - createEffect(() => { - const pendingMatchesStore = router().stores.pendingMatches - setPending(pendingMatchesStore.get()) - const subscription = ( - (pendingMatchesStore as any).subscribe as Subscribe - )(() => { - setPending(pendingMatchesStore.get()) - }) - onCleanup(() => subscription.unsubscribe()) - }) - - createEffect(() => { - const cachedMatchesStore = router().stores.cachedMatches - setCached(cachedMatchesStore.get()) - const subscription = ( - (cachedMatchesStore as any).subscribe as Subscribe - )(() => { - setCached(cachedMatchesStore.get()) - }) - onCleanup(() => subscription.unsubscribe()) - }) - } - // signal implementation - else { - pendingMatches = () => router().stores.pendingMatches.get() - cachedMatches = () => router().stores.cachedMatches.get() + const pendingMatches = () => { + const matches = routerState().matches + return matches.some((match: AnyRouteMatch) => match.status === 'pending') + ? matches + : [] } + let cache = router()._cache + let cacheMatches = [...cache.values()] + const [cachedMatches, setCachedMatches] = createSignal(cacheMatches) + + createEffect(() => { + const refreshCache = () => { + const next = router()._cache + let valuesChanged = next.size !== cacheMatches.length + if (!valuesChanged) { + let index = 0 + for (const match of next.values()) { + if (match !== cacheMatches[index++]) { + valuesChanged = true + break + } + } + } + + if (next !== cache || valuesChanged) { + cache = next + cacheMatches = [...next.values()] + setCachedMatches(cacheMatches) + } + } + refreshCache() + const interval = setInterval(refreshCache, 500) + onCleanup(() => clearInterval(interval)) + }) createEffect(() => { const matches = routerState().matches @@ -392,7 +386,6 @@ export const BaseTanStackRouterDevtoolsPanel = 'stores', 'basepath', 'subscribers', - 'latestLoadPromise', '_scroll', 'tempLocationKey', 'latestLocation', @@ -719,7 +712,9 @@ export const BaseTanStackRouterDevtoolsPanel =
State:
- {pendingMatches().find((d) => d.id === activeMatch()?.id) + {pendingMatches().find( + (d: AnyRouteMatch) => d.id === activeMatch()?.id, + ) ? 'Pending' : routerState().matches.find( (d: any) => d.id === activeMatch()?.id, diff --git a/packages/router-devtools-core/src/useStyles.tsx b/packages/router-devtools-core/src/useStyles.tsx index 5524ed0ae8..32908c524d 100644 --- a/packages/router-devtools-core/src/useStyles.tsx +++ b/packages/router-devtools-core/src/useStyles.tsx @@ -428,7 +428,7 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => { line-height: ${tokens.font.lineHeight.sm}; `, matchStatus: ( - status: 'pending' | 'success' | 'error' | 'notFound' | 'redirected', + status: 'pending' | 'success' | 'error' | 'notFound', isFetching: false | 'beforeLoad' | 'loader', ) => { const colorMap = { @@ -436,7 +436,6 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => { success: 'green', error: 'red', notFound: 'purple', - redirected: 'gray', } as const const color = diff --git a/packages/router-devtools-core/src/utils.tsx b/packages/router-devtools-core/src/utils.tsx index c14bfd80be..962cdaf0f3 100644 --- a/packages/router-devtools-core/src/utils.tsx +++ b/packages/router-devtools-core/src/utils.tsx @@ -25,7 +25,6 @@ export function getStatusColor(match: AnyRouteMatch) { success: 'green', error: 'red', notFound: 'purple', - redirected: 'gray', } as const return match.isFetching && match.status === 'success' diff --git a/packages/router-devtools-core/tests/cache-replacement.test.ts b/packages/router-devtools-core/tests/cache-replacement.test.ts new file mode 100644 index 0000000000..e44e1cce53 --- /dev/null +++ b/packages/router-devtools-core/tests/cache-replacement.test.ts @@ -0,0 +1,105 @@ +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' +import { TanStackRouterDevtoolsPanelCore } from '../src/TanStackRouterDevtoolsPanelCore' +import type { AnyRouteMatch, AnyRouter } from '@tanstack/router-core' + +function createCachedMatch(loaderData: string): AnyRouteMatch { + return { + id: 'cached-match', + routeId: '/cached', + pathname: '/cached', + params: {}, + search: {}, + status: 'success', + isFetching: false, + updatedAt: Date.now(), + loaderData, + } as AnyRouteMatch +} + +describe('cached matches', () => { + let panel: TanStackRouterDevtoolsPanelCore | undefined + + // Warm Vite's lazy transform of the panel chunk outside any test so its + // cost never counts against a test's timeout budget on slow CI runners. + beforeAll(async () => { + await import('../src/BaseTanStackRouterDevtoolsPanel') + }, 30_000) + + afterEach(() => { + panel?.unmount() + panel = undefined + document.body.innerHTML = '' + try { + window.localStorage.clear() + } catch {} + vi.useRealTimers() + }) + + it('uses the default gc time and refreshes replaced cache entries', async () => { + vi.useFakeTimers() + + const route = { + id: '/cached', + path: 'cached', + fullPath: '/cached', + rank: 0, + children: [], + options: { loader: () => undefined }, + } + const routeTree = { + id: '__root__', + path: '/', + fullPath: '/', + rank: 0, + children: [route], + options: {}, + } + const cache = new Map([['cached-match', createCachedMatch('old data')]]) + const router = { + _cache: cache, + routeTree, + routesById: { + __root__: routeTree, + '/cached': route, + }, + options: {}, + navigate: vi.fn(), + } as unknown as AnyRouter + const routerState = { + location: { + href: '/', + pathname: '/', + search: {}, + searchStr: '', + hash: '', + }, + matches: [], + } + const container = document.createElement('div') + document.body.append(container) + + panel = new TanStackRouterDevtoolsPanelCore({ router, routerState }) + panel.mount(container) + + await vi.waitFor(() => { + expect( + container.querySelector( + '[aria-label="Open match details for cached-match"]', + ), + ).not.toBeNull() + }) + expect(container.textContent).toContain('5min') + + const cachedMatch = container.querySelector( + '[aria-label="Open match details for cached-match"]', + ) as HTMLElement + cachedMatch.click() + expect(container.textContent).toContain('old data') + + cache.set('cached-match', createCachedMatch('new data')) + await vi.advanceTimersByTimeAsync(500) + + expect(container.textContent).toContain('new data') + expect(container.textContent).not.toContain('old data') + }, 10_000) +}) diff --git a/packages/router-devtools-core/tsconfig.json b/packages/router-devtools-core/tsconfig.json index 1b802b64d1..e24672b5de 100644 --- a/packages/router-devtools-core/tsconfig.json +++ b/packages/router-devtools-core/tsconfig.json @@ -4,5 +4,5 @@ "jsx": "preserve", "jsxImportSource": "solid-js" }, - "include": ["src", "vite.config.ts"] + "include": ["src", "tests", "vite.config.ts"] } diff --git a/packages/router-devtools-core/vite.config.ts b/packages/router-devtools-core/vite.config.ts index 53da7f8839..b14dd2bdf5 100644 --- a/packages/router-devtools-core/vite.config.ts +++ b/packages/router-devtools-core/vite.config.ts @@ -1,9 +1,23 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackViteConfig } from '@tanstack/vite-config' import solid from 'vite-plugin-solid' +import packageJson from './package.json' const config = defineConfig({ plugins: [solid()], + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'jsdom', + typecheck: { enabled: true }, + setupFiles: [], + server: { + deps: { + inline: [/solid-js/], + }, + }, + }, }) const merged = mergeConfig( diff --git a/packages/router-devtools/CHANGELOG.md b/packages/router-devtools/CHANGELOG.md index 38a9a2be35..445e7578dc 100644 --- a/packages/router-devtools/CHANGELOG.md +++ b/packages/router-devtools/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/router-devtools +## 1.167.1 + +### Patch Changes + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/react-router@1.170.19 + - @tanstack/react-router-devtools@1.167.1 + ## 1.167.0 ### Minor Changes diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index b6a2a3d437..484b722638 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.167.0", + "version": "1.167.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, diff --git a/packages/router-generator/CHANGELOG.md b/packages/router-generator/CHANGELOG.md index cf16585672..fd9dc5dcea 100644 --- a/packages/router-generator/CHANGELOG.md +++ b/packages/router-generator/CHANGELOG.md @@ -1,5 +1,32 @@ # @tanstack/router-generator +## 1.167.22 + +### Patch Changes + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + +## 1.167.21 + +### Patch Changes + +- [#7845](https://github.com/TanStack/router/pull/7845) [`78dd1a6`](https://github.com/TanStack/router/commit/78dd1a645dc1a9e9f4f649d9aff12005044d4fcc) - Preserve dots in explicit virtual route paths and pathless layout IDs instead + of treating them as flat-file route separators. + +## 1.167.20 + +### Patch Changes + +- [#7751](https://github.com/TanStack/router/pull/7751) [`e56a677`](https://github.com/TanStack/router/commit/e56a67742da9021b009b8db0cdc8bfe99878c25b) - Make `buildRouteTree` route ordering deterministic. Its final sort tiebreaker compared whole route-node objects (`(d) => d`), which coerce to `"[object Object]"` and so never order — an inconsistent comparator that left routes tying on segment-count/index-token in an engine- and input-dependent order, producing non-deterministic `routeTree.gen.ts` diffs across machines. The tiebreaker now compares `routePath`, mirroring the pre-sort and giving a stable total order. + +## 1.167.19 + +### Patch Changes + +- Updated dependencies [[`e2dd204`](https://github.com/TanStack/router/commit/e2dd2049cb42eb219d3b447b8605066d19d9c1fa)]: + - @tanstack/router-core@1.171.15 + ## 1.167.18 ### Patch Changes diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json index 8abf6f6d0b..492c8cc726 100644 --- a/packages/router-generator/package.json +++ b/packages/router-generator/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-generator", - "version": "1.167.18", + "version": "1.167.22", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -28,12 +28,12 @@ "clean:snapshots": "rimraf **/*snapshot* --glob", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js -p tsconfig.legacy.json", - "test:types:ts60": "tsc -p tsconfig.legacy.json", + "test:types:ts60": "tsc6 -p tsconfig.legacy.json", + "test:types:ts70": "tsc -p tsconfig.legacy.json", "test:unit": "vitest --typecheck false && vitest run --typecheck.only", "test:unit:dev": "pnpm run test:unit --watch --hideSkippedTests", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", diff --git a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts index 509b1be321..5d3cd610be 100644 --- a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts @@ -3,7 +3,7 @@ import { cleanPath, createLiteralRoutePathSegmentMetadata, createRoutePathSegmentMetadata, - determineInitialRoutePath, + determineInitialRoutePathFromExplicitPath, joinRoutePathSegmentMetadata, removeExt, removeLeadingSlash, @@ -156,7 +156,9 @@ export async function getRouteNodesRecursive( routePath: routePathPrefix, originalRoutePath: originalRoutePathPrefix, } = node.pathPrefix - ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix)) + ? determineInitialRoutePathFromExplicitPath( + removeLeadingSlash(node.pathPrefix), + ) : { routePath: '', originalRoutePath: '' } const { routeNodes, physicalDirectories } = await getRouteNodesPhysical( { @@ -240,7 +242,9 @@ export async function getRouteNodesRecursive( const { routePath: escapedSegment, originalRoutePath: originalSegment, - } = determineInitialRoutePath(removeLeadingSlash(lastSegment)) + } = determineInitialRoutePathFromExplicitPath( + removeLeadingSlash(lastSegment), + ) const routePath = `${parentRoutePath}${escapedSegment}` const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}` const routePathSegmentMetadata = @@ -305,7 +309,9 @@ export async function getRouteNodesRecursive( const { routePath: escapedSegment, originalRoutePath: originalSegment, - } = determineInitialRoutePath(removeLeadingSlash(lastSegment)) + } = determineInitialRoutePathFromExplicitPath( + removeLeadingSlash(lastSegment), + ) const routePath = `${parentRoutePath}${escapedSegment}` // Store the original path with brackets for escape detection const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}` diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 4203f2b44f..e8cd170e40 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -38,6 +38,7 @@ import { removeLayoutSegmentsAndUnderscoresWithEscape, removeTrailingSlash, replaceBackslash, + sortRouteNodes, trimPathLeft, } from './utils' import { fillTemplate, getTargetTemplate } from './template' @@ -616,19 +617,10 @@ export class Generator { ? this.indexTokenSegmentRegex : createTokenRegex(config.indexToken, { type: 'segment' }) - const sortedRouteNodes = multiSortBy(acc.routeNodes, [ - (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1), - (d) => - d.routePath === undefined - ? undefined - : countSlashSeparatedParts(d.routePath), - (d) => { - const segments = d.routePath?.split('/').filter(Boolean) ?? [] - const last = segments[segments.length - 1] ?? '' - return indexTokenSegmentRegex.test(last) ? -1 : 1 - }, - (d) => d, - ]) + const sortedRouteNodes = sortRouteNodes( + acc.routeNodes, + indexTokenSegmentRegex, + ) const routeImports: Array = [] const virtualRouteNodes: Array = [] diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 2354c5ed1c..747980aa7b 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -123,6 +123,30 @@ export function multiSortBy( return result } +/** + * Sorts route nodes by root, path depth, index status, and finally `routePath`. + * The `routePath` comparison keeps the output consistent when the earlier + * values are the same. + */ +export function sortRouteNodes( + routeNodes: Array, + indexTokenSegmentRegex: RegExp, +): Array { + return multiSortBy(routeNodes, [ + (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1), + (d) => + d.routePath === undefined + ? undefined + : countSlashSeparatedParts(d.routePath), + (d) => { + const segments = d.routePath?.split('/').filter(Boolean) ?? [] + const last = segments[segments.length - 1] ?? '' + return indexTokenSegmentRegex.test(last) ? -1 : 1 + }, + (d) => d.routePath, + ]) +} + export function cleanPath(path: string) { // remove double slashes return path.replace(/\/{2,}/g, '/') @@ -162,13 +186,12 @@ const DISALLOWED_ESCAPE_CHARS = new Set([ '%', ]) -export function determineInitialRoutePath(routePath: string) { +function determineInitialRoutePathFromParts( + routePath: string, + parts: Array, +) { const originalRoutePath = - cleanPath( - `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`, - ) || '' - - const parts = routePath.split(SPLIT_REGEX) + cleanPath(`/${cleanPath(parts.join('/')) || ''}`) || '' // Escape any characters that in square brackets // we keep the original path untouched @@ -207,6 +230,21 @@ export function determineInitialRoutePath(routePath: string) { } } +export function determineInitialRoutePath(routePath: string) { + return determineInitialRoutePathFromParts( + routePath, + routePath.split(SPLIT_REGEX), + ) +} + +/** + * Resolves bracket escapes in an explicit route path or ID without applying + * the dot-delimited flat-file route convention. + */ +export function determineInitialRoutePathFromExplicitPath(routePath: string) { + return determineInitialRoutePathFromParts(routePath, [routePath]) +} + /** * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets). * E.g., "[index]" -> true, "[_layout]" -> true, "foo[.]bar" -> false, "index" -> false diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 79f7d505b2..3e0831fe91 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -166,6 +166,26 @@ function rewriteConfigByFolderName(folderName: string, config: Config) { config.virtualRouteConfig = virtualRouteConfig } break + case 'virtual-pathless-layout-dotted-filename': + { + const virtualRouteConfig = rootRoute('root.route.tsx', [ + index('index.route.tsx'), + layout('pathless.layout.tsx', [ + route('subpath', 'subpath.route.tsx'), + ]), + ]) + config.virtualRouteConfig = virtualRouteConfig + } + break + case 'virtual-explicit-dotted-paths': + { + const virtualRouteConfig = rootRoute('root.tsx', [ + route('direct.path', 'direct.tsx'), + physical('/mounted.path', 'physical-routes'), + ]) + config.virtualRouteConfig = virtualRouteConfig + } + break case 'virtual-root-sibling-routes': { // Test case for issue #5431: Virtual routes that are siblings at the root level diff --git a/packages/router-generator/tests/generator/add-extensions-custom/routeTree.snapshot.ts b/packages/router-generator/tests/generator/add-extensions-custom/routeTree.snapshot.ts index 7859b1f9ff..2e6aa9d355 100644 --- a/packages/router-generator/tests/generator/add-extensions-custom/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/add-extensions-custom/routeTree.snapshot.ts @@ -9,19 +9,19 @@ // 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.js' -import { Route as PostsRouteImport } from './routes/posts.js' import { Route as IndexRouteImport } from './routes/index.js' +import { Route as PostsRouteImport } from './routes/posts.js' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } } } 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 e80d1e7e73..8f0ed564f4 100644 --- a/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/custom-scaffolding/routeTree.snapshot.ts @@ -16,16 +16,16 @@ import { Route as ApiBarRouteImport } from './routes/api/bar' const FooLazyRouteImport = createFileRoute('/foo')() -const FooLazyRoute = FooLazyRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => rootRouteImport, -} as any).lazy(() => import('./routes/foo.lazy').then((d) => d.Route)) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const FooLazyRoute = FooLazyRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any).lazy(() => import('./routes/foo.lazy').then((d) => d.Route)) const ApiBarRoute = ApiBarRouteImport.update({ id: '/api/bar', path: '/api/bar', @@ -64,13 +64,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/foo': { - id: '/foo' - path: '/foo' - fullPath: '/foo' - preLoaderRoute: typeof FooLazyRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -78,6 +71,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/foo': { + id: '/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooLazyRouteImport + parentRoute: typeof rootRouteImport + } '/api/bar': { id: '/api/bar' path: '/api/bar' diff --git a/packages/router-generator/tests/generator/custom-tokens/routeTree.snapshot.ts b/packages/router-generator/tests/generator/custom-tokens/routeTree.snapshot.ts index f49d059f20..fc136dc277 100644 --- a/packages/router-generator/tests/generator/custom-tokens/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/custom-tokens/routeTree.snapshot.ts @@ -9,18 +9,18 @@ // 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 PostsR0ut3RouteImport } from './routes/posts/_r0ut3_' -import { Route as BlogR0ut3RouteImport } from './routes/blog/_r0ut3_' import { Route as R1nd3xRouteImport } from './routes/_1nd3x' -import { Route as Posts1nd3xRouteImport } from './routes/posts/_1nd3x' +import { Route as BlogR0ut3RouteImport } from './routes/blog/_r0ut3_' +import { Route as PostsR0ut3RouteImport } from './routes/posts/_r0ut3_' import { Route as Blog1nd3xRouteImport } from './routes/blog/_1nd3x' import { Route as BlogSlugRouteImport } from './routes/blog/$slug' +import { Route as Posts1nd3xRouteImport } from './routes/posts/_1nd3x' import { Route as PostsPostId1nd3xRouteImport } from './routes/posts/$postId/_1nd3x' import { Route as PostsPostIdDeepRouteImport } from './routes/posts/$postId/deep' -const PostsR0ut3Route = PostsR0ut3RouteImport.update({ - id: '/posts', - path: '/posts', +const R1nd3xRoute = R1nd3xRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const BlogR0ut3Route = BlogR0ut3RouteImport.update({ @@ -28,16 +28,11 @@ const BlogR0ut3Route = BlogR0ut3RouteImport.update({ path: '/blog', getParentRoute: () => rootRouteImport, } as any) -const R1nd3xRoute = R1nd3xRouteImport.update({ - id: '/', - path: '/', +const PostsR0ut3Route = PostsR0ut3RouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const Posts1nd3xRoute = Posts1nd3xRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsR0ut3Route, -} as any) const Blog1nd3xRoute = Blog1nd3xRouteImport.update({ id: '/', path: '/', @@ -48,6 +43,11 @@ const BlogSlugRoute = BlogSlugRouteImport.update({ path: '/$slug', getParentRoute: () => BlogR0ut3Route, } as any) +const Posts1nd3xRoute = Posts1nd3xRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsR0ut3Route, +} as any) const PostsPostId1nd3xRoute = PostsPostId1nd3xRouteImport.update({ id: '/$postId/', path: '/$postId/', @@ -127,11 +127,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsR0ut3RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof R1nd3xRouteImport parentRoute: typeof rootRouteImport } '/blog': { @@ -141,20 +141,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogR0ut3RouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof R1nd3xRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsR0ut3RouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof Posts1nd3xRouteImport - parentRoute: typeof PostsR0ut3Route - } '/blog/': { id: '/blog/' path: '/' @@ -169,6 +162,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogSlugRouteImport parentRoute: typeof BlogR0ut3Route } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof Posts1nd3xRouteImport + parentRoute: typeof PostsR0ut3Route + } '/posts/$postId/': { id: '/posts/$postId/' path: '/$postId' 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 2ccc3a0a38..815dba6590 100644 --- a/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/dot-escaped/routeTree.snapshot.ts @@ -9,25 +9,20 @@ // 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 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 ScriptDotjsRouteImport } from './routes/script[.]js' import { Route as NestedDotjsDoubleDotextDotjsRouteImport } from './routes/nested[.]js.double[.]ext[.]js' +import { Route as NestedDotjsScriptDotjsRouteImport } from './routes/nested[.]js.script[.]js' -const ScriptDotjsRoute = ScriptDotjsRouteImport.update({ - id: '/script.js', - path: '/script.js', - getParentRoute: () => rootRouteImport, -} as any) const NestedDotjsRoute = NestedDotjsRouteImport.update({ id: '/nested.js', path: '/nested.js', getParentRoute: () => rootRouteImport, } as any) -const NestedDotjsScriptDotjsRoute = NestedDotjsScriptDotjsRouteImport.update({ +const ScriptDotjsRoute = ScriptDotjsRouteImport.update({ id: '/script.js', path: '/script.js', - getParentRoute: () => NestedDotjsRoute, + getParentRoute: () => rootRouteImport, } as any) const NestedDotjsDoubleDotextDotjsRoute = NestedDotjsDoubleDotextDotjsRouteImport.update({ @@ -35,6 +30,11 @@ const NestedDotjsDoubleDotextDotjsRoute = path: '/double.ext.js', getParentRoute: () => NestedDotjsRoute, } as any) +const NestedDotjsScriptDotjsRoute = NestedDotjsScriptDotjsRouteImport.update({ + id: '/script.js', + path: '/script.js', + getParentRoute: () => NestedDotjsRoute, +} as any) export interface FileRoutesByFullPath { '/nested.js': typeof NestedDotjsRouteWithChildren @@ -83,13 +83,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/script.js': { - id: '/script.js' - path: '/script.js' - fullPath: '/script.js' - preLoaderRoute: typeof ScriptDotjsRouteImport - parentRoute: typeof rootRouteImport - } '/nested.js': { id: '/nested.js' path: '/nested.js' @@ -97,12 +90,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedDotjsRouteImport parentRoute: typeof rootRouteImport } - '/nested.js/script.js': { - id: '/nested.js/script.js' + '/script.js': { + id: '/script.js' path: '/script.js' - fullPath: '/nested.js/script.js' - preLoaderRoute: typeof NestedDotjsScriptDotjsRouteImport - parentRoute: typeof NestedDotjsRoute + fullPath: '/script.js' + preLoaderRoute: typeof ScriptDotjsRouteImport + parentRoute: typeof rootRouteImport } '/nested.js/double.ext.js': { id: '/nested.js/double.ext.js' @@ -111,6 +104,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedDotjsDoubleDotextDotjsRouteImport parentRoute: typeof NestedDotjsRoute } + '/nested.js/script.js': { + id: '/nested.js/script.js' + path: '/script.js' + fullPath: '/nested.js/script.js' + preLoaderRoute: typeof NestedDotjsScriptDotjsRouteImport + parentRoute: typeof NestedDotjsRoute + } } } diff --git a/packages/router-generator/tests/generator/escaped-custom-tokens/routeTree.snapshot.ts b/packages/router-generator/tests/generator/escaped-custom-tokens/routeTree.snapshot.ts index e8b7404df9..e7902c671c 100644 --- a/packages/router-generator/tests/generator/escaped-custom-tokens/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/escaped-custom-tokens/routeTree.snapshot.ts @@ -10,8 +10,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as Char91_1nd3xChar93RouteImport } from './routes/[_1nd3x]' -import { Route as BlogR0ut3RouteImport } from './routes/blog._r0ut3_' import { Route as R1nd3xRouteImport } from './routes/_1nd3x' +import { Route as BlogR0ut3RouteImport } from './routes/blog._r0ut3_' import { Route as NestedChar91_1nd3xChar93RouteImport } from './routes/nested.[_1nd3x]' import { Route as PostsChar91_r0ut3_Char93RouteImport } from './routes/posts.[_r0ut3_]' @@ -20,16 +20,16 @@ const Char91_1nd3xChar93Route = Char91_1nd3xChar93RouteImport.update({ path: '/_1nd3x', getParentRoute: () => rootRouteImport, } as any) -const BlogR0ut3Route = BlogR0ut3RouteImport.update({ - id: '/blog', - path: '/blog', - getParentRoute: () => rootRouteImport, -} as any) const R1nd3xRoute = R1nd3xRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const BlogR0ut3Route = BlogR0ut3RouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) const NestedChar91_1nd3xChar93Route = NestedChar91_1nd3xChar93RouteImport.update({ id: '/nested/_1nd3x', @@ -96,13 +96,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char91_1nd3xChar93RouteImport parentRoute: typeof rootRouteImport } - '/blog': { - id: '/blog' - path: '/blog' - fullPath: '/blog' - preLoaderRoute: typeof BlogR0ut3RouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -110,6 +103,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof R1nd3xRouteImport parentRoute: typeof rootRouteImport } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogR0ut3RouteImport + parentRoute: typeof rootRouteImport + } '/nested/_1nd3x': { id: '/nested/_1nd3x' path: '/nested/_1nd3x' diff --git a/packages/router-generator/tests/generator/escaped-special-strings/routeTree.snapshot.ts b/packages/router-generator/tests/generator/escaped-special-strings/routeTree.snapshot.ts index ea71d9c21b..2600d0296d 100644 --- a/packages/router-generator/tests/generator/escaped-special-strings/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/escaped-special-strings/routeTree.snapshot.ts @@ -10,15 +10,15 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as Char91indexChar93RouteImport } from './routes/[index]' -import { Route as Char91routeChar93RouteImport } from './routes/[route]' -import { Route as Char91lazyChar93RouteImport } from './routes/[lazy]' -import { Route as Foo_barRouteImport } from './routes/foo[_]bar' -import { Route as BlogRouteImport } from './routes/blog[_]' -import { Route as Api_v2_usersRouteImport } from './routes/api[_]v2[_]users' -import { Route as Prefix_middle_suffixRouteImport } from './routes/[_]prefix[_]middle[_]suffix' -import { Route as LayoutRouteImport } from './routes/[_]layout' -import { Route as AuthRouteRouteImport } from './routes/[_]auth.route' import { Route as IndexRouteImport } from './routes/index' +import { Route as AuthRouteRouteImport } from './routes/[_]auth.route' +import { Route as LayoutRouteImport } from './routes/[_]layout' +import { Route as Prefix_middle_suffixRouteImport } from './routes/[_]prefix[_]middle[_]suffix' +import { Route as Api_v2_usersRouteImport } from './routes/api[_]v2[_]users' +import { Route as BlogRouteImport } from './routes/blog[_]' +import { Route as Foo_barRouteImport } from './routes/foo[_]bar' +import { Route as Char91lazyChar93RouteImport } from './routes/[lazy]' +import { Route as Char91routeChar93RouteImport } from './routes/[route]' import { Route as NestedChar91indexChar93RouteImport } from './routes/nested.[index]' const Char91indexChar93Route = Char91indexChar93RouteImport.update({ @@ -26,24 +26,24 @@ const Char91indexChar93Route = Char91indexChar93RouteImport.update({ path: '/index', getParentRoute: () => rootRouteImport, } as any) -const Char91routeChar93Route = Char91routeChar93RouteImport.update({ - id: '/route', - path: '/route', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const Char91lazyChar93Route = Char91lazyChar93RouteImport.update({ - id: '/lazy', - path: '/lazy', +const AuthRouteRoute = AuthRouteRouteImport.update({ + id: '/_auth', + path: '/_auth', getParentRoute: () => rootRouteImport, } as any) -const Foo_barRoute = Foo_barRouteImport.update({ - id: '/foo_bar', - path: '/foo_bar', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', + path: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const BlogRoute = BlogRouteImport.update({ - id: '/blog_', - path: '/blog_', +const Prefix_middle_suffixRoute = Prefix_middle_suffixRouteImport.update({ + id: '/_prefix_middle_suffix', + path: '/_prefix_middle_suffix', getParentRoute: () => rootRouteImport, } as any) const Api_v2_usersRoute = Api_v2_usersRouteImport.update({ @@ -51,24 +51,24 @@ const Api_v2_usersRoute = Api_v2_usersRouteImport.update({ path: '/api_v2_users', getParentRoute: () => rootRouteImport, } as any) -const Prefix_middle_suffixRoute = Prefix_middle_suffixRouteImport.update({ - id: '/_prefix_middle_suffix', - path: '/_prefix_middle_suffix', +const BlogRoute = BlogRouteImport.update({ + id: '/blog_', + path: '/blog_', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', - path: '/_layout', +const Foo_barRoute = Foo_barRouteImport.update({ + id: '/foo_bar', + path: '/foo_bar', getParentRoute: () => rootRouteImport, } as any) -const AuthRouteRoute = AuthRouteRouteImport.update({ - id: '/_auth', - path: '/_auth', +const Char91lazyChar93Route = Char91lazyChar93RouteImport.update({ + id: '/lazy', + path: '/lazy', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const Char91routeChar93Route = Char91routeChar93RouteImport.update({ + id: '/route', + path: '/route', getParentRoute: () => rootRouteImport, } as any) const NestedChar91indexChar93Route = NestedChar91indexChar93RouteImport.update({ @@ -182,32 +182,32 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char91indexChar93RouteImport parentRoute: typeof rootRouteImport } - '/route': { - id: '/route' - path: '/route' - fullPath: '/route' - preLoaderRoute: typeof Char91routeChar93RouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/lazy': { - id: '/lazy' - path: '/lazy' - fullPath: '/lazy' - preLoaderRoute: typeof Char91lazyChar93RouteImport + '/_auth': { + id: '/_auth' + path: '/_auth' + fullPath: '/_auth' + preLoaderRoute: typeof AuthRouteRouteImport parentRoute: typeof rootRouteImport } - '/foo_bar': { - id: '/foo_bar' - path: '/foo_bar' - fullPath: '/foo_bar' - preLoaderRoute: typeof Foo_barRouteImport + '/_layout': { + id: '/_layout' + path: '/_layout' + fullPath: '/_layout' + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/blog_': { - id: '/blog_' - path: '/blog_' - fullPath: '/blog_' - preLoaderRoute: typeof BlogRouteImport + '/_prefix_middle_suffix': { + id: '/_prefix_middle_suffix' + path: '/_prefix_middle_suffix' + fullPath: '/_prefix_middle_suffix' + preLoaderRoute: typeof Prefix_middle_suffixRouteImport parentRoute: typeof rootRouteImport } '/api_v2_users': { @@ -217,32 +217,32 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Api_v2_usersRouteImport parentRoute: typeof rootRouteImport } - '/_prefix_middle_suffix': { - id: '/_prefix_middle_suffix' - path: '/_prefix_middle_suffix' - fullPath: '/_prefix_middle_suffix' - preLoaderRoute: typeof Prefix_middle_suffixRouteImport + '/blog_': { + id: '/blog_' + path: '/blog_' + fullPath: '/blog_' + preLoaderRoute: typeof BlogRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' - path: '/_layout' - fullPath: '/_layout' - preLoaderRoute: typeof LayoutRouteImport + '/foo_bar': { + id: '/foo_bar' + path: '/foo_bar' + fullPath: '/foo_bar' + preLoaderRoute: typeof Foo_barRouteImport parentRoute: typeof rootRouteImport } - '/_auth': { - id: '/_auth' - path: '/_auth' - fullPath: '/_auth' - preLoaderRoute: typeof AuthRouteRouteImport + '/lazy': { + id: '/lazy' + path: '/lazy' + fullPath: '/lazy' + preLoaderRoute: typeof Char91lazyChar93RouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/route': { + id: '/route' + path: '/route' + fullPath: '/route' + preLoaderRoute: typeof Char91routeChar93RouteImport parentRoute: typeof rootRouteImport } '/nested/index': { diff --git a/packages/router-generator/tests/generator/export-variations/routeTree.snapshot.ts b/packages/router-generator/tests/generator/export-variations/routeTree.snapshot.ts index 6362489435..bb251cc516 100644 --- a/packages/router-generator/tests/generator/export-variations/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/export-variations/routeTree.snapshot.ts @@ -9,20 +9,20 @@ // 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 ExportWithAsRouteImport } from './routes/export-with-as' import { Route as ExportSeparateFromDeclarationRouteImport } from './routes/export-separate-from-declaration' +import { Route as ExportWithAsRouteImport } from './routes/export-with-as' -const ExportWithAsRoute = ExportWithAsRouteImport.update({ - id: '/export-with-as', - path: '/export-with-as', - getParentRoute: () => rootRouteImport, -} as any) const ExportSeparateFromDeclarationRoute = ExportSeparateFromDeclarationRouteImport.update({ id: '/export-separate-from-declaration', path: '/export-separate-from-declaration', getParentRoute: () => rootRouteImport, } as any) +const ExportWithAsRoute = ExportWithAsRouteImport.update({ + id: '/export-with-as', + path: '/export-with-as', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/export-separate-from-declaration': typeof ExportSeparateFromDeclarationRoute @@ -52,13 +52,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/export-with-as': { - id: '/export-with-as' - path: '/export-with-as' - fullPath: '/export-with-as' - preLoaderRoute: typeof ExportWithAsRouteImport - parentRoute: typeof rootRouteImport - } '/export-separate-from-declaration': { id: '/export-separate-from-declaration' path: '/export-separate-from-declaration' @@ -66,6 +59,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ExportSeparateFromDeclarationRouteImport parentRoute: typeof rootRouteImport } + '/export-with-as': { + id: '/export-with-as' + path: '/export-with-as' + fullPath: '/export-with-as' + preLoaderRoute: typeof ExportWithAsRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/file-modification/routeTree.snapshot.ts b/packages/router-generator/tests/generator/file-modification/routeTree.snapshot.ts index ddcc5ea6d6..7ec8640782 100644 --- a/packages/router-generator/tests/generator/file-modification/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/file-modification/routeTree.snapshot.ts @@ -11,12 +11,12 @@ import { createFileRoute } from '@tanstack/react-router' import { Route as rootRouteImport } from './routes/__root' -import { Route as testTemplateLiteralRouteImport } from './routes/(test)/template-literal' -import { Route as testInitiallyLazyRouteImport } from './routes/(test)/initiallyLazy' -import { Route as testInitiallyEmptyRouteImport } from './routes/(test)/initiallyEmpty' -import { Route as testFooRouteImport } from './routes/(test)/foo' -import { Route as testDuplicateImportRouteImport } from './routes/(test)/duplicate-import' import { Route as testDoubleQuotesRouteImport } from './routes/(test)/double-quotes' +import { Route as testDuplicateImportRouteImport } from './routes/(test)/duplicate-import' +import { Route as testFooRouteImport } from './routes/(test)/foo' +import { Route as testInitiallyEmptyRouteImport } from './routes/(test)/initiallyEmpty' +import { Route as testInitiallyLazyRouteImport } from './routes/(test)/initiallyLazy' +import { Route as testTemplateLiteralRouteImport } from './routes/(test)/template-literal' import { Route as testFooBarRouteImport } from './routes/(test)/foo.bar' const testBarLazyRouteImport = createFileRoute('/(test)/bar')() @@ -28,14 +28,19 @@ const testBarLazyRoute = testBarLazyRouteImport getParentRoute: () => rootRouteImport, } as any) .lazy(() => import('./routes/(test)/bar.lazy').then((d) => d.Route)) -const testTemplateLiteralRoute = testTemplateLiteralRouteImport.update({ - id: '/(test)/template-literal', - path: '/template-literal', +const testDoubleQuotesRoute = testDoubleQuotesRouteImport.update({ + id: '/(test)/double-quotes', + path: '/double-quotes', getParentRoute: () => rootRouteImport, } as any) -const testInitiallyLazyRoute = testInitiallyLazyRouteImport.update({ - id: '/(test)/initiallyLazy', - path: '/initiallyLazy', +const testDuplicateImportRoute = testDuplicateImportRouteImport.update({ + id: '/(test)/duplicate-import', + path: '/duplicate-import', + getParentRoute: () => rootRouteImport, +} as any) +const testFooRoute = testFooRouteImport.update({ + id: '/(test)/foo', + path: '/foo', getParentRoute: () => rootRouteImport, } as any) const testInitiallyEmptyRoute = testInitiallyEmptyRouteImport @@ -47,19 +52,14 @@ const testInitiallyEmptyRoute = testInitiallyEmptyRouteImport .lazy(() => import('./routes/(test)/initiallyEmpty.lazy').then((d) => d.Route), ) -const testFooRoute = testFooRouteImport.update({ - id: '/(test)/foo', - path: '/foo', - getParentRoute: () => rootRouteImport, -} as any) -const testDuplicateImportRoute = testDuplicateImportRouteImport.update({ - id: '/(test)/duplicate-import', - path: '/duplicate-import', +const testInitiallyLazyRoute = testInitiallyLazyRouteImport.update({ + id: '/(test)/initiallyLazy', + path: '/initiallyLazy', getParentRoute: () => rootRouteImport, } as any) -const testDoubleQuotesRoute = testDoubleQuotesRouteImport.update({ - id: '/(test)/double-quotes', - path: '/double-quotes', +const testTemplateLiteralRoute = testTemplateLiteralRouteImport.update({ + id: '/(test)/template-literal', + path: '/template-literal', getParentRoute: () => rootRouteImport, } as any) const testFooBarRoute = testFooBarRouteImport.update({ @@ -151,25 +151,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testBarLazyRouteImport parentRoute: typeof rootRouteImport } - '/(test)/template-literal': { - id: '/(test)/template-literal' - path: '/template-literal' - fullPath: '/template-literal' - preLoaderRoute: typeof testTemplateLiteralRouteImport - parentRoute: typeof rootRouteImport - } - '/(test)/initiallyLazy': { - id: '/(test)/initiallyLazy' - path: '/initiallyLazy' - fullPath: '/initiallyLazy' - preLoaderRoute: typeof testInitiallyLazyRouteImport + '/(test)/double-quotes': { + id: '/(test)/double-quotes' + path: '/double-quotes' + fullPath: '/double-quotes' + preLoaderRoute: typeof testDoubleQuotesRouteImport parentRoute: typeof rootRouteImport } - '/(test)/initiallyEmpty': { - id: '/(test)/initiallyEmpty' - path: '/initiallyEmpty' - fullPath: '/initiallyEmpty' - preLoaderRoute: typeof testInitiallyEmptyRouteImport + '/(test)/duplicate-import': { + id: '/(test)/duplicate-import' + path: '/duplicate-import' + fullPath: '/duplicate-import' + preLoaderRoute: typeof testDuplicateImportRouteImport parentRoute: typeof rootRouteImport } '/(test)/foo': { @@ -179,18 +172,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof testFooRouteImport parentRoute: typeof rootRouteImport } - '/(test)/duplicate-import': { - id: '/(test)/duplicate-import' - path: '/duplicate-import' - fullPath: '/duplicate-import' - preLoaderRoute: typeof testDuplicateImportRouteImport + '/(test)/initiallyEmpty': { + id: '/(test)/initiallyEmpty' + path: '/initiallyEmpty' + fullPath: '/initiallyEmpty' + preLoaderRoute: typeof testInitiallyEmptyRouteImport parentRoute: typeof rootRouteImport } - '/(test)/double-quotes': { - id: '/(test)/double-quotes' - path: '/double-quotes' - fullPath: '/double-quotes' - preLoaderRoute: typeof testDoubleQuotesRouteImport + '/(test)/initiallyLazy': { + id: '/(test)/initiallyLazy' + path: '/initiallyLazy' + fullPath: '/initiallyLazy' + preLoaderRoute: typeof testInitiallyLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/template-literal': { + id: '/(test)/template-literal' + path: '/template-literal' + fullPath: '/template-literal' + preLoaderRoute: typeof testTemplateLiteralRouteImport parentRoute: typeof rootRouteImport } '/(test)/foo/bar': { diff --git a/packages/router-generator/tests/generator/flat-route-group/routeTree.snapshot.ts b/packages/router-generator/tests/generator/flat-route-group/routeTree.snapshot.ts index 7f7990e787..ca3cf2f2b7 100644 --- a/packages/router-generator/tests/generator/flat-route-group/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/flat-route-group/routeTree.snapshot.ts @@ -10,8 +10,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as AppRouteImport } from './routes/app' -import { Route as AppcomprasComprasOrdenesRouteImport } from './routes/app.(compras)/compras_.ordenes' import { Route as AppcomprasCompras_masRouteImport } from './routes/app.(compras)/compras_._mas' +import { Route as AppcomprasComprasOrdenesRouteImport } from './routes/app.(compras)/compras_.ordenes' import { Route as AppcomprasCompras_masDivisionesRouteImport } from './routes/app.(compras)/compras_._mas.divisiones' const AppRoute = AppRouteImport.update({ @@ -19,17 +19,17 @@ const AppRoute = AppRouteImport.update({ path: '/app', getParentRoute: () => rootRouteImport, } as any) +const AppcomprasCompras_masRoute = AppcomprasCompras_masRouteImport.update({ + id: '/(compras)/compras_/_mas', + path: '/compras', + getParentRoute: () => AppRoute, +} as any) const AppcomprasComprasOrdenesRoute = AppcomprasComprasOrdenesRouteImport.update({ id: '/(compras)/compras_/ordenes', path: '/compras/ordenes', getParentRoute: () => AppRoute, } as any) -const AppcomprasCompras_masRoute = AppcomprasCompras_masRouteImport.update({ - id: '/(compras)/compras_/_mas', - path: '/compras', - getParentRoute: () => AppRoute, -} as any) const AppcomprasCompras_masDivisionesRoute = AppcomprasCompras_masDivisionesRouteImport.update({ id: '/divisiones', @@ -90,13 +90,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppRouteImport parentRoute: typeof rootRouteImport } - '/app/(compras)/compras_/ordenes': { - id: '/app/(compras)/compras_/ordenes' - path: '/compras/ordenes' - fullPath: '/app/compras/ordenes' - preLoaderRoute: typeof AppcomprasComprasOrdenesRouteImport - parentRoute: typeof AppRoute - } '/app/(compras)/compras_/_mas': { id: '/app/(compras)/compras_/_mas' path: '/compras' @@ -104,6 +97,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppcomprasCompras_masRouteImport parentRoute: typeof AppRoute } + '/app/(compras)/compras_/ordenes': { + id: '/app/(compras)/compras_/ordenes' + path: '/compras/ordenes' + fullPath: '/app/compras/ordenes' + preLoaderRoute: typeof AppcomprasComprasOrdenesRouteImport + parentRoute: typeof AppRoute + } '/app/(compras)/compras_/_mas/divisiones': { id: '/app/(compras)/compras_/_mas/divisiones' path: '/divisiones' diff --git a/packages/router-generator/tests/generator/flat/routeTree.snapshot.ts b/packages/router-generator/tests/generator/flat/routeTree.snapshot.ts index b1bf6587fc..923cfc0530 100644 --- a/packages/router-generator/tests/generator/flat/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/flat/routeTree.snapshot.ts @@ -9,23 +9,23 @@ // 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 PostsRouteRouteImport } from './routes/posts.route' -import { Route as BlogRouteRouteImport } from './routes/blog.route' import { Route as IndexRouteImport } from './routes/index' -import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as BlogRouteRouteImport } from './routes/blog.route' +import { Route as PostsRouteRouteImport } from './routes/posts.route' import { Route as BlogIndexRouteImport } from './routes/blog.index' -import { Route as BlogStatsRouteImport } from './routes/blog_.stats' import { Route as BlogBlogIdRouteRouteImport } from './routes/blog_.$blogId.route' -import { Route as PostsPostIdIndexRouteImport } from './routes/posts.$postId.index' +import { Route as BlogStatsRouteImport } from './routes/blog_.stats' +import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as BlogSlugIndexRouteImport } from './routes/blog.$slug.index' -import { Route as PostsPostIdDeepRouteImport } from './routes/posts.$postId.deep' -import { Route as BlogBlogIdEditRouteImport } from './routes/blog_.$blogId_.edit' import { Route as BlogBlogIdSlugRouteRouteImport } from './routes/blog_.$blogId.$slug.route' +import { Route as BlogBlogIdEditRouteImport } from './routes/blog_.$blogId_.edit' +import { Route as PostsPostIdIndexRouteImport } from './routes/posts.$postId.index' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts.$postId.deep' import { Route as BlogBlogIdSlugBarRouteImport } from './routes/blog_.$blogId.$slug_.bar' -const PostsRouteRoute = PostsRouteRouteImport.update({ - id: '/posts', - path: '/posts', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const BlogRouteRoute = BlogRouteRouteImport.update({ @@ -33,34 +33,29 @@ const BlogRouteRoute = BlogRouteRouteImport.update({ path: '/blog', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRouteRoute, -} as any) const BlogIndexRoute = BlogIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => BlogRouteRoute, } as any) -const BlogStatsRoute = BlogStatsRouteImport.update({ - id: '/blog_/stats', - path: '/blog/stats', - getParentRoute: () => rootRouteImport, -} as any) const BlogBlogIdRouteRoute = BlogBlogIdRouteRouteImport.update({ id: '/blog_/$blogId', path: '/blog/$blogId', getParentRoute: () => rootRouteImport, } as any) -const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ - id: '/$postId/', - path: '/$postId/', +const BlogStatsRoute = BlogStatsRouteImport.update({ + id: '/blog_/stats', + path: '/blog/stats', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => PostsRouteRoute, } as any) const BlogSlugIndexRoute = BlogSlugIndexRouteImport.update({ @@ -68,20 +63,25 @@ const BlogSlugIndexRoute = BlogSlugIndexRouteImport.update({ path: '/$slug/', getParentRoute: () => BlogRouteRoute, } as any) -const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ - id: '/$postId/deep', - path: '/$postId/deep', - getParentRoute: () => PostsRouteRoute, +const BlogBlogIdSlugRouteRoute = BlogBlogIdSlugRouteRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogBlogIdRouteRoute, } as any) const BlogBlogIdEditRoute = BlogBlogIdEditRouteImport.update({ id: '/blog_/$blogId_/edit', path: '/blog/$blogId/edit', getParentRoute: () => rootRouteImport, } as any) -const BlogBlogIdSlugRouteRoute = BlogBlogIdSlugRouteRouteImport.update({ - id: '/$slug', - path: '/$slug', - getParentRoute: () => BlogBlogIdRouteRoute, +const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ + id: '/$postId/', + path: '/$postId/', + getParentRoute: () => PostsRouteRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/$postId/deep', + path: '/$postId/deep', + getParentRoute: () => PostsRouteRoute, } as any) const BlogBlogIdSlugBarRoute = BlogBlogIdSlugBarRouteImport.update({ id: '/$slug_/bar', @@ -190,11 +190,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/blog': { @@ -204,20 +204,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRouteRoute - } '/blog/': { id: '/blog/' path: '/' @@ -225,13 +218,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogIndexRouteImport parentRoute: typeof BlogRouteRoute } - '/blog_/stats': { - id: '/blog_/stats' - path: '/blog/stats' - fullPath: '/blog/stats' - preLoaderRoute: typeof BlogStatsRouteImport - parentRoute: typeof rootRouteImport - } '/blog_/$blogId': { id: '/blog_/$blogId' path: '/blog/$blogId' @@ -239,11 +225,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogBlogIdRouteRouteImport parentRoute: typeof rootRouteImport } - '/posts/$postId/': { - id: '/posts/$postId/' - path: '/$postId' - fullPath: '/posts/$postId/' - preLoaderRoute: typeof PostsPostIdIndexRouteImport + '/blog_/stats': { + id: '/blog_/stats' + path: '/blog/stats' + fullPath: '/blog/stats' + preLoaderRoute: typeof BlogStatsRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport parentRoute: typeof PostsRouteRoute } '/blog/$slug/': { @@ -253,12 +246,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogSlugIndexRouteImport parentRoute: typeof BlogRouteRoute } - '/posts/$postId/deep': { - id: '/posts/$postId/deep' - path: '/$postId/deep' - fullPath: '/posts/$postId/deep' - preLoaderRoute: typeof PostsPostIdDeepRouteImport - parentRoute: typeof PostsRouteRoute + '/blog_/$blogId/$slug': { + id: '/blog_/$blogId/$slug' + path: '/$slug' + fullPath: '/blog/$blogId/$slug' + preLoaderRoute: typeof BlogBlogIdSlugRouteRouteImport + parentRoute: typeof BlogBlogIdRouteRoute } '/blog_/$blogId_/edit': { id: '/blog_/$blogId_/edit' @@ -267,12 +260,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogBlogIdEditRouteImport parentRoute: typeof rootRouteImport } - '/blog_/$blogId/$slug': { - id: '/blog_/$blogId/$slug' - path: '/$slug' - fullPath: '/blog/$blogId/$slug' - preLoaderRoute: typeof BlogBlogIdSlugRouteRouteImport - parentRoute: typeof BlogBlogIdRouteRoute + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId/' + preLoaderRoute: typeof PostsPostIdIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/posts/$postId/deep': { + id: '/posts/$postId/deep' + path: '/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof PostsRouteRoute } '/blog_/$blogId/$slug_/bar': { id: '/blog_/$blogId/$slug_/bar' diff --git a/packages/router-generator/tests/generator/invalid-param-names/routeTree.snapshot.ts b/packages/router-generator/tests/generator/invalid-param-names/routeTree.snapshot.ts index 4728dd439e..9048976e40 100644 --- a/packages/router-generator/tests/generator/invalid-param-names/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/invalid-param-names/routeTree.snapshot.ts @@ -9,13 +9,13 @@ // 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 ValidParamRouteImport } from './routes/$validParam' -import { Route as UserNameRouteImport } from './routes/$user-name' import { Route as R123RouteImport } from './routes/$123' +import { Route as UserNameRouteImport } from './routes/$user-name' +import { Route as ValidParamRouteImport } from './routes/$validParam' -const ValidParamRoute = ValidParamRouteImport.update({ - id: '/$validParam', - path: '/$validParam', +const R123Route = R123RouteImport.update({ + id: '/$123', + path: '/$123', getParentRoute: () => rootRouteImport, } as any) const UserNameRoute = UserNameRouteImport.update({ @@ -23,9 +23,9 @@ const UserNameRoute = UserNameRouteImport.update({ path: '/$user-name', getParentRoute: () => rootRouteImport, } as any) -const R123Route = R123RouteImport.update({ - id: '/$123', - path: '/$123', +const ValidParamRoute = ValidParamRouteImport.update({ + id: '/$validParam', + path: '/$validParam', getParentRoute: () => rootRouteImport, } as any) @@ -61,11 +61,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/$validParam': { - id: '/$validParam' - path: '/$validParam' - fullPath: '/$validParam' - preLoaderRoute: typeof ValidParamRouteImport + '/$123': { + id: '/$123' + path: '/$123' + fullPath: '/$123' + preLoaderRoute: typeof R123RouteImport parentRoute: typeof rootRouteImport } '/$user-name': { @@ -75,11 +75,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof UserNameRouteImport parentRoute: typeof rootRouteImport } - '/$123': { - id: '/$123' - path: '/$123' - fullPath: '/$123' - preLoaderRoute: typeof R123RouteImport + '/$validParam': { + id: '/$validParam' + path: '/$validParam' + fullPath: '/$validParam' + preLoaderRoute: typeof ValidParamRouteImport parentRoute: typeof rootRouteImport } } diff --git a/packages/router-generator/tests/generator/nested-layouts/routeTree.snapshot.ts b/packages/router-generator/tests/generator/nested-layouts/routeTree.snapshot.ts index d99745d928..7fb6ebb0d0 100644 --- a/packages/router-generator/tests/generator/nested-layouts/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/nested-layouts/routeTree.snapshot.ts @@ -9,141 +9,141 @@ // 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 LayoutA2RouteImport } from './routes/_layout-a2' +import { Route as IndexRouteImport } from './routes/index' import { Route as LayoutA1RouteImport } from './routes/_layout-a1' +import { Route as LayoutA2RouteImport } from './routes/_layout-a2' import { Route as JestedRouteRouteImport } from './routes/jested/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as NestedLayoutB2RouteImport } from './routes/nested/_layout-b2' -import { Route as NestedLayoutB1RouteImport } from './routes/nested/_layout-b1' -import { Route as JestedLayoutB4RouteImport } from './routes/jested/_layout-b4' -import { Route as JestedLayoutB3RouteImport } from './routes/jested/_layout-b3' -import { Route as FooBarRouteImport } from './routes/foo/bar' -import { Route as LayoutA2BarRouteImport } from './routes/_layout-a2/bar' -import { Route as LayoutA1FooRouteImport } from './routes/_layout-a1/foo' import { Route as folderInFolderRouteImport } from './routes/(folder)/in-folder' +import { Route as LayoutA1FooRouteImport } from './routes/_layout-a1/foo' +import { Route as LayoutA2BarRouteImport } from './routes/_layout-a2/bar' import { Route as FooLayoutB5RouteRouteImport } from './routes/foo/_layout-b5/route' -import { Route as NestedLayoutB1IndexRouteImport } from './routes/nested/_layout-b1/index' -import { Route as JestedLayoutB3IndexRouteImport } from './routes/jested/_layout-b3/index' +import { Route as FooBarRouteImport } from './routes/foo/bar' +import { Route as JestedLayoutB3RouteImport } from './routes/jested/_layout-b3' +import { Route as JestedLayoutB4RouteImport } from './routes/jested/_layout-b4' +import { Route as NestedLayoutB1RouteImport } from './routes/nested/_layout-b1' +import { Route as NestedLayoutB2RouteImport } from './routes/nested/_layout-b2' import { Route as FooLayoutB5IndexRouteImport } from './routes/foo/_layout-b5/index' -import { Route as NestedLayoutB2FooRouteImport } from './routes/nested/_layout-b2/foo' -import { Route as NestedLayoutB1LayoutC1RouteImport } from './routes/nested/_layout-b1/_layout-c1' -import { Route as JestedLayoutB4FooRouteImport } from './routes/jested/_layout-b4/foo' -import { Route as JestedLayoutB3LayoutC2RouteImport } from './routes/jested/_layout-b3/_layout-c2' import { Route as FooLayoutB5IdRouteImport } from './routes/foo/_layout-b5/$id' -import { Route as NestedLayoutB1LayoutC1BarRouteImport } from './routes/nested/_layout-b1/_layout-c1/bar' +import { Route as JestedLayoutB3IndexRouteImport } from './routes/jested/_layout-b3/index' +import { Route as JestedLayoutB3LayoutC2RouteImport } from './routes/jested/_layout-b3/_layout-c2' +import { Route as JestedLayoutB4FooRouteImport } from './routes/jested/_layout-b4/foo' +import { Route as NestedLayoutB1IndexRouteImport } from './routes/nested/_layout-b1/index' +import { Route as NestedLayoutB1LayoutC1RouteImport } from './routes/nested/_layout-b1/_layout-c1' +import { Route as NestedLayoutB2FooRouteImport } from './routes/nested/_layout-b2/foo' import { Route as JestedLayoutB3LayoutC2BarRouteImport } from './routes/jested/_layout-b3/_layout-c2/bar' +import { Route as NestedLayoutB1LayoutC1BarRouteImport } from './routes/nested/_layout-b1/_layout-c1/bar' -const LayoutA2Route = LayoutA2RouteImport.update({ - id: '/_layout-a2', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const LayoutA1Route = LayoutA1RouteImport.update({ id: '/_layout-a1', getParentRoute: () => rootRouteImport, } as any) +const LayoutA2Route = LayoutA2RouteImport.update({ + id: '/_layout-a2', + getParentRoute: () => rootRouteImport, +} as any) const JestedRouteRoute = JestedRouteRouteImport.update({ id: '/jested', path: '/jested', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const NestedLayoutB2Route = NestedLayoutB2RouteImport.update({ - id: '/nested/_layout-b2', - path: '/nested', +const folderInFolderRoute = folderInFolderRouteImport.update({ + id: '/(folder)/in-folder', + path: '/in-folder', getParentRoute: () => rootRouteImport, } as any) -const NestedLayoutB1Route = NestedLayoutB1RouteImport.update({ - id: '/nested/_layout-b1', - path: '/nested', - getParentRoute: () => rootRouteImport, +const LayoutA1FooRoute = LayoutA1FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => LayoutA1Route, } as any) -const JestedLayoutB4Route = JestedLayoutB4RouteImport.update({ - id: '/_layout-b4', - getParentRoute: () => JestedRouteRoute, +const LayoutA2BarRoute = LayoutA2BarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => LayoutA2Route, } as any) -const JestedLayoutB3Route = JestedLayoutB3RouteImport.update({ - id: '/_layout-b3', - getParentRoute: () => JestedRouteRoute, +const FooLayoutB5RouteRoute = FooLayoutB5RouteRouteImport.update({ + id: '/foo/_layout-b5', + path: '/foo', + getParentRoute: () => rootRouteImport, } as any) const FooBarRoute = FooBarRouteImport.update({ id: '/foo/bar', path: '/foo/bar', getParentRoute: () => rootRouteImport, } as any) -const LayoutA2BarRoute = LayoutA2BarRouteImport.update({ - id: '/bar', - path: '/bar', - getParentRoute: () => LayoutA2Route, +const JestedLayoutB3Route = JestedLayoutB3RouteImport.update({ + id: '/_layout-b3', + getParentRoute: () => JestedRouteRoute, } as any) -const LayoutA1FooRoute = LayoutA1FooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => LayoutA1Route, +const JestedLayoutB4Route = JestedLayoutB4RouteImport.update({ + id: '/_layout-b4', + getParentRoute: () => JestedRouteRoute, } as any) -const folderInFolderRoute = folderInFolderRouteImport.update({ - id: '/(folder)/in-folder', - path: '/in-folder', +const NestedLayoutB1Route = NestedLayoutB1RouteImport.update({ + id: '/nested/_layout-b1', + path: '/nested', getParentRoute: () => rootRouteImport, } as any) -const FooLayoutB5RouteRoute = FooLayoutB5RouteRouteImport.update({ - id: '/foo/_layout-b5', - path: '/foo', +const NestedLayoutB2Route = NestedLayoutB2RouteImport.update({ + id: '/nested/_layout-b2', + path: '/nested', getParentRoute: () => rootRouteImport, } as any) -const NestedLayoutB1IndexRoute = NestedLayoutB1IndexRouteImport.update({ +const FooLayoutB5IndexRoute = FooLayoutB5IndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => NestedLayoutB1Route, + getParentRoute: () => FooLayoutB5RouteRoute, +} as any) +const FooLayoutB5IdRoute = FooLayoutB5IdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => FooLayoutB5RouteRoute, } as any) const JestedLayoutB3IndexRoute = JestedLayoutB3IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => JestedLayoutB3Route, } as any) -const FooLayoutB5IndexRoute = FooLayoutB5IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => FooLayoutB5RouteRoute, +const JestedLayoutB3LayoutC2Route = JestedLayoutB3LayoutC2RouteImport.update({ + id: '/_layout-c2', + getParentRoute: () => JestedLayoutB3Route, } as any) -const NestedLayoutB2FooRoute = NestedLayoutB2FooRouteImport.update({ +const JestedLayoutB4FooRoute = JestedLayoutB4FooRouteImport.update({ id: '/foo', path: '/foo', - getParentRoute: () => NestedLayoutB2Route, + getParentRoute: () => JestedLayoutB4Route, +} as any) +const NestedLayoutB1IndexRoute = NestedLayoutB1IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NestedLayoutB1Route, } as any) const NestedLayoutB1LayoutC1Route = NestedLayoutB1LayoutC1RouteImport.update({ id: '/_layout-c1', getParentRoute: () => NestedLayoutB1Route, } as any) -const JestedLayoutB4FooRoute = JestedLayoutB4FooRouteImport.update({ +const NestedLayoutB2FooRoute = NestedLayoutB2FooRouteImport.update({ id: '/foo', path: '/foo', - getParentRoute: () => JestedLayoutB4Route, -} as any) -const JestedLayoutB3LayoutC2Route = JestedLayoutB3LayoutC2RouteImport.update({ - id: '/_layout-c2', - getParentRoute: () => JestedLayoutB3Route, -} as any) -const FooLayoutB5IdRoute = FooLayoutB5IdRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => FooLayoutB5RouteRoute, + getParentRoute: () => NestedLayoutB2Route, } as any) -const NestedLayoutB1LayoutC1BarRoute = - NestedLayoutB1LayoutC1BarRouteImport.update({ - id: '/bar', - path: '/bar', - getParentRoute: () => NestedLayoutB1LayoutC1Route, - } as any) const JestedLayoutB3LayoutC2BarRoute = JestedLayoutB3LayoutC2BarRouteImport.update({ id: '/bar', path: '/bar', getParentRoute: () => JestedLayoutB3LayoutC2Route, } as any) +const NestedLayoutB1LayoutC1BarRoute = + NestedLayoutB1LayoutC1BarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => NestedLayoutB1LayoutC1Route, + } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -275,11 +275,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout-a2': { - id: '/_layout-a2' - path: '' + '/': { + id: '/' + path: '/' fullPath: '/' - preLoaderRoute: typeof LayoutA2RouteImport + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/_layout-a1': { @@ -289,6 +289,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutA1RouteImport parentRoute: typeof rootRouteImport } + '/_layout-a2': { + id: '/_layout-a2' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutA2RouteImport + parentRoute: typeof rootRouteImport + } '/jested': { id: '/jested' path: '/jested' @@ -296,40 +303,33 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof JestedRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/_layout-b2': { - id: '/nested/_layout-b2' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedLayoutB2RouteImport + '/(folder)/in-folder': { + id: '/(folder)/in-folder' + path: '/in-folder' + fullPath: '/in-folder' + preLoaderRoute: typeof folderInFolderRouteImport parentRoute: typeof rootRouteImport } - '/nested/_layout-b1': { - id: '/nested/_layout-b1' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedLayoutB1RouteImport - parentRoute: typeof rootRouteImport + '/_layout-a1/foo': { + id: '/_layout-a1/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof LayoutA1FooRouteImport + parentRoute: typeof LayoutA1Route } - '/jested/_layout-b4': { - id: '/jested/_layout-b4' - path: '' - fullPath: '/jested' - preLoaderRoute: typeof JestedLayoutB4RouteImport - parentRoute: typeof JestedRouteRoute + '/_layout-a2/bar': { + id: '/_layout-a2/bar' + path: '/bar' + fullPath: '/bar' + preLoaderRoute: typeof LayoutA2BarRouteImport + parentRoute: typeof LayoutA2Route } - '/jested/_layout-b3': { - id: '/jested/_layout-b3' - path: '' - fullPath: '/jested' - preLoaderRoute: typeof JestedLayoutB3RouteImport - parentRoute: typeof JestedRouteRoute + '/foo/_layout-b5': { + id: '/foo/_layout-b5' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooLayoutB5RouteRouteImport + parentRoute: typeof rootRouteImport } '/foo/bar': { id: '/foo/bar' @@ -338,40 +338,47 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FooBarRouteImport parentRoute: typeof rootRouteImport } - '/_layout-a2/bar': { - id: '/_layout-a2/bar' - path: '/bar' - fullPath: '/bar' - preLoaderRoute: typeof LayoutA2BarRouteImport - parentRoute: typeof LayoutA2Route + '/jested/_layout-b3': { + id: '/jested/_layout-b3' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB3RouteImport + parentRoute: typeof JestedRouteRoute } - '/_layout-a1/foo': { - id: '/_layout-a1/foo' - path: '/foo' - fullPath: '/foo' - preLoaderRoute: typeof LayoutA1FooRouteImport - parentRoute: typeof LayoutA1Route + '/jested/_layout-b4': { + id: '/jested/_layout-b4' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB4RouteImport + parentRoute: typeof JestedRouteRoute } - '/(folder)/in-folder': { - id: '/(folder)/in-folder' - path: '/in-folder' - fullPath: '/in-folder' - preLoaderRoute: typeof folderInFolderRouteImport + '/nested/_layout-b1': { + id: '/nested/_layout-b1' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedLayoutB1RouteImport parentRoute: typeof rootRouteImport } - '/foo/_layout-b5': { - id: '/foo/_layout-b5' - path: '/foo' - fullPath: '/foo' - preLoaderRoute: typeof FooLayoutB5RouteRouteImport + '/nested/_layout-b2': { + id: '/nested/_layout-b2' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedLayoutB2RouteImport parentRoute: typeof rootRouteImport } - '/nested/_layout-b1/': { - id: '/nested/_layout-b1/' + '/foo/_layout-b5/': { + id: '/foo/_layout-b5/' path: '/' - fullPath: '/nested/' - preLoaderRoute: typeof NestedLayoutB1IndexRouteImport - parentRoute: typeof NestedLayoutB1Route + fullPath: '/foo/' + preLoaderRoute: typeof FooLayoutB5IndexRouteImport + parentRoute: typeof FooLayoutB5RouteRoute + } + '/foo/_layout-b5/$id': { + id: '/foo/_layout-b5/$id' + path: '/$id' + fullPath: '/foo/$id' + preLoaderRoute: typeof FooLayoutB5IdRouteImport + parentRoute: typeof FooLayoutB5RouteRoute } '/jested/_layout-b3/': { id: '/jested/_layout-b3/' @@ -380,19 +387,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof JestedLayoutB3IndexRouteImport parentRoute: typeof JestedLayoutB3Route } - '/foo/_layout-b5/': { - id: '/foo/_layout-b5/' - path: '/' - fullPath: '/foo/' - preLoaderRoute: typeof FooLayoutB5IndexRouteImport - parentRoute: typeof FooLayoutB5RouteRoute + '/jested/_layout-b3/_layout-c2': { + id: '/jested/_layout-b3/_layout-c2' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB3LayoutC2RouteImport + parentRoute: typeof JestedLayoutB3Route } - '/nested/_layout-b2/foo': { - id: '/nested/_layout-b2/foo' + '/jested/_layout-b4/foo': { + id: '/jested/_layout-b4/foo' path: '/foo' - fullPath: '/nested/foo' - preLoaderRoute: typeof NestedLayoutB2FooRouteImport - parentRoute: typeof NestedLayoutB2Route + fullPath: '/jested/foo' + preLoaderRoute: typeof JestedLayoutB4FooRouteImport + parentRoute: typeof JestedLayoutB4Route + } + '/nested/_layout-b1/': { + id: '/nested/_layout-b1/' + path: '/' + fullPath: '/nested/' + preLoaderRoute: typeof NestedLayoutB1IndexRouteImport + parentRoute: typeof NestedLayoutB1Route } '/nested/_layout-b1/_layout-c1': { id: '/nested/_layout-b1/_layout-c1' @@ -401,26 +415,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedLayoutB1LayoutC1RouteImport parentRoute: typeof NestedLayoutB1Route } - '/jested/_layout-b4/foo': { - id: '/jested/_layout-b4/foo' + '/nested/_layout-b2/foo': { + id: '/nested/_layout-b2/foo' path: '/foo' - fullPath: '/jested/foo' - preLoaderRoute: typeof JestedLayoutB4FooRouteImport - parentRoute: typeof JestedLayoutB4Route - } - '/jested/_layout-b3/_layout-c2': { - id: '/jested/_layout-b3/_layout-c2' - path: '' - fullPath: '/jested' - preLoaderRoute: typeof JestedLayoutB3LayoutC2RouteImport - parentRoute: typeof JestedLayoutB3Route + fullPath: '/nested/foo' + preLoaderRoute: typeof NestedLayoutB2FooRouteImport + parentRoute: typeof NestedLayoutB2Route } - '/foo/_layout-b5/$id': { - id: '/foo/_layout-b5/$id' - path: '/$id' - fullPath: '/foo/$id' - preLoaderRoute: typeof FooLayoutB5IdRouteImport - parentRoute: typeof FooLayoutB5RouteRoute + '/jested/_layout-b3/_layout-c2/bar': { + id: '/jested/_layout-b3/_layout-c2/bar' + path: '/bar' + fullPath: '/jested/bar' + preLoaderRoute: typeof JestedLayoutB3LayoutC2BarRouteImport + parentRoute: typeof JestedLayoutB3LayoutC2Route } '/nested/_layout-b1/_layout-c1/bar': { id: '/nested/_layout-b1/_layout-c1/bar' @@ -429,13 +436,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedLayoutB1LayoutC1BarRouteImport parentRoute: typeof NestedLayoutB1LayoutC1Route } - '/jested/_layout-b3/_layout-c2/bar': { - id: '/jested/_layout-b3/_layout-c2/bar' - path: '/bar' - fullPath: '/jested/bar' - preLoaderRoute: typeof JestedLayoutB3LayoutC2BarRouteImport - parentRoute: typeof JestedLayoutB3LayoutC2Route - } } } diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts index 6c5c9950b2..8f2741b206 100644 --- a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts @@ -9,45 +9,45 @@ // 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 groupCLayoutCRouteImport } from './routes/(group-c)/_layout-c' -import { Route as groupBLayoutBRouteImport } from './routes/(group-b)/_layout-b' import { Route as groupALayoutARouteImport } from './routes/(group-a)/_layout-a' -import { Route as groupCLayoutCIndexRouteImport } from './routes/(group-c)/_layout-c/index' -import { Route as groupBLayoutBDashboardRouteImport } from './routes/(group-b)/_layout-b/dashboard' -import { Route as groupALayoutASignupRouteImport } from './routes/(group-a)/_layout-a/signup' +import { Route as groupBLayoutBRouteImport } from './routes/(group-b)/_layout-b' +import { Route as groupCLayoutCRouteImport } from './routes/(group-c)/_layout-c' import { Route as groupALayoutALoginRouteImport } from './routes/(group-a)/_layout-a/login' +import { Route as groupALayoutASignupRouteImport } from './routes/(group-a)/_layout-a/signup' +import { Route as groupBLayoutBDashboardRouteImport } from './routes/(group-b)/_layout-b/dashboard' +import { Route as groupCLayoutCIndexRouteImport } from './routes/(group-c)/_layout-c/index' -const groupCLayoutCRoute = groupCLayoutCRouteImport.update({ - id: '/(group-c)/_layout-c', +const groupALayoutARoute = groupALayoutARouteImport.update({ + id: '/(group-a)/_layout-a', getParentRoute: () => rootRouteImport, } as any) const groupBLayoutBRoute = groupBLayoutBRouteImport.update({ id: '/(group-b)/_layout-b', getParentRoute: () => rootRouteImport, } as any) -const groupALayoutARoute = groupALayoutARouteImport.update({ - id: '/(group-a)/_layout-a', +const groupCLayoutCRoute = groupCLayoutCRouteImport.update({ + id: '/(group-c)/_layout-c', getParentRoute: () => rootRouteImport, } as any) -const groupCLayoutCIndexRoute = groupCLayoutCIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => groupCLayoutCRoute, -} as any) -const groupBLayoutBDashboardRoute = groupBLayoutBDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => groupBLayoutBRoute, +const groupALayoutALoginRoute = groupALayoutALoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => groupALayoutARoute, } as any) const groupALayoutASignupRoute = groupALayoutASignupRouteImport.update({ id: '/signup', path: '/signup', getParentRoute: () => groupALayoutARoute, } as any) -const groupALayoutALoginRoute = groupALayoutALoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => groupALayoutARoute, +const groupBLayoutBDashboardRoute = groupBLayoutBDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => groupBLayoutBRoute, +} as any) +const groupCLayoutCIndexRoute = groupCLayoutCIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => groupCLayoutCRoute, } as any) export interface FileRoutesByFullPath { @@ -96,11 +96,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/(group-c)/_layout-c': { - id: '/(group-c)/_layout-c' + '/(group-a)/_layout-a': { + id: '/(group-a)/_layout-a' path: '' fullPath: '' - preLoaderRoute: typeof groupCLayoutCRouteImport + preLoaderRoute: typeof groupALayoutARouteImport parentRoute: typeof rootRouteImport } '/(group-b)/_layout-b': { @@ -110,26 +110,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof groupBLayoutBRouteImport parentRoute: typeof rootRouteImport } - '/(group-a)/_layout-a': { - id: '/(group-a)/_layout-a' + '/(group-c)/_layout-c': { + id: '/(group-c)/_layout-c' path: '' fullPath: '' - preLoaderRoute: typeof groupALayoutARouteImport + preLoaderRoute: typeof groupCLayoutCRouteImport parentRoute: typeof rootRouteImport } - '/(group-c)/_layout-c/': { - id: '/(group-c)/_layout-c/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof groupCLayoutCIndexRouteImport - parentRoute: typeof groupCLayoutCRoute - } - '/(group-b)/_layout-b/dashboard': { - id: '/(group-b)/_layout-b/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof groupBLayoutBDashboardRouteImport - parentRoute: typeof groupBLayoutBRoute + '/(group-a)/_layout-a/login': { + id: '/(group-a)/_layout-a/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof groupALayoutALoginRouteImport + parentRoute: typeof groupALayoutARoute } '/(group-a)/_layout-a/signup': { id: '/(group-a)/_layout-a/signup' @@ -138,12 +131,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof groupALayoutASignupRouteImport parentRoute: typeof groupALayoutARoute } - '/(group-a)/_layout-a/login': { - id: '/(group-a)/_layout-a/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof groupALayoutALoginRouteImport - parentRoute: typeof groupALayoutARoute + '/(group-b)/_layout-b/dashboard': { + id: '/(group-b)/_layout-b/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof groupBLayoutBDashboardRouteImport + parentRoute: typeof groupBLayoutBRoute + } + '/(group-c)/_layout-c/': { + id: '/(group-c)/_layout-c/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupCLayoutCIndexRouteImport + parentRoute: typeof groupCLayoutCRoute } } } diff --git a/packages/router-generator/tests/generator/nested/routeTree.snapshot.ts b/packages/router-generator/tests/generator/nested/routeTree.snapshot.ts index 9a87dd6953..685b1b29ca 100644 --- a/packages/router-generator/tests/generator/nested/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/nested/routeTree.snapshot.ts @@ -9,29 +9,29 @@ // 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 PathlessLayoutRouteImport } from './routes/_pathlessLayout' -import { Route as PostsRouteRouteImport } from './routes/posts/route' import { Route as BlogRouteRouteImport } from './routes/blog/route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as PostsIndexRouteImport } from './routes/posts/index' +import { Route as PostsRouteRouteImport } from './routes/posts/route' +import { Route as PathlessLayoutSettingsRouteImport } from './routes/_pathlessLayout/settings' import { Route as BlogIndexRouteImport } from './routes/blog/index' -import { Route as BlogStatsRouteImport } from './routes/blog_/stats' import { Route as BlogSlugRouteImport } from './routes/blog/$slug' -import { Route as PathlessLayoutSettingsRouteImport } from './routes/_pathlessLayout/settings' import { Route as BlogBlogIdRouteRouteImport } from './routes/blog_/$blogId/route' +import { Route as BlogStatsRouteImport } from './routes/blog_/stats' +import { Route as PostsIndexRouteImport } from './routes/posts/index' +import { Route as BlogBlogIdSlugRouteRouteImport } from './routes/blog_/$blogId/$slug/route' +import { Route as BlogBlogIdEditRouteImport } from './routes/blog_/$blogId_/edit' import { Route as PostsPostIdIndexRouteImport } from './routes/posts/$postId/index' import { Route as PostsPostIdDeepRouteImport } from './routes/posts/$postId/deep' -import { Route as BlogBlogIdEditRouteImport } from './routes/blog_/$blogId_/edit' -import { Route as BlogBlogIdSlugRouteRouteImport } from './routes/blog_/$blogId/$slug/route' import { Route as BlogBlogIdSlugBarRouteImport } from './routes/blog_/$blogId/$slug_/bar' -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ - id: '/_pathlessLayout', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PostsRouteRoute = PostsRouteRouteImport.update({ - id: '/posts', - path: '/posts', +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', getParentRoute: () => rootRouteImport, } as any) const BlogRouteRoute = BlogRouteRouteImport.update({ @@ -39,41 +39,51 @@ const BlogRouteRoute = BlogRouteRouteImport.update({ path: '/blog', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, } as any) -const PostsIndexRoute = PostsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => PostsRouteRoute, +const PathlessLayoutSettingsRoute = PathlessLayoutSettingsRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => PathlessLayoutRoute, } as any) const BlogIndexRoute = BlogIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => BlogRouteRoute, } as any) -const BlogStatsRoute = BlogStatsRouteImport.update({ - id: '/blog_/stats', - path: '/blog/stats', - getParentRoute: () => rootRouteImport, -} as any) const BlogSlugRoute = BlogSlugRouteImport.update({ id: '/$slug', path: '/$slug', getParentRoute: () => BlogRouteRoute, } as any) -const PathlessLayoutSettingsRoute = PathlessLayoutSettingsRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => PathlessLayoutRoute, -} as any) const BlogBlogIdRouteRoute = BlogBlogIdRouteRouteImport.update({ id: '/blog_/$blogId', path: '/blog/$blogId', getParentRoute: () => rootRouteImport, } as any) +const BlogStatsRoute = BlogStatsRouteImport.update({ + id: '/blog_/stats', + path: '/blog/stats', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRouteRoute, +} as any) +const BlogBlogIdSlugRouteRoute = BlogBlogIdSlugRouteRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogBlogIdRouteRoute, +} as any) +const BlogBlogIdEditRoute = BlogBlogIdEditRouteImport.update({ + id: '/blog_/$blogId_/edit', + path: '/blog/$blogId/edit', + getParentRoute: () => rootRouteImport, +} as any) const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ id: '/$postId/', path: '/$postId/', @@ -84,16 +94,6 @@ const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ path: '/$postId/deep', getParentRoute: () => PostsRouteRoute, } as any) -const BlogBlogIdEditRoute = BlogBlogIdEditRouteImport.update({ - id: '/blog_/$blogId_/edit', - path: '/blog/$blogId/edit', - getParentRoute: () => rootRouteImport, -} as any) -const BlogBlogIdSlugRouteRoute = BlogBlogIdSlugRouteRouteImport.update({ - id: '/$slug', - path: '/$slug', - getParentRoute: () => BlogBlogIdRouteRoute, -} as any) const BlogBlogIdSlugBarRoute = BlogBlogIdSlugBarRouteImport.update({ id: '/$slug_/bar', path: '/$slug/bar', @@ -210,6 +210,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' @@ -217,13 +224,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PathlessLayoutRouteImport parentRoute: typeof rootRouteImport } - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteRouteImport - parentRoute: typeof rootRouteImport - } '/blog': { id: '/blog' path: '/blog' @@ -231,19 +231,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogRouteRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport parentRoute: typeof rootRouteImport } - '/posts/': { - id: '/posts/' - path: '/' - fullPath: '/posts/' - preLoaderRoute: typeof PostsIndexRouteImport - parentRoute: typeof PostsRouteRoute + '/_pathlessLayout/settings': { + id: '/_pathlessLayout/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof PathlessLayoutSettingsRouteImport + parentRoute: typeof PathlessLayoutRoute } '/blog/': { id: '/blog/' @@ -252,13 +252,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogIndexRouteImport parentRoute: typeof BlogRouteRoute } - '/blog_/stats': { - id: '/blog_/stats' - path: '/blog/stats' - fullPath: '/blog/stats' - preLoaderRoute: typeof BlogStatsRouteImport - parentRoute: typeof rootRouteImport - } '/blog/$slug': { id: '/blog/$slug' path: '/$slug' @@ -266,13 +259,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogSlugRouteImport parentRoute: typeof BlogRouteRoute } - '/_pathlessLayout/settings': { - id: '/_pathlessLayout/settings' - path: '/settings' - fullPath: '/settings' - preLoaderRoute: typeof PathlessLayoutSettingsRouteImport - parentRoute: typeof PathlessLayoutRoute - } '/blog_/$blogId': { id: '/blog_/$blogId' path: '/blog/$blogId' @@ -280,6 +266,34 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogBlogIdRouteRouteImport parentRoute: typeof rootRouteImport } + '/blog_/stats': { + id: '/blog_/stats' + path: '/blog/stats' + fullPath: '/blog/stats' + preLoaderRoute: typeof BlogStatsRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog_/$blogId/$slug': { + id: '/blog_/$blogId/$slug' + path: '/$slug' + fullPath: '/blog/$blogId/$slug' + preLoaderRoute: typeof BlogBlogIdSlugRouteRouteImport + parentRoute: typeof BlogBlogIdRouteRoute + } + '/blog_/$blogId_/edit': { + id: '/blog_/$blogId_/edit' + path: '/blog/$blogId/edit' + fullPath: '/blog/$blogId/edit' + preLoaderRoute: typeof BlogBlogIdEditRouteImport + parentRoute: typeof rootRouteImport + } '/posts/$postId/': { id: '/posts/$postId/' path: '/$postId' @@ -294,20 +308,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PostsPostIdDeepRouteImport parentRoute: typeof PostsRouteRoute } - '/blog_/$blogId_/edit': { - id: '/blog_/$blogId_/edit' - path: '/blog/$blogId/edit' - fullPath: '/blog/$blogId/edit' - preLoaderRoute: typeof BlogBlogIdEditRouteImport - parentRoute: typeof rootRouteImport - } - '/blog_/$blogId/$slug': { - id: '/blog_/$blogId/$slug' - path: '/$slug' - fullPath: '/blog/$blogId/$slug' - preLoaderRoute: typeof BlogBlogIdSlugRouteRouteImport - parentRoute: typeof BlogBlogIdRouteRoute - } '/blog_/$blogId/$slug_/bar': { id: '/blog_/$blogId/$slug_/bar' path: '/$slug/bar' diff --git a/packages/router-generator/tests/generator/numbers-in-path/routeTree.snapshot.ts b/packages/router-generator/tests/generator/numbers-in-path/routeTree.snapshot.ts index 76af935401..826e1b8b4f 100644 --- a/packages/router-generator/tests/generator/numbers-in-path/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/numbers-in-path/routeTree.snapshot.ts @@ -9,15 +9,15 @@ // 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 AboutRouteImport } from './routes/about' -import { Route as R03RouteImport } from './routes/03' import { Route as IndexRouteImport } from './routes/index' -import { Route as R02IndexRouteImport } from './routes/02.index' +import { Route as R03RouteImport } from './routes/03' +import { Route as AboutRouteImport } from './routes/about' import { Route as R01ExampleIndexRouteImport } from './routes/01-example/index' +import { Route as R02IndexRouteImport } from './routes/02.index' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const R03Route = R03RouteImport.update({ @@ -25,14 +25,9 @@ const R03Route = R03RouteImport.update({ path: '/03', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const R02IndexRoute = R02IndexRouteImport.update({ - id: '/02/', - path: '/02/', +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', getParentRoute: () => rootRouteImport, } as any) const R01ExampleIndexRoute = R01ExampleIndexRouteImport.update({ @@ -40,6 +35,11 @@ const R01ExampleIndexRoute = R01ExampleIndexRouteImport.update({ path: '/01-example/', getParentRoute: () => rootRouteImport, } as any) +const R02IndexRoute = R02IndexRouteImport.update({ + id: '/02/', + path: '/02/', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -81,11 +81,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/03': { @@ -95,18 +95,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof R03RouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/02/': { - id: '/02/' - path: '/02' - fullPath: '/02/' - preLoaderRoute: typeof R02IndexRouteImport + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport parentRoute: typeof rootRouteImport } '/01-example/': { @@ -116,6 +109,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof R01ExampleIndexRouteImport parentRoute: typeof rootRouteImport } + '/02/': { + id: '/02/' + path: '/02' + fullPath: '/02/' + preLoaderRoute: typeof R02IndexRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts index 53a35bf149..7c7789143a 100644 --- a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts @@ -10,23 +10,23 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as Char91__rootIndexChar93IndexRouteImport } from './routes/[__root-index]/index' -import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as FooRouteImport } from './routes/[_]foo' -import { Route as Char91__literalChar93RouteImport } from './routes/[__literal]' import { Route as _layoutRouteImport } from './routes/__layout' -import { Route as RootIndexIndexRouteImport } from './routes/[_]root-index/index' -import { Route as LayoutNestedRouteImport } from './routes/_layout/_nested' -import { Route as LayoutBarRouteImport } from './routes/_layout/[_]bar' -import { Route as Layout_nested2RouteImport } from './routes/_layout/__nested2' -import { Route as LayoutChar91__doubleChar93RouteImport } from './routes/_layout/[__double]' +import { Route as Char91__literalChar93RouteImport } from './routes/[__literal]' +import { Route as FooRouteImport } from './routes/[_]foo' +import { Route as LayoutRouteImport } from './routes/_layout' import { Route as _layoutQuxRouteImport } from './routes/__layout/[_]qux' -import { Route as LayoutNestedTrailingIndexRouteImport } from './routes/_layout/nested-trailing[_]/index' -import { Route as LayoutNestedIndexIndexRouteImport } from './routes/_layout/[_]nested-index/index' +import { Route as LayoutChar91__doubleChar93RouteImport } from './routes/_layout/[__double]' +import { Route as Layout_nested2RouteImport } from './routes/_layout/__nested2' +import { Route as LayoutBarRouteImport } from './routes/_layout/[_]bar' +import { Route as LayoutNestedRouteImport } from './routes/_layout/_nested' +import { Route as RootIndexIndexRouteImport } from './routes/[_]root-index/index' import { Route as LayoutChar91__doubleIndexChar93IndexRouteImport } from './routes/_layout/[__double-index]/index' -import { Route as LayoutNestedBazRouteImport } from './routes/_layout/_nested/[_]baz' -import { Route as LayoutNestedIndexIdRouteImport } from './routes/_layout/[_]nested-index/$id' -import { Route as Layout_nested2DeepRouteImport } from './routes/_layout/__nested2/[_]deep' import { Route as LayoutChar91__doubleIndexChar93IdRouteImport } from './routes/_layout/[__double-index]/$id' +import { Route as Layout_nested2DeepRouteImport } from './routes/_layout/__nested2/[_]deep' +import { Route as LayoutNestedIndexIndexRouteImport } from './routes/_layout/[_]nested-index/index' +import { Route as LayoutNestedIndexIdRouteImport } from './routes/_layout/[_]nested-index/$id' +import { Route as LayoutNestedBazRouteImport } from './routes/_layout/_nested/[_]baz' +import { Route as LayoutNestedTrailingIndexRouteImport } from './routes/_layout/nested-trailing[_]/index' import { Route as LayoutNestedDeepIndexIndexRouteImport } from './routes/_layout/_nested/[_]deep-index/index' const Char91__rootIndexChar93IndexRoute = @@ -35,13 +35,8 @@ const Char91__rootIndexChar93IndexRoute = path: '/__root-index/', getParentRoute: () => rootRouteImport, } as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) -const FooRoute = FooRouteImport.update({ - id: '/_foo', - path: '/_foo', +const _layoutRoute = _layoutRouteImport.update({ + id: '/__layout', getParentRoute: () => rootRouteImport, } as any) const Char91__literalChar93Route = Char91__literalChar93RouteImport.update({ @@ -49,17 +44,28 @@ const Char91__literalChar93Route = Char91__literalChar93RouteImport.update({ path: '/__literal', getParentRoute: () => rootRouteImport, } as any) -const _layoutRoute = _layoutRouteImport.update({ - id: '/__layout', +const FooRoute = FooRouteImport.update({ + id: '/_foo', + path: '/_foo', getParentRoute: () => rootRouteImport, } as any) -const RootIndexIndexRoute = RootIndexIndexRouteImport.update({ - id: '/_root-index/', - path: '/_root-index/', +const LayoutRoute = LayoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const LayoutNestedRoute = LayoutNestedRouteImport.update({ - id: '/_nested', +const _layoutQuxRoute = _layoutQuxRouteImport.update({ + id: '/_qux', + path: '/_qux', + getParentRoute: () => _layoutRoute, +} as any) +const LayoutChar91__doubleChar93Route = + LayoutChar91__doubleChar93RouteImport.update({ + id: '/__double', + path: '/__double', + getParentRoute: () => LayoutRoute, + } as any) +const Layout_nested2Route = Layout_nested2RouteImport.update({ + id: '/__nested2', getParentRoute: () => LayoutRoute, } as any) const LayoutBarRoute = LayoutBarRouteImport.update({ @@ -67,57 +73,51 @@ const LayoutBarRoute = LayoutBarRouteImport.update({ path: '/_bar', getParentRoute: () => LayoutRoute, } as any) -const Layout_nested2Route = Layout_nested2RouteImport.update({ - id: '/__nested2', +const LayoutNestedRoute = LayoutNestedRouteImport.update({ + id: '/_nested', getParentRoute: () => LayoutRoute, } as any) -const LayoutChar91__doubleChar93Route = - LayoutChar91__doubleChar93RouteImport.update({ - id: '/__double', - path: '/__double', +const RootIndexIndexRoute = RootIndexIndexRouteImport.update({ + id: '/_root-index/', + path: '/_root-index/', + getParentRoute: () => rootRouteImport, +} as any) +const LayoutChar91__doubleIndexChar93IndexRoute = + LayoutChar91__doubleIndexChar93IndexRouteImport.update({ + id: '/__double-index/', + path: '/__double-index/', getParentRoute: () => LayoutRoute, } as any) -const _layoutQuxRoute = _layoutQuxRouteImport.update({ - id: '/_qux', - path: '/_qux', - getParentRoute: () => _layoutRoute, -} as any) -const LayoutNestedTrailingIndexRoute = - LayoutNestedTrailingIndexRouteImport.update({ - id: '/nested-trailing_/', - path: '/nested-trailing_/', +const LayoutChar91__doubleIndexChar93IdRoute = + LayoutChar91__doubleIndexChar93IdRouteImport.update({ + id: '/__double-index/$id', + path: '/__double-index/$id', getParentRoute: () => LayoutRoute, } as any) +const Layout_nested2DeepRoute = Layout_nested2DeepRouteImport.update({ + id: '/_deep', + path: '/_deep', + getParentRoute: () => Layout_nested2Route, +} as any) const LayoutNestedIndexIndexRoute = LayoutNestedIndexIndexRouteImport.update({ id: '/_nested-index/', path: '/_nested-index/', getParentRoute: () => LayoutRoute, } as any) -const LayoutChar91__doubleIndexChar93IndexRoute = - LayoutChar91__doubleIndexChar93IndexRouteImport.update({ - id: '/__double-index/', - path: '/__double-index/', - getParentRoute: () => LayoutRoute, - } as any) -const LayoutNestedBazRoute = LayoutNestedBazRouteImport.update({ - id: '/_baz', - path: '/_baz', - getParentRoute: () => LayoutNestedRoute, -} as any) const LayoutNestedIndexIdRoute = LayoutNestedIndexIdRouteImport.update({ id: '/_nested-index/$id', path: '/_nested-index/$id', getParentRoute: () => LayoutRoute, } as any) -const Layout_nested2DeepRoute = Layout_nested2DeepRouteImport.update({ - id: '/_deep', - path: '/_deep', - getParentRoute: () => Layout_nested2Route, +const LayoutNestedBazRoute = LayoutNestedBazRouteImport.update({ + id: '/_baz', + path: '/_baz', + getParentRoute: () => LayoutNestedRoute, } as any) -const LayoutChar91__doubleIndexChar93IdRoute = - LayoutChar91__doubleIndexChar93IdRouteImport.update({ - id: '/__double-index/$id', - path: '/__double-index/$id', +const LayoutNestedTrailingIndexRoute = + LayoutNestedTrailingIndexRouteImport.update({ + id: '/nested-trailing_/', + path: '/nested-trailing_/', getParentRoute: () => LayoutRoute, } as any) const LayoutNestedDeepIndexIndexRoute = @@ -263,18 +263,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char91__rootIndexChar93IndexRouteImport parentRoute: typeof rootRouteImport } - '/_layout': { - id: '/_layout' + '/__layout': { + id: '/__layout' path: '' fullPath: '/' - preLoaderRoute: typeof LayoutRouteImport - parentRoute: typeof rootRouteImport - } - '/_foo': { - id: '/_foo' - path: '/_foo' - fullPath: '/_foo' - preLoaderRoute: typeof FooRouteImport + preLoaderRoute: typeof _layoutRouteImport parentRoute: typeof rootRouteImport } '/__literal': { @@ -284,25 +277,39 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char91__literalChar93RouteImport parentRoute: typeof rootRouteImport } - '/__layout': { - id: '/__layout' + '/_foo': { + id: '/_foo' + path: '/_foo' + fullPath: '/_foo' + preLoaderRoute: typeof FooRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout': { + id: '/_layout' path: '' fullPath: '/' - preLoaderRoute: typeof _layoutRouteImport + preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/_root-index/': { - id: '/_root-index/' - path: '/_root-index' - fullPath: '/_root-index/' - preLoaderRoute: typeof RootIndexIndexRouteImport - parentRoute: typeof rootRouteImport + '/__layout/_qux': { + id: '/__layout/_qux' + path: '/_qux' + fullPath: '/_qux' + preLoaderRoute: typeof _layoutQuxRouteImport + parentRoute: typeof _layoutRoute } - '/_layout/_nested': { - id: '/_layout/_nested' + '/_layout/__double': { + id: '/_layout/__double' + path: '/__double' + fullPath: '/__double' + preLoaderRoute: typeof LayoutChar91__doubleChar93RouteImport + parentRoute: typeof LayoutRoute + } + '/_layout/__nested2': { + id: '/_layout/__nested2' path: '' fullPath: '/' - preLoaderRoute: typeof LayoutNestedRouteImport + preLoaderRoute: typeof Layout_nested2RouteImport parentRoute: typeof LayoutRoute } '/_layout/_bar': { @@ -312,34 +319,41 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutBarRouteImport parentRoute: typeof LayoutRoute } - '/_layout/__nested2': { - id: '/_layout/__nested2' + '/_layout/_nested': { + id: '/_layout/_nested' path: '' fullPath: '/' - preLoaderRoute: typeof Layout_nested2RouteImport + preLoaderRoute: typeof LayoutNestedRouteImport parentRoute: typeof LayoutRoute } - '/_layout/__double': { - id: '/_layout/__double' - path: '/__double' - fullPath: '/__double' - preLoaderRoute: typeof LayoutChar91__doubleChar93RouteImport - parentRoute: typeof LayoutRoute + '/_root-index/': { + id: '/_root-index/' + path: '/_root-index' + fullPath: '/_root-index/' + preLoaderRoute: typeof RootIndexIndexRouteImport + parentRoute: typeof rootRouteImport } - '/__layout/_qux': { - id: '/__layout/_qux' - path: '/_qux' - fullPath: '/_qux' - preLoaderRoute: typeof _layoutQuxRouteImport - parentRoute: typeof _layoutRoute + '/_layout/__double-index/': { + id: '/_layout/__double-index/' + path: '/__double-index' + fullPath: '/__double-index/' + preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IndexRouteImport + parentRoute: typeof LayoutRoute } - '/_layout/nested-trailing_/': { - id: '/_layout/nested-trailing_/' - path: '/nested-trailing_' - fullPath: '/nested-trailing_/' - preLoaderRoute: typeof LayoutNestedTrailingIndexRouteImport + '/_layout/__double-index/$id': { + id: '/_layout/__double-index/$id' + path: '/__double-index/$id' + fullPath: '/__double-index/$id' + preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IdRouteImport parentRoute: typeof LayoutRoute } + '/_layout/__nested2/_deep': { + id: '/_layout/__nested2/_deep' + path: '/_deep' + fullPath: '/_deep' + preLoaderRoute: typeof Layout_nested2DeepRouteImport + parentRoute: typeof Layout_nested2Route + } '/_layout/_nested-index/': { id: '/_layout/_nested-index/' path: '/_nested-index' @@ -347,11 +361,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutNestedIndexIndexRouteImport parentRoute: typeof LayoutRoute } - '/_layout/__double-index/': { - id: '/_layout/__double-index/' - path: '/__double-index' - fullPath: '/__double-index/' - preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IndexRouteImport + '/_layout/_nested-index/$id': { + id: '/_layout/_nested-index/$id' + path: '/_nested-index/$id' + fullPath: '/_nested-index/$id' + preLoaderRoute: typeof LayoutNestedIndexIdRouteImport parentRoute: typeof LayoutRoute } '/_layout/_nested/_baz': { @@ -361,25 +375,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutNestedBazRouteImport parentRoute: typeof LayoutNestedRoute } - '/_layout/_nested-index/$id': { - id: '/_layout/_nested-index/$id' - path: '/_nested-index/$id' - fullPath: '/_nested-index/$id' - preLoaderRoute: typeof LayoutNestedIndexIdRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/__nested2/_deep': { - id: '/_layout/__nested2/_deep' - path: '/_deep' - fullPath: '/_deep' - preLoaderRoute: typeof Layout_nested2DeepRouteImport - parentRoute: typeof Layout_nested2Route - } - '/_layout/__double-index/$id': { - id: '/_layout/__double-index/$id' - path: '/__double-index/$id' - fullPath: '/__double-index/$id' - preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IdRouteImport + '/_layout/nested-trailing_/': { + id: '/_layout/nested-trailing_/' + path: '/nested-trailing_' + fullPath: '/nested-trailing_/' + preLoaderRoute: typeof LayoutNestedTrailingIndexRouteImport parentRoute: typeof LayoutRoute } '/_layout/_nested/_deep-index/': { diff --git a/packages/router-generator/tests/generator/prefix-suffix/routeTree.snapshot.ts b/packages/router-generator/tests/generator/prefix-suffix/routeTree.snapshot.ts index 5f7f854d9f..2ef38f491d 100644 --- a/packages/router-generator/tests/generator/prefix-suffix/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/prefix-suffix/routeTree.snapshot.ts @@ -9,15 +9,20 @@ // 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 WildcardChar123Char125suffixRouteImport } from './routes/wildcard/{$}suffix' -import { Route as WildcardChar123Char125DotsuffixRouteImport } from './routes/wildcard/{$}[.]suffix' -import { Route as WildcardPrefixChar123Char125RouteImport } from './routes/wildcard/prefix{$}' import { Route as WildcardSplatRouteImport } from './routes/wildcard/$' +import { Route as WildcardPrefixChar123Char125RouteImport } from './routes/wildcard/prefix{$}' +import { Route as WildcardChar123Char125DotsuffixRouteImport } from './routes/wildcard/{$}[.]suffix' +import { Route as WildcardChar123Char125suffixRouteImport } from './routes/wildcard/{$}suffix' -const WildcardChar123Char125suffixRoute = - WildcardChar123Char125suffixRouteImport.update({ - id: '/wildcard/{$}suffix', - path: '/wildcard/{$}suffix', +const WildcardSplatRoute = WildcardSplatRouteImport.update({ + id: '/wildcard/$', + path: '/wildcard/$', + getParentRoute: () => rootRouteImport, +} as any) +const WildcardPrefixChar123Char125Route = + WildcardPrefixChar123Char125RouteImport.update({ + id: '/wildcard/prefix{$}', + path: '/wildcard/prefix{$}', getParentRoute: () => rootRouteImport, } as any) const WildcardChar123Char125DotsuffixRoute = @@ -26,17 +31,12 @@ const WildcardChar123Char125DotsuffixRoute = path: '/wildcard/{$}.suffix', getParentRoute: () => rootRouteImport, } as any) -const WildcardPrefixChar123Char125Route = - WildcardPrefixChar123Char125RouteImport.update({ - id: '/wildcard/prefix{$}', - path: '/wildcard/prefix{$}', +const WildcardChar123Char125suffixRoute = + WildcardChar123Char125suffixRouteImport.update({ + id: '/wildcard/{$}suffix', + path: '/wildcard/{$}suffix', getParentRoute: () => rootRouteImport, } as any) -const WildcardSplatRoute = WildcardSplatRouteImport.update({ - id: '/wildcard/$', - path: '/wildcard/$', - getParentRoute: () => rootRouteImport, -} as any) export interface FileRoutesByFullPath { '/wildcard/$': typeof WildcardSplatRoute @@ -87,18 +87,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/wildcard/{$}suffix': { - id: '/wildcard/{$}suffix' - path: '/wildcard/{$}suffix' - fullPath: '/wildcard/{$}suffix' - preLoaderRoute: typeof WildcardChar123Char125suffixRouteImport - parentRoute: typeof rootRouteImport - } - '/wildcard/{$}.suffix': { - id: '/wildcard/{$}.suffix' - path: '/wildcard/{$}.suffix' - fullPath: '/wildcard/{$}.suffix' - preLoaderRoute: typeof WildcardChar123Char125DotsuffixRouteImport + '/wildcard/$': { + id: '/wildcard/$' + path: '/wildcard/$' + fullPath: '/wildcard/$' + preLoaderRoute: typeof WildcardSplatRouteImport parentRoute: typeof rootRouteImport } '/wildcard/prefix{$}': { @@ -108,11 +101,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof WildcardPrefixChar123Char125RouteImport parentRoute: typeof rootRouteImport } - '/wildcard/$': { - id: '/wildcard/$' - path: '/wildcard/$' - fullPath: '/wildcard/$' - preLoaderRoute: typeof WildcardSplatRouteImport + '/wildcard/{$}.suffix': { + id: '/wildcard/{$}.suffix' + path: '/wildcard/{$}.suffix' + fullPath: '/wildcard/{$}.suffix' + preLoaderRoute: typeof WildcardChar123Char125DotsuffixRouteImport + parentRoute: typeof rootRouteImport + } + '/wildcard/{$}suffix': { + id: '/wildcard/{$}suffix' + path: '/wildcard/{$}suffix' + fullPath: '/wildcard/{$}suffix' + preLoaderRoute: typeof WildcardChar123Char125suffixRouteImport parentRoute: typeof rootRouteImport } } diff --git a/packages/router-generator/tests/generator/regex-tokens-inline/routeTree.snapshot.ts b/packages/router-generator/tests/generator/regex-tokens-inline/routeTree.snapshot.ts index 3909bbe9f4..669cebaf59 100644 --- a/packages/router-generator/tests/generator/regex-tokens-inline/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/regex-tokens-inline/routeTree.snapshot.ts @@ -9,21 +9,21 @@ // 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 DashboardMainLayoutRouteImport } from './routes/dashboard.main-layout' import { Route as IndexPageRouteImport } from './routes/index-page' +import { Route as DashboardMainLayoutRouteImport } from './routes/dashboard.main-layout' import { Route as DashboardHomePageRouteImport } from './routes/dashboard.home-page' import { Route as DashboardSettingsRouteImport } from './routes/dashboard.settings' -const DashboardMainLayoutRoute = DashboardMainLayoutRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const IndexPageRoute = IndexPageRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardMainLayoutRoute = DashboardMainLayoutRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardHomePageRoute = DashboardHomePageRouteImport.update({ id: '/', path: '/', @@ -68,13 +68,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardMainLayoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -82,6 +75,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexPageRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardMainLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/packages/router-generator/tests/generator/regex-tokens-json/routeTree.snapshot.ts b/packages/router-generator/tests/generator/regex-tokens-json/routeTree.snapshot.ts index 3909bbe9f4..669cebaf59 100644 --- a/packages/router-generator/tests/generator/regex-tokens-json/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/regex-tokens-json/routeTree.snapshot.ts @@ -9,21 +9,21 @@ // 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 DashboardMainLayoutRouteImport } from './routes/dashboard.main-layout' import { Route as IndexPageRouteImport } from './routes/index-page' +import { Route as DashboardMainLayoutRouteImport } from './routes/dashboard.main-layout' import { Route as DashboardHomePageRouteImport } from './routes/dashboard.home-page' import { Route as DashboardSettingsRouteImport } from './routes/dashboard.settings' -const DashboardMainLayoutRoute = DashboardMainLayoutRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const IndexPageRoute = IndexPageRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardMainLayoutRoute = DashboardMainLayoutRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardHomePageRoute = DashboardHomePageRouteImport.update({ id: '/', path: '/', @@ -68,13 +68,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardMainLayoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -82,6 +75,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexPageRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardMainLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/packages/router-generator/tests/generator/regex-tokens-plus-json/routeTree.snapshot.ts b/packages/router-generator/tests/generator/regex-tokens-plus-json/routeTree.snapshot.ts index b9b7117716..74c38f90ad 100644 --- a/packages/router-generator/tests/generator/regex-tokens-plus-json/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/regex-tokens-plus-json/routeTree.snapshot.ts @@ -9,21 +9,21 @@ // 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 DashboardChar43layoutRouteImport } from './routes/dashboard/+layout' import { Route as Char43pageRouteImport } from './routes/+page' +import { Route as DashboardChar43layoutRouteImport } from './routes/dashboard/+layout' import { Route as DashboardChar43pageRouteImport } from './routes/dashboard/+page' import { Route as DashboardSettingsRouteImport } from './routes/dashboard/settings' -const DashboardChar43layoutRoute = DashboardChar43layoutRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) const Char43pageRoute = Char43pageRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const DashboardChar43layoutRoute = DashboardChar43layoutRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const DashboardChar43pageRoute = DashboardChar43pageRouteImport.update({ id: '/', path: '/', @@ -68,13 +68,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardChar43layoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -82,6 +75,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char43pageRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardChar43layoutRouteImport + parentRoute: typeof rootRouteImport + } '/dashboard/': { id: '/dashboard/' path: '/' diff --git a/packages/router-generator/tests/generator/root-export-specifier/routeTree.snapshot.ts b/packages/router-generator/tests/generator/root-export-specifier/routeTree.snapshot.ts index 59499d9fbf..12bc916ae4 100644 --- a/packages/router-generator/tests/generator/root-export-specifier/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/root-export-specifier/routeTree.snapshot.ts @@ -9,19 +9,19 @@ // 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 AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } } } 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 f2e84bbbb7..e76c675d2c 100644 --- a/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts @@ -12,13 +12,13 @@ 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' -import { Route as fooAsdfLayoutFooRouteImport } from './routes/(foo)/asdf/_layout.foo' -import { Route as fooAsdfbarIdRouteImport } from './routes/(foo)/asdf/(bar)/$id' +import { Route as fooAsdfLayoutRouteImport } from './routes/(foo)/asdf/_layout' import { Route as fooAsdfanotherGroupLayoutRouteImport } from './routes/(foo)/asdf/(another-group)/_layout' -import { Route as fooAsdfbarLayoutAboutRouteImport } from './routes/(foo)/asdf/(bar)/_layout.about' +import { Route as fooAsdfbarIdRouteImport } from './routes/(foo)/asdf/(bar)/$id' +import { Route as fooAsdfLayoutFooRouteImport } from './routes/(foo)/asdf/_layout.foo' import { Route as fooAsdfanotherGroupLayoutBazRouteImport } from './routes/(foo)/asdf/(another-group)/_layout.baz' +import { Route as fooAsdfbarLayoutAboutRouteImport } from './routes/(foo)/asdf/(bar)/_layout.about' const fooAsdfbarLayoutXyzLazyRouteImport = createFileRoute( '/(foo)/asdf/(bar)/_layout/xyz', @@ -28,24 +28,14 @@ const barBarRoute = barBarRouteImport.update({ id: '/(bar)/_bar', getParentRoute: () => rootRouteImport, } as any) -const fooAsdfLayoutRoute = fooAsdfLayoutRouteImport.update({ - id: '/(foo)/asdf/_layout', - path: '/asdf', - getParentRoute: () => rootRouteImport, -} as any) const barBarHelloRoute = barBarHelloRouteImport.update({ id: '/hello', path: '/hello', getParentRoute: () => barBarRoute, } as any) -const fooAsdfLayoutFooRoute = fooAsdfLayoutFooRouteImport.update({ - id: '/foo', - path: '/foo', - getParentRoute: () => fooAsdfLayoutRoute, -} as any) -const fooAsdfbarIdRoute = fooAsdfbarIdRouteImport.update({ - id: '/(foo)/asdf/(bar)/$id', - path: '/asdf/$id', +const fooAsdfLayoutRoute = fooAsdfLayoutRouteImport.update({ + id: '/(foo)/asdf/_layout', + path: '/asdf', getParentRoute: () => rootRouteImport, } as any) const fooAsdfanotherGroupLayoutRoute = @@ -54,6 +44,27 @@ const fooAsdfanotherGroupLayoutRoute = path: '/asdf', getParentRoute: () => rootRouteImport, } as any) +const fooAsdfbarIdRoute = fooAsdfbarIdRouteImport.update({ + id: '/(foo)/asdf/(bar)/$id', + path: '/asdf/$id', + getParentRoute: () => rootRouteImport, +} as any) +const fooAsdfLayoutFooRoute = fooAsdfLayoutFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => fooAsdfLayoutRoute, +} as any) +const fooAsdfanotherGroupLayoutBazRoute = + fooAsdfanotherGroupLayoutBazRouteImport.update({ + id: '/baz', + path: '/baz', + getParentRoute: () => fooAsdfanotherGroupLayoutRoute, + } as any) +const fooAsdfbarLayoutAboutRoute = fooAsdfbarLayoutAboutRouteImport.update({ + id: '/(foo)/asdf/(bar)/_layout/about', + path: '/asdf/about', + getParentRoute: () => rootRouteImport, +} as any) const fooAsdfbarLayoutXyzLazyRoute = fooAsdfbarLayoutXyzLazyRouteImport .update({ id: '/(foo)/asdf/(bar)/_layout/xyz', @@ -63,17 +74,6 @@ const fooAsdfbarLayoutXyzLazyRoute = fooAsdfbarLayoutXyzLazyRouteImport .lazy(() => import('./routes/(foo)/asdf/(bar)/_layout.xyz.lazy').then((d) => d.Route), ) -const fooAsdfbarLayoutAboutRoute = fooAsdfbarLayoutAboutRouteImport.update({ - id: '/(foo)/asdf/(bar)/_layout/about', - path: '/asdf/about', - getParentRoute: () => rootRouteImport, -} as any) -const fooAsdfanotherGroupLayoutBazRoute = - fooAsdfanotherGroupLayoutBazRouteImport.update({ - id: '/baz', - path: '/baz', - getParentRoute: () => fooAsdfanotherGroupLayoutRoute, - } as any) export interface FileRoutesByFullPath { '/hello': typeof barBarHelloRoute @@ -155,13 +155,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof barBarRouteImport parentRoute: typeof rootRouteImport } - '/(foo)/asdf/_layout': { - id: '/(foo)/asdf/_layout' - path: '/asdf' - fullPath: '/asdf' - preLoaderRoute: typeof fooAsdfLayoutRouteImport - parentRoute: typeof rootRouteImport - } '/(bar)/_bar/hello': { id: '/(bar)/_bar/hello' path: '/hello' @@ -169,18 +162,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof barBarHelloRouteImport parentRoute: typeof barBarRoute } - '/(foo)/asdf/_layout/foo': { - id: '/(foo)/asdf/_layout/foo' - path: '/foo' - fullPath: '/asdf/foo' - preLoaderRoute: typeof fooAsdfLayoutFooRouteImport - parentRoute: typeof fooAsdfLayoutRoute - } - '/(foo)/asdf/(bar)/$id': { - id: '/(foo)/asdf/(bar)/$id' - path: '/asdf/$id' - fullPath: '/asdf/$id' - preLoaderRoute: typeof fooAsdfbarIdRouteImport + '/(foo)/asdf/_layout': { + id: '/(foo)/asdf/_layout' + path: '/asdf' + fullPath: '/asdf' + preLoaderRoute: typeof fooAsdfLayoutRouteImport parentRoute: typeof rootRouteImport } '/(foo)/asdf/(another-group)/_layout': { @@ -190,19 +176,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof fooAsdfanotherGroupLayoutRouteImport parentRoute: typeof rootRouteImport } - '/(foo)/asdf/(bar)/_layout/xyz': { - id: '/(foo)/asdf/(bar)/_layout/xyz' - path: '/asdf/xyz' - fullPath: '/asdf/xyz' - preLoaderRoute: typeof fooAsdfbarLayoutXyzLazyRouteImport + '/(foo)/asdf/(bar)/$id': { + id: '/(foo)/asdf/(bar)/$id' + path: '/asdf/$id' + fullPath: '/asdf/$id' + preLoaderRoute: typeof fooAsdfbarIdRouteImport parentRoute: typeof rootRouteImport } - '/(foo)/asdf/(bar)/_layout/about': { - id: '/(foo)/asdf/(bar)/_layout/about' - path: '/asdf/about' - fullPath: '/asdf/about' - preLoaderRoute: typeof fooAsdfbarLayoutAboutRouteImport - parentRoute: typeof rootRouteImport + '/(foo)/asdf/_layout/foo': { + id: '/(foo)/asdf/_layout/foo' + path: '/foo' + fullPath: '/asdf/foo' + preLoaderRoute: typeof fooAsdfLayoutFooRouteImport + parentRoute: typeof fooAsdfLayoutRoute } '/(foo)/asdf/(another-group)/_layout/baz': { id: '/(foo)/asdf/(another-group)/_layout/baz' @@ -211,6 +197,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof fooAsdfanotherGroupLayoutBazRouteImport parentRoute: typeof fooAsdfanotherGroupLayoutRoute } + '/(foo)/asdf/(bar)/_layout/about': { + id: '/(foo)/asdf/(bar)/_layout/about' + path: '/asdf/about' + fullPath: '/asdf/about' + preLoaderRoute: typeof fooAsdfbarLayoutAboutRouteImport + parentRoute: typeof rootRouteImport + } + '/(foo)/asdf/(bar)/_layout/xyz': { + id: '/(foo)/asdf/(bar)/_layout/xyz' + path: '/asdf/xyz' + fullPath: '/asdf/xyz' + preLoaderRoute: typeof fooAsdfbarLayoutXyzLazyRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/routeFileIgnore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/routeFileIgnore/routeTree.snapshot.ts index 33a4093b9e..ceb6811cf7 100644 --- a/packages/router-generator/tests/generator/routeFileIgnore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/routeFileIgnore/routeTree.snapshot.ts @@ -9,19 +9,19 @@ // 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 BlogRouteRouteImport } from './routes/blog.route' import { Route as IndexRouteImport } from './routes/index' +import { Route as BlogRouteRouteImport } from './routes/blog.route' -const BlogRouteRoute = BlogRouteRouteImport.update({ - id: '/blog', - path: '/blog', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/blog': { - id: '/blog' - path: '/blog' - fullPath: '/blog' - preLoaderRoute: typeof BlogRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/routeFilePrefix/routeTree.snapshot.ts b/packages/router-generator/tests/generator/routeFilePrefix/routeTree.snapshot.ts index 46ee215c03..9805633c47 100644 --- a/packages/router-generator/tests/generator/routeFilePrefix/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/routeFilePrefix/routeTree.snapshot.ts @@ -9,19 +9,19 @@ // 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/r&__root' -import { Route as BlogRouteRouteImport } from './routes/r&blog.route' import { Route as IndexRouteImport } from './routes/r&index' +import { Route as BlogRouteRouteImport } from './routes/r&blog.route' -const BlogRouteRoute = BlogRouteRouteImport.update({ - id: '/blog', - path: '/blog', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/blog': { - id: '/blog' - path: '/blog' - fullPath: '/blog' - preLoaderRoute: typeof BlogRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/single-level/routeTree.snapshot.ts b/packages/router-generator/tests/generator/single-level/routeTree.snapshot.ts index b60f0b934f..12b48f052a 100644 --- a/packages/router-generator/tests/generator/single-level/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/single-level/routeTree.snapshot.ts @@ -9,19 +9,19 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsRouteImport } from './routes/posts' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/posts': { - id: '/posts' - path: '/posts' - fullPath: '/posts' - preLoaderRoute: typeof PostsRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/types-disabled/routeTree.snapshot.js b/packages/router-generator/tests/generator/types-disabled/routeTree.snapshot.js index 9c410eea0f..4dc8f12ad7 100644 --- a/packages/router-generator/tests/generator/types-disabled/routeTree.snapshot.js +++ b/packages/router-generator/tests/generator/types-disabled/routeTree.snapshot.js @@ -9,24 +9,19 @@ // 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 PostsRouteImport } from './routes/posts' import { Route as IndexRouteImport } from './routes/index' -import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as PostsRouteImport } from './routes/posts' import { Route as PostsPostIdRouteImport } from './routes/posts/$postId' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' -const PostsRoute = PostsRouteImport.update({ - id: '/posts', - path: '/posts', - getParentRoute: () => rootRouteImport, -}) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, }) -const UsersUserIdRoute = UsersUserIdRouteImport.update({ - id: '/users/$userId', - path: '/users/$userId', +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', getParentRoute: () => rootRouteImport, }) const PostsPostIdRoute = PostsPostIdRouteImport.update({ @@ -34,6 +29,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({ path: '/$postId', getParentRoute: () => PostsRoute, }) +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/users/$userId', + path: '/users/$userId', + getParentRoute: () => rootRouteImport, +}) const PostsRouteChildren = { PostsPostIdRoute: PostsPostIdRoute, diff --git a/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.snapshot.ts index 3d3c2de9d5..6b16924d5b 100644 --- a/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.snapshot.ts @@ -9,51 +9,61 @@ // 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 layoutRouteImport } from './routes/layout' import { Route as indexRouteImport } from './routes/index' -import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as layoutRouteImport } from './routes/layout' import { Route as pagesRouteImport } from './routes/pages' -import { Route as HelloIndexRouteImport } from './routes/subtree/index' -import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' -import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' -import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' -const layoutRoute = layoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) const indexRoute = indexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const dbDashboardRoute = dbDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => layoutRoute, +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, } as any) const pagesRoute = pagesRouteImport.update({ id: '/$lang/', path: '/$lang/', getParentRoute: () => rootRouteImport, } as any) -const HelloIndexRoute = HelloIndexRouteImport.update({ - id: '/hello/', - path: '/hello/', +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', getParentRoute: () => layoutRoute, } as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ id: '/invoices', path: '/invoices', getParentRoute: () => dbDashboardRoute, } as any) -const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => dbDashboardRoute, + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, } as any) const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ id: '/hello/foo/', @@ -65,16 +75,6 @@ const HelloFooIdRoute = HelloFooIdRouteImport.update({ path: '/hello/foo/$id', getParentRoute: () => layoutRoute, } as any) -const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) -const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof indexRoute @@ -158,13 +158,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -172,12 +165,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof indexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/dashboard': { - id: '/_layout/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof dbDashboardRouteImport - parentRoute: typeof layoutRoute + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport } '/$lang/': { id: '/$lang/' @@ -186,13 +179,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof pagesRouteImport parentRoute: typeof rootRouteImport } - '/_layout/hello/': { - id: '/_layout/hello/' - path: '/hello' - fullPath: '/hello/' - preLoaderRoute: typeof HelloIndexRouteImport + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport parentRoute: typeof layoutRoute } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } '/_layout/dashboard/invoices': { id: '/_layout/dashboard/invoices' path: '/invoices' @@ -200,12 +200,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof dbDashboardInvoicesRouteImport parentRoute: typeof dbDashboardRoute } - '/_layout/dashboard/': { - id: '/_layout/dashboard/' + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello/' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof dbDashboardIndexRouteImport - parentRoute: typeof dbDashboardRoute + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute } '/_layout/hello/foo/': { id: '/_layout/hello/foo/' @@ -221,20 +235,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HelloFooIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/dashboard/invoices/$id': { - id: '/_layout/dashboard/invoices/$id' - path: '/$id' - fullPath: '/dashboard/invoices/$id' - preLoaderRoute: typeof dbInvoiceDetailRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } - '/_layout/dashboard/invoices/': { - id: '/_layout/dashboard/invoices/' - path: '/' - fullPath: '/dashboard/invoices/' - preLoaderRoute: typeof dbInvoicesIndexRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } } } diff --git a/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.snapshot.ts index 3d3c2de9d5..6b16924d5b 100644 --- a/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.snapshot.ts @@ -9,51 +9,61 @@ // 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 layoutRouteImport } from './routes/layout' import { Route as indexRouteImport } from './routes/index' -import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as layoutRouteImport } from './routes/layout' import { Route as pagesRouteImport } from './routes/pages' -import { Route as HelloIndexRouteImport } from './routes/subtree/index' -import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' -import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' -import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' -const layoutRoute = layoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) const indexRoute = indexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const dbDashboardRoute = dbDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => layoutRoute, +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, } as any) const pagesRoute = pagesRouteImport.update({ id: '/$lang/', path: '/$lang/', getParentRoute: () => rootRouteImport, } as any) -const HelloIndexRoute = HelloIndexRouteImport.update({ - id: '/hello/', - path: '/hello/', +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', getParentRoute: () => layoutRoute, } as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ id: '/invoices', path: '/invoices', getParentRoute: () => dbDashboardRoute, } as any) -const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => dbDashboardRoute, + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, } as any) const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ id: '/hello/foo/', @@ -65,16 +75,6 @@ const HelloFooIdRoute = HelloFooIdRouteImport.update({ path: '/hello/foo/$id', getParentRoute: () => layoutRoute, } as any) -const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) -const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof indexRoute @@ -158,13 +158,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -172,12 +165,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof indexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/dashboard': { - id: '/_layout/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof dbDashboardRouteImport - parentRoute: typeof layoutRoute + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport } '/$lang/': { id: '/$lang/' @@ -186,13 +179,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof pagesRouteImport parentRoute: typeof rootRouteImport } - '/_layout/hello/': { - id: '/_layout/hello/' - path: '/hello' - fullPath: '/hello/' - preLoaderRoute: typeof HelloIndexRouteImport + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport parentRoute: typeof layoutRoute } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } '/_layout/dashboard/invoices': { id: '/_layout/dashboard/invoices' path: '/invoices' @@ -200,12 +200,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof dbDashboardInvoicesRouteImport parentRoute: typeof dbDashboardRoute } - '/_layout/dashboard/': { - id: '/_layout/dashboard/' + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello/' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof dbDashboardIndexRouteImport - parentRoute: typeof dbDashboardRoute + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute } '/_layout/hello/foo/': { id: '/_layout/hello/foo/' @@ -221,20 +235,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HelloFooIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/dashboard/invoices/$id': { - id: '/_layout/dashboard/invoices/$id' - path: '/$id' - fullPath: '/dashboard/invoices/$id' - preLoaderRoute: typeof dbInvoiceDetailRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } - '/_layout/dashboard/invoices/': { - id: '/_layout/dashboard/invoices/' - path: '/' - fullPath: '/dashboard/invoices/' - preLoaderRoute: typeof dbInvoicesIndexRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } } } diff --git a/packages/router-generator/tests/generator/virtual-config-file-tsconfig-paths/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-config-file-tsconfig-paths/routeTree.snapshot.ts index 3d3c2de9d5..6b16924d5b 100644 --- a/packages/router-generator/tests/generator/virtual-config-file-tsconfig-paths/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-config-file-tsconfig-paths/routeTree.snapshot.ts @@ -9,51 +9,61 @@ // 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 layoutRouteImport } from './routes/layout' import { Route as indexRouteImport } from './routes/index' -import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as layoutRouteImport } from './routes/layout' import { Route as pagesRouteImport } from './routes/pages' -import { Route as HelloIndexRouteImport } from './routes/subtree/index' -import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' -import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' -import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' -const layoutRoute = layoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) const indexRoute = indexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const dbDashboardRoute = dbDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => layoutRoute, +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, } as any) const pagesRoute = pagesRouteImport.update({ id: '/$lang/', path: '/$lang/', getParentRoute: () => rootRouteImport, } as any) -const HelloIndexRoute = HelloIndexRouteImport.update({ - id: '/hello/', - path: '/hello/', +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', getParentRoute: () => layoutRoute, } as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ id: '/invoices', path: '/invoices', getParentRoute: () => dbDashboardRoute, } as any) -const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => dbDashboardRoute, + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, } as any) const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ id: '/hello/foo/', @@ -65,16 +75,6 @@ const HelloFooIdRoute = HelloFooIdRouteImport.update({ path: '/hello/foo/$id', getParentRoute: () => layoutRoute, } as any) -const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) -const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof indexRoute @@ -158,13 +158,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -172,12 +165,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof indexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/dashboard': { - id: '/_layout/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof dbDashboardRouteImport - parentRoute: typeof layoutRoute + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport } '/$lang/': { id: '/$lang/' @@ -186,13 +179,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof pagesRouteImport parentRoute: typeof rootRouteImport } - '/_layout/hello/': { - id: '/_layout/hello/' - path: '/hello' - fullPath: '/hello/' - preLoaderRoute: typeof HelloIndexRouteImport + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport parentRoute: typeof layoutRoute } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } '/_layout/dashboard/invoices': { id: '/_layout/dashboard/invoices' path: '/invoices' @@ -200,12 +200,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof dbDashboardInvoicesRouteImport parentRoute: typeof dbDashboardRoute } - '/_layout/dashboard/': { - id: '/_layout/dashboard/' + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello/' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof dbDashboardIndexRouteImport - parentRoute: typeof dbDashboardRoute + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute } '/_layout/hello/foo/': { id: '/_layout/hello/foo/' @@ -221,20 +235,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HelloFooIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/dashboard/invoices/$id': { - id: '/_layout/dashboard/invoices/$id' - path: '/$id' - fullPath: '/dashboard/invoices/$id' - preLoaderRoute: typeof dbInvoiceDetailRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } - '/_layout/dashboard/invoices/': { - id: '/_layout/dashboard/invoices/' - path: '/' - fullPath: '/dashboard/invoices/' - preLoaderRoute: typeof dbInvoicesIndexRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } } } diff --git a/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routeTree.snapshot.ts new file mode 100644 index 0000000000..6a5263d55b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routeTree.snapshot.ts @@ -0,0 +1,77 @@ +/* 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 directRouteImport } from './routes/direct' +import { Route as MountedDotpathIndexRouteImport } from './routes/physical-routes/index' + +const directRoute = directRouteImport.update({ + id: '/direct.path', + path: '/direct.path', + getParentRoute: () => rootRouteImport, +} as any) +const MountedDotpathIndexRoute = MountedDotpathIndexRouteImport.update({ + id: '/mounted.path/', + path: '/mounted.path/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/direct.path': typeof directRoute + '/mounted.path/': typeof MountedDotpathIndexRoute +} +export interface FileRoutesByTo { + '/direct.path': typeof directRoute + '/mounted.path': typeof MountedDotpathIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/direct.path': typeof directRoute + '/mounted.path/': typeof MountedDotpathIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/direct.path' | '/mounted.path/' + fileRoutesByTo: FileRoutesByTo + to: '/direct.path' | '/mounted.path' + id: '__root__' | '/direct.path' | '/mounted.path/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + directRoute: typeof directRoute + MountedDotpathIndexRoute: typeof MountedDotpathIndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/direct.path': { + id: '/direct.path' + path: '/direct.path' + fullPath: '/direct.path' + preLoaderRoute: typeof directRouteImport + parentRoute: typeof rootRouteImport + } + '/mounted.path/': { + id: '/mounted.path/' + path: '/mounted.path' + fullPath: '/mounted.path/' + preLoaderRoute: typeof MountedDotpathIndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + directRoute: directRoute, + MountedDotpathIndexRoute: MountedDotpathIndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/direct.tsx b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/direct.tsx new file mode 100644 index 0000000000..e56ad97d40 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/direct.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/direct.path')({ + component: () => 'Direct', +}) diff --git a/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/physical-routes/index.tsx b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/physical-routes/index.tsx new file mode 100644 index 0000000000..2e768e44ac --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/physical-routes/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/mounted.path/')({ + component: () => 'Mounted index', +}) diff --git a/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/root.tsx b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/root.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-explicit-dotted-paths/routes/root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.snapshot.ts index 9f60b82e06..37de7dc290 100644 --- a/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.snapshot.ts @@ -11,8 +11,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as FooBarRouteImport } from './routes/foo/bar' -import { Route as FooBarDetailsRouteImport } from './routes/foo/bar/details' import { Route as FooBarHomeRouteImport } from './routes/foo/bar/home' +import { Route as FooBarDetailsRouteImport } from './routes/foo/bar/details' const IndexRoute = IndexRouteImport.update({ id: '/', @@ -24,16 +24,16 @@ const FooBarRoute = FooBarRouteImport.update({ path: '/foo/bar', getParentRoute: () => rootRouteImport, } as any) -const FooBarDetailsRoute = FooBarDetailsRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => FooBarRoute, -} as any) const FooBarHomeRoute = FooBarHomeRouteImport.update({ id: '/', path: '/', getParentRoute: () => FooBarRoute, } as any) +const FooBarDetailsRoute = FooBarDetailsRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => FooBarRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -82,13 +82,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FooBarRouteImport parentRoute: typeof rootRouteImport } - '/foo/bar/$id': { - id: '/foo/bar/$id' - path: '/$id' - fullPath: '/foo/bar/$id' - preLoaderRoute: typeof FooBarDetailsRouteImport - parentRoute: typeof FooBarRoute - } '/foo/bar/': { id: '/foo/bar/' path: '/' @@ -96,6 +89,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FooBarHomeRouteImport parentRoute: typeof FooBarRoute } + '/foo/bar/$id': { + id: '/foo/bar/$id' + path: '/$id' + fullPath: '/foo/bar/$id' + preLoaderRoute: typeof FooBarDetailsRouteImport + parentRoute: typeof FooBarRoute + } } } diff --git a/packages/router-generator/tests/generator/virtual-inside-with-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-inside-with-escaped-underscore/routeTree.snapshot.ts index 664cea729c..340a1f0f5e 100644 --- a/packages/router-generator/tests/generator/virtual-inside-with-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-inside-with-escaped-underscore/routeTree.snapshot.ts @@ -10,18 +10,18 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' -import { Route as NestedCallbackRouteImport } from './routes/nested/callback' -import { Route as NestedAuthRouteImport } from './routes/nested/auth' import { Route as NestedHomeRouteImport } from './routes/nested/home' +import { Route as NestedAuthRouteImport } from './routes/nested/auth' +import { Route as NestedCallbackRouteImport } from './routes/nested/callback' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const NestedCallbackRoute = NestedCallbackRouteImport.update({ - id: '/nested/_callback', - path: '/nested/_callback', +const NestedHomeRoute = NestedHomeRouteImport.update({ + id: '/nested/', + path: '/nested/', getParentRoute: () => rootRouteImport, } as any) const NestedAuthRoute = NestedAuthRouteImport.update({ @@ -29,9 +29,9 @@ const NestedAuthRoute = NestedAuthRouteImport.update({ path: '/nested/_auth', getParentRoute: () => rootRouteImport, } as any) -const NestedHomeRoute = NestedHomeRouteImport.update({ - id: '/nested/', - path: '/nested/', +const NestedCallbackRoute = NestedCallbackRouteImport.update({ + id: '/nested/_callback', + path: '/nested/_callback', getParentRoute: () => rootRouteImport, } as any) @@ -78,11 +78,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/nested/_callback': { - id: '/nested/_callback' - path: '/nested/_callback' - fullPath: '/nested/_callback' - preLoaderRoute: typeof NestedCallbackRouteImport + '/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested/' + preLoaderRoute: typeof NestedHomeRouteImport parentRoute: typeof rootRouteImport } '/nested/_auth': { @@ -92,11 +92,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NestedAuthRouteImport parentRoute: typeof rootRouteImport } - '/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested/' - preLoaderRoute: typeof NestedHomeRouteImport + '/nested/_callback': { + id: '/nested/_callback' + path: '/nested/_callback' + fullPath: '/nested/_callback' + preLoaderRoute: typeof NestedCallbackRouteImport parentRoute: typeof rootRouteImport } } diff --git a/packages/router-generator/tests/generator/virtual-nested-layouts-with-virtual-route/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-nested-layouts-with-virtual-route/routeTree.snapshot.ts index e991cd3964..7268f402ae 100644 --- a/packages/router-generator/tests/generator/virtual-nested-layouts-with-virtual-route/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-nested-layouts-with-virtual-route/routeTree.snapshot.ts @@ -9,35 +9,35 @@ // 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 layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as homeRouteImport } from './routes/home' +import { Route as layoutFirstLayoutRouteImport } from './routes/layout/first-layout' import { Route as layoutSecondLayoutRouteImport } from './routes/layout/second-layout' -import { Route as bRouteImport } from './routes/b' import { Route as aRouteImport } from './routes/a' +import { Route as bRouteImport } from './routes/b' -const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ - id: '/_first', - getParentRoute: () => rootRouteImport, -} as any) const homeRoute = homeRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const layoutFirstLayoutRoute = layoutFirstLayoutRouteImport.update({ + id: '/_first', + getParentRoute: () => rootRouteImport, +} as any) const layoutSecondLayoutRoute = layoutSecondLayoutRouteImport.update({ id: '/_second-layout', getParentRoute: () => layoutFirstLayoutRoute, } as any) -const bRoute = bRouteImport.update({ - id: '/route-without-file/layout-b', - path: '/route-without-file/layout-b', - getParentRoute: () => layoutSecondLayoutRoute, -} as any) const aRoute = aRouteImport.update({ id: '/route-without-file/layout-a', path: '/route-without-file/layout-a', getParentRoute: () => layoutSecondLayoutRoute, } as any) +const bRoute = bRouteImport.update({ + id: '/route-without-file/layout-b', + path: '/route-without-file/layout-b', + getParentRoute: () => layoutSecondLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof homeRoute @@ -81,13 +81,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_first': { - id: '/_first' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutFirstLayoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -95,6 +88,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof homeRouteImport parentRoute: typeof rootRouteImport } + '/_first': { + id: '/_first' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutFirstLayoutRouteImport + parentRoute: typeof rootRouteImport + } '/_first/_second-layout': { id: '/_first/_second-layout' path: '' @@ -102,13 +102,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof layoutSecondLayoutRouteImport parentRoute: typeof layoutFirstLayoutRoute } - '/_first/_second-layout/route-without-file/layout-b': { - id: '/_first/_second-layout/route-without-file/layout-b' - path: '/route-without-file/layout-b' - fullPath: '/route-without-file/layout-b' - 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' @@ -116,6 +109,13 @@ declare module '@tanstack/react-router' { 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' + fullPath: '/route-without-file/layout-b' + preLoaderRoute: typeof bRouteImport + parentRoute: typeof layoutSecondLayoutRoute + } } } diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routeTree.snapshot.ts new file mode 100644 index 0000000000..e9d9980378 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routeTree.snapshot.ts @@ -0,0 +1,101 @@ +/* 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.route' +import { Route as indexDotrouteRouteImport } from './routes/index.route' +import { Route as pathlessDotlayoutRouteImport } from './routes/pathless.layout' +import { Route as subpathDotrouteRouteImport } from './routes/subpath.route' + +const indexDotrouteRoute = indexDotrouteRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const pathlessDotlayoutRoute = pathlessDotlayoutRouteImport.update({ + id: '/_pathless.layout', + getParentRoute: () => rootRouteImport, +} as any) +const subpathDotrouteRoute = subpathDotrouteRouteImport.update({ + id: '/subpath', + path: '/subpath', + getParentRoute: () => pathlessDotlayoutRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof indexDotrouteRoute + '/subpath': typeof subpathDotrouteRoute +} +export interface FileRoutesByTo { + '/': typeof indexDotrouteRoute + '/subpath': typeof subpathDotrouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof indexDotrouteRoute + '/_pathless.layout': typeof pathlessDotlayoutRouteWithChildren + '/_pathless.layout/subpath': typeof subpathDotrouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/subpath' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/subpath' + id: '__root__' | '/' | '/_pathless.layout' | '/_pathless.layout/subpath' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + indexDotrouteRoute: typeof indexDotrouteRoute + pathlessDotlayoutRoute: typeof pathlessDotlayoutRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof indexDotrouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathless.layout': { + id: '/_pathless.layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof pathlessDotlayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathless.layout/subpath': { + id: '/_pathless.layout/subpath' + path: '/subpath' + fullPath: '/subpath' + preLoaderRoute: typeof subpathDotrouteRouteImport + parentRoute: typeof pathlessDotlayoutRoute + } + } +} + +interface pathlessDotlayoutRouteChildren { + subpathDotrouteRoute: typeof subpathDotrouteRoute +} + +const pathlessDotlayoutRouteChildren: pathlessDotlayoutRouteChildren = { + subpathDotrouteRoute: subpathDotrouteRoute, +} + +const pathlessDotlayoutRouteWithChildren = + pathlessDotlayoutRoute._addFileChildren(pathlessDotlayoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + indexDotrouteRoute: indexDotrouteRoute, + pathlessDotlayoutRoute: pathlessDotlayoutRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx new file mode 100644 index 0000000000..379428d7b2 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () => 'Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx new file mode 100644 index 0000000000..03c79de0aa --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathless.layout')({ + component: () => 'Pathless Layout', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx new file mode 100644 index 0000000000..6c28fa210f --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathless.layout/subpath')({ + component: () => 'Subpath', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts index 70d06b2004..ec20742d1e 100644 --- a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts @@ -9,34 +9,30 @@ // 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 layoutRouteImport } from './routes/layout' -import { Route as fooRouteImport } from './routes/foo' -import { Route as escapedRouteImport } from './routes/escaped' import { Route as doubleRouteImport } from './routes/double' -import { Route as rootIdRouteImport } from './routes/root-id' -import { Route as nestedLayoutRouteImport } from './routes/nested-layout' -import { Route as innerLayoutRouteImport } from './routes/inner-layout' -import { Route as escapedBarRouteImport } from './routes/escaped-bar' -import { Route as barRouteImport } from './routes/bar' +import { Route as escapedRouteImport } from './routes/escaped' +import { Route as fooRouteImport } from './routes/foo' +import { Route as layoutRouteImport } from './routes/layout' import { Route as doubleBarRouteImport } from './routes/double-bar' +import { Route as barRouteImport } from './routes/bar' +import { Route as escapedBarRouteImport } from './routes/escaped-bar' +import { Route as innerLayoutRouteImport } from './routes/inner-layout' +import { Route as nestedLayoutRouteImport } from './routes/nested-layout' import { Route as rootIndexRouteImport } from './routes/root-index' -import { Route as bazRouteImport } from './routes/baz' -import { Route as nestedIdRouteImport } from './routes/nested-id' -import { Route as escapedIdRouteImport } from './routes/escaped-id' +import { Route as rootIdRouteImport } from './routes/root-id' +import { Route as doubleIndexRouteImport } from './routes/double-index' import { Route as doubleIdRouteImport } from './routes/double-id' -import { Route as trailIndexRouteImport } from './routes/trail-index' -import { Route as nestedIndexRouteImport } from './routes/nested-index' import { Route as escapedIndexRouteImport } from './routes/escaped-index' -import { Route as doubleIndexRouteImport } from './routes/double-index' +import { Route as escapedIdRouteImport } from './routes/escaped-id' +import { Route as nestedIndexRouteImport } from './routes/nested-index' +import { Route as nestedIdRouteImport } from './routes/nested-id' +import { Route as bazRouteImport } from './routes/baz' +import { Route as trailIndexRouteImport } from './routes/trail-index' import { Route as deepIndexRouteImport } from './routes/deep-index' -const layoutRoute = layoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) -const fooRoute = fooRouteImport.update({ - id: '/_foo', - path: '/_foo', +const doubleRoute = doubleRouteImport.update({ + id: '/__double', + path: '/__double', getParentRoute: () => rootRouteImport, } as any) const escapedRoute = escapedRouteImport.update({ @@ -44,22 +40,23 @@ const escapedRoute = escapedRouteImport.update({ path: '/_escaped', getParentRoute: () => rootRouteImport, } as any) -const doubleRoute = doubleRouteImport.update({ - id: '/__double', - path: '/__double', +const fooRoute = fooRouteImport.update({ + id: '/_foo', + path: '/_foo', getParentRoute: () => rootRouteImport, } as any) -const rootIdRoute = rootIdRouteImport.update({ - id: '/_root-index/$id', - path: '/_root-index/$id', +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const nestedLayoutRoute = nestedLayoutRouteImport.update({ - id: '/_nested', +const doubleBarRoute = doubleBarRouteImport.update({ + id: '/__double-bar', + path: '/__double-bar', getParentRoute: () => layoutRoute, } as any) -const innerLayoutRoute = innerLayoutRouteImport.update({ - id: '/_inner', +const barRoute = barRouteImport.update({ + id: '/_bar', + path: '/_bar', getParentRoute: () => layoutRoute, } as any) const escapedBarRoute = escapedBarRouteImport.update({ @@ -67,14 +64,12 @@ const escapedBarRoute = escapedBarRouteImport.update({ path: '/_escaped-bar', getParentRoute: () => layoutRoute, } as any) -const barRoute = barRouteImport.update({ - id: '/_bar', - path: '/_bar', +const innerLayoutRoute = innerLayoutRouteImport.update({ + id: '/_inner', getParentRoute: () => layoutRoute, } as any) -const doubleBarRoute = doubleBarRouteImport.update({ - id: '/__double-bar', - path: '/__double-bar', +const nestedLayoutRoute = nestedLayoutRouteImport.update({ + id: '/_nested', getParentRoute: () => layoutRoute, } as any) const rootIndexRoute = rootIndexRouteImport.update({ @@ -82,19 +77,14 @@ const rootIndexRoute = rootIndexRouteImport.update({ path: '/_root-index/', getParentRoute: () => rootRouteImport, } as any) -const bazRoute = bazRouteImport.update({ - id: '/_baz', - path: '/_baz', - getParentRoute: () => nestedLayoutRoute, -} as any) -const nestedIdRoute = nestedIdRouteImport.update({ - id: '/_nested-index/$id', - path: '/_nested-index/$id', - getParentRoute: () => layoutRoute, +const rootIdRoute = rootIdRouteImport.update({ + id: '/_root-index/$id', + path: '/_root-index/$id', + getParentRoute: () => rootRouteImport, } as any) -const escapedIdRoute = escapedIdRouteImport.update({ - id: '/_escaped-index/$id', - path: '/_escaped-index/$id', +const doubleIndexRoute = doubleIndexRouteImport.update({ + id: '/__double-index/', + path: '/__double-index/', getParentRoute: () => layoutRoute, } as any) const doubleIdRoute = doubleIdRouteImport.update({ @@ -102,9 +92,14 @@ const doubleIdRoute = doubleIdRouteImport.update({ path: '/__double-index/$id', getParentRoute: () => layoutRoute, } as any) -const trailIndexRoute = trailIndexRouteImport.update({ - id: '/trail_/', - path: '/trail_/', +const escapedIndexRoute = escapedIndexRouteImport.update({ + id: '/_escaped-index/', + path: '/_escaped-index/', + getParentRoute: () => layoutRoute, +} as any) +const escapedIdRoute = escapedIdRouteImport.update({ + id: '/_escaped-index/$id', + path: '/_escaped-index/$id', getParentRoute: () => layoutRoute, } as any) const nestedIndexRoute = nestedIndexRouteImport.update({ @@ -112,14 +107,19 @@ const nestedIndexRoute = nestedIndexRouteImport.update({ path: '/_nested-index/', getParentRoute: () => layoutRoute, } as any) -const escapedIndexRoute = escapedIndexRouteImport.update({ - id: '/_escaped-index/', - path: '/_escaped-index/', +const nestedIdRoute = nestedIdRouteImport.update({ + id: '/_nested-index/$id', + path: '/_nested-index/$id', getParentRoute: () => layoutRoute, } as any) -const doubleIndexRoute = doubleIndexRouteImport.update({ - id: '/__double-index/', - path: '/__double-index/', +const bazRoute = bazRouteImport.update({ + id: '/_baz', + path: '/_baz', + getParentRoute: () => nestedLayoutRoute, +} as any) +const trailIndexRoute = trailIndexRouteImport.update({ + id: '/trail_/', + path: '/trail_/', getParentRoute: () => layoutRoute, } as any) const deepIndexRoute = deepIndexRouteImport.update({ @@ -267,18 +267,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutRouteImport - parentRoute: typeof rootRouteImport - } - '/_foo': { - id: '/_foo' - path: '/_foo' - fullPath: '/_foo' - preLoaderRoute: typeof fooRouteImport + '/__double': { + id: '/__double' + path: '/__double' + fullPath: '/__double' + preLoaderRoute: typeof doubleRouteImport parentRoute: typeof rootRouteImport } '/_escaped': { @@ -288,32 +281,32 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof escapedRouteImport parentRoute: typeof rootRouteImport } - '/__double': { - id: '/__double' - path: '/__double' - fullPath: '/__double' - preLoaderRoute: typeof doubleRouteImport - parentRoute: typeof rootRouteImport - } - '/_root-index/$id': { - id: '/_root-index/$id' - path: '/_root-index/$id' - fullPath: '/_root-index/$id' - preLoaderRoute: typeof rootIdRouteImport + '/_foo': { + id: '/_foo' + path: '/_foo' + fullPath: '/_foo' + preLoaderRoute: typeof fooRouteImport parentRoute: typeof rootRouteImport } - '/_layout/_nested': { - id: '/_layout/_nested' + '/_layout': { + id: '/_layout' path: '' fullPath: '/' - preLoaderRoute: typeof nestedLayoutRouteImport + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/__double-bar': { + id: '/_layout/__double-bar' + path: '/__double-bar' + fullPath: '/__double-bar' + preLoaderRoute: typeof doubleBarRouteImport parentRoute: typeof layoutRoute } - '/_layout/_inner': { - id: '/_layout/_inner' - path: '' - fullPath: '/' - preLoaderRoute: typeof innerLayoutRouteImport + '/_layout/_bar': { + id: '/_layout/_bar' + path: '/_bar' + fullPath: '/_bar' + preLoaderRoute: typeof barRouteImport parentRoute: typeof layoutRoute } '/_layout/_escaped-bar': { @@ -323,18 +316,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof escapedBarRouteImport parentRoute: typeof layoutRoute } - '/_layout/_bar': { - id: '/_layout/_bar' - path: '/_bar' - fullPath: '/_bar' - preLoaderRoute: typeof barRouteImport + '/_layout/_inner': { + id: '/_layout/_inner' + path: '' + fullPath: '/' + preLoaderRoute: typeof innerLayoutRouteImport parentRoute: typeof layoutRoute } - '/_layout/__double-bar': { - id: '/_layout/__double-bar' - path: '/__double-bar' - fullPath: '/__double-bar' - preLoaderRoute: typeof doubleBarRouteImport + '/_layout/_nested': { + id: '/_layout/_nested' + path: '' + fullPath: '/' + preLoaderRoute: typeof nestedLayoutRouteImport parentRoute: typeof layoutRoute } '/_root-index/': { @@ -344,25 +337,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof rootIndexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/_nested/_baz': { - id: '/_layout/_nested/_baz' - path: '/_baz' - fullPath: '/_baz' - preLoaderRoute: typeof bazRouteImport - parentRoute: typeof nestedLayoutRoute - } - '/_layout/_nested-index/$id': { - id: '/_layout/_nested-index/$id' - path: '/_nested-index/$id' - fullPath: '/_nested-index/$id' - preLoaderRoute: typeof nestedIdRouteImport - parentRoute: typeof layoutRoute + '/_root-index/$id': { + id: '/_root-index/$id' + path: '/_root-index/$id' + fullPath: '/_root-index/$id' + preLoaderRoute: typeof rootIdRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/_escaped-index/$id': { - id: '/_layout/_escaped-index/$id' - path: '/_escaped-index/$id' - fullPath: '/_escaped-index/$id' - preLoaderRoute: typeof escapedIdRouteImport + '/_layout/__double-index/': { + id: '/_layout/__double-index/' + path: '/__double-index' + fullPath: '/__double-index/' + preLoaderRoute: typeof doubleIndexRouteImport parentRoute: typeof layoutRoute } '/_layout/__double-index/$id': { @@ -372,11 +358,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof doubleIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/trail_/': { - id: '/_layout/trail_/' - path: '/trail_' - fullPath: '/trail_/' - preLoaderRoute: typeof trailIndexRouteImport + '/_layout/_escaped-index/': { + id: '/_layout/_escaped-index/' + path: '/_escaped-index' + fullPath: '/_escaped-index/' + preLoaderRoute: typeof escapedIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_escaped-index/$id': { + id: '/_layout/_escaped-index/$id' + path: '/_escaped-index/$id' + fullPath: '/_escaped-index/$id' + preLoaderRoute: typeof escapedIdRouteImport parentRoute: typeof layoutRoute } '/_layout/_nested-index/': { @@ -386,18 +379,25 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof nestedIndexRouteImport parentRoute: typeof layoutRoute } - '/_layout/_escaped-index/': { - id: '/_layout/_escaped-index/' - path: '/_escaped-index' - fullPath: '/_escaped-index/' - preLoaderRoute: typeof escapedIndexRouteImport + '/_layout/_nested-index/$id': { + id: '/_layout/_nested-index/$id' + path: '/_nested-index/$id' + fullPath: '/_nested-index/$id' + preLoaderRoute: typeof nestedIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/__double-index/': { - id: '/_layout/__double-index/' - path: '/__double-index' - fullPath: '/__double-index/' - preLoaderRoute: typeof doubleIndexRouteImport + '/_layout/_nested/_baz': { + id: '/_layout/_nested/_baz' + path: '/_baz' + fullPath: '/_baz' + preLoaderRoute: typeof bazRouteImport + parentRoute: typeof nestedLayoutRoute + } + '/_layout/trail_/': { + id: '/_layout/trail_/' + path: '/trail_' + fullPath: '/trail_/' + preLoaderRoute: typeof trailIndexRouteImport parentRoute: typeof layoutRoute } '/_layout/_inner/_deep-index/': { diff --git a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts index 6de1ed6975..eeb3c71359 100644 --- a/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-physical-empty-path-merge/routeTree.snapshot.ts @@ -9,13 +9,13 @@ // 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 ContactRouteImport } from './routes/merged/contact' -import { Route as aboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/merged/index' +import { Route as aboutRouteImport } from './routes/about' +import { Route as ContactRouteImport } from './routes/merged/contact' -const ContactRoute = ContactRouteImport.update({ - id: '/contact', - path: '/contact', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const aboutRoute = aboutRouteImport.update({ @@ -23,9 +23,9 @@ const aboutRoute = aboutRouteImport.update({ path: '/about', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ContactRoute = ContactRouteImport.update({ + id: '/contact', + path: '/contact', getParentRoute: () => rootRouteImport, } as any) @@ -61,11 +61,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/contact': { - id: '/contact' - path: '/contact' - fullPath: '/contact' - preLoaderRoute: typeof ContactRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/about': { @@ -75,11 +75,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof aboutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/contact': { + id: '/contact' + path: '/contact' + fullPath: '/contact' + preLoaderRoute: typeof ContactRouteImport parentRoute: typeof rootRouteImport } } diff --git a/packages/router-generator/tests/generator/virtual-physical-layout-and-index/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-layout-and-index/routeTree.snapshot.ts index bade75773c..e8cddcbc9c 100644 --- a/packages/router-generator/tests/generator/virtual-physical-layout-and-index/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-physical-layout-and-index/routeTree.snapshot.ts @@ -9,20 +9,20 @@ // 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 FeatureRouteRouteImport } from './routes/feature/route' import { Route as indexRouteImport } from './routes/index' +import { Route as FeatureRouteRouteImport } from './routes/feature/route' import { Route as FeatureIndexRouteImport } from './routes/feature/index' -const FeatureRouteRoute = FeatureRouteRouteImport.update({ - id: '/feature', - path: '/feature', - getParentRoute: () => rootRouteImport, -} as any) const indexRoute = indexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const FeatureRouteRoute = FeatureRouteRouteImport.update({ + id: '/feature', + path: '/feature', + getParentRoute: () => rootRouteImport, +} as any) const FeatureIndexRoute = FeatureIndexRouteImport.update({ id: '/', path: '/', @@ -59,13 +59,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/feature': { - id: '/feature' - path: '/feature' - fullPath: '/feature' - preLoaderRoute: typeof FeatureRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -73,6 +66,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof indexRouteImport parentRoute: typeof rootRouteImport } + '/feature': { + id: '/feature' + path: '/feature' + fullPath: '/feature' + preLoaderRoute: typeof FeatureRouteRouteImport + parentRoute: typeof rootRouteImport + } '/feature/': { id: '/feature/' path: '/' diff --git a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts index 6de1ed6975..eeb3c71359 100644 --- a/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-physical-no-prefix/routeTree.snapshot.ts @@ -9,13 +9,13 @@ // 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 ContactRouteImport } from './routes/merged/contact' -import { Route as aboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/merged/index' +import { Route as aboutRouteImport } from './routes/about' +import { Route as ContactRouteImport } from './routes/merged/contact' -const ContactRoute = ContactRouteImport.update({ - id: '/contact', - path: '/contact', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) const aboutRoute = aboutRouteImport.update({ @@ -23,9 +23,9 @@ const aboutRoute = aboutRouteImport.update({ path: '/about', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ContactRoute = ContactRouteImport.update({ + id: '/contact', + path: '/contact', getParentRoute: () => rootRouteImport, } as any) @@ -61,11 +61,11 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/contact': { - id: '/contact' - path: '/contact' - fullPath: '/contact' - preLoaderRoute: typeof ContactRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/about': { @@ -75,11 +75,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof aboutRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/contact': { + id: '/contact' + path: '/contact' + fullPath: '/contact' + preLoaderRoute: typeof ContactRouteImport parentRoute: typeof rootRouteImport } } diff --git a/packages/router-generator/tests/generator/virtual-with-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-with-escaped-underscore/routeTree.snapshot.ts index 8eed55a7fe..18d7439610 100644 --- a/packages/router-generator/tests/generator/virtual-with-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-with-escaped-underscore/routeTree.snapshot.ts @@ -11,8 +11,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as indexRouteImport } from './routes/index' import { Route as ApiIndexRouteImport } from './routes/physical-routes/index' -import { Route as ApiChar91_Char93helloRouteImport } from './routes/physical-routes/[_]hello' import { Route as ApiChar91_Char93authDotrouteRouteImport } from './routes/physical-routes/[_]auth.route' +import { Route as ApiChar91_Char93helloRouteImport } from './routes/physical-routes/[_]hello' const indexRoute = indexRouteImport.update({ id: '/', @@ -24,17 +24,17 @@ const ApiIndexRoute = ApiIndexRouteImport.update({ path: '/api/', getParentRoute: () => rootRouteImport, } as any) -const ApiChar91_Char93helloRoute = ApiChar91_Char93helloRouteImport.update({ - id: '/api/_hello', - path: '/api/_hello', - getParentRoute: () => rootRouteImport, -} as any) const ApiChar91_Char93authDotrouteRoute = ApiChar91_Char93authDotrouteRouteImport.update({ id: '/api/_auth', path: '/api/_auth', getParentRoute: () => rootRouteImport, } as any) +const ApiChar91_Char93helloRoute = ApiChar91_Char93helloRouteImport.update({ + id: '/api/_hello', + path: '/api/_hello', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof indexRoute @@ -86,13 +86,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiIndexRouteImport parentRoute: typeof rootRouteImport } - '/api/_hello': { - id: '/api/_hello' - path: '/api/_hello' - fullPath: '/api/_hello' - preLoaderRoute: typeof ApiChar91_Char93helloRouteImport - parentRoute: typeof rootRouteImport - } '/api/_auth': { id: '/api/_auth' path: '/api/_auth' @@ -100,6 +93,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiChar91_Char93authDotrouteRouteImport parentRoute: typeof rootRouteImport } + '/api/_hello': { + id: '/api/_hello' + path: '/api/_hello' + fullPath: '/api/_hello' + preLoaderRoute: typeof ApiChar91_Char93helloRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/packages/router-generator/tests/generator/virtual/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual/routeTree.snapshot.ts index 3d3c2de9d5..6b16924d5b 100644 --- a/packages/router-generator/tests/generator/virtual/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual/routeTree.snapshot.ts @@ -9,51 +9,61 @@ // 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 layoutRouteImport } from './routes/layout' import { Route as indexRouteImport } from './routes/index' -import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as layoutRouteImport } from './routes/layout' import { Route as pagesRouteImport } from './routes/pages' -import { Route as HelloIndexRouteImport } from './routes/subtree/index' -import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' -import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' -import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' -const layoutRoute = layoutRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) const indexRoute = indexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const dbDashboardRoute = dbDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => layoutRoute, +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, } as any) const pagesRoute = pagesRouteImport.update({ id: '/$lang/', path: '/$lang/', getParentRoute: () => rootRouteImport, } as any) -const HelloIndexRoute = HelloIndexRouteImport.update({ - id: '/hello/', - path: '/hello/', +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', getParentRoute: () => layoutRoute, } as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ id: '/invoices', path: '/invoices', getParentRoute: () => dbDashboardRoute, } as any) -const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => dbDashboardRoute, + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, } as any) const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ id: '/hello/foo/', @@ -65,16 +75,6 @@ const HelloFooIdRoute = HelloFooIdRouteImport.update({ path: '/hello/foo/$id', getParentRoute: () => layoutRoute, } as any) -const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ - id: '/$id', - path: '/$id', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) -const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => dbDashboardInvoicesRoute, -} as any) export interface FileRoutesByFullPath { '/': typeof indexRoute @@ -158,13 +158,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof layoutRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -172,12 +165,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof indexRouteImport parentRoute: typeof rootRouteImport } - '/_layout/dashboard': { - id: '/_layout/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof dbDashboardRouteImport - parentRoute: typeof layoutRoute + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport } '/$lang/': { id: '/$lang/' @@ -186,13 +179,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof pagesRouteImport parentRoute: typeof rootRouteImport } - '/_layout/hello/': { - id: '/_layout/hello/' - path: '/hello' - fullPath: '/hello/' - preLoaderRoute: typeof HelloIndexRouteImport + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport parentRoute: typeof layoutRoute } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } '/_layout/dashboard/invoices': { id: '/_layout/dashboard/invoices' path: '/invoices' @@ -200,12 +200,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof dbDashboardInvoicesRouteImport parentRoute: typeof dbDashboardRoute } - '/_layout/dashboard/': { - id: '/_layout/dashboard/' + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello/' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' path: '/' - fullPath: '/dashboard/' - preLoaderRoute: typeof dbDashboardIndexRouteImport - parentRoute: typeof dbDashboardRoute + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute } '/_layout/hello/foo/': { id: '/_layout/hello/foo/' @@ -221,20 +235,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HelloFooIdRouteImport parentRoute: typeof layoutRoute } - '/_layout/dashboard/invoices/$id': { - id: '/_layout/dashboard/invoices/$id' - path: '/$id' - fullPath: '/dashboard/invoices/$id' - preLoaderRoute: typeof dbInvoiceDetailRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } - '/_layout/dashboard/invoices/': { - id: '/_layout/dashboard/invoices/' - path: '/' - fullPath: '/dashboard/invoices/' - preLoaderRoute: typeof dbInvoicesIndexRouteImport - parentRoute: typeof dbDashboardInvoicesRoute - } } } diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 0cdce3cfae..504dacc0e6 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -6,7 +6,9 @@ import { createRoutePathSegmentMetadata, createRouteNodesByFullPath, createRouteNodesByTo, + createTokenRegex, determineInitialRoutePath, + determineInitialRoutePathFromExplicitPath, hasEscapedLeadingUnderscore, hasEscapedTrailingUnderscore, inferFullPath, @@ -21,6 +23,7 @@ import { removeUnderscores, removeUnderscoresWithEscape, routePathToVariable, + sortRouteNodes, } from '../src/utils' import type { ImportDeclaration, RouteNode } from '../src/types' @@ -306,6 +309,26 @@ describe('determineInitialRoutePath', () => { }) }) +describe('determineInitialRoutePathFromExplicitPath', () => { + it('preserves dots as literal route path characters', () => { + expect( + determineInitialRoutePathFromExplicitPath('pathless.layout'), + ).toStrictEqual({ + routePath: '/pathless.layout', + originalRoutePath: '/pathless.layout', + }) + }) + + it('resolves bracket escapes without applying file-route separators', () => { + expect( + determineInitialRoutePathFromExplicitPath('pathless[.]layout'), + ).toStrictEqual({ + routePath: '/pathless.layout', + originalRoutePath: '/pathless[.]layout', + }) + }) +}) + describe('multiSortBy', () => { it('sorts by single accessor', () => { const arr = [{ v: 3 }, { v: 1 }, { v: 2 }] @@ -965,3 +988,85 @@ describe('RoutePrefixMap', () => { }) }) }) + +describe('sortRouteNodes', () => { + const indexTokenSegmentRegex = createTokenRegex('index', { type: 'segment' }) + + const node = (routePath: string): RouteNode => + ({ routePath }) as unknown as RouteNode + + const sortPaths = (routePaths: Array): Array => + sortRouteNodes(routePaths.map(node), indexTokenSegmentRegex).map( + (n) => n.routePath, + ) + + /** Every distinct ordering of `arr` (Heap-free recursive permutation). */ + function permutations(arr: Array): Array> { + if (arr.length <= 1) { + return [arr] + } + const result: Array> = [] + arr.forEach((item, i) => { + const rest = [...arr.slice(0, i), ...arr.slice(i + 1)] + for (const perm of permutations(rest)) { + result.push([item, ...perm]) + } + }) + return result + } + + it('orders tied siblings by routePath', () => { + // Same segment count, both non-index: they tie on every key before routePath. + const expected = ['/account/beta-features', '/account/busy-guard'] + expect( + sortPaths(['/account/busy-guard', '/account/beta-features']), + ).toEqual(expected) + expect( + sortPaths(['/account/beta-features', '/account/busy-guard']), + ).toEqual(expected) + }) + + it('applies root, segment-count and index precedence before the routePath tiebreaker', () => { + const sorted = sortPaths([ + '/posts', + '/account/busy-guard', + '/index', + '/__root', + '/account/beta-features', + '/about', + '/posts/index', + ]) + + expect(sorted).toEqual([ + '/__root', // root always first + '/index', // depth 1, index segment before non-index + '/about', // depth 1, non-index, routePath tiebreak + '/posts', // depth 1, non-index, routePath tiebreak + '/posts/index', // depth 2, index segment before non-index + '/account/beta-features', // depth 2, non-index, routePath tiebreak + '/account/busy-guard', // depth 2, non-index, routePath tiebreak + ]) + }) + + it('is deterministic for every input permutation (stable sort)', () => { + // Includes several sets that tie on all keys before routePath (the two + // `/account/*` leaves, `/about` vs `/posts`) — exactly the case that the + // former whole-object tiebreaker left in an engine- and input-order- + // dependent order. `sortRouteNodes` must map every permutation to the + // same output. + const routePaths = [ + '/__root', + '/index', + '/about', + '/posts', + '/account/beta-features', + '/account/busy-guard', + ] + + const reference = sortPaths(routePaths) + + for (const permutation of permutations(routePaths)) { + expect(sortPaths(permutation)).toEqual(reference) + } + }) +}) diff --git a/packages/router-plugin/CHANGELOG.md b/packages/router-plugin/CHANGELOG.md index 50dca919fa..43463e919a 100644 --- a/packages/router-plugin/CHANGELOG.md +++ b/packages/router-plugin/CHANGELOG.md @@ -1,5 +1,43 @@ # @tanstack/router-plugin +## 1.168.24 + +### Patch Changes + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + - @tanstack/react-router@1.170.19 + - @tanstack/router-generator@1.167.22 + +## 1.168.23 + +### Patch Changes + +- [#7855](https://github.com/TanStack/router/pull/7855) [`49f6863`](https://github.com/TanStack/router/commit/49f686324574ea65851317cbba084eb386a911c6) - Preserve React component state during HMR when route components are declared with lowercase function names. The development-only React transforms cover split route component groups and unsplit root route component options, including shell, pending, and error components, without changing production output. + +## 1.168.22 + +### Patch Changes + +- Updated dependencies [[`78dd1a6`](https://github.com/TanStack/router/commit/78dd1a645dc1a9e9f4f649d9aff12005044d4fcc)]: + - @tanstack/router-generator@1.167.21 + +## 1.168.21 + +### Patch Changes + +- Updated dependencies [[`e56a677`](https://github.com/TanStack/router/commit/e56a67742da9021b009b8db0cdc8bfe99878c25b)]: + - @tanstack/router-generator@1.167.20 + +## 1.168.20 + +### Patch Changes + +- Updated dependencies [[`e2dd204`](https://github.com/TanStack/router/commit/e2dd2049cb42eb219d3b447b8605066d19d9c1fa)]: + - @tanstack/router-core@1.171.15 + - @tanstack/react-router@1.170.18 + - @tanstack/router-generator@1.167.19 + ## 1.168.19 ### Patch Changes diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index 485eb70525..380c7e29be 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-plugin", - "version": "1.168.19", + "version": "1.168.24", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -31,12 +31,12 @@ "test:unit:dev": "vitest --watch", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, @@ -138,6 +138,7 @@ "zod": "^4.4.3" }, "devDependencies": { + "@rsbuild/core": "^2.1.0", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", "@types/node": ">=20" diff --git a/packages/router-plugin/skills/_artifacts/domain_map.yaml b/packages/router-plugin/skills/_artifacts/domain_map.yaml index 8ce9e31f4d..dd83f2c913 100644 --- a/packages/router-plugin/skills/_artifacts/domain_map.yaml +++ b/packages/router-plugin/skills/_artifacts/domain_map.yaml @@ -1,12 +1,12 @@ # domain_map.yaml # Library: @tanstack/router-plugin -# Version: 1.166.2 +# Version: 1.168.23 # Date: 2026-03-08 # Status: reviewed library: name: '@tanstack/router-plugin' - version: '1.166.2' + version: '1.168.23' repository: 'https://github.com/TanStack/router' description: >- Bundler plugin for route generation and automatic code splitting. @@ -59,4 +59,12 @@ skills: priority: MEDIUM status: active + - mistake: 'Editing routeTree.gen.ts instead of regenerating' + mechanism: >- + Manual changes are overwritten and can put source routes, generated + types, and runtime routing out of sync. Fix source or config and + regenerate the tree. + priority: HIGH + status: active + gaps: [] diff --git a/packages/router-plugin/skills/_artifacts/skill_spec.md b/packages/router-plugin/skills/_artifacts/skill_spec.md index 4d4881232f..586ee34ade 100644 --- a/packages/router-plugin/skills/_artifacts/skill_spec.md +++ b/packages/router-plugin/skills/_artifacts/skill_spec.md @@ -12,13 +12,14 @@ Bundler plugin for route generation and automatic code splitting. Supports Vite, | Skill | Type | Domain | What it covers | Failure modes | | ------------- | ---- | ------------------- | -------------------------------------------------------------- | ------------- | -| router-plugin | core | bundler-integration | Vite/Webpack/Rspack/esbuild plugins, route gen, code splitting | 2 | +| router-plugin | core | bundler-integration | Vite/Webpack/Rspack/esbuild plugins, route gen, code splitting | 3 | ## Failure Mode Inventory -### router-plugin (2 failure modes) +### router-plugin (3 failure modes) -| # | Mistake | Priority | Source | -| --- | ---------------------------------------------------- | -------- | ----------- | -| 1 | Using wrong plugin export for bundler | HIGH | source/docs | -| 2 | Misconfiguring routesDirectory or generatedRouteTree | MEDIUM | source/docs | +| # | Mistake | Priority | Source | +| --- | ---------------------------------------------------- | -------- | ----------------- | +| 1 | Using wrong plugin export for bundler | HIGH | source/docs | +| 2 | Misconfiguring routesDirectory or generatedRouteTree | MEDIUM | source/docs | +| 3 | Editing routeTree.gen.ts instead of regenerating | HIGH | consistency audit | diff --git a/packages/router-plugin/skills/_artifacts/skill_tree.yaml b/packages/router-plugin/skills/_artifacts/skill_tree.yaml index eccd302d0f..a313cc8a4e 100644 --- a/packages/router-plugin/skills/_artifacts/skill_tree.yaml +++ b/packages/router-plugin/skills/_artifacts/skill_tree.yaml @@ -1,6 +1,6 @@ library: name: '@tanstack/router-plugin' - version: '1.166.2' + version: '1.168.23' repository: 'https://github.com/TanStack/router' description: >- Bundler plugin for route generation and automatic code splitting. diff --git a/packages/router-plugin/skills/router-plugin/SKILL.md b/packages/router-plugin/skills/router-plugin/SKILL.md index b3ba5db5a3..972584dc96 100644 --- a/packages/router-plugin/skills/router-plugin/SKILL.md +++ b/packages/router-plugin/skills/router-plugin/SKILL.md @@ -5,9 +5,10 @@ description: >- code splitting. Supports Vite, Webpack, Rspack, and esbuild. Configures autoCodeSplitting, routesDirectory, target framework, and code split groupings. -type: core -library: tanstack-router -library_version: '1.166.2' +metadata: + type: core + library: tanstack-router + library_version: '1.168.23' sources: - TanStack/router:packages/router-plugin/src - TanStack/router:docs/router/routing/file-based-routing.md @@ -170,6 +171,18 @@ The composed plugin assembles up to 3 sub-plugins: 2. **Code Splitter** (when `autoCodeSplitting: true`) — Splits route files into lazy-loaded chunks using virtual modules 3. **HMR** (dev mode, when code splitter is off) — Hot-reloads route changes without full refresh +## Route Refactor Workflow + +When moving, renaming, adding, or deleting file routes: + +1. Change the source files under `routesDirectory`. Keep the exported route identifier named `Route`. +2. Let the bundler plugin regenerate the tree, or run `pnpm exec tsr generate` when the project uses the CLI. +3. Inspect the generated diff for the expected route IDs, parents, paths, and imports. Never repair `routeTree.gen.ts` by hand. +4. Update links, redirects, `from` narrowing, params, preload calls, and tests that reference the old route. +5. Run route-generation tests, type tests, and a production build. A passing editor typecheck does not prove the plugin generated or split the new route correctly. + +Commit `routeTree.gen.ts`; it is generated source used by the application at runtime. + ## Individual Plugin Exports For advanced use, each sub-plugin is exported separately from the Vite entry: @@ -227,6 +240,10 @@ function AboutPage() { } ``` +### 4. HIGH: Editing the generated route tree + +Changes to `routeTree.gen.ts` are overwritten and can leave source routes, generated types, and runtime routing out of sync. Fix route filenames or plugin configuration, regenerate, and verify the generated diff instead. + ## Cross-References - [router-core/code-splitting](../../../router-core/skills/router-core/code-splitting/SKILL.md) — manual code splitting concepts diff --git a/packages/router-plugin/src/core/code-splitter/compilers.ts b/packages/router-plugin/src/core/code-splitter/compilers.ts index 9411165630..89cdae41ad 100644 --- a/packages/router-plugin/src/core/code-splitter/compilers.ts +++ b/packages/router-plugin/src/core/code-splitter/compilers.ts @@ -25,8 +25,8 @@ import { createRouteHmrStatement } from '../hmr' import { getObjectPropertyKeyName } from '../utils' import { getFrameworkOptions } from './framework-options' import type { + CodeSplitCompilerPlugin, CompileCodeSplitReferenceRouteOptions, - ReferenceRouteCompilerPlugin, } from './plugins' import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils' import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants' @@ -364,7 +364,7 @@ function removeSharedDeclarations(ast: t.File, sharedBindings: Set) { export function compileCodeSplitReferenceRoute( opts: ParseAstOptions & CompileCodeSplitReferenceRouteOptions & { - compilerPlugins?: Array + compilerPlugins?: Array }, ): GeneratorResult | null { const ast = parseAst(opts) @@ -879,6 +879,7 @@ export function compileCodeSplitVirtualRoute( splitTargets: Array filename: string sharedBindings?: Set + compilerPlugins?: Array }, ): GeneratorResult { const ast = parseAst(opts) @@ -1015,6 +1016,14 @@ export function compileCodeSplitVirtualRoute( // Add the node to the program if (splitNode) { + for (const plugin of opts.compilerPlugins ?? []) { + plugin.onVirtualRouteSplitNode?.({ + programPath, + splitNode, + splitNodeMeta: splitMeta, + }) + } + if (t.isFunctionDeclaration(splitNode)) { // an anonymous function declaration should only happen for `export default function() {...}` // so we should never get here @@ -1023,6 +1032,7 @@ export function compileCodeSplitVirtualRoute( `Function declaration for "${SPLIT_TYPE}" must have an identifier.`, ) } + splitMeta.shouldRemoveNode = false splitMeta.localExporterIdent = splitNode.id.name } else if ( diff --git a/packages/router-plugin/src/core/code-splitter/plugins.ts b/packages/router-plugin/src/core/code-splitter/plugins.ts index ebf1f4aa30..1e8ada32b5 100644 --- a/packages/router-plugin/src/core/code-splitter/plugins.ts +++ b/packages/router-plugin/src/core/code-splitter/plugins.ts @@ -40,7 +40,13 @@ export type ReferenceRouteCompilerPluginResult = { modified?: boolean } -export type ReferenceRouteCompilerPlugin = { +export type VirtualRouteSplitNodeCompilerPluginContext = { + programPath: babel.NodePath + splitNode: t.Node + splitNodeMeta: SplitNodeMeta +} + +export type CodeSplitCompilerPlugin = { name: string getStableRouteOptionKeys?: () => Array onRouteOptions?: ( @@ -55,4 +61,9 @@ export type ReferenceRouteCompilerPlugin = { onSplitRouteProperty?: ( ctx: ReferenceRouteSplitPropertyCompilerPluginContext, ) => void | t.Expression + onVirtualRouteSplitNode?: ( + ctx: VirtualRouteSplitNodeCompilerPluginContext, + ) => void } + +export type ReferenceRouteCompilerPlugin = CodeSplitCompilerPlugin diff --git a/packages/router-plugin/src/core/code-splitter/plugins/framework-plugins.ts b/packages/router-plugin/src/core/code-splitter/plugins/framework-plugins.ts index de8195853a..a0351ad210 100644 --- a/packages/router-plugin/src/core/code-splitter/plugins/framework-plugins.ts +++ b/packages/router-plugin/src/core/code-splitter/plugins/framework-plugins.ts @@ -1,27 +1,23 @@ import { createReactRefreshIgnoredRouteExportsPlugin } from './react-refresh-ignored-route-exports' import { createReactRefreshRouteComponentsPlugin } from './react-refresh-route-components' import { createReactStableHmrSplitRouteComponentsPlugin } from './react-stable-hmr-split-route-components' -import type { ReferenceRouteCompilerPlugin } from '../plugins' +import type { CodeSplitCompilerPlugin } from '../plugins' import type { Config, HmrStyle } from '../../config' -export function getReferenceRouteCompilerPlugins(opts: { +export function getFrameworkHmrCompilerPlugins(opts: { targetFramework: Config['target'] - addHmr?: boolean hmrStyle?: HmrStyle -}): Array | undefined { +}): Array | undefined { switch (opts.targetFramework) { case 'react': { - if (opts.addHmr) { - const hmrStyle = opts.hmrStyle ?? 'vite' - return [ - ...(hmrStyle === 'vite' - ? [createReactRefreshIgnoredRouteExportsPlugin()] - : []), - createReactRefreshRouteComponentsPlugin(), - createReactStableHmrSplitRouteComponentsPlugin({ hmrStyle }), - ] - } - return undefined + const hmrStyle = opts.hmrStyle ?? 'vite' + return [ + ...(hmrStyle === 'vite' + ? [createReactRefreshIgnoredRouteExportsPlugin()] + : []), + createReactRefreshRouteComponentsPlugin(), + createReactStableHmrSplitRouteComponentsPlugin({ hmrStyle }), + ] } default: return undefined diff --git a/packages/router-plugin/src/core/code-splitter/plugins/react-refresh-route-components.ts b/packages/router-plugin/src/core/code-splitter/plugins/react-refresh-route-components.ts index f7c4bca338..44041671e0 100644 --- a/packages/router-plugin/src/core/code-splitter/plugins/react-refresh-route-components.ts +++ b/packages/router-plugin/src/core/code-splitter/plugins/react-refresh-route-components.ts @@ -13,29 +13,74 @@ const REACT_REFRESH_ROUTE_COMPONENT_IDENTS = new Set([ 'notFoundComponent', ]) -function hoistInlineRouteComponents(ctx: { +type RouteComponentContext = { programPath: Parameters[0] insertionPath: { insertBefore: (nodes: Array) => void } routeOptions: t.ObjectExpression -}) { +} + +function isReactComponentName(name: string) { + const firstCharacter = name[0] + + return ( + firstCharacter !== undefined && + firstCharacter >= 'A' && + firstCharacter <= 'Z' + ) +} + +function getRouteComponentKey(prop: t.ObjectProperty) { + const key = getObjectPropertyKeyName(prop) + + return key && REACT_REFRESH_ROUTE_COMPONENT_IDENTS.has(key) ? key : undefined +} + +function prepareRouteComponentsForReactRefresh(ctx: RouteComponentContext) { const hoistedDeclarations: Array = [] + let modified = false - ctx.routeOptions.properties.forEach((prop) => { + for (const prop of ctx.routeOptions.properties) { if (!t.isObjectProperty(prop)) { - return + continue } - const key = getObjectPropertyKeyName(prop) + const key = getRouteComponentKey(prop) - if (!key || !REACT_REFRESH_ROUTE_COMPONENT_IDENTS.has(key)) { - return + if (!key) { + continue + } + + if (t.isIdentifier(prop.value)) { + if (isReactComponentName(prop.value.name)) { + continue + } + + const bindingNode = ctx.programPath.scope.getBinding(prop.value.name) + ?.path.node + const isLocalComponentBinding = + t.isFunctionDeclaration(bindingNode) || + t.isClassDeclaration(bindingNode) || + t.isVariableDeclarator(bindingNode) + + if (!isLocalComponentBinding) { + continue + } + + const componentIdentifier = getUniqueProgramIdentifier( + ctx.programPath, + `TSR${key[0]!.toUpperCase()}${key.slice(1)}`, + ) + + ctx.programPath.scope.rename(prop.value.name, componentIdentifier.name) + modified = true + continue } if ( !t.isArrowFunctionExpression(prop.value) && !t.isFunctionExpression(prop.value) ) { - return + continue } const hoistedIdentifier = getUniqueProgramIdentifier( @@ -50,14 +95,14 @@ function hoistInlineRouteComponents(ctx: { ) prop.value = t.cloneNode(hoistedIdentifier) - }) + modified = true + } - if (hoistedDeclarations.length === 0) { - return false + if (hoistedDeclarations.length > 0) { + ctx.insertionPath.insertBefore(hoistedDeclarations) } - ctx.insertionPath.insertBefore(hoistedDeclarations) - return true + return modified } export function createReactRefreshRouteComponentsPlugin(): ReferenceRouteCompilerPlugin { @@ -66,27 +111,32 @@ export function createReactRefreshRouteComponentsPlugin(): ReferenceRouteCompile getStableRouteOptionKeys() { return [...REACT_REFRESH_ROUTE_COMPONENT_IDENTS] }, - onUnsplittableRoute(ctx) { - if (!ctx.opts.addHmr) { - return - } - - if (hoistInlineRouteComponents(ctx)) { + onAddHmr(ctx) { + if (prepareRouteComponentsForReactRefresh(ctx)) { return { modified: true } } return }, - onAddHmr(ctx) { - if (!ctx.opts.addHmr) { + onVirtualRouteSplitNode(ctx) { + if ( + ctx.splitNodeMeta.splitStrategy !== 'lazyRouteComponent' || + !t.isFunctionDeclaration(ctx.splitNode) || + !ctx.splitNode.id || + isReactComponentName(ctx.splitNode.id.name) + ) { return } - if (hoistInlineRouteComponents(ctx)) { - return { modified: true } - } + const componentIdentifier = getUniqueProgramIdentifier( + ctx.programPath, + ctx.splitNodeMeta.localExporterIdent, + ) - return + ctx.programPath.scope.rename( + ctx.splitNode.id.name, + componentIdentifier.name, + ) }, } } diff --git a/packages/router-plugin/src/core/config.ts b/packages/router-plugin/src/core/config.ts index c0b47b9ef5..17c0dd08da 100644 --- a/packages/router-plugin/src/core/config.ts +++ b/packages/router-plugin/src/core/config.ts @@ -9,7 +9,7 @@ import type { RouteIds, } from '@tanstack/router-core' import type { CodeSplitGroupings } from './constants' -import type { ReferenceRouteCompilerPlugin } from './code-splitter/plugins' +import type { CodeSplitCompilerPlugin } from './code-splitter/plugins' export const splitGroupingsSchema = z .array( @@ -76,7 +76,7 @@ export type CodeSplittingOptions = { * Internal compiler plugins used by framework integrations. * @internal */ - compilerPlugins?: Array + compilerPlugins?: Array } export type HmrStyle = 'vite' | 'webpack' diff --git a/packages/router-plugin/src/core/hmr/handle-route-update.ts b/packages/router-plugin/src/core/hmr/handle-route-update.ts index c0325b06e9..23b51d2432 100644 --- a/packages/router-plugin/src/core/hmr/handle-route-update.ts +++ b/packages/router-plugin/src/core/hmr/handle-route-update.ts @@ -1,15 +1,9 @@ -import type { - AnyRoute, - AnyRouteMatch, - AnyRouter, - RouterWritableStore, -} from '@tanstack/router-core' +import type { AnyRoute, AnyRouter } from '@tanstack/router-core' type AnyRouteWithPrivateProps = AnyRoute & { options: Record parentRoute: AnyRoute - _componentsPromise?: Promise - _lazyPromise?: Promise + _lazy?: Promise | true update: (options: Record) => unknown _path: string _id: string @@ -17,37 +11,19 @@ type AnyRouteWithPrivateProps = AnyRoute & { _to: string } -type AnyRouterWithPrivateMaps = AnyRouter & { +type AnyRouterWithPrivateState = AnyRouter & { routesById: Record buildRouteTree: () => Parameters[0] setRoutes: AnyRouter['setRoutes'] - stores: AnyRouter['stores'] & { - cachedMatchStores: Map< - string, - Pick, 'get' | 'set'> - > - pendingMatchStores: Map< - string, - Pick, 'get' | 'set'> - > - matchStores: Map< - string, - Pick, 'get' | 'set'> - > - } -} - -type AnyRouteMatchWithPrivateProps = AnyRouteMatch & { - __beforeLoadContext?: unknown - __routeContext?: Record - context?: Record + _refreshRoute?: () => Promise + _replaceRouteChunk: (route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void } function handleRouteUpdate( routeId: string, newRoute: AnyRouteWithPrivateProps, ) { - const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateMaps + const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateState const oldRoute = router.routesById[routeId] as | AnyRouteWithPrivateProps | undefined @@ -66,14 +42,6 @@ function handleRouteUpdate( } }) - const removedKeys = new Set() - Object.keys(oldRoute.options).forEach((key) => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key) - delete oldRoute.options[key] - } - }) - const oldHasShellComponent = 'shellComponent' in oldRoute.options const newHasShellComponent = 'shellComponent' in newRoute.options const preserveComponentIdentity = @@ -106,60 +74,12 @@ function handleRouteUpdate( oldRoute.options = nextOptions oldRoute.update(nextOptions) - oldRoute._componentsPromise = undefined - oldRoute._lazyPromise = undefined + router._replaceRouteChunk(oldRoute, newRoute.lazyFn) router.setRoutes(router.buildRouteTree()) syncHotRouteExport(oldRoute) router.resolvePathCache.clear() - - const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id - const activeMatch = router.stores.matches.get().find(filter) - const pendingMatch = router.stores.pendingMatches.get().find(filter) - const cachedMatches = router.stores.cachedMatches.get().filter(filter) - - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - // Clear stale match data for removed route options BEFORE invalidating. - // Without this, router.invalidate() -> matchRoutes() reuses the existing - // match from the store (via ...existingMatch spread) and the stale - // loaderData / __beforeLoadContext survives the reload cycle. - // - // We must update the store directly (not via router.updateMatch) because - // updateMatch wraps in startTransition which may defer the state update, - // and we need the clear to be visible before invalidate reads the store. - if (removedKeys.has('loader') || removedKeys.has('beforeLoad')) { - const matchIds = [ - activeMatch?.id, - pendingMatch?.id, - ...cachedMatches.map((match) => match.id), - ].filter(Boolean) as Array - router.batch(() => { - for (const matchId of matchIds) { - const store = - router.stores.pendingMatchStores.get(matchId) || - router.stores.matchStores.get(matchId) || - router.stores.cachedMatchStores.get(matchId) - if (store) { - store.set((prev) => { - const next: AnyRouteMatchWithPrivateProps = { ...prev } - - if (removedKeys.has('loader')) { - next.loaderData = undefined - } - if (removedKeys.has('beforeLoad')) { - next.__beforeLoadContext = undefined - next.context = rebuildMatchContextWithoutBeforeLoad(next) - } - - return next - }) - } - } - }) - } - - router.invalidate({ filter, sync: true }) - } + void router._refreshRoute?.() function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) { // routeTree.gen.ts mutates the original module export with generated @@ -172,66 +92,6 @@ function handleRouteUpdate( newRoute._fullPath = liveRoute._fullPath newRoute._to = liveRoute._to } - - function getStoreMatch(matchId: string) { - return ( - router.stores.pendingMatchStores.get(matchId)?.get() || - router.stores.matchStores.get(matchId)?.get() || - router.stores.cachedMatchStores.get(matchId)?.get() - ) - } - - function getMatchList(matchId: string) { - const pendingMatches = router.stores.pendingMatches.get() - if (pendingMatches.some((match) => match.id === matchId)) { - return pendingMatches - } - - const activeMatches = router.stores.matches.get() - if (activeMatches.some((match) => match.id === matchId)) { - return activeMatches - } - - const cachedMatches = router.stores.cachedMatches.get() - if (cachedMatches.some((match) => match.id === matchId)) { - return cachedMatches - } - - return [] - } - - function getParentMatch(match: AnyRouteMatch) { - const matchList = getMatchList(match.id) - const matchIndex = matchList.findIndex((item) => item.id === match.id) - - if (matchIndex <= 0) { - return undefined - } - - const parentMatch = matchList[matchIndex - 1]! - return getStoreMatch(parentMatch.id) || parentMatch - } - - function rebuildMatchContextWithoutBeforeLoad( - match: AnyRouteMatchWithPrivateProps, - ) { - const parentMatch = getParentMatch(match) - const getParentContext = ( - router as unknown as { - getParentContext?: ( - parentMatch?: AnyRouteMatch, - ) => Record | undefined - } - ).getParentContext - const parentContext = getParentContext - ? getParentContext.call(router, parentMatch) - : (parentMatch?.context ?? router.options.context) - - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}), - } - } } const handleRouteUpdateStr = handleRouteUpdate.toString() 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 4b6af091cc..f1f75d4f3c 100644 --- a/packages/router-plugin/src/core/router-code-splitter-plugin.ts +++ b/packages/router-plugin/src/core/router-code-splitter-plugin.ts @@ -13,7 +13,7 @@ import { computeSharedBindings, detectCodeSplitGroupingsFromRoute, } from './code-splitter/compilers' -import { getReferenceRouteCompilerPlugins } from './code-splitter/plugins/framework-plugins' +import { getFrameworkHmrCompilerPlugins } from './code-splitter/plugins/framework-plugins' import { defaultCodeSplitGroupings, splitRouteIdentNodes, @@ -24,7 +24,8 @@ import { debug, normalizePath, routeFactoryCallCodeFilter } from './utils' import { createRouterPluginContext } from './router-plugin-context' import type { CodeSplitGroupings, SplitRouteIdentNodes } from './constants' import type { GetRoutesByFileMapResultValue } from '@tanstack/router-generator' -import type { Config } from './config' +import type { CodeSplitCompilerPlugin } from './code-splitter/plugins' +import type { Config, HmrStyle } from './config' import type { RouterPluginContext } from './router-plugin-context' import type { UnpluginFactory, @@ -83,6 +84,12 @@ export function createRouterCodeSplitterPlugin( ): ReturnType Config)> | undefined>> { let ROOT: string = process.cwd() let userConfig: Config + let addHmr: boolean + let hmrStyle: HmrStyle + let compilerPlugins: Array + let virtualRouteCompilerPlugins: Array + + let isProduction = process.env.NODE_ENV === 'production' function initUserConfig() { if (typeof options === 'function') { @@ -90,8 +97,22 @@ export function createRouterCodeSplitterPlugin( } else { userConfig = getConfig(options, ROOT) } + + addHmr = (userConfig.codeSplittingOptions?.addHmr ?? true) && !isProduction + hmrStyle = userConfig.plugin?.hmr?.style ?? 'vite' + compilerPlugins = [ + ...(addHmr + ? (getFrameworkHmrCompilerPlugins({ + targetFramework: userConfig.target, + hmrStyle, + }) ?? []) + : []), + ...(userConfig.codeSplittingOptions?.compilerPlugins ?? []), + ] + virtualRouteCompilerPlugins = compilerPlugins.filter( + (plugin) => plugin.onVirtualRouteSplitNode, + ) } - const isProduction = process.env.NODE_ENV === 'production' // Map from normalized route file path → set of shared binding names. // Populated by the reference compiler, consumed by virtual and shared compilers. const sharedBindingsMap = new Map>() @@ -159,10 +180,6 @@ export function createRouterCodeSplitterPlugin( sharedBindingsMap.delete(id) } - const addHmr = - (userConfig.codeSplittingOptions?.addHmr ?? true) && !isProduction - const hmrStyle = userConfig.plugin?.hmr?.style ?? 'vite' - const compiledReferenceRoute = compileCodeSplitReferenceRoute({ code, codeSplitGroupings: splitGroupings, @@ -176,14 +193,7 @@ export function createRouterCodeSplitterPlugin( hmrStyle, hmrRouteId: generatorNodeInfo.routeId, sharedBindings: sharedBindings.size > 0 ? sharedBindings : undefined, - compilerPlugins: [ - ...(getReferenceRouteCompilerPlugins({ - targetFramework: userConfig.target, - addHmr, - hmrStyle, - }) ?? []), - ...(userConfig.codeSplittingOptions?.compilerPlugins ?? []), - ], + compilerPlugins, }) if (compiledReferenceRoute === null) { @@ -232,6 +242,7 @@ export function createRouterCodeSplitterPlugin( filename: id, splitTargets: grouping, sharedBindings: resolvedSharedBindings, + compilerPlugins: virtualRouteCompilerPlugins, }) if (debug) { @@ -276,6 +287,7 @@ export function createRouterCodeSplitterPlugin( vite: { configResolved(config) { + isProduction = config.command === 'build' ROOT = config.root initUserConfig() @@ -319,12 +331,14 @@ export function createRouterCodeSplitterPlugin( }, }, - rspack() { + rspack(compiler) { + isProduction = compiler.options.mode === 'production' ROOT = process.cwd() initUserConfig() }, - webpack() { + webpack(compiler) { + isProduction = compiler.options.mode === 'production' ROOT = process.cwd() initUserConfig() }, diff --git a/packages/router-plugin/src/core/router-hmr-plugin.ts b/packages/router-plugin/src/core/router-hmr-plugin.ts index 75dfde11f8..e1dc01a43b 100644 --- a/packages/router-plugin/src/core/router-hmr-plugin.ts +++ b/packages/router-plugin/src/core/router-hmr-plugin.ts @@ -1,6 +1,6 @@ import { generateFromAst, logDiff, parseAst } from '@tanstack/router-utils' import { compileCodeSplitReferenceRoute } from './code-splitter/compilers' -import { getReferenceRouteCompilerPlugins } from './code-splitter/plugins/framework-plugins' +import { getFrameworkHmrCompilerPlugins } from './code-splitter/plugins/framework-plugins' import { createRouteHmrStatement } from './hmr' import { debug, normalizePath, routeFactoryCallCodeFilter } from './utils' import { getConfig } from './config' @@ -50,9 +50,8 @@ export function createRouterHmrPlugin( const hmrStyle = userConfig.plugin?.hmr?.style ?? 'vite' if (userConfig.target === 'react') { - const compilerPlugins = getReferenceRouteCompilerPlugins({ + const compilerPlugins = getFrameworkHmrCompilerPlugins({ targetFramework: 'react', - addHmr: true, hmrStyle, }) const compiled = compileCodeSplitReferenceRoute({ diff --git a/packages/router-plugin/src/index.ts b/packages/router-plugin/src/index.ts index e390c046a2..6a6ad158bb 100644 --- a/packages/router-plugin/src/index.ts +++ b/packages/router-plugin/src/index.ts @@ -13,8 +13,10 @@ export type { export type { RouterPluginContext } from './core/router-plugin-context' export { getObjectPropertyKeyName } from './core/utils' export type { + CodeSplitCompilerPlugin, ReferenceRouteCompilerPlugin, ReferenceRouteCompilerPluginContext, + VirtualRouteSplitNodeCompilerPluginContext, } from './core/code-splitter/plugins' export { tsrSplit, diff --git a/packages/router-plugin/tests/add-hmr.test.ts b/packages/router-plugin/tests/add-hmr.test.ts index 4430cef74c..98b3e61a5f 100644 --- a/packages/router-plugin/tests/add-hmr.test.ts +++ b/packages/router-plugin/tests/add-hmr.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest' import { compileCodeSplitReferenceRoute } from '../src/core/code-splitter/compilers' import { defaultCodeSplitGroupings } from '../src/core/constants' -import { getReferenceRouteCompilerPlugins } from '../src/core/code-splitter/plugins/framework-plugins' +import { getFrameworkHmrCompilerPlugins } from '../src/core/code-splitter/plugins/framework-plugins' import { createRouteHmrStatement } from '../src/core/hmr' import { frameworks } from './constants' @@ -32,9 +32,8 @@ describe('add-hmr works', () => { addHmr: true, codeSplitGroupings: defaultCodeSplitGroupings, targetFramework: framework, - compilerPlugins: getReferenceRouteCompilerPlugins({ + compilerPlugins: getFrameworkHmrCompilerPlugins({ targetFramework: framework, - addHmr: true, }), }) @@ -57,10 +56,6 @@ describe('add-hmr works', () => { addHmr: false, codeSplitGroupings: defaultCodeSplitGroupings, targetFramework: framework, - compilerPlugins: getReferenceRouteCompilerPlugins({ - targetFramework: framework, - addHmr: false, - }), }) await expect(compileResult?.code || code).toMatchFileSnapshot( @@ -86,9 +81,8 @@ describe('add-hmr works', () => { hmrStyle: 'webpack', codeSplitGroupings: defaultCodeSplitGroupings, targetFramework: framework, - compilerPlugins: getReferenceRouteCompilerPlugins({ + compilerPlugins: getFrameworkHmrCompilerPlugins({ targetFramework: framework, - addHmr: true, hmrStyle: 'webpack', }), }) @@ -111,7 +105,6 @@ describe('add-hmr works', () => { expect(output).toContain('webpackHot') expect(output).not.toContain('import.meta.hot') expect(output).toContain('oldHasShellComponent') - expect(output).toContain('__routeContext') }) it('supports configurable Vite unsplittable HMR generation', async () => { @@ -127,6 +120,36 @@ describe('add-hmr works', () => { expect(output).toContain('newModule') }) + it('generates lazy reset and scoped route refresh without route context mutation', async () => { + const filename = 'arrow-function.tsx' + const framework = 'react' + const file = await readFile( + path.join(getFrameworkDir(framework).files, filename), + ) + const code = file.toString() + const compileResult = compileCodeSplitReferenceRoute({ + code, + filename, + id: filename, + addHmr: true, + codeSplitGroupings: defaultCodeSplitGroupings, + targetFramework: framework, + compilerPlugins: getFrameworkHmrCompilerPlugins({ + targetFramework: framework, + }), + }) + if (!compileResult) { + throw new Error('Expected the reference route to be transformed') + } + const output = compileResult.code + + expect(output).toContain( + 'router._replaceRouteChunk(oldRoute, newRoute.lazyFn);', + ) + expect(output).toContain('void router._refreshRoute?.();') + expect(output).not.toContain('__routeContext') + }) + it('uses a generated route id fallback for Vite HMR', async () => { const statement = createRouteHmrStatement([], { hmrStyle: 'vite', diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx index a463d9ab22..4f32369e8f 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx @@ -52,13 +52,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -77,48 +70,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -127,46 +83,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx index 44908e32a9..12e47f429e 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx @@ -38,13 +38,6 @@ if (import.meta.webpackHot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -63,48 +56,11 @@ if (import.meta.webpackHot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -113,46 +69,6 @@ if (import.meta.webpackHot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } })(routeId, Route); try { const tsrReactRefreshUtils = typeof __react_refresh_utils__ !== 'undefined' ? __react_refresh_utils__ : undefined; diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@false.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@false.tsx new file mode 100644 index 0000000000..a2983e31bd --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@false.tsx @@ -0,0 +1,12 @@ +const $$splitComponentImporter = () => import('createFileRoute-lowercase-components.tsx?tsr-split=component'); +const $$splitErrorComponentImporter = () => import('createFileRoute-lowercase-components.tsx?tsr-split=errorComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export const Route = createFileRoute('/lowercase-components')({ + pendingComponent: pendingComponent, + errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent'), + component: lazyRouteComponent($$splitComponentImporter, 'component') +}); +function pendingComponent() { + return
Pending
; +} \ No newline at end of file diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@true.tsx new file mode 100644 index 0000000000..70c428363e --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@true.tsx @@ -0,0 +1,120 @@ +const $$splitComponentImporter = () => import('createFileRoute-lowercase-components.tsx?tsr-split=component'); +const $$splitErrorComponentImporter = () => import('createFileRoute-lowercase-components.tsx?tsr-split=errorComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +const TSRSplitErrorComponent = (() => { + const hot = import.meta.hot; + const hotData = hot ? hot.data ??= {} : undefined; + return hotData?.["tsr-split-component:errorComponent"] ?? lazyRouteComponent($$splitErrorComponentImporter, "errorComponent"); +})(); +if (import.meta.hot) { + (import.meta.hot.data ??= {})["tsr-split-component:errorComponent"] = TSRSplitErrorComponent; +} +const TSRSplitComponent = (() => { + const hot = import.meta.hot; + const hotData = hot ? hot.data ??= {} : undefined; + return hotData?.["tsr-split-component:component"] ?? lazyRouteComponent($$splitComponentImporter, "component"); +})(); +if (import.meta.hot) { + (import.meta.hot.data ??= {})["tsr-split-component:component"] = TSRSplitComponent; +} +export const Route = createFileRoute('/lowercase-components')({ + pendingComponent: TSRPendingComponent, + errorComponent: TSRSplitErrorComponent, + component: TSRSplitComponent +}); +function TSRPendingComponent() { + return
Pending
; +} +const hot = import.meta.hot; +if (hot && typeof window !== 'undefined') { + hot.data ??= {}; + const tsrReactRefresh = window.__TSR_REACT_REFRESH__ ??= (() => { + const ignoredExportsById = new Map(); + const previousGetIgnoredExports = window.__getReactRefreshIgnoredExports; + window.__getReactRefreshIgnoredExports = ctx => { + const ignoredExports = previousGetIgnoredExports?.(ctx) ?? []; + const moduleIgnored = ignoredExportsById.get(ctx.id) ?? []; + return [...ignoredExports, ...moduleIgnored]; + }; + return { + ignoredExportsById + }; + })(); + tsrReactRefresh.ignoredExportsById.set("createFileRoute-lowercase-components.tsx", ['Route']); +} +export function TSRFastRefreshAnchor() { + return null; +} +if (import.meta.hot) { + const hot = import.meta.hot; + const hotData = hot.data ??= {}; + const handleRouteUpdate = function handleRouteUpdate(routeId, newRoute) { + const router = window.__TSR_ROUTER__; + const oldRoute = router.routesById[routeId]; + if (!oldRoute) { + return; + } + ; + const generatedRouteOptionKeys = new Set(["id", "path", "getParentRoute"]); + const generatedRouteOptions = {}; + generatedRouteOptionKeys.forEach(key => { + if (key in oldRoute.options) { + generatedRouteOptions[key] = oldRoute.options[key]; + } + }); + const oldHasShellComponent = "shellComponent" in oldRoute.options; + const newHasShellComponent = "shellComponent" in newRoute.options; + const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; + const componentKeys = ["component", "shellComponent", "pendingComponent", "errorComponent", "notFoundComponent"]; + if (preserveComponentIdentity) { + componentKeys.forEach(key => { + if (key in oldRoute.options && key in newRoute.options) { + newRoute.options[key] = oldRoute.options[key]; + } + }); + } + ; + const nextOptions = { + ...newRoute.options, + ...generatedRouteOptions + }; + oldRoute.options = nextOptions; + oldRoute.update(nextOptions); + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); + router.setRoutes(router.buildRouteTree()); + syncHotRouteExport(oldRoute); + router.resolvePathCache.clear(); + void router._refreshRoute?.(); + function syncHotRouteExport(liveRoute) { + newRoute.options = liveRoute.options; + newRoute.parentRoute = liveRoute.parentRoute; + newRoute._path = liveRoute._path; + newRoute._id = liveRoute._id; + newRoute._fullPath = liveRoute._fullPath; + newRoute._to = liveRoute._to; + } + }; + const initialRouteId = Route.id ?? hotData['tsr-route-id']; + if (initialRouteId) { + hotData['tsr-route-id'] = initialRouteId; + } + const existingRoute = typeof window !== 'undefined' && initialRouteId ? window.__TSR_ROUTER__?.routesById?.[initialRouteId] : undefined; + if (initialRouteId && existingRoute && existingRoute !== Route) { + handleRouteUpdate(initialRouteId, Route); + hotData['tsr-route-update-handled'] = Route; + } + hot.accept(newModule => { + if (Route && newModule && newModule.Route) { + const routeId = hotData['tsr-route-id'] ?? Route.id; + if (routeId) { + hotData['tsr-route-id'] = routeId; + } + if (hotData['tsr-route-update-handled'] === newModule.Route) { + delete hotData['tsr-route-update-handled']; + return; + } + handleRouteUpdate(routeId, newModule.Route); + } + }); +} \ No newline at end of file diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx index 9ef182f4b7..e5a473dc14 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx @@ -41,13 +41,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -66,48 +59,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -116,46 +72,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@false.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@false.tsx new file mode 100644 index 0000000000..20c9a07798 --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@false.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + shellComponent: shellComponent, + pendingComponent: pendingComponent, + errorComponent: errorComponent, + component: component, +}) + +function shellComponent({ children }: { children: React.ReactNode }) { + return {children} +} + +function pendingComponent() { + return
Pending
+} + +function errorComponent() { + return
Error
+} + +function component() { + return
Hello
+} diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@true.tsx new file mode 100644 index 0000000000..cb5014cd35 --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@true.tsx @@ -0,0 +1,116 @@ +import * as React from 'react'; +import { createRootRoute } from '@tanstack/react-router'; +export const Route = createRootRoute({ + shellComponent: TSRShellComponent, + pendingComponent: TSRPendingComponent, + errorComponent: TSRErrorComponent, + component: TSRComponent +}); +function TSRShellComponent({ + children +}: { + children: React.ReactNode; +}) { + return {children}; +} +function TSRPendingComponent() { + return
Pending
; +} +function TSRErrorComponent() { + return
Error
; +} +function TSRComponent() { + return
Hello
; +} +const hot = import.meta.hot; +if (hot && typeof window !== 'undefined') { + hot.data ??= {}; + const tsrReactRefresh = window.__TSR_REACT_REFRESH__ ??= (() => { + const ignoredExportsById = new Map(); + const previousGetIgnoredExports = window.__getReactRefreshIgnoredExports; + window.__getReactRefreshIgnoredExports = ctx => { + const ignoredExports = previousGetIgnoredExports?.(ctx) ?? []; + const moduleIgnored = ignoredExportsById.get(ctx.id) ?? []; + return [...ignoredExports, ...moduleIgnored]; + }; + return { + ignoredExportsById + }; + })(); + tsrReactRefresh.ignoredExportsById.set("createRootRoute-lowercase-components.tsx", ['Route']); +} +export function TSRFastRefreshAnchor() { + return null; +} +if (import.meta.hot) { + const hot = import.meta.hot; + const hotData = hot.data ??= {}; + const handleRouteUpdate = function handleRouteUpdate(routeId, newRoute) { + const router = window.__TSR_ROUTER__; + const oldRoute = router.routesById[routeId]; + if (!oldRoute) { + return; + } + ; + const generatedRouteOptionKeys = new Set(["id", "path", "getParentRoute"]); + const generatedRouteOptions = {}; + generatedRouteOptionKeys.forEach(key => { + if (key in oldRoute.options) { + generatedRouteOptions[key] = oldRoute.options[key]; + } + }); + const oldHasShellComponent = "shellComponent" in oldRoute.options; + const newHasShellComponent = "shellComponent" in newRoute.options; + const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; + const componentKeys = ["component", "shellComponent", "pendingComponent", "errorComponent", "notFoundComponent"]; + if (preserveComponentIdentity) { + componentKeys.forEach(key => { + if (key in oldRoute.options && key in newRoute.options) { + newRoute.options[key] = oldRoute.options[key]; + } + }); + } + ; + const nextOptions = { + ...newRoute.options, + ...generatedRouteOptions + }; + oldRoute.options = nextOptions; + oldRoute.update(nextOptions); + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); + router.setRoutes(router.buildRouteTree()); + syncHotRouteExport(oldRoute); + router.resolvePathCache.clear(); + void router._refreshRoute?.(); + function syncHotRouteExport(liveRoute) { + newRoute.options = liveRoute.options; + newRoute.parentRoute = liveRoute.parentRoute; + newRoute._path = liveRoute._path; + newRoute._id = liveRoute._id; + newRoute._fullPath = liveRoute._fullPath; + newRoute._to = liveRoute._to; + } + }; + const initialRouteId = Route.id ?? hotData['tsr-route-id']; + if (initialRouteId) { + hotData['tsr-route-id'] = initialRouteId; + } + const existingRoute = typeof window !== 'undefined' && initialRouteId ? window.__TSR_ROUTER__?.routesById?.[initialRouteId] : undefined; + if (initialRouteId && existingRoute && existingRoute !== Route) { + handleRouteUpdate(initialRouteId, Route); + hotData['tsr-route-update-handled'] = Route; + } + hot.accept(newModule => { + if (Route && newModule && newModule.Route) { + const routeId = hotData['tsr-route-id'] ?? Route.id; + if (routeId) { + hotData['tsr-route-id'] = routeId; + } + if (hotData['tsr-route-update-handled'] === newModule.Route) { + delete hotData['tsr-route-update-handled']; + return; + } + handleRouteUpdate(routeId, newModule.Route); + } + }); +} \ No newline at end of file diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute@true.tsx index 9789370efc..7ba271c212 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute@true.tsx @@ -43,13 +43,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -68,48 +61,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -118,46 +74,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRouteWithContext-type-args@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRouteWithContext-type-args@true.tsx index 6c7073a8f3..b66449aebf 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRouteWithContext-type-args@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/createRootRouteWithContext-type-args@true.tsx @@ -46,13 +46,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -71,48 +64,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -121,46 +77,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx index bee793d372..fee0b2f2e6 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx @@ -40,13 +40,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -65,48 +58,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -115,46 +71,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx index 75b457b778..4a925828e2 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx @@ -52,13 +52,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -77,48 +70,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -127,46 +83,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx index 7f28b7b3d3..ca2a7ab2e2 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx @@ -62,13 +62,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -87,48 +80,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -137,46 +93,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx index 6bee992948..b6f67e67b1 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx @@ -62,13 +62,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -87,48 +80,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -137,46 +93,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx b/packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx index 2d1be7e32b..754da5c902 100644 --- a/packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx +++ b/packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx @@ -23,13 +23,6 @@ if (import.meta.hot) { generatedRouteOptions[key] = oldRoute.options[key]; } }); - const removedKeys = new Set(); - Object.keys(oldRoute.options).forEach(key => { - if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) { - removedKeys.add(key); - delete oldRoute.options[key]; - } - }); const oldHasShellComponent = "shellComponent" in oldRoute.options; const newHasShellComponent = "shellComponent" in newRoute.options; const preserveComponentIdentity = oldHasShellComponent === newHasShellComponent; @@ -48,48 +41,11 @@ if (import.meta.hot) { }; oldRoute.options = nextOptions; oldRoute.update(nextOptions); - oldRoute._componentsPromise = undefined; - oldRoute._lazyPromise = undefined; + router._replaceRouteChunk(oldRoute, newRoute.lazyFn); router.setRoutes(router.buildRouteTree()); syncHotRouteExport(oldRoute); router.resolvePathCache.clear(); - const filter = m => m.routeId === oldRoute.id; - const activeMatch = router.stores.matches.get().find(filter); - const pendingMatch = router.stores.pendingMatches.get().find(filter); - const cachedMatches = router.stores.cachedMatches.get().filter(filter); - if (activeMatch || pendingMatch || cachedMatches.length > 0) { - if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) { - const matchIds = [activeMatch?.id, pendingMatch?.id, ...cachedMatches.map(match => match.id)].filter(Boolean); - router.batch(() => { - for (const matchId of matchIds) { - const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId); - if (store) { - store.set(prev => { - const next = { - ...prev - }; - if (removedKeys.has("loader")) { - next.loaderData = undefined; - } - ; - if (removedKeys.has("beforeLoad")) { - next.__beforeLoadContext = undefined; - next.context = rebuildMatchContextWithoutBeforeLoad(next); - } - ; - return next; - }); - } - } - }); - } - ; - router.invalidate({ - filter, - sync: true - }); - } - ; + void router._refreshRoute?.(); function syncHotRouteExport(liveRoute) { newRoute.options = liveRoute.options; newRoute.parentRoute = liveRoute.parentRoute; @@ -98,46 +54,6 @@ if (import.meta.hot) { newRoute._fullPath = liveRoute._fullPath; newRoute._to = liveRoute._to; } - function getStoreMatch(matchId) { - return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get(); - } - function getMatchList(matchId) { - const pendingMatches = router.stores.pendingMatches.get(); - if (pendingMatches.some(match => match.id === matchId)) { - return pendingMatches; - } - ; - const activeMatches = router.stores.matches.get(); - if (activeMatches.some(match => match.id === matchId)) { - return activeMatches; - } - ; - const cachedMatches = router.stores.cachedMatches.get(); - if (cachedMatches.some(match => match.id === matchId)) { - return cachedMatches; - } - ; - return []; - } - function getParentMatch(match) { - const matchList = getMatchList(match.id); - const matchIndex = matchList.findIndex(item => item.id === match.id); - if (matchIndex <= 0) { - return undefined; - } - ; - const parentMatch = matchList[matchIndex - 1]; - return getStoreMatch(parentMatch.id) || parentMatch; - } - function rebuildMatchContextWithoutBeforeLoad(match) { - const parentMatch = getParentMatch(match); - const getParentContext = router.getParentContext; - const parentContext = getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context; - return { - ...(parentContext ?? {}), - ...(match.__routeContext ?? {}) - }; - } }; const initialRouteId = Route.id ?? hotData['tsr-route-id']; if (initialRouteId) { diff --git a/packages/router-plugin/tests/add-hmr/test-files/react/createFileRoute-lowercase-components.tsx b/packages/router-plugin/tests/add-hmr/test-files/react/createFileRoute-lowercase-components.tsx new file mode 100644 index 0000000000..a09f8d24e2 --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/test-files/react/createFileRoute-lowercase-components.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/lowercase-components')({ + pendingComponent: pendingComponent, + errorComponent: errorComponent, + component: component, +}) + +function pendingComponent() { + return
Pending
+} + +function errorComponent() { + return
Error
+} + +function component() { + return
Hello
+} diff --git a/packages/router-plugin/tests/add-hmr/test-files/react/createRootRoute-lowercase-components.tsx b/packages/router-plugin/tests/add-hmr/test-files/react/createRootRoute-lowercase-components.tsx new file mode 100644 index 0000000000..20c9a07798 --- /dev/null +++ b/packages/router-plugin/tests/add-hmr/test-files/react/createRootRoute-lowercase-components.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + shellComponent: shellComponent, + pendingComponent: pendingComponent, + errorComponent: errorComponent, + component: component, +}) + +function shellComponent({ children }: { children: React.ReactNode }) { + return {children} +} + +function pendingComponent() { + return
Pending
+} + +function errorComponent() { + return
Error
+} + +function component() { + return
Hello
+} diff --git a/packages/router-plugin/tests/code-splitter.test.ts b/packages/router-plugin/tests/code-splitter.test.ts index ae64e2778b..967f102e4d 100644 --- a/packages/router-plugin/tests/code-splitter.test.ts +++ b/packages/router-plugin/tests/code-splitter.test.ts @@ -20,6 +20,7 @@ import { } from '../src/core/code-splitter/compilers' import { createIdentifier } from '@tanstack/router-utils' import { defaultCodeSplitGroupings } from '../src/core/constants' +import { getFrameworkHmrCompilerPlugins } from '../src/core/code-splitter/plugins/framework-plugins' import { frameworks } from './constants' import type { CodeSplitGroupings } from '../src/core/constants' @@ -165,6 +166,62 @@ describe('code-splitter works', () => { }) }) +describe('React Refresh virtual route compiler plugin', () => { + const splitComponentCases = [ + { splitType: 'component', splitIdentifier: 'SplitComponent' }, + { + splitType: 'pendingComponent', + splitIdentifier: 'SplitPendingComponent', + }, + { splitType: 'errorComponent', splitIdentifier: 'SplitErrorComponent' }, + ] as const + + function createRouteCode(splitType: string) { + return ` +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ ${splitType} }) + +function ${splitType}() { + return
lowercase function declaration
+} +` + } + + it.each(splitComponentCases)( + 'renames a lowercase $splitType binding when HMR is enabled', + ({ splitType, splitIdentifier }) => { + const result = compileCodeSplitVirtualRoute({ + code: createRouteCode(splitType), + filename: `lowercase-function-declaration.tsx?tsr-split=${splitType}`, + splitTargets: [splitType], + compilerPlugins: getFrameworkHmrCompilerPlugins({ + targetFramework: 'react', + }), + }) + + expect(result.code).toContain(`function ${splitIdentifier}()`) + expect(result.code).toContain( + `export { ${splitIdentifier} as ${splitType} }`, + ) + }, + ) + + it.each(splitComponentCases)( + 'keeps a lowercase $splitType binding when HMR is disabled', + ({ splitType }) => { + const result = compileCodeSplitVirtualRoute({ + code: createRouteCode(splitType), + filename: `lowercase-function-declaration.tsx?tsr-split=${splitType}`, + splitTargets: [splitType], + }) + + expect(result.code).toContain(`function ${splitType}()`) + expect(result.code).toContain(`export { ${splitType} }`) + }, + ) +}) + describe('computeSharedBindings fast paths', () => { it('returns empty when only one split group is present (default groupings)', () => { const code = ` diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration.tsx new file mode 100644 index 0000000000..b2f908ab45 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration.tsx @@ -0,0 +1,12 @@ +const $$splitErrorComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=errorComponent'); +const $$splitComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export const Route = createFileRoute('/')({ + component: lazyRouteComponent($$splitComponentImporter, 'component'), + pendingComponent, + errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent') +}); +function pendingComponent() { + return
lowercase pending function declaration
; +} \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@component.tsx new file mode 100644 index 0000000000..65735a0f20 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@component.tsx @@ -0,0 +1,4 @@ +function component() { + return
lowercase function declaration
; +} +export { component }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@errorComponent.tsx new file mode 100644 index 0000000000..acc1c0be8d --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@errorComponent.tsx @@ -0,0 +1,4 @@ +function errorComponent() { + return
lowercase error function declaration
; +} +export { errorComponent }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@notFoundComponent.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/lowercase-function-declaration@shared.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration.tsx new file mode 100644 index 0000000000..75657cb44a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration.tsx @@ -0,0 +1,10 @@ +const $$splitErrorComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component---errorComponent---notFoundComponent---pendingComponent'); +const $$splitPendingComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component---errorComponent---notFoundComponent---pendingComponent'); +const $$splitComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component---errorComponent---notFoundComponent---pendingComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export const Route = createFileRoute('/')({ + component: lazyRouteComponent($$splitComponentImporter, 'component'), + pendingComponent: lazyRouteComponent($$splitPendingComponentImporter, 'pendingComponent'), + errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent') +}); \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 0000000000..86876d616e --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,12 @@ +function component() { + return
lowercase function declaration
; +} +function pendingComponent() { + return
lowercase pending function declaration
; +} +function errorComponent() { + return
lowercase error function declaration
; +} +export { component }; +export { pendingComponent }; +export { errorComponent }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@loader.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/lowercase-function-declaration@shared.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration.tsx new file mode 100644 index 0000000000..574c6d4c2b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration.tsx @@ -0,0 +1,10 @@ +const $$splitErrorComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=errorComponent'); +const $$splitPendingComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); +const $$splitComponentImporter = () => import('lowercase-function-declaration.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export const Route = createFileRoute('/')({ + component: lazyRouteComponent($$splitComponentImporter, 'component'), + pendingComponent: lazyRouteComponent($$splitPendingComponentImporter, 'pendingComponent'), + errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent') +}); \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 0000000000..382621ea9a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@component---loader---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,8 @@ +function component() { + return
lowercase function declaration
; +} +function pendingComponent() { + return
lowercase pending function declaration
; +} +export { component }; +export { pendingComponent }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@errorComponent.tsx new file mode 100644 index 0000000000..acc1c0be8d --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@errorComponent.tsx @@ -0,0 +1,4 @@ +function errorComponent() { + return
lowercase error function declaration
; +} +export { errorComponent }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/lowercase-function-declaration@shared.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/lowercase-function-declaration.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/lowercase-function-declaration.tsx new file mode 100644 index 0000000000..bfc37b1d6a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/lowercase-function-declaration.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component, + pendingComponent, + errorComponent, +}) + +function component() { + return
lowercase function declaration
+} + +function pendingComponent() { + return
lowercase pending function declaration
+} + +function errorComponent() { + return
lowercase error function declaration
+} diff --git a/packages/router-plugin/tests/handle-route-update.test.ts b/packages/router-plugin/tests/handle-route-update.test.ts index 9a80bfbeb4..db371746af 100644 --- a/packages/router-plugin/tests/handle-route-update.test.ts +++ b/packages/router-plugin/tests/handle-route-update.test.ts @@ -1,3 +1,5 @@ +// @vitest-environment jsdom + import { BaseRootRoute, BaseRoute, @@ -6,10 +8,23 @@ import { createNonReactiveReadonlyStore, trimPathRight, } from '@tanstack/router-core' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' +import { Fragment, act, createElement } from 'react' +import { createPortal } from 'react-dom' +import { createRoot } from 'react-dom/client' +import { createMemoryHistory } from '../../history/src' +import { HeadContent } from '../../react-router/src/HeadContent' +import { RouterContextProvider } from '../../react-router/src/RouterProvider' +import { Scripts } from '../../react-router/src/Scripts' +import { Router as ReactRouter } from '../../react-router/src/router' import { getHandleRouteUpdateCode } from '../src/core/hmr' import type { AnyRoute, GetStoreConfig } from '@tanstack/router-core' +vi.mock('@tanstack/router-core/isServer', async (importOriginal) => ({ + ...(await importOriginal()), + isServer: undefined, +})) + const getStoreConfig: GetStoreConfig = () => ({ createMutableStore: createNonReactiveMutableStore, createReadonlyStore: createNonReactiveReadonlyStore, @@ -23,6 +38,10 @@ const getHandleRouteUpdate = () => { ) => void } +function asAnyRoute(route: TRoute): TRoute & AnyRoute { + return route as unknown as TRoute & AnyRoute +} + function createTestHistory() { const location = { href: '/', @@ -51,10 +70,10 @@ function createTestHistory() { } } -function createTestRouter(routeTree: AnyRoute) { +function createTestRouter(routeTree: TRouteTree) { return new RouterCore( { - routeTree, + routeTree: asAnyRoute(routeTree), history: createTestHistory() as any, isServer: true, }, @@ -62,19 +81,64 @@ function createTestRouter(routeTree: AnyRoute) { ) } +function createClientTestRouter( + routeTree: TRouteTree, + initialEntry: string, +) { + return new RouterCore( + { + routeTree: asAnyRoute(routeTree), + history: createMemoryHistory({ initialEntries: [initialEntry] }), + isServer: false, + origin: 'http://localhost', + }, + getStoreConfig, + ) +} + +function createReactClientTestRouter( + routeTree: TRouteTree, + initialEntry: string, +) { + return new ReactRouter({ + routeTree: asAnyRoute(routeTree), + history: createMemoryHistory({ initialEntries: [initialEntry] }), + isServer: false, + origin: 'http://localhost', + }) +} + function withWindowRouter(router: RouterCore) { - const previousWindow = (globalThis as any).window - ;(globalThis as any).window = { __TSR_ROUTER__: router } + const testWindow = globalThis.window as any + const hadOwnRouter = Object.prototype.hasOwnProperty.call( + testWindow, + '__TSR_ROUTER__', + ) + const previousRouter = testWindow.__TSR_ROUTER__ + testWindow.__TSR_ROUTER__ = router return () => { - if (previousWindow) { - ;(globalThis as any).window = previousWindow + if (hadOwnRouter) { + testWindow.__TSR_ROUTER__ = previousRouter } else { - delete (globalThis as any).window + delete testWindow.__TSR_ROUTER__ } } } +function runHandleRouteUpdate( + router: RouterCore, + routeId: string, + newRoute: TRoute, +) { + const restoreWindow = withWindowRouter(router) + try { + getHandleRouteUpdate()(routeId, asAnyRoute(newRoute)) + } finally { + restoreWindow() + } +} + function getProcessedTreeRouteForPath( router: RouterCore, path: string, @@ -86,25 +150,25 @@ describe('handleRouteUpdate', () => { it('keeps routesByPath pointed at an index route when a pathless layout updates', () => { const rootRoute = new BaseRootRoute({}) const parentRoute = new BaseRoute({ - getParentRoute: () => rootRoute, + getParentRoute: () => asAnyRoute(rootRoute), path: '/livestock/$farmId/medicine', }) const pathlessRoute = new BaseRoute({ - getParentRoute: () => parentRoute, + getParentRoute: () => asAnyRoute(parentRoute), id: '_form', }) const newRoute = new BaseRoute({ - getParentRoute: () => pathlessRoute, + getParentRoute: () => asAnyRoute(pathlessRoute), path: 'new', }) const indexRoute = new BaseRoute({ - getParentRoute: () => parentRoute, + getParentRoute: () => asAnyRoute(parentRoute), path: '/', }) const routeTree = rootRoute.addChildren([ parentRoute.addChildren([ - pathlessRoute.addChildren([newRoute]), - indexRoute, + pathlessRoute.addChildren([asAnyRoute(newRoute)]), + asAnyRoute(indexRoute), ]), ]) const router = createTestRouter(routeTree) @@ -114,7 +178,10 @@ describe('handleRouteUpdate', () => { const restoreWindow = withWindowRouter(router) try { - getHandleRouteUpdate()(pathlessRoute.id, new BaseRoute({} as any)) + getHandleRouteUpdate()( + pathlessRoute.id, + asAnyRoute(new BaseRoute({} as any)), + ) } finally { restoreWindow() } @@ -128,15 +195,15 @@ describe('handleRouteUpdate', () => { it('keeps an index route winning the trimmed path key when its parent updates', () => { const rootRoute = new BaseRootRoute({}) const parentRoute = new BaseRoute({ - getParentRoute: () => rootRoute, + getParentRoute: () => asAnyRoute(rootRoute), path: '/path', }) const indexRoute = new BaseRoute({ - getParentRoute: () => parentRoute, + getParentRoute: () => asAnyRoute(parentRoute), path: '/', }) const routeTree = rootRoute.addChildren([ - parentRoute.addChildren([indexRoute]), + parentRoute.addChildren([asAnyRoute(indexRoute)]), ]) const router = createTestRouter(routeTree) const key = trimPathRight(indexRoute.fullPath) @@ -145,7 +212,10 @@ describe('handleRouteUpdate', () => { const restoreWindow = withWindowRouter(router) try { - getHandleRouteUpdate()(parentRoute.id, new BaseRoute({} as any)) + getHandleRouteUpdate()( + parentRoute.id, + asAnyRoute(new BaseRoute({} as any)), + ) } finally { restoreWindow() } @@ -159,7 +229,7 @@ describe('handleRouteUpdate', () => { it('refreshes matching data when params.parse changes', () => { const rootRoute = new BaseRootRoute({}) const itemRoute = new BaseRoute({ - getParentRoute: () => rootRoute, + getParentRoute: () => asAnyRoute(rootRoute), path: '/items/$itemId', params: { parse: ({ itemId }: { itemId: string }) => { @@ -167,7 +237,7 @@ describe('handleRouteUpdate', () => { }, }, }) - const routeTree = rootRoute.addChildren([itemRoute]) + const routeTree = rootRoute.addChildren([asAnyRoute(itemRoute)]) const router = createTestRouter(routeTree) expect(router.getMatchedRoutes('/items/abc').foundRoute?.id).toBeUndefined() @@ -176,11 +246,13 @@ describe('handleRouteUpdate', () => { try { getHandleRouteUpdate()( itemRoute.id, - new BaseRoute({ - params: { - parse: ({ itemId }: { itemId: string }) => ({ itemId }), - }, - } as any), + asAnyRoute( + new BaseRoute({ + params: { + parse: ({ itemId }: { itemId: string }) => ({ itemId }), + }, + } as any), + ), ) } finally { restoreWindow() @@ -194,10 +266,10 @@ describe('handleRouteUpdate', () => { it('hydrates the hot module route export with generated route tree state', () => { const rootRoute = new BaseRootRoute({}) const itemRoute = new BaseRoute({ - getParentRoute: () => rootRoute, + getParentRoute: () => asAnyRoute(rootRoute), path: '/items/$itemId', }) - const routeTree = rootRoute.addChildren([itemRoute]) + const routeTree = rootRoute.addChildren([asAnyRoute(itemRoute)]) const router = createTestRouter(routeTree) const newRoute = new BaseRoute({} as any) @@ -205,7 +277,7 @@ describe('handleRouteUpdate', () => { const restoreWindow = withWindowRouter(router) try { - getHandleRouteUpdate()(itemRoute.id, newRoute) + getHandleRouteUpdate()(itemRoute.id, asAnyRoute(newRoute)) } finally { restoreWindow() } @@ -217,4 +289,366 @@ describe('handleRouteUpdate', () => { expect((newRoute as any).parentRoute).toBe(rootRoute) expect((newRoute as any).options).toBe((itemRoute as any).options) }) + + it('clears lazy ownership and delegates rematerialization to the router', () => { + const rootRoute = new BaseRootRoute({}) + const itemRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/items/$itemId', + }) + const router = createTestRouter( + rootRoute.addChildren([asAnyRoute(itemRoute)]), + ) + const lazyOwner = Promise.resolve() + const refreshRoute = vi.fn(async () => {}) + ;(itemRoute as any)._lazy = lazyOwner + ;(router as any)._refreshRoute = refreshRoute + + expect((itemRoute as any)._lazy).toBe(lazyOwner) + + runHandleRouteUpdate(router, itemRoute.id, new BaseRoute({} as any)) + + expect((itemRoute as any)._lazy).toBeUndefined() + expect(refreshRoute).toHaveBeenCalledTimes(1) + expect(refreshRoute).toHaveBeenCalledWith() + }) + + it('uses a fresh source lazy importer without erasing an omitted one', () => { + const oldImporter = vi.fn(async () => ({ options: {} }) as any) + const newImporter = vi.fn(async () => ({ options: {} }) as any) + const rootRoute = new BaseRootRoute({}) + const itemRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/items/$itemId', + }).lazy(oldImporter) + const router = createTestRouter( + rootRoute.addChildren([asAnyRoute(itemRoute)]), + ) + + runHandleRouteUpdate( + router, + itemRoute.id, + new BaseRoute({} as any).lazy(newImporter), + ) + expect(itemRoute.lazyFn).toBe(newImporter) + + runHandleRouteUpdate(router, itemRoute.id, new BaseRoute({} as any)) + expect(itemRoute.lazyFn).toBe(newImporter) + }) + + it('removes stale loader data when a hot route removes its loader', async () => { + const rootRoute = new BaseRootRoute({}) + const hotRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/hot', + loader: () => 'stale loader data', + }) + const router = createClientTestRouter( + rootRoute.addChildren([asAnyRoute(hotRoute)]), + '/hot', + ) + await router.load() + + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + hotRoute.id, + ]) + expect(router.state.matches[1]!.loaderData).toBe('stale loader data') + + const restoreWindow = withWindowRouter(router) + try { + getHandleRouteUpdate()(hotRoute.id, asAnyRoute(new BaseRoute({} as any))) + await vi.waitFor(() => { + expect(router.state.matches.map((match) => match.routeId)).toEqual([ + rootRoute.id, + hotRoute.id, + ]) + expect(router.state.matches[1]!.loaderData).toBeUndefined() + }) + } finally { + restoreWindow() + } + }) + + it('keeps hot loader data when an older refresh settles later', async () => { + let resolveStaleRefresh!: (value: string) => void + const staleRefresh = new Promise((resolve) => { + resolveStaleRefresh = resolve + }) + const oldLoader = vi + .fn<() => string | Promise>() + .mockReturnValueOnce('initial') + .mockReturnValueOnce(staleRefresh) + const newLoader = vi.fn(() => 'hot') + const rootRoute = new BaseRootRoute({ loader: oldLoader }) + const router = createClientTestRouter(rootRoute, '/') + + await router.load() + expect(router.state.matches[0]?.loaderData).toBe('initial') + + const oldRefresh = router.invalidate() + await vi.waitFor(() => expect(oldLoader).toHaveBeenCalledTimes(2)) + + runHandleRouteUpdate( + router, + rootRoute.id, + new BaseRootRoute({ loader: newLoader }), + ) + + await vi.waitFor(() => { + expect(router.state.matches[0]?.loaderData).toBe('hot') + }) + + resolveStaleRefresh('stale') + await oldRefresh + + expect(newLoader).toHaveBeenCalledTimes(1) + expect(router.state.matches[0]!.routeId).toBe(rootRoute.id) + expect(router.state.matches[0]!.loaderData).toBe('hot') + }) + + it('does not cache an obsolete inactive preload that settles after a hot update', async () => { + let resolveOldLoader!: (value: string) => void + const oldLoaderResult = new Promise((resolve) => { + resolveOldLoader = resolve + }) + const oldLoader = vi.fn(() => oldLoaderResult) + const newLoader = vi.fn(() => 'hot') + const rootRoute = new BaseRootRoute({}) + const hotRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/hot', + loader: oldLoader, + preloadStaleTime: Infinity, + }) + const router = createClientTestRouter( + rootRoute.addChildren([asAnyRoute(hotRoute)]), + '/', + ) + + await router.load() + const preload = router.preloadRoute({ to: '/hot' }) + await vi.waitFor(() => expect(oldLoader).toHaveBeenCalledOnce()) + + runHandleRouteUpdate( + router, + hotRoute.id, + new BaseRoute({ loader: newLoader } as any), + ) + + resolveOldLoader('obsolete') + await preload + await router.navigate({ to: '/hot' }) + + expect(newLoader).toHaveBeenCalledOnce() + expect(router.state.matches.at(-1)?.loaderData).toBe('hot') + }) + + it('observes hot parser and route-context changes on the active route', async () => { + const rootRoute = new BaseRootRoute({}) + const itemRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/items/$itemId', + params: { + parse: ({ itemId }: { itemId: string }) => ({ + itemId, + parser: 'old', + }), + }, + context: () => ({ source: 'old' }), + }) + const router = createClientTestRouter( + rootRoute.addChildren([asAnyRoute(itemRoute)]), + '/items/abc', + ) + + await router.load() + expect(router.state.matches[1]!.routeId).toBe(itemRoute.id) + expect(router.state.matches[1]!.params).toEqual({ + itemId: 'abc', + parser: 'old', + }) + expect(router.state.matches[1]!.context).toMatchObject({ source: 'old' }) + + runHandleRouteUpdate( + router, + itemRoute.id, + new BaseRoute({ + params: { + parse: ({ itemId }: { itemId: string }) => ({ + itemId, + parser: 'new', + }), + }, + context: () => ({ source: 'new' }), + } as any), + ) + + await vi.waitFor(() => { + expect(router.state.matches[1]!.routeId).toBe(itemRoute.id) + expect(router.state.matches[1]!.params).toEqual({ + itemId: 'abc', + parser: 'new', + }) + expect(router.state.matches[1]!.context).toMatchObject({ source: 'new' }) + }) + }) + + it('rematerializes descendants when a parent route definition changes', async () => { + const rootRoute = new BaseRootRoute({}) + const parentRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/parent', + context: () => ({ source: 'old' }), + }) + const childRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(parentRoute), + path: '/child', + context: ({ context }) => ({ inheritedSource: context.source }), + }) + const router = createClientTestRouter( + rootRoute.addChildren([ + parentRoute.addChildren([asAnyRoute(childRoute)]), + ]), + '/parent/child', + ) + + await router.load() + expect(router.state.matches[2]?.context).toMatchObject({ + inheritedSource: 'old', + }) + + runHandleRouteUpdate( + router, + parentRoute.id, + new BaseRoute({ context: () => ({ source: 'new' }) } as any), + ) + + await vi.waitFor(() => { + expect(router.state.matches[2]?.context).toMatchObject({ + inheritedSource: 'new', + }) + }) + }) + + it('does not run removed loader or beforeLoad callbacks on a later visit', async () => { + const beforeLoad = vi.fn(() => ({ hotBeforeLoad: true })) + const loader = vi.fn(() => 'hot loader data') + const rootRoute = new BaseRootRoute({}) + const hotRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/hot', + beforeLoad, + loader, + staleTime: Infinity, + gcTime: Infinity, + }) + const otherRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/other', + }) + const router = createClientTestRouter( + rootRoute.addChildren([asAnyRoute(hotRoute), asAnyRoute(otherRoute)]), + '/hot', + ) + + await router.load() + await router.navigate({ to: '/other' }) + expect(beforeLoad).toHaveBeenCalledTimes(1) + expect(loader).toHaveBeenCalledTimes(1) + + runHandleRouteUpdate(router, hotRoute.id, new BaseRoute({} as any)) + await router.navigate({ to: '/hot' }) + + const match = router.state.matches.find( + (candidate) => candidate.routeId === hotRoute.id, + ) + expect(beforeLoad).toHaveBeenCalledTimes(1) + expect(loader).toHaveBeenCalledTimes(1) + expect(match).toBeDefined() + expect(match!.loaderData).toBeUndefined() + expect(match!.context).not.toHaveProperty('hotBeforeLoad') + }) + + it('removes rendered HeadContent and Scripts output after a hot update', async () => { + const rootRoute = new BaseRootRoute({}) + const hotRoute = new BaseRoute({ + getParentRoute: () => asAnyRoute(rootRoute), + path: '/hot', + head: () => ({ + meta: [{ title: 'hot title' }], + scripts: [{ src: '/hot-head.js' }], + }), + scripts: () => [{ src: '/hot-body.js' }], + }) + const router = createReactClientTestRouter( + rootRoute.addChildren([asAnyRoute(hotRoute)]), + '/hot', + ) + + await router.load() + expect(router.state.matches[1]!).toMatchObject({ + routeId: hotRoute.id, + meta: [{ title: 'hot title' }], + headScripts: [{ src: '/hot-head.js' }], + scripts: [{ src: '/hot-body.js' }], + }) + + const container = document.createElement('div') + document.body.appendChild(container) + const reactRoot = createRoot(container) + const previousActEnvironment = (globalThis as any).IS_REACT_ACT_ENVIRONMENT + ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true + onTestFinished(() => { + if (previousActEnvironment === undefined) { + delete (globalThis as any).IS_REACT_ACT_ENVIRONMENT + } else { + ;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = previousActEnvironment + } + }) + onTestFinished(() => { + container.remove() + }) + onTestFinished(async () => { + await act(async () => { + reactRoot.unmount() + }) + }) + + await act(async () => { + reactRoot.render( + createElement(RouterContextProvider, { + router, + children: createElement( + Fragment, + null, + createPortal(createElement(HeadContent), document.head), + createElement(Scripts), + ), + }), + ) + }) + + expect(document.head.querySelector('title')?.textContent).toBe('hot title') + expect( + document.head.querySelector('script[src="/hot-head.js"]'), + ).not.toBeNull() + expect( + document.head.querySelector('script[src="/hot-body.js"]'), + ).not.toBeNull() + + await act(async () => { + runHandleRouteUpdate(router, hotRoute.id, new BaseRoute({} as any)) + await vi.waitFor(() => { + expect(router.state.matches[1]!.routeId).toBe(hotRoute.id) + expect(router.state.matches[1]!.meta).toBeUndefined() + expect(router.state.matches[1]!.headScripts).toBeUndefined() + expect(router.state.matches[1]!.scripts).toBeUndefined() + }) + }) + + expect(document.head.querySelector('title')).toBeNull() + expect(document.head.querySelector('script[src="/hot-head.js"]')).toBeNull() + expect(document.head.querySelector('script[src="/hot-body.js"]')).toBeNull() + }) }) diff --git a/packages/router-plugin/tests/router-plugin-context.test.ts b/packages/router-plugin/tests/router-plugin-context.test.ts index 40664c50a8..ecb6e7e167 100644 --- a/packages/router-plugin/tests/router-plugin-context.test.ts +++ b/packages/router-plugin/tests/router-plugin-context.test.ts @@ -1,8 +1,9 @@ import path from 'node:path' import * as t from '@babel/types' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { parseAst } from '@tanstack/router-utils' import { createRouterCodeSplitterPlugin } from '../src/core/router-code-splitter-plugin' +import { unpluginRouterComposedFactory } from '../src/core/router-composed-plugin' import { createRouterHmrPlugin } from '../src/core/router-hmr-plugin' import { createRouterPluginContext } from '../src/core/router-plugin-context' import { normalizePath } from '../src/core/utils' @@ -11,19 +12,30 @@ import type { UnpluginOptions, TransformResult } from 'unplugin' const referencePluginName = 'tanstack-router:code-splitter:compile-reference-file' +const virtualPluginName = 'tanstack-router:code-splitter:compile-virtual-file' -function getReferencePlugin( +function getCodeSplitterPlugin( plugins: ReturnType, + pluginName: string, ): UnpluginOptions { const pluginArray = Array.isArray(plugins) ? plugins : [plugins] - const plugin = pluginArray.find((item) => item.name === referencePluginName) + const plugin = pluginArray.find((item) => item.name === pluginName) if (!plugin) { - throw new Error('Reference code-splitter plugin not found') + throw new Error(`Code-splitter plugin "${pluginName}" not found`) } return plugin } -async function configurePlugin(plugin: UnpluginOptions) { +function getReferencePlugin( + plugins: ReturnType, +) { + return getCodeSplitterPlugin(plugins, referencePluginName) +} + +async function configurePlugin( + plugin: UnpluginOptions, + command: 'serve' | 'build' = 'serve', +) { const hook = plugin.vite?.configResolved if (!hook) { return @@ -31,6 +43,7 @@ async function configurePlugin(plugin: UnpluginOptions) { const config = { root: process.cwd(), + command, plugins: [{ name: referencePluginName }], } as never @@ -77,6 +90,87 @@ function countProgramHotDeclarations(code: string) { } describe('router plugin context', () => { + it.each([ + { mode: 'production', production: true }, + { mode: 'development with addHmr disabled', production: false }, + ])( + 'does not run React HMR compiler plugins in $mode', + async ({ production }) => { + const routeFile = normalizePath( + path.join(process.cwd(), 'src/routes/lowercase.tsx'), + ) + const routeCode = ` +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/lowercase')({ component }) + +function component() { + return
Hello
+} +` + + const context = createRouterPluginContext() + context.routesByFile.set(routeFile, { routeId: '/lowercase' }) + + if (production) { + vi.stubEnv('NODE_ENV', 'production') + } + + try { + const plugins = createRouterCodeSplitterPlugin( + { + target: 'react', + autoCodeSplitting: true, + codeSplittingOptions: production ? undefined : { addHmr: false }, + }, + context, + ) + const referencePlugin = getReferencePlugin(plugins) + const virtualPlugin = getCodeSplitterPlugin(plugins, virtualPluginName) + + await configurePlugin(referencePlugin, production ? 'build' : 'serve') + + const referenceCode = getCode( + await transformReferenceRoute(referencePlugin, routeCode, routeFile), + ) + const virtualCode = getCode( + await transformReferenceRoute( + virtualPlugin, + routeCode, + `${routeFile}?tsr-split=component`, + ), + ) + + expect(referenceCode).not.toContain('TSRFastRefreshAnchor') + expect(virtualCode).toContain('function component()') + expect(virtualCode).toContain('export { component }') + expect(virtualCode).not.toContain('SplitComponent') + } finally { + if (production) { + vi.unstubAllEnvs() + } + } + }, + ) + + it('does not install the standalone route HMR plugin in production', () => { + vi.stubEnv('NODE_ENV', 'production') + + try { + const plugins = unpluginRouterComposedFactory( + { target: 'react', autoCodeSplitting: false }, + { framework: 'vite' }, + ) + + const pluginArray = Array.isArray(plugins) ? plugins : [plugins] + expect( + pluginArray.some((plugin) => plugin.name === 'tanstack-router:hmr'), + ).toBe(false) + } finally { + vi.unstubAllEnvs() + } + }) + it('keeps multiple code-splitter instances isolated by explicit context', async () => { const routeFile = normalizePath( path.join(process.cwd(), 'src/routes-a/owned.tsx'), diff --git a/packages/router-ssr-query-core/package.json b/packages/router-ssr-query-core/package.json index bb0ee71c70..c4dffe0473 100644 --- a/packages/router-ssr-query-core/package.json +++ b/packages/router-ssr-query-core/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "vitest", "test:unit:dev": "pnpm run test:unit --watch", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", diff --git a/packages/router-utils/package.json b/packages/router-utils/package.json index 1c5d976594..bcccf41cca 100644 --- a/packages/router-utils/package.json +++ b/packages/router-utils/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "exit 0; vitest --typecheck", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" diff --git a/packages/router-vite-plugin/CHANGELOG.md b/packages/router-vite-plugin/CHANGELOG.md index dbd469a8d2..a8abbec3f2 100644 --- a/packages/router-vite-plugin/CHANGELOG.md +++ b/packages/router-vite-plugin/CHANGELOG.md @@ -1,5 +1,40 @@ # @tanstack/router-vite-plugin +## 1.167.24 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/router-plugin@1.168.24 + +## 1.167.23 + +### Patch Changes + +- Updated dependencies [[`49f6863`](https://github.com/TanStack/router/commit/49f686324574ea65851317cbba084eb386a911c6)]: + - @tanstack/router-plugin@1.168.23 + +## 1.167.22 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/router-plugin@1.168.22 + +## 1.167.21 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/router-plugin@1.168.21 + +## 1.167.20 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/router-plugin@1.168.20 + ## 1.167.19 ### Patch Changes diff --git a/packages/router-vite-plugin/package.json b/packages/router-vite-plugin/package.json index aed06c1a13..f80dd4d53e 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.167.19", + "version": "1.167.24", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -29,12 +29,12 @@ "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:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build" }, diff --git a/packages/solid-router-devtools/CHANGELOG.md b/packages/solid-router-devtools/CHANGELOG.md index 7d5c1e7e5c..e3a7bfdaf3 100644 --- a/packages/solid-router-devtools/CHANGELOG.md +++ b/packages/solid-router-devtools/CHANGELOG.md @@ -231,6 +231,15 @@ - Updated dependencies [[`a0191af`](https://github.com/TanStack/router/commit/a0191afd21afe0e7571af8b0faab171f62e71db7)]: - @tanstack/solid-router@2.0.0-alpha.0 +## 1.167.1 + +### Patch Changes + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + - @tanstack/solid-router@1.170.19 + - @tanstack/router-devtools-core@1.168.1 + ## 1.167.0 ### Minor Changes diff --git a/packages/solid-router-devtools/README.md b/packages/solid-router-devtools/README.md index 2011760db8..cf8c44252d 100644 --- a/packages/solid-router-devtools/README.md +++ b/packages/solid-router-devtools/README.md @@ -1,5 +1,22 @@ +
+ + + + TanStack Solid Router + +
# TanStack Solid Router Devtools See https://tanstack.com/router/latest/docs/framework/solid/devtools diff --git a/packages/solid-router-devtools/package.json b/packages/solid-router-devtools/package.json index 2dd48ae40a..3c117d48be 100644 --- a/packages/solid-router-devtools/package.json +++ b/packages/solid-router-devtools/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", "build": "vite build && tsc -p tsconfig.build.json" }, diff --git a/packages/solid-router-ssr-query/README.md b/packages/solid-router-ssr-query/README.md index f7c1691123..2aadc50710 100644 --- a/packages/solid-router-ssr-query/README.md +++ b/packages/solid-router-ssr-query/README.md @@ -1,4 +1,18 @@ -![TanStack Router Header](https://raw.githubusercontent.com/TanStack/router/main/media/header_router.png) + + + + TanStack Solid Router + # @tanstack/solid-router-ssr-query diff --git a/packages/solid-router-ssr-query/package.json b/packages/solid-router-ssr-query/package.json index 057138b66d..6b26fe202c 100644 --- a/packages/solid-router-ssr-query/package.json +++ b/packages/solid-router-ssr-query/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint ./src", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js", - "test:types:ts60": "tsc", + "test:types:ts60": "tsc6", + "test:types:ts70": "tsc", "test:unit": "exit 0; vitest", "test:unit:dev": "pnpm run test:unit --watch", "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", diff --git a/packages/solid-router/CHANGELOG.md b/packages/solid-router/CHANGELOG.md index dce9459105..9ddbd465ea 100644 --- a/packages/solid-router/CHANGELOG.md +++ b/packages/solid-router/CHANGELOG.md @@ -208,6 +208,59 @@ - solid v2 pre-release for solid-router and start ([#6904](https://github.com/TanStack/router/pull/6904)) +## 1.170.19 + +### Patch Changes + +- [#7805](https://github.com/TanStack/router/pull/7805) [`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd) - Rewrite match loading around a lane-based scheduler that tracks each navigation, preload, and background reload as an ordered unit of work. This fixes pending/redirect/retry state leaking between overlapping navigations, restores correct SSR status codes for redirects, errors, and not-found responses, and closes hydration gaps where the client re-ran work the server had already completed. + - Invalidation now retires matching active preloads so older speculative loader results cannot become fresh cache data after invalidation. + - Route `headers()` now only runs on the server, matching the documented behavior — it is no longer invoked during client-side asset projection. + - The documented default `gcTime` and `preloadGcTime` now match the existing runtime default of 5 minutes (`300_000`). + + **Removed / changed exported internals** + - `RouterState` no longer includes `loadedAt`, `isTransitioning`, `statusCode`, or `redirect`. Use `match.updatedAt` in place of `loadedAt`; subscribe to `router.state.status` / `router.state.isLoading` in place of `isTransitioning`; server response status and redirect handling are now internal to the server loader and are no longer exposed on `router.state`. + - `RouteMatch.fetchCount` has been removed, with no replacement — it was purely informational. + - `RouteMatch.status` no longer includes `'redirected'` (it remains `'pending' | 'success' | 'error' | 'notFound'`) — redirected matches are dropped from the match list instead of being rendered. + - `RouteMatch.globalNotFound` has been renamed and privatized to the internal `_notFound` field. Use `match.status === 'notFound'` instead. + - The exported React, Solid, and Vue `Match` components now accept `routeId` instead of `matchId`. + - The exported `RouterStores` adapter contract now uses route-keyed presentation stores: `matchesId` is replaced by `ids`, `matchStores` by `byRoute`, and `getRouteMatchStore()` by `getMatchStore()`. The separate `loadedAt`, `isLoading`, `isTransitioning`, `statusCode`, and `redirect` stores have been removed, along with the pending/cache stores and their setters. `StoreConfig.init` has also been removed. Read application-facing state from `router.state`; preload and cache coordination are now internal. + - Removed `RouterCore` members `getMatch()`, `updateMatch()`, `cancelMatch()`, and `cancelMatches()` — read matches from `router.state.matches` (e.g. `router.state.matches.find((m) => m.id === id)`); there is no replacement for mutating or cancelling an individual in-flight match from outside the router. + - Removed `RouterCore.hasNotFoundMatch()` — use `router.state.matches.some((m) => m.status === 'notFound')`. + - Removed `RouterCore.looseRoutesById` — use `routesById`. + - Removed `RouterCore.isPrerendering()`, `RouterCore.isViewTransitionTypesSupported`, and `RouterCore.viewTransitionPromise`, with no replacement. + - Removed `RouterCore.getParsedLocationHref()` and `RouterCore.clearExpiredCache()`, with no replacement — expired cache entries are now reconciled automatically as part of match commit. + - Removed `RouterCore.latestLoadPromise` and `RouterCore.beforeLoad()`, with no replacement. + - `RouterCore.commitLocationPromise` and `RouterCore.pendingBuiltLocation` have been replaced by the internal `_commitPromise` and `_pendingLocation` fields. + - Removed the exported `GetMatchFn` and `UpdateMatchFn` types, along with the methods they typed. + - Removed the standalone `getMatchedRoutes()` export from `@tanstack/router-core` — use the `router.getMatchedRoutes()` instance method instead. + - `RouterCore.loadRouteChunk()` no longer accepts an array of component types as its second argument. One-argument usage is unchanged; the optional second argument is now `'errorComponent'`, `'notFoundComponent'`, or `false` for internal boundary loading. + - Removed `Redirect.redirectHandled`, which was internal redirect bookkeeping. + - `MatchRoutesOpts.preload` and `MatchRoutesOpts.dest` have been removed. + - `StartTransitionFn` is now `(fn, expected) => Promise` (previously `(fn) => void`). This only affects custom framework adapters that implement `startTransition`. + +- Updated dependencies [[`45c4ad8`](https://github.com/TanStack/router/commit/45c4ad8d629e291fab70c37900525449e415ffcd)]: + - @tanstack/router-core@1.171.16 + +## 1.170.18 + +### Patch Changes + +- Updated dependencies [[`e2dd204`](https://github.com/TanStack/router/commit/e2dd2049cb42eb219d3b447b8605066d19d9c1fa)]: + - @tanstack/router-core@1.171.15 + +## 1.170.17 + +### Patch Changes + +- Updated dependencies [[`9809a06`](https://github.com/TanStack/router/commit/9809a0619d4ed3fe8c2a393af5b9eca4b6c7695b)]: + - @tanstack/router-core@1.171.14 + +## 1.170.16 + +### Patch Changes + +- [#7584](https://github.com/TanStack/router/pull/7584) [`41e7a24`](https://github.com/TanStack/router/commit/41e7a24f693b0f58c2bef89a2b8c4d084acbd531) - Remove unnecessary setTimeout from Match components + ## 1.170.15 ### Patch Changes diff --git a/packages/solid-router/README.md b/packages/solid-router/README.md index 94a0f37468..3e41f35f1b 100644 --- a/packages/solid-router/README.md +++ b/packages/solid-router/README.md @@ -2,7 +2,21 @@ # TanStack Solid Router -![TanStack Router Header](https://raw.githubusercontent.com/TanStack/router/main/media/header_router.png) + + + + TanStack Solid Router + 🤖 Type-safe router w/ built-in caching & URL state management for Solid! diff --git a/packages/solid-router/package.json b/packages/solid-router/package.json index fb66415b69..e24c848bd8 100644 --- a/packages/solid-router/package.json +++ b/packages/solid-router/package.json @@ -27,12 +27,12 @@ "clean": "rimraf ./dist && rimraf ./coverage", "test:eslint": "eslint", "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", - "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js -p tsconfig.legacy.json", "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js -p tsconfig.legacy.json", - "test:types:ts60": "tsc -p tsconfig.legacy.json", + "test:types:ts60": "tsc6 -p tsconfig.legacy.json", + "test:types:ts70": "tsc -p tsconfig.legacy.json", "test:unit": "vitest && vitest --mode server", "test:unit:dev": "pnpm run test:unit --watch --hideSkippedTests", "test:perf": "vitest bench", diff --git a/packages/solid-router/skills/solid-router/SKILL.md b/packages/solid-router/skills/solid-router/SKILL.md index 75c82805df..0cfd22ad73 100644 --- a/packages/solid-router/skills/solid-router/SKILL.md +++ b/packages/solid-router/skills/solid-router/SKILL.md @@ -8,10 +8,11 @@ description: >- Outlet, CatchBoundary, ErrorComponent. Solid-specific patterns with Accessor returns, createSignal/createMemo/createEffect, Show/Switch/Match/Dynamic, and @solidjs/meta for head management. -type: framework -library: tanstack-router -library_version: '1.166.2' -framework: solid +metadata: + type: framework + library: tanstack-router + library_version: '1.166.2' + framework: solid requires: - router-core sources: @@ -22,8 +23,6 @@ sources: This skill builds on router-core. Read [router-core](../../../router-core/skills/router-core/SKILL.md) first for foundational concepts. -This skill covers the Solid-specific bindings, components, hooks, and setup for TanStack Router. - > **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values. > **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server. > **CRITICAL**: Most hooks return `Accessor` — you MUST call the accessor (`value()`) to read the reactive value. This is the #1 difference from the React version. diff --git a/packages/solid-router/src/CatchBoundary.tsx b/packages/solid-router/src/CatchBoundary.tsx index 11b51c67c9..2f79ec5162 100644 --- a/packages/solid-router/src/CatchBoundary.tsx +++ b/packages/solid-router/src/CatchBoundary.tsx @@ -5,24 +5,47 @@ import type { JSX } from '@solidjs/web' export function CatchBoundary( props: { - getResetKey: () => number | string - children: JSX.Element + getResetKey: () => unknown + children?: JSX.Element + render?: () => JSX.Element errorComponent?: ErrorRouteComponent onCatch?: (error: Error) => void } & Solid.ParentProps, ) { + const [retryKey, setRetryKey] = Solid.createSignal({}) + let resetBoundary: (() => void) | undefined + let initialized = false + + Solid.onCleanup(() => { + resetBoundary = undefined + }) + + Solid.createEffect(props.getResetKey, () => { + if (!initialized) { + initialized = true + return + } + + const reset = resetBoundary + if (reset) { + queueMicrotask(() => { + if (resetBoundary !== reset) { + return + } + setRetryKey({}) + reset() + Solid.flush() + }) + } + }) + return ( { const resolvedError = Solid.untrack(() => error() as Error) props.onCatch?.(resolvedError) - - Solid.createEffect(props.getResetKey, () => { - // We trigger reset here. For a fully deferred effect we might need usePrevious, - // but calling reset on key change is the main goal. - reset() - }) + resetBoundary = reset return ( - {props.children} + + {(_retryKey) => props.render?.() ?? props.children} + ) } @@ -46,7 +71,7 @@ export function ErrorComponent({ error }: { error: any }) { return (
- ∂Something went wrong! + Something went wrong! + ) + } + + const rootRoute = createRootRoute() + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + component: Page, + errorComponent: RouteError, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render(() => ) + + fireEvent.click(await screen.findByRole('button', { name: 'Retry' })) + + expect(await screen.findByText('Page content')).toBeInTheDocument() +}) diff --git a/packages/solid-router/tests/createLazyRoute.test.tsx b/packages/solid-router/tests/createLazyRoute.test.tsx index ddee828467..4dcbbbfc2c 100644 --- a/packages/solid-router/tests/createLazyRoute.test.tsx +++ b/packages/solid-router/tests/createLazyRoute.test.tsx @@ -1,9 +1,12 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest' import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library' import { Link, + Outlet, RouterProvider, createBrowserHistory, + createControlledPromise, + createLazyRoute, createMemoryHistory, createRootRoute, createRoute, @@ -93,3 +96,135 @@ describe('preload: matched routes', { timeout: 20000 }, () => { expect(lazyRoute.options.component).toBeDefined() }) }) + +// https://github.com/TanStack/router/issues/4467 +it('replaces default pending UI when delayed lazy options provide pending UI', async () => { + const loader = createControlledPromise() + const componentPreload = createControlledPromise() + const pendingComponentPreload = createControlledPromise() + const preloadComponent = vi.fn(() => componentPreload) + const preloadPendingComponent = vi.fn(() => pendingComponentPreload) + const Page = Object.assign(() =>

Page

, { + preload: preloadComponent, + }) + const Pending = Object.assign(() =>

Loading lazy page

, { + preload: preloadPendingComponent, + }) + const lazyPageOptions = createLazyRoute('/page')({ + pendingComponent: Pending, + component: Page, + }) + const lazyOptions = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>

Index page

, + }) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: () => loader, + }).lazy(() => lazyOptions) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, pageRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPendingMs: 0, + defaultPendingMinMs: 0, + defaultPendingComponent: () =>

Loading default

, + }) + render(() => ) + expect(await screen.findByText('Index page')).toBeInTheDocument() + + const navigation = router.navigate({ to: '/page' }) + onTestFinished(async () => { + lazyOptions.resolve(lazyPageOptions) + pendingComponentPreload.resolve() + componentPreload.resolve() + loader.resolve() + await Promise.allSettled([navigation]) + }) + + expect(await screen.findByRole('status')).toHaveTextContent('Loading default') + + lazyOptions.resolve(lazyPageOptions) + await vi.waitFor(() => { + expect(preloadComponent).toHaveBeenCalledOnce() + expect(preloadPendingComponent).toHaveBeenCalledOnce() + }) + expect(screen.getByRole('status')).toHaveTextContent('Loading default') + expect(pendingComponentPreload.status).toBe('pending') + + pendingComponentPreload.resolve() + await vi.waitFor(() => + expect(screen.getByRole('status')).toHaveTextContent('Loading lazy page'), + ) + expect(componentPreload.status).toBe('pending') + expect(screen.queryByText('Page')).not.toBeInTheDocument() + + componentPreload.resolve() + loader.resolve() + await navigation + + expect(await screen.findByText('Page')).toBeInTheDocument() +}) + +it('renders an eager loader error with a delayed lazy errorComponent', async () => { + const loader = createControlledPromise() + const loaderErrorHandled = createControlledPromise() + const loaderError = new Error('loader failed') + const lazyPageOptions = createLazyRoute('/page')({ + component: () =>

Page

, + errorComponent: ({ error }) => ( +

Lazy error: {error.message}

+ ), + }) + const lazyOptions = createControlledPromise() + const rootRoute = createRootRoute({ component: Outlet }) + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>

Index page

, + }) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + loader: async () => { + await loader + throw loaderError + }, + onError: () => loaderErrorHandled.resolve(), + }).lazy(() => lazyOptions) + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, pageRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + defaultPendingMs: 0, + defaultPendingMinMs: 0, + defaultPendingComponent: () =>

Loading default

, + }) + let navigation: Promise | undefined + + try { + render(() => ) + expect(await screen.findByText('Index page')).toBeInTheDocument() + + navigation = router.navigate({ to: '/page' }) + expect(await screen.findByText('Loading default')).toBeInTheDocument() + + loader.resolve() + await loaderErrorHandled + lazyOptions.resolve(lazyPageOptions) + await navigation + + expect(await screen.findByRole('alert')).toHaveTextContent( + 'Lazy error: loader failed', + ) + } finally { + lazyOptions.resolve(lazyPageOptions) + loader.resolve() + loaderErrorHandled.resolve() + if (navigation) { + await Promise.allSettled([navigation]) + } + } +}) diff --git a/packages/solid-router/tests/errorComponent.test.tsx b/packages/solid-router/tests/errorComponent.test.tsx index 4ee1abf3f1..af45fdccee 100644 --- a/packages/solid-router/tests/errorComponent.test.tsx +++ b/packages/solid-router/tests/errorComponent.test.tsx @@ -1,8 +1,10 @@ -import { afterEach, describe, expect, test, vi } from 'vitest' +import { afterEach, describe, expect, onTestFinished, test, vi } from 'vitest' import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library' +import { createControlledPromise } from '@tanstack/router-core' import { Link, + Outlet, RouterProvider, createLazyRoute, createRootRoute, @@ -160,3 +162,101 @@ describe.each([true, false])( ) }, ) + +test('global catch boundary resets when a background child generation recovers', async () => { + const refresh = createControlledPromise() + let loaderCalls = 0 + const rootRoute = createRootRoute({ component: Outlet }) + const childRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: { + staleReloadMode: 'background', + handler: () => (++loaderCalls === 1 ? 1 : refresh), + }, + component: () => { + const revision = childRoute.useLoaderData() + if (revision() === 1) { + throw new Error('stale child render failed') + } + return
Recovered child revision {revision()}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([childRoute]), + }) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) + + render(() => ) + expect( + await screen.findByText('stale child render failed'), + ).toBeInTheDocument() + + const invalidation = router.invalidate() + await vi.waitFor(() => expect(loaderCalls).toBe(2)) + expect(screen.getByText('stale child render failed')).toBeInTheDocument() + expect(screen.queryByText(/Recovered child revision/)).not.toBeInTheDocument() + refresh.resolve(2) + await invalidation + + expect( + await screen.findByText('Recovered child revision 2'), + ).toBeInTheDocument() + expect( + screen.queryByText('stale child render failed'), + ).not.toBeInTheDocument() +}) + +test('ancestor route errorComponent resets when a background child generation recovers', async () => { + const refresh = createControlledPromise() + let loaderCalls = 0 + const rootRoute = createRootRoute({ + component: Outlet, + errorComponent: ({ error }) =>
Ancestor error: {error.message}
, + }) + const childRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: { + staleReloadMode: 'background', + handler: () => (++loaderCalls === 1 ? 1 : refresh), + }, + component: () => { + const revision = childRoute.useLoaderData() + if (revision() === 1) { + throw new Error('stale child render failed') + } + return
Recovered child revision {revision()}
+ }, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([childRoute]), + }) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) + + render(() => ) + expect( + await screen.findByText('Ancestor error: stale child render failed'), + ).toBeInTheDocument() + + const invalidation = router.invalidate({ + filter: (match) => match.routeId === childRoute.id, + }) + onTestFinished(async () => { + refresh.resolve(2) + await Promise.allSettled([invalidation]) + }) + + await vi.waitFor(() => expect(loaderCalls).toBe(2)) + expect( + screen.getByText('Ancestor error: stale child render failed'), + ).toBeInTheDocument() + refresh.resolve(2) + await invalidation + + expect( + await screen.findByText('Recovered child revision 2'), + ).toBeInTheDocument() +}) diff --git a/packages/solid-router/tests/link.test.tsx b/packages/solid-router/tests/link.test.tsx index 42e3a07284..67596e744b 100644 --- a/packages/solid-router/tests/link.test.tsx +++ b/packages/solid-router/tests/link.test.tsx @@ -577,6 +577,45 @@ describe('Link', () => { ).toHaveTextContent('false') }) + test('updates href when link options change reactively', async () => { + const [to, setTo] = Solid.createSignal<'/a' | '/b'>('/a') + const rootRoute = createRootRoute({ + component: () => ( + <> + + + Destination + + + ), + }) + const aRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/a', + }) + const bRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/b', + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([aRoute, bRoute]), + history, + }) + + render(() => ) + + const link = await screen.findByTestId('dynamic-link') + expect(link).toHaveAttribute('href', '/a') + + fireEvent.click( + screen.getByRole('button', { name: 'Change destination' }), + ) + + await waitFor(() => { + expect(link).toHaveAttribute('href', '/b') + }) + }) + test('updates exact and fuzzy active state before the next route renders', async () => { const postLoader = createControlledPromise() @@ -736,14 +775,14 @@ describe('Link', () => { fireEvent.click(screen.getByTestId('switch-post')) - // In Solid, Suspense (Loading) keeps old content visible during transitions - // instead of showing a fallback, so "Post 1" stays visible while loading. - // The current-post link uses params={true} which tracks current URL params. - // In Solid, the href updates after the loader resolves (no startTransition batching). + expect(await screen.findByText('Loading...')).toBeInTheDocument() + expect(screen.queryByText('Post 2')).not.toBeInTheDocument() + expect(router.state.location.pathname).toBe('/posts/2') + expect(currentPost).toHaveClass('active') + await waitFor(() => { - expect(router.state.location.pathname).toBe('/posts/2') + expect(currentPost).toHaveAttribute('href', '/posts/2') }) - expect(screen.getByText('Post 1')).toBeInTheDocument() postLoader.resolve() @@ -834,11 +873,15 @@ describe('Link', () => { fireEvent.click(screen.getByTestId('switch-search')) - // In Solid, Suspense keeps old content visible during transitions + expect(await screen.findByText('Loading...')).toBeInTheDocument() + expect(screen.queryByText('Posts 2')).not.toBeInTheDocument() + expect(router.state.location.search).toEqual({ page: 2 }) + expect(staticSearch).toHaveClass('inactive') + expect(currentSearch).toHaveClass('active') + await waitFor(() => { - expect(router.state.location.search).toEqual({ page: 2 }) + expect(currentSearch).toHaveAttribute('href', '/posts?page=2') }) - expect(screen.getByText('Posts 1')).toBeInTheDocument() postsLoader.resolve() @@ -1422,22 +1465,21 @@ describe('Link', () => { '/Dashboard/posts?page=2&filter=inactive', ) - await fireEvent.click(updateSearchLink) + fireEvent.click(updateSearchLink) - // Wait for navigation to complete and search params to update await waitFor(() => { + expect(window.location.pathname).toBe('/Dashboard/posts') expect(window.location.search).toBe('?page=2&filter=inactive') + expect(screen.getByTestId('current-page')).toHaveTextContent('Page: 2') + expect(screen.getByTestId('current-filter')).toHaveTextContent( + 'Filter: inactive', + ) }) - - await screen.findByTestId('current-page') - // Verify search was updated - expect(window.location.pathname).toBe('/Dashboard/posts') - expect(window.location.search).toBe('?page=2&filter=inactive') - expect(router.state.location.search).toEqual({ page: 2, filter: 'inactive', }) + await vi.waitFor(() => expect(router.state.status).toBe('idle')) }) test('when navigating to /posts with invalid search', async () => { diff --git a/packages/solid-router/tests/loaders.test.tsx b/packages/solid-router/tests/loaders.test.tsx index ad341a861c..33d4681cd0 100644 --- a/packages/solid-router/tests/loaders.test.tsx +++ b/packages/solid-router/tests/loaders.test.tsx @@ -11,16 +11,16 @@ import { createRootRoute, createRoute, createRouter, - useLoaderData, useRouter, } from '../src' import { sleep } from './utils' afterEach(() => { + cleanup() + vi.useRealTimers() vi.resetAllMocks() window.history.replaceState(null, 'root', '/') - cleanup() }) const WAIT_TIME = 100 @@ -321,31 +321,58 @@ test('throw error from beforeLoad when navigating to route', async () => { expect(indexElement).toBeInTheDocument() }) -test('throw abortError from loader upon initial load with basepath', async () => { +// https://github.com/TanStack/router/pull/7673 +test('#7673: a spontaneous loader AbortError renders the boundary without executing the route component', async () => { window.history.replaceState(null, 'root', '/app') const rootRoute = createRootRoute({}) + const abortError = new DOMException('Aborted', 'AbortError') + const routeComponentRendered = vi.fn() + const renderedError = vi.fn() + let routeSignal: AbortSignal | undefined const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - loader: async () => { - return Promise.reject(new DOMException('Aborted', 'AbortError')) + loader: async ({ abortController }): Promise<{ value: string }> => { + routeSignal = abortController.signal + return Promise.reject(abortError) + }, + component: () => { + routeComponentRendered() + const data = indexRoute.useLoaderData() + return
{data().value}
+ }, + errorComponent: ({ error }) => { + renderedError(error) + return
indexErrorComponent
}, - component: () =>
Index route content
, - errorComponent: () => ( -
indexErrorComponent
- ), }) const routeTree = rootRoute.addChildren([indexRoute]) const router = createRouter({ routeTree, basepath: '/app' }) + const rendered = new Promise((resolve) => { + const unsubscribe = router.subscribe('onRendered', () => { + unsubscribe() + resolve() + }) + }) render(() => ) - const indexElement = await screen.findByText('Index route content') - expect(indexElement).toBeInTheDocument() - expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() - expect(window.location.pathname.startsWith('/app')).toBe(true) + expect(await screen.findByTestId('index-error')).toBeInTheDocument() + await rendered + expect(screen.queryByTestId('index-content')).not.toBeInTheDocument() + expect(routeComponentRendered).not.toHaveBeenCalled() + expect(renderedError).toHaveBeenCalledWith(abortError) + expect(routeSignal?.aborted).toBe(false) + expect( + router.state.matches.find((match) => match.routeId === indexRoute.id), + ).toMatchObject({ + status: 'error', + error: abortError, + }) + expect(window.location.pathname).toBe('/app') + expect(router.state.status).toBe('idle') }) test('reproducer #4245', async () => { @@ -615,12 +642,13 @@ test('reproducer #4546', async () => { fireEvent.click(indexLink) { - // Wait for navigation to complete before checking values + // beforeLoad is fresh for this navigation, while the cached loader data is + // allowed to remain visible until its background reload completes. await screen.findByText('Index route') const routeContext = await screen.findByTestId('index-route-context') const loaderData = await screen.findByTestId('index-loader-data') expect(routeContext).toHaveTextContent('3') - expect(loaderData).toHaveTextContent('3') + await vi.waitFor(() => expect(loaderData).toHaveTextContent('3')) } await router.invalidate() @@ -632,16 +660,16 @@ test('reproducer #4546', async () => { fireEvent.click(idLink) { - // Wait for navigation to complete before checking values + // This loader generation can likewise begin with reusable cached data. await screen.findByText('$id route') const routeContext = await screen.findByTestId('id-route-context') const loaderData = await screen.findByTestId('id-loader-data') expect(routeContext).toHaveTextContent('5') - expect(loaderData).toHaveTextContent('5') + await vi.waitFor(() => expect(loaderData).toHaveTextContent('5')) } }) -test('clears pendingTimeout when match resolves', async () => { +test('does not show pending UI when loaders finish before their pending delays', async () => { const defaultPendingComponentOnMountMock = vi.fn() const nestedPendingComponentOnMountMock = vi.fn() const fooPendingComponentOnMountMock = vi.fn() @@ -709,13 +737,16 @@ test('clears pendingTimeout when match resolves', async () => { }) render(() => ) - await router.latestLoadPromise - const linkToFoo = await screen.findByTestId('link-to-foo') - fireEvent.click(linkToFoo) - const fooElement = await screen.findByText('Nested Foo page') + await screen.findByTestId('link-to-foo') + vi.useFakeTimers() + const navigation = router.navigate({ to: '/nested/foo' }) + await vi.advanceTimersByTimeAsync(WAIT_TIME * 5) + await navigation + const fooElement = screen.getByText('Nested Foo page') expect(fooElement).toBeInTheDocument() expect(router.state.location.href).toBe('/nested/foo') + expect(router.state.status).toBe('idle') // none of the pending components should have been called expect(defaultPendingComponentOnMountMock).not.toHaveBeenCalled() @@ -723,52 +754,7 @@ test('clears pendingTimeout when match resolves', async () => { expect(fooPendingComponentOnMountMock).not.toHaveBeenCalled() }) -test('useLoaderData retains previous data while route match is pending', async () => { - const history = createMemoryHistory({ initialEntries: ['/app'] }) - const rootRoute = createRootRoute({ - component: () => { - const loaderData = useLoaderData({ from: '/app' }) - - return ( - <> -
{`${loaderData().length}:0`}
- - - ) - }, - }) - const appRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/app', - loader: () => 'loaded', - component: () =>
App route
, - }) - const routeTree = rootRoute.addChildren([appRoute]) - const router = createRouter({ routeTree, history }) - - render(() => ) - - expect(await screen.findByTestId('combined')).toHaveTextContent('6:0') - - const appMatch = router.state.matches.find( - (match) => match.routeId === '/app', - ) - - expect(appMatch).toBeDefined() - - if (!appMatch) { - throw new Error('Expected /app match to be active') - } - - router.stores.setPending([{ ...appMatch, id: `${appMatch.id}__pending` }]) - router.stores.setMatches( - router.state.matches.filter((match) => match.routeId !== '/app'), - ) - - expect(screen.getByTestId('combined')).toHaveTextContent('6:0') -}) - -test('cancelMatches after pending timeout', async () => { +test('navigating away from a pending route aborts its loader', async () => { function getPendingComponent(onMount: () => void) { const PendingComponent = () => { onMount() @@ -778,6 +764,7 @@ test('cancelMatches after pending timeout', async () => { } const onAbortMock = vi.fn() const fooPendingComponentOnMountMock = vi.fn() + let fooSignal: AbortSignal | undefined const history = createMemoryHistory({ initialEntries: ['/'] }) const rootRoute = createRootRoute({ component: () => ( @@ -796,17 +783,18 @@ test('cancelMatches after pending timeout', async () => { const fooRoute = createRoute({ getParentRoute: () => rootRoute, path: '/foo', - pendingMs: WAIT_TIME * 20, + pendingMs: 0, loader: async ({ abortController }) => { + fooSignal = abortController.signal await new Promise((resolve) => { - const timer = setTimeout(() => { - resolve() - }, WAIT_TIME * 40) - abortController.signal.addEventListener('abort', () => { - onAbortMock() - clearTimeout(timer) - resolve() - }) + abortController.signal.addEventListener( + 'abort', + () => { + onAbortMock() + resolve() + }, + { once: true }, + ) }) }, pendingComponent: getPendingComponent(fooPendingComponentOnMountMock), @@ -820,17 +808,20 @@ test('cancelMatches after pending timeout', async () => { const routeTree = rootRoute.addChildren([fooRoute, barRoute]) const router = createRouter({ routeTree, history }) render(() => ) - await router.latestLoadPromise const fooLink = await screen.findByTestId('link-to-foo') fireEvent.click(fooLink) - await sleep(WAIT_TIME * 30) const pendingElement = await screen.findByText('Pending...') expect(pendingElement).toBeInTheDocument() - - const barLink = await screen.findByTestId('link-to-bar') - fireEvent.click(barLink) - const barElement = await screen.findByText('Bar page') + await vi.waitFor(() => expect(fooSignal?.aborted).toBe(false)) + await router.navigate({ to: '/bar' }) + const barElement = screen.getByText('Bar page') expect(barElement).toBeInTheDocument() + expect(fooPendingComponentOnMountMock).toHaveBeenCalled() - expect(onAbortMock).toHaveBeenCalled() + expect(onAbortMock).toHaveBeenCalledTimes(1) + expect(fooSignal?.aborted).toBe(true) + expect(screen.queryByText('Pending...')).not.toBeInTheDocument() + expect(screen.queryByText('Foo page')).not.toBeInTheDocument() + expect(router.state.location.href).toBe('/bar') + expect(router.state.status).toBe('idle') }) diff --git a/packages/solid-router/tests/pending-fallback-promise-replacement.test.tsx b/packages/solid-router/tests/pending-fallback-promise-replacement.test.tsx new file mode 100644 index 0000000000..1b1ac2268c --- /dev/null +++ b/packages/solid-router/tests/pending-fallback-promise-replacement.test.tsx @@ -0,0 +1,179 @@ +import { cleanup, render, screen } from '@solidjs/testing-library' +import { afterEach, expect, test, vi } from 'vitest' +import { createControlledPromise } from '@tanstack/router-core' +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' +import type { AnyRouter } from '../src' + +const testCleanups: Array<() => void | Promise> = [] + +afterEach(async () => { + const pendingCleanups = testCleanups + .splice(0) + .reverse() + .map((testCleanup) => testCleanup()) + if (vi.isFakeTimers()) { + await vi.runAllTimersAsync() + } + await Promise.allSettled(pendingCleanups) + cleanup() + vi.useRealTimers() +}) + +test.each(['child', 'root'] as const)( + 'a mounted %s pending fallback follows an overlapping load generation', + async (routeLevel) => { + const firstReload = createControlledPromise() + const secondReload = createControlledPromise() + const reloads = [firstReload, secondReload] + let loaderCall = 0 + + const routeOptions = { + pendingMs: 0, + pendingMinMs: 100, + pendingComponent: () =>
Pending
, + loader: () => { + const generation = ++loaderCall + const gate = reloads[generation - 2] + return gate ? gate.then(() => generation) : generation + }, + } + + const makeRouter = (): AnyRouter => { + if (routeLevel === 'root') { + const rootRoute = createRootRoute({ + ...routeOptions, + component: () =>
Generation {rootRoute.useLoaderData()()}
, + }) + return createRouter({ + routeTree: rootRoute, + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + } + + const rootRoute = createRootRoute({ component: () => }) + const pageRoute = createRoute({ + ...routeOptions, + getParentRoute: () => rootRoute, + path: '/page', + component: () =>
Generation {pageRoute.useLoaderData()()}
, + }) + return createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + } + const router = makeRouter() + + render(() => ) + expect(await screen.findByText('Generation 1')).toBeInTheDocument() + + vi.useFakeTimers() + const firstInvalidation = router.invalidate({ forcePending: true }) + const invalidations = [firstInvalidation] + testCleanups.push(async () => { + firstReload.resolve() + secondReload.resolve() + await Promise.allSettled(invalidations) + }) + await vi.advanceTimersByTimeAsync(0) + expect(loaderCall).toBe(2) + expect(screen.getByTestId('pending')).toBeInTheDocument() + expect(screen.queryByText('Generation 1')).not.toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(25) + const secondInvalidation = router.invalidate({ forcePending: true }) + invalidations.push(secondInvalidation) + await vi.advanceTimersByTimeAsync(0) + expect(loaderCall).toBe(3) + + firstReload.resolve() + await vi.advanceTimersByTimeAsync(0) + + expect(screen.getByTestId('pending')).toBeInTheDocument() + expect(screen.queryByText('Generation 2')).not.toBeInTheDocument() + + let secondSettled = false + void secondInvalidation.then(() => { + secondSettled = true + }) + secondReload.resolve() + await vi.advanceTimersByTimeAsync(0) + + expect(secondSettled).toBe(false) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(74) + expect(secondSettled).toBe(false) + expect(screen.getByTestId('pending')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(1) + await Promise.all(invalidations) + expect(screen.getByText('Generation 3')).toBeInTheDocument() + expect(screen.queryByText('Generation 1')).not.toBeInTheDocument() + expect(screen.queryByText('Generation 2')).not.toBeInTheDocument() + expect(screen.queryByTestId('pending')).not.toBeInTheDocument() + expect(router.state.status).toBe('idle') + }, +) + +test('forcePending honors pendingMinMs when the reload settles before pendingMs', async () => { + const reload = createControlledPromise() + let loaderCall = 0 + + const rootRoute = createRootRoute({ component: () => }) + const pageRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/page', + pendingMinMs: 100, + pendingComponent: () =>
Pending
, + loader: async () => { + if (++loaderCall > 1) { + await reload + } + return loaderCall + }, + component: () =>
Generation {pageRoute.useLoaderData()()}
, + }) + const router = createRouter({ + routeTree: rootRoute.addChildren([pageRoute]), + history: createMemoryHistory({ initialEntries: ['/page'] }), + }) + + render(() => ) + expect(await screen.findByText('Generation 1')).toBeInTheDocument() + + vi.useFakeTimers() + const invalidation = router.invalidate({ forcePending: true }) + testCleanups.push(async () => { + reload.resolve() + await Promise.allSettled([invalidation]) + }) + await vi.advanceTimersByTimeAsync(0) + expect(screen.getByTestId('fast-pending')).toBeInTheDocument() + + let settled = false + void invalidation.then(() => { + settled = true + }) + reload.resolve() + await vi.advanceTimersByTimeAsync(0) + + await vi.advanceTimersByTimeAsync(99) + expect(settled).toBe(false) + expect(screen.getByTestId('fast-pending')).toBeInTheDocument() + expect(screen.queryByText('Generation 2')).not.toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(1) + await invalidation + expect(screen.getByText('Generation 2')).toBeInTheDocument() + expect(screen.queryByText('Generation 1')).not.toBeInTheDocument() + expect(screen.queryByTestId('fast-pending')).not.toBeInTheDocument() + expect(router.state.status).toBe('idle') +}) diff --git a/packages/solid-router/tests/redirect.test.tsx b/packages/solid-router/tests/redirect.test.tsx index 81bd1f6bc6..2192f4f962 100644 --- a/packages/solid-router/tests/redirect.test.tsx +++ b/packages/solid-router/tests/redirect.test.tsx @@ -1,13 +1,13 @@ import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library' import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { createControlledPromise } from '@tanstack/router-core' import { Link, Outlet, RouterProvider, createBrowserHistory, - createMemoryHistory, createRootRoute, createRoute, createRouter, @@ -107,6 +107,7 @@ describe('redirect', () => { test('when root `beforeLoad` redirects while root pendingComponent is showing and the target route is lazy', async () => { let hasRedirected = false + const beforeLoad = createControlledPromise() const consoleError = vi .spyOn(console, 'error') .mockImplementation(() => {}) @@ -116,7 +117,7 @@ describe('redirect', () => { pendingMs: 0, pendingComponent: () =>
loading
, beforeLoad: async () => { - await sleep(WAIT_TIME) + await beforeLoad if (!hasRedirected) { hasRedirected = true throw redirect({ to: '/posts' }) @@ -142,12 +143,20 @@ describe('redirect', () => { render(() => ) + try { + expect(await screen.findByTestId('pending')).toBeInTheDocument() + expect(screen.queryByTestId('index-page')).not.toBeInTheDocument() + } finally { + beforeLoad.resolve() + } + // The lazy target route adds the async boundary that exposes the stale // redirected-match render path this regression is guarding. expect(await screen.findByTestId('lazy-route-page')).toBeInTheDocument() expect(screen.queryByTestId('pending')).not.toBeInTheDocument() + expect(screen.queryByTestId('index-page')).not.toBeInTheDocument() expect(router.state.location.href).toBe('/posts') - expect(router.state.status).toBe('idle') + await vi.waitFor(() => expect(router.state.status).toBe('idle')) expect(consoleError).not.toHaveBeenCalled() }) @@ -303,116 +312,4 @@ describe('redirect', () => { expect(window.location.pathname).toBe('/final') }) }) - - describe('SSR', () => { - test('when `redirect` is thrown in `beforeLoad`', async () => { - const rootRoute = createRootRoute() - - const indexRoute = createRoute({ - path: '/', - getParentRoute: () => rootRoute, - beforeLoad: () => { - throw redirect({ - to: '/about', - }) - }, - }) - - const aboutRoute = createRoute({ - path: '/about', - getParentRoute: () => rootRoute, - component: () => { - return 'About' - }, - }) - - const router = createRouter({ - routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), - // Mock server mode - isServer: true, - history: createMemoryHistory({ - initialEntries: ['/'], - }), - }) - - await router.load() - - expect(router.state.redirect).toBeDefined() - expect(router.state.redirect).toBeInstanceOf(Response) - const redirectResponse = router.state.redirect! - - expect(redirectResponse.options).toEqual({ - _fromLocation: expect.objectContaining({ - hash: '', - href: '/', - pathname: '/', - search: {}, - searchStr: '', - }), - to: '/about', - href: '/about', - statusCode: 307, - }) - }) - }) - - test('when `redirect` is thrown in `loader`', async () => { - const rootRoute = createRootRoute() - - const indexRoute = createRoute({ - path: '/', - getParentRoute: () => rootRoute, - loader: () => { - throw redirect({ - to: '/about', - }) - }, - }) - - const aboutRoute = createRoute({ - path: '/about', - getParentRoute: () => rootRoute, - component: () => { - return 'About' - }, - }) - - const router = createRouter({ - history: createMemoryHistory({ - initialEntries: ['/'], - }), - routeTree: rootRoute.addChildren([indexRoute, aboutRoute]), - // Mock server mode - isServer: true, - }) - - await router.load() - - const currentRedirect = router.state.redirect - - expect(currentRedirect).toBeDefined() - expect(currentRedirect).toBeInstanceOf(Response) - const redirectResponse = currentRedirect! - expect(redirectResponse.status).toEqual(307) - expect(redirectResponse.headers.get('Location')).toEqual('/about') - expect(redirectResponse.options).toEqual({ - _fromLocation: { - external: false, - publicHref: '/', - hash: '', - href: '/', - pathname: '/', - search: {}, - searchStr: '', - state: { - __TSR_index: 0, - __TSR_key: redirectResponse.options._fromLocation!.state.__TSR_key, - key: redirectResponse.options._fromLocation!.state.key, - }, - }, - href: '/about', - to: '/about', - statusCode: 307, - }) - }) }) diff --git a/packages/solid-router/tests/renderRouterToStream.test.tsx b/packages/solid-router/tests/renderRouterToStream.test.tsx index ba5ee51ca0..6351d83ebd 100644 --- a/packages/solid-router/tests/renderRouterToStream.test.tsx +++ b/packages/solid-router/tests/renderRouterToStream.test.tsx @@ -56,7 +56,7 @@ function drainBody(response: Response) { } describe('renderRouterToStream - bot abort', () => { - test('request abort during bot wait returns and terminates response stream', async () => { + test('request abort during bot wait terminates before rendering starts', async () => { const neverReady = new Promise(() => {}) solidMocks.pipeTo.mockImplementationOnce( (writable: WritableStream) => { @@ -99,6 +99,7 @@ describe('renderRouterToStream - bot abort', () => { expect(result).not.toBe(false) expect(solidMocks.pipeTo).not.toHaveBeenCalled() const response = unwrapResponse(result as Exclude) + expect(response.body).not.toBeNull() const terminated = await Promise.race([ drainBody(response), diff --git a/packages/solid-router/tests/routeContext.test.tsx b/packages/solid-router/tests/routeContext.test.tsx index ddca164b6a..69ab52b6b5 100644 --- a/packages/solid-router/tests/routeContext.test.tsx +++ b/packages/solid-router/tests/routeContext.test.tsx @@ -139,7 +139,7 @@ describe('context function', () => { }) test('when loader deps change', async () => { - const mockContextFn = vi.fn() + let generation = 0 const rootRoute = createRootRoute() const indexRoute = createRoute({ @@ -151,14 +151,21 @@ describe('context function', () => { path: '/', loaderDeps: ({ search }) => ({ foo: search.foo }), context: ({ deps }) => { - mockContextFn(deps) + return { + generation: ++generation, + deps: JSON.stringify(deps), + } }, component: () => { const navigate = indexRoute.useNavigate() + const context = indexRoute.useRouteContext() return (

Index page

search: {JSON.stringify(indexRoute.useSearch()())}

+

+ context: {context().generation}:{context().deps} +