From bd7d71a2840a3196e075aed2cf8a04887bfa0c50 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 14 Aug 2026 16:06:27 -0400 Subject: [PATCH] build(vite): remove stale hashed chunks after each build emptyOutDir is false because web/static also holds tracked assets, so *-.js chunks from every previous build accumulated indefinitely and leaked into Docker images via the build context (the reader serves whichever chunk the import chain names, so the orphans are pure confusion + bloat). A closeBundle plugin now deletes any hashed chunk this build did not produce. --- vite.config.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 772b272..c9124cc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,5 @@ import { defineConfig } from "vite"; -import { cpSync, mkdirSync } from "node:fs"; +import { cpSync, mkdirSync, readdirSync, rmSync } from "node:fs"; import { join } from "node:path"; const pdfjsAssets = () => ({ @@ -16,6 +16,29 @@ const pdfjsAssets = () => ({ }, }); +// emptyOutDir must stay false (web/static also holds tracked assets), +// so hashed chunks from previous builds would otherwise accumulate +// forever and leak into Docker images via the build context. Remove +// any *-.js(.map) that this build did not produce. +const cleanStaleChunks = () => { + const produced = new Set(); + return { + name: "clean-stale-chunks", + generateBundle(_options, bundle) { + for (const fileName of Object.keys(bundle)) produced.add(fileName); + }, + closeBundle() { + const outDir = "web/static"; + const chunkRe = /-[A-Za-z0-9_-]{8}\.js(\.map)?$/; + for (const f of readdirSync(outDir)) { + if (chunkRe.test(f) && !produced.has(f)) { + rmSync(join(outDir, f)); + } + } + }, + }; +}; + export default defineConfig({ resolve: { alias: { @@ -24,7 +47,7 @@ export default defineConfig({ }, }, base: "/static/", - plugins: [pdfjsAssets()], + plugins: [pdfjsAssets(), cleanStaleChunks()], build: { outDir: "web/static", emptyOutDir: false,