Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New Serialization Paradigm #1824

Merged
merged 30 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f48606b
checkpoint
tannerlinsley May 27, 2024
ff9ad72
fix: config
tannerlinsley May 28, 2024
2652653
checkpoint
tannerlinsley May 28, 2024
fb86d95
testing fixes (#1679)
nksaraf May 30, 2024
76417b4
examples: init trellaux
tannerlinsley May 30, 2024
828ab81
feat: Better serialization/streaming, RSC proof of concept
tannerlinsley Jun 24, 2024
b7cdb37
fixup server-handler
tannerlinsley Jun 24, 2024
a07fcb0
fix: update deps and lock
tannerlinsley Jun 24, 2024
703e0b4
fix: remove rscFrom
tannerlinsley Jun 25, 2024
5766446
fix: remove useLoaderDataTransform
tannerlinsley Jun 25, 2024
52571be
fix: min react version
tannerlinsley Jun 25, 2024
900b9ab
chore: lockfile
SeanCassiere Jun 26, 2024
8f19582
style: prettier fail
SeanCassiere Jun 26, 2024
509f055
chore: swap expect-error for ignore
SeanCassiere Jun 26, 2024
3697e1c
chore(react-router): try coercing the types
SeanCassiere Jun 26, 2024
7b9bacb
chore: put this back in
SeanCassiere Jun 26, 2024
8244900
fix: some tests
tannerlinsley Jun 27, 2024
8a4b865
remove conditions
tannerlinsley Jun 27, 2024
08e2b60
fix tests
tannerlinsley Jun 27, 2024
b532374
update lockfile
tannerlinsley Jun 27, 2024
64001c4
fix: refactor dehydrated data
tannerlinsley Jun 27, 2024
129e8bd
Update docs
tannerlinsley Jun 27, 2024
18abc6c
fix: remove unused deferred logic
tannerlinsley Jun 27, 2024
3aaa5f2
fix port
tannerlinsley Jun 27, 2024
ddf7e83
nevermind
tannerlinsley Jun 27, 2024
8cec835
fix: remove rebase garbage
tannerlinsley Jun 28, 2024
7d216ce
disable playwright for a hot minute
tannerlinsley Jun 28, 2024
ae73ca6
oops, actually disable it now
tannerlinsley Jun 28, 2024
3da1b6a
fix: prettier formatting
tannerlinsley Jun 28, 2024
3b35e77
fix: do not ignore app.config files
tannerlinsley Jun 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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