2
0
Эх сурвалжийг харах

♻️ refactor(server): 重构请求处理中间件,合并API和SSR逻辑

- 将多个中间件合并为统一的请求处理中间件,简化流程
- 提取模板处理逻辑为独立函数processDevTemplate和processProdTemplate
- 创建createSsrHandler函数统一处理SSR渲染逻辑
- 移除冗余的中间件和路由预加载代码,优化启动流程
- 开发环境和生产环境的路由处理统一通过子应用实现

♻️ refactor(server): 优化API路由导出方式

- 在index.tsx中显式导出api路由,简化导入逻辑
- 统一API路由和SSR渲染的访问入口
yourname 4 сар өмнө
parent
commit
5267eb191d
2 өөрчлөгдсөн 144 нэмэгдсэн , 175 устгасан
  1. 141 175
      server.js
  2. 3 0
      src/server/index.tsx

+ 141 - 175
server.js

@@ -84,202 +84,76 @@ if (!isProduction) {
 }
 
 
-// 请求处理中间件 - 通用逻辑
-app.use(async (c, next) => {
-  try {
-    // 使用 c.env 获取原生请求响应对象
-    const req = c.env.incoming;
-    const res = c.env.outgoing;
-    const url = new URL(req.url, `http://${req.headers.host}`);
-    const path = url.pathname;
-
-    // 检查是否匹配基础路径
-    if (!path.startsWith(baseUrl.pathname)) {
-      return c.text('未找到', 404);
-    }
-
-    // 开发环境:使用 Vite 中间件
-    if (!isProduction && vite) {
-      // 使用 Vite 中间件处理请求
-      const handled = await new Promise((resolve) => {
-        vite.middlewares(req, res, () => resolve(false));
-      });
-      
-      // 如果 Vite 中间件已经处理了请求,直接返回
-      if (handled) {
-        return c.body;
-      }
-    } 
-    // 生产环境:使用 compression 和 sirv 中间件
-    else if (isProduction) {
-      // 先尝试 compression 中间件
-      const compressed = await new Promise((resolve) => {
-        compressionMiddleware(req, res, () => resolve(false));
-      });
-      
-      if (compressed) {
-        return c.body;
-      }
-      
-      // 再尝试 sirv 中间件处理静态文件
-      const served = await new Promise((resolve) => {
-        sirvMiddleware(req, res, () => resolve(false));
-      });
-      
-      if (served) {
-        return c.body;
-      }
-    }
-
-    await next()
-  } catch (e) {
-    if (!isProduction && vite) {
-      vite.ssrFixStacktrace(e);
-    }
-    console.error('请求处理中间件错误:', e.stack);
-    return c.text('服务器内部错误', 500);
+// 开发环境模板处理函数 - 仅处理模板转换
+const processDevTemplate = async (template) => {
+  if (!isProduction && vite) {
+    console.log('开发环境: 处理模板...');
+    const processedTemplate = await vite.transformIndexHtml('/', template);
+    console.log('开发环境模板处理完成');
+    return processedTemplate;
   }
-});
-
-// 生产环境:启动时加载 API 路由
-if (isProduction) {
-  console.log('生产环境: 加载编译后的 API 路由...');
-  const api = (await import('./dist/api/api.js')).default;
-  app.route('/', api);
-  console.log('API 路由加载完成');
-}
-// 开发环境:不在此处加载 API 路由,改为在中间件中动态加载
+  return template;
+};
 
-// 添加动态 API 路由中间件
-app.use(async (c, next) => {
+// 生产环境模板处理函数 - 仅处理资源路径替换
+const processProdTemplate = async (template) => {
+  console.log('生产环境: 处理模板资源路径...');
   try {
-    // 开发环境:每次请求动态加载 API 路由
-    if (!isProduction && vite) {
-      // 动态加载最新 API 模块
-      const apiModule = await vite.ssrLoadModule('./src/server/api.ts');
-      
-      // 创建临时子应用并挂载路由
-      const apiApp = new Hono();
-      apiApp.route('/', apiModule.default);
-      
-      // 检查是否为 API 请求
-      if (
-        c.req.path.startsWith('/api')
-        || c.req.path.startsWith('/ui')
-        || c.req.path.startsWith('/doc')
-      ) {
-        // 直接由子应用处理 API 请求
-        return apiApp.fetch(c.req.raw, {
-          ...c.env,
-          // 传递原始请求对象
-          incoming: c.env.incoming,
-          outgoing: c.env.outgoing
-        });
-      }
+    // 读取 manifest
+    const manifestPath = new URL('./dist/client/.vite/manifest.json', import.meta.url);
+    const manifestContent = await fs.readFile(manifestPath, 'utf-8');
+    const manifest = JSON.parse(manifestContent);
+    console.log('生产环境: 成功读取 manifest.json');
+
+    // 获取 src/client/index.tsx 对应的资源信息
+    const indexManifest = manifest['src/client/index.tsx'];
+    if (!indexManifest) {
+      throw new Error('manifest 中未找到 src/client/index.tsx 入口配置');
     }
 
-    await next();
-  } catch (e) {
-    if (!isProduction && vite) {
-      vite.ssrFixStacktrace(e);
+    // 获取 src/style.css 对应的资源信息
+    const styleManifest = manifest['src/style.css'];
+    if (!styleManifest) {
+      throw new Error('manifest 中未找到 src/style.css 入口配置');
     }
-    console.error('API 路由加载错误:', e.stack);
-    return c.text('API 服务器错误', 500);
-  }
-});
 
-// 请求处理中间件 - SSR 渲染逻辑
-app.use(async (c) => {
-  try {
-    // 使用 c.env 获取原生请求响应对象
-    const req = c.env.incoming;
-    const res = c.env.outgoing;
-    const url = new URL(req.url, `http://${req.headers.host}`);
-    const path = url.pathname;
+    const cssPath = new URL(styleManifest.file, baseUrl).pathname;
+    const cssLinks = `<link href="${cssPath}" rel="stylesheet" />`;
 
-    // 检查是否匹配基础路径
-    if (!path.startsWith(baseUrl.pathname)) {
-      return c.text('未找到', 404);
-    }
+    // 替换入口脚本
+    const jsEntryPath = new URL(indexManifest.file, baseUrl).pathname;
+    const entryScript = `<script type="module" src="${jsEntryPath}"></script>`;
 
-    // 处理基础路径
-    const normalizedUrl = path.replace(baseUrl.pathname, '/') || '/';
-    console.log(`处理请求: ${normalizedUrl}`);
+    // 执行替换
+    const processedTemplate = template
+      .replace(/<link href="\/src\/style.css" rel="stylesheet"\/>/, cssLinks)
+      .replace(/<script type="module" src="\/src\/client\/index.tsx"><\/script>/, entryScript);
 
-    // 处理所有其他请求的 SSR 逻辑
-    /** @type {string} */
-    let template;
-    /** @type {import('./src/server/index.tsx').render} */
-    let render;
-    
-    if (!isProduction && vite) {
-      console.log('开发环境: 加载模板和渲染函数...');
-      // 开发环境:读取最新模板并转换
-      const module = (await vite.ssrLoadModule('/src/server/index.tsx'));
-      template = module.template;
-      template = await vite.transformIndexHtml(normalizedUrl, template);
-      render = module.render;
-      console.log('开发环境模板处理完成');
-    } else {
-      // 生产环境:使用缓存的模板
-      console.log('生产环境: 加载编译后的模板和渲染函数...');
-      const module = await import('./dist/server/index.js');
-      
-      // 读取 manifest.json 并处理模板
-      try {
-        // 读取 manifest
-        const manifestPath = new URL('./dist/client/.vite/manifest.json', import.meta.url);
-        const manifestContent = await fs.readFile(manifestPath, 'utf-8');
-        const manifest = JSON.parse(manifestContent);
-        console.log('生产环境: 成功读取 manifest.json');
-
-        // 获取 index.html 对应的资源信息
-        const indexManifest = manifest['index.html'];
-        if (!indexManifest) {
-          throw new Error('manifest 中未找到 index.html 入口配置');
-        }
-
-        template = module.template;
-
-        // 替换 CSS 链接
-        const cssLinks = indexManifest.css?.map(cssFile => {
-          // 结合基础路径生成完整 URL(处理 base 前缀)
-          const cssUrl = new URL(cssFile, baseUrl).pathname;
-          return `<link href="${cssUrl}" rel="stylesheet" />`;
-        }).join('\n') || ''; // 无 CSS 则清空
-
-        // 替换入口脚本
-        const jsEntryPath = new URL(indexManifest.file, baseUrl).pathname;
-        const entryScript = `<script type="module" src="${jsEntryPath}"></script>`;
-
-        // 执行替换
-        template = template
-          .replace(/<link href="\/src\/style.css" rel="stylesheet"\/>/, cssLinks)
-          .replace(/<script type="module" src="\/src\/client\/index.tsx"><\/script>/, entryScript);
-
-        console.log('生产环境模板处理完成');
-
-      } catch (err) {
-        console.error('生产环境模板处理失败:', err);
-        throw err; // 终止启动,避免使用错误模板
-      }
+    console.log('生产环境模板处理完成');
+    return processedTemplate;
 
-      render = module.render;
-    }
+  } catch (err) {
+    console.error('生产环境模板处理失败:', err);
+    throw err;
+  }
+};
 
+// SSR 渲染中间件函数
+const createSsrHandler = (template, render, normalizedUrl) => {
+  return async (c) => {
     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() {
@@ -299,7 +173,7 @@ app.use(async (c) => {
         });
 
         pipe(transformStream);
-        
+
         // 当 transformStream 完成时,添加 HTML 尾部
         transformStream.on('finish', () => {
           ssrStream.push(htmlEnd);
@@ -327,15 +201,107 @@ app.use(async (c) => {
         clearTimeout(abortTimeout);
       }
     });
+  };
+};
+
+// 统一的请求处理中间件 - 合并 API 和 SSR 渲染逻辑
+app.use(async (c) => {
+  try {
+    // 使用 c.env 获取原生请求响应对象
+    const req = c.env.incoming;
+    const res = c.env.outgoing;
+    const url = new URL(req.url, `http://${req.headers.host}`);
+    const path = url.pathname;
+
+    // 检查是否匹配基础路径
+    if (!path.startsWith(baseUrl.pathname)) {
+      return c.text('未找到', 404);
+    }
+
+    // 处理基础路径
+    const normalizedUrl = path.replace(baseUrl.pathname, '/') || '/';
+    console.log(`处理请求: ${normalizedUrl}`);
+
+    // 开发环境:使用 Vite 中间件
+    if (!isProduction && vite) {
+      // 使用 Vite 中间件处理请求
+      const handled = await new Promise((resolve) => {
+        vite.middlewares(req, res, () => resolve(false));
+      });
+      
+      // 如果 Vite 中间件已经处理了请求,直接返回
+      if (handled) {
+        return c.body;
+      }
+
+
+      // 动态加载最新 API 模块
+      const apiModule = await vite.ssrLoadModule('./src/server/index.tsx');
+      
+      // 创建临时子应用并挂载路由
+      const apiApp = new Hono();
+      apiApp.route('/', apiModule.api);
+      
+      // 处理开发环境模板
+      const template = await processDevTemplate(apiModule.template);
+      apiApp.get('*', createSsrHandler(template, apiModule.render, normalizedUrl));
+
+      // 直接由子应用处理 API 请求
+      return apiApp.fetch(c.req.raw, {
+        ...c.env,
+        // 传递原始请求对象
+        incoming: c.env.incoming,
+        outgoing: c.env.outgoing
+      });
+    }
+    // 生产环境:使用 compression 和 sirv 中间件
+    else if (isProduction) {
+      // 先尝试 compression 中间件
+      const compressed = await new Promise((resolve) => {
+        compressionMiddleware(req, res, () => resolve(false));
+      });
+      
+      if (compressed) {
+        return c.body;
+      }
+      
+      // 再尝试 sirv 中间件处理静态文件
+      const served = await new Promise((resolve) => {
+        sirvMiddleware(req, res, () => resolve(false));
+      });
+      
+      if (served) {
+        return c.body;
+      }
+
+      // 生产环境:使用缓存的 API 路由
+      const { api, template: rawTemplate, render } = (await import('./dist/server/index.js'));
+      const apiApp = new Hono();
+      apiApp.route('/', api);
+
+      // 处理生产环境模板
+      const template = await processProdTemplate(rawTemplate);
+      apiApp.get('*', createSsrHandler(template, render, normalizedUrl));
+
+      return apiApp.fetch(c.req.raw, {
+        ...c.env,
+        incoming: c.env.incoming,
+        outgoing: c.env.outgoing
+      });
+    }
+
   } catch (e) {
     if (!isProduction && vite) {
       vite.ssrFixStacktrace(e);
     }
-    console.error('SSR 处理错误:', e.stack);
+    console.error('请求处理错误:', e.stack);
     return c.text('服务器内部错误', 500);
   }
 });
 
+// 移除生产环境的 API 路由预加载(已在统一中间件中处理)
+// 移除动态 API 路由中间件(已合并到统一中间件中)
+
 // 启动服务器
 console.log('准备启动服务器...');
 parentServer.listen(port, () => {  

+ 3 - 0
src/server/index.tsx

@@ -23,3 +23,6 @@ export const template = renderToStaticMarkup(
   <Rooter />
 );
 
+
+// 导出 API 路由
+export { default as api } from './api';