webpack-bundle-analyzer 5.1.1 → 5.3.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/lib/analyzer.js CHANGED
@@ -1,74 +1,254 @@
1
1
  "use strict";
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
5
  const {
6
6
  parseChunked
7
- } = require('@discoveryjs/json-ext');
8
- const Logger = require('./Logger');
9
- const Folder = require('./tree/Folder').default;
7
+ } = require("@discoveryjs/json-ext");
8
+ const Logger = require("./Logger");
10
9
  const {
11
10
  parseBundle
12
- } = require('./parseUtils');
13
- const {
14
- createAssetsFilter
15
- } = require('./utils');
11
+ } = require("./parseUtils");
16
12
  const {
17
13
  getCompressedSize
18
- } = require('./sizeUtils');
14
+ } = require("./sizeUtils");
15
+ const Folder = require("./tree/Folder").default;
16
+ const {
17
+ createAssetsFilter
18
+ } = require("./utils");
19
19
  const FILENAME_QUERY_REGEXP = /\?.*$/u;
20
20
  const FILENAME_EXTENSIONS = /\.(js|mjs|cjs|bundle)$/iu;
21
- module.exports = {
22
- getViewerData,
23
- readStatsFromFile
24
- };
21
+
22
+ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */
23
+ /** @typedef {import("webpack").StatsModule} StatsModule */
24
+ /** @typedef {import("webpack").StatsAsset} StatsAsset */
25
+ /** @typedef {import("./BundleAnalyzerPlugin").CompressionAlgorithm} CompressionAlgorithm */
26
+ /** @typedef {import("./BundleAnalyzerPlugin").ExcludeAssets} ExcludeAssets */
27
+
28
+ /**
29
+ * @typedef {object} AnalyzerOptions
30
+ * @property {"gzip" | "brotli" | "zstd"} compressionAlgorithm compression algorithm
31
+ */
32
+
33
+ /**
34
+ * @param {StatsModule[]} modules modules
35
+ * @param {AnalyzerOptions} options options
36
+ * @returns {Folder} a folder class
37
+ */
38
+ function createModulesTree(modules, options) {
39
+ const root = new Folder(".", options);
40
+ for (const module of modules) {
41
+ root.addModule(module);
42
+ }
43
+ root.mergeNestedFolders();
44
+ return root;
45
+ }
46
+
47
+ /**
48
+ * arr-flatten <https://github.com/jonschlinkert/arr-flatten>
49
+ *
50
+ * Copyright (c) 2014-2017, Jon Schlinkert.
51
+ * Released under the MIT License.
52
+ *
53
+ * Modified by Sukka <https://skk.moe>
54
+ *
55
+ * Replace recursively flatten with one-level deep flatten to match lodash.flatten
56
+ *
57
+ * TODO: replace with Array.prototype.flat once Node.js 10 support is dropped
58
+ */
59
+ /**
60
+ * Flattens an array by one level.
61
+ * @template T
62
+ * @param {(T | T[])[]} arr the array to flatten
63
+ * @returns {T[]} a new array containing the flattened elements
64
+ */
65
+ function flatten(arr) {
66
+ if (!arr) return [];
67
+ const len = arr.length;
68
+ if (!len) return [];
69
+ let cur;
70
+ const res = [];
71
+ for (let i = 0; i < len; i++) {
72
+ cur = arr[i];
73
+ if (Array.isArray(cur)) {
74
+ res.push(...cur);
75
+ } else {
76
+ res.push(cur);
77
+ }
78
+ }
79
+ return res;
80
+ }
81
+
82
+ /**
83
+ * @param {StatsCompilation} bundleStats bundle stats
84
+ * @param {string} assetName asset name
85
+ * @returns {boolean} child asset bundlers
86
+ */
87
+ function getChildAssetBundles(bundleStats, assetName) {
88
+ return flatten((bundleStats.children || (/** @type {StatsCompilation} */[])).find(
89
+ /**
90
+ * @param {StatsCompilation} child child stats
91
+ * @returns {string[][]} assets by chunk name
92
+ */
93
+ child => Object.values(child.assetsByChunkName || []))).includes(assetName);
94
+ }
95
+
96
+ /**
97
+ * @param {StatsAsset} statsAsset stats asset
98
+ * @param {StatsModule} statsModule stats modules
99
+ * @returns {boolean} true when asset has a module
100
+ */
101
+ function assetHasModule(statsAsset, statsModule) {
102
+ // Checking if this module is the part of asset chunks
103
+ return (statsModule.chunks || []).some(moduleChunk => statsAsset.chunks && statsAsset.chunks.includes(moduleChunk));
104
+ }
105
+
106
+ /**
107
+ * @param {StatsModule} statsModule stats Module
108
+ * @returns {boolean} true when runtime modules, otherwise false
109
+ */
110
+ function isRuntimeModule(statsModule) {
111
+ return statsModule.moduleType === "runtime";
112
+ }
113
+
114
+ /**
115
+ * @param {StatsCompilation} bundleStats bundle stats
116
+ * @returns {StatsModule[]} modules
117
+ */
118
+ function getBundleModules(bundleStats) {
119
+ /** @type {Set<string | number>} */
120
+ const seenIds = new Set();
121
+ const modules = /** @type {StatsModule[]} */[...(bundleStats.chunks?.map(chunk => chunk.modules) || []), ...(bundleStats.modules || [])].filter(Boolean);
122
+ return flatten(modules).filter(mod => {
123
+ // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
124
+ if (isRuntimeModule(mod)) {
125
+ return false;
126
+ }
127
+ if (seenIds.has(mod.id)) {
128
+ return false;
129
+ }
130
+ seenIds.add(mod.id);
131
+ return true;
132
+ });
133
+ }
134
+
135
+ /** @typedef {Record<string, Record<string, boolean>>} ChunkToInitialByEntrypoint */
136
+
137
+ /**
138
+ * @param {StatsCompilation} bundleStats bundle stats
139
+ * @returns {ChunkToInitialByEntrypoint} chunk to initial by entrypoint
140
+ */
141
+ function getChunkToInitialByEntrypoint(bundleStats) {
142
+ if (bundleStats === null || bundleStats === undefined) {
143
+ return {};
144
+ }
145
+ /** @type {ChunkToInitialByEntrypoint} */
146
+ const chunkToEntrypointInititalMap = {};
147
+ for (const entrypoint of Object.values(bundleStats.entrypoints || {})) {
148
+ for (const asset of entrypoint.assets || []) {
149
+ chunkToEntrypointInititalMap[asset.name] ??= {};
150
+ chunkToEntrypointInititalMap[asset.name][(/** @type {string} */
151
+ entrypoint.name)] = true;
152
+ }
153
+ }
154
+ return chunkToEntrypointInititalMap;
155
+ }
156
+
157
+ /**
158
+ * @param {StatsModule} statsModule stats modules
159
+ * @returns {boolean} true when entry module, otherwise false
160
+ */
161
+ function isEntryModule(statsModule) {
162
+ return statsModule.depth === 0;
163
+ }
164
+
165
+ /**
166
+ * @typedef {object} ViewerDataOptions
167
+ * @property {Logger} logger logger
168
+ * @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
169
+ * @property {ExcludeAssets} excludeAssets exclude assets
170
+ */
171
+
172
+ /** @typedef {import("./tree/Module").ModuleChartData} ModuleChartData */
173
+ /** @typedef {import("./tree/ContentModule").ContentModuleChartData} ContentModuleChartData */
174
+ /** @typedef {import("./tree/ConcatenatedModule").ConcatenatedModuleChartData} ConcatenatedModuleChartData */
175
+ /** @typedef {import("./tree/ContentFolder").ContentFolderChartData} ContentFolderChartData */
176
+ /** @typedef {import("./tree/Folder").FolderChartData} FolderChartData */
177
+
178
+ /**
179
+ * @typedef {object} ChartDataItem
180
+ * @property {string} label label
181
+ * @property {true} isAsset true when is asset, otherwise false
182
+ * @property {number} statSize stat size
183
+ * @property {number | undefined} parsedSize stat size
184
+ * @property {number | undefined} gzipSize gzip size
185
+ * @property {number | undefined} brotliSize brotli size
186
+ * @property {number | undefined} zstdSize zstd size
187
+ * @property {(ModuleChartData | ContentModuleChartData | ConcatenatedModuleChartData | ContentFolderChartData | FolderChartData)[]} groups groups
188
+ * @property {Record<string, boolean>} isInitialByEntrypoint record with initial entrypoints
189
+ */
190
+
191
+ /**
192
+ * @typedef {ChartDataItem[]} ChartData
193
+ */
194
+
195
+ /**
196
+ * @param {StatsCompilation} bundleStats bundle stats
197
+ * @param {string | null} bundleDir bundle dir
198
+ * @param {ViewerDataOptions=} opts options
199
+ * @returns {ChartData} chart data
200
+ */
25
201
  function getViewerData(bundleStats, bundleDir, opts) {
26
202
  const {
27
203
  logger = new Logger(),
28
- compressionAlgorithm,
204
+ compressionAlgorithm = "gzip",
29
205
  excludeAssets = null
30
206
  } = opts || {};
31
207
  const isAssetIncluded = createAssetsFilter(excludeAssets);
32
208
 
33
209
  // Sometimes all the information is located in `children` array (e.g. problem in #10)
34
- if ((bundleStats.assets == null || bundleStats.assets.length === 0) && bundleStats.children && bundleStats.children.length > 0) {
210
+ if ((bundleStats.assets === null || bundleStats.assets === undefined || bundleStats.assets.length === 0) && bundleStats.children && bundleStats.children.length > 0) {
35
211
  const {
36
212
  children
37
213
  } = bundleStats;
38
- bundleStats = bundleStats.children[0];
214
+ [bundleStats] = bundleStats.children;
39
215
  // Sometimes if there are additional child chunks produced add them as child assets,
40
216
  // leave the 1st one as that is considered the 'root' asset.
41
217
  for (let i = 1; i < children.length; i++) {
42
- children[i].assets.forEach(asset => {
218
+ for (const asset of children[i].assets || []) {
43
219
  asset.isChild = true;
220
+ /** @type {StatsAsset[]} */
44
221
  bundleStats.assets.push(asset);
45
- });
222
+ }
46
223
  }
47
224
  } else if (bundleStats.children && bundleStats.children.length > 0) {
48
225
  // Sometimes if there are additional child chunks produced add them as child assets
49
- bundleStats.children.forEach(child => {
50
- child.assets.forEach(asset => {
226
+ for (const child of bundleStats.children) {
227
+ for (const asset of child.assets || []) {
51
228
  asset.isChild = true;
229
+ /** @type {StatsAsset[]} */
52
230
  bundleStats.assets.push(asset);
53
- });
54
- });
231
+ }
232
+ }
55
233
  }
56
234
 
57
235
  // Picking only `*.js, *.cjs or *.mjs` assets from bundle that has non-empty `chunks` array
58
236
  bundleStats.assets = (bundleStats.assets || []).filter(asset => {
59
237
  // Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
60
- if (asset.type && asset.type !== 'asset') {
238
+ if (asset.type && asset.type !== "asset") {
61
239
  return false;
62
240
  }
63
241
 
64
242
  // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
65
243
  // See #22
66
- asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
67
- return FILENAME_EXTENSIONS.test(asset.name) && asset.chunks.length > 0 && isAssetIncluded(asset.name);
244
+ asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, "");
245
+ return FILENAME_EXTENSIONS.test(asset.name) && asset.chunks && asset.chunks.length > 0 && isAssetIncluded(asset.name);
68
246
  });
69
247
 
70
248
  // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
249
+ /** @type {Record<string, { src: string, runtimeSrc: string }> | null} */
71
250
  let bundlesSources = null;
251
+ /** @type {Record<string | number, boolean> | null} */
72
252
  let parsedModules = null;
73
253
  if (bundleDir) {
74
254
  bundlesSources = {};
@@ -78,10 +258,10 @@ function getViewerData(bundleStats, bundleDir, opts) {
78
258
  let bundleInfo;
79
259
  try {
80
260
  bundleInfo = parseBundle(assetFile, {
81
- sourceType: statAsset.info.javascriptModule ? 'module' : 'script'
261
+ sourceType: statAsset.info.javascriptModule ? "module" : "script"
82
262
  });
83
263
  } catch (err) {
84
- const msg = err.code === 'ENOENT' ? 'no such file' : err.message;
264
+ const msg = /** @type {NodeJS.ErrnoException} */err.code === "ENOENT" ? "no such file" : /** @type {Error} */err.message;
85
265
  logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`, {
86
266
  cause: err
87
267
  });
@@ -96,34 +276,49 @@ function getViewerData(bundleStats, bundleDir, opts) {
96
276
  if (Object.keys(bundlesSources).length === 0) {
97
277
  bundlesSources = null;
98
278
  parsedModules = null;
99
- logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
279
+ logger.warn("\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n");
100
280
  }
101
281
  }
282
+
283
+ /** @typedef {{ size: number, parsedSize?: number, gzipSize?: number, brotliSize?: number, zstdSize?: number, modules: StatsModule[], tree: Folder }} Asset */
284
+
102
285
  const assets = bundleStats.assets.reduce((result, statAsset) => {
103
286
  // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
104
287
  const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
105
- const modules = assetBundles ? getBundleModules(assetBundles) : [];
106
- const asset = result[statAsset.name] = {
288
+ /** @type {StatsModule[]} */
289
+ const modules = assetBundles ?
290
+ // @ts-expect-error TODO looks like we have a bug with child compilation parsing, need to add test cases
291
+ getBundleModules(assetBundles) : [];
292
+ const asset = result[statAsset.name] = /** @type {Asset} */{
107
293
  size: statAsset.size
108
294
  };
109
- const assetSources = bundlesSources && Object.prototype.hasOwnProperty.call(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null;
295
+ const assetSources = bundlesSources && Object.hasOwn(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null;
110
296
  if (assetSources) {
111
297
  asset.parsedSize = Buffer.byteLength(assetSources.src);
112
- if (compressionAlgorithm === 'gzip') asset.gzipSize = getCompressedSize('gzip', assetSources.src);
113
- if (compressionAlgorithm === 'brotli') asset.brotliSize = getCompressedSize('brotli', assetSources.src);
298
+ if (compressionAlgorithm === "gzip") {
299
+ asset.gzipSize = getCompressedSize("gzip", assetSources.src);
300
+ }
301
+ if (compressionAlgorithm === "brotli") {
302
+ asset.brotliSize = getCompressedSize("brotli", assetSources.src);
303
+ }
304
+ if (compressionAlgorithm === "zstd") {
305
+ asset.zstdSize = getCompressedSize("zstd", assetSources.src);
306
+ }
114
307
  }
115
308
 
116
309
  // Picking modules from current bundle script
310
+ /** @type {StatsModule[]} */
117
311
  let assetModules = (modules || []).filter(statModule => assetHasModule(statAsset, statModule));
118
312
 
119
313
  // Adding parsed sources
120
314
  if (parsedModules) {
315
+ /** @type {StatsModule[]} */
121
316
  const unparsedEntryModules = [];
122
- for (const statModule of assetModules) {
123
- if (parsedModules[statModule.id]) {
124
- statModule.parsedSrc = parsedModules[statModule.id];
125
- } else if (isEntryModule(statModule)) {
126
- unparsedEntryModules.push(statModule);
317
+ for (const statsModule of assetModules) {
318
+ if (typeof statsModule.id !== "undefined" && parsedModules[statsModule.id]) {
319
+ statsModule.parsedSrc = parsedModules[statsModule.id];
320
+ } else if (isEntryModule(statsModule)) {
321
+ unparsedEntryModules.push(statsModule);
127
322
  }
128
323
  }
129
324
 
@@ -139,10 +334,10 @@ function getViewerData(bundleStats, bundleDir, opts) {
139
334
  // If there are multiple entry points we move all of them under synthetic concatenated module.
140
335
  assetModules = (assetModules || []).filter(mod => !unparsedEntryModules.includes(mod));
141
336
  assetModules.unshift({
142
- identifier: './entry modules',
143
- name: './entry modules',
337
+ identifier: "./entry modules",
338
+ name: "./entry modules",
144
339
  modules: unparsedEntryModules,
145
- size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
340
+ size: unparsedEntryModules.reduce((totalSize, module) => totalSize + (/** @type {number} */module.size), 0),
146
341
  parsedSrc: assetSources.runtimeSrc
147
342
  });
148
343
  }
@@ -153,7 +348,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
153
348
  compressionAlgorithm
154
349
  });
155
350
  return result;
156
- }, {});
351
+ }, /** @type {Record<string, Asset>} */{});
157
352
  const chunkToInitialByEntrypoint = getChunkToInitialByEntrypoint(bundleStats);
158
353
  return Object.entries(assets).map(([filename, asset]) => ({
159
354
  label: filename,
@@ -166,88 +361,22 @@ function getViewerData(bundleStats, bundleDir, opts) {
166
361
  parsedSize: asset.parsedSize,
167
362
  gzipSize: asset.gzipSize,
168
363
  brotliSize: asset.brotliSize,
364
+ zstdSize: asset.zstdSize,
169
365
  groups: Object.values(asset.tree.children).map(i => i.toChartData()),
170
366
  isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
171
367
  }));
172
368
  }
369
+
370
+ /**
371
+ * @param {string} filename filename
372
+ * @returns {Promise<StatsCompilation>} result
373
+ */
173
374
  function readStatsFromFile(filename) {
174
375
  return parseChunked(fs.createReadStream(filename, {
175
- encoding: 'utf8'
376
+ encoding: "utf8"
176
377
  }));
177
378
  }
178
- function getChildAssetBundles(bundleStats, assetName) {
179
- return flatten((bundleStats.children || []).find(c => Object.values(c.assetsByChunkName))).includes(assetName);
180
- }
181
- function getBundleModules(bundleStats) {
182
- const seenIds = new Set();
183
- return flatten((bundleStats.chunks?.map(chunk => chunk.modules) || []).concat(bundleStats.modules).filter(Boolean)).filter(mod => {
184
- // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
185
- if (isRuntimeModule(mod)) {
186
- return false;
187
- }
188
- if (seenIds.has(mod.id)) {
189
- return false;
190
- }
191
- seenIds.add(mod.id);
192
- return true;
193
- });
194
- }
195
- function assetHasModule(statAsset, statModule) {
196
- // Checking if this module is the part of asset chunks
197
- return (statModule.chunks || []).some(moduleChunk => statAsset.chunks.includes(moduleChunk));
198
- }
199
- function isEntryModule(statModule) {
200
- return statModule.depth === 0;
201
- }
202
- function isRuntimeModule(statModule) {
203
- return statModule.moduleType === 'runtime';
204
- }
205
- function createModulesTree(modules, opts) {
206
- const root = new Folder('.', opts);
207
- modules.forEach(module => root.addModule(module));
208
- root.mergeNestedFolders();
209
- return root;
210
- }
211
- function getChunkToInitialByEntrypoint(bundleStats) {
212
- if (bundleStats == null) {
213
- return {};
214
- }
215
- const chunkToEntrypointInititalMap = {};
216
- Object.values(bundleStats.entrypoints || {}).forEach(entrypoint => {
217
- for (const asset of entrypoint.assets) {
218
- chunkToEntrypointInititalMap[asset.name] = chunkToEntrypointInititalMap[asset.name] ?? {};
219
- chunkToEntrypointInititalMap[asset.name][entrypoint.name] = true;
220
- }
221
- });
222
- return chunkToEntrypointInititalMap;
223
- }
224
- ;
225
-
226
- /**
227
- * arr-flatten <https://github.com/jonschlinkert/arr-flatten>
228
- *
229
- * Copyright (c) 2014-2017, Jon Schlinkert.
230
- * Released under the MIT License.
231
- *
232
- * Modified by Sukka <https://skk.moe>
233
- *
234
- * Replace recursively flatten with one-level deep flatten to match lodash.flatten
235
- *
236
- * TODO: replace with Array.prototype.flat once Node.js 10 support is dropped
237
- */
238
- function flatten(arr) {
239
- if (!arr) return [];
240
- const len = arr.length;
241
- if (!len) return [];
242
- let cur;
243
- const res = [];
244
- for (let i = 0; i < len; i++) {
245
- cur = arr[i];
246
- if (Array.isArray(cur)) {
247
- res.push(...cur);
248
- } else {
249
- res.push(cur);
250
- }
251
- }
252
- return res;
253
- }
379
+ module.exports = {
380
+ getViewerData,
381
+ readStatsFromFile
382
+ };
@@ -2,30 +2,49 @@
2
2
  "use strict";
3
3
 
4
4
  const {
5
- resolve,
6
- dirname
7
- } = require('path');
8
- const commander = require('commander');
5
+ dirname,
6
+ resolve
7
+ } = require("node:path");
8
+ const {
9
+ program: commanderProgram
10
+ } = require("commander");
9
11
  const {
10
12
  magenta
11
- } = require('picocolors');
12
- const analyzer = require('../analyzer');
13
- const viewer = require('../viewer');
14
- const Logger = require('../Logger');
15
- const utils = require('../utils');
16
- const SIZES = new Set(['stat', 'parsed', 'gzip']);
17
- const COMPRESSION_ALGORITHMS = new Set(['gzip', 'brotli']);
18
- const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
13
+ } = require("picocolors");
14
+ const Logger = require("../Logger");
15
+ const analyzer = require("../analyzer");
16
+ const {
17
+ isZstdSupported
18
+ } = require("../sizeUtils");
19
+ const utils = require("../utils");
20
+ const viewer = require("../viewer");
21
+ const SIZES = new Set(["stat", "parsed", "gzip"]);
22
+ const COMPRESSION_ALGORITHMS = new Set(isZstdSupported ? ["gzip", "brotli", "zstd"] : ["gzip", "brotli"]);
19
23
 
20
- Arguments:
24
+ /**
25
+ * @param {string} str string
26
+ * @returns {string} break with string
27
+ */
28
+ function br(str) {
29
+ return `\n${" ".repeat(32)}${str}`;
30
+ }
21
31
 
22
- bundleStatsFile Path to Webpack Stats JSON file.
23
- bundleDir Directory containing all generated bundles.
24
- You should provided it if you want analyzer to show you the real parsed module sizes.
25
- 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(
32
+ /**
33
+ * @template T
34
+ * @returns {(val: T) => T[]} array
35
+ */
36
+ function array() {
37
+ /** @type {T[]} */
38
+ const arr = [];
39
+ return val => {
40
+ arr.push(val);
41
+ return arr;
42
+ };
43
+ }
44
+ const program = commanderProgram.version(require("../../package.json").version).argument("<bundleStatsFile>", "Path to Webpack Stats JSON file.").argument("[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(
26
45
  // Had to make `host` parameter optional in order to let `-h` flag output help message
27
46
  // Fixes https://github.com/webpack/webpack-bundle-analyzer/issues/239
28
- '-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('--compression-algorithm <type>', 'Compression algorithm that will be used to calculate the compressed module sizes.' + br(`Possible values: ${[...COMPRESSION_ALGORITHMS].join(', ')}`), 'gzip').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);
47
+ "-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("--compression-algorithm <type>", `Compression algorithm that will be used to calculate the compressed module sizes.${br(`Possible values: ${[...COMPRESSION_ALGORITHMS].join(", ")}`)}`, "gzip").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();
29
48
  let [bundleStatsFile, bundleDir] = program.args;
30
49
  let {
31
50
  mode,
@@ -40,29 +59,48 @@ let {
40
59
  exclude: excludeAssets
41
60
  } = program.opts();
42
61
  const logger = new Logger(logLevel);
43
- if (typeof reportTitle === 'undefined') {
62
+ if (typeof reportTitle === "undefined") {
44
63
  reportTitle = utils.defaultTitle;
45
64
  }
46
- if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
47
- if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
48
- showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
65
+
66
+ /**
67
+ * @param {string} error error message
68
+ */
69
+ function showHelp(error) {
70
+ if (error) console.log(`\n ${magenta(error)}\n`);
71
+ program.outputHelp();
72
+ process.exit(1);
73
+ }
74
+ if (!bundleStatsFile) {
75
+ showHelp("Provide path to Webpack Stats file as first argument");
49
76
  }
50
- if (mode === 'server') {
51
- if (!host) showHelp('Invalid host name');
52
- port = port === 'auto' ? 0 : Number(port);
53
- if (isNaN(port)) showHelp('Invalid port. Should be a number or `auto`');
77
+ if (mode !== "server" && mode !== "static" && mode !== "json") {
78
+ showHelp("Invalid mode. Should be either `server`, `static` or `json`.");
79
+ }
80
+ if (mode === "server") {
81
+ if (!host) showHelp("Invalid host name");
82
+ port = port === "auto" ? 0 : Number(port);
83
+ if (Number.isNaN(port)) {
84
+ showHelp("Invalid port. Should be a number or `auto`");
85
+ }
54
86
  }
55
87
  if (!COMPRESSION_ALGORITHMS.has(compressionAlgorithm)) {
56
- showHelp(`Invalid compression algorithm option. Possible values are: ${[...COMPRESSION_ALGORITHMS].join(', ')}`);
88
+ showHelp(`Invalid compression algorithm option. Possible values are: ${[...COMPRESSION_ALGORITHMS].join(", ")}`);
89
+ }
90
+ if (!SIZES.has(defaultSizes)) {
91
+ showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(", ")}`);
57
92
  }
58
- if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(', ')}`);
59
93
  bundleStatsFile = resolve(bundleStatsFile);
60
94
  if (!bundleDir) bundleDir = dirname(bundleStatsFile);
61
- parseAndAnalyse(bundleStatsFile);
95
+
96
+ /**
97
+ * @param {string} bundleStatsFile bundle stats file
98
+ * @returns {Promise<void>}
99
+ */
62
100
  async function parseAndAnalyse(bundleStatsFile) {
63
101
  try {
64
102
  const bundleStats = await analyzer.readStatsFromFile(bundleStatsFile);
65
- if (mode === 'server') {
103
+ if (mode === "server") {
66
104
  viewer.startServer(bundleStats, {
67
105
  openBrowser,
68
106
  port,
@@ -75,10 +113,10 @@ async function parseAndAnalyse(bundleStatsFile) {
75
113
  logger: new Logger(logLevel),
76
114
  analyzerUrl: utils.defaultAnalyzerUrl
77
115
  });
78
- } else if (mode === 'static') {
116
+ } else if (mode === "static") {
79
117
  viewer.generateReport(bundleStats, {
80
118
  openBrowser,
81
- reportFilename: resolve(reportFilename || 'report.html'),
119
+ reportFilename: resolve(reportFilename || "report.html"),
82
120
  reportTitle,
83
121
  defaultSizes,
84
122
  compressionAlgorithm,
@@ -86,9 +124,9 @@ async function parseAndAnalyse(bundleStatsFile) {
86
124
  excludeAssets,
87
125
  logger: new Logger(logLevel)
88
126
  });
89
- } else if (mode === 'json') {
127
+ } else if (mode === "json") {
90
128
  viewer.generateJSONReport(bundleStats, {
91
- reportFilename: resolve(reportFilename || 'report.json'),
129
+ reportFilename: resolve(reportFilename || "report.json"),
92
130
  compressionAlgorithm,
93
131
  bundleDir,
94
132
  excludeAssets,
@@ -97,22 +135,8 @@ async function parseAndAnalyse(bundleStatsFile) {
97
135
  }
98
136
  } catch (err) {
99
137
  logger.error(`Couldn't read webpack bundle stats from "${bundleStatsFile}":\n${err}`);
100
- logger.debug(err.stack);
138
+ logger.debug(/** @type {Error} */err.stack);
101
139
  process.exit(1);
102
140
  }
103
141
  }
104
- function showHelp(error) {
105
- if (error) console.log(`\n ${magenta(error)}\n`);
106
- program.outputHelp();
107
- process.exit(1);
108
- }
109
- function br(str) {
110
- return `\n${' '.repeat(32)}${str}`;
111
- }
112
- function array() {
113
- const arr = [];
114
- return val => {
115
- arr.push(val);
116
- return arr;
117
- };
118
- }
142
+ parseAndAnalyse(bundleStatsFile);
package/lib/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  const {
4
4
  start
5
- } = require('./viewer');
5
+ } = require("./viewer");
6
6
  module.exports = {
7
7
  start,
8
- BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
8
+ BundleAnalyzerPlugin: require("./BundleAnalyzerPlugin")
9
9
  };