返回 DeepSeek-Reasonix
vite.config.ts
根目录 / desktop / frontend / vite.config.ts
1 import { defineConfig, searchForWorkspaceRoot, type Plugin } from "vite";
2 import react from "@vitejs/plugin-react";
3 import { execSync } from "node:child_process";
4 import { mkdir, readdir, rename, writeFile } from "node:fs/promises";
5 import { dirname, resolve } from "node:path";
6 import { fileURLToPath } from "node:url";
7
8 const devPort = Number(process.env.REASONIX_DESKTOP_VITE_PORT || "5173");
9 const configDir = dirname(fileURLToPath(import.meta.url));
10
11 // Stamps the build commit into the bundle so a minified crash stack can be mapped
12 // back to the sourcemap of the exact build. Falls back to "dev" off a git checkout.
13 function buildCommit(): string {
14 if (process.env.REASONIX_COMMIT) return process.env.REASONIX_COMMIT;
15 try {
16 return execSync("git rev-parse --short HEAD", { cwd: configDir }).toString().trim();
17 } catch {
18 return "dev";
19 }
20 }
21
22 function buildChannel(): string {
23 return process.env.REASONIX_CHANNEL || "stable";
24 }
25
26 // On macOS ≤ 12 (Safari 15 WebKit) a crossorigin module/stylesheet fetched over the
27 // wails:// scheme is CORS-blocked (no Access-Control-Allow-Origin from the handler),
28 // so the bundle never loads and the window paints blank; newer WebKit tolerates it.
29 function stripCrossorigin(): Plugin {
30 return {
31 name: "strip-crossorigin",
32 enforce: "post",
33 transformIndexHtml: (html) => html.replace(/\s+crossorigin(?==["']|[\s/>])/g, ""),
34 };
35 }
36
37 function archiveHiddenSourcemaps(commit: string): Plugin {
38 async function collectMapFiles(dir: string): Promise<string[]> {
39 const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
40 const files: string[] = [];
41 for (const entry of entries) {
42 const p = resolve(dir, entry.name);
43 if (entry.isDirectory()) files.push(...(await collectMapFiles(p)));
44 else if (entry.isFile() && entry.name.endsWith(".map")) files.push(p);
45 }
46 return files;
47 }
48
49 return {
50 name: "archive-hidden-sourcemaps",
51 apply: "build",
52 closeBundle: async () => {
53 const distDir = resolve(configDir, "dist");
54 const maps = await collectMapFiles(distDir);
55 if (!maps.length) return;
56
57 const archiveDir = resolve(configDir, "sourcemaps", commit);
58 await mkdir(archiveDir, { recursive: true });
59 await Promise.all(
60 maps.map(async (mapPath) => {
61 const rel = mapPath.slice(distDir.length + 1).replace(/[\\/]+/g, "__");
62 await rename(mapPath, resolve(archiveDir, rel));
63 }),
64 );
65 await writeFile(
66 resolve(archiveDir, "manifest.json"),
67 JSON.stringify({ commit, channel: buildChannel(), archivedAt: new Date().toISOString() }, null, 2) + "\n",
68 );
69 },
70 };
71 }
72
73 // Vite must empty dist before production builds so stale hashed assets disappear.
74 // Recreate the tracked placeholder afterwards so git status stays clean and
75 // Go's //go:embed all:frontend/dist still works on a fresh checkout.
76 function keepDistPlaceholder(): Plugin {
77 return {
78 name: "keep-dist-placeholder",
79 apply: "build",
80 closeBundle: async () => {
81 const distDir = resolve(configDir, "dist");
82 await mkdir(distDir, { recursive: true });
83 await writeFile(resolve(distDir, ".gitkeep"), "\n");
84 },
85 };
86 }
87
88 const commit = buildCommit();
89 const channel = buildChannel();
90
91 const nodeModulePath = String.raw`[\\/]node_modules[\\/](?:\.pnpm[\\/][^\\/]+[\\/]node_modules[\\/])?`;
92 const vendorReact = new RegExp(`${nodeModulePath}(?:react|react-dom)(?:[\\/]|$)`);
93 const vendorMarkdown = new RegExp(
94 `${nodeModulePath}(?:react-markdown|remark-gfm|remark-math|rehype-katex|katex)(?:[\\/]|$)`,
95 );
96 const vendorHighlight = new RegExp(`${nodeModulePath}highlight\\.js(?:[\\/]|$)`);
97
98 // base: "./" so built asset URLs are relative. Wails serves the embedded dist from
99 // the app root over the wails:// scheme, where absolute "/assets/..." URLs 404.
100 export default defineConfig({
101 // errorRecovery tells lightningcss to skip unparseable rules instead of
102 // failing the whole build. Vite 8 + lightningcss 1.32.0 can reject valid
103 // @keyframes in concatenated CSS bundles (heartbeat.css + styles.css).
104 css: {
105 lightningcss: { errorRecovery: true },
106 },
107 plugins: [react(), stripCrossorigin(), archiveHiddenSourcemaps(commit), keepDistPlaceholder()],
108 base: "./",
109 define: { __BUILD_COMMIT__: JSON.stringify(commit), __BUILD_CHANNEL__: JSON.stringify(channel) },
110 build: {
111 outDir: "dist",
112 emptyOutDir: true,
113 sourcemap: "hidden",
114 target: "es2021",
115 // Use terser for smaller output (esbuild is faster to build but produces
116 // larger bundles). Disabled for dev builds via the default.
117 minify: "terser",
118 terserOptions: {
119 compress: {
120 // Keep warn/error so crash breadcrumbs still capture them; drop the noise.
121 drop_console: ["log", "debug", "info", "trace"],
122 passes: 2,
123 },
124 // Preserve names so minified crash stacks stay readable.
125 keep_classnames: true,
126 keep_fnames: true,
127 },
128 rolldownOptions: {
129 output: {
130 // Manual chunk splitting: keep the heavy markdown/math/code pipeline
131 // in a separate chunk so it can be cached independently from the
132 // app shell. The vendor chunk splits react+react-dom (stable, rarely
133 // changes) from the markdown stack (changes more often).
134 codeSplitting: {
135 groups: [
136 { name: "vendor-react", test: vendorReact },
137 { name: "vendor-markdown", test: vendorMarkdown },
138 { name: "vendor-highlight", test: vendorHighlight },
139 ],
140 },
141 },
142 },
143 // Raise the warning limit — the markdown vendor chunk is legitimately large
144 // (katex alone is ~300KB). The manual split ensures it's cached separately.
145 chunkSizeWarningLimit: 600,
146 },
147 server: {
148 // Bind IPv4 — unset host listens on ::1, and the Wails dev proxy's [::1]
149 // dial fails on Windows hosts where IPv6 loopback is filtered.
150 host: "127.0.0.1",
151 port: devPort,
152 strictPort: true,
153 fs: {
154 // Browser-dev theme mocks use the same embedded source assets as Wails.
155 // Keep the allow-list narrow while retaining Vite's workspace root.
156 allow: [searchForWorkspaceRoot(configDir), resolve(configDir, "../themes/official")],
157 },
158 },
159 });
160
160 lines TYPESCRIPT