This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+62 -83
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {