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/parseUtils.js CHANGED
@@ -1,42 +1,281 @@
1
1
  "use strict";
2
2
 
3
- const fs = require('fs');
4
- const acorn = require('acorn');
5
- const walk = require('acorn-walk');
6
- module.exports = {
7
- parseBundle
8
- };
9
- function parseBundle(bundlePath, opts) {
3
+ /** @typedef {import("acorn").Node} Node */
4
+ /** @typedef {import("acorn").CallExpression} CallExpression */
5
+ /** @typedef {import("acorn").ExpressionStatement} ExpressionStatement */
6
+ /** @typedef {import("acorn").Expression} Expression */
7
+ /** @typedef {import("acorn").SpreadElement} SpreadElement */
8
+
9
+ const fs = require("node:fs");
10
+ const acorn = require("acorn");
11
+ const walk = require("acorn-walk");
12
+
13
+ /**
14
+ * @param {Expression} node node
15
+ * @returns {boolean} true when id is numeric, otherwise false
16
+ */
17
+ function isNumericId(node) {
18
+ return node.type === "Literal" && node.value !== null && node.value !== undefined && Number.isInteger(node.value) && /** @type {number} */node.value >= 0;
19
+ }
20
+
21
+ /**
22
+ * @param {Expression | SpreadElement | null} node node
23
+ * @returns {boolean} true when module id, otherwise false
24
+ */
25
+ function isModuleId(node) {
26
+ return node !== null && node.type === "Literal" && (isNumericId(node) || typeof node.value === "string");
27
+ }
28
+
29
+ /**
30
+ * @param {Expression | SpreadElement} node node
31
+ * @returns {boolean} true when module wrapper, otherwise false
32
+ */
33
+ function isModuleWrapper(node) {
34
+ return (
35
+ // It's an anonymous function expression that wraps module
36
+ (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && !node.id ||
37
+ // If `DedupePlugin` is used it can be an ID of duplicated module...
38
+ isModuleId(node) ||
39
+ // or an array of shape [<module_id>, ...args]
40
+ node.type === "ArrayExpression" && node.elements.length > 1 && isModuleId(node.elements[0])
41
+ );
42
+ }
43
+
44
+ /**
45
+ * @param {Expression | SpreadElement | null} node node
46
+ * @returns {boolean} true when module hash, otherwise false
47
+ */
48
+ function isModulesHash(node) {
49
+ return node !== null && node.type === "ObjectExpression" && node.properties.filter(property => property.type !== "SpreadElement").map(node => node.value).every(isModuleWrapper);
50
+ }
51
+
52
+ /**
53
+ * @param {Expression | SpreadElement | null} node node
54
+ * @returns {boolean} true when module array, otherwise false
55
+ */
56
+ function isModulesArray(node) {
57
+ return node !== null && node.type === "ArrayExpression" && node.elements.every(elem =>
58
+ // Some of array items may be skipped because there is no module with such id
59
+ !elem || isModuleWrapper(elem));
60
+ }
61
+
62
+ /**
63
+ * @param {Expression | SpreadElement | null} node node
64
+ * @returns {boolean} true when simple modules list, otherwise false
65
+ */
66
+ function isSimpleModulesList(node) {
67
+ return (
68
+ // Modules are contained in hash. Keys are module ids.
69
+ isModulesHash(node) ||
70
+ // Modules are contained in array. Indexes are module ids.
71
+ isModulesArray(node)
72
+ );
73
+ }
74
+
75
+ /**
76
+ * @param {Expression | SpreadElement | null} node node
77
+ * @returns {boolean} true when optimized modules array, otherwise false
78
+ */
79
+ function isOptimizedModulesArray(node) {
80
+ // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
81
+ // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
82
+ // The `<minimum ID>` + array indexes are module ids
83
+ return node !== null && node.type === "CallExpression" && node.callee.type === "MemberExpression" &&
84
+ // Make sure the object called is `Array(<some number>)`
85
+ node.callee.object.type === "CallExpression" && node.callee.object.callee.type === "Identifier" && node.callee.object.callee.name === "Array" && node.callee.object.arguments.length === 1 && node.callee.object.arguments[0].type !== "SpreadElement" && isNumericId(node.callee.object.arguments[0]) &&
86
+ // Make sure the property X called for `Array(<some number>).X` is `concat`
87
+ node.callee.property.type === "Identifier" && node.callee.property.name === "concat" &&
88
+ // Make sure exactly one array is passed in to `concat`
89
+ node.arguments.length === 1 && isModulesArray(node.arguments[0]);
90
+ }
91
+
92
+ /**
93
+ * @param {Expression | SpreadElement | null} node node
94
+ * @returns {boolean} true when modules list, otherwise false
95
+ */
96
+ function isModulesList(node) {
97
+ return isSimpleModulesList(node) ||
98
+ // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
99
+ isOptimizedModulesArray(node);
100
+ }
101
+
102
+ /** @typedef {{ start: number, end: number }} Location */
103
+
104
+ /**
105
+ * @param {Node} node node
106
+ * @returns {Location} location
107
+ */
108
+ function getModuleLocation(node) {
109
+ return {
110
+ start: node.start,
111
+ end: node.end
112
+ };
113
+ }
114
+
115
+ /** @typedef {Record<number, Location>} ModulesLocations */
116
+
117
+ /**
118
+ * @param {Expression | SpreadElement} node node
119
+ * @returns {ModulesLocations} modules locations
120
+ */
121
+ function getModulesLocations(node) {
122
+ if (node.type === "ObjectExpression") {
123
+ // Modules hash
124
+ const modulesNodes = node.properties;
125
+ return modulesNodes.reduce((result, moduleNode) => {
126
+ if (moduleNode.type !== "Property") {
127
+ return result;
128
+ }
129
+ const moduleId = moduleNode.key.type === "Identifier" ? moduleNode.key.name :
130
+ // @ts-expect-error need verify why we need it, tests not cover it case
131
+ moduleNode.key.value;
132
+ if (moduleId === "undefined") {
133
+ return result;
134
+ }
135
+ result[moduleId] = getModuleLocation(moduleNode.value);
136
+ return result;
137
+ }, /** @type {ModulesLocations} */{});
138
+ }
139
+ const isOptimizedArray = node.type === "CallExpression";
140
+ if (node.type === "ArrayExpression" || isOptimizedArray) {
141
+ // Modules array or optimized array
142
+ const minId = isOptimizedArray && node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression" && node.callee.object.arguments[0].type === "Literal" ?
143
+ // Get the [minId] value from the Array() call first argument literal value
144
+ /** @type {number} */
145
+ node.callee.object.arguments[0].value :
146
+ // `0` for simple array
147
+ 0;
148
+ const modulesNodes = isOptimizedArray ?
149
+ // The modules reside in the `concat()` function call arguments
150
+ node.arguments[0].type === "ArrayExpression" ? node.arguments[0].elements : [] : node.elements;
151
+ return modulesNodes.reduce((result, moduleNode, i) => {
152
+ if (moduleNode) {
153
+ result[i + minId] = getModuleLocation(moduleNode);
154
+ }
155
+ return result;
156
+ }, /** @type {ModulesLocations} */{});
157
+ }
158
+ return {};
159
+ }
160
+
161
+ /**
162
+ * @param {ExpressionStatement} node node
163
+ * @returns {boolean} true when IIFE, otherwise false
164
+ */
165
+ function isIIFE(node) {
166
+ return node.type === "ExpressionStatement" && (node.expression.type === "CallExpression" || node.expression.type === "UnaryExpression" && node.expression.argument.type === "CallExpression");
167
+ }
168
+
169
+ /**
170
+ * @param {ExpressionStatement} node node
171
+ * @returns {Expression} IIFE call expression
172
+ */
173
+ function getIIFECallExpression(node) {
174
+ if (node.expression.type === "UnaryExpression") {
175
+ return node.expression.argument;
176
+ }
177
+ return node.expression;
178
+ }
179
+
180
+ /**
181
+ * @param {Expression} node node
182
+ * @returns {boolean} true when chunks ids, otherwose false
183
+ */
184
+ function isChunkIds(node) {
185
+ // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
186
+ return node.type === "ArrayExpression" && node.elements.every(isModuleId);
187
+ }
188
+
189
+ /**
190
+ * @param {(Expression | SpreadElement | null)[]} args arguments
191
+ * @returns {boolean} true when async chunk arguments, otherwise false
192
+ */
193
+ function mayBeAsyncChunkArguments(args) {
194
+ return args.length >= 2 && args[0] !== null && args[0].type !== "SpreadElement" && isChunkIds(args[0]);
195
+ }
196
+
197
+ /**
198
+ * Returns bundle source except modules
199
+ * @param {string} content content
200
+ * @param {ModulesLocations | null} modulesLocations modules locations
201
+ * @returns {string} runtime code
202
+ */
203
+ function getBundleRuntime(content, modulesLocations) {
204
+ const sortedLocations = Object.values(modulesLocations || {}).toSorted((a, b) => a.start - b.start);
205
+ let result = "";
206
+ let lastIndex = 0;
207
+ for (const {
208
+ start,
209
+ end
210
+ } of sortedLocations) {
211
+ result += content.slice(lastIndex, start);
212
+ lastIndex = end;
213
+ }
214
+ return result + content.slice(lastIndex);
215
+ }
216
+
217
+ /**
218
+ * @param {CallExpression} node node
219
+ * @returns {boolean} true when is async chunk push expression, otheriwse false
220
+ */
221
+ function isAsyncChunkPushExpression(node) {
222
+ const {
223
+ callee,
224
+ arguments: args
225
+ } = node;
226
+ return callee.type === "MemberExpression" && callee.property.type === "Identifier" && callee.property.name === "push" && callee.object.type === "AssignmentExpression" && args.length === 1 && args[0].type === "ArrayExpression" && mayBeAsyncChunkArguments(args[0].elements) && isModulesList(args[0].elements[1]);
227
+ }
228
+
229
+ /**
230
+ * @param {CallExpression} node node
231
+ * @returns {boolean} true when is async web worker, otherwise false
232
+ */
233
+ function isAsyncWebWorkerChunkExpression(node) {
234
+ const {
235
+ callee,
236
+ type,
237
+ arguments: args
238
+ } = node;
239
+ return type === "CallExpression" && callee.type === "MemberExpression" && args.length === 2 && args[0].type !== "SpreadElement" && isChunkIds(args[0]) && isModulesList(args[1]);
240
+ }
241
+
242
+ /** @typedef {Record<string, string>} Modules */
243
+
244
+ /**
245
+ * @param {string} bundlePath bundle path
246
+ * @param {{ sourceType: "script" | "module" }} opts options
247
+ * @returns {{ modules: Modules, src: string, runtimeSrc: string }} parsed result
248
+ */
249
+ module.exports.parseBundle = function parseBundle(bundlePath, opts) {
10
250
  const {
11
- sourceType = 'script'
251
+ sourceType = "script"
12
252
  } = opts || {};
13
- const content = fs.readFileSync(bundlePath, 'utf8');
253
+ const content = fs.readFileSync(bundlePath, "utf8");
14
254
  const ast = acorn.parse(content, {
15
255
  sourceType,
16
- // I believe in a bright future of ECMAScript!
17
- // Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
18
- // Seems like `acorn` supports such weird option value.
19
- ecmaVersion: 2050
256
+ ecmaVersion: "latest"
20
257
  });
258
+
259
+ /** @type {{ locations: ModulesLocations | null, expressionStatementDepth: number }} */
21
260
  const walkState = {
22
261
  locations: null,
23
262
  expressionStatementDepth: 0
24
263
  };
25
264
  walk.recursive(ast, walkState, {
26
- ExpressionStatement(node, state, c) {
265
+ ExpressionStatement(node, state, callback) {
27
266
  if (state.locations) return;
28
267
  state.expressionStatementDepth++;
29
268
  if (
30
269
  // Webpack 5 stores modules in the the top-level IIFE
31
270
  state.expressionStatementDepth === 1 && ast.body.includes(node) && isIIFE(node)) {
32
271
  const fn = getIIFECallExpression(node);
33
- if (
272
+ if (fn.type === "CallExpression" &&
34
273
  // It should not contain neither arguments
35
- fn.arguments.length === 0 &&
274
+ fn.arguments.length === 0 && (fn.callee.type === "FunctionExpression" || fn.callee.type === "ArrowFunctionExpression") &&
36
275
  // ...nor parameters
37
- fn.callee.params.length === 0) {
276
+ fn.callee.params.length === 0 && fn.callee.body.type === "BlockStatement") {
38
277
  // Modules are stored in the very first variable declaration as hash
39
- const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
278
+ const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === "VariableDeclaration");
40
279
  if (firstVariableDeclaration) {
41
280
  for (const declaration of firstVariableDeclaration.declarations) {
42
281
  if (declaration.init && isModulesList(declaration.init)) {
@@ -50,7 +289,7 @@ function parseBundle(bundlePath, opts) {
50
289
  }
51
290
  }
52
291
  if (!state.locations) {
53
- c(node.expression, state);
292
+ callback(node.expression, state);
54
293
  }
55
294
  state.expressionStatementDepth--;
56
295
  },
@@ -63,18 +302,18 @@ function parseBundle(bundlePath, opts) {
63
302
  left,
64
303
  right
65
304
  } = node;
66
- if (left && left.object && left.object.name === 'exports' && left.property && left.property.name === 'modules' && isModulesHash(right)) {
305
+ if (left && left.type === "MemberExpression" && left.object && left.object.type === "Identifier" && left.object.name === "exports" && left.property && left.property.type === "Identifier" && left.property.name === "modules" && isModulesHash(right)) {
67
306
  state.locations = getModulesLocations(right);
68
307
  }
69
308
  },
70
- CallExpression(node, state, c) {
309
+ CallExpression(node, state, callback) {
71
310
  if (state.locations) return;
72
311
  const args = node.arguments;
73
312
 
74
313
  // Main chunk with webpack loader.
75
314
  // Modules are stored in first argument:
76
315
  // (function (...) {...})(<modules>)
77
- if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
316
+ if (node.callee.type === "FunctionExpression" && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
78
317
  state.locations = getModulesLocations(args[0]);
79
318
  return;
80
319
  }
@@ -82,7 +321,7 @@ function parseBundle(bundlePath, opts) {
82
321
  // Async Webpack < v4 chunk without webpack loader.
83
322
  // webpackJsonp([<chunks>], <modules>, ...)
84
323
  // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
85
- if (node.callee.type === 'Identifier' && mayBeAsyncChunkArguments(args) && isModulesList(args[1])) {
324
+ if (node.callee.type === "Identifier" && mayBeAsyncChunkArguments(args) && args[1].type !== "SpreadElement" && isModulesList(args[1])) {
86
325
  state.locations = getModulesLocations(args[1]);
87
326
  return;
88
327
  }
@@ -90,7 +329,7 @@ function parseBundle(bundlePath, opts) {
90
329
  // Async Webpack v4 chunk without webpack loader.
91
330
  // (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
92
331
  // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
93
- if (isAsyncChunkPushExpression(node)) {
332
+ if (isAsyncChunkPushExpression(node) && args[0].type === "ArrayExpression" && args[0].elements[1]) {
94
333
  state.locations = getModulesLocations(args[0].elements[1]);
95
334
  return;
96
335
  }
@@ -105,152 +344,22 @@ function parseBundle(bundlePath, opts) {
105
344
 
106
345
  // Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
107
346
  // features (e.g. `umd` library output) can wrap modules list into additional IIFE.
108
- args.forEach(arg => c(arg, state));
347
+ for (const arg of args) {
348
+ callback(arg, state);
349
+ }
109
350
  }
110
351
  });
352
+
353
+ /** @type {Modules} */
111
354
  const modules = {};
112
355
  if (walkState.locations) {
113
- Object.entries(walkState.locations).forEach(([id, loc]) => {
356
+ for (const [id, loc] of Object.entries(walkState.locations)) {
114
357
  modules[id] = content.slice(loc.start, loc.end);
115
- });
358
+ }
116
359
  }
117
360
  return {
118
361
  modules,
119
362
  src: content,
120
363
  runtimeSrc: getBundleRuntime(content, walkState.locations)
121
364
  };
122
- }
123
-
124
- /**
125
- * Returns bundle source except modules
126
- */
127
- function getBundleRuntime(content, modulesLocations) {
128
- const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
129
- let result = '';
130
- let lastIndex = 0;
131
- for (const {
132
- start,
133
- end
134
- } of sortedLocations) {
135
- result += content.slice(lastIndex, start);
136
- lastIndex = end;
137
- }
138
- return result + content.slice(lastIndex, content.length);
139
- }
140
- function isIIFE(node) {
141
- return node.type === 'ExpressionStatement' && (node.expression.type === 'CallExpression' || node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression');
142
- }
143
- function getIIFECallExpression(node) {
144
- if (node.expression.type === 'UnaryExpression') {
145
- return node.expression.argument;
146
- } else {
147
- return node.expression;
148
- }
149
- }
150
- function isModulesList(node) {
151
- return isSimpleModulesList(node) ||
152
- // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
153
- isOptimizedModulesArray(node);
154
- }
155
- function isSimpleModulesList(node) {
156
- return (
157
- // Modules are contained in hash. Keys are module ids.
158
- isModulesHash(node) ||
159
- // Modules are contained in array. Indexes are module ids.
160
- isModulesArray(node)
161
- );
162
- }
163
- function isModulesHash(node) {
164
- return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
165
- }
166
- function isModulesArray(node) {
167
- return node.type === 'ArrayExpression' && node.elements.every(elem =>
168
- // Some of array items may be skipped because there is no module with such id
169
- !elem || isModuleWrapper(elem));
170
- }
171
- function isOptimizedModulesArray(node) {
172
- // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
173
- // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
174
- // The `<minimum ID>` + array indexes are module ids
175
- return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
176
- // Make sure the object called is `Array(<some number>)`
177
- node.callee.object.type === 'CallExpression' && node.callee.object.callee.type === 'Identifier' && node.callee.object.callee.name === 'Array' && node.callee.object.arguments.length === 1 && isNumericId(node.callee.object.arguments[0]) &&
178
- // Make sure the property X called for `Array(<some number>).X` is `concat`
179
- node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' &&
180
- // Make sure exactly one array is passed in to `concat`
181
- node.arguments.length === 1 && isModulesArray(node.arguments[0]);
182
- }
183
- function isModuleWrapper(node) {
184
- return (
185
- // It's an anonymous function expression that wraps module
186
- (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id ||
187
- // If `DedupePlugin` is used it can be an ID of duplicated module...
188
- isModuleId(node) ||
189
- // or an array of shape [<module_id>, ...args]
190
- node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0])
191
- );
192
- }
193
- function isModuleId(node) {
194
- return node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string');
195
- }
196
- function isNumericId(node) {
197
- return node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0;
198
- }
199
- function isChunkIds(node) {
200
- // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
201
- return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
202
- }
203
- function isAsyncChunkPushExpression(node) {
204
- const {
205
- callee,
206
- arguments: args
207
- } = node;
208
- return callee.type === 'MemberExpression' && callee.property.name === 'push' && callee.object.type === 'AssignmentExpression' && args.length === 1 && args[0].type === 'ArrayExpression' && mayBeAsyncChunkArguments(args[0].elements) && isModulesList(args[0].elements[1]);
209
- }
210
- function mayBeAsyncChunkArguments(args) {
211
- return args.length >= 2 && isChunkIds(args[0]);
212
- }
213
- function isAsyncWebWorkerChunkExpression(node) {
214
- const {
215
- callee,
216
- type,
217
- arguments: args
218
- } = node;
219
- return type === 'CallExpression' && callee.type === 'MemberExpression' && args.length === 2 && isChunkIds(args[0]) && isModulesList(args[1]);
220
- }
221
- function getModulesLocations(node) {
222
- if (node.type === 'ObjectExpression') {
223
- // Modules hash
224
- const modulesNodes = node.properties;
225
- return modulesNodes.reduce((result, moduleNode) => {
226
- const moduleId = moduleNode.key.name || moduleNode.key.value;
227
- result[moduleId] = getModuleLocation(moduleNode.value);
228
- return result;
229
- }, {});
230
- }
231
- const isOptimizedArray = node.type === 'CallExpression';
232
- if (node.type === 'ArrayExpression' || isOptimizedArray) {
233
- // Modules array or optimized array
234
- const minId = isOptimizedArray ?
235
- // Get the [minId] value from the Array() call first argument literal value
236
- node.callee.object.arguments[0].value :
237
- // `0` for simple array
238
- 0;
239
- const modulesNodes = isOptimizedArray ?
240
- // The modules reside in the `concat()` function call arguments
241
- node.arguments[0].elements : node.elements;
242
- return modulesNodes.reduce((result, moduleNode, i) => {
243
- if (moduleNode) {
244
- result[i + minId] = getModuleLocation(moduleNode);
245
- }
246
- return result;
247
- }, {});
248
- }
249
- return {};
250
- }
251
- function getModuleLocation(node) {
252
- return {
253
- start: node.start,
254
- end: node.end
255
- };
256
- }
365
+ };
package/lib/sizeUtils.js CHANGED
@@ -4,11 +4,30 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getCompressedSize = getCompressedSize;
7
- const zlib = require('zlib');
8
- function getCompressedSize(compressionAlgorithm, input) {
9
- if (compressionAlgorithm === 'gzip') return zlib.gzipSync(input, {
10
- level: 9
11
- }).length;
12
- if (compressionAlgorithm === 'brotli') return zlib.brotliCompressSync(input).length;
13
- throw new Error(`Unsupported compression algorithm: ${compressionAlgorithm}.`);
7
+ exports.isZstdSupported = void 0;
8
+ var _nodeZlib = _interopRequireDefault(require("node:zlib"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const isZstdSupported = exports.isZstdSupported = "createZstdCompress" in _nodeZlib.default;
11
+
12
+ /** @typedef {"gzip" | "brotli" | "zstd"} Algorithm */
13
+
14
+ /**
15
+ * @param {Algorithm} algorithm compression algorithm
16
+ * @param {string} input input
17
+ * @returns {number} compressed size
18
+ */
19
+ function getCompressedSize(algorithm, input) {
20
+ if (algorithm === "gzip") {
21
+ return _nodeZlib.default.gzipSync(input, {
22
+ level: 9
23
+ }).length;
24
+ }
25
+ if (algorithm === "brotli") {
26
+ return _nodeZlib.default.brotliCompressSync(input).length;
27
+ }
28
+ if (algorithm === "zstd" && isZstdSupported) {
29
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
30
+ return _nodeZlib.default.zstdCompressSync(input).length;
31
+ }
32
+ throw new Error(`Unsupported compression algorithm: ${algorithm}.`);
14
33
  }
package/lib/statsUtils.js CHANGED
@@ -2,18 +2,28 @@
2
2
 
3
3
  const {
4
4
  createWriteStream
5
- } = require('fs');
5
+ } = require("node:fs");
6
6
  const {
7
7
  Readable
8
- } = require('stream');
8
+ } = require("node:stream");
9
+ const {
10
+ pipeline
11
+ } = require("node:stream/promises");
12
+
13
+ /** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */
14
+ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */
15
+
9
16
  class StatsSerializeStream extends Readable {
17
+ /**
18
+ * @param {StatsCompilation} stats stats
19
+ */
10
20
  constructor(stats) {
11
21
  super();
12
22
  this._indentLevel = 0;
13
23
  this._stringifier = this._stringify(stats);
14
24
  }
15
25
  get _indent() {
16
- return ' '.repeat(this._indentLevel);
26
+ return " ".repeat(this._indentLevel);
17
27
  }
18
28
  _read() {
19
29
  let readMore = true;
@@ -30,25 +40,31 @@ class StatsSerializeStream extends Readable {
30
40
  }
31
41
  }
32
42
  }
43
+
44
+ /**
45
+ * @param {EXPECTED_ANY} obj obj
46
+ * @returns {Generator<string, undefined, unknown>} stringified result
47
+ * @private
48
+ */
33
49
  *_stringify(obj) {
34
- if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
50
+ if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean" || obj === null) {
35
51
  yield JSON.stringify(obj);
36
52
  } else if (Array.isArray(obj)) {
37
- yield '[';
53
+ yield "[";
38
54
  this._indentLevel++;
39
55
  let isFirst = true;
40
56
  for (let item of obj) {
41
57
  if (item === undefined) {
42
58
  item = null;
43
59
  }
44
- yield `${isFirst ? '' : ','}\n${this._indent}`;
60
+ yield `${isFirst ? "" : ","}\n${this._indent}`;
45
61
  yield* this._stringify(item);
46
62
  isFirst = false;
47
63
  }
48
64
  this._indentLevel--;
49
- yield obj.length ? `\n${this._indent}]` : ']';
65
+ yield obj.length ? `\n${this._indent}]` : "]";
50
66
  } else {
51
- yield '{';
67
+ yield "{";
52
68
  this._indentLevel++;
53
69
  let isFirst = true;
54
70
  const entries = Object.entries(obj);
@@ -56,19 +72,25 @@ class StatsSerializeStream extends Readable {
56
72
  if (itemValue === undefined) {
57
73
  continue;
58
74
  }
59
- yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
75
+ yield `${isFirst ? "" : ","}\n${this._indent}${JSON.stringify(itemKey)}: `;
60
76
  yield* this._stringify(itemValue);
61
77
  isFirst = false;
62
78
  }
63
79
  this._indentLevel--;
64
- yield entries.length ? `\n${this._indent}}` : '}';
80
+ yield entries.length ? `\n${this._indent}}` : "}";
65
81
  }
66
82
  }
67
83
  }
68
- exports.StatsSerializeStream = StatsSerializeStream;
69
- exports.writeStats = writeStats;
84
+
85
+ /**
86
+ * @param {StatsCompilation} stats stats
87
+ * @param {string} filepath filepath file path
88
+ * @returns {Promise<void>}
89
+ */
70
90
  async function writeStats(stats, filepath) {
71
- return new Promise((resolve, reject) => {
72
- new StatsSerializeStream(stats).on('end', resolve).on('error', reject).pipe(createWriteStream(filepath));
73
- });
74
- }
91
+ await pipeline(new StatsSerializeStream(stats), createWriteStream(filepath));
92
+ }
93
+ module.exports = {
94
+ StatsSerializeStream,
95
+ writeStats
96
+ };