Files
pansy/web/vite.config.ts
T
steveandClaude Opus 4.8 79df0df53f
Build image / build-and-push (push) Successful in 10s
Address code-split review: chunk-load recovery + tidier lazy + gesture split
Gadfly on #106:

- (2 models) React.lazy memoizes a REJECTED import, so after a deploy (every
  main push) a still-open app references chunk hashes the server just
  replaced — the import 404s and RouteError's "Try again" can never recover.
  New lazyPage() helper reloads once on a chunk-load failure to fetch fresh
  hashes (session-flag guarded against a reload loop; cleared on success).
- (perf) @use-gesture is editor-only, but the single vendor chunk pulled it
  into the eager first paint. Exclude it from vendor so it rides with the
  lazy editor chunk (vendor 421→392 KB; the gesture code moved into the
  editor's own chunk). react/react-dom/tanstack still share one vendor chunk
  — splitting react-dom out is what broke React 19 at load.
- (nits) lazyPage also unwraps the named export, so the five route lazies are
  uniform one-liners; moved the lazy block below the import group.

Re-verified live: app mounts, /gardens/1 lazy-loads + renders the editor,
console clean. tsc + build green, no >500 KB warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 02:17:55 -04:00

56 lines
2.2 KiB
TypeScript

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: {
// One vendor chunk for ALL node_modules: it's rarely-changing, so it
// caches across app deploys while the tiny app chunk churns. Kept as a
// SINGLE chunk on purpose — splitting react-dom/scheduler into their own
// chunk reorders their module init across chunk boundaries and breaks
// React 19 at load ("Cannot set 'Activity' of undefined"). The heavy
// routes are code-split separately via React.lazy (router.tsx), which is
// where the real first-paint win is.
manualChunks(id) {
if (!id.includes('node_modules')) return undefined
// Editor-only libs (the gesture engine) ride with the lazy editor
// chunk instead of the eager vendor one, so the first paint doesn't
// pay for code only the canvas needs. Everything else — react, tanstack
// and the rest — stays in ONE vendor chunk; splitting react-dom out
// reorders its init across chunks and breaks React 19 at load.
if (id.includes('@use-gesture')) return undefined
return 'vendor'
},
},
},
},
}
})