import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import fs from "node:fs"; import path from "node:path"; import { defineConfig, type Plugin } from "vite"; import { vitePluginManusRuntime } from "vite-plugin-manus-runtime"; const PROJECT_ROOT = import.meta.dirname; /** Dev: / → i.modern.html; /i.html → UA router entry */ function vitePluginModernDevEntry(): Plugin { return { name: "modern-dev-entry", apply: "serve", configureServer(server) { server.middlewares.use((req, _res, next) => { const url = (req.url ?? "").split("?")[0]; if (url === "/" || url === "/index.html") { req.url = "/i.modern.html"; } next(); }); }, }; } /** 构建后将统一入口 i.html 复制到 dist/ 根目录 */ function vitePluginCopyRouterEntry(): Plugin { const src = path.resolve(import.meta.dirname, "client", "i.html"); const dest = path.resolve(import.meta.dirname, "dist", "i.html"); return { name: "copy-router-entry", closeBundle() { if (process.env.NODE_ENV !== "production") return; const distRoot = path.resolve(import.meta.dirname, "dist"); if (!fs.existsSync(distRoot)) fs.mkdirSync(distRoot, { recursive: true }); fs.copyFileSync(src, dest); }, }; } const plugins = [ vitePluginModernDevEntry(), react(), tailwindcss(), jsxLocPlugin(), vitePluginManusRuntime(), vitePluginCopyRouterEntry(), ]; export default defineConfig({ plugins, base: process.env.NODE_ENV === "production" ? "/x8/v2/modern/" : "/", resolve: { alias: { "@": path.resolve(import.meta.dirname, "client", "src"), "@shared": path.resolve(import.meta.dirname, "shared"), "@assets": path.resolve(import.meta.dirname, "attached_assets"), }, dedupe: ["react", "react-dom"], }, envDir: path.resolve(import.meta.dirname), root: path.resolve(import.meta.dirname, "client"), publicDir: path.resolve(import.meta.dirname, "client/public"), // 性能优化 build: { target: 'esnext', cssTarget: 'esnext', outDir: path.resolve(import.meta.dirname, "dist/modern"), emptyOutDir: true, minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, }, }, rollupOptions: { input: { i: path.resolve(import.meta.dirname, "client", "i.modern.html"), }, output: { manualChunks(id) { if (!id.includes("node_modules")) return; // Only split echarts (lazy EQ page). Do not manually chunk react, radix, lucide, // or ai-markdown — forced splits hoist shared helpers and break React.forwardRef. if (id.includes("/echarts/")) return "echarts-vendor"; }, }, }, // streamdown pulls full @shikijs/langs (~7 MB) + themes + mermaid; lazy-loaded with AIPage. chunkSizeWarningLimit: 15000, cssCodeSplit: true, }, server: { port: 3000, strictPort: false, // Will find next available port if 3000 is busy host: true, // Browser requests with Origin are rejected (403) by the cloud API; proxy in dev avoids CORS/WAF. proxy: { "/luxsin-audio-api": { target: "https://api.luxsin.com.cn", changeOrigin: true, rewrite: (p) => p.replace(/^\/luxsin-audio-api/, "/audio"), }, }, allowedHosts: [ ".manuspre.computer", ".manus.computer", ".manus-asia.computer", ".manuscomputer.ai", ".manusvm.computer", "localhost", "127.0.0.1", ], fs: { strict: true, deny: ["**/.*"], }, }, });