import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // Minimal typing for process.env so we don't need @types/node just for this. declare const process: { env: Record }; // Where a `vite dev` session sends its API calls. Playpen prefers 8420 and walks // upward if it's taken, so this is right unless you have two windows open — in // which case read the real one out of PLAYPEN_REVIEW_URL in any of its terminals. const apiPort = process.env.PLAYPEN_REVIEW_PORT || '8420'; const apiTarget = `http://127.0.0.1:${apiPort}`; export default defineConfig({ plugins: [react()], resolve: { // The syntax highlighter runs in a worker (see src/lib/highlight.ts), and one // package deep under refractor — decode-named-character-reference, pulled in // by the markup language to decode HTML entities — ships a browser build // that calls `document.createElement` at module scope. A worker has no // document, so importing refractor there threw before a line was highlighted. // // The package publishes a document-free build for exactly this case and // lists it first in its export map, under `worker`. Asking for that // condition is what picks it up. It resolves for the main bundle too, which // is what we want: it is the same function with a lookup table in place of // the DOM's entity parser, and one implementation beats two. conditions: ['worker'], }, // Relative asset URLs, because the page is served from a tab's path // (`/t/tab3/`) and not from the server root. With an absolute base the // browser would ask for `/assets/app.js` and get the tab router instead. base: './', server: { port: 5173, proxy: { // A tab's own API and event stream. Matched as a regex so the HTML at // `/t//` still comes from Vite — only the calls underneath it are // forwarded, which is what lets hot reloading work against a live review. // // Open http://localhost:5173/t// to develop against a real tab. '^/t/[^/]+/api': { target: apiTarget, changeOrigin: true }, // The app-wide endpoints: /api/tabs and /api/health. '/api': { target: apiTarget, changeOrigin: true }, }, }, build: { outDir: 'dist', emptyOutDir: true, // Fixed filenames rather than Vite's content hashes. The bundle is embedded // in the playpen binary (src/review/assets.zig), so the Zig side has to be // able to name the files at compile time — and cache busting buys nothing // for a bundle that only changes when the binary does. rollupOptions: { output: { entryFileNames: 'assets/app.js', chunkFileNames: 'assets/app.js', assetFileNames: 'assets/app[extname]', // One file, so there is one name to embed. The app is a few hundred // kilobytes loaded from localhost; splitting it saves nothing. manualChunks: undefined, }, }, }, });