Skip to content

Commit a477fd7

Browse files
committed
feat(rsc): react-router cloudflare single worker example
1 parent 3772be1 commit a477fd7

File tree

10 files changed

+103
-54
lines changed

10 files changed

+103
-54
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { fetchServer } from '../react-router-vite/entry.rsc'
1+
import handler from '../react-router-vite/entry.rsc'
22

33
console.log('[debug:cf-rsc-entry]')
44

55
export default {
66
fetch(request: Request) {
7-
return fetchServer(request)
7+
return handler(request)
88
},
99
}

packages/plugin-rsc/examples/react-router/cf/entry.ssr.tsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/plugin-rsc/examples/react-router/cf/vite.config.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import react from '@vitejs/plugin-react'
55
import { defineConfig } from 'vite'
66
// import inspect from 'vite-plugin-inspect'
77

8-
export default defineConfig({
8+
export default defineConfig((env) => ({
99
clearScreen: false,
1010
build: {
1111
minify: false,
@@ -17,22 +17,16 @@ export default defineConfig({
1717
rsc({
1818
entries: {
1919
client: './react-router-vite/entry.browser.tsx',
20+
ssr: './react-router-vite/entry.ssr.tsx',
2021
},
2122
serverHandler: false,
23+
loadModuleDevProxy: true,
2224
}),
2325
cloudflare({
24-
configPath: './cf/wrangler.ssr.jsonc',
26+
configPath: './cf/wrangler.jsonc',
2527
viteEnvironment: {
26-
name: 'ssr',
28+
name: 'rsc',
2729
},
28-
auxiliaryWorkers: [
29-
{
30-
configPath: './cf/wrangler.rsc.jsonc',
31-
viteEnvironment: {
32-
name: 'rsc',
33-
},
34-
},
35-
],
3630
}),
3731
],
3832
environments: {
@@ -42,14 +36,24 @@ export default defineConfig({
4236
},
4337
},
4438
ssr: {
45-
optimizeDeps: {
46-
exclude: ['react-router'],
39+
keepProcessEnv: false,
40+
build: {
41+
outDir: './dist/rsc/ssr',
4742
},
43+
resolve:
44+
env.command === 'build'
45+
? {
46+
noExternal: true,
47+
}
48+
: undefined,
49+
// optimizeDeps: {
50+
// exclude: ['react-router'],
51+
// },
4852
},
4953
rsc: {
5054
optimizeDeps: {
5155
exclude: ['react-router'],
5256
},
5357
},
5458
},
55-
})
59+
}))

packages/plugin-rsc/examples/react-router/cf/wrangler.rsc.jsonc renamed to packages/plugin-rsc/examples/react-router/cf/wrangler.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://www.unpkg.com/[email protected]/config-schema.json",
3-
"name": "vite-rsc-react-router-rsc",
3+
"name": "vite-rsc-react-router",
44
"main": "./entry.rsc.tsx",
55
"workers_dev": true,
66
"compatibility_date": "2025-04-01",

packages/plugin-rsc/examples/react-router/cf/wrangler.ssr.jsonc

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.single.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,21 @@ import {
99
import { unstable_matchRSCServerRequest as matchRSCServerRequest } from 'react-router'
1010
import { routes } from '../app/routes'
1111

12-
export function fetchServer(request: Request) {
12+
export default async function handler(request: Request) {
13+
// Import the generateHTML function from the client environment
14+
const ssr = await import.meta.viteRsc.loadModule<
15+
typeof import('./entry.ssr')
16+
>('ssr', 'index')
17+
const rscResponse = await fetchServer(request)
18+
const ssrResponse = await ssr.generateHTML(
19+
request.url,
20+
request.headers,
21+
rscResponse,
22+
)
23+
return ssrResponse
24+
}
25+
26+
function fetchServer(request: Request) {
1327
return matchRSCServerRequest({
1428
// Provide the React Server touchpoints.
1529
createTemporaryReferenceSet,

packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import {
55
unstable_RSCStaticRouter as RSCStaticRouter,
66
} from 'react-router'
77

8+
// https://github.com/remix-run/react-router/blob/20d8307d4a51c219f6e13e0b66461e7162d944e4/packages/react-router/lib/rsc/server.ssr.tsx#L95-L102
9+
810
export async function generateHTML(
9-
request: Request,
10-
fetchServer: (request: Request) => Promise<Response>,
11+
url: string,
12+
headers: Headers,
13+
rscResponse: Response,
1114
): Promise<Response> {
1215
return await routeRSCServerRequest({
1316
// The incoming request.
14-
request,
17+
request: new Request(url, { headers }),
1518
// How to call the React Server.
16-
fetchServer,
19+
fetchServer: async () => rscResponse,
1720
// Provide the React Server touchpoints.
1821
createFromReadableStream,
1922
// Render the router to HTML.

packages/plugin-rsc/examples/react-router/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default defineConfig({
1717
entries: {
1818
client: './react-router-vite/entry.browser.tsx',
1919
ssr: './react-router-vite/entry.ssr.tsx',
20-
rsc: './react-router-vite/entry.rsc.single.tsx',
20+
rsc: './react-router-vite/entry.rsc.tsx',
2121
},
2222
}),
2323
],

packages/plugin-rsc/src/utils/rpc.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { decode, encode } from 'turbo-stream'
1+
import {
2+
decode,
3+
encode,
4+
type DecodeOptions,
5+
type DecodePlugin,
6+
type EncodeOptions,
7+
type EncodePlugin,
8+
} from 'turbo-stream'
29

310
type RequestPayload = {
411
method: string
@@ -17,6 +24,7 @@ export function createRpcServer<T extends object>(handlers: T) {
1724
}
1825
const reqPayload = await decode<RequestPayload>(
1926
request.body.pipeThrough(new TextDecoderStream()),
27+
decodeOptions,
2028
)
2129
const handler = (handlers as any)[reqPayload.method]
2230
if (!handler) {
@@ -31,7 +39,7 @@ export function createRpcServer<T extends object>(handlers: T) {
3139
resPayload.ok = false
3240
resPayload.data = e
3341
}
34-
return new Response(encode(resPayload))
42+
return new Response(encode(resPayload, encodeOptions))
3543
}
3644
}
3745

@@ -41,7 +49,9 @@ export function createRpcClient<T>(options: { endpoint: string }): T {
4149
method,
4250
args,
4351
}
44-
const body = encode(reqPayload).pipeThrough(new TextEncoderStream())
52+
const body = encode(reqPayload, encodeOptions).pipeThrough(
53+
new TextEncoderStream(),
54+
)
4555
const res = await fetch(options.endpoint, {
4656
method: 'POST',
4757
body,
@@ -55,6 +65,7 @@ export function createRpcClient<T>(options: { endpoint: string }): T {
5565
}
5666
const resPayload = await decode<ResponsePayload>(
5767
res.body.pipeThrough(new TextDecoderStream()),
68+
decodeOptions,
5869
)
5970
if (!resPayload.ok) {
6071
throw resPayload.data
@@ -74,3 +85,48 @@ export function createRpcClient<T>(options: { endpoint: string }): T {
7485
},
7586
) as any
7687
}
88+
89+
const encodePlugin: EncodePlugin = (value) => {
90+
if (value instanceof Response) {
91+
return [
92+
'vite-rsc/response',
93+
value.status,
94+
value.statusText,
95+
[...value.headers],
96+
value.body,
97+
]
98+
}
99+
if (value instanceof Headers) {
100+
return ['vite-rsc/headers', [...value]]
101+
}
102+
}
103+
104+
const decodePlugin: DecodePlugin = (type, ...data) => {
105+
if (type === 'vite-rsc/response') {
106+
const [status, statusText, headers, body] = data as [
107+
number,
108+
string,
109+
[string, string][],
110+
ReadableStream<Uint8Array> | null,
111+
]
112+
const value = new Response(body, {
113+
status,
114+
statusText,
115+
headers,
116+
})
117+
return { value }
118+
}
119+
if (type === 'vite-rsc/headers') {
120+
const [headers] = data as [[string, string][]]
121+
const value = new Headers(headers)
122+
return { value }
123+
}
124+
}
125+
126+
const encodeOptions: EncodeOptions = {
127+
plugins: [encodePlugin],
128+
}
129+
130+
const decodeOptions: DecodeOptions = {
131+
plugins: [decodePlugin],
132+
}

0 commit comments

Comments
 (0)