-
-
Notifications
You must be signed in to change notification settings - Fork 249
Expand file tree
/
Copy pathentry.browser.tsx
More file actions
127 lines (111 loc) · 3.81 KB
/
entry.browser.tsx
File metadata and controls
127 lines (111 loc) · 3.81 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
123
124
125
126
127
import * as ReactClient from '@vitejs/plugin-rsc/browser'
import { getRscStreamFromHtml } from '@vitejs/plugin-rsc/rsc-html-stream/browser'
import React from 'react'
import * as ReactDOMClient from 'react-dom/client'
import type { RscPayload } from './entry.rsc'
async function main() {
// stash `setPayload` function to trigger re-rendering
// from outside of `BrowserRoot` component (e.g. server function call, navigation, hmr)
let setPayload: (v: RscPayload) => void
// deserialize RSC stream back to React VDOM for CSR
const initialPayload = await ReactClient.createFromReadableStream<RscPayload>(
// initial RSC stream is injected in SSR stream as <script>...FLIGHT_DATA...</script>
getRscStreamFromHtml(),
)
// browser root component to (re-)render RSC payload as state
function BrowserRoot() {
const [payload, setPayload_] = React.useState(initialPayload)
React.useEffect(() => {
setPayload = (v) => React.startTransition(() => setPayload_(v))
}, [setPayload_])
// re-fetch/render on client side navigation
React.useEffect(() => {
return listenNavigation(() => fetchRscPayload())
}, [])
return payload.root
}
// re-fetch RSC and trigger re-rendering
async function fetchRscPayload() {
const payload = await ReactClient.createFromFetch<RscPayload>(
fetch(window.location.href),
)
setPayload(payload)
}
// register a handler which will be internally called by React
// on server function request after hydration.
ReactClient.setServerCallback(async (id, args) => {
const url = new URL(window.location.href)
const temporaryReferences = ReactClient.createTemporaryReferenceSet()
const payload = await ReactClient.createFromFetch<RscPayload>(
fetch(url, {
method: 'POST',
body: await ReactClient.encodeReply(args, { temporaryReferences }),
headers: {
'x-rsc-action': id,
},
}),
{ temporaryReferences },
)
setPayload(payload)
return payload.returnValue
})
// hydration
const browserRoot = (
<React.StrictMode>
<BrowserRoot />
</React.StrictMode>
)
ReactDOMClient.hydrateRoot(document, browserRoot, {
formState: initialPayload.formState,
})
// implement server HMR by trigering re-fetch/render of RSC upon server code change
if (import.meta.hot) {
import.meta.hot.on('rsc:update', () => {
fetchRscPayload()
})
}
}
// a little helper to setup events interception for client side navigation
function listenNavigation(onNavigation: () => void) {
window.addEventListener('popstate', onNavigation)
const oldPushState = window.history.pushState
window.history.pushState = function (...args) {
const res = oldPushState.apply(this, args)
onNavigation()
return res
}
const oldReplaceState = window.history.replaceState
window.history.replaceState = function (...args) {
const res = oldReplaceState.apply(this, args)
onNavigation()
return res
}
function onClick(e: MouseEvent) {
let link = (e.target as Element).closest('a')
if (
link &&
link instanceof HTMLAnchorElement &&
link.href &&
(!link.target || link.target === '_self') &&
link.origin === location.origin &&
!link.hasAttribute('download') &&
e.button === 0 && // left clicks only
!e.metaKey && // open in new tab (mac)
!e.ctrlKey && // open in new tab (windows)
!e.altKey && // download
!e.shiftKey &&
!e.defaultPrevented
) {
e.preventDefault()
history.pushState(null, '', link.href)
}
}
document.addEventListener('click', onClick)
return () => {
document.removeEventListener('click', onClick)
window.removeEventListener('popstate', onNavigation)
window.history.pushState = oldPushState
window.history.replaceState = oldReplaceState
}
}
main()