11import {
2- createFromFetch ,
32 createFromReadableStream ,
3+ createFromFetch ,
44} from '@vitejs/plugin-rsc/browser'
55import React from 'react'
66import { createRoot , hydrateRoot } from 'react-dom/client'
@@ -9,53 +9,64 @@ import { GlobalErrorBoundary } from './error-boundary'
99import { createRscRenderRequest } from './request'
1010import 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