Skip to content

Commit

Permalink
feat: New Serialization Paradigm (#1824)
Browse files Browse the repository at this point in the history
* checkpoint

* fix: config

* checkpoint

* testing fixes (#1679)

* examples: init trellaux

* feat: Better serialization/streaming, RSC proof of concept

* fixup server-handler

* fix: update deps and lock

* fix: remove rscFrom

* fix: remove useLoaderDataTransform

* fix: min react version

* chore: lockfile

* style: prettier fail

* chore: swap expect-error for ignore

* chore(react-router): try coercing the types

* chore: put this back in

* fix: some tests

* remove conditions

* fix tests

* update lockfile

* fix: refactor dehydrated data

* Update docs

* fix: remove unused deferred logic

* fix port

* nevermind

* fix: remove rebase garbage

* disable playwright for a hot minute

* oops, actually disable it now

* fix: prettier formatting

* fix: do not ignore app.config files

---------

Co-authored-by: Nikhil Saraf <[email protected]>
Co-authored-by: SeanCassiere <[email protected]>
  • Loading branch information
3 people authored Jun 28, 2024
1 parent 4310311 commit fd692cf
Show file tree
Hide file tree
Showing 194 changed files with 4,852 additions and 2,382 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ artifacts
.rpt2_cache
coverage
*.tgz
**/*app.config.*

# tests
packages/router-generator/tests/**/*.gen.ts
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/react/guide/data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,10 @@ import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
// Pass the fetchPosts function to the route context
beforeLoad: () => ({
fetchPosts: () => console.log('foo'),
fetchPosts: () => console.info('foo'),
}),
loader: ({ context: { fetchPosts } }) => {
console.log(fetchPosts()) // 'foo'
console.info(fetchPosts()) // 'foo'

// ...
},
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/react/guide/external-data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export function createRouter() {
context: {
queryClient,
},
// On the server, dehydrate the loader client and return it
// to the router to get injected into `<DehydrateRouter />`
// On the server, dehydrate the loader client so the router
// can serialize it and send it to the client for us
dehydrate: () => {
return {
queryClientState: dehydrate(queryClient),
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/react/guide/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ function Component() {

useEffect(() => {
if (matchRoute({ to: '/users', pending: true })) {
console.log('The /users route is matched and pending')
console.info('The /users route is matched and pending')
}
})

Expand Down
26 changes: 0 additions & 26 deletions docs/framework/react/guide/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ To implement non-streaming SSR with TanStack Router, you will need the following
- `StartClient` from `@tanstack/start`
- e.g. `<StartClient router={router} />`
- Rendering this component in your client entry will render your application and also automatically implement the `Wrap` component option on `Router`
- `DehydrateRouter` from `@tanstack/start`
- e.g. `<DehydrateRouter />`
- Render this component **inside your application** to embed the router's dehydrated data into the application.

### Router Creation

Expand Down Expand Up @@ -117,29 +114,6 @@ Resolved loader data fetched by routes is automatically dehydrated and rehydrate

For more information on how to utilize data loading and data streaming, see the [Data Loading](../data-loading) and [Data Streaming](../data-streaming) guides.

### Dehydrating the Router

**SSR would be a waste of time without access to all of the precious data you just fetched on the server!** One of the last steps to prepping your app for SSR is to dehydrate your application data into the markup on the server.

To do this, render the `<DehydrateRouter />` component somewhere inside your Root component. `<DehydrateRouter />` will render a `<script>` tag that contains the JSON of the router's dehydrated state that can then be rehydrated on the client.

```tsx
// src/root.tsx

import * as React from 'react'
import { DehydrateRouter } from '@tanstack/start'

export function Root() {
return (
<html>
<body>
<DehydrateRouter />
</body>
</html>
)
}
```

### Rendering the Application on the Server

Now that you have a router instance that has loaded all of the critical data for the current URL, you can render your application on the server:
Expand Down
6 changes: 4 additions & 2 deletions docs/framework/react/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ You can install TanStack Router with any [NPM](https://npmjs.com) package manage
npm install @tanstack/react-router
```

TanStack Router is compatible with React v16.8+ and is currently only compatible with ReactDOM. If you would like to contribute to the React Native adapter, please reach out to us on [Discord](https://tlinz.com/discord).
TanStack Router is currently only compatible with React and ReactDOM. If you would like to contribute to the React Native adapter, please reach out to us on [Discord](https://tlinz.com/discord).

### Requirements

TanStack Router requires React v16.8+ and ReactDOM v16.8+. If you're working with TypeScript, you'll need TypeScript v5.0+.
- React v18.x.x
- ReactDOM v18.x.x
- TypeScript v5.x.x (TypeScript is optional, but recommended)
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default [
'ts/require-await': 'off',
'no-empty': 'off',
'no-prototype-builtins': 'off',
'no-shadow': 'off',
},
},
]
2 changes: 1 addition & 1 deletion examples/react/authenticated-routes/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
4 changes: 2 additions & 2 deletions examples/react/basic-default-search-params/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ type PostType = {
}

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

const fetchPost = async (postId: number) => {
console.log(`Fetching post with id ${postId}...`)
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 300))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
Expand Down
2 changes: 1 addition & 1 deletion examples/react/basic-default-search-params/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
4 changes: 2 additions & 2 deletions examples/react/basic-file-based-codesplitting/src/posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type PostType = {
}

export const fetchPost = async (postId: string) => {
console.log(`Fetching post with id ${postId}...`)
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
Expand All @@ -24,7 +24,7 @@ export const fetchPost = async (postId: string) => {
}

export const fetchPosts = async () => {
console.log('Fetching posts...')
console.info('Fetching posts...')
await new Promise((r) => setTimeout(r, 500))
return axios
.get<PostType[]>('https://jsonplaceholder.typicode.com/posts')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
4 changes: 2 additions & 2 deletions examples/react/basic-file-based/src/posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type PostType = {
}

export const fetchPost = async (postId: string) => {
console.log(`Fetching post with id ${postId}...`)
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
Expand All @@ -24,7 +24,7 @@ export const fetchPost = async (postId: string) => {
}

export const fetchPosts = async () => {
console.log('Fetching posts...')
console.info('Fetching posts...')
await new Promise((r) => setTimeout(r, 500))
return axios
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
Expand Down
2 changes: 1 addition & 1 deletion examples/react/basic-file-based/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
4 changes: 2 additions & 2 deletions examples/react/basic-react-query-file-based/src/posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type PostType = {
export class PostNotFoundError extends Error {}

export const fetchPost = async (postId: string) => {
console.log(`Fetching post with id ${postId}...`)
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
Expand All @@ -25,7 +25,7 @@ export const fetchPost = async (postId: string) => {
}

export const fetchPosts = async () => {
console.log('Fetching posts...')
console.info('Fetching posts...')
await new Promise((r) => setTimeout(r, 500))
return axios
.get<PostType[]>('https://jsonplaceholder.typicode.com/posts')
Expand Down
2 changes: 1 addition & 1 deletion examples/react/basic-react-query-file-based/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
4 changes: 2 additions & 2 deletions examples/react/basic-react-query/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ type PostType = {
}

const fetchPosts = async () => {
console.log('Fetching posts...')
console.info('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}...`)
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
Expand Down
2 changes: 1 addition & 1 deletion examples/react/basic-react-query/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
"jsx": "react-jsx"
}
}
6 changes: 3 additions & 3 deletions examples/react/basic-ssr-file-based/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ export async function createServer(
}
})()

console.log('Rendering: ', url, '...')
console.info('Rendering: ', url, '...')
entry.render({ req, res, url, head: viteHead })
} catch (e) {
!isProd && vite.ssrFixStacktrace(e)
console.log(e.stack)
console.info(e.stack)
res.status(500).end(e.stack)
}
})
Expand All @@ -87,7 +87,7 @@ export async function createServer(
if (!isTest) {
createServer().then(async ({ app }) =>
app.listen(await getPort({ port: portNumbers(3000, 3100) }), () => {
console.log('Client Server: http://localhost:3000')
console.info('Client Server: http://localhost:3000')
}),
)
}
4 changes: 2 additions & 2 deletions examples/react/basic-ssr-file-based/src/entry-client.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import ReactDOM from 'react-dom/client'
import { hydrateRoot } from 'react-dom/client'

import { StartClient } from '@tanstack/start'
import { createRouter } from './router'

const router = createRouter()

ReactDOM.hydrateRoot(document, <StartClient router={router} />)
hydrateRoot(document.getElementById('root')!, <StartClient router={router} />)
4 changes: 2 additions & 2 deletions examples/react/basic-ssr-file-based/src/entry-server.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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/start/server'
import { createRouter } from './router'
import type { ServerResponse } from 'http'
import type express from 'express'

// index.js
import './fetch-polyfill'
Expand Down
72 changes: 43 additions & 29 deletions examples/react/basic-ssr-file-based/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,56 @@
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import * as React from 'react'
import {
Link,
Outlet,
createRootRouteWithContext,
} from '@tanstack/react-router'
import { DehydrateRouter } from '@tanstack/start'
import { RouterContext } from '../routerContext'
import { Body, Head, Html, Meta, Scripts } from '@tanstack/start'
import type { RouterContext } from '../routerContext'

export const Route = createRootRouteWithContext<RouterContext>()({
meta: () => [
{
title: 'TanStack Router SSR Basic File Based',
},
{
charSet: 'UTF-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1.0',
},
],
scripts: () => [
{
src: 'https://cdn.tailwindcss.com',
},
{
type: 'module',
children: `import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true`,
},
{
type: 'module',
src: '/@vite/client',
},
{
type: 'module',
src: '/src/entry-client.tsx',
},
],
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>
<Html lang="en">
<Head>
<Meta />
</Head>
<Body>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
Expand Down Expand Up @@ -67,8 +81,8 @@ function RootComponent() {
<hr />
<Outlet /> {/* Start rendering router matches */}
<TanStackRouterDevtools position="bottom-right" />
<DehydrateRouter />
</body>
</html>
<Scripts />
</Body>
</Html>
)
}
2 changes: 1 addition & 1 deletion examples/react/basic-ssr-file-based/src/routes/posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type PostType = {

export const Route = createFileRoute('/posts')({
loader: async () => {
console.log('Fetching posts...')
console.info('Fetching posts...')
await new Promise((r) =>
setTimeout(r, 300 + Math.round(Math.random() * 300)),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PostType } from '../posts'

export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
console.log(`Fetching post with id ${params.postId}...`)
console.info(`Fetching post with id ${params.postId}...`)

await new Promise((r) => setTimeout(r, Math.round(Math.random() * 300)))

Expand Down
Loading

0 comments on commit fd692cf

Please sign in to comment.