diff --git a/e2e/solid-start/basic-solid-query/.gitignore b/e2e/solid-start/basic-solid-query/.gitignore new file mode 100644 index 0000000000..be342025da --- /dev/null +++ b/e2e/solid-start/basic-solid-query/.gitignore @@ -0,0 +1,22 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +.output +.vinxi + +/build/ +/api/ +/server/build +/public/build +.vinxi +# Sentry Config File +.env.sentry-build-plugin +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/e2e/solid-start/basic-solid-query/.prettierignore b/e2e/solid-start/basic-solid-query/.prettierignore new file mode 100644 index 0000000000..2be5eaa6ec --- /dev/null +++ b/e2e/solid-start/basic-solid-query/.prettierignore @@ -0,0 +1,4 @@ +**/build +**/public +pnpm-lock.yaml +routeTree.gen.ts \ No newline at end of file diff --git a/e2e/solid-start/basic-solid-query/app.config.ts b/e2e/solid-start/basic-solid-query/app.config.ts new file mode 100644 index 0000000000..2a06e3d3f0 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/app.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from '@tanstack/solid-start/config' +import tsConfigPaths from 'vite-tsconfig-paths' + +export default defineConfig({ + tsr: { + appDirectory: 'src', + }, + vite: { + plugins: [ + tsConfigPaths({ + projects: ['./tsconfig.json'], + }), + ], + }, +}) diff --git a/e2e/solid-start/basic-solid-query/package.json b/e2e/solid-start/basic-solid-query/package.json new file mode 100644 index 0000000000..9072a0833b --- /dev/null +++ b/e2e/solid-start/basic-solid-query/package.json @@ -0,0 +1,36 @@ +{ + "name": "tanstack-solid-start-e2e-basic-solid-query", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", + "build": "vinxi build && tsc --noEmit", + "start": "vinxi start", + "test:e2e": "playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/solid-query": "^5.66.0", + "@tanstack/solid-query-devtools": "^5.66.0", + "@tanstack/solid-router": "workspace:^", + "@tanstack/solid-router-with-query": "workspace:^", + "@tanstack/solid-router-devtools": "workspace:^", + "@tanstack/solid-start": "workspace:^", + "solid-js": "^1.9.5", + "redaxios": "^0.5.1", + "tailwind-merge": "^2.6.0", + "vinxi": "0.5.3" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/node": "^22.10.2", + "@vitejs/plugin-react": "^4.3.4", + "postcss": "^8.5.1", + "autoprefixer": "^10.4.20", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/e2e/solid-start/basic-solid-query/playwright.config.ts b/e2e/solid-start/basic-solid-query/playwright.config.ts new file mode 100644 index 0000000000..bb77d0cf70 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test' +import { derivePort } from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +const PORT = derivePort(packageJson.name) +const baseURL = `http://localhost:${PORT}` +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + workers: 1, + + reporter: [['line']], + + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL, + }, + + webServer: { + command: `VITE_SERVER_PORT=${PORT} pnpm build && VITE_SERVER_PORT=${PORT} pnpm start --port ${PORT}`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/solid-start/basic-solid-query/postcss.config.mjs b/e2e/solid-start/basic-solid-query/postcss.config.mjs new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/e2e/solid-start/basic-solid-query/public/android-chrome-192x192.png b/e2e/solid-start/basic-solid-query/public/android-chrome-192x192.png new file mode 100644 index 0000000000..09c8324f8c Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/android-chrome-192x192.png differ diff --git a/e2e/solid-start/basic-solid-query/public/android-chrome-512x512.png b/e2e/solid-start/basic-solid-query/public/android-chrome-512x512.png new file mode 100644 index 0000000000..11d626ea3d Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/android-chrome-512x512.png differ diff --git a/e2e/solid-start/basic-solid-query/public/apple-touch-icon.png b/e2e/solid-start/basic-solid-query/public/apple-touch-icon.png new file mode 100644 index 0000000000..5a9423cc02 Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/apple-touch-icon.png differ diff --git a/e2e/solid-start/basic-solid-query/public/favicon-16x16.png b/e2e/solid-start/basic-solid-query/public/favicon-16x16.png new file mode 100644 index 0000000000..e3389b0044 Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/favicon-16x16.png differ diff --git a/e2e/solid-start/basic-solid-query/public/favicon-32x32.png b/e2e/solid-start/basic-solid-query/public/favicon-32x32.png new file mode 100644 index 0000000000..900c77d444 Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/favicon-32x32.png differ diff --git a/e2e/solid-start/basic-solid-query/public/favicon.ico b/e2e/solid-start/basic-solid-query/public/favicon.ico new file mode 100644 index 0000000000..1a1751676f Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/favicon.ico differ diff --git a/e2e/solid-start/basic-solid-query/public/favicon.png b/e2e/solid-start/basic-solid-query/public/favicon.png new file mode 100644 index 0000000000..1e77bc0609 Binary files /dev/null and b/e2e/solid-start/basic-solid-query/public/favicon.png differ diff --git a/e2e/solid-start/basic-solid-query/public/site.webmanifest b/e2e/solid-start/basic-solid-query/public/site.webmanifest new file mode 100644 index 0000000000..fa99de77db --- /dev/null +++ b/e2e/solid-start/basic-solid-query/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/e2e/solid-start/basic-solid-query/src/api.ts b/e2e/solid-start/basic-solid-query/src/api.ts new file mode 100644 index 0000000000..ed511bcd26 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/api.ts @@ -0,0 +1,6 @@ +import { + createStartAPIHandler, + defaultAPIFileRouteHandler, +} from '@tanstack/solid-start/api' + +export default createStartAPIHandler(defaultAPIFileRouteHandler) diff --git a/e2e/solid-start/basic-solid-query/src/client.tsx b/e2e/solid-start/basic-solid-query/src/client.tsx new file mode 100644 index 0000000000..071dbdf4ad --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/client.tsx @@ -0,0 +1,8 @@ +/// +import { hydrate } from 'solid-js/web' +import { StartClient } from '@tanstack/solid-start' +import { createRouter } from './router' + +const router = createRouter() + +hydrate(() => , document!) diff --git a/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx b/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx new file mode 100644 index 0000000000..32aed20e67 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx @@ -0,0 +1,53 @@ +import { + ErrorComponent, + Link, + rootRouteId, + useMatch, + useRouter, +} from '@tanstack/solid-router' +import type { ErrorComponentProps } from '@tanstack/solid-router' + +export function DefaultCatchBoundary({ error }: ErrorComponentProps) { + const router = useRouter() + const isRoot = useMatch({ + strict: false, + select: (state) => state.id === rootRouteId, + }) + + console.error(error) + + return ( +
+ +
+ + {isRoot() ? ( + + Home + + ) : ( + { + e.preventDefault() + window.history.back() + }} + > + Go Back + + )} +
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx b/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx new file mode 100644 index 0000000000..ca4c1960fa --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx @@ -0,0 +1,25 @@ +import { Link } from '@tanstack/solid-router' + +export function NotFound({ children }: { children?: any }) { + return ( +
+
+ {children ||

The page you are looking for does not exist.

} +
+

+ + + Start Over + +

+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts new file mode 100644 index 0000000000..55b94eb5b5 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts @@ -0,0 +1,470 @@ +/* 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 Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as UsersImport } from './routes/users' +import { Route as RedirectImport } from './routes/redirect' +import { Route as PostsImport } from './routes/posts' +import { Route as DeferredImport } from './routes/deferred' +import { Route as LayoutImport } from './routes/_layout' +import { Route as IndexImport } from './routes/index' +import { Route as UsersIndexImport } from './routes/users.index' +import { Route as PostsIndexImport } from './routes/posts.index' +import { Route as UsersUserIdImport } from './routes/users.$userId' +import { Route as PostsPostIdImport } from './routes/posts.$postId' +import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2' +import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep' +import { Route as LayoutLayout2LayoutBImport } from './routes/_layout/_layout-2/layout-b' +import { Route as LayoutLayout2LayoutAImport } from './routes/_layout/_layout-2/layout-a' + +// Create/Update Routes + +const UsersRoute = UsersImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRoute, +} as any) + +const RedirectRoute = RedirectImport.update({ + id: '/redirect', + path: '/redirect', + getParentRoute: () => rootRoute, +} as any) + +const PostsRoute = PostsImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRoute, +} as any) + +const DeferredRoute = DeferredImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRoute, +} as any) + +const LayoutRoute = LayoutImport.update({ + id: '/_layout', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const UsersIndexRoute = UsersIndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, +} as any) + +const PostsIndexRoute = PostsIndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, +} as any) + +const UsersUserIdRoute = UsersUserIdImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, +} as any) + +const PostsPostIdRoute = PostsPostIdImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) + +const LayoutLayout2Route = LayoutLayout2Import.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) + +const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRoute, +} as any) + +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBImport.update({ + id: '/layout-b', + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) + +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutAImport.update({ + id: '/layout-a', + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutImport + parentRoute: typeof rootRoute + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredImport + parentRoute: typeof rootRoute + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsImport + parentRoute: typeof rootRoute + } + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectImport + parentRoute: typeof rootRoute + } + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersImport + parentRoute: typeof rootRoute + } + '/_layout/_layout-2': { + id: '/_layout/_layout-2' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutLayout2Import + parentRoute: typeof LayoutImport + } + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdImport + parentRoute: typeof PostsImport + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdImport + parentRoute: typeof UsersImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexImport + parentRoute: typeof PostsImport + } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexImport + parentRoute: typeof UsersImport + } + '/_layout/_layout-2/layout-a': { + id: '/_layout/_layout-2/layout-a' + path: '/layout-a' + fullPath: '/layout-a' + preLoaderRoute: typeof LayoutLayout2LayoutAImport + parentRoute: typeof LayoutLayout2Import + } + '/_layout/_layout-2/layout-b': { + id: '/_layout/_layout-2/layout-b' + path: '/layout-b' + fullPath: '/layout-b' + preLoaderRoute: typeof LayoutLayout2LayoutBImport + parentRoute: typeof LayoutLayout2Import + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +interface LayoutLayout2RouteChildren { + LayoutLayout2LayoutARoute: typeof LayoutLayout2LayoutARoute + LayoutLayout2LayoutBRoute: typeof LayoutLayout2LayoutBRoute +} + +const LayoutLayout2RouteChildren: LayoutLayout2RouteChildren = { + LayoutLayout2LayoutARoute: LayoutLayout2LayoutARoute, + LayoutLayout2LayoutBRoute: LayoutLayout2LayoutBRoute, +} + +const LayoutLayout2RouteWithChildren = LayoutLayout2Route._addFileChildren( + LayoutLayout2RouteChildren, +) + +interface LayoutRouteChildren { + LayoutLayout2Route: typeof LayoutLayout2RouteWithChildren +} + +const LayoutRouteChildren: LayoutRouteChildren = { + LayoutLayout2Route: LayoutLayout2RouteWithChildren, +} + +const LayoutRouteWithChildren = + LayoutRoute._addFileChildren(LayoutRouteChildren) + +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) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '': typeof LayoutLayout2RouteWithChildren + '/deferred': typeof DeferredRoute + '/posts': typeof PostsRouteWithChildren + '/redirect': typeof RedirectRoute + '/users': typeof UsersRouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts/': typeof PostsIndexRoute + '/users/': typeof UsersIndexRoute + '/layout-a': typeof LayoutLayout2LayoutARoute + '/layout-b': typeof LayoutLayout2LayoutBRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '': typeof LayoutLayout2RouteWithChildren + '/deferred': typeof DeferredRoute + '/redirect': typeof RedirectRoute + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts': typeof PostsIndexRoute + '/users': typeof UsersIndexRoute + '/layout-a': typeof LayoutLayout2LayoutARoute + '/layout-b': typeof LayoutLayout2LayoutBRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/_layout': typeof LayoutRouteWithChildren + '/deferred': typeof DeferredRoute + '/posts': typeof PostsRouteWithChildren + '/redirect': typeof RedirectRoute + '/users': typeof UsersRouteWithChildren + '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts/': typeof PostsIndexRoute + '/users/': typeof UsersIndexRoute + '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute + '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute + '/posts_/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '' + | '/deferred' + | '/posts' + | '/redirect' + | '/users' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/layout-a' + | '/layout-b' + | '/posts/$postId/deep' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '' + | '/deferred' + | '/redirect' + | '/posts/$postId' + | '/users/$userId' + | '/posts' + | '/users' + | '/layout-a' + | '/layout-b' + | '/posts/$postId/deep' + id: + | '__root__' + | '/' + | '/_layout' + | '/deferred' + | '/posts' + | '/redirect' + | '/users' + | '/_layout/_layout-2' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/_layout/_layout-2/layout-a' + | '/_layout/_layout-2/layout-b' + | '/posts_/$postId/deep' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + LayoutRoute: typeof LayoutRouteWithChildren + DeferredRoute: typeof DeferredRoute + PostsRoute: typeof PostsRouteWithChildren + RedirectRoute: typeof RedirectRoute + UsersRoute: typeof UsersRouteWithChildren + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + LayoutRoute: LayoutRouteWithChildren, + DeferredRoute: DeferredRoute, + PostsRoute: PostsRouteWithChildren, + RedirectRoute: RedirectRoute, + UsersRoute: UsersRouteWithChildren, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/_layout", + "/deferred", + "/posts", + "/redirect", + "/users", + "/posts_/$postId/deep" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/_layout": { + "filePath": "_layout.tsx", + "children": [ + "/_layout/_layout-2" + ] + }, + "/deferred": { + "filePath": "deferred.tsx" + }, + "/posts": { + "filePath": "posts.tsx", + "children": [ + "/posts/$postId", + "/posts/" + ] + }, + "/redirect": { + "filePath": "redirect.tsx" + }, + "/users": { + "filePath": "users.tsx", + "children": [ + "/users/$userId", + "/users/" + ] + }, + "/_layout/_layout-2": { + "filePath": "_layout/_layout-2.tsx", + "parent": "/_layout", + "children": [ + "/_layout/_layout-2/layout-a", + "/_layout/_layout-2/layout-b" + ] + }, + "/posts/$postId": { + "filePath": "posts.$postId.tsx", + "parent": "/posts" + }, + "/users/$userId": { + "filePath": "users.$userId.tsx", + "parent": "/users" + }, + "/posts/": { + "filePath": "posts.index.tsx", + "parent": "/posts" + }, + "/users/": { + "filePath": "users.index.tsx", + "parent": "/users" + }, + "/_layout/_layout-2/layout-a": { + "filePath": "_layout/_layout-2/layout-a.tsx", + "parent": "/_layout/_layout-2" + }, + "/_layout/_layout-2/layout-b": { + "filePath": "_layout/_layout-2/layout-b.tsx", + "parent": "/_layout/_layout-2" + }, + "/posts_/$postId/deep": { + "filePath": "posts_.$postId.deep.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/e2e/solid-start/basic-solid-query/src/router.tsx b/e2e/solid-start/basic-solid-query/src/router.tsx new file mode 100644 index 0000000000..684b6a60b0 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/router.tsx @@ -0,0 +1,32 @@ +import { QueryClient } from '@tanstack/solid-query' +import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { routerWithQueryClient } from '@tanstack/solid-router-with-query' +import { routeTree } from './routeTree.gen' +import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' +import { NotFound } from './components/NotFound' + +// NOTE: Most of the integration code found here is experimental and will +// definitely end up in a more streamlined API in the future. This is just +// to show what's possible with the current APIs. + +export function createRouter() { + const queryClient = new QueryClient() + + return routerWithQueryClient( + createTanStackRouter({ + routeTree, + context: { queryClient }, + scrollRestoration: true, + defaultPreload: 'intent', + defaultErrorComponent: DefaultCatchBoundary, + defaultNotFoundComponent: () => , + }), + queryClient, + ) +} + +declare module '@tanstack/solid-router' { + interface Register { + router: ReturnType + } +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/__root.tsx b/e2e/solid-start/basic-solid-query/src/routes/__root.tsx new file mode 100644 index 0000000000..857ecea572 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/__root.tsx @@ -0,0 +1,141 @@ +import { + HeadContent, + Link, + Outlet, + Scripts, + createRootRouteWithContext, +} from '@tanstack/solid-router' +import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' +import { TanStackRouterDevtoolsInProd } from '@tanstack/solid-router-devtools' +import * as React from 'react' +import type { QueryClient } from '@tanstack/solid-query' +import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary' +import { NotFound } from '~/components/NotFound' +import appCss from '~/styles/app.css?url' +import { seo } from '~/utils/seo' +import * as Solid from 'solid-js' + +export const Route = createRootRouteWithContext<{ + queryClient: QueryClient +}>()({ + 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' }, + ], + }), + errorComponent: (props) => { + return ( + + + + ) + }, + notFoundComponent: () => , + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + ) +} + +function RootDocument({ children }: { children: Solid.JSX.Element }) { + return ( + <> + +
+ + Home + {' '} + + Posts + {' '} + + Users + {' '} + + Layout + {' '} + + Deferred + {' '} + + This Route Does Not Exist + +
+
+ {children} + + + + + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/_layout.tsx b/e2e/solid-start/basic-solid-query/src/routes/_layout.tsx new file mode 100644 index 0000000000..6cda4a6954 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/_layout.tsx @@ -0,0 +1,16 @@ +import { Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_layout')({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( +
+
I'm a layout
+
+ +
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2.tsx b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2.tsx new file mode 100644 index 0000000000..a0c65e6fe7 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2.tsx @@ -0,0 +1,34 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_layout/_layout-2')({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( +
+
I'm a nested layout
+
+ + Layout A + + + Layout B + +
+
+ +
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-a.tsx b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-a.tsx new file mode 100644 index 0000000000..139c891946 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-a.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_layout/_layout-2/layout-a')({ + component: LayoutAComponent, +}) + +function LayoutAComponent() { + return
I'm A!
+} diff --git a/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-b.tsx b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-b.tsx new file mode 100644 index 0000000000..c9b5d7fffd --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/_layout/_layout-2/layout-b.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_layout/_layout-2/layout-b')({ + component: LayoutBComponent, +}) + +function LayoutBComponent() { + return
I'm B!
+} diff --git a/e2e/solid-start/basic-solid-query/src/routes/api.users.ts b/e2e/solid-start/basic-solid-query/src/routes/api.users.ts new file mode 100644 index 0000000000..f37bf0db1b --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/api.users.ts @@ -0,0 +1,17 @@ +import { json } from '@tanstack/solid-start' +import { createAPIFileRoute } from '@tanstack/solid-start/api' +import axios from 'redaxios' +import type { User } from '../utils/users' + +export const APIRoute = createAPIFileRoute('/api/users')({ + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>( + 'https://jsonplaceholder.typicode.com/users', + ) + + const list = res.data.slice(0, 10) + + return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) + }, +}) diff --git a/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts b/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts new file mode 100644 index 0000000000..b1786f6a30 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts @@ -0,0 +1,24 @@ +import { json } from '@tanstack/solid-start' +import { createAPIFileRoute } from '@tanstack/solid-start/api' +import axios from 'redaxios' +import type { User } from '../../utils/users' + +export const APIRoute = createAPIFileRoute('/api/users/$id')({ + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get( + 'https://jsonplaceholder.typicode.com/users/' + params.id, + ) + + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, +}) diff --git a/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx b/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx new file mode 100644 index 0000000000..7ce060840f --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx @@ -0,0 +1,53 @@ +import { queryOptions, createQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' +import { Suspense, createSignal } from 'solid-js' + +const deferredQueryOptions = () => + queryOptions({ + queryKey: ['deferred'], + queryFn: async () => { + await new Promise((r) => setTimeout(r, 3000)) + return { + message: `Hello deferred from the server!`, + status: 'success', + time: new Date(), + } + }, + }) + +export const Route = createFileRoute('/deferred')({ + loader: ({ context }) => { + // Kick off loading as early as possible! + context.queryClient.prefetchQuery(deferredQueryOptions()) + }, + component: Deferred, +}) + +function Deferred() { + const [count, setCount] = createSignal(0) + + return ( +
+ + + +
Count: {count()}
+
+ +
+
+ ) +} + +function DeferredQuery() { + const deferredQuery = createQuery(() => deferredQueryOptions()) + + return ( +
+

Deferred Query

+
Status: {deferredQuery.data?.status}
+
Message: {deferredQuery.data?.message}
+
Time: {deferredQuery.data?.time.toISOString()}
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/index.tsx b/e2e/solid-start/basic-solid-query/src/routes/index.tsx new file mode 100644 index 0000000000..a128aeca0e --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Welcome Home!!!

+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx new file mode 100644 index 0000000000..331775f0d4 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx @@ -0,0 +1,52 @@ +import { ErrorComponent, Link, createFileRoute } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import type { ErrorComponentProps } from '@tanstack/solid-router' + +import { postQueryOptions } from '~/utils/posts' +import { NotFound } from '~/components/NotFound' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId }, context }) => { + const data = await context.queryClient.ensureQueryData( + postQueryOptions(postId), + ) + + return { + title: data.title, + } + }, + head: ({ loaderData }) => ({ + meta: loaderData ? [{ title: loaderData.title }] : undefined, + }), + errorComponent: PostErrorComponent, + notFoundComponent: () => { + return Post not found + }, + component: PostComponent, +}) + +export function PostErrorComponent({ error }: ErrorComponentProps) { + return +} + +function PostComponent() { + const params = Route.useParams() + const postQuery = createQuery(() => postQueryOptions(params().postId)) + + return ( +
+

{postQuery.data?.title}

+
{postQuery.data?.body}
+ + Deep View + +
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx new file mode 100644 index 0000000000..33d0386c19 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Select a post.
+} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.tsx new file mode 100644 index 0000000000..3c61a1a929 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.tsx @@ -0,0 +1,44 @@ +import { createQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' + +import { postsQueryOptions } from '~/utils/posts' + +export const Route = createFileRoute('/posts')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(postsQueryOptions()) + }, + head: () => ({ meta: [{ title: 'Posts' }] }), + component: PostsComponent, +}) + +function PostsComponent() { + const postsQuery = createQuery(() => postsQueryOptions()) + + return ( +
+
    + {[ + ...postsQuery.data!, + { id: 'i-do-not-exist', title: 'Non-existent Post' }, + ].map((post) => { + return ( +
  • + +
    {post.title.substring(0, 20)}
    + +
  • + ) + })} +
+
+ +
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx new file mode 100644 index 0000000000..0d84e1f2d7 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx @@ -0,0 +1,36 @@ +import { Link, createFileRoute } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import { postQueryOptions } from '../utils/posts' +import { PostErrorComponent } from './posts.$postId' + +export const Route = createFileRoute('/posts_/$postId/deep')({ + loader: async ({ params: { postId }, context }) => { + const data = await context.queryClient.ensureQueryData( + postQueryOptions(postId), + ) + + return { + title: data.title, + } + }, + head: ({ loaderData }) => ({ + meta: loaderData ? [{ title: loaderData.title }] : undefined, + }), + errorComponent: PostErrorComponent, + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const params = Route.useParams() + const postQuery = createQuery(() => postQueryOptions(params().postId)) + + return ( +
+ + ← All Posts + +

{postQuery.data?.title}

+
{postQuery.data?.body}
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/redirect.tsx b/e2e/solid-start/basic-solid-query/src/routes/redirect.tsx new file mode 100644 index 0000000000..ca017f0635 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/redirect.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, redirect } from '@tanstack/solid-router' + +export const Route = createFileRoute('/redirect')({ + beforeLoad: async () => { + throw redirect({ + to: '/posts', + }) + }, +}) diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx new file mode 100644 index 0000000000..fe9f58faf3 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx @@ -0,0 +1,34 @@ +import { createQuery } from '@tanstack/solid-query' +import { ErrorComponent, createFileRoute } from '@tanstack/solid-router' +import type { ErrorComponentProps } from '@tanstack/solid-router' + +import { NotFound } from '~/components/NotFound' +import { userQueryOptions } from '~/utils/users' + +export const Route = createFileRoute('/users/$userId')({ + loader: async ({ context, params: { userId } }) => { + await context.queryClient.ensureQueryData(userQueryOptions(userId)) + }, + errorComponent: UserErrorComponent, + component: UserComponent, + notFoundComponent: () => { + return User not found + }, +}) + +export function UserErrorComponent({ error }: ErrorComponentProps) { + return +} + +function UserComponent() { + const params = Route.useParams() + const userQuery = createQuery(() => userQueryOptions(params().userId)) + const user = userQuery.data + + return ( +
+

{user?.name}

+
{user?.email}
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx new file mode 100644 index 0000000000..bbc96801a9 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/users/')({ + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return
Select a user.
+} diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.tsx new file mode 100644 index 0000000000..92f595b171 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.tsx @@ -0,0 +1,43 @@ +import { createQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' + +import { usersQueryOptions } from '~/utils/users' + +export const Route = createFileRoute('/users')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(usersQueryOptions()) + }, + component: UsersComponent, +}) + +function UsersComponent() { + const usersQuery = createQuery(() => usersQueryOptions()) + + return ( +
+
    + {[ + ...usersQuery.data!, + { id: 'i-do-not-exist', name: 'Non-existent User', email: '' }, + ].map((user) => { + return ( +
  • + +
    {user.name}
    + +
  • + ) + })} +
+
+ +
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/ssr.tsx b/e2e/solid-start/basic-solid-query/src/ssr.tsx new file mode 100644 index 0000000000..6d10bea05f --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/ssr.tsx @@ -0,0 +1,12 @@ +import { + createStartHandler, + defaultStreamHandler, +} from '@tanstack/solid-start/server' +import { getRouterManifest } from '@tanstack/solid-start/router-manifest' + +import { createRouter } from './router' + +export default createStartHandler({ + createRouter, + getRouterManifest, +})(defaultStreamHandler) diff --git a/e2e/solid-start/basic-solid-query/src/styles/app.css b/e2e/solid-start/basic-solid-query/src/styles/app.css new file mode 100644 index 0000000000..c53c870665 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/styles/app.css @@ -0,0 +1,22 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html { + color-scheme: light dark; + } + + * { + @apply border-gray-200 dark:border-gray-800; + } + + html, + body { + @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; + } + + .using-mouse * { + outline: none !important; + } +} diff --git a/e2e/solid-start/basic-solid-query/src/utils/posts.tsx b/e2e/solid-start/basic-solid-query/src/utils/posts.tsx new file mode 100644 index 0000000000..d3ab71d525 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/utils/posts.tsx @@ -0,0 +1,49 @@ +import { queryOptions } from '@tanstack/solid-query' +import { notFound } from '@tanstack/solid-router' +import { createServerFn } from '@tanstack/solid-start' +import axios from 'redaxios' + +export type PostType = { + id: string + title: string + body: string +} + +export const fetchPosts = createServerFn({ method: 'GET' }).handler( + async () => { + console.info('Fetching posts...') + return axios + .get>('https://jsonplaceholder.typicode.com/posts') + .then((r) => r.data.slice(0, 10)) + }, +) + +export const postsQueryOptions = () => + queryOptions({ + queryKey: ['posts'], + queryFn: () => fetchPosts(), + }) + +export const fetchPost = createServerFn({ method: 'GET' }) + .validator((postId: string) => postId) + .handler(async ({ data: postId }) => { + console.info(`Fetching post with id ${postId}...`) + const post = await axios + .get(`https://jsonplaceholder.typicode.com/posts/${postId}`) + .then((r) => r.data) + .catch((err) => { + console.error(err) + if (err.status === 404) { + throw notFound() + } + throw err + }) + + return post + }) + +export const postQueryOptions = (postId: string) => + queryOptions({ + queryKey: ['post', postId], + queryFn: () => fetchPost({ data: postId }), + }) diff --git a/e2e/solid-start/basic-solid-query/src/utils/seo.ts b/e2e/solid-start/basic-solid-query/src/utils/seo.ts new file mode 100644 index 0000000000..d18ad84b74 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/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/e2e/solid-start/basic-solid-query/src/utils/users.tsx b/e2e/solid-start/basic-solid-query/src/utils/users.tsx new file mode 100644 index 0000000000..52d659b001 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/utils/users.tsx @@ -0,0 +1,37 @@ +import { queryOptions } from '@tanstack/solid-query' +import axios from 'redaxios' + +export type User = { + id: number + name: string + email: string +} + +const PORT = + import.meta.env.VITE_SERVER_PORT || process.env.VITE_SERVER_PORT || 3000 + +export const DEPLOY_URL = `http://localhost:${PORT}` + +export const usersQueryOptions = () => + queryOptions({ + queryKey: ['users'], + queryFn: () => + axios + .get>(DEPLOY_URL + '/api/users') + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch users') + }), + }) + +export const userQueryOptions = (id: string) => + queryOptions({ + queryKey: ['users', id], + queryFn: () => + axios + .get(DEPLOY_URL + '/api/users/' + id) + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch user') + }), + }) diff --git a/e2e/solid-start/basic-solid-query/tailwind.config.mjs b/e2e/solid-start/basic-solid-query/tailwind.config.mjs new file mode 100644 index 0000000000..e49f4eb776 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tailwind.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], +} diff --git a/e2e/solid-start/basic-solid-query/tests/app.spec.ts b/e2e/solid-start/basic-solid-query/tests/app.spec.ts new file mode 100644 index 0000000000..af128bfcb4 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tests/app.spec.ts @@ -0,0 +1,36 @@ +import { expect, test } from '@playwright/test' + +test('Navigating to post', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'Posts' }).click() + await page.getByRole('link', { name: 'sunt aut facere repe' }).click() + await page.getByRole('link', { name: 'Deep View' }).click() + await expect(page.getByRole('heading')).toContainText('sunt aut facere') +}) + +test('Navigating to user', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'Users' }).click() + await page.getByRole('link', { name: 'Leanne Graham' }).click() + await expect(page.getByRole('heading')).toContainText('Leanne Graham') +}) + +test('Navigating nested layouts', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'Layout', exact: true }).click() + await page.getByRole('link', { name: 'Layout A' }).click() + await expect(page.locator('body')).toContainText("I'm A!") + await page.getByRole('link', { name: 'Layout B' }).click() + await expect(page.locator('body')).toContainText("I'm B!") +}) + +test('Navigating to a not-found route', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() + await page.getByRole('link', { name: 'Start Over' }).click() + await expect(page.getByRole('heading')).toContainText('Welcome Home!') +}) diff --git a/e2e/solid-start/basic-solid-query/tsconfig.json b/e2e/solid-start/basic-solid-query/tsconfig.json new file mode 100644 index 0000000000..a40235b863 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +} diff --git a/examples/solid/start-basic-solid-query/.gitignore b/examples/solid/start-basic-solid-query/.gitignore new file mode 100644 index 0000000000..be342025da --- /dev/null +++ b/examples/solid/start-basic-solid-query/.gitignore @@ -0,0 +1,22 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +.output +.vinxi + +/build/ +/api/ +/server/build +/public/build +.vinxi +# Sentry Config File +.env.sentry-build-plugin +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/examples/solid/start-basic-solid-query/.prettierignore b/examples/solid/start-basic-solid-query/.prettierignore new file mode 100644 index 0000000000..2be5eaa6ec --- /dev/null +++ b/examples/solid/start-basic-solid-query/.prettierignore @@ -0,0 +1,4 @@ +**/build +**/public +pnpm-lock.yaml +routeTree.gen.ts \ No newline at end of file diff --git a/examples/solid/start-basic-solid-query/.vscode/settings.json b/examples/solid/start-basic-solid-query/.vscode/settings.json new file mode 100644 index 0000000000..00b5278e58 --- /dev/null +++ b/examples/solid/start-basic-solid-query/.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/solid/start-basic-solid-query/README.md b/examples/solid/start-basic-solid-query/README.md new file mode 100644 index 0000000000..90cba4aac1 --- /dev/null +++ b/examples/solid/start-basic-solid-query/README.md @@ -0,0 +1,72 @@ +# Welcome to TanStack.com! + +This site is built with TanStack Router! + +- [TanStack Router Docs](https://tanstack.com/router) + +It's deployed automagically with Netlify! + +- [Netlify](https://netlify.com/) + +## Development + +From your terminal: + +```sh +pnpm install +pnpm dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## Editing and previewing the docs of TanStack projects locally + +The documentations for all TanStack projects except for `React Charts` are hosted on [https://tanstack.com](https://tanstack.com), powered by this TanStack Router app. +In production, the markdown doc pages are fetched from the GitHub repos of the projects, but in development they are read from the local file system. + +Follow these steps if you want to edit the doc pages of a project (in these steps we'll assume it's [`TanStack/form`](https://github.com/tanstack/form)) and preview them locally : + +1. Create a new directory called `tanstack`. + +```sh +mkdir tanstack +``` + +2. Enter the directory and clone this repo and the repo of the project there. + +```sh +cd tanstack +git clone git@github.com:TanStack/tanstack.com.git +git clone git@github.com:TanStack/form.git +``` + +> [!NOTE] +> Your `tanstack` directory should look like this: +> +> ``` +> tanstack/ +> | +> +-- form/ +> | +> +-- tanstack.com/ +> ``` + +> [!WARNING] +> Make sure the name of the directory in your local file system matches the name of the project's repo. For example, `tanstack/form` must be cloned into `form` (this is the default) instead of `some-other-name`, because that way, the doc pages won't be found. + +3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode: + +```sh +cd tanstack.com +pnpm i +# The app will run on https://localhost:3000 by default +pnpm dev +``` + +4. Now you can visit http://localhost:3000/form/latest/docs/overview in the browser and see the changes you make in `tanstack/form/docs`. + +> [!NOTE] +> The updated pages need to be manually reloaded in the browser. + +> [!WARNING] +> You will need to update the `docs/config.json` file (in the project's repo) if you add a new doc page! diff --git a/examples/solid/start-basic-solid-query/app.config.ts b/examples/solid/start-basic-solid-query/app.config.ts new file mode 100644 index 0000000000..2a06e3d3f0 --- /dev/null +++ b/examples/solid/start-basic-solid-query/app.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from '@tanstack/solid-start/config' +import tsConfigPaths from 'vite-tsconfig-paths' + +export default defineConfig({ + tsr: { + appDirectory: 'src', + }, + vite: { + plugins: [ + tsConfigPaths({ + projects: ['./tsconfig.json'], + }), + ], + }, +}) diff --git a/examples/solid/start-basic-solid-query/package.json b/examples/solid/start-basic-solid-query/package.json new file mode 100644 index 0000000000..5a16d749dd --- /dev/null +++ b/examples/solid/start-basic-solid-query/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-solid-start-example-basic-solid-query", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vinxi dev", + "build": "vinxi build", + "start": "vinxi start" + }, + "dependencies": { + "@tanstack/solid-query": "^5.66.0", + "@tanstack/solid-query-devtools": "^5.66.0", + "@tanstack/solid-router": "^1.114.29", + "@tanstack/solid-router-with-query": "^1.114.29", + "@tanstack/solid-router-devtools": "^1.114.29", + "@tanstack/solid-start": "^1.114.30", + "solid-js": "^1.9.5", + "redaxios": "^0.5.1", + "tailwind-merge": "^2.6.0", + "vinxi": "0.5.3" + }, + "devDependencies": { + "@types/node": "^22.5.4", + "postcss": "^8.5.1", + "autoprefixer": "^10.4.20", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/examples/solid/start-basic-solid-query/postcss.config.mjs b/examples/solid/start-basic-solid-query/postcss.config.mjs new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/examples/solid/start-basic-solid-query/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/solid/start-basic-solid-query/public/android-chrome-192x192.png b/examples/solid/start-basic-solid-query/public/android-chrome-192x192.png new file mode 100644 index 0000000000..09c8324f8c Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/android-chrome-192x192.png differ diff --git a/examples/solid/start-basic-solid-query/public/android-chrome-512x512.png b/examples/solid/start-basic-solid-query/public/android-chrome-512x512.png new file mode 100644 index 0000000000..11d626ea3d Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/android-chrome-512x512.png differ diff --git a/examples/solid/start-basic-solid-query/public/apple-touch-icon.png b/examples/solid/start-basic-solid-query/public/apple-touch-icon.png new file mode 100644 index 0000000000..5a9423cc02 Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/apple-touch-icon.png differ diff --git a/examples/solid/start-basic-solid-query/public/favicon-16x16.png b/examples/solid/start-basic-solid-query/public/favicon-16x16.png new file mode 100644 index 0000000000..e3389b0044 Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/favicon-16x16.png differ diff --git a/examples/solid/start-basic-solid-query/public/favicon-32x32.png b/examples/solid/start-basic-solid-query/public/favicon-32x32.png new file mode 100644 index 0000000000..900c77d444 Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/favicon-32x32.png differ diff --git a/examples/solid/start-basic-solid-query/public/favicon.ico b/examples/solid/start-basic-solid-query/public/favicon.ico new file mode 100644 index 0000000000..1a1751676f Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/favicon.ico differ diff --git a/examples/solid/start-basic-solid-query/public/favicon.png b/examples/solid/start-basic-solid-query/public/favicon.png new file mode 100644 index 0000000000..1e77bc0609 Binary files /dev/null and b/examples/solid/start-basic-solid-query/public/favicon.png differ diff --git a/examples/solid/start-basic-solid-query/public/site.webmanifest b/examples/solid/start-basic-solid-query/public/site.webmanifest new file mode 100644 index 0000000000..fa99de77db --- /dev/null +++ b/examples/solid/start-basic-solid-query/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/solid/start-basic-solid-query/src/api.ts b/examples/solid/start-basic-solid-query/src/api.ts new file mode 100644 index 0000000000..ed511bcd26 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/api.ts @@ -0,0 +1,6 @@ +import { + createStartAPIHandler, + defaultAPIFileRouteHandler, +} from '@tanstack/solid-start/api' + +export default createStartAPIHandler(defaultAPIFileRouteHandler) diff --git a/examples/solid/start-basic-solid-query/src/client.tsx b/examples/solid/start-basic-solid-query/src/client.tsx new file mode 100644 index 0000000000..b8d415d9a8 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/client.tsx @@ -0,0 +1,8 @@ +/// +import { hydrateRoot } from 'solid-dom/client' +import { StartClient } from '@tanstack/solid-start' +import { createRouter } from './router' + +const router = createRouter() + +hydrateRoot(document, ) diff --git a/examples/solid/start-basic-solid-query/src/components/DefaultCatchBoundary.tsx b/examples/solid/start-basic-solid-query/src/components/DefaultCatchBoundary.tsx new file mode 100644 index 0000000000..32aed20e67 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/components/DefaultCatchBoundary.tsx @@ -0,0 +1,53 @@ +import { + ErrorComponent, + Link, + rootRouteId, + useMatch, + useRouter, +} from '@tanstack/solid-router' +import type { ErrorComponentProps } from '@tanstack/solid-router' + +export function DefaultCatchBoundary({ error }: ErrorComponentProps) { + const router = useRouter() + const isRoot = useMatch({ + strict: false, + select: (state) => state.id === rootRouteId, + }) + + console.error(error) + + return ( +
+ +
+ + {isRoot() ? ( + + Home + + ) : ( + { + e.preventDefault() + window.history.back() + }} + > + Go Back + + )} +
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/components/NotFound.tsx b/examples/solid/start-basic-solid-query/src/components/NotFound.tsx new file mode 100644 index 0000000000..ca4c1960fa --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/components/NotFound.tsx @@ -0,0 +1,25 @@ +import { Link } from '@tanstack/solid-router' + +export function NotFound({ children }: { children?: any }) { + return ( +
+
+ {children ||

The page you are looking for does not exist.

} +
+

+ + + Start Over + +

+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routeTree.gen.ts b/examples/solid/start-basic-solid-query/src/routeTree.gen.ts new file mode 100644 index 0000000000..3a3b2216ad --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routeTree.gen.ts @@ -0,0 +1,483 @@ +/* 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 Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as RedirectImport } from './routes/redirect' +import { Route as DeferredImport } from './routes/deferred' +import { Route as PathlessLayoutImport } from './routes/_pathlessLayout' +import { Route as UsersRouteImport } from './routes/users.route' +import { Route as PostsRouteImport } from './routes/posts.route' +import { Route as IndexImport } from './routes/index' +import { Route as UsersIndexImport } from './routes/users.index' +import { Route as PostsIndexImport } from './routes/posts.index' +import { Route as UsersUserIdImport } from './routes/users.$userId' +import { Route as PostsPostIdImport } from './routes/posts.$postId' +import { Route as PathlessLayoutNestedLayoutImport } from './routes/_pathlessLayout/_nested-layout' +import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep' +import { Route as PathlessLayoutNestedLayoutRouteBImport } from './routes/_pathlessLayout/_nested-layout/route-b' +import { Route as PathlessLayoutNestedLayoutRouteAImport } from './routes/_pathlessLayout/_nested-layout/route-a' + +// Create/Update Routes + +const RedirectRoute = RedirectImport.update({ + id: '/redirect', + path: '/redirect', + getParentRoute: () => rootRoute, +} as any) + +const DeferredRoute = DeferredImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRoute, +} as any) + +const PathlessLayoutRoute = PathlessLayoutImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRoute, +} as any) + +const UsersRouteRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRoute, +} as any) + +const PostsRouteRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const UsersIndexRoute = UsersIndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRouteRoute, +} as any) + +const PostsIndexRoute = PostsIndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRouteRoute, +} as any) + +const UsersUserIdRoute = UsersUserIdImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRouteRoute, +} as any) + +const PostsPostIdRoute = PostsPostIdImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRouteRoute, +} as any) + +const PathlessLayoutNestedLayoutRoute = PathlessLayoutNestedLayoutImport.update( + { + id: '/_nested-layout', + getParentRoute: () => PathlessLayoutRoute, + } as any, +) + +const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRoute, +} as any) + +const PathlessLayoutNestedLayoutRouteBRoute = + PathlessLayoutNestedLayoutRouteBImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) + +const PathlessLayoutNestedLayoutRouteARoute = + PathlessLayoutNestedLayoutRouteAImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutNestedLayoutRoute, + } as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRoute + } + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRoute + } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '' + preLoaderRoute: typeof PathlessLayoutImport + parentRoute: typeof rootRoute + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredImport + parentRoute: typeof rootRoute + } + '/redirect': { + id: '/redirect' + path: '/redirect' + fullPath: '/redirect' + preLoaderRoute: typeof RedirectImport + parentRoute: typeof rootRoute + } + '/_pathlessLayout/_nested-layout': { + id: '/_pathlessLayout/_nested-layout' + path: '' + fullPath: '' + preLoaderRoute: typeof PathlessLayoutNestedLayoutImport + parentRoute: typeof PathlessLayoutImport + } + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdImport + parentRoute: typeof PostsRouteImport + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdImport + parentRoute: typeof UsersRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexImport + parentRoute: typeof PostsRouteImport + } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexImport + parentRoute: typeof UsersRouteImport + } + '/_pathlessLayout/_nested-layout/route-a': { + id: '/_pathlessLayout/_nested-layout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteAImport + parentRoute: typeof PathlessLayoutNestedLayoutImport + } + '/_pathlessLayout/_nested-layout/route-b': { + id: '/_pathlessLayout/_nested-layout/route-b' + path: '/route-b' + fullPath: '/route-b' + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBImport + parentRoute: typeof PathlessLayoutNestedLayoutImport + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +interface PostsRouteRouteChildren { + PostsPostIdRoute: typeof PostsPostIdRoute + PostsIndexRoute: typeof PostsIndexRoute +} + +const PostsRouteRouteChildren: PostsRouteRouteChildren = { + PostsPostIdRoute: PostsPostIdRoute, + PostsIndexRoute: PostsIndexRoute, +} + +const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren( + PostsRouteRouteChildren, +) + +interface UsersRouteRouteChildren { + UsersUserIdRoute: typeof UsersUserIdRoute + UsersIndexRoute: typeof UsersIndexRoute +} + +const UsersRouteRouteChildren: UsersRouteRouteChildren = { + UsersUserIdRoute: UsersUserIdRoute, + UsersIndexRoute: UsersIndexRoute, +} + +const UsersRouteRouteWithChildren = UsersRouteRoute._addFileChildren( + UsersRouteRouteChildren, +) + +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, +) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/posts': typeof PostsRouteRouteWithChildren + '/users': typeof UsersRouteRouteWithChildren + '': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/deferred': typeof DeferredRoute + '/redirect': typeof RedirectRoute + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts/': typeof PostsIndexRoute + '/users/': typeof UsersIndexRoute + '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute + '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/deferred': typeof DeferredRoute + '/redirect': typeof RedirectRoute + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts': typeof PostsIndexRoute + '/users': typeof UsersIndexRoute + '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute + '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/posts': typeof PostsRouteRouteWithChildren + '/users': typeof UsersRouteRouteWithChildren + '/_pathlessLayout': typeof PathlessLayoutRouteWithChildren + '/deferred': typeof DeferredRoute + '/redirect': typeof RedirectRoute + '/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren + '/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 + '/posts_/$postId/deep': typeof PostsPostIdDeepRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/posts' + | '/users' + | '' + | '/deferred' + | '/redirect' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/route-a' + | '/route-b' + | '/posts/$postId/deep' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '' + | '/deferred' + | '/redirect' + | '/posts/$postId' + | '/users/$userId' + | '/posts' + | '/users' + | '/route-a' + | '/route-b' + | '/posts/$postId/deep' + id: + | '__root__' + | '/' + | '/posts' + | '/users' + | '/_pathlessLayout' + | '/deferred' + | '/redirect' + | '/_pathlessLayout/_nested-layout' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/_pathlessLayout/_nested-layout/route-a' + | '/_pathlessLayout/_nested-layout/route-b' + | '/posts_/$postId/deep' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PostsRouteRoute: typeof PostsRouteRouteWithChildren + UsersRouteRoute: typeof UsersRouteRouteWithChildren + PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren + DeferredRoute: typeof DeferredRoute + RedirectRoute: typeof RedirectRoute + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PostsRouteRoute: PostsRouteRouteWithChildren, + UsersRouteRoute: UsersRouteRouteWithChildren, + PathlessLayoutRoute: PathlessLayoutRouteWithChildren, + DeferredRoute: DeferredRoute, + RedirectRoute: RedirectRoute, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/posts", + "/users", + "/_pathlessLayout", + "/deferred", + "/redirect", + "/posts_/$postId/deep" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/posts": { + "filePath": "posts.route.tsx", + "children": [ + "/posts/$postId", + "/posts/" + ] + }, + "/users": { + "filePath": "users.route.tsx", + "children": [ + "/users/$userId", + "/users/" + ] + }, + "/_pathlessLayout": { + "filePath": "_pathlessLayout.tsx", + "children": [ + "/_pathlessLayout/_nested-layout" + ] + }, + "/deferred": { + "filePath": "deferred.tsx" + }, + "/redirect": { + "filePath": "redirect.tsx" + }, + "/_pathlessLayout/_nested-layout": { + "filePath": "_pathlessLayout/_nested-layout.tsx", + "parent": "/_pathlessLayout", + "children": [ + "/_pathlessLayout/_nested-layout/route-a", + "/_pathlessLayout/_nested-layout/route-b" + ] + }, + "/posts/$postId": { + "filePath": "posts.$postId.tsx", + "parent": "/posts" + }, + "/users/$userId": { + "filePath": "users.$userId.tsx", + "parent": "/users" + }, + "/posts/": { + "filePath": "posts.index.tsx", + "parent": "/posts" + }, + "/users/": { + "filePath": "users.index.tsx", + "parent": "/users" + }, + "/_pathlessLayout/_nested-layout/route-a": { + "filePath": "_pathlessLayout/_nested-layout/route-a.tsx", + "parent": "/_pathlessLayout/_nested-layout" + }, + "/_pathlessLayout/_nested-layout/route-b": { + "filePath": "_pathlessLayout/_nested-layout/route-b.tsx", + "parent": "/_pathlessLayout/_nested-layout" + }, + "/posts_/$postId/deep": { + "filePath": "posts_.$postId.deep.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/examples/solid/start-basic-solid-query/src/router.tsx b/examples/solid/start-basic-solid-query/src/router.tsx new file mode 100644 index 0000000000..91891f3193 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/router.tsx @@ -0,0 +1,31 @@ +import { QueryClient } from '@tanstack/solid-query' +import { createRouter as createTanStackRouter } from '@tanstack/solid-router' +import { routerWithQueryClient } from '@tanstack/solid-router-with-query' +import { routeTree } from './routeTree.gen' +import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' +import { NotFound } from './components/NotFound' + +// NOTE: Most of the integration code found here is experimental and will +// definitely end up in a more streamlined API in the future. This is just +// to show what's possible with the current APIs. + +export function createRouter() { + const queryClient = new QueryClient() + + return routerWithQueryClient( + createTanStackRouter({ + routeTree, + context: { queryClient }, + defaultPreload: 'intent', + defaultErrorComponent: DefaultCatchBoundary, + defaultNotFoundComponent: () => , + }), + queryClient, + ) +} + +declare module '@tanstack/solid-router' { + interface Register { + router: ReturnType + } +} diff --git a/examples/solid/start-basic-solid-query/src/routes/__root.tsx b/examples/solid/start-basic-solid-query/src/routes/__root.tsx new file mode 100644 index 0000000000..053962a196 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/__root.tsx @@ -0,0 +1,136 @@ +import { + HeadContent, + Link, + Outlet, + Scripts, + createRootRouteWithContext, +} from '@tanstack/solid-router' +import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +import * as Solid from 'solid-js' +import type { QueryClient } from '@tanstack/solid-query' +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 = createRootRouteWithContext<{ + queryClient: QueryClient +}>()({ + 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' }, + ], + }), + errorComponent: (props) => { + return ( + + + + ) + }, + notFoundComponent: () => , + component: RootComponent, +}) + +function RootComponent() { + return +} + +function RootDocument({ children }: { children: Solid.JSX.Element }) { + return ( + <> + +
+ + Home + {' '} + + Posts + {' '} + + Users + {' '} + + Pathless Layout + {' '} + + Deferred + {' '} + + This Route Does Not Exist + +
+
+ {children} + + + + + ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout.tsx b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout.tsx new file mode 100644 index 0000000000..e525470c60 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout.tsx @@ -0,0 +1,16 @@ +import { Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_pathlessLayout')({ + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( +
+
I'm a pathless layout
+
+ +
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout.tsx b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout.tsx new file mode 100644 index 0000000000..8651932b0f --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout.tsx @@ -0,0 +1,34 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({ + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( +
+
I'm a nested pathless layout
+
+ + Go to route A + + + Go to route B + +
+
+ +
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-a.tsx b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-a.tsx new file mode 100644 index 0000000000..a22902a271 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-a.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')( + { + component: LayoutAComponent, + }, +) + +function LayoutAComponent() { + return
I'm A!
+} diff --git a/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-b.tsx b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-b.tsx new file mode 100644 index 0000000000..36231d2153 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/_pathlessLayout/_nested-layout/route-b.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')( + { + component: LayoutBComponent, + }, +) + +function LayoutBComponent() { + return
I'm B!
+} diff --git a/examples/solid/start-basic-solid-query/src/routes/api.users.ts b/examples/solid/start-basic-solid-query/src/routes/api.users.ts new file mode 100644 index 0000000000..f37bf0db1b --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/api.users.ts @@ -0,0 +1,17 @@ +import { json } from '@tanstack/solid-start' +import { createAPIFileRoute } from '@tanstack/solid-start/api' +import axios from 'redaxios' +import type { User } from '../utils/users' + +export const APIRoute = createAPIFileRoute('/api/users')({ + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>( + 'https://jsonplaceholder.typicode.com/users', + ) + + const list = res.data.slice(0, 10) + + return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email }))) + }, +}) diff --git a/examples/solid/start-basic-solid-query/src/routes/api/users.$id.ts b/examples/solid/start-basic-solid-query/src/routes/api/users.$id.ts new file mode 100644 index 0000000000..b1786f6a30 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/api/users.$id.ts @@ -0,0 +1,24 @@ +import { json } from '@tanstack/solid-start' +import { createAPIFileRoute } from '@tanstack/solid-start/api' +import axios from 'redaxios' +import type { User } from '../../utils/users' + +export const APIRoute = createAPIFileRoute('/api/users/$id')({ + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get( + 'https://jsonplaceholder.typicode.com/users/' + params.id, + ) + + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, +}) diff --git a/examples/solid/start-basic-solid-query/src/routes/deferred.tsx b/examples/solid/start-basic-solid-query/src/routes/deferred.tsx new file mode 100644 index 0000000000..7ce060840f --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/deferred.tsx @@ -0,0 +1,53 @@ +import { queryOptions, createQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' +import { Suspense, createSignal } from 'solid-js' + +const deferredQueryOptions = () => + queryOptions({ + queryKey: ['deferred'], + queryFn: async () => { + await new Promise((r) => setTimeout(r, 3000)) + return { + message: `Hello deferred from the server!`, + status: 'success', + time: new Date(), + } + }, + }) + +export const Route = createFileRoute('/deferred')({ + loader: ({ context }) => { + // Kick off loading as early as possible! + context.queryClient.prefetchQuery(deferredQueryOptions()) + }, + component: Deferred, +}) + +function Deferred() { + const [count, setCount] = createSignal(0) + + return ( +
+ + + +
Count: {count()}
+
+ +
+
+ ) +} + +function DeferredQuery() { + const deferredQuery = createQuery(() => deferredQueryOptions()) + + return ( +
+

Deferred Query

+
Status: {deferredQuery.data?.status}
+
Message: {deferredQuery.data?.message}
+
Time: {deferredQuery.data?.time.toISOString()}
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/index.tsx b/examples/solid/start-basic-solid-query/src/routes/index.tsx new file mode 100644 index 0000000000..a128aeca0e --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Welcome Home!!!

+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/posts.$postId.tsx b/examples/solid/start-basic-solid-query/src/routes/posts.$postId.tsx new file mode 100644 index 0000000000..a370d08090 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/posts.$postId.tsx @@ -0,0 +1,51 @@ +import { ErrorComponent, Link, createFileRoute } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import { postQueryOptions } from '../utils/posts' +import type { ErrorComponentProps } from '@tanstack/solid-router' +import { NotFound } from '~/components/NotFound' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId }, context }) => { + const data = await context.queryClient.ensureQueryData( + postQueryOptions(postId), + ) + + return { + title: data.title, + } + }, + head: ({ loaderData }) => ({ + meta: loaderData ? [{ title: loaderData.title }] : undefined, + }), + errorComponent: PostErrorComponent, + notFoundComponent: () => { + return Post not found + }, + component: PostComponent, +}) + +export function PostErrorComponent({ error }: ErrorComponentProps) { + return +} + +function PostComponent() { + const params = Route.useParams() + const postQuery = createQuery(() => postQueryOptions(params().postId)) + + return ( +
+

{postQuery.data?.title}

+
{postQuery.data?.body}
+ + Deep View + +
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/posts.index.tsx b/examples/solid/start-basic-solid-query/src/routes/posts.index.tsx new file mode 100644 index 0000000000..33d0386c19 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/posts.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Select a post.
+} diff --git a/examples/solid/start-basic-solid-query/src/routes/posts.route.tsx b/examples/solid/start-basic-solid-query/src/routes/posts.route.tsx new file mode 100644 index 0000000000..b568cdd3d6 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/posts.route.tsx @@ -0,0 +1,45 @@ +import { createQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' +import { postsQueryOptions } from '../utils/posts' + +export const Route = createFileRoute('/posts')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(postsQueryOptions()) + }, + head: () => ({ + meta: [{ title: 'Posts' }], + }), + component: PostsComponent, +}) + +function PostsComponent() { + const postsQuery = createQuery(() => postsQueryOptions()) + + return ( +
+
    + {[ + ...postsQuery.data!, + { id: 'i-do-not-exist', title: 'Non-existent Post' }, + ].map((post) => { + return ( +
  • + +
    {post.title.substring(0, 20)}
    + +
  • + ) + })} +
+
+ +
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/posts_.$postId.deep.tsx b/examples/solid/start-basic-solid-query/src/routes/posts_.$postId.deep.tsx new file mode 100644 index 0000000000..f5ae7ff840 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/posts_.$postId.deep.tsx @@ -0,0 +1,36 @@ +import { Link, createFileRoute } from '@tanstack/solid-router' +import { useSuspenseQuery } from '@tanstack/solid-query' +import { postQueryOptions } from '../utils/posts' +import { PostErrorComponent } from './posts.$postId' + +export const Route = createFileRoute('/posts_/$postId/deep')({ + loader: async ({ params: { postId }, context }) => { + const data = await context.queryClient.ensureQueryData( + postQueryOptions(postId), + ) + + return { + title: data.title, + } + }, + head: ({ loaderData }) => ({ + meta: loaderData ? [{ title: loaderData.title }] : undefined, + }), + errorComponent: PostErrorComponent, + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const { postId } = Route.useParams() + const postQuery = useSuspenseQuery(postQueryOptions(postId)) + + return ( +
+ + ← All Posts + +

{postQuery.data.title}

+
{postQuery.data.body}
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/redirect.tsx b/examples/solid/start-basic-solid-query/src/routes/redirect.tsx new file mode 100644 index 0000000000..ca017f0635 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/redirect.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, redirect } from '@tanstack/solid-router' + +export const Route = createFileRoute('/redirect')({ + beforeLoad: async () => { + throw redirect({ + to: '/posts', + }) + }, +}) diff --git a/examples/solid/start-basic-solid-query/src/routes/users.$userId.tsx b/examples/solid/start-basic-solid-query/src/routes/users.$userId.tsx new file mode 100644 index 0000000000..ad8a1a1209 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/users.$userId.tsx @@ -0,0 +1,33 @@ +import { createQuery } from '@tanstack/solid-query' +import { ErrorComponent, createFileRoute } from '@tanstack/solid-router' +import type { ErrorComponentProps } from '@tanstack/solid-router' +import { NotFound } from '~/components/NotFound' +import { userQueryOptions } from '~/utils/users' + +export const Route = createFileRoute('/users/$userId')({ + loader: async ({ context, params: { userId } }) => { + await context.queryClient.ensureQueryData(userQueryOptions(userId)) + }, + errorComponent: UserErrorComponent, + component: UserComponent, + notFoundComponent: () => { + return User not found + }, +}) + +export function UserErrorComponent({ error }: ErrorComponentProps) { + return +} + +function UserComponent() { + const params = Route.useParams() + const userQuery = createQuery(() => userQueryOptions(params().userId)) + const user = userQuery.data + + return ( +
+

{user?.name}

+
{user?.email}
+
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/routes/users.index.tsx b/examples/solid/start-basic-solid-query/src/routes/users.index.tsx new file mode 100644 index 0000000000..bbc96801a9 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/users.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/users/')({ + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return
Select a user.
+} diff --git a/examples/solid/start-basic-solid-query/src/routes/users.route.tsx b/examples/solid/start-basic-solid-query/src/routes/users.route.tsx new file mode 100644 index 0000000000..fa2edb30a5 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/routes/users.route.tsx @@ -0,0 +1,42 @@ +import { createQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' +import { usersQueryOptions } from '../utils/users' + +export const Route = createFileRoute('/users')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(usersQueryOptions()) + }, + component: UsersComponent, +}) + +function UsersComponent() { + const usersQuery = createQuery(() => usersQueryOptions()) + + return ( +
+
    + {[ + ...usersQuery.data!, + { id: 'i-do-not-exist', name: 'Non-existent User', email: '' }, + ].map((user) => { + return ( +
  • + +
    {user.name}
    + +
  • + ) + })} +
+
+ +
+ ) +} diff --git a/examples/solid/start-basic-solid-query/src/ssr.tsx b/examples/solid/start-basic-solid-query/src/ssr.tsx new file mode 100644 index 0000000000..ebd14c8120 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/ssr.tsx @@ -0,0 +1,13 @@ +/// +import { + createStartHandler, + defaultStreamHandler, +} from '@tanstack/solid-start/server' +import { getRouterManifest } from '@tanstack/solid-start/router-manifest' + +import { createRouter } from './router' + +export default createStartHandler({ + createRouter, + getRouterManifest, +})(defaultStreamHandler) diff --git a/examples/solid/start-basic-solid-query/src/styles/app.css b/examples/solid/start-basic-solid-query/src/styles/app.css new file mode 100644 index 0000000000..c53c870665 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/styles/app.css @@ -0,0 +1,22 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html { + color-scheme: light dark; + } + + * { + @apply border-gray-200 dark:border-gray-800; + } + + html, + body { + @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; + } + + .using-mouse * { + outline: none !important; + } +} diff --git a/examples/solid/start-basic-solid-query/src/utils/posts.tsx b/examples/solid/start-basic-solid-query/src/utils/posts.tsx new file mode 100644 index 0000000000..84091f451a --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/utils/posts.tsx @@ -0,0 +1,49 @@ +import { queryOptions } from '@tanstack/solid-query' +import { notFound } from '@tanstack/solid-router' +import { createServerFn } from '@tanstack/solid-start' +import axios from 'redaxios' + +export type PostType = { + id: string + title: string + body: string +} + +export const fetchPosts = createServerFn({ method: 'GET' }).handler( + async () => { + console.info('Fetching posts...') + return axios + .get>('https://jsonplaceholder.typicode.com/posts') + .then((r) => r.data.slice(0, 10)) + }, +) + +export const postsQueryOptions = () => + queryOptions({ + queryKey: ['posts'], + queryFn: () => fetchPosts(), + }) + +export const fetchPost = createServerFn({ method: 'GET' }) + .validator((d: string) => d) + .handler(async ({ data }) => { + console.info(`Fetching post with id ${data}...`) + const post = await axios + .get(`https://jsonplaceholder.typicode.com/posts/${data}`) + .then((r) => r.data) + .catch((err) => { + console.error(err) + if (err.status === 404) { + throw notFound() + } + throw err + }) + + return post + }) + +export const postQueryOptions = (postId: string) => + queryOptions({ + queryKey: ['post', postId], + queryFn: () => fetchPost({ data: postId }), + }) diff --git a/examples/solid/start-basic-solid-query/src/utils/seo.ts b/examples/solid/start-basic-solid-query/src/utils/seo.ts new file mode 100644 index 0000000000..d18ad84b74 --- /dev/null +++ b/examples/solid/start-basic-solid-query/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/solid/start-basic-solid-query/src/utils/users.tsx b/examples/solid/start-basic-solid-query/src/utils/users.tsx new file mode 100644 index 0000000000..a27f1bb142 --- /dev/null +++ b/examples/solid/start-basic-solid-query/src/utils/users.tsx @@ -0,0 +1,34 @@ +import { queryOptions } from '@tanstack/solid-query' +import axios from 'redaxios' + +export type User = { + id: number + name: string + email: string +} + +export const DEPLOY_URL = 'http://localhost:3000' + +export const usersQueryOptions = () => + queryOptions({ + queryKey: ['users'], + queryFn: () => + axios + .get>(DEPLOY_URL + '/api/users') + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch users') + }), + }) + +export const userQueryOptions = (id: string) => + queryOptions({ + queryKey: ['users', id], + queryFn: () => + axios + .get(DEPLOY_URL + '/api/users/' + id) + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch user') + }), + }) diff --git a/examples/solid/start-basic-solid-query/tailwind.config.mjs b/examples/solid/start-basic-solid-query/tailwind.config.mjs new file mode 100644 index 0000000000..e49f4eb776 --- /dev/null +++ b/examples/solid/start-basic-solid-query/tailwind.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], +} diff --git a/examples/solid/start-basic-solid-query/tsconfig.json b/examples/solid/start-basic-solid-query/tsconfig.json new file mode 100644 index 0000000000..a40235b863 --- /dev/null +++ b/examples/solid/start-basic-solid-query/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +} diff --git a/package.json b/package.json index aa7c8639b0..5d1474219a 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,8 @@ "@tanstack/history": "workspace:*", "@tanstack/router-core": "workspace:*", "@tanstack/react-router": "workspace:*", + "@tanstack/solid-router": "workspace:*", + "@tanstack/solid-router-with-query": "workspace:*", "@tanstack/router-cli": "workspace:*", "@tanstack/router-devtools": "workspace:*", "@tanstack/router-devtools-core": "workspace:^", diff --git a/packages/solid-router-with-query/README.md b/packages/solid-router-with-query/README.md new file mode 100644 index 0000000000..d83bf5fdf4 --- /dev/null +++ b/packages/solid-router-with-query/README.md @@ -0,0 +1,31 @@ + + +# TanStack React Router + +![TanStack Router Header](https://github.com/tanstack/router/raw/main/media/header.png) + +🤖 Type-safe router w/ built-in caching & URL state management for React! + + + #TanStack + + + + + + + + semantic-release + + Join the discussion on Github +Best of JS + + + + + + + +Enjoy this library? Try the entire [TanStack](https://tanstack.com)! [React Query](https://github.com/tannerlinsley/react-query), [React Table](https://github.com/tanstack/react-table), [React Charts](https://github.com/tannerlinsley/react-charts), [React Virtual](https://github.com/tannerlinsley/react-virtual) + +## Visit [tanstack.com/router](https://tanstack.com/router) for docs, guides, API and more! diff --git a/packages/solid-router-with-query/eslint.config.js b/packages/solid-router-with-query/eslint.config.js new file mode 100644 index 0000000000..bd7118fa1a --- /dev/null +++ b/packages/solid-router-with-query/eslint.config.js @@ -0,0 +1,20 @@ +// @ts-check + +import rootConfig from '../../eslint.config.js' + +export default [ + ...rootConfig, + { + files: ['**/*.{ts,tsx}'], + }, + { + plugins: {}, + rules: {}, + }, + { + files: ['**/__tests__/**'], + rules: { + '@typescript-eslint/no-unnecessary-condition': 'off', + }, + }, +] diff --git a/packages/solid-router-with-query/package.json b/packages/solid-router-with-query/package.json new file mode 100644 index 0000000000..0036794a23 --- /dev/null +++ b/packages/solid-router-with-query/package.json @@ -0,0 +1,79 @@ +{ + "name": "@tanstack/solid-router-with-query", + "version": "1.114.29", + "description": "Modern and scalable routing for React applications", + "author": "Tanner Linsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/router.git", + "directory": "packages/solid-router-with-query" + }, + "homepage": "https://tanstack.com/router", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "keywords": [ + "solid", + "location", + "router", + "routing", + "async", + "async router", + "typescript" + ], + "scripts": { + "clean": "rimraf ./dist && rimraf ./coverage", + "test:eslint": "eslint ./src", + "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", + "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js", + "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js", + "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": "tsc", + "test:unit": "vitest", + "test:unit:dev": "pnpm run test:unit --watch", + "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", + "build": "vite build" + }, + "type": "module", + "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "engines": { + "node": ">=12" + }, + "devDependencies": { + "vite-plugin-solid": "^2.11.6", + "solid-js": ">=1.9.5", + "@tanstack/router-core": "workspace:^", + "@tanstack/solid-router": "workspace:^", + "@tanstack/solid-query": ">=5.66.0" + }, + "peerDependencies": { + "solid-js": ">=1.9.5", + "@tanstack/router-core": ">=1.114.7", + "@tanstack/solid-router": ">=1.43.2", + "@tanstack/solid-query": ">=5.49.2" + } +} diff --git a/packages/solid-router-with-query/src/index.tsx b/packages/solid-router-with-query/src/index.tsx new file mode 100644 index 0000000000..4601542ea1 --- /dev/null +++ b/packages/solid-router-with-query/src/index.tsx @@ -0,0 +1,215 @@ +import { + QueryClientProvider, + dehydrate, + hashKey, + hydrate, +} from '@tanstack/solid-query' +import { isRedirect } from '@tanstack/router-core' +import type * as Solid from 'solid-js' +import type { AnyRouter } from '@tanstack/solid-router' +import type { + CreateQueryOptions, + QueryClient, + QueryKey, + QueryObserverResult, +} from '@tanstack/solid-query' + +// Extended query options to include the properties used in this file +interface ExtendedQueryOptions extends CreateQueryOptions { + queryKey: QueryKey + queryKeyHashFn?: (queryKey: QueryKey) => string + __skipInjection?: boolean +} + +type AdditionalOptions = { + WrapProvider?: (props: { children: any }) => Solid.JSX.Element + /** + * If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries. + * + * @default true + * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction) + */ + handleRedirects?: boolean +} + +export type ValidateRouter = + NonNullable extends { + queryClient: QueryClient + } + ? TRouter + : never + +export function routerWithQueryClient( + router: ValidateRouter, + queryClient: QueryClient, + additionalOpts?: AdditionalOptions, +): TRouter { + const seenQueryKeys = new Set() + const streamedQueryKeys = new Set() + + const ogClientOptions = queryClient.getDefaultOptions() + queryClient.setDefaultOptions({ + ...ogClientOptions, + queries: { + ...ogClientOptions.queries, + _experimental_beforeQuery: (options: CreateQueryOptions) => { + // Call the original beforeQuery + ;(ogClientOptions.queries as any)?._experimental_beforeQuery?.(options) + + const extOptions = options as ExtendedQueryOptions + const hash = extOptions.queryKeyHashFn || hashKey + // On the server, check if we've already seen the query before + if (router.isServer) { + if (seenQueryKeys.has(hash(extOptions.queryKey))) { + return + } + + seenQueryKeys.add(hash(extOptions.queryKey)) + + // If we haven't seen the query and we have data for it, + // That means it's going to get dehydrated with critical + // data, so we can skip the injection + if (queryClient.getQueryData(extOptions.queryKey) !== undefined) { + extOptions.__skipInjection = true + return + } + } else { + // On the client, pick up the deferred data from the stream + const dehydratedClient = router.clientSsr!.getStreamedValue( + '__QueryClient__' + hash(extOptions.queryKey), + ) + + // If we have data, hydrate it into the query client + if (dehydratedClient && !dehydratedClient.hydrated) { + dehydratedClient.hydrated = true + hydrate(queryClient, dehydratedClient) + } + } + }, + _experimental_afterQuery: ( + options: CreateQueryOptions, + _result: QueryObserverResult, + ) => { + // On the server (if we're not skipping injection) + // send down the dehydrated query + const extOptions = options as ExtendedQueryOptions + const hash = extOptions.queryKeyHashFn || hashKey + if ( + router.isServer && + !extOptions.__skipInjection && + queryClient.getQueryData(extOptions.queryKey) !== undefined && + !streamedQueryKeys.has(hash(extOptions.queryKey)) + ) { + streamedQueryKeys.add(hash(extOptions.queryKey)) + + router.serverSsr!.streamValue( + '__QueryClient__' + hash(extOptions.queryKey), + dehydrate(queryClient, { + shouldDehydrateMutation: () => false, + shouldDehydrateQuery: (query) => + hash(query.queryKey) === hash(extOptions.queryKey), + }), + ) + } + + // Call the original afterQuery + ;(ogClientOptions.queries as any)?._experimental_afterQuery?.( + options, + _result, + ) + }, + } as any, + }) + + if (additionalOpts?.handleRedirects ?? true) { + const ogMutationCacheConfig = queryClient.getMutationCache().config + queryClient.getMutationCache().config = { + ...ogMutationCacheConfig, + onError: (error, _variables, _context, _mutation) => { + if (isRedirect(error)) { + return router.navigate( + router.resolveRedirect({ + ...error, + _fromLocation: router.state.location, + }), + ) + } + + return ogMutationCacheConfig.onError?.( + error, + _variables, + _context, + _mutation, + ) + }, + } + + const ogQueryCacheConfig = queryClient.getQueryCache().config + queryClient.getQueryCache().config = { + ...ogQueryCacheConfig, + onError: (error, _query) => { + if (isRedirect(error)) { + return router.navigate( + router.resolveRedirect({ + ...error, + _fromLocation: router.state.location, + }), + ) + } + + return ogQueryCacheConfig.onError?.(error, _query) + }, + } + } + + const ogOptions = router.options + router.options = { + ...router.options, + dehydrate: () => { + return { + ...ogOptions.dehydrate?.(), + // When critical data is dehydrated, we also dehydrate the query client + dehydratedQueryClient: dehydrate(queryClient), + } + }, + hydrate: (dehydrated: any) => { + ogOptions.hydrate?.(dehydrated) + // On the client, hydrate the query client with the dehydrated data + hydrate(queryClient, dehydrated.dehydratedQueryClient) + }, + context: { + ...ogOptions.context, + // Pass the query client to the context, so we can access it in loaders + queryClient, + }, + // Wrap the app in a QueryClientProvider + Wrap: ({ children }) => { + const OuterWrapper = additionalOpts?.WrapProvider + return ( + <> + {OuterWrapper ? ( + + + {ogOptions.Wrap ? ( + {children} + ) : ( + children + )} + + + ) : ( + + {ogOptions.Wrap ? ( + {children} + ) : ( + children + )} + + )} + + ) + }, + } + + return router +} diff --git a/packages/solid-router-with-query/tests/index.test-d.ts b/packages/solid-router-with-query/tests/index.test-d.ts new file mode 100644 index 0000000000..a6a220526a --- /dev/null +++ b/packages/solid-router-with-query/tests/index.test-d.ts @@ -0,0 +1,55 @@ +import { QueryClient } from '@tanstack/solid-query' +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/solid-router' + +import { expectTypeOf, test } from 'vitest' + +import { routerWithQueryClient } from '../src' + +test('basic { queryClient } context', () => { + const root = createRootRouteWithContext<{ + queryClient: QueryClient + }>()({}) + + const queryClient = new QueryClient() + const router = createRouter({ + context: { queryClient }, + routeTree: root, + }) + + const routerWithQuery = routerWithQueryClient(router, queryClient) + expectTypeOf(routerWithQuery).toEqualTypeOf(router) +}) + +test('no context fails', () => { + const root = createRootRouteWithContext()({}) + + const queryClient = new QueryClient() + const router = createRouter({ + routeTree: root, + }) + + routerWithQueryClient( + // @ts-expect-error - QueryClient must be in context type + router, + queryClient, + ) +}) + +test('allows additional props on context', () => { + const root = createRootRouteWithContext<{ + queryClient: QueryClient + extra: string + }>()({}) + + const queryClient = new QueryClient() + const router = createRouter({ + context: { queryClient, extra: 'extra' }, + routeTree: root, + }) + + const routerWithQuery = routerWithQueryClient(router, queryClient) + expectTypeOf(routerWithQuery).toEqualTypeOf(router) +}) diff --git a/packages/solid-router-with-query/tsconfig.json b/packages/solid-router-with-query/tsconfig.json new file mode 100644 index 0000000000..e24672b5de --- /dev/null +++ b/packages/solid-router-with-query/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js" + }, + "include": ["src", "tests", "vite.config.ts"] +} diff --git a/packages/solid-router-with-query/vite.config.ts b/packages/solid-router-with-query/vite.config.ts new file mode 100644 index 0000000000..b78cdf1373 --- /dev/null +++ b/packages/solid-router-with-query/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/config/vite' +import solid from 'vite-plugin-solid' +import packageJson from './package.json' +import type { UserConfig } from 'vitest/config' + +const config = defineConfig({ + plugins: [solid()] as UserConfig['plugins'], + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'jsdom', + typecheck: { enabled: true }, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: './src/index.tsx', + srcDir: './src', + }), +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a505dbe86..a7870eec6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,8 @@ overrides: '@tanstack/history': workspace:* '@tanstack/router-core': workspace:* '@tanstack/react-router': workspace:* + '@tanstack/solid-router': workspace:* + '@tanstack/solid-router-with-query': workspace:* '@tanstack/router-cli': workspace:* '@tanstack/router-devtools': workspace:* '@tanstack/router-devtools-core': workspace:^ @@ -1498,7 +1500,7 @@ importers: e2e/solid-router/basic: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1538,7 +1540,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1575,7 +1577,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1624,7 +1626,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1661,7 +1663,7 @@ importers: e2e/solid-router/basic-scroll-restoration: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1707,7 +1709,7 @@ importers: specifier: ^5.66.0 version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1753,7 +1755,7 @@ importers: specifier: ^5.66.0 version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1796,7 +1798,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1842,7 +1844,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1885,7 +1887,7 @@ importers: e2e/solid-router/rspack-basic-file-based: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1931,7 +1933,7 @@ importers: e2e/solid-router/rspack-basic-virtual-named-export-config-file-based: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -1983,7 +1985,7 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -2026,7 +2028,7 @@ importers: e2e/solid-start/basic: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -2081,10 +2083,71 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.2)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) - e2e/solid-start/basic-tsr-config: + e2e/solid-start/basic-solid-query: dependencies: + '@tanstack/solid-query': + specifier: ^5.66.0 + version: 5.66.0(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.66.0 + version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': + specifier: workspace:* + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + '@tanstack/solid-router-with-query': + specifier: workspace:* + version: link:../../../packages/solid-router-with-query + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + vinxi: + specifier: 0.5.3 + version: 0.5.3(@types/node@22.13.4)(db0@0.2.3)(ioredis@5.4.2)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(typescript@5.8.2)(yaml@2.7.0) + devDependencies: + '@playwright/test': + specifier: ^1.50.1 + version: 1.50.1 + '@tanstack/router-e2e-utils': specifier: workspace:^ + version: link:../../e2e-utils + '@types/node': + specifier: ^22.10.2 + version: 22.13.4 + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + postcss: + specifier: ^8.5.1 + version: 8.5.3 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + typescript: + specifier: ^5.7.2 + version: 5.8.2 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.2)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + + e2e/solid-start/basic-tsr-config: + dependencies: + '@tanstack/solid-router': + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -2112,7 +2175,7 @@ importers: e2e/solid-start/scroll-restoration: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -2173,7 +2236,7 @@ importers: e2e/solid-start/server-functions: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -2237,7 +2300,7 @@ importers: e2e/solid-start/website: dependencies: '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5191,7 +5254,7 @@ importers: examples/solid/basic: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5231,7 +5294,7 @@ importers: examples/solid/basic-devtools-panel: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5265,7 +5328,7 @@ importers: examples/solid/basic-file-based: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5305,7 +5368,7 @@ importers: examples/solid/basic-non-nested-devtools: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5351,7 +5414,7 @@ importers: specifier: ^5.66.0 version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5394,7 +5457,7 @@ importers: specifier: ^5.66.0 version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5434,7 +5497,7 @@ importers: examples/solid/kitchen-sink-file-based: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5477,7 +5540,7 @@ importers: examples/solid/quickstart-file-based: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5517,7 +5580,7 @@ importers: examples/solid/start-bare: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5566,7 +5629,7 @@ importers: examples/solid/start-basic: dependencies: '@tanstack/solid-router': - specifier: ^1.114.29 + specifier: workspace:* version: link:../../../packages/solid-router '@tanstack/solid-router-devtools': specifier: workspace:^ @@ -5609,6 +5672,58 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.2)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + examples/solid/start-basic-solid-query: + dependencies: + '@tanstack/solid-query': + specifier: ^5.66.0 + version: 5.66.0(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.66.0 + version: 5.66.0(@tanstack/solid-query@5.66.0(solid-js@1.9.5))(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: workspace:* + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + '@tanstack/solid-router-with-query': + specifier: workspace:* + version: link:../../../packages/solid-router-with-query + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + vinxi: + specifier: 0.5.3 + version: 0.5.3(@types/node@22.13.4)(db0@0.2.3)(ioredis@5.4.2)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(typescript@5.8.2)(yaml@2.7.0) + devDependencies: + '@types/node': + specifier: ^22.5.4 + version: 22.13.4 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + postcss: + specifier: ^8.5.1 + version: 8.5.3 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + typescript: + specifier: ^5.7.2 + version: 5.8.2 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.2)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + packages/arktype-adapter: devDependencies: '@tanstack/react-router': @@ -6525,7 +6640,7 @@ importers: specifier: workspace:^ version: link:../router-devtools-core '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../solid-router solid-js: specifier: ^1.9.5 @@ -6535,6 +6650,24 @@ importers: specifier: ^2.11.6 version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + packages/solid-router-with-query: + devDependencies: + '@tanstack/router-core': + specifier: workspace:* + version: link:../router-core + '@tanstack/solid-query': + specifier: '>=5.66.0' + version: 5.66.0(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: workspace:* + version: link:../solid-router + solid-js: + specifier: '>=1.9.5' + version: 1.9.5 + vite-plugin-solid: + specifier: ^2.11.6 + version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + packages/solid-start: dependencies: '@tanstack/solid-start-client': @@ -6581,7 +6714,7 @@ importers: specifier: workspace:* version: link:../router-core '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../solid-router '@tanstack/start-client-core': specifier: workspace:* @@ -6740,7 +6873,7 @@ importers: specifier: workspace:* version: link:../router-core '@tanstack/solid-router': - specifier: workspace:^ + specifier: workspace:* version: link:../solid-router '@tanstack/start-client-core': specifier: workspace:* diff --git a/scripts/publish.js b/scripts/publish.js index 1d55ca6ddc..e1f9c8b3f1 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -28,6 +28,10 @@ await publish({ name: '@tanstack/react-router-with-query', packageDir: 'packages/react-router-with-query', }, + { + name: '@tanstack/solid-router-with-query', + packageDir: 'packages/solid-router-with-query', + }, { name: '@tanstack/zod-adapter', packageDir: 'packages/zod-adapter',