webpack-chunk-report-plugin
Version:
Webpack Chunk Report Plugin
185 lines (184 loc) • 7.92 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateGraphFromChunkIdVsChunkMap = generateGraphFromChunkIdVsChunkMap;
const uniq_1 = __importDefault(require("lodash/uniq"));
const uniqBy_1 = __importDefault(require("lodash/uniqBy"));
const getImportersOfConcatenatedModule = (module, allModules, seen = new Set()) => {
// Base case: already processed this module
if (seen.has(module.fileName)) {
return new Set();
}
seen.add(module.fileName);
const importers = new Set();
for (const reason of module.reasons) {
const parentModuleId = reason.from;
if (!parentModuleId)
continue;
const parentModule = allModules[parentModuleId];
if (!parentModule)
continue;
if (parentModule.type === "Concatenated") {
// Recursively get importers of concatenated parent
const parentImporters = getImportersOfConcatenatedModule(parentModule, allModules, seen);
parentImporters.forEach(imp => importers.add(imp));
}
else {
// Regular module - add directly
importers.add(parentModuleId);
}
}
return importers;
};
function generateGraphFromChunkIdVsChunkMap(chunkIdVsChunkMap) {
const nodes = [];
const links = [];
const moduleMap = {};
const chunkMap = {};
const allModules = {};
// First pass: collect all modules across chunks
Object.values(chunkIdVsChunkMap).forEach(chunk => {
const collectModules = (module) => {
if (module.fileName in allModules) {
return;
}
if (module.fileName) {
allModules[module.fileName] = module;
}
if (module.type === "Concatenated" && module.subModules) {
module.subModules.forEach(subModule => collectModules(subModule));
}
};
chunk.modules.forEach(collectModules);
});
// Process chunks
Object.entries(chunkIdVsChunkMap).forEach(([chunkId, chunk]) => {
if (!chunkId)
return;
const chunkNode = {
id: chunkId,
type: "chunk",
data: chunk,
dependencies: [],
};
chunkMap[chunkId] = chunkNode;
nodes.push(chunkNode);
});
// Process modules
const processModule = (module, parentChunkId) => {
if (!module.fileName)
return;
moduleMap[parentChunkId] = moduleMap[parentChunkId] ?? {};
if (module.type !== "Concatenated" &&
!moduleMap[parentChunkId][module.fileName]) {
const moduleNode = {
id: module.fileName,
type: "module",
data: module,
dependencies: [],
};
moduleMap[parentChunkId][module.fileName] = moduleNode;
nodes.push(moduleNode);
}
if (module.type !== "Concatenated") {
links.push({
source: parentChunkId,
target: module.fileName,
});
chunkMap[parentChunkId]?.dependencies.push(module.fileName);
}
// Process concatenated submodules
else if (module.subModules) {
module.subModules.forEach(subModule => {
processModule(subModule, parentChunkId);
links.push({
source: parentChunkId,
target: subModule.fileName,
});
chunkMap[parentChunkId]?.dependencies.push(subModule.fileName);
});
}
};
// First process all modules to ensure they exist in the graph
Object.entries(chunkIdVsChunkMap).forEach(([chunkId, chunk]) => {
if (!chunkId)
return;
chunk.modules.forEach(module => processModule(module, chunkId));
});
// Then process all reasons to create dependencies
Object.entries(moduleMap).forEach(([chunkId, moduleIdVsGraphNode]) => {
Object.entries(moduleIdVsGraphNode).forEach(([moduleId, graphNode]) => {
const module = graphNode.data;
const seen = new Set();
const processReasons = (mod) => {
mod.reasons.forEach(reason => {
const shouldAvoidAdding = !reason.from ||
reason.from === mod.fileName ||
seen.has(reason.from);
if (shouldAvoidAdding) {
return;
}
seen.add(reason.from);
// Find the source module (could be in any chunk)
const sourceModule = allModules[reason.from];
if (!sourceModule)
return;
switch (module.type) {
case "Concatenated": {
mod.subModules.forEach(processReasons);
break;
}
default: {
switch (sourceModule.type) {
case "Concatenated": {
const importers = getImportersOfConcatenatedModule(sourceModule, allModules);
importers.forEach(importer => {
links.push({
source: importer,
target: module.fileName,
reason: {
from: importer,
explanation: `Via concatenated source module: ${sourceModule.fileName}`,
type: reason.type,
},
});
const importerNode = nodes.find(node => node.id === importer);
if (importerNode) {
importerNode.dependencies ??= [];
importerNode.dependencies.push(module.fileName);
}
});
break;
}
default: {
// External Module or Normal Module
links.push({
source: reason.from,
target: module.fileName,
reason,
});
// Find the source module's node to add dependency
Object.values(moduleMap).forEach(modIdVsModMap => {
if (modIdVsModMap[reason.from]) {
modIdVsModMap[reason.from].dependencies.push(module.fileName);
}
});
}
}
}
}
});
};
processReasons(module);
});
});
// Deduplicate dependencies
const graphNodes = (0, uniqBy_1.default)(nodes, node => node.id).map(node => ({
...node,
dependencies: (0, uniq_1.default)(node.dependencies),
}));
const graphLinks = (0, uniqBy_1.default)(links, link => `${link.source}-${link.target}`);
return { nodes: graphNodes, links: graphLinks };
}