From 55e01c14606ff4e5c67c540d19a75812477cdf25 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 31 Jul 2026 22:11:42 +0200 Subject: [PATCH] fix(web): stop shipping 7 MB of sound the console cannot play MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@unom/ui/button` reaches `sound/defaults.js`, which resolves two game-UI sprite sheets with `new URL(…, import.meta.url)` at module scope — a 4.8 MB .wav and a 2.2 MB .mp3. Vite emitted both into the build, so they rode into the Windows installer and the .deb. The console never mounts UnomProviders, so no player is bundled and not one byte of it could ever be played. A build-time rewrite of those two expressions takes the asset payload from 8.2 MB to 1.5 MB; the login page and the button chunk are unchanged. Deleting the plugin is the whole revert if the console ever wants click sounds. Also: - `bun run dev` forwards the management bearer, so developing against a real host stops 401ing into a /login bounce that dev has no gate to satisfy. - `check-i18n` runs after `build`, not only inside `codegen`. It exists to stop a zero-message console shipping, and the CI job and the installer build both install with `--ignore-scripts`, so it had never once run where it mattered. - Bun's idle timeout goes from its 10 s default to 120 s. The host sends SSE keep-alives every 15 s, so anything long-lived proxied through the console was cut by us first — which the event stream is about to depend on. - The typecheck covers the Storybook config and preview. Co-Authored-By: Claude Opus 5 (1M context) --- web/nitro-entry/bun-https.mjs | 5 +++- web/package.json | 1 + web/tsconfig.json | 9 ++++++- web/vite.config.ts | 49 ++++++++++++++++++++++++++++++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/web/nitro-entry/bun-https.mjs b/web/nitro-entry/bun-https.mjs index 736c6039..585f1d0f 100644 --- a/web/nitro-entry/bun-https.mjs +++ b/web/nitro-entry/bun-https.mjs @@ -51,8 +51,11 @@ const tls = const server = Bun.serve({ port: process.env.NITRO_PORT || process.env.PORT || 3000, host: process.env.NITRO_HOST || process.env.HOST, + // Bun defaults this to 10 s, which is SHORTER than the host's 15 s SSE keep-alive comment — so a + // proxied `/api/v1/events` stream (or any other quiet long-lived response) gets cut by us and + // reconnects on a loop. 120 s is comfortably above any keep-alive we forward; still overridable. idleTimeout: - Number.parseInt(process.env.NITRO_BUN_IDLE_TIMEOUT, 10) || undefined, + Number.parseInt(process.env.NITRO_BUN_IDLE_TIMEOUT, 10) || 120, // `tls: undefined` ⇒ plain HTTP (dev); otherwise HTTPS over HTTP/1.1. tls, websocket: import.meta._websocket ? ws.websocket : undefined, diff --git a/web/package.json b/web/package.json index bcc47a24..499bbae1 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ "dev": "vite dev --port 47992", "prebuild": "orval --config orval.config.ts", "build": "vite build", + "postbuild": "node tools/check-i18n.mjs", "start": "bun run .output/server/index.mjs", "api:gen": "orval --config orval.config.ts", "lint": "tsc --noEmit", diff --git a/web/tsconfig.json b/web/tsconfig.json index 656e187e..51189929 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -20,5 +20,12 @@ "@/*": ["./src/*"] } }, - "include": ["src", "server", "vite.config.ts", "orval.config.ts"] + "include": [ + "src", + "server", + ".storybook", + "vite.config.ts", + "vite.storybook.config.ts", + "orval.config.ts" + ] } diff --git a/web/vite.config.ts b/web/vite.config.ts index aeba419d..028e2d7b 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -115,16 +115,63 @@ function pluginUiDevProxy(): Plugin { }; } +/** + * Drop @unom/ui's game-UI sound sprites from the build. + * + * `@unom/ui/button` pulls in `sound/defaults.js`, which resolves two sprite sheets with + * `new URL(…, import.meta.url)` at module scope — a 4.8 MB .wav and a 2.2 MB .mp3. Vite therefore + * emits both into `.output/public/assets/`, where they were ~7 MB of an 8.2 MB asset payload, and + * they ride along into the Windows installer and the .deb. + * + * The console never mounts `UnomProviders`, so no sound player is bundled and not one byte of that + * can ever be played. Stub the two files to an empty URL instead of shipping them. + * + * If the console ever DOES want click sounds, delete this plugin — that is the whole revert. + */ +function dropUnomSoundSprites(): Plugin { + // The module that names them, and the `new URL(, import.meta.url).href` expressions + // inside it. Rewriting the EXPRESSION is what works: Vite emits these assets from its own + // `new URL(…, import.meta.url)` transform, so intercepting the .wav/.mp3 module id never fires. + const DEFAULTS = /@unom[\\/]ui[\\/].*sound[\\/]defaults\.(?:js|mjs)$/; + const SPRITE_URL = + /new URL\(\s*(["'])[^"']*\.(?:wav|mp3)\1\s*,\s*import\.meta\.url\s*\)\.href/g; + return { + name: "punktfunk-drop-unom-sound-sprites", + enforce: "pre", + transform(code, id) { + if (!DEFAULTS.test(id)) return null; + const out = code.replace(SPRITE_URL, '""'); + return out === code ? null : { code: out, map: null }; + }, + }; +} + export default defineConfig({ server: { proxy: { // `secure: false`: the host serves its own self-signed identity cert on loopback. - "/api": { target: MGMT_URL, changeOrigin: true, secure: false }, + "/api": { + target: MGMT_URL, + changeOrigin: true, + secure: false, + // Inject the management bearer, exactly as the deployed BFF does + // (server/routes/api/[...].ts). The host requires a token on every route now, so + // without this `bun run dev` 401s on every call and `apiFetch` bounces the developer + // to /login — where logging in doesn't help, because dev has no login gate at all. + configure(proxy) { + const token = process.env.PUNKTFUNK_MGMT_TOKEN; + if (!token) return; + proxy.on("proxyReq", (proxyReq) => { + proxyReq.setHeader("authorization", `Bearer ${token}`); + }); + }, + }, }, }, plugins: [ // First, so it intercepts /plugin-ui before the SSR catch-all in dev. pluginUiDevProxy(), + dropUnomSoundSprites(), viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tailwindcss(), paraglideVitePlugin({