server.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import fs from 'node:fs/promises';
  2. import http from 'node:http';
  3. import { URL } from 'node:url';
  4. import { Transform } from 'node:stream';
  5. // Constants
  6. const isProduction = process.env.NODE_ENV === 'production';
  7. const port = process.env.PORT || 5173;
  8. const base = process.env.BASE || '/';
  9. const ABORT_DELAY = 10000;
  10. // 解析基础路径为 URL 对象,方便后续处理
  11. const baseUrl = new URL(base, `http://localhost:${port}`);
  12. // Cached production assets
  13. let templateHtml = '';
  14. if (isProduction) {
  15. templateHtml = await fs.readFile('./dist/client/index.html', 'utf-8');
  16. }
  17. // 生产环境中间件
  18. let compressionMiddleware;
  19. let sirvMiddleware;
  20. if (isProduction) {
  21. compressionMiddleware = (await import('compression')).default();
  22. sirvMiddleware = (await import('sirv')).default('./dist/client', {
  23. extensions: [],
  24. baseUrl: base
  25. });
  26. }
  27. // Vite 开发服务器
  28. /** @type {import('vite').ViteDevServer | undefined} */
  29. let vite;
  30. if (!isProduction) {
  31. const { createServer } = await import('vite');
  32. vite = await createServer({
  33. server: { middlewareMode: true },
  34. appType: 'custom',
  35. base,
  36. });
  37. }
  38. // 处理请求的函数
  39. async function handleRequest(req, res) {
  40. try {
  41. const url = new URL(req.url, `http://${req.headers.host}`);
  42. const path = url.pathname;
  43. // 检查是否匹配基础路径
  44. if (!path.startsWith(baseUrl.pathname)) {
  45. res.writeHead(404, { 'Content-Type': 'text/plain' });
  46. res.end('Not found');
  47. return;
  48. }
  49. // 处理基础路径
  50. const normalizedUrl = path.replace(baseUrl.pathname, '/') || '/';
  51. // 开发环境:使用 Vite 中间件
  52. if (!isProduction && vite) {
  53. // 使用 Vite 中间件处理请求
  54. const handled = await new Promise((resolve) => {
  55. vite.middlewares(req, res, () => resolve(false));
  56. });
  57. // 如果 Vite 中间件已经处理了请求,直接返回
  58. if (handled) return;
  59. }
  60. // 生产环境:使用 compression 和 sirv 中间件
  61. else if (isProduction) {
  62. // 先尝试 compression 中间件
  63. const compressed = await new Promise((resolve) => {
  64. compressionMiddleware(req, res, () => resolve(false));
  65. });
  66. if (compressed) return;
  67. // 再尝试 sirv 中间件处理静态文件
  68. const served = await new Promise((resolve) => {
  69. sirvMiddleware(req, res, () => resolve(false));
  70. });
  71. if (served) return;
  72. }
  73. // 处理所有其他请求的 SSR 逻辑
  74. /** @type {string} */
  75. let template;
  76. /** @type {import('./src/entry-server.ts').render} */
  77. let render;
  78. if (!isProduction && vite) {
  79. // 开发环境:读取最新模板并转换
  80. template = await fs.readFile('./index.html', 'utf-8');
  81. template = await vite.transformIndexHtml(normalizedUrl, template);
  82. render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render;
  83. } else {
  84. // 生产环境:使用缓存的模板
  85. template = templateHtml;
  86. render = (await import('./dist/server/entry-server.js')).render;
  87. }
  88. let didError = false;
  89. const { pipe, abort } = render(normalizedUrl, {
  90. onShellError() {
  91. res.writeHead(500, { 'Content-Type': 'text/html' });
  92. res.end('<h1>Something went wrong</h1>');
  93. },
  94. onShellReady() {
  95. res.writeHead(didError ? 500 : 200, { 'Content-Type': 'text/html' });
  96. const transformStream = new Transform({
  97. transform(chunk, encoding, callback) {
  98. res.write(chunk, encoding);
  99. callback();
  100. },
  101. });
  102. const [htmlStart, htmlEnd] = template.split(`<!--app-html-->`);
  103. res.write(htmlStart);
  104. transformStream.on('finish', () => {
  105. res.end(htmlEnd);
  106. });
  107. pipe(transformStream);
  108. },
  109. onError(error) {
  110. didError = true;
  111. console.error(error);
  112. },
  113. });
  114. // 设置超时中止
  115. const abortTimeout = setTimeout(() => {
  116. abort();
  117. }, ABORT_DELAY);
  118. // 清理超时
  119. res.on('finish', () => {
  120. clearTimeout(abortTimeout);
  121. });
  122. } catch (e) {
  123. if (!isProduction && vite) {
  124. vite.ssrFixStacktrace(e);
  125. }
  126. console.error(e.stack);
  127. res.writeHead(500, { 'Content-Type': 'text/plain' });
  128. res.end(e.stack);
  129. }
  130. }
  131. // 创建 HTTP 服务器
  132. const server = http.createServer(handleRequest);
  133. // 启动服务器
  134. server.listen(port, () => {
  135. console.log(`Server started at http://localhost:${port}`);
  136. });
  137. // 处理服务器错误
  138. server.on('error', (err) => {
  139. console.error('Server error:', err);
  140. });