UNPKG

4.95 kBJavaScriptView Raw
1const _ = require('lodash');
2const filesize = require('filesize');
3const gzipSize = require('gzip-size');
4
5class Node {
6
7 constructor(name, parent) {
8 this.name = name;
9 this.parent = parent;
10 }
11
12 get path() {
13 const path = [];
14 let node = this;
15
16 while (node) {
17 path.push(node.name);
18 node = node.parent;
19 }
20
21 return path.reverse().join('/');
22 }
23
24 toString(indent) {
25 indent = indent || '|';
26
27 return `${indent} ${this.name}`;
28 }
29
30}
31
32class Module extends Node {
33
34 constructor(name, data, parent) {
35 super(name, parent);
36 this.data = data;
37 }
38
39 get src() {
40 return this.data.parsedSrc;
41 }
42
43 set src(value) {
44 this.data.parsedSrc = value;
45 delete this._gzipSize;
46 }
47
48 get size() {
49 return this.data.size;
50 }
51
52 set size(value) {
53 this.data.size = value;
54 }
55
56 get parsedSize() {
57 return this.src ? this.src.length : undefined;
58 }
59
60 get gzipSize() {
61 if (!_.has(this, '_gzipSize')) {
62 this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
63 }
64
65 return this._gzipSize;
66 }
67
68 mergeData(data) {
69 if (data.size) {
70 this.size += data.size;
71 }
72
73 if (data.parsedSrc) {
74 this.src = (this.src || '') + data.parsedSrc;
75 }
76 }
77
78 toString(indent) {
79 return `${super.toString(indent)} [${this.data.id}] (${filesize(this.size)})`;
80 }
81
82 toChartData() {
83 return {
84 id: this.data.id,
85 label: this.name,
86 path: this.path,
87 statSize: this.size,
88 parsedSize: this.parsedSize,
89 gzipSize: this.gzipSize
90 };
91 }
92
93}
94
95
96class Folder extends Node {
97
98 constructor(name, parent) {
99 super(name, parent);
100 this.children = Object.create(null);
101 }
102
103 get src() {
104 if (!_.has(this, '_src')) {
105 this._src = this.walk((node, src, stop) => {
106 if (node.src === undefined) return stop(undefined);
107 return (src += node.src);
108 }, '', false);
109 }
110
111 return this._src;
112 }
113
114 get size() {
115 if (!_.has(this, '_size')) {
116 this._size = this.walk((node, size) => (size + node.size), 0, false);
117 }
118
119 return this._size;
120 }
121
122 get parsedSize() {
123 return this.src ? this.src.length : undefined;
124 }
125
126 get gzipSize() {
127 if (!_.has(this, '_gzipSize')) {
128 this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
129 }
130
131 return this._gzipSize;
132 }
133
134 getChild(name) {
135 return this.children[name];
136 }
137
138 addFolder(name) {
139 const folder = new Folder(name, this);
140
141 this.children[name] = folder;
142 delete this._size;
143 delete this._src;
144
145 return folder;
146 }
147
148 addModule(name, data) {
149 let node = this.children[name];
150
151 // For some reason we already have this node in children and it's a folder.
152 if (node && node instanceof Folder) return false;
153
154 if (node) {
155 // We already have this node in children and it's a module.
156 // Merging it's data.
157 node.mergeData(data);
158 } else {
159 // Creating new module.
160 node = new Module(name, data, this);
161 this.children[name] = node;
162 }
163
164 delete this._size;
165 delete this._src;
166
167 return true;
168 }
169
170 addModuleByPath(path, data) {
171 const [pathParts, fileName] = [path.slice(0, -1), _.last(path)];
172 let currentFolder = this;
173
174 _.each(pathParts, pathPart => {
175 let childNode = currentFolder.getChild(pathPart);
176
177 if (
178 // Folder is not created yet
179 !childNode ||
180 // In some situations (invalid usage of dynamic `require()`) webpack generates a module with empty require
181 // context, but it's moduleId points to a directory in filesystem.
182 // In this case we replace this `File` node with `Folder`.
183 // See `test/stats/with-invalid-dynamic-require.json` as an example.
184 !(childNode instanceof Folder)
185 ) {
186 childNode = currentFolder.addFolder(pathPart);
187 }
188
189 currentFolder = childNode;
190 });
191
192 currentFolder.addModule(fileName, data);
193 }
194
195 walk(walker, state = {}, deep = true) {
196 let stopped = false;
197
198 _.each(this.children, child => {
199 if (deep && child.walk) {
200 state = child.walk(walker, state, stop);
201 } else {
202 state = walker(child, state, stop);
203 }
204
205 if (stopped) return false;
206 });
207
208 return state;
209
210 function stop(finalState) {
211 stopped = true;
212 return finalState;
213 }
214 }
215
216 toString(indent, opts) {
217 const { sortBy } = opts || {};
218 indent = indent || '|';
219
220 let str = `${indent} ${this.name} (${filesize(this.size)})\n`;
221
222 str += _(this.children)
223 .sortBy(sortBy)
224 .reverse()
225 .map(child => child.toString(`${indent} |`, opts))
226 .join('\n');
227
228 return str;
229 }
230
231 toChartData() {
232 return {
233 label: this.name,
234 path: this.path,
235 statSize: this.size,
236 parsedSize: this.parsedSize,
237 gzipSize: this.gzipSize,
238 groups: _.invokeMap(this.children, 'toChartData')
239 };
240 }
241
242}
243
244module.exports = {
245 Node,
246 Module,
247 Folder
248};