|
|
@@ -1,6 +1,8 @@
|
|
|
import fs from 'node:fs/promises';
|
|
|
import { URL } from 'node:url';
|
|
|
import { Transform } from 'node:stream';
|
|
|
+import { Readable } from 'node:stream';
|
|
|
+import { pipeline } from 'node:stream/promises';
|
|
|
import { serve } from '@hono/node-server';
|
|
|
import { Hono } from 'hono';
|
|
|
import { createServer as createNodeServer } from 'node:http';
|
|
|
@@ -46,8 +48,8 @@ if (!isProduction) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-// 将原有请求处理逻辑转换为 Hono 中间件
|
|
|
-app.use(async (c, next) => {
|
|
|
+// 将请求处理逻辑适配为 Hono 中间件
|
|
|
+app.use(async (c) => {
|
|
|
try {
|
|
|
// 使用 c.env 获取原生请求响应对象(关键修复)
|
|
|
const req = c.env.incoming;
|
|
|
@@ -72,7 +74,7 @@ app.use(async (c, next) => {
|
|
|
|
|
|
// 如果 Vite 中间件已经处理了请求,直接返回
|
|
|
if (handled) {
|
|
|
- return;
|
|
|
+ return c.body;
|
|
|
}
|
|
|
}
|
|
|
// 生产环境:使用 compression 和 sirv 中间件
|
|
|
@@ -83,7 +85,7 @@ app.use(async (c, next) => {
|
|
|
});
|
|
|
|
|
|
if (compressed) {
|
|
|
- return;
|
|
|
+ return c.body;
|
|
|
}
|
|
|
|
|
|
// 再尝试 sirv 中间件处理静态文件
|
|
|
@@ -92,7 +94,7 @@ app.use(async (c, next) => {
|
|
|
});
|
|
|
|
|
|
if (served) {
|
|
|
- return;
|
|
|
+ return c.body;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -114,31 +116,42 @@ app.use(async (c, next) => {
|
|
|
}
|
|
|
|
|
|
let didError = false;
|
|
|
+ let abortController;
|
|
|
|
|
|
+ // 创建一个可读流用于 SSR 渲染内容
|
|
|
+ const [htmlStart, htmlEnd] = template.split(`<!--app-html-->`);
|
|
|
+ const ssrStream = new Readable({ read: () => {} });
|
|
|
+
|
|
|
+ // 写入 HTML 头部
|
|
|
+ ssrStream.push(htmlStart);
|
|
|
+
|
|
|
+ // 设置响应头和状态码
|
|
|
+ c.header('Content-Type', 'text/html');
|
|
|
+
|
|
|
+ // 处理渲染
|
|
|
const { pipe, abort } = render(normalizedUrl, {
|
|
|
onShellError() {
|
|
|
- res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
|
- res.end('<h1>Something went wrong</h1>');
|
|
|
+ didError = true;
|
|
|
+ c.status(500);
|
|
|
+ ssrStream.push('<h1>Something went wrong</h1>');
|
|
|
+ ssrStream.push(null); // 结束流
|
|
|
},
|
|
|
onShellReady() {
|
|
|
- res.writeHead(didError ? 500 : 200, { 'Content-Type': 'text/html' });
|
|
|
-
|
|
|
+ // 将渲染结果通过管道传入 ssrStream
|
|
|
const transformStream = new Transform({
|
|
|
transform(chunk, encoding, callback) {
|
|
|
- res.write(chunk, encoding);
|
|
|
+ ssrStream.push(chunk, encoding);
|
|
|
callback();
|
|
|
- },
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
- const [htmlStart, htmlEnd] = template.split(`<!--app-html-->`);
|
|
|
-
|
|
|
- res.write(htmlStart);
|
|
|
-
|
|
|
+ pipe(transformStream);
|
|
|
+
|
|
|
+ // 当 transformStream 完成时,添加 HTML 尾部
|
|
|
transformStream.on('finish', () => {
|
|
|
- res.end(htmlEnd);
|
|
|
+ ssrStream.push(htmlEnd);
|
|
|
+ ssrStream.push(null); // 结束流
|
|
|
});
|
|
|
-
|
|
|
- pipe(transformStream);
|
|
|
},
|
|
|
onError(error) {
|
|
|
didError = true;
|
|
|
@@ -147,17 +160,19 @@ app.use(async (c, next) => {
|
|
|
});
|
|
|
|
|
|
// 设置超时中止
|
|
|
+ abortController = new AbortController();
|
|
|
const abortTimeout = setTimeout(() => {
|
|
|
abort();
|
|
|
+ abortController.abort();
|
|
|
}, ABORT_DELAY);
|
|
|
|
|
|
- // 清理超时
|
|
|
- res.on('finish', () => {
|
|
|
- clearTimeout(abortTimeout);
|
|
|
+ // 将流通过 Hono 响应返回
|
|
|
+ return c.body(ssrStream, {
|
|
|
+ onEnd: () => {
|
|
|
+ clearTimeout(abortTimeout);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
- // 告诉 Hono 我们已经手动处理了响应
|
|
|
- c.res = new Response(null, { status: 200 });
|
|
|
} catch (e) {
|
|
|
if (!isProduction && vite) {
|
|
|
vite.ssrFixStacktrace(e);
|
|
|
@@ -175,4 +190,3 @@ serve({
|
|
|
}, () => {
|
|
|
console.log(`Server started at http://localhost:${port}`);
|
|
|
});
|
|
|
-
|