diff --git a/index.html b/index.html index f0d3f6a..9c6b4d5 100644 --- a/index.html +++ b/index.html @@ -8,10 +8,8 @@ -
- -
+
- \ No newline at end of file + diff --git a/package.json b/package.json index 8e2b444..6f927b6 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { - "name": "@vitejs/plugin-rsc-examples-starter", + "name": "rsc", "version": "0.0.0", "private": true, "license": "MIT", "type": "module", "scripts": { - "dev": "bun run --host src/entry.tsx" + "dev": "tsx src/server-node.tsx", + "build": "bun build index.html --outdir dist " }, "dependencies": { "@ant-design/cssinjs": "^2.1.2", diff --git a/src/browser-entry.tsx b/src/browser-entry.tsx index 984a386..0655bcf 100644 --- a/src/browser-entry.tsx +++ b/src/browser-entry.tsx @@ -1,6 +1,14 @@ import { hydrateRoot } from 'react-dom/client'; -import A from './pages/a/index'; +import App from './pages/a/main'; +// import AServer from './pages/a/server/index.tsx'; -// React 19: renderToPipeableStream embeds RSC payload in HTML -// hydrateRoot will find and use that payload automatically -hydrateRoot(document.getElementById('root')!, ); \ No newline at end of file +declare global { + interface Window { + __SERVER_DATA__?: { version: string }; + } +} + +if (typeof document !== 'undefined') { + const data = window.__SERVER_DATA__ ?? { version: '' }; + hydrateRoot(document.getElementById('root')!, ); +} \ No newline at end of file diff --git a/src/entry.tsx b/src/entry.tsx index e894884..5ae2c99 100644 --- a/src/entry.tsx +++ b/src/entry.tsx @@ -1,10 +1,10 @@ import { renderToReadableStream } from 'react-dom/server'; -import A from './pages/a/index'; +import A from './pages/a/List'; Bun.serve({ port: 3000, async fetch(req) { - const stream = await renderToReadableStream(, { + const stream = await renderToReadableStream(, { bootstrapScripts: [], }); diff --git a/src/pages/a/List.tsx b/src/pages/a/List.tsx new file mode 100644 index 0000000..6943c5f --- /dev/null +++ b/src/pages/a/List.tsx @@ -0,0 +1,16 @@ +"use client"; +import { useEffect } from "react"; + +export default function List({ version }: { version: string }) { + useEffect(() => { + console.log('useEffect in List'); + }, []); + return ( +
+

List - Version {version}

+
Primary Button
+
+ ); +} \ No newline at end of file diff --git a/src/pages/a/index.tsx b/src/pages/a/index.tsx deleted file mode 100644 index e24e787..0000000 --- a/src/pages/a/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; -import { useEffect } from "react"; - -export default function List() { - useEffect(() => { - console.log('useEffect in List'); - }, []); - return ( -
-

List 2

-
- ); -} \ No newline at end of file diff --git a/src/pages/a/main.tsx b/src/pages/a/main.tsx new file mode 100644 index 0000000..a797c15 --- /dev/null +++ b/src/pages/a/main.tsx @@ -0,0 +1,18 @@ +import List from './List.tsx'; + +// 模拟异步获取数据 +async function fetchData() { + await new Promise(resolve => setTimeout(resolve, 1000)); + return { version: '2.0.0', timestamp: Date.now() }; +} + +// Server Component - 直接 await 获取数据 +export default async function ServerApp() { + const data = await fetchData(); + return ( +
+

Server Time: {new Date(data.timestamp).toLocaleTimeString()}

+ +
+ ); +} \ No newline at end of file diff --git a/src/pages/a/server-index.tsx b/src/pages/a/server-index.tsx deleted file mode 100644 index 0f67558..0000000 --- a/src/pages/a/server-index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// Server component - no 'use client' directive -export default function ServerList() { - return ( -
-

Server List

-
- ); -} \ No newline at end of file diff --git a/src/pages/a/server/index.tsx b/src/pages/a/server/index.tsx index fa45e53..2e943b1 100644 --- a/src/pages/a/server/index.tsx +++ b/src/pages/a/server/index.tsx @@ -1,18 +1,12 @@ 'use server'; -import { useEffect } from "react"; +import A from '../List'; const getVersion = async () => { await new Promise(resolve => setTimeout(resolve, 1000)); - return '1.0.0'; + return '2.0.0'; } -export default async function List() { + +export default async function AServer() { const v = await getVersion(); - return ( -
-

List - Version {v}

-
Primary Button
-
- ); + return
; } \ No newline at end of file diff --git a/src/server-node.tsx b/src/server-node.tsx index 5c69000..467c48a 100644 --- a/src/server-node.tsx +++ b/src/server-node.tsx @@ -1,31 +1,86 @@ -"use server"; -import { renderToPipeableStream, renderToString } from 'react-dom/server'; -// import A from './pages/a/index.tsx'; -import { AEntry } from './browser-entry.tsx'; -import AServer from './pages/a/server/index.tsx'; +import { renderToReadableStream } from 'react-dom/server'; +import Main from './pages/a/main.tsx'; import http from 'http'; +import fs from 'fs'; +import path from 'path'; const PORT = 3000; +const distDir = path.join(process.cwd(), 'dist'); +const indexHtmlPath = path.join(process.cwd(), 'dist/index.html'); + +const mimeTypes: Record = { + '.html': 'text/html', + '.js': 'application/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', +}; + +async function ssrRender(res: http.ServerResponse, version: string) { + let template: string; + try { + template = fs.readFileSync(indexHtmlPath, 'utf-8'); + } catch { + res.writeHead(500); + res.end('dist/index.html not found'); + return; + } + + const serverData = { version }; + + try { + const stream = await renderToReadableStream(
, { + bootstrapScripts: [] + }); + + let renderedHtml = ''; + for await (const chunk of stream) { + renderedHtml += new TextDecoder().decode(chunk); + } + + console.log('Rendered HTML:', renderedHtml); + const dataScript = ``; + const html = template + .replace('
', `
${renderedHtml}
`) + .replace('', `${dataScript}`); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + } catch (err) { + console.error('Render error:', err); + res.writeHead(500); + res.end('Server Error'); + } +} http.createServer((req, res) => { - if (req.url === '/ssr') { - const { pipe } = renderToPipeableStream(, { - bootstrapScripts: [], - onShellReady() { - res.writeHead(200, { 'Content-Type': 'text/html' }); - pipe(res); - }, - onShellError(err) { - console.error('Shell error:', err); - res.writeHead(500); - res.end('Server Error'); - } - }); - } else { - const str = renderToString(
); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(str); + const urlPath = req.url?.split('?')[0] || '/'; + + if (urlPath === '/') { + ssrRender(res, ''); + return; } + + if (urlPath === '/a') { + ssrRender(res, '1.0.0'); + return; + } + + const distFilePath = path.join(distDir, urlPath); + try { + if (fs.existsSync(distFilePath) && fs.statSync(distFilePath).isFile()) { + const ext = path.extname(distFilePath); + const contentType = mimeTypes[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + fs.createReadStream(distFilePath).pipe(res); + return; + } + } catch { + } + + ssrRender(res, ''); }).listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); }); \ No newline at end of file diff --git a/src/ssr.tsx b/src/ssr.tsx index 0ae1d1e..0584173 100644 --- a/src/ssr.tsx +++ b/src/ssr.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { renderToReadableStream } from 'react-dom/server'; -import VA from './pages/v/a'; -import { createRoot, hydrateRoot } from 'react-dom/client' +import VA from './pages/a/main.tsx'; +// import { createRoot, hydrateRoot } from 'react-dom/client' export async function renderToString(comp: React.ReactElement): Promise { const response = await renderToReadableStream( <>{comp}, @@ -20,10 +20,10 @@ export async function renderToString(comp: React.ReactElement): Promise return html; } -console.log(await renderToString()); +console.log(await renderToString()); // const root = createRoot(document.getElementById('root')!); // root.render(); -// hydrateRoot(document.getElementById('root')!, ); +// hydrateRoot(document.getElementById('root')!, ); // -// console.log(await renderToString()); \ No newline at end of file +// console.log(await renderToString()); \ No newline at end of file