On Windows a report file being rewritten answered the watcher with EBUSY, which took the dev server down, and each run reloaded every open page.
68 lines
3.2 KiB
TypeScript
68 lines
3.2 KiB
TypeScript
import { ServerResponse } from "node:http";
|
|
import { defineConfig, type Plugin, type ProxyOptions } from "vite";
|
|
import { reactWithCompiler } from "./reactPlugin";
|
|
import { devOnlyGuard } from "./devOnlyGuard";
|
|
import { chunkGuard } from "./chunkGuard";
|
|
|
|
// During `vite dev` (host with Node), proxy API calls to the backend container.
|
|
// Use 127.0.0.1 (not "localhost") so Node doesn't resolve to IPv6 ::1, which the
|
|
// Docker port publish may not answer — that surfaces as proxy connection failures.
|
|
const target = "http://127.0.0.1:8080";
|
|
// changeOrigin:false keeps the original `Host: localhost:5173` header when proxying to the backend
|
|
// (the TCP target is still 127.0.0.1:8080 — changeOrigin only rewrites the header). The backend uses
|
|
// that header to send the OAuth redirect_uri back to :5173, so a Google login started on the Vite dev
|
|
// server returns to :5173 instead of the fixed :8080 callback. (Both are registered in the console.)
|
|
//
|
|
// A failed connect to the container (on Windows the Docker port publish now and then times one out)
|
|
// is a gateway failure, so it answers 502 like the prod reverse proxy: the app reads that as a
|
|
// passing connection loss (a quiet notice; a GET is retried once). Vite's own handler, which runs
|
|
// after this one and leaves an answered response alone, sends a bodiless 500 — read as a real
|
|
// server fault, the blocking error dialog.
|
|
const proxyOpts: ProxyOptions = {
|
|
target,
|
|
changeOrigin: false,
|
|
configure: (proxy) =>
|
|
proxy.on("error", (_err, _req, res) => {
|
|
if (res instanceof ServerResponse && !res.headersSent) res.writeHead(502).end();
|
|
}),
|
|
};
|
|
const proxy = {
|
|
"/api": proxyOpts,
|
|
"/auth": proxyOpts,
|
|
"/healthz": proxyOpts,
|
|
};
|
|
|
|
// Two HTML entries: the app (index.html) and a public /watch/<token> link (watch.html), which loads
|
|
// only the player page. In a build the backend picks the file by path (`main.py`); this does the
|
|
// same for `vite dev`, whose history fallback would otherwise hand /watch/… the app.
|
|
const watchEntry = (): Plugin => ({
|
|
name: "siftlode-watch-entry",
|
|
configureServer(server) {
|
|
server.middlewares.use((req, _res, next) => {
|
|
if (req.url?.startsWith("/watch/")) req.url = "/watch.html";
|
|
next();
|
|
});
|
|
},
|
|
// The server answers every /watch/ link with the built watch.html, so a build without it would
|
|
// ship a 500 for each one, and nothing downstream reads `dist` before prod does. Fail it here —
|
|
// after Vite's own HTML plugin has emitted the pages (`enforce: "post"`), or none is there yet.
|
|
enforce: "post",
|
|
generateBundle(_options, bundle) {
|
|
for (const page of ["index.html", "watch.html"]) {
|
|
if (!(page in bundle)) this.error(`${page} was not built — add it to rollupOptions.input`);
|
|
}
|
|
},
|
|
});
|
|
|
|
export default defineConfig({
|
|
plugins: [reactWithCompiler(), devOnlyGuard(), chunkGuard(), watchEntry()],
|
|
server: {
|
|
port: 5173,
|
|
proxy,
|
|
// What an e2e run writes is no source. On Windows a file it is rewriting answers the watcher with
|
|
// EBUSY, which takes the whole dev server down; each run also reloaded every open page.
|
|
watch: { ignored: ["**/playwright-report/**", "**/test-results/**"] },
|
|
},
|
|
build: { outDir: "dist", rollupOptions: { input: ["index.html", "watch.html"] } },
|
|
});
|