Skip to content

Commit

Permalink
async getRoutes and tanstack app router example
Browse files Browse the repository at this point in the history
  • Loading branch information
nksaraf committed Jul 17, 2023
1 parent a908039 commit 3ec37ba
Show file tree
Hide file tree
Showing 34 changed files with 712 additions and 92 deletions.
1 change: 1 addition & 0 deletions examples/react-spa-tanstack-router-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vercel
120 changes: 120 additions & 0 deletions examples/react-spa-tanstack-router-app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import reactRefresh from "@vitejs/plugin-react";
import { init, parse } from "es-module-lexer";
import esbuild from "esbuild";
import fs from "fs";
import { join } from "path";
import { createApp } from "vinxi";
import { glob, pathToRegexp } from "vinxi/file-system-router";

class TanstackFileSystemRouter {
routes;
constructor(config) {
this.config = config;
}

async buildRoutes() {
function toPath(path, config) {
const routePath = path
.slice(config.dir.length)
.replace(/\.(ts|tsx|js|jsx)$/, "")
.replace(/\/page$/, "/")
.replace(/\/layout$/, "")
.replace(/index$/, "")
.replace(/\[([^\/]+)\]/g, (_, m) => {
if (m.length > 3 && m.startsWith("...")) {
return `*${m.slice(3)}`;
}
if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) {
return `$${m.slice(1, -1)}?`;
}
return `$${m}`;
});

return routePath?.length > 0 ? `${routePath}` : "/";
}
await init;
this.routes = [
...glob(join(this.config.dir, "**/(page|layout).tsx"), {
absolute: true,
}).map((src) => {
console.log(src);
let path = toPath(src, this.config);
let keys = [];
let regex = pathToRegexp(path, keys);

const [_, exports] = parse(
esbuild.transformSync(fs.readFileSync(src, "utf-8"), {
jsx: "transform",
format: "esm",
loader: "tsx",
}).code,
src,
);
const hasLoader = exports.find((e) => e.n === "loader");
const hasErrorBoundary = exports.find((e) => e.n === "ErrorBoundary");
const hasLoading = exports.find((e) => e.n === "Loading");
const hasConfig = exports.find((e) => e.n === "config");
return {
regex,
keys,
$component: {
src: src,
pick: ["default", "$css"],
},
$error: hasErrorBoundary
? { src: src, pick: ["ErrorBoundary"] }
: undefined,
$loading: hasLoading ? { src: src, pick: ["Loading"] } : undefined,
$$loader: hasLoader
? {
src: src,
pick: ["loader"],
}
: undefined,
$$config: hasConfig ? { src: src, pick: ["config"] } : undefined,
path,
filePath: src,
};
}),
];

return this.routes;
}

buildRoutesPromise = undefined;

async getRoutes() {
if (!this.buildRoutesPromise) {
this.buildRoutesPromise = this.buildRoutes();
}
return await this.buildRoutesPromise;
}
}

export default createApp({
routers: [
{
name: "public",
mode: "static",
dir: "./public",
},
{
name: "client",
mode: "spa",
handler: "./index.html",
dir: "./app/routes",
root: "./app/root.tsx",
style: TanstackFileSystemRouter,
build: {
babel: {
plugins: [
"@babel/plugin-syntax-jsx",
["@babel/plugin-syntax-typescript", { isTSX: true }],
],
},
target: "browser",
plugins: () => [reactRefresh()],
},
},
],
});
219 changes: 219 additions & 0 deletions examples/react-spa-tanstack-router-app/app/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/// <reference types="vinxi/client" />
import {
Loader,
LoaderClient,
LoaderClientProvider,
} from "@tanstack/react-loaders";
import {
Link,
Outlet,
RootRoute,
Route,
Router,
RouterProvider,
} from "@tanstack/router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { lazyRoute } from "@vinxi/react";
import axios from "axios";
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import fileRoutes from "vinxi/routes";
import "vinxi/runtime/client";

import { NotFoundError } from "./error";
import "./style.css";

type PostType = {
id: string;
title: string;
body: string;
};

const fetchPosts = async () => {
console.log("Fetching posts...");
await new Promise((r) => setTimeout(r, 500));
return axios
.get<PostType[]>("https://jsonplaceholder.typicode.com/posts")
.then((r) => r.data.slice(0, 10));
};

const fetchPost = async (postId: string) => {
console.log(`Fetching post with id ${postId}...`);
await new Promise((r) => setTimeout(r, 500));
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((r) => r.data)
.catch((e) => {
if (e.response.status === 404) {
return null;
}
throw e;
});

if (!post) {
throw new NotFoundError(`Post with id "${postId}" not found!`);
}

return post;
};

const postsLoader = new Loader({
fn: fetchPosts,
});

const postLoader = new Loader({
fn: fetchPost,
onInvalidate: () => {
postsLoader.invalidate();
},
});

const loaderClient = new LoaderClient({
getLoaders: () => ({
posts: postsLoader,
post: postLoader,
}),
});

declare module "@tanstack/react-loaders" {
interface Register {
loaderClient: typeof loaderClient;
}
}

type RouterContext = {
loaderClient: typeof loaderClient;
};

const rootRoute = RootRoute.withRouterContext<RouterContext>()({
component: () => {
return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: "font-bold",
}}
activeOptions={{ exact: true }}
>
Home
</Link>{" "}
<Link
to={"/posts"}
activeProps={{
className: "font-bold",
}}
>
Posts
</Link>
</div>
<hr />
<Outlet />
{/* Start rendering router matches */}
<TanStackRouterDevtools position="bottom-right" />
</>
);
},
});

const defineRoutes = (fileRoutes) => {
function processRoute(routes, route, id, full) {
const parentRoute = Object.values(routes).find((o) => {
// if (o.id.endsWith("/index")) {
// return false;
// }
return id.startsWith(o.path + "/");
});

if (!parentRoute) {
routes.push({ ...route, path: id });
return routes;
}
processRoute(
parentRoute.children || (parentRoute.children = []),
route,
id.slice(parentRoute.path.length),
full,
);

return routes;
}

return fileRoutes
.sort((a, b) => a.path.length - b.path.length)
.reduce((prevRoutes, route) => {
return processRoute(prevRoutes, route, route.path, route.path);
}, []);
};

const routes = defineRoutes(fileRoutes);
console.log(routes);

function createRoute(route, parent) {
const parentRoute = new Route({
path: route.path,
component: lazyRoute(route.$component, import.meta.env.MANIFEST["client"]),
errorComponent: route.$error
? lazyRoute(
route.$error,
import.meta.env.MANIFEST["client"],
undefined,
"ErrorBoundary",
)
: undefined,
pendingComponent: route.$loading
? lazyRoute(
route.$loading,
import.meta.env.MANIFEST["client"],
undefined,
"Loading",
)
: undefined,
loader: route.$$loader?.require().loader,
...(route.$$config?.require().config ?? {}),
getParentRoute: () => parent,
});
if (route.children) {
parentRoute.addChildren(
route.children.map((route) => createRoute(route, parentRoute)),
);
}
return parentRoute;
}

const routeTree = rootRoute.addChildren([
...routes.map((route) => {
return createRoute(route, rootRoute);
}),
]);

// Set up a Router instance
const router = new Router({
routeTree,
defaultPreload: "intent",
context: {
loaderClient,
},
});

// Register things for typesafety
declare module "@tanstack/router" {
interface Register {
router: typeof router;
}
}

const rootElement = document.getElementById("root")!;

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);

root.render(
<StrictMode>
<LoaderClientProvider loaderClient={loaderClient}>
<RouterProvider router={router} />
</LoaderClientProvider>
</StrictMode>,
);
}
3 changes: 3 additions & 0 deletions examples/react-spa-tanstack-router-app/app/routes/hello.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* {
background-color: purple;
}
12 changes: 12 additions & 0 deletions examples/react-spa-tanstack-router-app/app/routes/hello/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Link } from "@tanstack/router";
import React from "react";

import "./white.css";

export default function Hello() {
return (
<div>
Hello world <Link to="/">Home 123</Link>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
html, body {
background-color: red;
}
Loading

0 comments on commit 3ec37ba

Please sign in to comment.