Build in the review tool.

This commit is contained in:
Greyson Parrelli
2026-08-24 09:24:01 -04:00
parent 4eef6a28d3
commit 407dd81512
55 changed files with 12932 additions and 40 deletions
+54
View File
@@ -0,0 +1,54 @@
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<string, string | undefined> };
// 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()],
// 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/<id>/` 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/<tabId>/ 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,
},
},
},
});