import path from "node:path"; import type { Plugin } from "vite"; type ModuleInventoryChunk = Readonly<{ fileName: string; modules: readonly string[]; }>; /** * Rollup knows the exact source-module set for every emitted chunk. Persisting * that graph makes optional-runtime exclusion verifiable without relying on * minified names, error strings, or source maps. */ export function viteModuleInventoryPlugin( repositoryRoot = process.cwd(), ): Plugin { return { name: "frontend-module-inventory", generateBundle(_options, bundle) { const chunks: ModuleInventoryChunk[] = Object.values(bundle) .filter((output) => output.type === "chunk") .map((chunk) => ({ fileName: chunk.fileName, modules: Object.freeze( [...new Set( Object.keys(chunk.modules).map((moduleId) => normalizeModuleId(moduleId, repositoryRoot), ), )].sort(), ), })) .sort((left, right) => left.fileName.localeCompare(right.fileName)); this.emitFile({ type: "asset", fileName: ".vite/module-inventory.json", source: `${JSON.stringify( { schemaVersion: 1, chunks, }, null, 2, )}\n`, }); }, }; } function normalizeModuleId( moduleId: string, repositoryRoot: string, ): string { const withoutQuery = moduleId.replace(/^\0/u, "").split("?", 1)[0] ?? ""; if (!path.isAbsolute(withoutQuery)) { return withoutQuery.replaceAll("\\", "/"); } const relative = path.relative(repositoryRoot, withoutQuery); return relative.startsWith("..") ? `external:${path.basename(withoutQuery)}` : relative.replaceAll("\\", "/"); }