import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import { fileURLToPath, URL } from 'node:url' // In dev, Vite serves the SPA with HMR and proxies /api to the Go backend. The // backend listens on PANSY_PORT (default 8080); read it so the proxy target // tracks an overridden port. The production build is embedded into the Go binary // (see internal/webdist), so no proxy is involved there. export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') const backendPort = env.PANSY_PORT || process.env.PANSY_PORT || '8080' return { plugins: [react(), tailwindcss()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, server: { port: 5173, proxy: { '/api': { target: `http://127.0.0.1:${backendPort}`, changeOrigin: true, }, }, }, build: { outDir: 'dist', sourcemap: true, rollupOptions: { output: { // The app-wide core goes in ONE cached vendor chunk (rarely changes, so // it survives deploys while the tiny app chunk churns). react + react-dom // + scheduler MUST stay together — splitting react-dom into its own chunk // reorders module init across chunk boundaries and breaks React 19 at // load ("Cannot set 'Activity' of undefined"). Everything else — the // gesture engine, the assistant's markdown renderer + its ecosystem — // rides with whatever imports it, so a lazily-loaded route/feature keeps // it out of the eager first paint. The routes are code-split via // React.lazy (router.tsx), where the real first-paint win is. manualChunks(id) { if (!id.includes('node_modules')) return undefined if ( /[/\\]node_modules[/\\](react|react-dom|scheduler|@tanstack|zustand|zod|clsx|tailwind-merge)[/\\]/.test( id, ) ) { return 'vendor' } return undefined }, }, }, }, } })