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
This commit is contained in:
2026-09-09 01:21:58 +08:00
parent 27132e89c2
commit e71c77b5ca
20 changed files with 407 additions and 287 deletions

View File

@@ -1,40 +1,14 @@
import path from 'path';
import { defineConfig, type Plugin } from 'vite';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vike from 'vike/plugin';
// 与 Go 后端默认端口一致,可通过 VITE_API_PORT 覆盖
const apiPort = process.env.VITE_API_PORT || '3000';
const apiTarget = `http://localhost:${apiPort}`;
/** 把 stylesheet 挪到 head 靠前(先于 module script),接近 Gitea 外链 CSS 顺序 */
function htmlStylesheetsFirst(): Plugin {
return {
name: 'html-stylesheets-first',
enforce: 'post',
transformIndexHtml(html) {
// 只处理真实标签,忽略注释里的示例文字
const linkRe = /<link\b(?![^>]*\/?>)[^>]*\brel=["']stylesheet["'][^>]*>\s*/gi;
// 更稳妥:逐个找 link 标签
const styles: string[] = [];
const out = html.replace(/<link\b[^>]*>/gi, (tag) => {
if (/\brel=["']stylesheet["']/i.test(tag)) {
styles.push(tag);
return '';
}
return tag;
});
if (!styles.length) return html;
const inject = `${styles.join('\n ')}\n `;
if (/<script\b/i.test(out)) {
return out.replace(/<script\b/i, `${inject}<script`);
}
return out.replace(/<\/head>/i, ` ${inject}</head>`);
},
};
}
export default defineConfig({
plugins: [react(), htmlStylesheetsFirst()],
plugins: [react(), vike()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
@@ -42,7 +16,8 @@ export default defineConfig({
},
base: '/',
build: {
outDir: '../embed_static/static/spa',
// Vike 双构建:产出 dist/client(静态资源)+ dist/server(SSR 渲染入口)
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
output: {