From 536007e971f97136a3394f32ecc768b7f89fe5e8 Mon Sep 17 00:00:00 2001 From: Ben McGinnis Date: Fri, 18 Oct 2024 16:38:05 -0400 Subject: [PATCH 1/5] feat(plugin-router): add esbuild plugin Addresses #2576 by adding an esbuild plugin to the router-plugin package. Duplicated the basic-file-based e2e test to verify validate the behavior, and replaces the vite config with an esbuild config as well as replacing the vite scripts with esbuild scripts. tested by running: pnpm run test:e2e from within the e2e/react-router/basic-esbuild-file-based directory. --- .../basic-esbuild-file-based/.gitignore | 10 + .../basic-esbuild-file-based/index.html | 24 + .../basic-esbuild-file-based/package.json | 28 ++ .../playwright.config.ts | 29 ++ .../src/esbuild.config.js | 11 + .../basic-esbuild-file-based/src/main.tsx | 25 ++ .../basic-esbuild-file-based/src/posts.tsx | 32 ++ .../src/routeTree.gen.ts | 424 ++++++++++++++++++ .../src/routes/(group)/_layout.inside.tsx | 25 ++ .../src/routes/(group)/_layout.tsx | 10 + .../src/routes/(group)/lazyinside.lazy.tsx | 24 + .../src/routes/(group)/lazyinside.tsx | 9 + .../src/routes/__root.tsx | 82 ++++ .../src/routes/_layout.tsx | 16 + .../src/routes/_layout/_layout-2.tsx | 34 ++ .../src/routes/_layout/_layout-2/layout-a.tsx | 9 + .../src/routes/_layout/_layout-2/layout-b.tsx | 9 + .../src/routes/index.tsx | 14 + .../src/routes/posts.$postId.tsx | 28 ++ .../src/routes/posts.index.tsx | 10 + .../src/routes/posts.tsx | 39 ++ .../tests/app.spec.ts | 48 ++ .../basic-esbuild-file-based/tsconfig.json | 8 + packages/router-plugin/package.json | 12 +- packages/router-plugin/src/esbuild.ts | 56 +++ pnpm-lock.yaml | 40 ++ 26 files changed, 1055 insertions(+), 1 deletion(-) create mode 100644 e2e/react-router/basic-esbuild-file-based/.gitignore create mode 100644 e2e/react-router/basic-esbuild-file-based/index.html create mode 100644 e2e/react-router/basic-esbuild-file-based/package.json create mode 100644 e2e/react-router/basic-esbuild-file-based/playwright.config.ts create mode 100644 e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js create mode 100644 e2e/react-router/basic-esbuild-file-based/src/main.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/posts.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routeTree.gen.ts create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.inside.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.lazy.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/__root.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/_layout.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-a.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-b.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/index.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/posts.$postId.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/posts.index.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/src/routes/posts.tsx create mode 100644 e2e/react-router/basic-esbuild-file-based/tests/app.spec.ts create mode 100644 e2e/react-router/basic-esbuild-file-based/tsconfig.json create mode 100644 packages/router-plugin/src/esbuild.ts diff --git a/e2e/react-router/basic-esbuild-file-based/.gitignore b/e2e/react-router/basic-esbuild-file-based/.gitignore new file mode 100644 index 0000000000..a6ea47e508 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/.gitignore @@ -0,0 +1,10 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/e2e/react-router/basic-esbuild-file-based/index.html b/e2e/react-router/basic-esbuild-file-based/index.html new file mode 100644 index 0000000000..292e03a54c --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/index.html @@ -0,0 +1,24 @@ + + + + + + Vite App + + + + +
+ + + diff --git a/e2e/react-router/basic-esbuild-file-based/package.json b/e2e/react-router/basic-esbuild-file-based/package.json new file mode 100644 index 0000000000..a7899cee67 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-router-e2e-react-basic-file-based", + "private": true, + "type": "module", + "scripts": { + "dev": "esbuild src/main.tsx --serve=3001 --bundle --outfile=dist/main.js --watch --servedir=.", + "build": "esbuild src/main.tsx --bundle --outfile=dist/main.js", + "serve": "esbuild src/main.tsx --bundle --outfile=dist/main.js --servedir=.", + "start": "dev", + "test:e2e": "playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/router-devtools": "workspace:^", + "@tanstack/router-plugin": "workspace:^", + "@tanstack/router-zod-adapter": "workspace:^", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "redaxios": "^0.5.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@playwright/test": "^1.48.0", + "@types/react": "^18.2.47", + "@types/react-dom": "^18.2.18", + "esbuild": "^0.23.1" + } +} \ No newline at end of file diff --git a/e2e/react-router/basic-esbuild-file-based/playwright.config.ts b/e2e/react-router/basic-esbuild-file-based/playwright.config.ts new file mode 100644 index 0000000000..f83eacf129 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/playwright.config.ts @@ -0,0 +1,29 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + + reporter: [['line']], + + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:3001/', + }, + + webServer: { + command: 'pnpm run dev', + url: 'http://localhost:3001', + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js new file mode 100644 index 0000000000..544de2c03d --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js @@ -0,0 +1,11 @@ +import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; + +export default { + jsx: 'transform', + minify: true, + sourcemap: true, + bundle: true, + format: 'esm', + target: ['esnext'], + plugins: [TanStackRouterEsbuild()] +} \ No newline at end of file diff --git a/e2e/react-router/basic-esbuild-file-based/src/main.tsx b/e2e/react-router/basic-esbuild-file-based/src/main.tsx new file mode 100644 index 0000000000..18b1b603f8 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/main.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +// Set up a Router instance +const router = createRouter({ + routeTree, + defaultPreload: 'intent', + defaultStaleTime: 5000, +}) + +// Register things for typesafety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const rootElement = document.getElementById('app')! + +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render() +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/posts.tsx b/e2e/react-router/basic-esbuild-file-based/src/posts.tsx new file mode 100644 index 0000000000..3ccf1ff421 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/posts.tsx @@ -0,0 +1,32 @@ +import { notFound } from '@tanstack/react-router' +import axios from 'redaxios' + +export type PostType = { + id: string + title: string + body: string +} + +export const fetchPost = async (postId: string) => { + console.info(`Fetching post with id ${postId}...`) + await new Promise((r) => setTimeout(r, 500)) + const post = await axios + .get(`https://jsonplaceholder.typicode.com/posts/${postId}`) + .then((r) => r.data) + .catch((err) => { + if (err.status === 404) { + throw notFound() + } + throw err + }) + + return post +} + +export const fetchPosts = async () => { + console.info('Fetching posts...') + await new Promise((r) => setTimeout(r, 500)) + return axios + .get>('https://jsonplaceholder.typicode.com/posts') + .then((r) => r.data.slice(0, 10)) +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-esbuild-file-based/src/routeTree.gen.ts new file mode 100644 index 0000000000..095f3f6619 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routeTree.gen.ts @@ -0,0 +1,424 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +import { createFileRoute } from '@tanstack/react-router' + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as PostsImport } from './routes/posts' +import { Route as LayoutImport } from './routes/_layout' +import { Route as IndexImport } from './routes/index' +import { Route as PostsIndexImport } from './routes/posts.index' +import { Route as PostsPostIdImport } from './routes/posts.$postId' +import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2' +import { Route as groupLazyinsideImport } from './routes/(group)/lazyinside' +import { Route as groupLayoutImport } from './routes/(group)/_layout' +import { Route as LayoutLayout2LayoutBImport } from './routes/_layout/_layout-2/layout-b' +import { Route as LayoutLayout2LayoutAImport } from './routes/_layout/_layout-2/layout-a' +import { Route as groupLayoutInsideImport } from './routes/(group)/_layout.inside' + +// Create Virtual Routes + +const groupImport = createFileRoute('/(group)')() + +// Create/Update Routes + +const groupRoute = groupImport.update({ + id: '/(group)', + getParentRoute: () => rootRoute, +} as any) + +const PostsRoute = PostsImport.update({ + path: '/posts', + getParentRoute: () => rootRoute, +} as any) + +const LayoutRoute = LayoutImport.update({ + id: '/_layout', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const PostsIndexRoute = PostsIndexImport.update({ + path: '/', + getParentRoute: () => PostsRoute, +} as any) + +const PostsPostIdRoute = PostsPostIdImport.update({ + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) + +const LayoutLayout2Route = LayoutLayout2Import.update({ + id: '/_layout-2', + getParentRoute: () => LayoutRoute, +} as any) + +const groupLazyinsideRoute = groupLazyinsideImport + .update({ + path: '/lazyinside', + getParentRoute: () => groupRoute, + } as any) + .lazy(() => import('./routes/(group)/lazyinside.lazy').then((d) => d.Route)) + +const groupLayoutRoute = groupLayoutImport.update({ + id: '/_layout', + getParentRoute: () => groupRoute, +} as any) + +const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBImport.update({ + path: '/layout-b', + getParentRoute: () => LayoutLayout2Route, +} as any) + +const LayoutLayout2LayoutARoute = LayoutLayout2LayoutAImport.update({ + path: '/layout-a', + getParentRoute: () => LayoutLayout2Route, +} as any) + +const groupLayoutInsideRoute = groupLayoutInsideImport.update({ + path: '/inside', + getParentRoute: () => groupLayoutRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutImport + parentRoute: typeof rootRoute + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsImport + parentRoute: typeof rootRoute + } + '/(group)': { + id: '/(group)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupImport + parentRoute: typeof rootRoute + } + '/(group)/_layout': { + id: '/(group)/_layout' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupLayoutImport + parentRoute: typeof groupRoute + } + '/(group)/lazyinside': { + id: '/(group)/lazyinside' + path: '/lazyinside' + fullPath: '/lazyinside' + preLoaderRoute: typeof groupLazyinsideImport + parentRoute: typeof groupImport + } + '/_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 + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexImport + parentRoute: typeof PostsImport + } + '/(group)/_layout/inside': { + id: '/(group)/_layout/inside' + path: '/inside' + fullPath: '/inside' + preLoaderRoute: typeof groupLayoutInsideImport + parentRoute: typeof groupLayoutImport + } + '/_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 + } + } +} + +// 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 groupLayoutRouteChildren { + groupLayoutInsideRoute: typeof groupLayoutInsideRoute +} + +const groupLayoutRouteChildren: groupLayoutRouteChildren = { + groupLayoutInsideRoute: groupLayoutInsideRoute, +} + +const groupLayoutRouteWithChildren = groupLayoutRoute._addFileChildren( + groupLayoutRouteChildren, +) + +interface groupRouteChildren { + groupLayoutRoute: typeof groupLayoutRouteWithChildren + groupLazyinsideRoute: typeof groupLazyinsideRoute +} + +const groupRouteChildren: groupRouteChildren = { + groupLayoutRoute: groupLayoutRouteWithChildren, + groupLazyinsideRoute: groupLazyinsideRoute, +} + +const groupRouteWithChildren = groupRoute._addFileChildren(groupRouteChildren) + +export interface FileRoutesByFullPath { + '/': typeof groupLayoutRouteWithChildren + '': typeof LayoutLayout2RouteWithChildren + '/posts': typeof PostsRouteWithChildren + '/lazyinside': typeof groupLazyinsideRoute + '/posts/$postId': typeof PostsPostIdRoute + '/posts/': typeof PostsIndexRoute + '/inside': typeof groupLayoutInsideRoute + '/layout-a': typeof LayoutLayout2LayoutARoute + '/layout-b': typeof LayoutLayout2LayoutBRoute +} + +export interface FileRoutesByTo { + '/': typeof groupLayoutRouteWithChildren + '': typeof LayoutLayout2RouteWithChildren + '/lazyinside': typeof groupLazyinsideRoute + '/posts/$postId': typeof PostsPostIdRoute + '/posts': typeof PostsIndexRoute + '/inside': typeof groupLayoutInsideRoute + '/layout-a': typeof LayoutLayout2LayoutARoute + '/layout-b': typeof LayoutLayout2LayoutBRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/_layout': typeof LayoutRouteWithChildren + '/posts': typeof PostsRouteWithChildren + '/(group)': typeof groupRouteWithChildren + '/(group)/_layout': typeof groupLayoutRouteWithChildren + '/(group)/lazyinside': typeof groupLazyinsideRoute + '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/posts/': typeof PostsIndexRoute + '/(group)/_layout/inside': typeof groupLayoutInsideRoute + '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute + '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '' + | '/posts' + | '/lazyinside' + | '/posts/$postId' + | '/posts/' + | '/inside' + | '/layout-a' + | '/layout-b' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '' + | '/lazyinside' + | '/posts/$postId' + | '/posts' + | '/inside' + | '/layout-a' + | '/layout-b' + id: + | '__root__' + | '/' + | '/_layout' + | '/posts' + | '/(group)' + | '/(group)/_layout' + | '/(group)/lazyinside' + | '/_layout/_layout-2' + | '/posts/$postId' + | '/posts/' + | '/(group)/_layout/inside' + | '/_layout/_layout-2/layout-a' + | '/_layout/_layout-2/layout-b' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + LayoutRoute: typeof LayoutRouteWithChildren + PostsRoute: typeof PostsRouteWithChildren + groupRoute: typeof groupRouteWithChildren +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + LayoutRoute: LayoutRouteWithChildren, + PostsRoute: PostsRouteWithChildren, + groupRoute: groupRouteWithChildren, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* prettier-ignore-end */ + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/_layout", + "/posts", + "/(group)" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/_layout": { + "filePath": "_layout.tsx", + "children": [ + "/_layout/_layout-2" + ] + }, + "/posts": { + "filePath": "posts.tsx", + "children": [ + "/posts/$postId", + "/posts/" + ] + }, + "/(group)": { + "filePath": "(group)", + "children": [ + "/(group)/_layout", + "/(group)/lazyinside" + ] + }, + "/(group)/_layout": { + "filePath": "(group)/_layout.tsx", + "parent": "/(group)", + "children": [ + "/(group)/_layout/inside" + ] + }, + "/(group)/lazyinside": { + "filePath": "(group)/lazyinside.tsx", + "parent": "/(group)" + }, + "/_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" + }, + "/posts/": { + "filePath": "posts.index.tsx", + "parent": "/posts" + }, + "/(group)/_layout/inside": { + "filePath": "(group)/_layout.inside.tsx", + "parent": "/(group)/_layout" + }, + "/_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" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.inside.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.inside.tsx new file mode 100644 index 0000000000..8b5f257d77 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.inside.tsx @@ -0,0 +1,25 @@ +import { createFileRoute, getRouteApi, useSearch } from '@tanstack/react-router' +import { z } from 'zod' +import { zodSearchValidator } from '@tanstack/router-zod-adapter' + +const routeApi = getRouteApi('/(group)/_layout/inside') + +export const Route = createFileRoute('/(group)/_layout/inside')({ + validateSearch: zodSearchValidator( + z.object({ hello: z.string().optional() }), + ), + component: () => { + const searchViaHook = useSearch({ from: '/(group)/_layout/inside' }) + const searchViaRouteHook = Route.useSearch() + const searchViaRouteApi = routeApi.useSearch() + return ( + <> +
{searchViaHook.hello}
+
+ {searchViaRouteHook.hello} +
+
{searchViaRouteApi.hello}
+ + ) + }, +}) diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.tsx new file mode 100644 index 0000000000..4b755791c0 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/_layout.tsx @@ -0,0 +1,10 @@ +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/(group)/_layout')({ + component: () => ( + <> +
/(group)/_layout!
+ + + ), +}) diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.lazy.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.lazy.tsx new file mode 100644 index 0000000000..527be4f520 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.lazy.tsx @@ -0,0 +1,24 @@ +import { + createLazyFileRoute, + getRouteApi, + useSearch, +} from '@tanstack/react-router' + +const routeApi = getRouteApi('/(group)/lazyinside') + +export const Route = createLazyFileRoute('/(group)/lazyinside')({ + component: () => { + const searchViaHook = useSearch({ from: '/(group)/lazyinside' }) + const searchViaRouteHook = Route.useSearch() + const searchViaRouteApi = routeApi.useSearch() + return ( + <> +
{searchViaHook.hello}
+
+ {searchViaRouteHook.hello} +
+
{searchViaRouteApi.hello}
+ + ) + }, +}) diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.tsx new file mode 100644 index 0000000000..d0803ef20a --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/(group)/lazyinside.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' +import { z } from 'zod' +import { zodSearchValidator } from '@tanstack/router-zod-adapter' + +export const Route = createFileRoute('/(group)/lazyinside')({ + validateSearch: zodSearchValidator( + z.object({ hello: z.string().optional() }), + ), +}) diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/__root.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/__root.tsx new file mode 100644 index 0000000000..2499f017ba --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/__root.tsx @@ -0,0 +1,82 @@ +import * as React from 'react' +import { Link, Outlet, createRootRoute } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' + +export const Route = createRootRoute({ + component: RootComponent, + notFoundComponent: () => { + return ( +
+

This is the notFoundComponent configured on root route

+ Start Over +
+ ) + }, +}) + +function RootComponent() { + return ( + <> +
+ + Home + {' '} + + Posts + {' '} + + Layout + {' '} + + Inside Group + {' '} + + Lazy Inside Group + {' '} + + This Route Does Not Exist + +
+
+ + {/* Start rendering router matches */} + + + ) +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/_layout.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout.tsx new file mode 100644 index 0000000000..02ddbb1cd9 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout.tsx @@ -0,0 +1,16 @@ +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout')({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( +
+
I'm a layout
+
+ +
+
+ ) +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2.tsx new file mode 100644 index 0000000000..3b7dbf2903 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2.tsx @@ -0,0 +1,34 @@ +import { Link, Outlet, createFileRoute } from '@tanstack/react-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/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-a.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-a.tsx new file mode 100644 index 0000000000..61e19b4d9f --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-a.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_layout-2/layout-a')({ + component: LayoutAComponent, +}) + +function LayoutAComponent() { + return
I'm layout A!
+} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-b.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-b.tsx new file mode 100644 index 0000000000..cceed1fb9a --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/_layout/_layout-2/layout-b.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_layout-2/layout-b')({ + component: LayoutBComponent, +}) + +function LayoutBComponent() { + return
I'm layout B!
+} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/index.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/index.tsx new file mode 100644 index 0000000000..eac82a9174 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/index.tsx @@ -0,0 +1,14 @@ +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Welcome Home!

+
+ ) +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/posts.$postId.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.$postId.tsx new file mode 100644 index 0000000000..febb02ab2f --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.$postId.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' +import { ErrorComponent, createFileRoute } from '@tanstack/react-router' +import { fetchPost } from '../posts' +import type { ErrorComponentProps } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId } }) => fetchPost(postId), + errorComponent: PostErrorComponent as any, + notFoundComponent: () => { + return

Post not found

+ }, + component: PostComponent, +}) + +export function PostErrorComponent({ error }: ErrorComponentProps) { + return +} + +function PostComponent() { + const post = Route.useLoaderData() + + return ( +
+

{post.title}

+
{post.body}
+
+ ) +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/posts.index.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.index.tsx new file mode 100644 index 0000000000..056433ca0a --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.index.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Select a post.
+} diff --git a/e2e/react-router/basic-esbuild-file-based/src/routes/posts.tsx b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.tsx new file mode 100644 index 0000000000..c7a09ed7f8 --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/src/routes/posts.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' +import { fetchPosts } from '../posts' + +export const Route = createFileRoute('/posts')({ + loader: fetchPosts, + component: PostsComponent, +}) + +function PostsComponent() { + const posts = Route.useLoaderData() + + return ( +
+
    + {[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map( + (post) => { + return ( +
  • + +
    {post.title.substring(0, 20)}
    + +
  • + ) + }, + )} +
+
+ +
+ ) +} diff --git a/e2e/react-router/basic-esbuild-file-based/tests/app.spec.ts b/e2e/react-router/basic-esbuild-file-based/tests/app.spec.ts new file mode 100644 index 0000000000..7b322a8b1d --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/tests/app.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test' + +test.beforeEach(async ({ page }) => { + await page.goto('/') +}) + +test('Navigating to a post page', async ({ page }) => { + await page.getByRole('link', { name: 'Posts' }).click() + await page.getByRole('link', { name: 'sunt aut facere repe' }).click() + await expect(page.getByRole('heading')).toContainText('sunt aut facere') +}) + +test('Navigating nested layouts', async ({ page }) => { + await page.goto('/') + await page.getByRole('link', { name: 'Layout', exact: true }).click() + + await expect(page.locator('#app')).toContainText("I'm a layout") + await expect(page.locator('#app')).toContainText("I'm a nested layout") + + await page.getByRole('link', { name: 'Layout A' }).click() + await expect(page.locator('#app')).toContainText("I'm layout A!") + + await page.getByRole('link', { name: 'Layout B' }).click() + await expect(page.locator('#app')).toContainText("I'm layout B!") +}) + +test('Navigating to a not-found route', async ({ page }) => { + await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() + await expect(page.getByRole('paragraph')).toContainText( + 'This is the notFoundComponent configured on root route', + ) + await page.getByRole('link', { name: 'Start Over' }).click() + await expect(page.getByRole('heading')).toContainText('Welcome Home!') +}) + +test('Navigating to a route inside a route group', async ({ page }) => { + await page.getByTestId('link-to-route-inside-group').click() + await expect(page.getByTestId('search-via-hook')).toContainText('world') + await expect(page.getByTestId('search-via-route-hook')).toContainText('world') + await expect(page.getByTestId('search-via-route-api')).toContainText('world') +}) + +test('Navigating to a lazy route inside a route group', async ({ page }) => { + await page.getByTestId('link-to-lazy-route-inside-group').click() + await expect(page.getByTestId('search-via-hook')).toContainText('world') + await expect(page.getByTestId('search-via-route-hook')).toContainText('world') + await expect(page.getByTestId('search-via-route-api')).toContainText('world') +}) diff --git a/e2e/react-router/basic-esbuild-file-based/tsconfig.json b/e2e/react-router/basic-esbuild-file-based/tsconfig.json new file mode 100644 index 0000000000..b50d8070ec --- /dev/null +++ b/e2e/react-router/basic-esbuild-file-based/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "skipLibCheck": true + } +} diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index 494bf655aa..70f169621f 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -82,6 +82,16 @@ "default": "./dist/cjs/webpack.cjs" } }, + "./esbuild": { + "import": { + "types": "./dist/esm/esbuild.d.ts", + "default": "./dist/esm/esbuild.js" + }, + "require": { + "types": "./dist/cjs/esbuild.d.cts", + "default": "./dist/cjs/esbuild.cjs" + } + }, "./package.json": "./package.json" }, "sideEffects": false, @@ -128,4 +138,4 @@ "optional": true } } -} +} \ No newline at end of file diff --git a/packages/router-plugin/src/esbuild.ts b/packages/router-plugin/src/esbuild.ts new file mode 100644 index 0000000000..94bc039963 --- /dev/null +++ b/packages/router-plugin/src/esbuild.ts @@ -0,0 +1,56 @@ +import { createEsbuildPlugin } from 'unplugin' + +import { configSchema } from './core/config' +import { unpluginRouterCodeSplitterFactory } from './core/router-code-splitter-plugin' +import { unpluginRouterGeneratorFactory } from './core/router-generator-plugin' +import { unpluginRouterComposedFactory } from './core/router-composed-plugin' + +import type { Config } from './core/config' + +/** + * @example + * ```ts + * export default { + * plugins: [TanStackRouterGeneratorEsbuild()], + * // ... + * } + * ``` + */ +const TanStackRouterGeneratorEsbuild = createEsbuildPlugin( + unpluginRouterGeneratorFactory, +); + +/** + * @example + * ```ts + * export default { + * plugins: [TanStackRouterCodeSplitterEsbuild()], + * // ... + * } + * ``` + */ +const TanStackRouterCodeSplitterEsbuild = createEsbuildPlugin( + unpluginRouterCodeSplitterFactory, +); + +/** + * @example + * ```ts + * export default { + * plugins: [TanStackRouterEsbuild()], + * // ... + * } + * ``` + */ +const TanStackRouterEsbuild = createEsbuildPlugin(unpluginRouterComposedFactory); + +export default TanStackRouterEsbuild; + +export { + configSchema, + TanStackRouterGeneratorEsbuild, + TanStackRouterCodeSplitterEsbuild, + TanStackRouterEsbuild, +}; + +export type { Config }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e7001a35f..26abc6ecf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,46 @@ importers: specifier: ^5.4.9 version: 5.4.9(@types/node@22.7.4)(terser@5.31.1) + e2e/react-router/basic-esbuild-file-based: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../../packages/react-router + '@tanstack/router-devtools': + specifier: workspace:* + version: link:../../../packages/router-devtools + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + '@tanstack/router-zod-adapter': + specifier: workspace:* + version: link:../../../packages/router-zod-adapter + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@playwright/test': + specifier: ^1.48.0 + version: 1.48.1 + '@types/react': + specifier: ^18.2.47 + version: 18.3.11 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.0 + esbuild: + specifier: ^0.23.1 + version: 0.23.1 + e2e/react-router/basic-file-based: dependencies: '@tanstack/react-router': From b2413610d73ee39089eb53a30f2d7f964c762516 Mon Sep 17 00:00:00 2001 From: Ben McGinnis Date: Mon, 21 Oct 2024 11:49:07 -0400 Subject: [PATCH 2/5] feat(plugin-router): update documentation This change adds a quick start example for esbuild. It also addresses some feedback from the [#2578](PR): - updates the file-based-routing docs - updates the router-plugin's vite config --- .../react/guide/file-based-routing.md | 26 ++++ .../src/esbuild.config.js | 11 +- .../quickstart-esbuild-file-based/.gitignore | 5 + .../quickstart-esbuild-file-based/README.md | 6 + .../esbuild.config.js | 13 ++ .../quickstart-esbuild-file-based/index.html | 24 ++++ .../package.json | 25 ++++ .../src/main.tsx | 24 ++++ .../src/posts.tsx | 33 ++++++ .../src/routeTree.gen.ts | 111 ++++++++++++++++++ .../src/routes/__root.tsx | 36 ++++++ .../src/routes/about.tsx | 14 +++ .../src/routes/index.tsx | 14 +++ .../tsconfig.dev.json | 10 ++ .../tsconfig.json | 7 ++ packages/router-plugin/vite.config.ts | 1 + pnpm-lock.yaml | 34 ++++++ 17 files changed, 387 insertions(+), 7 deletions(-) create mode 100644 examples/react/quickstart-esbuild-file-based/.gitignore create mode 100644 examples/react/quickstart-esbuild-file-based/README.md create mode 100644 examples/react/quickstart-esbuild-file-based/esbuild.config.js create mode 100644 examples/react/quickstart-esbuild-file-based/index.html create mode 100644 examples/react/quickstart-esbuild-file-based/package.json create mode 100644 examples/react/quickstart-esbuild-file-based/src/main.tsx create mode 100644 examples/react/quickstart-esbuild-file-based/src/posts.tsx create mode 100644 examples/react/quickstart-esbuild-file-based/src/routeTree.gen.ts create mode 100644 examples/react/quickstart-esbuild-file-based/src/routes/__root.tsx create mode 100644 examples/react/quickstart-esbuild-file-based/src/routes/about.tsx create mode 100644 examples/react/quickstart-esbuild-file-based/src/routes/index.tsx create mode 100644 examples/react/quickstart-esbuild-file-based/tsconfig.dev.json create mode 100644 examples/react/quickstart-esbuild-file-based/tsconfig.json diff --git a/docs/framework/react/guide/file-based-routing.md b/docs/framework/react/guide/file-based-routing.md index e623a595b5..aac036ee02 100644 --- a/docs/framework/react/guide/file-based-routing.md +++ b/docs/framework/react/guide/file-based-routing.md @@ -150,6 +150,7 @@ To enable file-based routing, you'll need to be using React with a supported bun - Vite - Rspack/Rsbuild - Webpack +- Esbuild - Others??? (let us know if you'd like to see support for a specific bundler) If your bundler is not yet supported, you can reach out to us on Discord or GitHub to let us know. Till then, fear not! You can still use the [`@tanstack/router-cli`](#configuration-with-the-tanstack-router-cli) package to generate your route tree file. @@ -240,6 +241,31 @@ export default { Or, you can clone our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-webpack-file-based) and get started. + +### Configuration with Esbuild + + +To use file-based routing with **Esbuild**, you'll need to install the `@tanstack/router-plugin` package. + +```sh +npm install -D @tanstack/router-plugin +``` + +Once installed, you'll need to add the plugin to your configuration. + +```tsx +// esbuild.config.js +import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; + +export default { + // ... + plugins: [TanStackRouterEsbuild()] +} +``` + +Or, you can clone our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-esbuild-file-based) and get started. + + ### Configuration with the TanStack Router CLI To use file-based routing with the TanStack Router CLI, you'll need to install the `@tanstack/router-cli` package. diff --git a/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js index 544de2c03d..d314a39ec1 100644 --- a/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js +++ b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js @@ -1,11 +1,8 @@ import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; export default { - jsx: 'transform', - minify: true, - sourcemap: true, - bundle: true, - format: 'esm', - target: ['esnext'], - plugins: [TanStackRouterEsbuild()] + // ... + plugins: [TanStackRouterEsbuild({ + autoCodeSplitting: true, + })] } \ No newline at end of file diff --git a/examples/react/quickstart-esbuild-file-based/.gitignore b/examples/react/quickstart-esbuild-file-based/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/quickstart-esbuild-file-based/README.md b/examples/react/quickstart-esbuild-file-based/README.md new file mode 100644 index 0000000000..115199d292 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm start` or `yarn start` diff --git a/examples/react/quickstart-esbuild-file-based/esbuild.config.js b/examples/react/quickstart-esbuild-file-based/esbuild.config.js new file mode 100644 index 0000000000..cd0fe301ef --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/esbuild.config.js @@ -0,0 +1,13 @@ +import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; + +export default { + jsx: 'transform', + minify: true, + sourcemap: true, + bundle: true, + format: 'esm', + target: ['esnext'], + plugins: [TanStackRouterEsbuild({ + autoCodeSplitting: true, + })] +} \ No newline at end of file diff --git a/examples/react/quickstart-esbuild-file-based/index.html b/examples/react/quickstart-esbuild-file-based/index.html new file mode 100644 index 0000000000..292e03a54c --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/index.html @@ -0,0 +1,24 @@ + + + + + + Vite App + + + + +
+ + + diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json new file mode 100644 index 0000000000..229b7f24fa --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-router-react-example-quickstart-file-based", + "private": true, + "type": "module", + "scripts": { + "dev": "esbuild src/main.tsx --serve=3001 --bundle --outfile=dist/main.js --watch --servedir=.", + "build": "esbuild src/main.tsx --bundle --outfile=dist/main.js", + "serve": "esbuild src/main.tsx --bundle --outfile=dist/main.js --servedir=.", + "start": "dev" + }, + "dependencies": { + "@tanstack/react-router": "^1.70.2", + "@tanstack/router-devtools": "^1.70.2", + "@tanstack/router-plugin": "^1.69.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "redaxios": "^0.5.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/react": "^18.2.47", + "@types/react-dom": "^18.2.18", + "esbuild": "^0.23.1" + } +} \ No newline at end of file diff --git a/examples/react/quickstart-esbuild-file-based/src/main.tsx b/examples/react/quickstart-esbuild-file-based/src/main.tsx new file mode 100644 index 0000000000..489ebafacf --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/main.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +// Set up a Router instance +const router = createRouter({ + routeTree, + defaultPreload: 'intent', +}) + +// Register things for typesafety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const rootElement = document.getElementById('app')! + +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render() +} diff --git a/examples/react/quickstart-esbuild-file-based/src/posts.tsx b/examples/react/quickstart-esbuild-file-based/src/posts.tsx new file mode 100644 index 0000000000..669c18114e --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/posts.tsx @@ -0,0 +1,33 @@ +import axios from 'redaxios' + +export type PostType = { + id: string + title: string + body: string +} + +export class PostNotFoundError extends Error {} + +export const fetchPost = async (postId: string) => { + console.info(`Fetching post with id ${postId}...`) + await new Promise((r) => setTimeout(r, 500)) + const post = await axios + .get(`https://jsonplaceholder.typicode.com/posts/${postId}`) + .then((r) => r.data) + .catch((err) => { + if (err.status === 404) { + throw new PostNotFoundError(`Post with id "${postId}" not found!`) + } + throw err + }) + + return post +} + +export const fetchPosts = async () => { + console.info('Fetching posts...') + await new Promise((r) => setTimeout(r, 500)) + return axios + .get('https://jsonplaceholder.typicode.com/posts') + .then((r) => r.data.slice(0, 10)) +} diff --git a/examples/react/quickstart-esbuild-file-based/src/routeTree.gen.ts b/examples/react/quickstart-esbuild-file-based/src/routeTree.gen.ts new file mode 100644 index 0000000000..f0cdd796ee --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/routeTree.gen.ts @@ -0,0 +1,111 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as AboutImport } from './routes/about' +import { Route as IndexImport } from './routes/index' + +// Create/Update Routes + +const AboutRoute = AboutImport.update({ + path: '/about', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + path: '/', + getParentRoute: () => rootRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/about': typeof AboutRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' + id: '__root__' | '/' | '/about' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AboutRoute: typeof AboutRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AboutRoute: AboutRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* prettier-ignore-end */ + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/about" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/about": { + "filePath": "about.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/examples/react/quickstart-esbuild-file-based/src/routes/__root.tsx b/examples/react/quickstart-esbuild-file-based/src/routes/__root.tsx new file mode 100644 index 0000000000..8896176e24 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/routes/__root.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import { Link, Outlet, createRootRoute } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +function RootComponent() { + return ( + <> +
+ + Home + {' '} + + About + +
+
+ + + + ) +} diff --git a/examples/react/quickstart-esbuild-file-based/src/routes/about.tsx b/examples/react/quickstart-esbuild-file-based/src/routes/about.tsx new file mode 100644 index 0000000000..492e6b85c2 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/routes/about.tsx @@ -0,0 +1,14 @@ +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/about')({ + component: AboutComponent, +}) + +function AboutComponent() { + return ( +
+

About

+
+ ) +} diff --git a/examples/react/quickstart-esbuild-file-based/src/routes/index.tsx b/examples/react/quickstart-esbuild-file-based/src/routes/index.tsx new file mode 100644 index 0000000000..c4588fb2c9 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/src/routes/index.tsx @@ -0,0 +1,14 @@ +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: HomeComponent, +}) + +function HomeComponent() { + return ( +
+

Welcome Home!

+
+ ) +} diff --git a/examples/react/quickstart-esbuild-file-based/tsconfig.dev.json b/examples/react/quickstart-esbuild-file-based/tsconfig.dev.json new file mode 100644 index 0000000000..285a09b0dc --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/tsconfig.dev.json @@ -0,0 +1,10 @@ +{ + "composite": true, + "extends": "../../../tsconfig.base.json", + + "files": ["src/main.tsx"], + "include": [ + "src" + // "__tests__/**/*.test.*" + ] +} diff --git a/examples/react/quickstart-esbuild-file-based/tsconfig.json b/examples/react/quickstart-esbuild-file-based/tsconfig.json new file mode 100644 index 0000000000..c9e17e2b68 --- /dev/null +++ b/examples/react/quickstart-esbuild-file-based/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx" + } +} diff --git a/packages/router-plugin/vite.config.ts b/packages/router-plugin/vite.config.ts index 89ac88dee1..b9bef04c34 100644 --- a/packages/router-plugin/vite.config.ts +++ b/packages/router-plugin/vite.config.ts @@ -19,6 +19,7 @@ export default mergeConfig( './src/vite.ts', './src/rspack.ts', './src/webpack.ts', + './src/esbuild.ts', ], srcDir: './src', }), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26abc6ecf9..7a8245964d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1518,6 +1518,40 @@ importers: specifier: ^5.4.9 version: 5.4.9(@types/node@22.7.4)(terser@5.31.1) + examples/react/quickstart-esbuild-file-based: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../../packages/react-router + '@tanstack/router-devtools': + specifier: workspace:* + version: link:../../../packages/router-devtools + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@types/react': + specifier: ^18.2.47 + version: 18.3.11 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.0 + esbuild: + specifier: ^0.23.1 + version: 0.23.1 + examples/react/quickstart-file-based: dependencies: '@tanstack/react-router': From d4251e947c0f6daae28a82bd36d231165a7ee846 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:19:51 +0000 Subject: [PATCH 3/5] ci: apply automated fixes --- docs/framework/react/guide/file-based-routing.md | 7 ++----- .../basic-esbuild-file-based/package.json | 2 +- .../basic-esbuild-file-based/src/esbuild.config.js | 12 +++++++----- .../quickstart-esbuild-file-based/esbuild.config.js | 12 +++++++----- .../react/quickstart-esbuild-file-based/package.json | 2 +- packages/router-plugin/package.json | 2 +- packages/router-plugin/src/esbuild.ts | 12 ++++++------ 7 files changed, 25 insertions(+), 24 deletions(-) diff --git a/docs/framework/react/guide/file-based-routing.md b/docs/framework/react/guide/file-based-routing.md index aac036ee02..acee06f2a4 100644 --- a/docs/framework/react/guide/file-based-routing.md +++ b/docs/framework/react/guide/file-based-routing.md @@ -241,10 +241,8 @@ export default { Or, you can clone our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-webpack-file-based) and get started. - ### Configuration with Esbuild - To use file-based routing with **Esbuild**, you'll need to install the `@tanstack/router-plugin` package. ```sh @@ -255,17 +253,16 @@ Once installed, you'll need to add the plugin to your configuration. ```tsx // esbuild.config.js -import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; +import { TanStackRouterEsbuild } from '@tanstack/router-plugin/esbuild' export default { // ... - plugins: [TanStackRouterEsbuild()] + plugins: [TanStackRouterEsbuild()], } ``` Or, you can clone our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-esbuild-file-based) and get started. - ### Configuration with the TanStack Router CLI To use file-based routing with the TanStack Router CLI, you'll need to install the `@tanstack/router-cli` package. diff --git a/e2e/react-router/basic-esbuild-file-based/package.json b/e2e/react-router/basic-esbuild-file-based/package.json index a7899cee67..30ad7719bc 100644 --- a/e2e/react-router/basic-esbuild-file-based/package.json +++ b/e2e/react-router/basic-esbuild-file-based/package.json @@ -25,4 +25,4 @@ "@types/react-dom": "^18.2.18", "esbuild": "^0.23.1" } -} \ No newline at end of file +} diff --git a/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js index d314a39ec1..9deed8a53d 100644 --- a/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js +++ b/e2e/react-router/basic-esbuild-file-based/src/esbuild.config.js @@ -1,8 +1,10 @@ -import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; +import { TanStackRouterEsbuild } from '@tanstack/router-plugin/esbuild' export default { // ... - plugins: [TanStackRouterEsbuild({ - autoCodeSplitting: true, - })] -} \ No newline at end of file + plugins: [ + TanStackRouterEsbuild({ + autoCodeSplitting: true, + }), + ], +} diff --git a/examples/react/quickstart-esbuild-file-based/esbuild.config.js b/examples/react/quickstart-esbuild-file-based/esbuild.config.js index cd0fe301ef..400535e6e6 100644 --- a/examples/react/quickstart-esbuild-file-based/esbuild.config.js +++ b/examples/react/quickstart-esbuild-file-based/esbuild.config.js @@ -1,4 +1,4 @@ -import {TanStackRouterEsbuild} from '@tanstack/router-plugin/esbuild'; +import { TanStackRouterEsbuild } from '@tanstack/router-plugin/esbuild' export default { jsx: 'transform', @@ -7,7 +7,9 @@ export default { bundle: true, format: 'esm', target: ['esnext'], - plugins: [TanStackRouterEsbuild({ - autoCodeSplitting: true, - })] -} \ No newline at end of file + plugins: [ + TanStackRouterEsbuild({ + autoCodeSplitting: true, + }), + ], +} diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 229b7f24fa..6f7c350e24 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -22,4 +22,4 @@ "@types/react-dom": "^18.2.18", "esbuild": "^0.23.1" } -} \ No newline at end of file +} diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index 70f169621f..522af60a89 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -138,4 +138,4 @@ "optional": true } } -} \ No newline at end of file +} diff --git a/packages/router-plugin/src/esbuild.ts b/packages/router-plugin/src/esbuild.ts index 94bc039963..10ffdaae57 100644 --- a/packages/router-plugin/src/esbuild.ts +++ b/packages/router-plugin/src/esbuild.ts @@ -18,7 +18,7 @@ import type { Config } from './core/config' */ const TanStackRouterGeneratorEsbuild = createEsbuildPlugin( unpluginRouterGeneratorFactory, -); +) /** * @example @@ -31,7 +31,7 @@ const TanStackRouterGeneratorEsbuild = createEsbuildPlugin( */ const TanStackRouterCodeSplitterEsbuild = createEsbuildPlugin( unpluginRouterCodeSplitterFactory, -); +) /** * @example @@ -42,15 +42,15 @@ const TanStackRouterCodeSplitterEsbuild = createEsbuildPlugin( * } * ``` */ -const TanStackRouterEsbuild = createEsbuildPlugin(unpluginRouterComposedFactory); +const TanStackRouterEsbuild = createEsbuildPlugin(unpluginRouterComposedFactory) -export default TanStackRouterEsbuild; +export default TanStackRouterEsbuild export { configSchema, TanStackRouterGeneratorEsbuild, TanStackRouterCodeSplitterEsbuild, TanStackRouterEsbuild, -}; +} -export type { Config }; +export type { Config } From aeeecd0dd35717ecfd7eb74041f00b5ad994f7fc Mon Sep 17 00:00:00 2001 From: SeanCassiere <33615041+SeanCassiere@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:08:41 +1300 Subject: [PATCH 4/5] chore: ensure unqiue package.json names --- e2e/react-router/basic-esbuild-file-based/package.json | 2 +- examples/react/quickstart-esbuild-file-based/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/react-router/basic-esbuild-file-based/package.json b/e2e/react-router/basic-esbuild-file-based/package.json index 30ad7719bc..0faedcd892 100644 --- a/e2e/react-router/basic-esbuild-file-based/package.json +++ b/e2e/react-router/basic-esbuild-file-based/package.json @@ -1,5 +1,5 @@ { - "name": "tanstack-router-e2e-react-basic-file-based", + "name": "tanstack-router-e2e-react-basic-esbuild-file-based", "private": true, "type": "module", "scripts": { diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 6f7c350e24..1b06c87434 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -1,5 +1,5 @@ { - "name": "tanstack-router-react-example-quickstart-file-based", + "name": "tanstack-router-react-example-quickstart-esbuild-file-based", "private": true, "type": "module", "scripts": { From 9e5b19d41d2a1312bb1b019fe344b41df8926f4c Mon Sep 17 00:00:00 2001 From: SeanCassiere <33615041+SeanCassiere@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:10:13 +1300 Subject: [PATCH 5/5] chore: turn off codesplitting in the quickstart example --- .../react/quickstart-esbuild-file-based/esbuild.config.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/react/quickstart-esbuild-file-based/esbuild.config.js b/examples/react/quickstart-esbuild-file-based/esbuild.config.js index 400535e6e6..b8e9496991 100644 --- a/examples/react/quickstart-esbuild-file-based/esbuild.config.js +++ b/examples/react/quickstart-esbuild-file-based/esbuild.config.js @@ -7,9 +7,5 @@ export default { bundle: true, format: 'esm', target: ['esnext'], - plugins: [ - TanStackRouterEsbuild({ - autoCodeSplitting: true, - }), - ], + plugins: [TanStackRouterEsbuild()], }