import type { IncomingMessage, ServerResponse } from 'http'; import fs from 'fs'; import path from 'path'; import { encodeRoutePath, normalizePath } from './pathNormalizer'; import { logVirtualHtmlDebug, logVirtualHtmlWarn } from '../logger'; import { createPreviewHostModuleCode, createPreviewHostOptions, replacePreviewLoaderScript, } from '../previewHost'; type HtmlResponder = (html: string, transformUrl?: string) => Promise; function replaceDevTemplateBootstrapScript(html: string, bootstrapImportPath: string): string { return html.replace( / `, ); } export async function handleIndexHtml( req: IncomingMessage, res: ServerResponse, devTemplate: string, htmlTemplate: string, respondHtml: HtmlResponder, ): Promise { if (!req.url) return false; // 先尝试标准化路径 const normalized = normalizePath(req.url); // 只处理预览请求(action === 'preview') if (normalized && normalized.action === 'preview') { const { type, name, versionId } = normalized; logVirtualHtmlDebug('预览请求:', normalized.originalUrl, '→', normalized.normalizedUrl); if (['components', 'prototypes', 'themes'].includes(type)) { const urlPath = encodeRoutePath(`/${type}/${name}`); const moduleImportPath = `/${type}/${name}`; let tsxPath: string; let basePath: string; // 如果有版本参数,从 Git 版本目录读取 if (versionId) { const gitVersionsDir = path.resolve(process.cwd(), '.git-versions', versionId); basePath = path.join(gitVersionsDir, 'src', type, name); tsxPath = path.join(basePath, 'index.tsx'); logVirtualHtmlDebug('从 Git 版本读取:', versionId, tsxPath); } else { // 否则从当前工作目录读取 basePath = path.resolve(process.cwd(), 'src', type, name); tsxPath = path.join(basePath, 'index.tsx'); } logVirtualHtmlDebug('检查 TSX 文件:', tsxPath, '存在:', fs.existsSync(tsxPath)); if (fs.existsSync(tsxPath)) { const typeLabel = type === 'components' ? 'Component' : type === 'prototypes' ? 'Prototype' : 'Theme'; const title = versionId ? `${typeLabel}: ${name} (版本: ${versionId}) - Dev Preview` : `${typeLabel}: ${name} - Dev Preview`; // Vite 的 html-proxy/import-analysis 在虚拟 HTML 模块里解析 import 时, // 对包含中文目录名的百分号编码路径兼容性不稳定。这里保留页面 URL 为编码形式, // 但模块 import 使用原始路由路径,让 Vite 能正确映射到 src 下的真实文件。 const entryImportPath = versionId ? `/@fs/${tsxPath}` : `${moduleImportPath}/index.tsx`; const hackCssPath = path.resolve(process.cwd(), 'src', type, name, 'hack.css'); const previewHostModuleCode = createPreviewHostModuleCode( createPreviewHostOptions({ type, name, entryImportPath, versionId, initialHackCssEnabled: fs.existsSync(hackCssPath), }), ); const bootstrapModulePath = path.resolve(process.cwd(), 'admin', 'assets', 'dev-template-bootstrap.js') .split(path.sep) .join('/'); let html = devTemplate.replace(/\{\{TITLE\}\}/g, title); html = replacePreviewLoaderScript(html, previewHostModuleCode); html = replaceDevTemplateBootstrapScript(html, `/@fs/${bootstrapModulePath}`); // 🔥 添加 标签来修正相对路径基准(重要!) // 新路径格式 /prototypes/ref-antd 会被浏览器当作目录,导致相对路径解析错误 // 添加 可以修正这个问题 const baseHref = `${urlPath}/`; html = html.replace('', ` \n `); if (fs.existsSync(hackCssPath)) { logVirtualHtmlDebug('注入 hack.css:', hackCssPath); html = html.replace( '', ` \n `, ); } logVirtualHtmlDebug('返回虚拟 HTML:', normalized.normalizedUrl); // 交给 Vite 转换 HTML 时需要使用一个稳定的 .html 虚拟地址。 // 当前版本与历史版本不能共用同一个 transformUrl,否则 Vite 的 html-proxy // 会复用当前页面的内联脚本,导致历史版本页面又加载回当前源码。 // 这里用一个仅供 transformIndexHtml 使用的虚拟路径段隔离不同版本, // 避免在 URL 查询参数里追加 ver 触发双问号问题。 const transformUrl = versionId ? `${urlPath}/__axhub_version__/${versionId}/index.html` : `${urlPath}/index.html`; await respondHtml(html, transformUrl); return true; } else if (versionId) { // 版本文件不存在 const errorHtml = ` 版本不存在

❌ 版本文件不存在

版本 ${versionId} 的文件未找到。

可能的原因:

1. 版本文件尚未提取
2. 该版本不包含此页面
3. 服务器已重启,临时文件被清理

解决方法:

请先调用 /api/git/build-version 接口提取版本文件。

`; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.statusCode = 404; res.end(errorHtml); return true; } } return false; } // 兼容旧的 .html 路径检查(如果标准化失败) if (req.url?.includes('/index.html')) { const [urlWithoutQuery, queryString] = req.url.split('?'); const urlPath = urlWithoutQuery.replace('/index.html', ''); const pathParts = urlPath.split('/').filter(Boolean); const params = new URLSearchParams(queryString || ''); const versionId = params.get('ver'); logVirtualHtmlDebug('旧格式请求路径:', req.url, '解析部分:', pathParts); if (pathParts.length >= 2 && ['components', 'prototypes', 'themes'].includes(pathParts[0])) { // 这种情况应该已经被标准化处理了,如果到这里说明有问题 logVirtualHtmlWarn('未被标准化处理的路径:', req.url); } } return false; }