Skip to content

Commit 6d65d66

Browse files
committed
Copied latest from vite sample in navigation repo
See grahammendick/navigation#883
1 parent d654680 commit 6d65d66

File tree

4 files changed

+75
-63
lines changed

4 files changed

+75
-63
lines changed

packages/plugin-rsc/examples/navigation/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"@types/react": "^19.1.8",
2020
"@types/react-dom": "^19.1.6",
2121
"@vitejs/plugin-react": "latest",
22-
"vite": "^7.0.2"
22+
"rsc-html-stream": "^0.0.7",
23+
"vite": "^7.0.4",
24+
"vite-plugin-inspect": "^11.3.0"
2325
}
2426
}
Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,23 @@
1+
import * as ReactClient from '@vitejs/plugin-rsc/browser'
12
import { useState, useMemo } from "react";
23
import ReactDOM from "react-dom/client";
3-
import { hydrate as _hydrate } from '@vitejs/plugin-rsc/extra/browser'
4-
import { createFromFetch, createFromReadableStream } from "@vitejs/plugin-rsc/browser";
4+
import { rscStream } from 'rsc-html-stream/client'
5+
import { createFromFetch } from "@vitejs/plugin-rsc/browser";
56
import { BundlerContext } from 'navigation-react';
67

7-
declare global{interface Window { __FLIGHT_DATA: any;}}
8-
9-
let encoder = new TextEncoder();
10-
let streamController: any;
11-
let rscStream = new ReadableStream({ start(controller) {
12-
if (typeof window === "undefined") return;
13-
let handleChunk = (chunk: any) => {
14-
if (typeof chunk === "string") controller.enqueue(encoder.encode(chunk));
15-
else controller.enqueue(chunk);
16-
};
17-
window.__FLIGHT_DATA ||= [];
18-
window.__FLIGHT_DATA.forEach(handleChunk);
19-
window.__FLIGHT_DATA.push = (chunk: any) => {
20-
handleChunk(chunk);
21-
};
22-
streamController = controller;
23-
} });
24-
if (typeof document !== "undefined" && document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => {
25-
streamController?.close();
26-
});
27-
else streamController?.close();
28-
29-
async function hydrate() {
30-
const initialPayload = await createFromReadableStream(rscStream) as any;
31-
function Shell() {
32-
const [root, setRoot] = useState(initialPayload.root);
33-
const bundler = useMemo(() => ({setRoot, deserialize: fetchRSC}), []);
34-
return (
35-
<BundlerContext.Provider value={bundler}>
36-
{root}
37-
</BundlerContext.Provider>
38-
);
39-
}
40-
ReactDOM.hydrateRoot(document, <Shell />);
41-
}
428
async function fetchRSC(url: string, {body, ...options}: any) {
43-
const payload = await createFromFetch(fetch(url, {...options, body: JSON.stringify(body), method: 'PUT'})) as any;
9+
const payload = await createFromFetch(fetch(url, {...options, body: JSON.stringify(body)})) as any;
4410
return payload.root;
4511
}
46-
hydrate();
12+
13+
const initialPayload = await ReactClient.createFromReadableStream<{root: React.ReactNode}>(rscStream)
14+
function Shell() {
15+
const [root, setRoot] = useState(initialPayload.root);
16+
const bundler = useMemo(() => ({setRoot, deserialize: fetchRSC}), []);
17+
return (
18+
<BundlerContext.Provider value={bundler}>
19+
{root}
20+
</BundlerContext.Provider>
21+
);
22+
}
23+
ReactDOM.hydrateRoot(document, <Shell />);
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,27 @@
1-
export * from '@vitejs/plugin-rsc/extra/ssr'
1+
import * as ReactClient from '@vitejs/plugin-rsc/ssr';
2+
import React from 'react';
3+
import * as ReactDOMServer from 'react-dom/server.edge';
4+
import { injectRSCPayload } from 'rsc-html-stream/server';
5+
6+
type RscPayload = {
7+
root: React.ReactNode
8+
}
9+
export async function renderHTML(
10+
rscStream: ReadableStream<Uint8Array>) {
11+
const [rscStream1, rscStream2] = rscStream.tee();
12+
let payload: Promise<RscPayload>;
13+
function SsrRoot() {
14+
payload ??= ReactClient.createFromReadableStream<RscPayload>(rscStream1);
15+
return React.use(payload).root;
16+
}
17+
const bootstrapScriptContent =
18+
await import.meta.viteRsc.loadBootstrapScriptContent('index');
19+
const htmlStream = await ReactDOMServer.renderToReadableStream(<SsrRoot />, {
20+
bootstrapScriptContent,
21+
});
22+
let responseStream: ReadableStream<Uint8Array> = htmlStream;
23+
responseStream = responseStream.pipeThrough(
24+
injectRSCPayload(rscStream2),
25+
);
26+
return responseStream;
27+
}

packages/plugin-rsc/examples/navigation/src/server.tsx

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,59 @@
1-
import { renderRequest } from '@vitejs/plugin-rsc/extra/rsc'
2-
import { StateNavigator } from 'navigation'
3-
import stateNavigator from './stateNavigator.ts'
1+
import * as ReactServer from '@vitejs/plugin-rsc/rsc';
2+
import { StateNavigator } from 'navigation';
3+
import stateNavigator from './stateNavigator.ts';
44

55
export default async function handler(request: Request): Promise<Response> {
6-
let url: string;
6+
let url: string = '';
77
let view: any;
88
const serverNavigator = new StateNavigator(stateNavigator);
99
if (request.method === 'GET') {
1010
let reqUrl = new URL(request.url);
1111
url = `${reqUrl.pathname}${reqUrl.search}`;
1212
const App = (await import('./App.tsx')).default;
1313
view = <App url={url} />;
14-
} else {
14+
}
15+
if (request.method === 'POST') {
1516
const sceneViews: any = {
1617
people: await import('./People.tsx'),
1718
person: await import('./Person.tsx'),
1819
friends: await import('./Friends.tsx')
1920
};
20-
const {url: reqUrl, sceneViewKey, state, data, crumbs} = await request.json();
21-
if (reqUrl) {
22-
url = reqUrl;
23-
const SceneView = sceneViews[sceneViewKey].default;
24-
view = <SceneView />;
25-
} else {
26-
let fluentNavigator = serverNavigator.fluent();
27-
for (let i = 0; i < crumbs.length; i++) {
28-
fluentNavigator = fluentNavigator.navigate(crumbs[i].state, crumbs[i].data);
29-
}
30-
fluentNavigator = fluentNavigator.navigate(state, data);
31-
url = fluentNavigator.url;
32-
const App = (await import('./App.tsx')).default;
33-
view = <App url={url} />;
21+
const {url: reqUrl, sceneViewKey} = await request.json();
22+
url = reqUrl;
23+
const SceneView = sceneViews[sceneViewKey].default;
24+
view = <SceneView />;
25+
}
26+
if (request.method === 'PUT') {
27+
const {state, data, crumbs} = await request.json();
28+
let fluentNavigator = serverNavigator.fluent();
29+
for (let i = 0; i < crumbs.length; i++) {
30+
fluentNavigator = fluentNavigator.navigate(crumbs[i].state, crumbs[i].data);
3431
}
32+
fluentNavigator = fluentNavigator.navigate(state, data);
33+
url = fluentNavigator.url;
34+
const App = (await import('./App.tsx')).default;
35+
view = <App url={url} />;
3536
}
3637
try {
37-
serverNavigator.navigateLink(url)
38+
serverNavigator.navigateLink(url);
3839
} catch(e) {
3940
return new Response('Not Found', { status: 404 });
4041
}
41-
const { NavigationHandler } = await import('navigation-react');
42+
const {NavigationHandler} = await import('navigation-react');
4243
const root = (
4344
<>
4445
<NavigationHandler stateNavigator={serverNavigator}>
4546
{view}
4647
</NavigationHandler>
4748
</>
4849
);
49-
return renderRequest(request, root);
50+
const rscStream = ReactServer.renderToReadableStream({root});
51+
if (request.method !== 'GET') {
52+
return new Response(rscStream, {headers: {'Content-type': 'text/x-component'}});
53+
}
54+
const ssrEntryModule = await import.meta.viteRsc.loadModule<typeof import('./server.ssr.tsx')>('ssr', 'index');
55+
const htmlStream = await ssrEntryModule.renderHTML(rscStream);
56+
return new Response(htmlStream, {headers: {'Content-type': 'text/html'}});
5057
}
5158

5259
if (import.meta.hot) {

0 commit comments

Comments
 (0)