|
@@ -51,8 +51,10 @@ export function previewProxy(req: Request, res: Response): void {
|
|
|
},
|
|
},
|
|
|
(proxyRes) => {
|
|
(proxyRes) => {
|
|
|
const contentType = (proxyRes.headers["content-type"] || "") as string;
|
|
const contentType = (proxyRes.headers["content-type"] || "") as string;
|
|
|
- const isHtml = contentType.includes("text/html");
|
|
|
|
|
- console.log(`[preview:proxy] response ${proxyRes.statusCode} content-type=${contentType} isHtml=${isHtml}`);
|
|
|
|
|
|
|
+ const isText = contentType.includes("text/html") ||
|
|
|
|
|
+ contentType.includes("text/javascript") ||
|
|
|
|
|
+ contentType.includes("application/javascript");
|
|
|
|
|
+ console.log(`[preview:proxy] response ${proxyRes.statusCode} content-type=${contentType} isText=${isText}`);
|
|
|
|
|
|
|
|
// 复制响应 headers(过滤 hop-by-hop)
|
|
// 复制响应 headers(过滤 hop-by-hop)
|
|
|
const resHeaders: Record<string, string> = {};
|
|
const resHeaders: Record<string, string> = {};
|
|
@@ -65,26 +67,29 @@ export function previewProxy(req: Request, res: Response): void {
|
|
|
|
|
|
|
|
res.status(proxyRes.statusCode || 200);
|
|
res.status(proxyRes.statusCode || 200);
|
|
|
|
|
|
|
|
- if (isHtml) {
|
|
|
|
|
|
|
+ if (isText) {
|
|
|
const chunks: Buffer[] = [];
|
|
const chunks: Buffer[] = [];
|
|
|
proxyRes.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
proxyRes.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
|
proxyRes.on("end", () => {
|
|
proxyRes.on("end", () => {
|
|
|
- let html = Buffer.concat(chunks).toString("utf-8");
|
|
|
|
|
|
|
+ let body = Buffer.concat(chunks).toString("utf-8");
|
|
|
|
|
|
|
|
- // 将 src="/..." 和 href="/..." 重写为 /ads/preview/...
|
|
|
|
|
- // 匹配 root-absolute 路径:以 / 开头,后跟非 / 字符(避免匹配 //)
|
|
|
|
|
- html = html.replace(
|
|
|
|
|
- /(src|href)="\/([^/][^"]*)"/g,
|
|
|
|
|
- '$1="/ads/preview/$2"',
|
|
|
|
|
|
|
+ // 重写 HTML (src="/...", href="/...") 和 JS (from "/...", import "/...")
|
|
|
|
|
+ // 为 /ads/preview/...(跳过已有前缀的,支持单双引号)
|
|
|
|
|
+ body = body.replace(
|
|
|
|
|
+ /((?:src|href)=["']|(?:from|import)\s*["'])\/(?!ads\/preview\/)([^"']+)(["'])/g,
|
|
|
|
|
+ '$1/ads/preview/$2$3',
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // 日志:检查重写后的 @vite/client 行
|
|
|
|
|
- const viteClientMatch = html.match(/src="[^"]*@vite\/client[^"]*"/);
|
|
|
|
|
- console.log(`[preview:proxy] HTML vite/client: ${viteClientMatch?.[0] || "NOT FOUND"}`);
|
|
|
|
|
|
|
+ // 日志
|
|
|
|
|
+ if (contentType.includes("text/html")) {
|
|
|
|
|
+ const viteClientMatch = body.match(/src="[^"]*@vite\/client[^"]*"/);
|
|
|
|
|
+ console.log(`[preview:proxy] HTML vite/client: ${viteClientMatch?.[0] || "NOT FOUND"}`);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const body = Buffer.from(html, "utf-8");
|
|
|
|
|
- resHeaders["content-type"] = "text/html; charset=utf-8";
|
|
|
|
|
- resHeaders["content-length"] = String(body.length);
|
|
|
|
|
|
|
+ resHeaders["content-type"] = contentType.includes("text/html")
|
|
|
|
|
+ ? "text/html; charset=utf-8"
|
|
|
|
|
+ : contentType;
|
|
|
|
|
+ resHeaders["content-length"] = String(Buffer.byteLength(body, "utf-8"));
|
|
|
res.set(resHeaders);
|
|
res.set(resHeaders);
|
|
|
res.send(body);
|
|
res.send(body);
|
|
|
});
|
|
});
|