247 lines
7.4 KiB
TypeScript
247 lines
7.4 KiB
TypeScript
// import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc"; // 禁用:与 R3F 冲突
|
||
import tailwindcss from "@tailwindcss/vite";
|
||
import react from "@vitejs/plugin-react";
|
||
import fs from "node:fs";
|
||
import { createRequire } from "node:module";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import { defineConfig, type Plugin, type ViteDevServer } from "vite";
|
||
|
||
// =============================================================================
|
||
// Manus Debug Collector - Vite Plugin
|
||
// Writes browser logs directly to files, trimmed when exceeding size limit
|
||
// =============================================================================
|
||
|
||
const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
||
/** 从仓库根解析依赖(Vite root 在 client/ 时,部分环境下需显式指向根目录 node_modules) */
|
||
const requireFromRoot = createRequire(import.meta.url);
|
||
const LOG_DIR = path.join(PROJECT_ROOT, ".manus-logs");
|
||
const MAX_LOG_SIZE_BYTES = 1 * 1024 * 1024; // 1MB per log file
|
||
const TRIM_TARGET_BYTES = Math.floor(MAX_LOG_SIZE_BYTES * 0.6); // Trim to 60% to avoid constant re-trimming
|
||
|
||
type LogSource = "browserConsole" | "networkRequests" | "sessionReplay";
|
||
|
||
function ensureLogDir() {
|
||
if (!fs.existsSync(LOG_DIR)) {
|
||
fs.mkdirSync(LOG_DIR, { recursive: true });
|
||
}
|
||
}
|
||
|
||
function trimLogFile(logPath: string, maxSize: number) {
|
||
try {
|
||
if (!fs.existsSync(logPath) || fs.statSync(logPath).size <= maxSize) {
|
||
return;
|
||
}
|
||
|
||
const lines = fs.readFileSync(logPath, "utf-8").split("\n");
|
||
const keptLines: string[] = [];
|
||
let keptBytes = 0;
|
||
|
||
// Keep newest lines (from end) that fit within 60% of maxSize
|
||
const targetSize = TRIM_TARGET_BYTES;
|
||
for (let i = lines.length - 1; i >= 0; i--) {
|
||
const lineBytes = Buffer.byteLength(`${lines[i]}\n`, "utf-8");
|
||
if (keptBytes + lineBytes > targetSize) break;
|
||
keptLines.unshift(lines[i]);
|
||
keptBytes += lineBytes;
|
||
}
|
||
|
||
fs.writeFileSync(logPath, keptLines.join("\n"), "utf-8");
|
||
} catch {
|
||
/* ignore trim errors */
|
||
}
|
||
}
|
||
|
||
function writeToLogFile(source: LogSource, entries: unknown[]) {
|
||
if (entries.length === 0) return;
|
||
|
||
ensureLogDir();
|
||
const logPath = path.join(LOG_DIR, `${source}.log`);
|
||
|
||
// Format entries with timestamps
|
||
const lines = entries.map((entry) => {
|
||
const ts = new Date().toISOString();
|
||
return `[${ts}] ${JSON.stringify(entry)}`;
|
||
});
|
||
|
||
// Append to log file
|
||
fs.appendFileSync(logPath, `${lines.join("\n")}\n`, "utf-8");
|
||
|
||
// Trim if exceeds max size
|
||
trimLogFile(logPath, MAX_LOG_SIZE_BYTES);
|
||
}
|
||
|
||
/**
|
||
* Vite plugin to collect browser debug logs
|
||
* - POST /__manus__/logs: Browser sends logs, written directly to files
|
||
* - Files: browserConsole.log, networkRequests.log, sessionReplay.log
|
||
* - Auto-trimmed when exceeding 1MB (keeps newest entries)
|
||
*/
|
||
function vitePluginManusDebugCollector(): Plugin {
|
||
return {
|
||
name: "manus-debug-collector",
|
||
|
||
transformIndexHtml(html) {
|
||
if (process.env.NODE_ENV === "production") {
|
||
return html;
|
||
}
|
||
return {
|
||
html,
|
||
tags: [
|
||
{
|
||
tag: "script",
|
||
attrs: {
|
||
src: "/__manus__/debug-collector.js",
|
||
defer: true,
|
||
},
|
||
injectTo: "head",
|
||
},
|
||
],
|
||
};
|
||
},
|
||
|
||
configureServer(server: ViteDevServer) {
|
||
// POST /__manus__/logs: Browser sends logs (written directly to files)
|
||
server.middlewares.use("/__manus__/logs", (req, res, next) => {
|
||
if (req.method !== "POST") {
|
||
return next();
|
||
}
|
||
|
||
const handlePayload = (payload: any) => {
|
||
// Write logs directly to files
|
||
if (payload.consoleLogs?.length > 0) {
|
||
writeToLogFile("browserConsole", payload.consoleLogs);
|
||
}
|
||
if (payload.networkRequests?.length > 0) {
|
||
writeToLogFile("networkRequests", payload.networkRequests);
|
||
}
|
||
if (payload.sessionEvents?.length > 0) {
|
||
writeToLogFile("sessionReplay", payload.sessionEvents);
|
||
}
|
||
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ success: true }));
|
||
};
|
||
|
||
const reqBody = (req as { body?: unknown }).body;
|
||
if (reqBody && typeof reqBody === "object") {
|
||
try {
|
||
handlePayload(reqBody);
|
||
} catch (e) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ success: false, error: String(e) }));
|
||
}
|
||
return;
|
||
}
|
||
|
||
let body = "";
|
||
req.on("data", (chunk) => {
|
||
body += chunk.toString();
|
||
});
|
||
|
||
req.on("end", () => {
|
||
try {
|
||
const payload = JSON.parse(body);
|
||
handlePayload(payload);
|
||
} catch (e) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ success: false, error: String(e) }));
|
||
}
|
||
});
|
||
});
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 自定义插件:禁用完整页面刷新,保留 HMR
|
||
* 当模块无法热更新时,不会自动刷新页面
|
||
*/
|
||
function vitePluginNoFullReload(): Plugin {
|
||
return {
|
||
name: 'no-full-reload',
|
||
|
||
// 修改 HMR 客户端代码,阻止自动刷新
|
||
transformIndexHtml(html) {
|
||
if (process.env.NODE_ENV === 'production') {
|
||
return html;
|
||
}
|
||
|
||
return {
|
||
html,
|
||
tags: [
|
||
{
|
||
tag: 'script',
|
||
children: `
|
||
// 拦截并阻止 Vite HMR 的完整页面刷新
|
||
(function() {
|
||
const originalReload = window.location.reload;
|
||
let reloadBlocked = false;
|
||
|
||
window.location.reload = function() {
|
||
if (!reloadBlocked) {
|
||
console.warn('[HMR] Page reload blocked. Manual refresh required if needed.');
|
||
reloadBlocked = true;
|
||
setTimeout(() => { reloadBlocked = false; }, 1000);
|
||
}
|
||
// 不执行实际刷新
|
||
};
|
||
})();
|
||
`,
|
||
injectTo: 'head',
|
||
},
|
||
],
|
||
};
|
||
},
|
||
};
|
||
}
|
||
|
||
// 注意:jsxLocPlugin 与 React Three Fiber 不兼容,已禁用
|
||
const plugins = [
|
||
react(),
|
||
tailwindcss(),
|
||
// jsxLocPlugin(), // 禁用:与 R3F 冲突,会导致 "Cannot set data-loc" 错误
|
||
vitePluginManusDebugCollector(),
|
||
vitePluginNoFullReload(), // 禁用自动刷新
|
||
];
|
||
|
||
export default defineConfig({
|
||
plugins,
|
||
resolve: {
|
||
alias: {
|
||
"@": path.resolve(PROJECT_ROOT, "client", "src"),
|
||
"@shared": path.resolve(PROJECT_ROOT, "shared"),
|
||
"@assets": path.resolve(PROJECT_ROOT, "attached_assets"),
|
||
delaunator: requireFromRoot.resolve("delaunator"),
|
||
"robust-predicates": requireFromRoot.resolve("robust-predicates"),
|
||
},
|
||
},
|
||
envDir: path.resolve(PROJECT_ROOT),
|
||
root: path.resolve(PROJECT_ROOT, "client"),
|
||
publicDir: path.resolve(PROJECT_ROOT, "client", "public"),
|
||
build: {
|
||
outDir: path.resolve(PROJECT_ROOT, "dist/public"),
|
||
emptyOutDir: true,
|
||
},
|
||
server: {
|
||
host: true,
|
||
allowedHosts: [
|
||
".manuspre.computer",
|
||
".manus.computer",
|
||
".manus-asia.computer",
|
||
".manuscomputer.ai",
|
||
".manusvm.computer",
|
||
"localhost",
|
||
"127.0.0.1",
|
||
"npsx.i-llusion.cn",
|
||
],
|
||
fs: {
|
||
strict: true,
|
||
deny: ["**/.*"],
|
||
allow: [PROJECT_ROOT, path.join(PROJECT_ROOT, "node_modules")],
|
||
},
|
||
hmr: false,
|
||
watch: null,
|
||
},
|
||
});
|