64 lines
1.7 KiB
TypeScript
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("\\", "/");
|
|
}
|