update
This commit is contained in:
+62
-83
@@ -1,21 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
||||
|
||||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
const bfj = require('bfj');
|
||||
const fs = require('fs');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const mkdir = require('mkdirp');
|
||||
|
||||
const {
|
||||
bold
|
||||
} = require('chalk');
|
||||
@@ -24,12 +12,19 @@ const Logger = require('./Logger');
|
||||
|
||||
const viewer = require('./viewer');
|
||||
|
||||
const utils = require('./utils');
|
||||
|
||||
const {
|
||||
writeStats
|
||||
} = require('./statsUtils');
|
||||
|
||||
class BundleAnalyzerPlugin {
|
||||
constructor(opts = {}) {
|
||||
this.opts = _objectSpread({
|
||||
this.opts = {
|
||||
analyzerMode: 'server',
|
||||
analyzerHost: '127.0.0.1',
|
||||
reportFilename: null,
|
||||
reportTitle: utils.defaultTitle,
|
||||
defaultSizes: 'parsed',
|
||||
openAnalyzer: true,
|
||||
generateStatsFile: false,
|
||||
@@ -38,10 +33,10 @@ class BundleAnalyzerPlugin {
|
||||
excludeAssets: null,
|
||||
logLevel: 'info',
|
||||
// deprecated
|
||||
startAnalyzer: true
|
||||
}, opts, {
|
||||
startAnalyzer: true,
|
||||
...opts,
|
||||
analyzerPort: 'analyzerPort' in opts ? opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort : 8888
|
||||
});
|
||||
};
|
||||
this.server = null;
|
||||
this.logger = new Logger(this.opts.logLevel);
|
||||
}
|
||||
@@ -73,14 +68,14 @@ class BundleAnalyzerPlugin {
|
||||
|
||||
if (actions.length) {
|
||||
// Making analyzer logs to be after all webpack logs in the console
|
||||
setImmediate( /*#__PURE__*/_asyncToGenerator(function* () {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
yield Promise.all(actions.map(action => action()));
|
||||
await Promise.all(actions.map(action => action()));
|
||||
callback();
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -93,79 +88,63 @@ class BundleAnalyzerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
generateStatsFile(stats) {
|
||||
var _this = this;
|
||||
async generateStatsFile(stats) {
|
||||
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
|
||||
await fs.promises.mkdir(path.dirname(statsFilepath), {
|
||||
recursive: true
|
||||
});
|
||||
|
||||
return _asyncToGenerator(function* () {
|
||||
const statsFilepath = path.resolve(_this.compiler.outputPath, _this.opts.statsFilename);
|
||||
mkdir.sync(path.dirname(statsFilepath));
|
||||
|
||||
try {
|
||||
yield bfj.write(statsFilepath, stats, {
|
||||
space: 2,
|
||||
promises: 'ignore',
|
||||
buffers: 'ignore',
|
||||
maps: 'ignore',
|
||||
iterables: 'ignore',
|
||||
circular: 'ignore'
|
||||
});
|
||||
|
||||
_this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
|
||||
} catch (error) {
|
||||
_this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
|
||||
}
|
||||
})();
|
||||
try {
|
||||
await writeStats(stats, statsFilepath);
|
||||
this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
startAnalyzerServer(stats) {
|
||||
var _this2 = this;
|
||||
|
||||
return _asyncToGenerator(function* () {
|
||||
if (_this2.server) {
|
||||
(yield _this2.server).updateChartData(stats);
|
||||
} else {
|
||||
_this2.server = viewer.startServer(stats, {
|
||||
openBrowser: _this2.opts.openAnalyzer,
|
||||
host: _this2.opts.analyzerHost,
|
||||
port: _this2.opts.analyzerPort,
|
||||
bundleDir: _this2.getBundleDirFromCompiler(),
|
||||
logger: _this2.logger,
|
||||
defaultSizes: _this2.opts.defaultSizes,
|
||||
excludeAssets: _this2.opts.excludeAssets
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
generateJSONReport(stats) {
|
||||
var _this3 = this;
|
||||
|
||||
return _asyncToGenerator(function* () {
|
||||
yield viewer.generateJSONReport(stats, {
|
||||
reportFilename: path.resolve(_this3.compiler.outputPath, _this3.opts.reportFilename || 'report.json'),
|
||||
bundleDir: _this3.getBundleDirFromCompiler(),
|
||||
logger: _this3.logger,
|
||||
excludeAssets: _this3.opts.excludeAssets
|
||||
async startAnalyzerServer(stats) {
|
||||
if (this.server) {
|
||||
(await this.server).updateChartData(stats);
|
||||
} else {
|
||||
this.server = viewer.startServer(stats, {
|
||||
openBrowser: this.opts.openAnalyzer,
|
||||
host: this.opts.analyzerHost,
|
||||
port: this.opts.analyzerPort,
|
||||
reportTitle: this.opts.reportTitle,
|
||||
bundleDir: this.getBundleDirFromCompiler(),
|
||||
logger: this.logger,
|
||||
defaultSizes: this.opts.defaultSizes,
|
||||
excludeAssets: this.opts.excludeAssets
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
generateStaticReport(stats) {
|
||||
var _this4 = this;
|
||||
async generateJSONReport(stats) {
|
||||
await viewer.generateJSONReport(stats, {
|
||||
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
|
||||
bundleDir: this.getBundleDirFromCompiler(),
|
||||
logger: this.logger,
|
||||
excludeAssets: this.opts.excludeAssets
|
||||
});
|
||||
}
|
||||
|
||||
return _asyncToGenerator(function* () {
|
||||
yield viewer.generateReport(stats, {
|
||||
openBrowser: _this4.opts.openAnalyzer,
|
||||
reportFilename: path.resolve(_this4.compiler.outputPath, _this4.opts.reportFilename || 'report.html'),
|
||||
bundleDir: _this4.getBundleDirFromCompiler(),
|
||||
logger: _this4.logger,
|
||||
defaultSizes: _this4.opts.defaultSizes,
|
||||
excludeAssets: _this4.opts.excludeAssets
|
||||
});
|
||||
})();
|
||||
async generateStaticReport(stats) {
|
||||
await viewer.generateReport(stats, {
|
||||
openBrowser: this.opts.openAnalyzer,
|
||||
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
|
||||
reportTitle: this.opts.reportTitle,
|
||||
bundleDir: this.getBundleDirFromCompiler(),
|
||||
logger: this.logger,
|
||||
defaultSizes: this.opts.defaultSizes,
|
||||
excludeAssets: this.opts.excludeAssets
|
||||
});
|
||||
}
|
||||
|
||||
getBundleDirFromCompiler() {
|
||||
if (typeof this.compiler.outputFileSystem.constructor === 'undefined') {
|
||||
return this.compiler.outputPath;
|
||||
}
|
||||
|
||||
switch (this.compiler.outputFileSystem.constructor.name) {
|
||||
case 'MemoryFileSystem':
|
||||
return null;
|
||||
|
||||
+105
-37
@@ -35,13 +35,37 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
||||
const isAssetIncluded = createAssetsFilter(excludeAssets); // Sometimes all the information is located in `children` array (e.g. problem in #10)
|
||||
|
||||
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
|
||||
bundleStats = bundleStats.children[0];
|
||||
const {
|
||||
children
|
||||
} = bundleStats;
|
||||
bundleStats = bundleStats.children[0]; // Sometimes if there are additional child chunks produced add them as child assets,
|
||||
// leave the 1st one as that is considered the 'root' asset.
|
||||
|
||||
for (let i = 1; i < children.length; i++) {
|
||||
children[i].assets.forEach(asset => {
|
||||
asset.isChild = true;
|
||||
bundleStats.assets.push(asset);
|
||||
});
|
||||
}
|
||||
} else if (!_.isEmpty(bundleStats.children)) {
|
||||
// Sometimes if there are additional child chunks produced add them as child assets
|
||||
bundleStats.children.forEach(child => {
|
||||
child.assets.forEach(asset => {
|
||||
asset.isChild = true;
|
||||
bundleStats.assets.push(asset);
|
||||
});
|
||||
});
|
||||
} // Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
|
||||
|
||||
|
||||
bundleStats.assets = _.filter(bundleStats.assets, asset => {
|
||||
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
|
||||
bundleStats.assets = bundleStats.assets.filter(asset => {
|
||||
// Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
|
||||
if (asset.type && asset.type !== 'asset') {
|
||||
return false;
|
||||
} // 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 FILENAME_EXTENSIONS.test(asset.name) && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
|
||||
}); // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
|
||||
@@ -65,9 +89,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bundlesSources[statAsset.name] = bundleInfo.src;
|
||||
|
||||
_.assign(parsedModules, bundleInfo.modules);
|
||||
bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
|
||||
Object.assign(parsedModules, bundleInfo.modules);
|
||||
}
|
||||
|
||||
if (_.isEmpty(bundlesSources)) {
|
||||
@@ -77,59 +100,104 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
const modules = getBundleModules(bundleStats);
|
||||
const assets = bundleStats.assets.reduce((result, statAsset) => {
|
||||
// If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
|
||||
const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
|
||||
const modules = assetBundles ? getBundleModules(assetBundles) : [];
|
||||
|
||||
const assets = _.transform(bundleStats.assets, (result, statAsset) => {
|
||||
const asset = result[statAsset.name] = _.pick(statAsset, 'size');
|
||||
|
||||
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
|
||||
asset.parsedSize = Buffer.byteLength(bundlesSources[statAsset.name]);
|
||||
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
|
||||
const assetSources = bundlesSources && _.has(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null;
|
||||
|
||||
if (assetSources) {
|
||||
asset.parsedSize = Buffer.byteLength(assetSources.src);
|
||||
asset.gzipSize = gzipSize.sync(assetSources.src);
|
||||
} // Picking modules from current bundle script
|
||||
|
||||
|
||||
asset.modules = _(modules).filter(statModule => assetHasModule(statAsset, statModule)).each(statModule => {
|
||||
if (parsedModules) {
|
||||
statModule.parsedSrc = parsedModules[statModule.id];
|
||||
}
|
||||
});
|
||||
asset.tree = createModulesTree(asset.modules);
|
||||
}, {});
|
||||
const assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule)); // Adding parsed sources
|
||||
|
||||
return _.transform(assets, (result, asset, filename) => {
|
||||
result.push({
|
||||
label: filename,
|
||||
isAsset: true,
|
||||
// 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.
|
||||
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
|
||||
statSize: asset.tree.size || asset.size,
|
||||
parsedSize: asset.parsedSize,
|
||||
gzipSize: asset.gzipSize,
|
||||
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
||||
});
|
||||
}, []);
|
||||
if (parsedModules) {
|
||||
const unparsedEntryModules = [];
|
||||
|
||||
for (const statModule of assetModules) {
|
||||
if (parsedModules[statModule.id]) {
|
||||
statModule.parsedSrc = parsedModules[statModule.id];
|
||||
} else if (isEntryModule(statModule)) {
|
||||
unparsedEntryModules.push(statModule);
|
||||
}
|
||||
} // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
|
||||
// Because of this they basically become a concatenated module, for which we can't even precisely determine its
|
||||
// parsed source as it's located in the same scope as all Webpack runtime helpers.
|
||||
|
||||
|
||||
if (unparsedEntryModules.length && assetSources) {
|
||||
if (unparsedEntryModules.length === 1) {
|
||||
// So if there is only one entry we consider its parsed source to be all the bundle code excluding code
|
||||
// from parsed modules.
|
||||
unparsedEntryModules[0].parsedSrc = assetSources.runtimeSrc;
|
||||
} else {
|
||||
// If there are multiple entry points we move all of them under synthetic concatenated module.
|
||||
_.pullAll(assetModules, unparsedEntryModules);
|
||||
|
||||
assetModules.unshift({
|
||||
identifier: './entry modules',
|
||||
name: './entry modules',
|
||||
modules: unparsedEntryModules,
|
||||
size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
|
||||
parsedSrc: assetSources.runtimeSrc
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asset.modules = assetModules;
|
||||
asset.tree = createModulesTree(asset.modules);
|
||||
return result;
|
||||
}, {});
|
||||
return Object.entries(assets).map(([filename, asset]) => ({
|
||||
label: filename,
|
||||
isAsset: true,
|
||||
// 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.
|
||||
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
|
||||
statSize: asset.tree.size || asset.size,
|
||||
parsedSize: asset.parsedSize,
|
||||
gzipSize: asset.gzipSize,
|
||||
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
||||
}));
|
||||
}
|
||||
|
||||
function readStatsFromFile(filename) {
|
||||
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||
}
|
||||
|
||||
function getChildAssetBundles(bundleStats, assetName) {
|
||||
return (bundleStats.children || []).find(c => _(c.assetsByChunkName).values().flatten().includes(assetName));
|
||||
}
|
||||
|
||||
function getBundleModules(bundleStats) {
|
||||
return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id').value();
|
||||
return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id') // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
|
||||
.reject(isRuntimeModule).value();
|
||||
}
|
||||
|
||||
function assetHasModule(statAsset, statModule) {
|
||||
// Checking if this module is the part of asset chunks
|
||||
return _.some(statModule.chunks, moduleChunk => _.includes(statAsset.chunks, moduleChunk));
|
||||
return (statModule.chunks || []).some(moduleChunk => statAsset.chunks.includes(moduleChunk));
|
||||
}
|
||||
|
||||
function isEntryModule(statModule) {
|
||||
return statModule.depth === 0;
|
||||
}
|
||||
|
||||
function isRuntimeModule(statModule) {
|
||||
return statModule.moduleType === 'runtime';
|
||||
}
|
||||
|
||||
function createModulesTree(modules) {
|
||||
const root = new Folder('.');
|
||||
|
||||
_.each(modules, module => root.addModule(module));
|
||||
|
||||
modules.forEach(module => root.addModule(module));
|
||||
root.mergeNestedFolders();
|
||||
return root;
|
||||
}
|
||||
+16
-8
@@ -6,8 +6,6 @@ const {
|
||||
dirname
|
||||
} = require('path');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const commander = require('commander');
|
||||
|
||||
const {
|
||||
@@ -20,29 +18,37 @@ const viewer = require('../viewer');
|
||||
|
||||
const Logger = require('../Logger');
|
||||
|
||||
const utils = require('../utils');
|
||||
|
||||
const SIZES = new Set(['stat', 'parsed', 'gzip']);
|
||||
const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
|
||||
|
||||
Arguments:
|
||||
|
||||
|
||||
bundleStatsFile Path to Webpack Stats JSON file.
|
||||
bundleDir Directory containing all generated bundles.
|
||||
You should provided it if you want analyzer to show you the real parsed module sizes.
|
||||
By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server`,`static` or `json`.' + br('In `server` mode analyzer will start HTTP server to show bundle report.') + br('In `static` mode single HTML file with bundle report will be generated.') + br('In `json` mode single JSON file with bundle report will be generated.'), 'server').option( // Had to make `host` parameter optional in order to let `-h` flag output help message
|
||||
// Fixes https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/239
|
||||
'-h, --host [host]', 'Host that will be used in `server` mode to start HTTP server.', '127.0.0.1').option('-p, --port <n>', 'Port that will be used in `server` mode to start HTTP server.', 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[...SIZES].join(', ')}`), 'parsed').option('-O, --no-open', "Don't open report in default browser automatically.").option('-e, --exclude <regexp>', 'Assets that should be excluded from the report.' + br('Can be specified multiple times.'), array()).option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[...Logger.levels].join(', ')}`), Logger.defaultLevel).parse(process.argv);
|
||||
'-h, --host [host]', 'Host that will be used in `server` mode to start HTTP server.', '127.0.0.1').option('-p, --port <n>', 'Port that will be used in `server` mode to start HTTP server.', 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.').option('-t, --title <title>', 'String to use in title element of html report.').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[...SIZES].join(', ')}`), 'parsed').option('-O, --no-open', "Don't open report in default browser automatically.").option('-e, --exclude <regexp>', 'Assets that should be excluded from the report.' + br('Can be specified multiple times.'), array()).option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[...Logger.levels].join(', ')}`), Logger.defaultLevel).parse(process.argv);
|
||||
let [bundleStatsFile, bundleDir] = program.args;
|
||||
let {
|
||||
mode,
|
||||
host,
|
||||
port,
|
||||
report: reportFilename,
|
||||
title: reportTitle,
|
||||
defaultSizes,
|
||||
logLevel,
|
||||
open: openBrowser,
|
||||
exclude: excludeAssets,
|
||||
args: [bundleStatsFile, bundleDir]
|
||||
} = program;
|
||||
exclude: excludeAssets
|
||||
} = program.opts();
|
||||
const logger = new Logger(logLevel);
|
||||
|
||||
if (typeof reportTitle === 'undefined') {
|
||||
reportTitle = utils.defaultTitle;
|
||||
}
|
||||
|
||||
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
|
||||
|
||||
if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
|
||||
@@ -74,6 +80,7 @@ if (mode === 'server') {
|
||||
port,
|
||||
host,
|
||||
defaultSizes,
|
||||
reportTitle,
|
||||
bundleDir,
|
||||
excludeAssets,
|
||||
logger: new Logger(logLevel)
|
||||
@@ -82,6 +89,7 @@ if (mode === 'server') {
|
||||
viewer.generateReport(bundleStats, {
|
||||
openBrowser,
|
||||
reportFilename: resolve(reportFilename || 'report.html'),
|
||||
reportTitle,
|
||||
defaultSizes,
|
||||
bundleDir,
|
||||
excludeAssets,
|
||||
@@ -103,7 +111,7 @@ function showHelp(error) {
|
||||
}
|
||||
|
||||
function br(str) {
|
||||
return `\n${_.repeat(' ', 28)}${str}`;
|
||||
return `\n${' '.repeat(28)}${str}`;
|
||||
}
|
||||
|
||||
function array() {
|
||||
|
||||
+83
-10
@@ -22,9 +22,45 @@ function parseBundle(bundlePath) {
|
||||
ecmaVersion: 2050
|
||||
});
|
||||
const walkState = {
|
||||
locations: null
|
||||
locations: null,
|
||||
expressionStatementDepth: 0
|
||||
};
|
||||
walk.recursive(ast, walkState, {
|
||||
ExpressionStatement(node, state, c) {
|
||||
if (state.locations) return;
|
||||
state.expressionStatementDepth++;
|
||||
|
||||
if ( // Webpack 5 stores modules in the the top-level IIFE
|
||||
state.expressionStatementDepth === 1 && ast.body.includes(node) && isIIFE(node)) {
|
||||
const fn = getIIFECallExpression(node);
|
||||
|
||||
if ( // It should not contain neither arguments
|
||||
fn.arguments.length === 0 && // ...nor parameters
|
||||
fn.callee.params.length === 0) {
|
||||
// Modules are stored in the very first variable declaration as hash
|
||||
const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
|
||||
|
||||
if (firstVariableDeclaration) {
|
||||
for (const declaration of firstVariableDeclaration.declarations) {
|
||||
if (declaration.init) {
|
||||
state.locations = getModulesLocations(declaration.init);
|
||||
|
||||
if (state.locations) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.locations) {
|
||||
c(node.expression, state);
|
||||
}
|
||||
|
||||
state.expressionStatementDepth--;
|
||||
},
|
||||
|
||||
AssignmentExpression(node, state) {
|
||||
if (state.locations) return; // Modules are stored in exports.modules:
|
||||
// exports.modules = {};
|
||||
@@ -76,7 +112,7 @@ function parseBundle(bundlePath) {
|
||||
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
||||
|
||||
|
||||
_.each(args, arg => c(arg, state));
|
||||
args.forEach(arg => c(arg, state));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -89,10 +125,43 @@ function parseBundle(bundlePath) {
|
||||
}
|
||||
|
||||
return {
|
||||
modules,
|
||||
src: content,
|
||||
modules
|
||||
runtimeSrc: getBundleRuntime(content, walkState.locations)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Returns bundle source except modules
|
||||
*/
|
||||
|
||||
|
||||
function getBundleRuntime(content, modulesLocations) {
|
||||
const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
|
||||
let result = '';
|
||||
let lastIndex = 0;
|
||||
|
||||
for (const {
|
||||
start,
|
||||
end
|
||||
} of sortedLocations) {
|
||||
result += content.slice(lastIndex, start);
|
||||
lastIndex = end;
|
||||
}
|
||||
|
||||
return result + content.slice(lastIndex, content.length);
|
||||
}
|
||||
|
||||
function isIIFE(node) {
|
||||
return node.type === 'ExpressionStatement' && (node.expression.type === 'CallExpression' || node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression');
|
||||
}
|
||||
|
||||
function getIIFECallExpression(node) {
|
||||
if (node.expression.type === 'UnaryExpression') {
|
||||
return node.expression.argument;
|
||||
} else {
|
||||
return node.expression;
|
||||
}
|
||||
}
|
||||
|
||||
function isModulesList(node) {
|
||||
return isSimpleModulesList(node) || // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
|
||||
@@ -107,11 +176,11 @@ function isSimpleModulesList(node) {
|
||||
}
|
||||
|
||||
function isModulesHash(node) {
|
||||
return node.type === 'ObjectExpression' && _(node.properties).map('value').every(isModuleWrapper);
|
||||
return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
|
||||
}
|
||||
|
||||
function isModulesArray(node) {
|
||||
return node.type === 'ArrayExpression' && _.every(node.elements, elem => // Some of array items may be skipped because there is no module with such id
|
||||
return node.type === 'ArrayExpression' && node.elements.every(elem => // Some of array items may be skipped because there is no module with such id
|
||||
!elem || isModuleWrapper(elem));
|
||||
}
|
||||
|
||||
@@ -143,7 +212,7 @@ function isNumericId(node) {
|
||||
|
||||
function isChunkIds(node) {
|
||||
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
||||
return node.type === 'ArrayExpression' && _.every(node.elements, isModuleId);
|
||||
return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
|
||||
}
|
||||
|
||||
function isAsyncChunkPushExpression(node) {
|
||||
@@ -171,9 +240,10 @@ function getModulesLocations(node) {
|
||||
if (node.type === 'ObjectExpression') {
|
||||
// Modules hash
|
||||
const modulesNodes = node.properties;
|
||||
return _.transform(modulesNodes, (result, moduleNode) => {
|
||||
return modulesNodes.reduce((result, moduleNode) => {
|
||||
const moduleId = moduleNode.key.name || moduleNode.key.value;
|
||||
result[moduleId] = getModuleLocation(moduleNode.value);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@@ -186,9 +256,12 @@ function getModulesLocations(node) {
|
||||
0;
|
||||
const modulesNodes = isOptimizedArray ? // The modules reside in the `concat()` function call arguments
|
||||
node.arguments[0].elements : node.elements;
|
||||
return _.transform(modulesNodes, (result, moduleNode, i) => {
|
||||
if (!moduleNode) return;
|
||||
result[i + minId] = getModuleLocation(moduleNode);
|
||||
return modulesNodes.reduce((result, moduleNode, i) => {
|
||||
if (moduleNode) {
|
||||
result[i + minId] = getModuleLocation(moduleNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
createWriteStream
|
||||
} = require('fs');
|
||||
|
||||
const {
|
||||
Readable
|
||||
} = require('stream');
|
||||
|
||||
class StatsSerializeStream extends Readable {
|
||||
constructor(stats) {
|
||||
super();
|
||||
this._indentLevel = 0;
|
||||
this._stringifier = this._stringify(stats);
|
||||
}
|
||||
|
||||
get _indent() {
|
||||
return ' '.repeat(this._indentLevel);
|
||||
}
|
||||
|
||||
_read() {
|
||||
let readMore = true;
|
||||
|
||||
while (readMore) {
|
||||
const {
|
||||
value,
|
||||
done
|
||||
} = this._stringifier.next();
|
||||
|
||||
if (done) {
|
||||
this.push(null);
|
||||
readMore = false;
|
||||
} else {
|
||||
readMore = this.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*_stringify(obj) {
|
||||
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
|
||||
yield JSON.stringify(obj);
|
||||
} else if (Array.isArray(obj)) {
|
||||
yield '[';
|
||||
this._indentLevel++;
|
||||
let isFirst = true;
|
||||
|
||||
for (let item of obj) {
|
||||
if (item === undefined) {
|
||||
item = null;
|
||||
}
|
||||
|
||||
yield `${isFirst ? '' : ','}\n${this._indent}`;
|
||||
yield* this._stringify(item);
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
this._indentLevel--;
|
||||
yield obj.length ? `\n${this._indent}]` : ']';
|
||||
} else {
|
||||
yield '{';
|
||||
this._indentLevel++;
|
||||
let isFirst = true;
|
||||
const entries = Object.entries(obj);
|
||||
|
||||
for (const [itemKey, itemValue] of entries) {
|
||||
if (itemValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
|
||||
yield* this._stringify(itemValue);
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
this._indentLevel--;
|
||||
yield entries.length ? `\n${this._indent}}` : '}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.StatsSerializeStream = StatsSerializeStream;
|
||||
exports.writeStats = writeStats;
|
||||
|
||||
async function writeStats(stats, filepath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
new StatsSerializeStream(stats).on('end', resolve).on('error', reject).pipe(createWriteStream(filepath));
|
||||
});
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
"use strict";
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const path = require('path');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
const assetsRoot = path.join(projectRoot, 'public');
|
||||
exports.renderViewer = renderViewer;
|
||||
/**
|
||||
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
||||
*/
|
||||
|
||||
function escapeJson(json) {
|
||||
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
||||
}
|
||||
|
||||
function getAssetContent(filename) {
|
||||
const assetPath = path.join(assetsRoot, filename);
|
||||
|
||||
if (!assetPath.startsWith(assetsRoot)) {
|
||||
throw new Error(`"${filename}" is outside of the assets root`);
|
||||
}
|
||||
|
||||
return fs.readFileSync(assetPath, 'utf8');
|
||||
}
|
||||
|
||||
function html(strings, ...values) {
|
||||
return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
|
||||
}
|
||||
|
||||
function getScript(filename, mode) {
|
||||
if (mode === 'static') {
|
||||
return `<!-- ${_.escape(filename)} -->
|
||||
<script>${getAssetContent(filename)}</script>`;
|
||||
} else {
|
||||
return `<script src="${_.escape(filename)}"></script>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderViewer({
|
||||
title,
|
||||
enableWebSocket,
|
||||
chartData,
|
||||
defaultSizes,
|
||||
mode
|
||||
} = {}) {
|
||||
return html`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>${_.escape(title)}</title>
|
||||
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII=" type="image/x-icon" />
|
||||
|
||||
<script>
|
||||
window.enableWebSocket = ${escapeJson(enableWebSocket)};
|
||||
</script>
|
||||
${getScript('viewer.js', mode)}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.chartData = ${escapeJson(chartData)};
|
||||
window.defaultSizes = ${escapeJson(defaultSizes)};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
+1
-3
@@ -69,8 +69,7 @@ class BaseFolder extends _Node.default {
|
||||
|
||||
walk(walker, state = {}, deep = true) {
|
||||
let stopped = false;
|
||||
|
||||
_lodash.default.each(this.children, child => {
|
||||
Object.values(this.children).forEach(child => {
|
||||
if (deep && child.walk) {
|
||||
state = child.walk(walker, state, stop);
|
||||
} else {
|
||||
@@ -79,7 +78,6 @@ class BaseFolder extends _Node.default {
|
||||
|
||||
if (stopped) return false;
|
||||
});
|
||||
|
||||
return state;
|
||||
|
||||
function stop(finalState) {
|
||||
|
||||
+4
-12
@@ -17,12 +17,6 @@ var _utils = require("./utils");
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
class ConcatenatedModule extends _Module.default {
|
||||
constructor(name, data, parent) {
|
||||
super(name, data, parent);
|
||||
@@ -32,7 +26,7 @@ class ConcatenatedModule extends _Module.default {
|
||||
}
|
||||
|
||||
fillContentModules() {
|
||||
_lodash.default.each(this.data.modules, moduleData => this.addContentModule(moduleData));
|
||||
this.data.modules.forEach(moduleData => this.addContentModule(moduleData));
|
||||
}
|
||||
|
||||
addContentModule(moduleData) {
|
||||
@@ -44,8 +38,7 @@ class ConcatenatedModule extends _Module.default {
|
||||
|
||||
const [folders, fileName] = [pathParts.slice(0, -1), _lodash.default.last(pathParts)];
|
||||
let currentFolder = this;
|
||||
|
||||
_lodash.default.each(folders, folderName => {
|
||||
folders.forEach(folderName => {
|
||||
let childFolder = currentFolder.getChild(folderName);
|
||||
|
||||
if (!childFolder) {
|
||||
@@ -54,7 +47,6 @@ class ConcatenatedModule extends _Module.default {
|
||||
|
||||
currentFolder = childFolder;
|
||||
});
|
||||
|
||||
const module = new _ContentModule.default(fileName, moduleData, this);
|
||||
currentFolder.addChildModule(module);
|
||||
}
|
||||
@@ -79,10 +71,10 @@ class ConcatenatedModule extends _Module.default {
|
||||
}
|
||||
|
||||
toChartData() {
|
||||
return _objectSpread({}, super.toChartData(), {
|
||||
return { ...super.toChartData(),
|
||||
concatenated: true,
|
||||
groups: _lodash.default.invokeMap(this.children, 'toChartData')
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-8
@@ -9,12 +9,6 @@ var _BaseFolder = _interopRequireDefault(require("./BaseFolder"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
class ContentFolder extends _BaseFolder.default {
|
||||
constructor(name, ownerModule, parent) {
|
||||
super(name, parent);
|
||||
@@ -38,11 +32,11 @@ class ContentFolder extends _BaseFolder.default {
|
||||
}
|
||||
|
||||
toChartData() {
|
||||
return _objectSpread({}, super.toChartData(), {
|
||||
return { ...super.toChartData(),
|
||||
parsedSize: this.parsedSize,
|
||||
gzipSize: this.gzipSize,
|
||||
inaccurateSizes: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-8
@@ -9,12 +9,6 @@ var _Module = _interopRequireDefault(require("./Module"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
class ContentModule extends _Module.default {
|
||||
constructor(name, data, ownerModule, parent) {
|
||||
super(name, data, parent);
|
||||
@@ -38,9 +32,9 @@ class ContentModule extends _Module.default {
|
||||
}
|
||||
|
||||
toChartData() {
|
||||
return _objectSpread({}, super.toChartData(), {
|
||||
return { ...super.toChartData(),
|
||||
inaccurateSizes: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-11
@@ -19,12 +19,6 @@ var _utils = require("./utils");
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
class Folder extends _BaseFolder.default {
|
||||
get parsedSize() {
|
||||
return this.src ? this.src.length : 0;
|
||||
@@ -47,8 +41,7 @@ class Folder extends _BaseFolder.default {
|
||||
|
||||
const [folders, fileName] = [pathParts.slice(0, -1), _lodash.default.last(pathParts)];
|
||||
let currentFolder = this;
|
||||
|
||||
_lodash.default.each(folders, folderName => {
|
||||
folders.forEach(folderName => {
|
||||
let childNode = currentFolder.getChild(folderName);
|
||||
|
||||
if ( // Folder is not created yet
|
||||
@@ -62,17 +55,16 @@ class Folder extends _BaseFolder.default {
|
||||
|
||||
currentFolder = childNode;
|
||||
});
|
||||
|
||||
const ModuleConstructor = moduleData.modules ? _ConcatenatedModule.default : _Module.default;
|
||||
const module = new ModuleConstructor(fileName, moduleData, this);
|
||||
currentFolder.addChildModule(module);
|
||||
}
|
||||
|
||||
toChartData() {
|
||||
return _objectSpread({}, super.toChartData(), {
|
||||
return { ...super.toChartData(),
|
||||
parsedSize: this.parsedSize,
|
||||
gzipSize: this.gzipSize
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-6
@@ -1,11 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
inspect
|
||||
inspect,
|
||||
types
|
||||
} = require('util');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const opener = require('opener');
|
||||
|
||||
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
exports.createAssetsFilter = createAssetsFilter;
|
||||
|
||||
@@ -15,11 +18,11 @@ function createAssetsFilter(excludePatterns) {
|
||||
pattern = new RegExp(pattern, 'u');
|
||||
}
|
||||
|
||||
if (_.isRegExp(pattern)) {
|
||||
if (types.isRegExp(pattern)) {
|
||||
return asset => pattern.test(asset);
|
||||
}
|
||||
|
||||
if (!_.isFunction(pattern)) {
|
||||
if (typeof pattern !== 'function') {
|
||||
throw new TypeError(`Pattern should be either string, RegExp or a function, but "${inspect(pattern, {
|
||||
depth: 0
|
||||
})}" got.`);
|
||||
@@ -29,7 +32,7 @@ function createAssetsFilter(excludePatterns) {
|
||||
}).value();
|
||||
|
||||
if (excludeFunctions.length) {
|
||||
return asset => _.every(excludeFunctions, fn => fn(asset) !== true);
|
||||
return asset => excludeFunctions.every(fn => fn(asset) !== true);
|
||||
} else {
|
||||
return () => true;
|
||||
}
|
||||
@@ -40,12 +43,25 @@ function createAssetsFilter(excludePatterns) {
|
||||
* */
|
||||
|
||||
|
||||
exports.getCurrentTime = function () {
|
||||
exports.defaultTitle = function () {
|
||||
const time = new Date();
|
||||
const year = time.getFullYear();
|
||||
const month = MONTHS[time.getMonth()];
|
||||
const day = time.getDate();
|
||||
const hour = `0${time.getHours()}`.slice(-2);
|
||||
const minute = `0${time.getMinutes()}`.slice(-2);
|
||||
return `${day} ${month} ${year} at ${hour}:${minute}`;
|
||||
const currentTime = `${day} ${month} ${year} at ${hour}:${minute}`;
|
||||
return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`;
|
||||
};
|
||||
/**
|
||||
* Calls opener on a URI, but silently try / catches it.
|
||||
*/
|
||||
|
||||
|
||||
exports.open = function (uri, logger) {
|
||||
try {
|
||||
opener(uri);
|
||||
} catch (err) {
|
||||
logger.debug(`Opener failed to open "${uri}":\n${err}`);
|
||||
}
|
||||
};
|
||||
+138
-188
@@ -1,9 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
||||
|
||||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -12,28 +8,36 @@ const http = require('http');
|
||||
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const sirv = require('sirv');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const ejs = require('ejs');
|
||||
|
||||
const opener = require('opener');
|
||||
|
||||
const mkdir = require('mkdirp');
|
||||
|
||||
const {
|
||||
bold
|
||||
} = require('chalk');
|
||||
|
||||
const utils = require('./utils');
|
||||
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const analyzer = require('./analyzer');
|
||||
|
||||
const {
|
||||
open
|
||||
} = require('./utils');
|
||||
|
||||
const {
|
||||
renderViewer
|
||||
} = require('./template');
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
const assetsRoot = path.join(projectRoot, 'public');
|
||||
|
||||
function resolveTitle(reportTitle) {
|
||||
if (typeof reportTitle === 'function') {
|
||||
return reportTitle();
|
||||
} else {
|
||||
return reportTitle;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
generateReport,
|
||||
@@ -41,192 +45,138 @@ module.exports = {
|
||||
// deprecated
|
||||
start: startServer
|
||||
};
|
||||
const title = `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${utils.getCurrentTime()}]`;
|
||||
|
||||
function startServer(_x, _x2) {
|
||||
return _startServer.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _startServer() {
|
||||
_startServer = _asyncToGenerator(function* (bundleStats, opts) {
|
||||
const {
|
||||
port = 8888,
|
||||
host = '127.0.0.1',
|
||||
openBrowser = true,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
defaultSizes = 'parsed',
|
||||
excludeAssets = null
|
||||
} = opts || {};
|
||||
const analyzerOpts = {
|
||||
logger,
|
||||
excludeAssets
|
||||
};
|
||||
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
const app = express(); // Explicitly using our `ejs` dependency to render templates
|
||||
// Fixes #17
|
||||
|
||||
app.engine('ejs', require('ejs').renderFile);
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', `${projectRoot}/views`);
|
||||
app.use(express.static(`${projectRoot}/public`));
|
||||
app.use('/', (req, res) => {
|
||||
res.render('viewer', {
|
||||
mode: 'server',
|
||||
title,
|
||||
|
||||
get chartData() {
|
||||
return chartData;
|
||||
},
|
||||
|
||||
defaultSizes,
|
||||
enableWebSocket: true,
|
||||
// Helpers
|
||||
escapeJson
|
||||
});
|
||||
});
|
||||
const server = http.createServer(app);
|
||||
yield new Promise(resolve => {
|
||||
server.listen(port, host, () => {
|
||||
resolve();
|
||||
const url = `http://${host}:${server.address().port}`;
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` + `Use ${bold('Ctrl+C')} to close it`);
|
||||
|
||||
if (openBrowser) {
|
||||
opener(url);
|
||||
}
|
||||
});
|
||||
});
|
||||
const wss = new WebSocket.Server({
|
||||
server
|
||||
});
|
||||
wss.on('connection', ws => {
|
||||
ws.on('error', err => {
|
||||
// Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
|
||||
if (err.errno) return;
|
||||
logger.info(err.message);
|
||||
});
|
||||
});
|
||||
return {
|
||||
ws: wss,
|
||||
http: server,
|
||||
updateChartData
|
||||
};
|
||||
|
||||
function updateChartData(bundleStats) {
|
||||
const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
||||
if (!newChartData) return;
|
||||
chartData = newChartData;
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({
|
||||
event: 'chartDataUpdated',
|
||||
data: newChartData
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
async function startServer(bundleStats, opts) {
|
||||
const {
|
||||
port = 8888,
|
||||
host = '127.0.0.1',
|
||||
openBrowser = true,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
defaultSizes = 'parsed',
|
||||
excludeAssets = null,
|
||||
reportTitle
|
||||
} = opts || {};
|
||||
const analyzerOpts = {
|
||||
logger,
|
||||
excludeAssets
|
||||
};
|
||||
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
const sirvMiddleware = sirv(`${projectRoot}/public`, {
|
||||
// disables caching and traverse the file system on every request
|
||||
dev: true
|
||||
});
|
||||
return _startServer.apply(this, arguments);
|
||||
}
|
||||
|
||||
function generateReport(_x3, _x4) {
|
||||
return _generateReport.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _generateReport() {
|
||||
_generateReport = _asyncToGenerator(function* (bundleStats, opts) {
|
||||
const {
|
||||
openBrowser = true,
|
||||
reportFilename,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
defaultSizes = 'parsed',
|
||||
excludeAssets = null
|
||||
} = opts || {};
|
||||
const chartData = getChartData({
|
||||
logger,
|
||||
excludeAssets
|
||||
}, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
yield new Promise((resolve, reject) => {
|
||||
ejs.renderFile(`${projectRoot}/views/viewer.ejs`, {
|
||||
mode: 'static',
|
||||
title,
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'GET' && req.url === '/') {
|
||||
const html = renderViewer({
|
||||
mode: 'server',
|
||||
title: resolveTitle(reportTitle),
|
||||
chartData,
|
||||
defaultSizes,
|
||||
enableWebSocket: false,
|
||||
// Helpers
|
||||
assetContent: getAssetContent,
|
||||
escapeJson
|
||||
}, (err, reportHtml) => {
|
||||
try {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
||||
mkdir.sync(path.dirname(reportFilepath));
|
||||
fs.writeFileSync(reportFilepath, reportHtml);
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
||||
|
||||
if (openBrowser) {
|
||||
opener(`file://${reportFilepath}`);
|
||||
}
|
||||
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
enableWebSocket: true
|
||||
});
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.end(html);
|
||||
} else {
|
||||
sirvMiddleware(req, res);
|
||||
}
|
||||
});
|
||||
await new Promise(resolve => {
|
||||
server.listen(port, host, () => {
|
||||
resolve();
|
||||
const url = `http://${host}:${server.address().port}`;
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` + `Use ${bold('Ctrl+C')} to close it`);
|
||||
|
||||
if (openBrowser) {
|
||||
open(url, logger);
|
||||
}
|
||||
});
|
||||
});
|
||||
return _generateReport.apply(this, arguments);
|
||||
}
|
||||
|
||||
function generateJSONReport(_x5, _x6) {
|
||||
return _generateJSONReport.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _generateJSONReport() {
|
||||
_generateJSONReport = _asyncToGenerator(function* (bundleStats, opts) {
|
||||
const {
|
||||
reportFilename,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
excludeAssets = null
|
||||
} = opts || {};
|
||||
const chartData = getChartData({
|
||||
logger,
|
||||
excludeAssets
|
||||
}, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
mkdir.sync(path.dirname(reportFilename));
|
||||
fs.writeFileSync(reportFilename, JSON.stringify(chartData));
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
||||
const wss = new WebSocket.Server({
|
||||
server
|
||||
});
|
||||
return _generateJSONReport.apply(this, arguments);
|
||||
}
|
||||
wss.on('connection', ws => {
|
||||
ws.on('error', err => {
|
||||
// Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
|
||||
if (err.errno) return;
|
||||
logger.info(err.message);
|
||||
});
|
||||
});
|
||||
return {
|
||||
ws: wss,
|
||||
http: server,
|
||||
updateChartData
|
||||
};
|
||||
|
||||
function getAssetContent(filename) {
|
||||
const assetPath = path.join(assetsRoot, filename);
|
||||
|
||||
if (!assetPath.startsWith(assetsRoot)) {
|
||||
throw new Error(`"${filename}" is outside of the assets root`);
|
||||
function updateChartData(bundleStats) {
|
||||
const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
||||
if (!newChartData) return;
|
||||
chartData = newChartData;
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({
|
||||
event: 'chartDataUpdated',
|
||||
data: newChartData
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fs.readFileSync(assetPath, 'utf8');
|
||||
}
|
||||
/**
|
||||
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
||||
*/
|
||||
|
||||
async function generateReport(bundleStats, opts) {
|
||||
const {
|
||||
openBrowser = true,
|
||||
reportFilename,
|
||||
reportTitle,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
defaultSizes = 'parsed',
|
||||
excludeAssets = null
|
||||
} = opts || {};
|
||||
const chartData = getChartData({
|
||||
logger,
|
||||
excludeAssets
|
||||
}, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
const reportHtml = renderViewer({
|
||||
mode: 'static',
|
||||
title: resolveTitle(reportTitle),
|
||||
chartData,
|
||||
defaultSizes,
|
||||
enableWebSocket: false
|
||||
});
|
||||
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
||||
fs.mkdirSync(path.dirname(reportFilepath), {
|
||||
recursive: true
|
||||
});
|
||||
fs.writeFileSync(reportFilepath, reportHtml);
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
||||
|
||||
function escapeJson(json) {
|
||||
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
||||
if (openBrowser) {
|
||||
open(`file://${reportFilepath}`, logger);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateJSONReport(bundleStats, opts) {
|
||||
const {
|
||||
reportFilename,
|
||||
bundleDir = null,
|
||||
logger = new Logger(),
|
||||
excludeAssets = null
|
||||
} = opts || {};
|
||||
const chartData = getChartData({
|
||||
logger,
|
||||
excludeAssets
|
||||
}, bundleStats, bundleDir);
|
||||
if (!chartData) return;
|
||||
await fs.promises.mkdir(path.dirname(reportFilename), {
|
||||
recursive: true
|
||||
});
|
||||
await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
||||
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
||||
}
|
||||
|
||||
function getChartData(analyzerOpts, ...args) {
|
||||
|
||||
Reference in New Issue
Block a user