webpack-bundle-analyzer 5.2.0 → 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,35 +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);
114
- if (compressionAlgorithm === 'zstd') asset.zstdSize = getCompressedSize('zstd', 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
+ }
115
307
  }
116
308
 
117
309
  // Picking modules from current bundle script
310
+ /** @type {StatsModule[]} */
118
311
  let assetModules = (modules || []).filter(statModule => assetHasModule(statAsset, statModule));
119
312
 
120
313
  // Adding parsed sources
121
314
  if (parsedModules) {
315
+ /** @type {StatsModule[]} */
122
316
  const unparsedEntryModules = [];
123
- for (const statModule of assetModules) {
124
- if (parsedModules[statModule.id]) {
125
- statModule.parsedSrc = parsedModules[statModule.id];
126
- } else if (isEntryModule(statModule)) {
127
- 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);
128
322
  }
129
323
  }
130
324
 
@@ -140,10 +334,10 @@ function getViewerData(bundleStats, bundleDir, opts) {
140
334
  // If there are multiple entry points we move all of them under synthetic concatenated module.
141
335
  assetModules = (assetModules || []).filter(mod => !unparsedEntryModules.includes(mod));
142
336
  assetModules.unshift({
143
- identifier: './entry modules',
144
- name: './entry modules',
337
+ identifier: "./entry modules",
338
+ name: "./entry modules",
145
339
  modules: unparsedEntryModules,
146
- size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
340
+ size: unparsedEntryModules.reduce((totalSize, module) => totalSize + (/** @type {number} */module.size), 0),
147
341
  parsedSrc: assetSources.runtimeSrc
148
342
  });
149
343
  }
@@ -154,7 +348,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
154
348
  compressionAlgorithm
155
349
  });
156
350
  return result;
157
- }, {});
351
+ }, /** @type {Record<string, Asset>} */{});
158
352
  const chunkToInitialByEntrypoint = getChunkToInitialByEntrypoint(bundleStats);
159
353
  return Object.entries(assets).map(([filename, asset]) => ({
160
354
  label: filename,
@@ -172,84 +366,17 @@ function getViewerData(bundleStats, bundleDir, opts) {
172
366
  isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
173
367
  }));
174
368
  }
369
+
370
+ /**
371
+ * @param {string} filename filename
372
+ * @returns {Promise<StatsCompilation>} result
373
+ */
175
374
  function readStatsFromFile(filename) {
176
375
  return parseChunked(fs.createReadStream(filename, {
177
- encoding: 'utf8'
376
+ encoding: "utf8"
178
377
  }));
179
378
  }
180
- function getChildAssetBundles(bundleStats, assetName) {
181
- return flatten((bundleStats.children || []).find(c => Object.values(c.assetsByChunkName))).includes(assetName);
182
- }
183
- function getBundleModules(bundleStats) {
184
- const seenIds = new Set();
185
- return flatten((bundleStats.chunks?.map(chunk => chunk.modules) || []).concat(bundleStats.modules).filter(Boolean)).filter(mod => {
186
- // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
187
- if (isRuntimeModule(mod)) {
188
- return false;
189
- }
190
- if (seenIds.has(mod.id)) {
191
- return false;
192
- }
193
- seenIds.add(mod.id);
194
- return true;
195
- });
196
- }
197
- function assetHasModule(statAsset, statModule) {
198
- // Checking if this module is the part of asset chunks
199
- return (statModule.chunks || []).some(moduleChunk => statAsset.chunks.includes(moduleChunk));
200
- }
201
- function isEntryModule(statModule) {
202
- return statModule.depth === 0;
203
- }
204
- function isRuntimeModule(statModule) {
205
- return statModule.moduleType === 'runtime';
206
- }
207
- function createModulesTree(modules, opts) {
208
- const root = new Folder('.', opts);
209
- modules.forEach(module => root.addModule(module));
210
- root.mergeNestedFolders();
211
- return root;
212
- }
213
- function getChunkToInitialByEntrypoint(bundleStats) {
214
- if (bundleStats == null) {
215
- return {};
216
- }
217
- const chunkToEntrypointInititalMap = {};
218
- Object.values(bundleStats.entrypoints || {}).forEach(entrypoint => {
219
- for (const asset of entrypoint.assets) {
220
- chunkToEntrypointInititalMap[asset.name] = chunkToEntrypointInititalMap[asset.name] ?? {};
221
- chunkToEntrypointInititalMap[asset.name][entrypoint.name] = true;
222
- }
223
- });
224
- return chunkToEntrypointInititalMap;
225
- }
226
- ;
227
-
228
- /**
229
- * arr-flatten <https://github.com/jonschlinkert/arr-flatten>
230
- *
231
- * Copyright (c) 2014-2017, Jon Schlinkert.
232
- * Released under the MIT License.
233
- *
234
- * Modified by Sukka <https://skk.moe>
235
- *
236
- * Replace recursively flatten with one-level deep flatten to match lodash.flatten
237
- *
238
- * TODO: replace with Array.prototype.flat once Node.js 10 support is dropped
239
- */
240
- function flatten(arr) {
241
- if (!arr) return [];
242
- const len = arr.length;
243
- if (!len) return [];
244
- let cur;
245
- const res = [];
246
- for (let i = 0; i < len; i++) {
247
- cur = arr[i];
248
- if (Array.isArray(cur)) {
249
- res.push(...cur);
250
- } else {
251
- res.push(cur);
252
- }
253
- }
254
- return res;
255
- }
379
+ module.exports = {
380
+ getViewerData,
381
+ readStatsFromFile
382
+ };
@@ -2,33 +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');
13
+ } = require("picocolors");
14
+ const Logger = require("../Logger");
15
+ const analyzer = require("../analyzer");
16
16
  const {
17
17
  isZstdSupported
18
- } = require('../sizeUtils');
19
- const SIZES = new Set(['stat', 'parsed', 'gzip']);
20
- const COMPRESSION_ALGORITHMS = new Set(isZstdSupported ? ['gzip', 'brotli', 'zstd'] : ['gzip', 'brotli']);
21
- const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
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"]);
22
23
 
23
- 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
+ }
24
31
 
25
- bundleStatsFile Path to Webpack Stats JSON file.
26
- bundleDir Directory containing all generated bundles.
27
- You should provided it if you want analyzer to show you the real parsed module sizes.
28
- 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(
29
45
  // Had to make `host` parameter optional in order to let `-h` flag output help message
30
46
  // Fixes https://github.com/webpack/webpack-bundle-analyzer/issues/239
31
- '-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();
32
48
  let [bundleStatsFile, bundleDir] = program.args;
33
49
  let {
34
50
  mode,
@@ -43,29 +59,48 @@ let {
43
59
  exclude: excludeAssets
44
60
  } = program.opts();
45
61
  const logger = new Logger(logLevel);
46
- if (typeof reportTitle === 'undefined') {
62
+ if (typeof reportTitle === "undefined") {
47
63
  reportTitle = utils.defaultTitle;
48
64
  }
49
- if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
50
- if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
51
- 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");
52
76
  }
53
- if (mode === 'server') {
54
- if (!host) showHelp('Invalid host name');
55
- port = port === 'auto' ? 0 : Number(port);
56
- 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
+ }
57
86
  }
58
87
  if (!COMPRESSION_ALGORITHMS.has(compressionAlgorithm)) {
59
- 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(", ")}`);
60
92
  }
61
- if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(', ')}`);
62
93
  bundleStatsFile = resolve(bundleStatsFile);
63
94
  if (!bundleDir) bundleDir = dirname(bundleStatsFile);
64
- parseAndAnalyse(bundleStatsFile);
95
+
96
+ /**
97
+ * @param {string} bundleStatsFile bundle stats file
98
+ * @returns {Promise<void>}
99
+ */
65
100
  async function parseAndAnalyse(bundleStatsFile) {
66
101
  try {
67
102
  const bundleStats = await analyzer.readStatsFromFile(bundleStatsFile);
68
- if (mode === 'server') {
103
+ if (mode === "server") {
69
104
  viewer.startServer(bundleStats, {
70
105
  openBrowser,
71
106
  port,
@@ -78,10 +113,10 @@ async function parseAndAnalyse(bundleStatsFile) {
78
113
  logger: new Logger(logLevel),
79
114
  analyzerUrl: utils.defaultAnalyzerUrl
80
115
  });
81
- } else if (mode === 'static') {
116
+ } else if (mode === "static") {
82
117
  viewer.generateReport(bundleStats, {
83
118
  openBrowser,
84
- reportFilename: resolve(reportFilename || 'report.html'),
119
+ reportFilename: resolve(reportFilename || "report.html"),
85
120
  reportTitle,
86
121
  defaultSizes,
87
122
  compressionAlgorithm,
@@ -89,9 +124,9 @@ async function parseAndAnalyse(bundleStatsFile) {
89
124
  excludeAssets,
90
125
  logger: new Logger(logLevel)
91
126
  });
92
- } else if (mode === 'json') {
127
+ } else if (mode === "json") {
93
128
  viewer.generateJSONReport(bundleStats, {
94
- reportFilename: resolve(reportFilename || 'report.json'),
129
+ reportFilename: resolve(reportFilename || "report.json"),
95
130
  compressionAlgorithm,
96
131
  bundleDir,
97
132
  excludeAssets,
@@ -100,22 +135,8 @@ async function parseAndAnalyse(bundleStatsFile) {
100
135
  }
101
136
  } catch (err) {
102
137
  logger.error(`Couldn't read webpack bundle stats from "${bundleStatsFile}":\n${err}`);
103
- logger.debug(err.stack);
138
+ logger.debug(/** @type {Error} */err.stack);
104
139
  process.exit(1);
105
140
  }
106
141
  }
107
- function showHelp(error) {
108
- if (error) console.log(`\n ${magenta(error)}\n`);
109
- program.outputHelp();
110
- process.exit(1);
111
- }
112
- function br(str) {
113
- return `\n${' '.repeat(32)}${str}`;
114
- }
115
- function array() {
116
- const arr = [];
117
- return val => {
118
- arr.push(val);
119
- return arr;
120
- };
121
- }
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
  };