Skip to content

Commit 83e3448

Browse files
hi-ogawaOpenCode
andauthored
refactor(rsc): align SSG browser entry (#1287)
Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: OpenCode <noreply@opencode.ai>
1 parent 2bed52c commit 83e3448

1 file changed

Lines changed: 26 additions & 15 deletions

File tree

packages/plugin-rsc/examples/ssg/src/framework/entry.browser.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
2-
createFromFetch,
32
createFromReadableStream,
3+
createFromFetch,
44
} from '@vitejs/plugin-rsc/browser'
55
import React from 'react'
66
import { createRoot, hydrateRoot } from 'react-dom/client'
@@ -9,53 +9,64 @@ import { GlobalErrorBoundary } from './error-boundary'
99
import { createRscRenderRequest } from './request'
1010
import type { RscPayload } from './shared'
1111

12-
async function hydrate(): Promise<void> {
13-
async function onNavigation() {
14-
const renderRequest = createRscRenderRequest(window.location.href)
15-
const payload = await createFromFetch<RscPayload>(fetch(renderRequest))
16-
setPayload(payload)
17-
}
18-
19-
const initialPayload = await createFromReadableStream<RscPayload>(rscStream)
20-
12+
async function main() {
13+
// stash `setPayload` function to trigger re-rendering
14+
// from outside of `BrowserRoot` component (e.g. navigation, hmr)
2115
let setPayload: (v: RscPayload) => void
2216

17+
// deserialize RSC stream back to React VDOM for CSR
18+
const initialPayload = await createFromReadableStream<RscPayload>(
19+
// initial RSC stream is injected in SSR stream as <script>...FLIGHT_DATA...</script>
20+
rscStream,
21+
)
22+
23+
// browser root component to (re-)render RSC payload as state
2324
function BrowserRoot() {
2425
const [payload, setPayload_] = React.useState(initialPayload)
2526

2627
React.useEffect(() => {
2728
setPayload = (v) => React.startTransition(() => setPayload_(v))
2829
}, [setPayload_])
2930

31+
// re-fetch/render on client side navigation
3032
React.useEffect(() => {
31-
return listenNavigation(() => onNavigation())
33+
return listenNavigation(() => fetchRscPayload())
3234
}, [])
3335

3436
return payload.root
3537
}
3638

39+
// re-fetch RSC and trigger re-rendering
40+
async function fetchRscPayload() {
41+
const renderRequest = createRscRenderRequest(window.location.href)
42+
const payload = await createFromFetch<RscPayload>(fetch(renderRequest))
43+
setPayload(payload)
44+
}
45+
46+
// hydration
3747
const browserRoot = (
3848
<React.StrictMode>
3949
<GlobalErrorBoundary>
4050
<BrowserRoot />
4151
</GlobalErrorBoundary>
4252
</React.StrictMode>
4353
)
44-
4554
if ('__NO_HYDRATE' in globalThis) {
4655
createRoot(document).render(browserRoot)
4756
} else {
4857
hydrateRoot(document, browserRoot)
4958
}
5059

60+
// implement server HMR by triggering re-fetch/render of RSC upon server code change
5161
if (import.meta.hot) {
5262
import.meta.hot.on('rsc:update', () => {
53-
window.history.replaceState({}, '', window.location.href)
63+
fetchRscPayload()
5464
})
5565
}
5666
}
5767

58-
function listenNavigation(onNavigation: () => void): () => void {
68+
// a little helper to setup events interception for client side navigation
69+
function listenNavigation(onNavigation: () => void) {
5970
window.addEventListener('popstate', onNavigation)
6071

6172
const oldPushState = window.history.pushState
@@ -102,4 +113,4 @@ function listenNavigation(onNavigation: () => void): () => void {
102113
}
103114
}
104115

105-
hydrate()
116+
main()

0 commit comments

Comments
 (0)