-
-
Notifications
You must be signed in to change notification settings - Fork 238
Expand file tree
/
Copy pathentry.rsc.tsx
More file actions
122 lines (113 loc) · 4.46 KB
/
entry.rsc.tsx
File metadata and controls
122 lines (113 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import {
renderToReadableStream,
createTemporaryReferenceSet,
decodeReply,
loadServerAction,
decodeAction,
decodeFormState,
} from '@vitejs/plugin-rsc/rsc'
import type { ReactFormState } from 'react-dom/client'
import { Root } from '../root.tsx'
import { parseRenderRequest } from './request.tsx'
// The schema of payload which is serialized into RSC stream on rsc environment
// and deserialized on ssr/client environments.
export type RscPayload = {
// this demo renders/serializes/deserizlies entire root html element
// but this mechanism can be changed to render/fetch different parts of components
// based on your own route conventions.
root: React.ReactNode
// server action return value of non-progressive enhancement case
returnValue?: { ok: boolean; data: unknown }
// server action form state (e.g. useActionState) of progressive enhancement case
formState?: ReactFormState
}
// the plugin by default assumes `rsc` entry having default export of request handler.
// however, how server entries are executed can be customized by registering own server handler.
export default { fetch: handler }
async function handler(request: Request): Promise<Response> {
// differentiate RSC, SSR, action, etc.
const renderRequest = parseRenderRequest(request)
request = renderRequest.request
// handle server function request
let returnValue: RscPayload['returnValue'] | undefined
let formState: ReactFormState | undefined
let temporaryReferences: unknown | undefined
let actionStatus: number | undefined
if (renderRequest.isAction === true) {
if (renderRequest.actionId) {
// action is called via `ReactClient.setServerCallback`.
const contentType = request.headers.get('content-type')
const body = contentType?.startsWith('multipart/form-data')
? await request.formData()
: await request.text()
temporaryReferences = createTemporaryReferenceSet()
const args = await decodeReply(body, { temporaryReferences })
const action = await loadServerAction(renderRequest.actionId)
try {
const data = await action.apply(null, args)
returnValue = { ok: true, data }
} catch (e) {
returnValue = { ok: false, data: e }
actionStatus = 500
}
} else {
// otherwise server function is called via `<form action={...}>`
// before hydration (e.g. when javascript is disabled).
// aka progressive enhancement.
const formData = await request.formData()
const decodedAction = await decodeAction(formData)
try {
const result = await decodedAction()
formState = await decodeFormState(result, formData)
} catch (e) {
// there's no single general obvious way to surface this error,
// so explicitly return classic 500 response.
return new Response('Internal Server Error: server action failed', {
status: 500,
})
}
}
}
// serialization from React VDOM tree to RSC stream.
// we render RSC stream after handling server function request
// so that new render reflects updated state from server function call
// to achieve single round trip to mutate and fetch from server.
const rscPayload: RscPayload = {
root: <Root url={renderRequest.url} />,
formState,
returnValue,
}
const rscOptions = { temporaryReferences }
const rscStream = renderToReadableStream<RscPayload>(rscPayload, rscOptions)
// Respond RSC stream without HTML rendering as decided by `RenderRequest`
if (renderRequest.isRsc) {
return new Response(rscStream, {
status: actionStatus,
headers: {
'content-type': 'text/x-component;charset=utf-8',
},
})
}
// Delegate to SSR environment for html rendering.
// The plugin provides `loadModule` helper to allow loading SSR environment entry module
// in RSC environment. however this can be customized by implementing own runtime communication
// e.g. `@cloudflare/vite-plugin`'s service binding.
const ssrEntryModule = await import.meta.viteRsc.loadModule<
typeof import('./entry.ssr.tsx')
>('ssr', 'index')
const ssrResult = await ssrEntryModule.renderHTML(rscStream, {
formState,
// allow quick simulation of javascript disabled browser
debugNojs: renderRequest.url.searchParams.has('__nojs'),
})
// respond html
return new Response(ssrResult.stream, {
status: ssrResult.status,
headers: {
'Content-type': 'text/html',
},
})
}
if (import.meta.hot) {
import.meta.hot.accept()
}