1 | const _ = require('lodash');
|
2 | const filesize = require('filesize');
|
3 | const gzipSize = require('gzip-size');
|
4 |
|
5 | class 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 |
|
32 | class 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 |
|
96 | class 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 |
|
152 | if (node && node instanceof Folder) return false;
|
153 |
|
154 | if (node) {
|
155 |
|
156 |
|
157 | node.mergeData(data);
|
158 | } else {
|
159 |
|
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 |
|
179 | !childNode ||
|
180 |
|
181 |
|
182 |
|
183 |
|
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 |
|
244 | module.exports = {
|
245 | Node,
|
246 | Module,
|
247 | Folder
|
248 | };
|