webpack-bundle-analyzer-sunburst
Version:
Webpack plugin and CLI utility that represents bundle content either as an interactive zoomable treemap or a sunburst chart
163 lines (130 loc) • 4.76 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var gzipSize = require('gzip-size');
var Logger = require('./Logger');
var _require = require('../lib/tree'),
Folder = _require.Folder;
var _require2 = require('../lib/parseUtils'),
parseBundle = _require2.parseBundle;
var FILENAME_QUERY_REGEXP = /\?.*$/;
module.exports = {
getViewerData: getViewerData,
readStatsFromFile: readStatsFromFile
};
function getViewerData(bundleStats, bundleDir, opts) {
var _ref = opts || {},
_ref$logger = _ref.logger,
logger = _ref$logger === undefined ? new Logger() : _ref$logger;
// Picking only `*.js` assets from bundle that has non-empty `chunks` array
bundleStats.assets = _.filter(bundleStats.assets, function (asset) {
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
// See #22
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks);
});
// Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
var parsedModuleSizes = null;
var bundlesSources = {};
var parsedModules = {};
if (bundleDir) {
for (var _iterator = bundleStats.assets, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref2;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref2 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref2 = _i.value;
}
var statAsset = _ref2;
var assetFile = path.join(bundleDir, statAsset.name);
var bundleInfo = void 0;
try {
bundleInfo = parseBundle(assetFile);
} catch (err) {
bundleInfo = null;
}
if (bundleInfo) {
bundlesSources[statAsset.name] = bundleInfo.src;
_.assign(parsedModules, bundleInfo.modules);
} else {
logger.warn('\nCouldn\'t parse bundle asset "' + assetFile + '".\n' + 'Analyzer will use module sizes from stats file.\n');
parsedModules = null;
bundlesSources = null;
break;
}
}
if (parsedModules) {
parsedModuleSizes = _.mapValues(parsedModules, function (moduleSrc) {
return {
raw: moduleSrc.length,
gzip: gzipSize.sync(moduleSrc)
};
});
}
}
var assets = _.transform(bundleStats.assets, function (result, statAsset) {
var asset = result[statAsset.name] = _.pick(statAsset, 'size');
if (bundlesSources) {
asset.parsedSize = bundlesSources[statAsset.name].length;
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
}
// Picking modules from current bundle script
asset.modules = _(bundleStats.modules).filter(function (statModule) {
return assetHasModule(statAsset, statModule);
}).each(function (statModule) {
if (parsedModuleSizes) {
statModule.parsedSize = parsedModuleSizes[statModule.id].raw;
statModule.gzipSize = parsedModuleSizes[statModule.id].gzip;
}
});
asset.tree = createModulesTree(asset.modules);
}, {});
return _.transform(assets, function (result, asset, filename) {
result.push({
label: filename,
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
// be the size of minified bundle.
statSize: asset.tree.size,
parsedSize: asset.parsedSize,
gzipSize: asset.gzipSize,
groups: _.invokeMap(asset.tree.children, 'toChartData')
});
}, []);
}
function readStatsFromFile(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
}
function assetHasModule(statAsset, statModule) {
return _.some(statModule.chunks, function (moduleChunk) {
return _.includes(statAsset.chunks, moduleChunk);
});
}
function createModulesTree(modules) {
var root = new Folder('.');
_.each(modules, function (module) {
var path = getModulePath(module.name);
if (path) {
root.addModuleByPath(path, module);
}
});
return root;
}
function getModulePath(path) {
var parsedPath = _
// Removing loaders from module path: they're joined by `!` and the last part is a raw module path
.last(path.split('!'))
// Splitting module path into parts
.split('/')
// Removing first `.`
.slice(1)
// Replacing `~` with `node_modules`
.map(function (part) {
return part === '~' ? 'node_modules' : part;
});
return parsedPath.length ? parsedPath : null;
}