1 | 'use strict';
|
2 | var __importDefault =
|
3 | (this && this.__importDefault) ||
|
4 | function (mod) {
|
5 | return mod && mod.__esModule ? mod : { default: mod };
|
6 | };
|
7 | Object.defineProperty(exports, '__esModule', { value: true });
|
8 | exports.getWebTreeMapData = exports.makeMergedTreeDataMap = exports.generateHtml = void 0;
|
9 | const btoa_1 = __importDefault(require('btoa'));
|
10 | const ejs_1 = __importDefault(require('ejs'));
|
11 | const fs_1 = __importDefault(require('fs'));
|
12 | const path_1 = __importDefault(require('path'));
|
13 | const escape_html_1 = __importDefault(require('escape-html'));
|
14 | const lodash_1 = require('lodash');
|
15 | const helpers_1 = require('./helpers');
|
16 | const coverage_1 = require('./coverage');
|
17 | const COMBINED_BUNDLE_NAME = '[combined]';
|
18 | function getTreeDataMap(exploreResults) {
|
19 | let treeData = exploreResults.map((data) => ({
|
20 | name: data.bundleName,
|
21 | data: getWebTreeMapData(data.files),
|
22 | }));
|
23 | if (treeData.length > 1) {
|
24 | treeData = [makeMergedTreeDataMap(lodash_1.cloneDeep(treeData)), ...treeData];
|
25 | }
|
26 | for (const webTreeData of treeData) {
|
27 | addSizeToTitle(webTreeData.data, webTreeData.data.data['$area']);
|
28 | }
|
29 | return { ...treeData };
|
30 | }
|
31 | function generateHtml(exploreResults, options) {
|
32 | const assets = {
|
33 | webtreemapJs: btoa_1.default(
|
34 | fs_1.default.readFileSync(require.resolve('./vendor/webtreemap.js'))
|
35 | ),
|
36 | webtreemapCss: btoa_1.default(
|
37 | fs_1.default.readFileSync(require.resolve('./vendor/webtreemap.css'))
|
38 | ),
|
39 | };
|
40 | const treeDataMap = getTreeDataMap(exploreResults);
|
41 | const bundles = exploreResults.map((data) => ({
|
42 | name: data.bundleName,
|
43 | size: helpers_1.formatBytes(data.totalBytes),
|
44 | }));
|
45 | if (exploreResults.length > 1) {
|
46 | bundles.unshift({
|
47 | name: COMBINED_BUNDLE_NAME,
|
48 | size: helpers_1.formatBytes(
|
49 | exploreResults.reduce((total, result) => total + result.totalBytes, 0)
|
50 | ),
|
51 | });
|
52 | }
|
53 | const template = helpers_1.getFileContent(path_1.default.join(__dirname, 'tree-viz.ejs'));
|
54 | return ejs_1.default.render(template, {
|
55 | options,
|
56 | bundles,
|
57 | treeDataMap,
|
58 | webtreemapJs: assets.webtreemapJs,
|
59 | webtreemapCss: assets.webtreemapCss,
|
60 | });
|
61 | }
|
62 | exports.generateHtml = generateHtml;
|
63 | function makeMergedTreeDataMap(treeData) {
|
64 | const data = newNode('/');
|
65 | data.children = [];
|
66 | for (const result of treeData) {
|
67 | const childTree = result.data;
|
68 | childTree.name = result.name;
|
69 | data.data['$area'] += childTree.data['$area'];
|
70 | data.children.push(childTree);
|
71 | }
|
72 | const commonPrefix = helpers_1.getCommonPathPrefix(data.children.map((node) => node.name));
|
73 | const commonPrefixLength = commonPrefix.length;
|
74 | if (commonPrefixLength > 0) {
|
75 | for (const node of data.children) {
|
76 | node.name = node.name.slice(commonPrefixLength);
|
77 | }
|
78 | }
|
79 | return {
|
80 | name: COMBINED_BUNDLE_NAME,
|
81 | data,
|
82 | };
|
83 | }
|
84 | exports.makeMergedTreeDataMap = makeMergedTreeDataMap;
|
85 | function getNodePath(parts, depthIndex) {
|
86 | return parts.slice(0, depthIndex + 1).join('/');
|
87 | }
|
88 | const WEBPACK_FILENAME_PREFIX = 'webpack:///';
|
89 | const WEBPACK_FILENAME_PREFIX_LENGTH = WEBPACK_FILENAME_PREFIX.length;
|
90 | const PATH_SEPARATOR_REGEX = /[\\/]/;
|
91 | function splitFilename(file) {
|
92 | const webpackPrefixIndex = file.indexOf(WEBPACK_FILENAME_PREFIX);
|
93 | if (webpackPrefixIndex !== -1) {
|
94 | return [
|
95 | ...file.substring(0, webpackPrefixIndex).split('/'),
|
96 | WEBPACK_FILENAME_PREFIX,
|
97 | ...file.substring(webpackPrefixIndex + WEBPACK_FILENAME_PREFIX_LENGTH).split('/'),
|
98 | ].filter(Boolean);
|
99 | }
|
100 | return file.split(PATH_SEPARATOR_REGEX);
|
101 | }
|
102 | function getTreeNodesMap(fileDataMap) {
|
103 | let partsSourceTuples = Object.keys(fileDataMap).map((file) => [splitFilename(file), file]);
|
104 | const maxDepth = Math.max(...partsSourceTuples.map(([parts]) => parts.length));
|
105 | for (let depthIndex = 0; depthIndex < maxDepth; depthIndex += 1) {
|
106 | partsSourceTuples = partsSourceTuples.map(([parts, file], currentNodeIndex) => {
|
107 | if (parts[depthIndex]) {
|
108 | const nodePath = getNodePath(parts, depthIndex);
|
109 | const hasSameRootPaths = partsSourceTuples.some(([pathParts], index) => {
|
110 | if (index === currentNodeIndex) {
|
111 | return false;
|
112 | }
|
113 | if (!pathParts[depthIndex]) {
|
114 | return false;
|
115 | }
|
116 | return getNodePath(pathParts, depthIndex) === nodePath;
|
117 | });
|
118 | if (!hasSameRootPaths) {
|
119 | return [[...parts.slice(0, depthIndex), parts.slice(depthIndex).join('/')], file];
|
120 | }
|
121 | }
|
122 | return [parts, file];
|
123 | });
|
124 | }
|
125 | return partsSourceTuples.reduce((result, [parts, file]) => {
|
126 | result[file] = parts;
|
127 | return result;
|
128 | }, {});
|
129 | }
|
130 | function getWebTreeMapData(files) {
|
131 | const treeNodesMap = getTreeNodesMap(files);
|
132 | const treeData = newNode('/');
|
133 | for (const source in files) {
|
134 | addNode(treeNodesMap[source], files[source], treeData);
|
135 | }
|
136 | return treeData;
|
137 | }
|
138 | exports.getWebTreeMapData = getWebTreeMapData;
|
139 | function newNode(name) {
|
140 | return {
|
141 | name: escape_html_1.default(name),
|
142 | data: {
|
143 | $area: 0,
|
144 | },
|
145 | };
|
146 | }
|
147 | function setNodeData(node, fileData) {
|
148 | const size = node.data['$area'] + fileData.size;
|
149 | if (fileData.coveredSize !== undefined) {
|
150 | const coveredSize = (node.data.coveredSize || 0) + fileData.coveredSize;
|
151 | node.data.coveredSize = coveredSize;
|
152 | node.data.backgroundColor = coverage_1.getColorByPercent(coveredSize / size);
|
153 | }
|
154 | node.data['$area'] = size;
|
155 | }
|
156 | function addNode(parts, fileData, treeData) {
|
157 | if (fileData.size === 0) {
|
158 | return;
|
159 | }
|
160 | let node = treeData;
|
161 | setNodeData(node, fileData);
|
162 | parts.forEach((part) => {
|
163 | if (!node.children) {
|
164 | node.children = [];
|
165 | }
|
166 | let child = node.children.find((child) => child.name === part);
|
167 | if (!child) {
|
168 | child = newNode(part);
|
169 | node.children.push(child);
|
170 | }
|
171 | node = child;
|
172 | setNodeData(child, fileData);
|
173 | });
|
174 | }
|
175 | function addSizeToTitle(node, total) {
|
176 | const { $area: size, coveredSize } = node.data;
|
177 | const titleParts = [
|
178 | node.name,
|
179 | helpers_1.formatBytes(size),
|
180 | `${helpers_1.formatPercent(size, total, 1)}%`,
|
181 | ];
|
182 | if (coveredSize !== undefined && node.children === undefined) {
|
183 | titleParts.push(`Coverage: ${helpers_1.formatPercent(coveredSize, size, 1)}%`);
|
184 | }
|
185 | node.name = titleParts.join(' • ');
|
186 | if (node.children) {
|
187 | node.children.forEach((child) => {
|
188 | addSizeToTitle(child, total);
|
189 | });
|
190 | }
|
191 | }
|
192 |
|