UNPKG

webpack-split-chunks-analyzer

Version:
155 lines 8.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SplitChunksAnalyzerPlugin = void 0; const tslib_1 = require("tslib"); const node_fs_1 = require("node:fs"); const node_path_1 = tslib_1.__importDefault(require("node:path")); const node_child_process_1 = require("node:child_process"); const node_util_1 = require("node:util"); const transducist_1 = require("transducist"); const dagre_1 = tslib_1.__importDefault(require("@dagrejs/dagre")); const pretty_bytes_1 = tslib_1.__importDefault(require("pretty-bytes")); const cheerio = tslib_1.__importStar(require("cheerio")); const { writeFile, readFile } = node_fs_1.promises; ; const DEFAULT_OPTIONS = { excludePattern: [], outputFile: "split-chunks-report.html", openOnFinish: false, }; class SplitChunksAnalyzerPlugin { constructor(userOptions = {}) { this.compiler = undefined; this.analyze = async (stats) => { if (!this.compiler) { throw new Error("did not call apply before trying to analyze"); } // the included webpack types are a bit lacking so we cast const compilation = stats.compilation; const dagreGraph = new dagre_1.default.graphlib.Graph(); dagreGraph.setDefaultEdgeLabel(() => ({})); dagreGraph.setGraph({ rankdir: "TB" }); const graph = { buildName: stats.compilation.name ?? stats.hash ?? "<unknown>", nodes: {}, edges: [], }; let edgeIdCounter = 0; const prodAssetsIds = (0, transducist_1.chainFrom)(Object.keys(compilation.assets)) .filter((id) => !id.endsWith(".LICENSE")) .filter((id) => compilation.assetsInfo.get(id)?.development !== true) .toSet(); const chunkGroups = compilation.chunkGroups; const entrypointIds = Array.from(compilation.entrypoints.keys()); (0, transducist_1.chainFrom)(chunkGroups) .filter((chunkGroup) => chunkGroup.chunks.length > 0 || chunkGroup.getChildren().length > 0) .forEach((chunkGroup) => { const splitOriginFileName = chunkGroup.origins[0]?.request?.split("/"); const originName = splitOriginFileName?.[splitOriginFileName.length - 1]; const chunkGroupSize = (0, transducist_1.chainFrom)(chunkGroup.getFiles()) .filter((id) => prodAssetsIds.has(id)) .map((id) => compilation.assets[id]) .map((asset) => asset.size()) .sum(); const chunks = (0, transducist_1.chainFrom)(chunkGroup.chunks) .map((chunk) => { const file = Array.from(chunk.files)[0]; const chunkSize = compilation.assets[file].size(); const modules = (0, transducist_1.chainFrom)(chunk.getModules()) .map((mod) => { const moduleName = typeof mod.userRequest === "string" ? node_path_1.default.relative(compilation.compiler.context, mod.userRequest) : // TODO make this a real name "<unknown>"; const moduleSize = mod.size(); return { name: moduleName, size: moduleSize, displaySize: (0, pretty_bytes_1.default)(moduleSize), }; }) .toArray() .sort((a, b) => b.size - a.size); return { name: file, size: chunkSize, displaySize: (0, pretty_bytes_1.default)(chunkSize), modules, }; }) .filter((chunk) => prodAssetsIds.has(chunk.name)) .filter((chunk) => !this.options.excludePattern.some(r => r.test(chunk.name))) .toArray() .sort((a, b) => b.size - a.size); const name = chunkGroup.name ?? originName ?? chunkGroup.id; const displaySize = (0, pretty_bytes_1.default)(chunkGroupSize); dagreGraph.setNode(chunkGroup.id, { width: 170, height: 55 }); graph.nodes[chunkGroup.id] = { id: chunkGroup.id, position: { x: 0, y: 0, }, data: { label: `${name} (${displaySize})`, name, size: chunkGroupSize, displaySize, entryPoint: entrypointIds.includes(chunkGroup.id), chunks, }, }; const childOrders = (0, transducist_1.chainFrom)(Object.entries(chunkGroup.getChildrenByOrders(stats.compilation.moduleGraph, stats.compilation.chunkGraph))) .map(([orderType, groups]) => [orderType, new Set(groups.map((group) => group.id))]) .toObject(([orderType, _]) => orderType, ([_, groups]) => groups); chunkGroup.getChildren().forEach((child) => { if (child.chunks.length > 0 || child.getChildren().length > 0) { const isPrefetched = childOrders.prefetch?.has(child.id) ?? false; const isPreloaded = childOrders.preload?.has(child.id) ?? false; dagreGraph.setEdge(chunkGroup.id, child.id); graph.edges.push({ id: (edgeIdCounter++).toString(), source: chunkGroup.id, target: child.id, data: { kind: isPrefetched ? "prefetch" : isPreloaded ? "preload" : undefined, }, }); } }); }); dagre_1.default.layout(dagreGraph); for (const node of Object.values(graph.nodes)) { const position = dagreGraph.node(node.id); // We are shifting the dagre node position (anchor=center center) to the top left // so it matches the React Flow node anchor point (top left). const x = position.x - (node.measured?.width ?? 0) / 2; const y = position.y - (node.measured?.height ?? 0) / 2; node.position = { x, y }; } const baseHtml = await readFile(node_path_1.default.resolve(__dirname, "../../dist/index.html")); let lastPath = undefined; const outputFiles = Array.isArray(this.options.outputFile) ? this.options.outputFile : [this.options.outputFile]; for (const outputFile of outputFiles) { const outputFilePath = node_path_1.default.resolve(this.compiler.outputPath, outputFile); const $ = cheerio.load(baseHtml); $("head").append(`<script type="application/json" id="data">${JSON.stringify(graph)}</script>`); await writeFile(outputFilePath, $.html()); lastPath = outputFilePath; } if (this.options.openOnFinish && lastPath != null) { await (0, node_util_1.promisify)(node_child_process_1.exec)(`open ${lastPath}`); } }; this.options = { ...DEFAULT_OPTIONS, ...userOptions }; } apply(compiler) { this.compiler = compiler; compiler.hooks.done.tapPromise("SplitChunksAnalyzerPlugin", this.analyze); } } exports.SplitChunksAnalyzerPlugin = SplitChunksAnalyzerPlugin; //# sourceMappingURL=SplitChunksAnalyzerPlugin.js.map