This commit is contained in:
2026-04-14 14:55:14 +08:00
parent 5ae545d24e
commit bf89f6ef6e
8 changed files with 195 additions and 34 deletions

View File

@@ -1,4 +1,4 @@
import { hydrateRoot } from 'react-dom/client';
import { hydrateRoot, createRoot } from 'react-dom/client';
import App from './pages/a/ClientApp';
// import AServer from './pages/a/server/index.tsx';
@@ -14,5 +14,11 @@ declare global {
if (typeof document !== 'undefined') {
// const data = window.__SERVER_DATA__ ?? { version: '' };
hydrateRoot(document.getElementById('root')!, <App />);
// hydrateRoot(document.getElementById('root')!, <App />);
// hydrateRoot(document.getElementById('root')!, <App />);
// console.log('Hydration complete');
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
}

View File

@@ -4,7 +4,7 @@ import A from './pages/a/List';
Bun.serve({
port: 3000,
async fetch(req) {
const stream = await renderToReadableStream(<A version=''/>, {
const stream = await renderToReadableStream(<A version='' data={{}} />, {
bootstrapScripts: [],
});

View File

@@ -1,19 +1,12 @@
'use client';
import List from './List.tsx';
// Client Component - 用于 hydration结构需要和 ServerApp 一致
declare global {
interface Window {
__SERVER_DATA__?: { version: string };
}
}
export default function ClientApp() {
const version = typeof window !== 'undefined' && window.__SERVER_DATA__?.version
? window.__SERVER_DATA__.version
: 'loading';
return <List version={version} />;
const data = typeof window !== 'undefined' && window.__SERVER_DATA__ ? window.__SERVER_DATA__ : { version: 'loading' };
return <List
version={data.version}
data={data}
/>;
}
// 'use client';

View File

@@ -1,15 +1,22 @@
"use client";
import { useEffect } from "react";
import { useEffect, useState } from "react";
export default function List({ version }: { version: string }) {
const Item = () => {
return <div>Item</div>
}
export default function List({ version, data = {} }: { version: string, data: any }) {
useEffect(() => {
console.log('useEffect in List123');
console.log('Received data in List:', data);
}, []);
// <div>Timestamp: {timestamp ? new Date(timestamp).toLocaleTimeString('en-US') : 'N/A'}</div>
const tm = <div>Timestamp: {data.timestamp ? new Date(data.timestamp).toLocaleTimeString('en-US') : 'N/A'}</div>
return (
<div>
<div>sdf3</div>
<h1>List - Version {version}</h1>
<div>sdf32323232</div>
{tm}
{version === '2.0.0' && <Item />}
{/* <h1>List - Version {version}</h1> */}
{/* <h1>List - Version2 {version + 1}</h1> */}
<div style={{
width: 200
}}>Primary Button</div>

View File

@@ -8,7 +8,8 @@ type ServerData = {
async function fetchServerData() {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
version: '3.0.0',
// version: '3.0.0' + Math.floor(Math.random() * 100),
version: '2.0.0',
timestamp: Date.now(),
innerHtml: '<div>Inner HTML Content</div>'
};
@@ -27,6 +28,6 @@ export const getData = fetchServerData; // 导出数据获取函数,供 server
// Server Component - 通过 props 接收数据,不再自己 fetch
export default async function ServerApp(props: { data: ServerData }) {
return <>
<List version={props.data.version} />
<List version={props.data.version} data={props.data} />
</>
}

View File

@@ -3,7 +3,7 @@ import Main, { createServerDataScript, getData } from './pages/a/main.tsx';
import http from 'http';
import fs from 'fs';
import path from 'path';
import fg from 'fast-glob'
const PORT = 3000;
const distDir = path.join(process.cwd(), 'dist');
const indexHtmlPath = path.join(process.cwd(), 'dist/index.html');
@@ -19,10 +19,24 @@ const mimeTypes: Record<string, string> = {
'.ico': 'image/x-icon',
};
const indexList = fg.globSync('index-*.js', {
cwd: process.cwd() + '/dist',
})
const indexPath = indexList.map(file => {
const basename = path.basename(file, '.js');
const [_, hash] = basename.split('-');
console.log('Found index file:', `http://localhost:${PORT}/${basename}`);
return { file, hash, basename };
});
async function ssrRender(res: http.ServerResponse, mode: 'server' | 'client' = 'client') {
async function ssrRender(res: http.ServerResponse, opts: {
mode: 'server' | 'client',
urlPath: string,
filename?: string
}) {
let template: string;
const mode = opts.mode || 'client';
try {
template = fs.readFileSync(indexHtmlPath, 'utf-8');
} catch {
@@ -30,7 +44,7 @@ async function ssrRender(res: http.ServerResponse, mode: 'server' | 'client' = '
res.end('dist/index.html not found');
return;
}
if (mode === 'client' ) {
if (mode === 'client') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(template);
return;
@@ -38,19 +52,21 @@ async function ssrRender(res: http.ServerResponse, mode: 'server' | 'client' = '
try {
const serverData = await getData()
const filename = opts.filename;
const bootstrapScripts = [filename]
const scriptTag = bootstrapScripts.map(src => `<script type='module' src="${src}"/></script>`).join('');
const stream = await renderToReadableStream(<Main data={serverData} />, {
bootstrapScripts: []
});
let renderedHtml = '';
for await (const chunk of stream) {
renderedHtml += new TextDecoder().decode(chunk);
}
// console.log('Rendered HTML:', renderedHtml);
console.log('Rendered HTML:', renderedHtml);
const html = template
.replace('<div id="root"></div>', `<div id="root">${renderedHtml}</div>`)
.replace('</head>', `${createServerDataScript(serverData)}</head>`);
.replace('</head>', `${createServerDataScript(serverData)}</head>`)
.replace('</body>', `${scriptTag}</body>`);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} catch (err) {
@@ -64,12 +80,12 @@ http.createServer((req, res) => {
const urlPath = req.url?.split('?')[0] || '/';
if (urlPath === '/') {
ssrRender(res, 'client');
ssrRender(res, { mode: 'client', urlPath: '' });
return;
}
if (urlPath === '/a') {
ssrRender(res, 'server');
const find = indexPath.find((item) => urlPath === `/${item.basename}`);
if (find) {
ssrRender(res, { mode: 'server', urlPath, filename: './' + find.file });
return;
}
@@ -85,7 +101,7 @@ http.createServer((req, res) => {
} catch {
}
ssrRender(res);
// ssrRender(res, { mode: 'client', urlPath: '/a' });
}).listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});