Files
tech-log-frontend/scripts/lib/vite-module-inventory.ts
T

64 lines
1.7 KiB
TypeScript

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("\\", "/");
}