previewProxy.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.previewProxy = previewProxy;
  7. const http_1 = __importDefault(require("http"));
  8. const VITE_HOST = "127.0.0.1";
  9. const VITE_PORT = 5199;
  10. // 这些 header 需要从代理响应中剔除(hop-by-hop)
  11. const HOP_BY_HOP = new Set([
  12. "transfer-encoding",
  13. "connection",
  14. "keep-alive",
  15. "proxy-connection",
  16. "proxy-authenticate",
  17. "proxy-authorization",
  18. "te",
  19. "trailers",
  20. "upgrade",
  21. ]);
  22. /**
  23. * 将 /preview/* 请求代理到 Vite dev server。
  24. * 对 HTML 响应自动重写路径,给所有根绝对路径加上 /ads/preview/ 前缀,
  25. * 确保浏览器通过 nginx 正确加载资源。
  26. */
  27. function previewProxy(req, res) {
  28. // Express mount 会剥离 /preview 前缀,req.url 即 Vite 需要的路径
  29. const targetPath = req.url || "/";
  30. console.log(`[preview:proxy] ${req.method} ${targetPath}`);
  31. // 过滤请求 headers(去掉 hop-by-hop、条件缓存头,设置正确的 host)
  32. const reqHeaders = {};
  33. for (const [key, value] of Object.entries(req.headers)) {
  34. if (value === undefined)
  35. continue;
  36. const lk = key.toLowerCase();
  37. if (lk === "host")
  38. continue;
  39. if (lk === "connection" || lk === "keep-alive" || lk === "upgrade")
  40. continue;
  41. // 强制完整响应,避免 304 缓存导致 HTML 无法被重写
  42. if (lk === "if-none-match" || lk === "if-modified-since" || lk === "cache-control")
  43. continue;
  44. reqHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
  45. }
  46. reqHeaders["host"] = `${VITE_HOST}:${VITE_PORT}`;
  47. reqHeaders["cache-control"] = "no-cache";
  48. const proxyReq = http_1.default.request({
  49. hostname: VITE_HOST,
  50. port: VITE_PORT,
  51. path: targetPath,
  52. method: req.method,
  53. headers: reqHeaders,
  54. }, (proxyRes) => {
  55. const contentType = (proxyRes.headers["content-type"] || "");
  56. const isText = contentType.includes("text/html") ||
  57. contentType.includes("text/javascript") ||
  58. contentType.includes("application/javascript");
  59. console.log(`[preview:proxy] response ${proxyRes.statusCode} content-type=${contentType} isText=${isText}`);
  60. // 复制响应 headers(过滤 hop-by-hop)
  61. const resHeaders = {};
  62. for (const [key, value] of Object.entries(proxyRes.headers)) {
  63. if (HOP_BY_HOP.has(key.toLowerCase()))
  64. continue;
  65. if (value !== undefined && value !== null) {
  66. resHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
  67. }
  68. }
  69. res.status(proxyRes.statusCode || 200);
  70. if (isText) {
  71. const chunks = [];
  72. proxyRes.on("data", (chunk) => chunks.push(chunk));
  73. proxyRes.on("end", () => {
  74. let body = Buffer.concat(chunks).toString("utf-8");
  75. // 重写 HTML (src="/...", href="/...") 和 JS (from "/...", import "/...")
  76. // 为 /ads/preview/...(跳过已有前缀的,支持单双引号)
  77. body = body.replace(/((?:src|href)=["']|(?:from|import)\s*["'])\/(?!ads\/preview\/)([^"']+)(["'])/g, '$1/ads/preview/$2$3');
  78. // 日志
  79. if (contentType.includes("text/html")) {
  80. const viteClientMatch = body.match(/src="[^"]*@vite\/client[^"]*"/);
  81. console.log(`[preview:proxy] HTML vite/client: ${viteClientMatch?.[0] || "NOT FOUND"}`);
  82. }
  83. resHeaders["content-type"] = contentType.includes("text/html")
  84. ? "text/html; charset=utf-8"
  85. : contentType;
  86. resHeaders["content-length"] = String(Buffer.byteLength(body, "utf-8"));
  87. res.set(resHeaders);
  88. res.send(body);
  89. });
  90. }
  91. else {
  92. res.set(resHeaders);
  93. proxyRes.pipe(res);
  94. }
  95. });
  96. proxyReq.on("error", (err) => {
  97. console.error(`[preview:proxy] ${err.message}`);
  98. if (!res.headersSent) {
  99. res.status(502).json({ error: "Preview server not available" });
  100. }
  101. });
  102. // POST/PUT/PATCH 请求需要转发 body
  103. if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
  104. req.pipe(proxyReq);
  105. }
  106. else {
  107. proxyReq.end();
  108. }
  109. }
  110. //# sourceMappingURL=previewProxy.js.map