-
-
Notifications
You must be signed in to change notification settings - Fork 794
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: bring back server utils via tanstack/react-router-server
- Loading branch information
1 parent
6fb3e2d
commit 15f7ea4
Showing
61 changed files
with
4,074 additions
and
452 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
.DS_Store | ||
dist | ||
dist-ssr | ||
*.local |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Example | ||
|
||
To run this example: | ||
|
||
- `npm install` or `yarn` | ||
- `npm start` or `yarn start` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "tanstack-router-react-example-basic-ssr-file-based", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "concurrently \"tsr watch\" \"node server\"", | ||
"build": "npm run build:tsr && npm run build:client && npm run build:server", | ||
"build:tsr": "tsr generate", | ||
"build:client": "vite build src/entry-client.tsx --outDir dist/client", | ||
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server", | ||
"serve": "NODE_ENV=production node server", | ||
"debug": "node --inspect-brk server" | ||
}, | ||
"dependencies": { | ||
"@tanstack/react-router": "0.0.1-beta.204", | ||
"@tanstack/react-start": "0.0.1-beta.204", | ||
"@tanstack/router-cli": "0.0.1-beta.204", | ||
"@tanstack/router-devtools": "0.0.1-beta.204", | ||
"axios": "^1.1.3", | ||
"get-port": "^7.0.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.20.2", | ||
"@babel/generator": "^7.20.4", | ||
"@rollup/plugin-babel": "^6.0.2", | ||
"@types/express": "^4.17.14", | ||
"@types/jsesc": "^3.0.1", | ||
"@vitejs/plugin-react": "^4", | ||
"compression": "^1.7.4", | ||
"concurrently": "^7.6.0", | ||
"express": "^4.18.2", | ||
"isbot": "^3.6.5", | ||
"jsesc": "^3.0.2", | ||
"node-fetch": "^3.3.0", | ||
"serve-static": "^1.15.0", | ||
"vite": "^4", | ||
"vite-plugin-babel": "^1.1.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import express from 'express' | ||
import getPort, { portNumbers } from 'get-port' | ||
|
||
const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD | ||
|
||
export async function createServer( | ||
root = process.cwd(), | ||
isProd = process.env.NODE_ENV === 'production', | ||
hmrPort, | ||
) { | ||
const app = express() | ||
|
||
/** | ||
* @type {import('vite').ViteDevServer} | ||
*/ | ||
let vite | ||
if (!isProd) { | ||
vite = await ( | ||
await import('vite') | ||
).createServer({ | ||
root, | ||
logLevel: isTest ? 'error' : 'info', | ||
server: { | ||
middlewareMode: true, | ||
watch: { | ||
// During tests we edit the files too fast and sometimes chokidar | ||
// misses change events, so enforce polling for consistency | ||
usePolling: true, | ||
interval: 100, | ||
}, | ||
hmr: { | ||
port: hmrPort, | ||
}, | ||
}, | ||
appType: 'custom', | ||
}) | ||
// use vite's connect instance as middleware | ||
app.use(vite.middlewares) | ||
} else { | ||
app.use((await import('compression')).default()) | ||
} | ||
|
||
app.use('*', async (req, res) => { | ||
try { | ||
const url = req.originalUrl | ||
|
||
if (url.includes('.')) { | ||
console.warn(`${url} is not valid router path`) | ||
res.status(404) | ||
res.end(`${url} is not valid router path`) | ||
return | ||
} | ||
|
||
// Extract the head from vite's index transformation hook | ||
let viteHead = !isProd | ||
? await vite.transformIndexHtml( | ||
url, | ||
`<html><head></head><body></body></html>`, | ||
) | ||
: '' | ||
|
||
viteHead = viteHead.substring( | ||
viteHead.indexOf('<head>') + 6, | ||
viteHead.indexOf('</head>'), | ||
) | ||
|
||
const entry = await (async () => { | ||
if (!isProd) { | ||
return vite.ssrLoadModule('/src/entry-server.tsx') | ||
} else { | ||
return import('./dist/server/entry-server.tsx') | ||
} | ||
})() | ||
|
||
console.log('Rendering: ', url, '...') | ||
entry.render({ req, res, url, head: viteHead }) | ||
} catch (e) { | ||
!isProd && vite.ssrFixStacktrace(e) | ||
console.log(e.stack) | ||
res.status(500).end(e.stack) | ||
} | ||
}) | ||
|
||
return { app, vite } | ||
} | ||
|
||
if (!isTest) { | ||
createServer().then(async ({ app }) => | ||
app.listen(await getPort({ port: portNumbers(3000, 3100) }), () => { | ||
console.log('Client Server: http://localhost:3000') | ||
}), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import * as React from 'react' | ||
import ReactDOM from 'react-dom/client' | ||
|
||
import { StartClient } from '@tanstack/react-start/client' | ||
import { createRouter } from './router' | ||
|
||
const router = createRouter() | ||
router.hydrate() | ||
|
||
ReactDOM.hydrateRoot(document, <StartClient router={router} />) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import * as React from 'react' | ||
import ReactDOMServer from 'react-dom/server' | ||
import { createMemoryHistory } from '@tanstack/react-router' | ||
import { ServerResponse } from 'http' | ||
import express from 'express' | ||
import { StartServer } from '@tanstack/react-start/server' | ||
import { createRouter } from './router' | ||
|
||
// index.js | ||
import './fetch-polyfill' | ||
|
||
export async function render(opts: { | ||
url: string | ||
head: string | ||
req: express.Request | ||
res: ServerResponse | ||
}) { | ||
const router = createRouter() | ||
|
||
const memoryHistory = createMemoryHistory({ | ||
initialEntries: [opts.url], | ||
}) | ||
|
||
// Update the history and context | ||
router.update({ | ||
history: memoryHistory, | ||
context: { | ||
...router.options.context, | ||
head: opts.head, | ||
}, | ||
}) | ||
|
||
// Since we're using renderToString, Wait for the router to finish loading | ||
await router.load() | ||
|
||
// Render the app | ||
const appHtml = ReactDOMServer.renderToString(<StartServer router={router} />) | ||
|
||
opts.res.statusCode = 200 | ||
opts.res.setHeader('Content-Type', 'text/html') | ||
opts.res.end(`<!DOCTYPE html>${appHtml}`) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// fetch-polyfill.js | ||
import fetch, { | ||
Blob, | ||
blobFrom, | ||
blobFromSync, | ||
File, | ||
fileFrom, | ||
fileFromSync, | ||
FormData, | ||
Headers, | ||
Request, | ||
Response, | ||
} from 'node-fetch' | ||
|
||
if (!globalThis.fetch) { | ||
globalThis.fetch = fetch | ||
globalThis.Headers = Headers | ||
globalThis.Request = Request | ||
globalThis.Response = Response | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Route as rootRoute } from "./routes/__root" | ||
import { Route as PostsRoute } from "./routes/posts" | ||
import { Route as IndexRoute } from "./routes/index" | ||
import { Route as PostsPostIdRoute } from "./routes/posts/$postId" | ||
import { Route as PostsIndexRoute } from "./routes/posts/index" | ||
|
||
declare module "@tanstack/react-router" { | ||
interface FileRoutesByPath { | ||
"/": { | ||
parentRoute: typeof rootRoute | ||
} | ||
"/posts": { | ||
parentRoute: typeof rootRoute | ||
} | ||
"/posts/": { | ||
parentRoute: typeof PostsRoute | ||
} | ||
"/posts/$postId": { | ||
parentRoute: typeof PostsRoute | ||
} | ||
} | ||
} | ||
|
||
Object.assign(IndexRoute.options, { | ||
path: "/", | ||
getParentRoute: () => rootRoute, | ||
}) | ||
|
||
Object.assign(PostsRoute.options, { | ||
path: "/posts", | ||
getParentRoute: () => rootRoute, | ||
}) | ||
|
||
Object.assign(PostsIndexRoute.options, { | ||
path: "/", | ||
getParentRoute: () => PostsRoute, | ||
}) | ||
|
||
Object.assign(PostsPostIdRoute.options, { | ||
path: "/$postId", | ||
getParentRoute: () => PostsRoute, | ||
}) | ||
|
||
export const routeTree = rootRoute.addChildren([ | ||
IndexRoute, | ||
PostsRoute.addChildren([PostsIndexRoute, PostsPostIdRoute]), | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Router } from '@tanstack/react-router' | ||
|
||
import { routeTree } from './routeTree.gen' | ||
|
||
export function createRouter() { | ||
return new Router({ | ||
routeTree, | ||
context: { | ||
head: '', | ||
}, | ||
}) | ||
} | ||
|
||
declare module '@tanstack/react-router' { | ||
interface Register { | ||
router: ReturnType<typeof createRouter> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export type RouterContext = { | ||
head: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { TanStackRouterDevtools } from '@tanstack/router-devtools' | ||
import * as React from 'react' | ||
import { Link, Outlet, rootRouteWithContext } from '@tanstack/react-router' | ||
import { DehydrateRouter } from '@tanstack/react-start/client' | ||
import { RouterContext } from '../routerContext' | ||
|
||
export const Route = rootRouteWithContext<RouterContext>()({ | ||
component: RootComponent, | ||
}) | ||
|
||
function RootComponent() { | ||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite App</title> | ||
<script src="https://cdn.tailwindcss.com" /> | ||
<script | ||
type="module" | ||
suppressHydrationWarning | ||
dangerouslySetInnerHTML={{ | ||
__html: ` | ||
import RefreshRuntime from "/@react-refresh" | ||
RefreshRuntime.injectIntoGlobalHook(window) | ||
window.$RefreshReg$ = () => {} | ||
window.$RefreshSig$ = () => (type) => type | ||
window.__vite_plugin_react_preamble_installed__ = true | ||
`, | ||
}} | ||
/> | ||
<script type="module" src="/@vite/client" /> | ||
<script type="module" src="/src/entry-client.tsx" /> | ||
</head> | ||
<body> | ||
<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" /> | ||
<DehydrateRouter /> | ||
</body> | ||
</html> | ||
) | ||
} |
Oops, something went wrong.