webpack-bundle-analyzer 4.10.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,7 +45,7 @@ This module will help you:
45
45
  4. Optimize it!
46
46
 
47
47
  And the best thing is it supports minified bundles! It parses them to get real size of bundled modules.
48
- And it also shows their gzipped sizes!
48
+ And it also shows their gzipped or Brotli sizes!
49
49
 
50
50
  <h2 align="center">Options (for plugin)</h2>
51
51
 
@@ -61,7 +61,8 @@ new BundleAnalyzerPlugin(options?: object)
61
61
  |**`analyzerUrl`**|`{Function}` called with `{ listenHost: string, listenHost: string, boundAddress: server.address}`. [server.address comes from Node.js](https://nodejs.org/api/net.html#serveraddress)| Default: `http://${listenHost}:${boundAddress.port}`. The URL printed to console with server mode.|
62
62
  |**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
63
63
  |**`reportTitle`**|`{String\|function}`|Default: function that returns pretty printed current date and time. Content of the HTML `title` element; or a function of the form `() => string` that provides the content.|
64
- |**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
64
+ |**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`, `brotli`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
65
+ |**`compressionAlgorithm`**|One of: `gzip`, `brotli`|Default: `gzip`. Compression type used to calculate the compressed module sizes.|
65
66
  |**`openAnalyzer`**|`{Boolean}`|Default: `true`. Automatically open report in default browser.|
66
67
  |**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory|
67
68
  |**`statsFilename`**|`{String}`|Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
@@ -80,7 +81,7 @@ command:
80
81
  webpack --profile --json > stats.json
81
82
  ```
82
83
 
83
- If you're on Windows and using PowerShell, you can generate the stats file with this command to [avoid BOM issues](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/47):
84
+ If you're on Windows and using PowerShell, you can generate the stats file with this command to [avoid BOM issues](https://github.com/webpack/webpack-bundle-analyzer/issues/47):
84
85
 
85
86
  ```
86
87
  webpack --profile --json | Out-file 'stats.json' -Encoding OEM
@@ -111,23 +112,25 @@ Directory containing all generated bundles.
111
112
  ### `options`
112
113
 
113
114
  ```
114
- -V, --version output the version number
115
- -m, --mode <mode> Analyzer mode. Should be `server`, `static` or `json`.
116
- In `server` mode analyzer will start HTTP server to show bundle report.
117
- In `static` mode single HTML file with bundle report will be generated.
118
- In `json` mode single JSON file with bundle report will be generated. (default: server)
119
- -h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
120
- -p, --port <n> Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
121
- -r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
122
- -t, --title <title> String to use in title element of html report. (default: pretty printed current date)
123
- -s, --default-sizes <type> Module sizes to show in treemap by default.
124
- Possible values: stat, parsed, gzip (default: parsed)
125
- -O, --no-open Don't open report in default browser automatically.
126
- -e, --exclude <regexp> Assets that should be excluded from the report.
127
- Can be specified multiple times.
128
- -l, --log-level <level> Log level.
129
- Possible values: debug, info, warn, error, silent (default: info)
130
- -h, --help output usage information
115
+ -V, --version output the version number
116
+ -m, --mode <mode> Analyzer mode. Should be `server`, `static` or `json`.
117
+ In `server` mode analyzer will start HTTP server to show bundle report.
118
+ In `static` mode single HTML file with bundle report will be generated.
119
+ In `json` mode single JSON file with bundle report will be generated. (default: server)
120
+ -h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
121
+ -p, --port <n> Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
122
+ -r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
123
+ -t, --title <title> String to use in title element of html report. (default: pretty printed current date)
124
+ -s, --default-sizes <type> Module sizes to show in treemap by default.
125
+ Possible values: stat, parsed, gzip, brotli (default: parsed)
126
+ --compression-algorithm <type> Compression algorithm that will be used to calculate the compressed module sizes.
127
+ Possible values: gzip, brotli (default: gzip)
128
+ -O, --no-open Don't open report in default browser automatically.
129
+ -e, --exclude <regexp> Assets that should be excluded from the report.
130
+ Can be specified multiple times.
131
+ -l, --log-level <level> Log level.
132
+ Possible values: debug, info, warn, error, silent (default: info)
133
+ -h, --help output usage information
131
134
  ```
132
135
 
133
136
  <h2 align="center" id="size-definitions">Size definitions</h2>
@@ -151,6 +154,10 @@ as Uglify, then this value will reflect the minified size of your code.
151
154
 
152
155
  This is the size of running the parsed bundles/modules through gzip compression.
153
156
 
157
+ ### `brotli`
158
+
159
+ This is the size of running the parsed bundles/modules through Brotli compression.
160
+
154
161
  <h2 align="center">Selecting Which Chunks to Display</h2>
155
162
 
156
163
  When opened, the report displays all of the Webpack chunks for your project. It's possible to filter to a more specific list of chunks by using the sidebar or the chunk context menu.
@@ -176,7 +183,7 @@ It happens when `webpack-bundle-analyzer` analyzes files that don't actually exi
176
183
  Error parsing bundle asset "your_bundle_name.bundle.js": no such file
177
184
  No bundles were parsed. Analyzer will show only original module sizes from stats file.
178
185
  ```
179
- To get more information about it you can read [issue #147](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/147).
186
+ To get more information about it you can read [issue #147](https://github.com/webpack/webpack-bundle-analyzer/issues/147).
180
187
 
181
188
  <h2 align="center">Other tools</h2>
182
189
 
@@ -210,8 +217,8 @@ To get more information about it you can read [issue #147](https://github.com/we
210
217
  [node]: https://img.shields.io/node/v/webpack-bundle-analyzer.svg
211
218
  [node-url]: https://nodejs.org
212
219
 
213
- [tests]: http://img.shields.io/travis/webpack-contrib/webpack-bundle-analyzer.svg
214
- [tests-url]: https://travis-ci.org/webpack-contrib/webpack-bundle-analyzer
220
+ [tests]: https://github.com/webpack/webpack-bundle-analyzer/actions/workflows/main.yml/badge.svg
221
+ [tests-url]: https://github.com/webpack/webpack-bundle-analyzer/actions/workflows/main.yml
215
222
 
216
223
  [downloads]: https://img.shields.io/npm/dt/webpack-bundle-analyzer.svg
217
224
  [downloads-url]: https://npmjs.com/package/webpack-bundle-analyzer
@@ -1,28 +1,22 @@
1
1
  "use strict";
2
2
 
3
3
  const fs = require('fs');
4
-
5
4
  const path = require('path');
6
-
7
5
  const {
8
6
  bold
9
7
  } = require('picocolors');
10
-
11
8
  const Logger = require('./Logger');
12
-
13
9
  const viewer = require('./viewer');
14
-
15
10
  const utils = require('./utils');
16
-
17
11
  const {
18
12
  writeStats
19
13
  } = require('./statsUtils');
20
-
21
14
  class BundleAnalyzerPlugin {
22
15
  constructor(opts = {}) {
23
16
  this.opts = {
24
17
  analyzerMode: 'server',
25
18
  analyzerHost: '127.0.0.1',
19
+ compressionAlgorithm: 'gzip',
26
20
  reportFilename: null,
27
21
  reportTitle: utils.defaultTitle,
28
22
  defaultSizes: 'parsed',
@@ -41,24 +35,19 @@ class BundleAnalyzerPlugin {
41
35
  this.server = null;
42
36
  this.logger = new Logger(this.opts.logLevel);
43
37
  }
44
-
45
38
  apply(compiler) {
46
39
  this.compiler = compiler;
47
-
48
40
  const done = (stats, callback) => {
49
41
  callback = callback || (() => {});
50
-
51
42
  const actions = [];
52
-
53
43
  if (this.opts.generateStatsFile) {
54
44
  actions.push(() => this.generateStatsFile(stats.toJson(this.opts.statsOptions)));
55
- } // Handling deprecated `startAnalyzer` flag
56
-
45
+ }
57
46
 
47
+ // Handling deprecated `startAnalyzer` flag
58
48
  if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
59
49
  this.opts.analyzerMode = 'disabled';
60
50
  }
61
-
62
51
  if (this.opts.analyzerMode === 'server') {
63
52
  actions.push(() => this.startAnalyzerServer(stats.toJson()));
64
53
  } else if (this.opts.analyzerMode === 'static') {
@@ -66,7 +55,6 @@ class BundleAnalyzerPlugin {
66
55
  } else if (this.opts.analyzerMode === 'json') {
67
56
  actions.push(() => this.generateJSONReport(stats.toJson()));
68
57
  }
69
-
70
58
  if (actions.length) {
71
59
  // Making analyzer logs to be after all webpack logs in the console
72
60
  setImmediate(async () => {
@@ -81,20 +69,17 @@ class BundleAnalyzerPlugin {
81
69
  callback();
82
70
  }
83
71
  };
84
-
85
72
  if (compiler.hooks) {
86
73
  compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
87
74
  } else {
88
75
  compiler.plugin('done', done);
89
76
  }
90
77
  }
91
-
92
78
  async generateStatsFile(stats) {
93
79
  const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
94
80
  await fs.promises.mkdir(path.dirname(statsFilepath), {
95
81
  recursive: true
96
82
  });
97
-
98
83
  try {
99
84
  await writeStats(stats, statsFilepath);
100
85
  this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
@@ -102,7 +87,6 @@ class BundleAnalyzerPlugin {
102
87
  this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
103
88
  }
104
89
  }
105
-
106
90
  async startAnalyzerServer(stats) {
107
91
  if (this.server) {
108
92
  (await this.server).updateChartData(stats);
@@ -112,6 +96,7 @@ class BundleAnalyzerPlugin {
112
96
  host: this.opts.analyzerHost,
113
97
  port: this.opts.analyzerPort,
114
98
  reportTitle: this.opts.reportTitle,
99
+ compressionAlgorithm: this.opts.compressionAlgorithm,
115
100
  bundleDir: this.getBundleDirFromCompiler(),
116
101
  logger: this.logger,
117
102
  defaultSizes: this.opts.defaultSizes,
@@ -120,47 +105,41 @@ class BundleAnalyzerPlugin {
120
105
  });
121
106
  }
122
107
  }
123
-
124
108
  async generateJSONReport(stats) {
125
109
  await viewer.generateJSONReport(stats, {
126
110
  reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
111
+ compressionAlgorithm: this.opts.compressionAlgorithm,
127
112
  bundleDir: this.getBundleDirFromCompiler(),
128
113
  logger: this.logger,
129
114
  excludeAssets: this.opts.excludeAssets
130
115
  });
131
116
  }
132
-
133
117
  async generateStaticReport(stats) {
134
118
  await viewer.generateReport(stats, {
135
119
  openBrowser: this.opts.openAnalyzer,
136
120
  reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
137
121
  reportTitle: this.opts.reportTitle,
122
+ compressionAlgorithm: this.opts.compressionAlgorithm,
138
123
  bundleDir: this.getBundleDirFromCompiler(),
139
124
  logger: this.logger,
140
125
  defaultSizes: this.opts.defaultSizes,
141
126
  excludeAssets: this.opts.excludeAssets
142
127
  });
143
128
  }
144
-
145
129
  getBundleDirFromCompiler() {
146
130
  if (typeof this.compiler.outputFileSystem.constructor === 'undefined') {
147
131
  return this.compiler.outputPath;
148
132
  }
149
-
150
133
  switch (this.compiler.outputFileSystem.constructor.name) {
151
134
  case 'MemoryFileSystem':
152
135
  return null;
153
136
  // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
154
137
  // Related: #274
155
-
156
138
  case 'AsyncMFS':
157
139
  return null;
158
-
159
140
  default:
160
141
  return this.compiler.outputPath;
161
142
  }
162
143
  }
163
-
164
144
  }
165
-
166
145
  module.exports = BundleAnalyzerPlugin;
package/lib/Logger.js CHANGED
@@ -2,35 +2,28 @@
2
2
 
3
3
  const LEVELS = ['debug', 'info', 'warn', 'error', 'silent'];
4
4
  const LEVEL_TO_CONSOLE_METHOD = new Map([['debug', 'log'], ['info', 'log'], ['warn', 'log']]);
5
-
6
5
  class Logger {
6
+ static levels = LEVELS;
7
+ static defaultLevel = 'info';
7
8
  constructor(level = Logger.defaultLevel) {
8
9
  this.activeLevels = new Set();
9
10
  this.setLogLevel(level);
10
11
  }
11
-
12
12
  setLogLevel(level) {
13
13
  const levelIndex = LEVELS.indexOf(level);
14
14
  if (levelIndex === -1) throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`);
15
15
  this.activeLevels.clear();
16
-
17
16
  for (const [i, level] of LEVELS.entries()) {
18
17
  if (i >= levelIndex) this.activeLevels.add(level);
19
18
  }
20
19
  }
21
-
22
20
  _log(level, ...args) {
23
21
  console[LEVEL_TO_CONSOLE_METHOD.get(level) || level](...args);
24
22
  }
25
-
26
23
  }
27
-
28
- Logger.levels = LEVELS;
29
- Logger.defaultLevel = 'info';
30
24
  ;
31
25
  LEVELS.forEach(level => {
32
26
  if (level === 'silent') return;
33
-
34
27
  Logger.prototype[level] = function (...args) {
35
28
  if (this.activeLevels.has(level)) this._log(level, ...args);
36
29
  };
package/lib/analyzer.js CHANGED
@@ -1,48 +1,43 @@
1
1
  "use strict";
2
2
 
3
3
  const fs = require('fs');
4
-
5
4
  const path = require('path');
6
-
7
- const gzipSize = require('gzip-size');
8
-
9
5
  const {
10
6
  parseChunked
11
7
  } = require('@discoveryjs/json-ext');
12
-
13
8
  const Logger = require('./Logger');
14
-
15
9
  const Folder = require('./tree/Folder').default;
16
-
17
10
  const {
18
11
  parseBundle
19
12
  } = require('./parseUtils');
20
-
21
13
  const {
22
14
  createAssetsFilter
23
15
  } = require('./utils');
24
-
16
+ const {
17
+ getCompressedSize
18
+ } = require('./sizeUtils');
25
19
  const FILENAME_QUERY_REGEXP = /\?.*$/u;
26
- const FILENAME_EXTENSIONS = /\.(js|mjs|cjs)$/iu;
20
+ const FILENAME_EXTENSIONS = /\.(js|mjs|cjs|bundle)$/iu;
27
21
  module.exports = {
28
22
  getViewerData,
29
23
  readStatsFromFile
30
24
  };
31
-
32
25
  function getViewerData(bundleStats, bundleDir, opts) {
33
26
  const {
34
27
  logger = new Logger(),
28
+ compressionAlgorithm,
35
29
  excludeAssets = null
36
30
  } = opts || {};
37
- const isAssetIncluded = createAssetsFilter(excludeAssets); // Sometimes all the information is located in `children` array (e.g. problem in #10)
31
+ const isAssetIncluded = createAssetsFilter(excludeAssets);
38
32
 
33
+ // Sometimes all the information is located in `children` array (e.g. problem in #10)
39
34
  if ((bundleStats.assets == null || bundleStats.assets.length === 0) && bundleStats.children && bundleStats.children.length > 0) {
40
35
  const {
41
36
  children
42
37
  } = bundleStats;
43
- bundleStats = bundleStats.children[0]; // Sometimes if there are additional child chunks produced add them as child assets,
38
+ bundleStats = bundleStats.children[0];
39
+ // Sometimes if there are additional child chunks produced add them as child assets,
44
40
  // leave the 1st one as that is considered the 'root' asset.
45
-
46
41
  for (let i = 1; i < children.length; i++) {
47
42
  children[i].assets.forEach(asset => {
48
43
  asset.isChild = true;
@@ -57,54 +52,53 @@ function getViewerData(bundleStats, bundleDir, opts) {
57
52
  bundleStats.assets.push(asset);
58
53
  });
59
54
  });
60
- } // Picking only `*.js, *.cjs or *.mjs` assets from bundle that has non-empty `chunks` array
61
-
55
+ }
62
56
 
57
+ // Picking only `*.js, *.cjs or *.mjs` assets from bundle that has non-empty `chunks` array
63
58
  bundleStats.assets = bundleStats.assets.filter(asset => {
64
59
  // Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
65
60
  if (asset.type && asset.type !== 'asset') {
66
61
  return false;
67
- } // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
68
- // See #22
69
-
62
+ }
70
63
 
64
+ // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
65
+ // See #22
71
66
  asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
72
67
  return FILENAME_EXTENSIONS.test(asset.name) && asset.chunks.length > 0 && isAssetIncluded(asset.name);
73
- }); // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
68
+ });
74
69
 
70
+ // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
75
71
  let bundlesSources = null;
76
72
  let parsedModules = null;
77
-
78
73
  if (bundleDir) {
79
74
  bundlesSources = {};
80
75
  parsedModules = {};
81
-
82
76
  for (const statAsset of bundleStats.assets) {
83
77
  const assetFile = path.join(bundleDir, statAsset.name);
84
78
  let bundleInfo;
85
-
86
79
  try {
87
- bundleInfo = parseBundle(assetFile);
80
+ bundleInfo = parseBundle(assetFile, {
81
+ sourceType: statAsset.info.javascriptModule ? 'module' : 'script'
82
+ });
88
83
  } catch (err) {
89
84
  const msg = err.code === 'ENOENT' ? 'no such file' : err.message;
90
- logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
85
+ logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`, {
86
+ cause: err
87
+ });
91
88
  continue;
92
89
  }
93
-
94
90
  bundlesSources[statAsset.name] = {
95
91
  src: bundleInfo.src,
96
92
  runtimeSrc: bundleInfo.runtimeSrc
97
93
  };
98
94
  Object.assign(parsedModules, bundleInfo.modules);
99
95
  }
100
-
101
96
  if (Object.keys(bundlesSources).length === 0) {
102
97
  bundlesSources = null;
103
98
  parsedModules = null;
104
99
  logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
105
100
  }
106
101
  }
107
-
108
102
  const assets = bundleStats.assets.reduce((result, statAsset) => {
109
103
  // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
110
104
  const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
@@ -113,29 +107,29 @@ function getViewerData(bundleStats, bundleDir, opts) {
113
107
  size: statAsset.size
114
108
  };
115
109
  const assetSources = bundlesSources && Object.prototype.hasOwnProperty.call(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null;
116
-
117
110
  if (assetSources) {
118
111
  asset.parsedSize = Buffer.byteLength(assetSources.src);
119
- asset.gzipSize = gzipSize.sync(assetSources.src);
120
- } // Picking modules from current bundle script
121
-
112
+ if (compressionAlgorithm === 'gzip') asset.gzipSize = getCompressedSize('gzip', assetSources.src);
113
+ if (compressionAlgorithm === 'brotli') asset.brotliSize = getCompressedSize('brotli', assetSources.src);
114
+ }
122
115
 
123
- let assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule)); // Adding parsed sources
116
+ // Picking modules from current bundle script
117
+ let assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule));
124
118
 
119
+ // Adding parsed sources
125
120
  if (parsedModules) {
126
121
  const unparsedEntryModules = [];
127
-
128
122
  for (const statModule of assetModules) {
129
123
  if (parsedModules[statModule.id]) {
130
124
  statModule.parsedSrc = parsedModules[statModule.id];
131
125
  } else if (isEntryModule(statModule)) {
132
126
  unparsedEntryModules.push(statModule);
133
127
  }
134
- } // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
128
+ }
129
+
130
+ // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
135
131
  // Because of this they basically become a concatenated module, for which we can't even precisely determine its
136
132
  // parsed source as it's located in the same scope as all Webpack runtime helpers.
137
-
138
-
139
133
  if (unparsedEntryModules.length && assetSources) {
140
134
  if (unparsedEntryModules.length === 1) {
141
135
  // So if there is only one entry we consider its parsed source to be all the bundle code excluding code
@@ -154,98 +148,81 @@ function getViewerData(bundleStats, bundleDir, opts) {
154
148
  }
155
149
  }
156
150
  }
157
-
158
151
  asset.modules = assetModules;
159
- asset.tree = createModulesTree(asset.modules);
152
+ asset.tree = createModulesTree(asset.modules, {
153
+ compressionAlgorithm
154
+ });
160
155
  return result;
161
156
  }, {});
162
157
  const chunkToInitialByEntrypoint = getChunkToInitialByEntrypoint(bundleStats);
163
- return Object.entries(assets).map(([filename, asset]) => {
164
- var _chunkToInitialByEntr;
165
-
166
- return {
167
- label: filename,
168
- isAsset: true,
169
- // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
170
- // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
171
- // be the size of minified bundle.
172
- // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
173
- statSize: asset.tree.size || asset.size,
174
- parsedSize: asset.parsedSize,
175
- gzipSize: asset.gzipSize,
176
- groups: Object.values(asset.tree.children).map(i => i.toChartData()),
177
- isInitialByEntrypoint: (_chunkToInitialByEntr = chunkToInitialByEntrypoint[filename]) !== null && _chunkToInitialByEntr !== void 0 ? _chunkToInitialByEntr : {}
178
- };
179
- });
158
+ return Object.entries(assets).map(([filename, asset]) => ({
159
+ label: filename,
160
+ isAsset: true,
161
+ // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
162
+ // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
163
+ // be the size of minified bundle.
164
+ // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
165
+ statSize: asset.tree.size || asset.size,
166
+ parsedSize: asset.parsedSize,
167
+ gzipSize: asset.gzipSize,
168
+ brotliSize: asset.brotliSize,
169
+ groups: Object.values(asset.tree.children).map(i => i.toChartData()),
170
+ isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
171
+ }));
180
172
  }
181
-
182
173
  function readStatsFromFile(filename) {
183
174
  return parseChunked(fs.createReadStream(filename, {
184
175
  encoding: 'utf8'
185
176
  }));
186
177
  }
187
-
188
178
  function getChildAssetBundles(bundleStats, assetName) {
189
179
  return flatten((bundleStats.children || []).find(c => Object.values(c.assetsByChunkName))).includes(assetName);
190
180
  }
191
-
192
181
  function getBundleModules(bundleStats) {
193
- var _bundleStats$chunks;
194
-
195
182
  const seenIds = new Set();
196
- return flatten((((_bundleStats$chunks = bundleStats.chunks) === null || _bundleStats$chunks === void 0 ? void 0 : _bundleStats$chunks.map(chunk => chunk.modules)) || []).concat(bundleStats.modules).filter(Boolean)).filter(mod => {
183
+ return flatten((bundleStats.chunks?.map(chunk => chunk.modules) || []).concat(bundleStats.modules).filter(Boolean)).filter(mod => {
197
184
  // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
198
185
  if (isRuntimeModule(mod)) {
199
186
  return false;
200
187
  }
201
-
202
188
  if (seenIds.has(mod.id)) {
203
189
  return false;
204
190
  }
205
-
206
191
  seenIds.add(mod.id);
207
192
  return true;
208
193
  });
209
194
  }
210
-
211
195
  function assetHasModule(statAsset, statModule) {
212
196
  // Checking if this module is the part of asset chunks
213
197
  return (statModule.chunks || []).some(moduleChunk => statAsset.chunks.includes(moduleChunk));
214
198
  }
215
-
216
199
  function isEntryModule(statModule) {
217
200
  return statModule.depth === 0;
218
201
  }
219
-
220
202
  function isRuntimeModule(statModule) {
221
203
  return statModule.moduleType === 'runtime';
222
204
  }
223
-
224
- function createModulesTree(modules) {
225
- const root = new Folder('.');
205
+ function createModulesTree(modules, opts) {
206
+ const root = new Folder('.', opts);
226
207
  modules.forEach(module => root.addModule(module));
227
208
  root.mergeNestedFolders();
228
209
  return root;
229
210
  }
230
-
231
211
  function getChunkToInitialByEntrypoint(bundleStats) {
232
212
  if (bundleStats == null) {
233
213
  return {};
234
214
  }
235
-
236
215
  const chunkToEntrypointInititalMap = {};
237
216
  Object.values(bundleStats.entrypoints || {}).forEach(entrypoint => {
238
217
  for (const asset of entrypoint.assets) {
239
- var _chunkToEntrypointIni;
240
-
241
- chunkToEntrypointInititalMap[asset.name] = (_chunkToEntrypointIni = chunkToEntrypointInititalMap[asset.name]) !== null && _chunkToEntrypointIni !== void 0 ? _chunkToEntrypointIni : {};
218
+ chunkToEntrypointInititalMap[asset.name] = chunkToEntrypointInititalMap[asset.name] ?? {};
242
219
  chunkToEntrypointInititalMap[asset.name][entrypoint.name] = true;
243
220
  }
244
221
  });
245
222
  return chunkToEntrypointInititalMap;
246
223
  }
247
-
248
224
  ;
225
+
249
226
  /**
250
227
  * arr-flatten <https://github.com/jonschlinkert/arr-flatten>
251
228
  *
@@ -258,23 +235,19 @@ function getChunkToInitialByEntrypoint(bundleStats) {
258
235
  *
259
236
  * TODO: replace with Array.prototype.flat once Node.js 10 support is dropped
260
237
  */
261
-
262
238
  function flatten(arr) {
263
239
  if (!arr) return [];
264
240
  const len = arr.length;
265
241
  if (!len) return [];
266
242
  let cur;
267
243
  const res = [];
268
-
269
244
  for (let i = 0; i < len; i++) {
270
245
  cur = arr[i];
271
-
272
246
  if (Array.isArray(cur)) {
273
247
  res.push(...cur);
274
248
  } else {
275
249
  res.push(cur);
276
250
  }
277
251
  }
278
-
279
252
  return res;
280
253
  }