Files
jiang13-forum/frontend/renderer/+onRenderHtml.tsx
freefire e71c77b5ca feat: Vike 骨架(pages feed+spa / renderer 双端入口 / AppShell 拆分 / serverClient)
vite.config 接入 vike 插件并改产出 dist/client+dist/server;feed 白名单 route 匹配 /、/board/:id、/post/:id(.html);spa catch-all ssr:false;onRenderHtml renderToReadableStream 等 allReady 输出完整 HTML 并带主题/缩放脚本;onRenderClient 沿用样式表等待+冷启动预热+hydrate 回退;getHomeStoreState 作用域 + hydrating 变体入 RequestScope;删除 index.html 与 main.tsx
2026-09-09 01:21:58 +08:00

66 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ComponentType, ReactElement } from 'react';
import { renderToReadableStream } from 'react-dom/server';
import { escapeInject, dangerouslySkipEscape } from 'vike/server';
import type { PageContextServer } from 'vike/types';
import App from '../src/App';
import { createHomeStore } from '../src/store/homeStore';
import { createRequestScope } from '../src/ssr/scopeTypes';
import { runWithScope } from '../src/ssr/scope';
export { onRenderHtml };
type PageComponent = ComponentType<{ url?: string }>;
/** 首帧主题脚本(localStorage j13-theme),与旧 index.html 保持一致 */
const THEME_INIT_SCRIPT
= "(function(){var t=localStorage.getItem('j13-theme')||'light';document.documentElement.classList.toggle('dark',t==='dark');document.documentElement.style.colorScheme=t;})();";
/** 锁死手机/平板双指与手势缩放;头像裁剪 / 图片灯箱放行 pinch */
const ZOOM_BLOCK_SCRIPT
= "(function(){function inZoomAllowArea(target){return !!(target&&target.closest&&target.closest('.avatar-crop-stage, .image-lightbox'));}function blockGesture(e){if(inZoomAllowArea(e.target))return;if(e.cancelable)e.preventDefault();}function blockMultiTouch(e){if(e.touches&&e.touches.length>1){if(inZoomAllowArea(e.target))return;if(e.cancelable)e.preventDefault();}}document.addEventListener('gesturestart',blockGesture,{passive:false});document.addEventListener('gesturechange',blockGesture,{passive:false});document.addEventListener('gestureend',blockGesture,{passive:false});document.addEventListener('touchstart',blockMultiTouch,{passive:false});document.addEventListener('touchmove',blockMultiTouch,{passive:false});})();";
/** 等 lazy 页面全部渲染完成(allReady)后缓冲为完整 HTML,首字节语义与旧 Go 文档级 SSR 一致 */
async function renderPageToString(element: ReactElement): Promise<string> {
const stream = await renderToReadableStream(element, {
onError(err) {
console.error('[j13] SSR 渲染错误:', err);
},
});
await stream.allReady;
return new Response(stream).text();
}
async function onRenderHtml(pageContext: PageContextServer) {
const Page = pageContext.Page as PageComponent | undefined;
let pageHtml = '';
if (Page) {
// SSR feed 页:per-request 作用域 + hydrate 同构变体;渲染失败降级空壳(客户端自行接管),不 500
const scope = createRequestScope(createHomeStore());
scope.hydrating = true;
const { pathname, searchOriginal } = pageContext.urlParsed;
const url = pathname + (searchOriginal ?? '');
try {
pageHtml = await runWithScope(scope, () => renderPageToString(<Page url={url} />));
} catch (err) {
console.error('[j13] SSR 渲染失败,降级 SPA 壳:', err);
pageHtml = '';
}
}
return escapeInject`<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="description" content="拾三一隅,自在交流" />
<title>姜十三论坛 - 拾三一隅,自在交流</title>
<script>${dangerouslySkipEscape(THEME_INIT_SCRIPT)}</script>
<script>${dangerouslySkipEscape(ZOOM_BLOCK_SCRIPT)}</script>
</head>
<body>
<div id="root">${dangerouslySkipEscape(pageHtml)}</div>
</body>
</html>`;
}