webpack-bundle-analyzer-sunburst
Version:
Webpack plugin and CLI utility that represents bundle content either as an interactive zoomable treemap or a sunburst chart
220 lines (167 loc) • 4.2 kB
JavaScript
const _ = require('lodash');
const filesize = require('filesize');
class Node {
constructor(name, parent) {
this.name = name;
this.parent = parent;
}
get path() {
const path = [];
let node = this;
while (node) {
path.push(node.name);
node = node.parent;
}
return path.reverse().join('/');
}
toString(indent) {
indent = indent || '|';
return `${indent} ${this.name}`;
}
}
class Module extends Node {
constructor(name, data, parent) {
super(name, parent);
this.data = data;
}
get size() {
return this.data.size;
}
get parsedSize() {
return this.data.parsedSize;
}
get gzipSize() {
return this.data.gzipSize;
}
mergeData(data) {
_.each(['size', 'parsedSize', 'gzipSize'], prop => {
if (data[prop]) {
this.data[prop] = (this.data[prop] || 0) + data[prop];
}
});
}
toString(indent) {
return `${super.toString(indent)} [${this.data.id}] (${filesize(this.size)})`;
}
toChartData() {
return {
id: this.data.id,
label: this.name,
path: this.path,
statSize: this.size,
parsedSize: this.parsedSize,
gzipSize: this.gzipSize
};
}
}
class Folder extends Node {
constructor(name, parent) {
super(name, parent);
this.children = Object.create(null);
}
get size() {
if (!_.has(this, '_size')) {
this._size = this.walk((node, size) => (size + node.size), 0);
}
return this._size;
}
get parsedSize() {
if (!_.has(this, '_parsedSize')) {
this._parsedSize = this.walk((node, size, stop) => {
if (node.parsedSize === undefined) {
return stop(undefined);
}
return (size + node.parsedSize);
}, 0);
}
return this._parsedSize;
}
get gzipSize() {
if (!_.has(this, '_gzipSize')) {
this._gzipSize = this.walk((node, size, stop) => {
if (node.gzipSize === undefined) {
return stop(undefined);
}
return (size + node.gzipSize);
}, 0);
}
return this._gzipSize;
}
getChild(name) {
return this.children[name];
}
addFolder(name) {
const folder = new Folder(name, this);
this.children[name] = folder;
delete this._size;
delete this._parsedSize;
return folder;
}
addModule(name, data) {
let node = this.children[name];
// For some reason we already have this node in children and it's a folder.
if (node && node instanceof Folder) return false;
if (node) {
// We already have this node in children and it's a module.
// Merging it's data.
node.mergeData(data);
} else {
// Creating new module.
node = new Module(name, data, this);
this.children[name] = node;
}
delete this._size;
delete this._parsedSize;
return true;
}
addModuleByPath(path, data) {
const [folderNames, fileName] = [path.slice(0, -1), _.last(path)];
let currentFolder = this;
_.each(folderNames, folderName => {
currentFolder = currentFolder.getChild(folderName) || currentFolder.addFolder(folderName);
});
currentFolder.addModule(fileName, data);
}
walk(walker, state = {}) {
let stopped = false;
_.each(this.children, child => {
if (child.walk) {
state = child.walk(walker, state, stop);
} else {
state = walker(child, state, stop);
}
if (stopped) return false;
});
return state;
function stop(finalState) {
stopped = true;
return finalState;
}
}
toString(indent, opts) {
const { sortBy } = opts || {};
indent = indent || '|';
let str = `${indent} ${this.name} (${filesize(this.size)})\n`;
str += _(this.children)
.sortBy(sortBy)
.reverse()
.map(child => child.toString(`${indent} |`, opts))
.join('\n');
return str;
}
toChartData() {
return {
label: this.name,
path: this.path,
statSize: this.size,
parsedSize: this.parsedSize,
gzipSize: this.gzipSize,
groups: _.invokeMap(this.children, 'toChartData')
};
}
}
module.exports = {
Node,
Module,
Folder
};