webpack-bundle-analyzer 4.10.2 → 5.0.1

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.
@@ -5,22 +5,16 @@ const {
5
5
  resolve,
6
6
  dirname
7
7
  } = require('path');
8
-
9
8
  const commander = require('commander');
10
-
11
9
  const {
12
10
  magenta
13
11
  } = require('picocolors');
14
-
15
12
  const analyzer = require('../analyzer');
16
-
17
13
  const viewer = require('../viewer');
18
-
19
14
  const Logger = require('../Logger');
20
-
21
15
  const utils = require('../utils');
22
-
23
16
  const SIZES = new Set(['stat', 'parsed', 'gzip']);
17
+ const COMPRESSION_ALGORITHMS = new Set(['gzip', 'brotli']);
24
18
  const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
25
19
 
26
20
  Arguments:
@@ -28,9 +22,10 @@ const program = commander.version(require('../../package.json').version).usage(`
28
22
  bundleStatsFile Path to Webpack Stats JSON file.
29
23
  bundleDir Directory containing all generated bundles.
30
24
  You should provided it if you want analyzer to show you the real parsed module sizes.
31
- By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server`,`static` or `json`.' + br('In `server` mode analyzer will start HTTP server to show bundle report.') + br('In `static` mode single HTML file with bundle report will be generated.') + br('In `json` mode single JSON file with bundle report will be generated.'), 'server').option( // Had to make `host` parameter optional in order to let `-h` flag output help message
32
- // Fixes https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/239
33
- '-h, --host [host]', 'Host that will be used in `server` mode to start HTTP server.', '127.0.0.1').option('-p, --port <n>', 'Port that will be used in `server` mode to start HTTP server.', 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.').option('-t, --title <title>', 'String to use in title element of html report.').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[...SIZES].join(', ')}`), 'parsed').option('-O, --no-open', "Don't open report in default browser automatically.").option('-e, --exclude <regexp>', 'Assets that should be excluded from the report.' + br('Can be specified multiple times.'), array()).option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[...Logger.levels].join(', ')}`), Logger.defaultLevel).parse(process.argv);
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(
26
+ // Had to make `host` parameter optional in order to let `-h` flag output help message
27
+ // 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);
34
29
  let [bundleStatsFile, bundleDir] = program.args;
35
30
  let {
36
31
  mode,
@@ -39,43 +34,41 @@ let {
39
34
  report: reportFilename,
40
35
  title: reportTitle,
41
36
  defaultSizes,
37
+ compressionAlgorithm,
42
38
  logLevel,
43
39
  open: openBrowser,
44
40
  exclude: excludeAssets
45
41
  } = program.opts();
46
42
  const logger = new Logger(logLevel);
47
-
48
43
  if (typeof reportTitle === 'undefined') {
49
44
  reportTitle = utils.defaultTitle;
50
45
  }
51
-
52
46
  if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
53
-
54
47
  if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
55
48
  showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
56
49
  }
57
-
58
50
  if (mode === 'server') {
59
51
  if (!host) showHelp('Invalid host name');
60
52
  port = port === 'auto' ? 0 : Number(port);
61
53
  if (isNaN(port)) showHelp('Invalid port. Should be a number or `auto`');
62
54
  }
63
-
55
+ if (!COMPRESSION_ALGORITHMS.has(compressionAlgorithm)) {
56
+ showHelp(`Invalid compression algorithm option. Possible values are: ${[...COMPRESSION_ALGORITHMS].join(', ')}`);
57
+ }
64
58
  if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(', ')}`);
65
59
  bundleStatsFile = resolve(bundleStatsFile);
66
60
  if (!bundleDir) bundleDir = dirname(bundleStatsFile);
67
61
  parseAndAnalyse(bundleStatsFile);
68
-
69
62
  async function parseAndAnalyse(bundleStatsFile) {
70
63
  try {
71
64
  const bundleStats = await analyzer.readStatsFromFile(bundleStatsFile);
72
-
73
65
  if (mode === 'server') {
74
66
  viewer.startServer(bundleStats, {
75
67
  openBrowser,
76
68
  port,
77
69
  host,
78
70
  defaultSizes,
71
+ compressionAlgorithm,
79
72
  reportTitle,
80
73
  bundleDir,
81
74
  excludeAssets,
@@ -88,6 +81,7 @@ async function parseAndAnalyse(bundleStatsFile) {
88
81
  reportFilename: resolve(reportFilename || 'report.html'),
89
82
  reportTitle,
90
83
  defaultSizes,
84
+ compressionAlgorithm,
91
85
  bundleDir,
92
86
  excludeAssets,
93
87
  logger: new Logger(logLevel)
@@ -95,6 +89,7 @@ async function parseAndAnalyse(bundleStatsFile) {
95
89
  } else if (mode === 'json') {
96
90
  viewer.generateJSONReport(bundleStats, {
97
91
  reportFilename: resolve(reportFilename || 'report.json'),
92
+ compressionAlgorithm,
98
93
  bundleDir,
99
94
  excludeAssets,
100
95
  logger: new Logger(logLevel)
@@ -106,17 +101,14 @@ async function parseAndAnalyse(bundleStatsFile) {
106
101
  process.exit(1);
107
102
  }
108
103
  }
109
-
110
104
  function showHelp(error) {
111
105
  if (error) console.log(`\n ${magenta(error)}\n`);
112
106
  program.outputHelp();
113
107
  process.exit(1);
114
108
  }
115
-
116
109
  function br(str) {
117
- return `\n${' '.repeat(28)}${str}`;
110
+ return `\n${' '.repeat(32)}${str}`;
118
111
  }
119
-
120
112
  function array() {
121
113
  const arr = [];
122
114
  return val => {
package/lib/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  const {
4
4
  start
5
5
  } = require('./viewer');
6
-
7
6
  module.exports = {
8
7
  start,
9
8
  BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
package/lib/parseUtils.js CHANGED
@@ -1,19 +1,18 @@
1
1
  "use strict";
2
2
 
3
3
  const fs = require('fs');
4
-
5
4
  const acorn = require('acorn');
6
-
7
5
  const walk = require('acorn-walk');
8
-
9
6
  module.exports = {
10
7
  parseBundle
11
8
  };
12
-
13
- function parseBundle(bundlePath) {
9
+ function parseBundle(bundlePath, opts) {
10
+ const {
11
+ sourceType = 'script'
12
+ } = opts || {};
14
13
  const content = fs.readFileSync(bundlePath, 'utf8');
15
14
  const ast = acorn.parse(content, {
16
- sourceType: 'script',
15
+ sourceType,
17
16
  // I believe in a bright future of ECMAScript!
18
17
  // Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
19
18
  // Seems like `acorn` supports such weird option value.
@@ -27,22 +26,21 @@ function parseBundle(bundlePath) {
27
26
  ExpressionStatement(node, state, c) {
28
27
  if (state.locations) return;
29
28
  state.expressionStatementDepth++;
30
-
31
- if ( // Webpack 5 stores modules in the the top-level IIFE
29
+ if (
30
+ // Webpack 5 stores modules in the the top-level IIFE
32
31
  state.expressionStatementDepth === 1 && ast.body.includes(node) && isIIFE(node)) {
33
32
  const fn = getIIFECallExpression(node);
34
-
35
- if ( // It should not contain neither arguments
36
- fn.arguments.length === 0 && // ...nor parameters
33
+ if (
34
+ // It should not contain neither arguments
35
+ fn.arguments.length === 0 &&
36
+ // ...nor parameters
37
37
  fn.callee.params.length === 0) {
38
38
  // Modules are stored in the very first variable declaration as hash
39
39
  const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
40
-
41
40
  if (firstVariableDeclaration) {
42
41
  for (const declaration of firstVariableDeclaration.declarations) {
43
42
  if (declaration.init) {
44
43
  state.locations = getModulesLocations(declaration.init);
45
-
46
44
  if (state.locations) {
47
45
  break;
48
46
  }
@@ -51,93 +49,85 @@ function parseBundle(bundlePath) {
51
49
  }
52
50
  }
53
51
  }
54
-
55
52
  if (!state.locations) {
56
53
  c(node.expression, state);
57
54
  }
58
-
59
55
  state.expressionStatementDepth--;
60
56
  },
61
-
62
57
  AssignmentExpression(node, state) {
63
- if (state.locations) return; // Modules are stored in exports.modules:
64
- // exports.modules = {};
58
+ if (state.locations) return;
65
59
 
60
+ // Modules are stored in exports.modules:
61
+ // exports.modules = {};
66
62
  const {
67
63
  left,
68
64
  right
69
65
  } = node;
70
-
71
66
  if (left && left.object && left.object.name === 'exports' && left.property && left.property.name === 'modules' && isModulesHash(right)) {
72
67
  state.locations = getModulesLocations(right);
73
68
  }
74
69
  },
75
-
76
70
  CallExpression(node, state, c) {
77
71
  if (state.locations) return;
78
- const args = node.arguments; // Main chunk with webpack loader.
72
+ const args = node.arguments;
73
+
74
+ // Main chunk with webpack loader.
79
75
  // Modules are stored in first argument:
80
76
  // (function (...) {...})(<modules>)
81
-
82
77
  if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
83
78
  state.locations = getModulesLocations(args[0]);
84
79
  return;
85
- } // Async Webpack < v4 chunk without webpack loader.
80
+ }
81
+
82
+ // Async Webpack < v4 chunk without webpack loader.
86
83
  // webpackJsonp([<chunks>], <modules>, ...)
87
84
  // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
88
-
89
-
90
85
  if (node.callee.type === 'Identifier' && mayBeAsyncChunkArguments(args) && isModulesList(args[1])) {
91
86
  state.locations = getModulesLocations(args[1]);
92
87
  return;
93
- } // Async Webpack v4 chunk without webpack loader.
88
+ }
89
+
90
+ // Async Webpack v4 chunk without webpack loader.
94
91
  // (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
95
92
  // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
96
-
97
-
98
93
  if (isAsyncChunkPushExpression(node)) {
99
94
  state.locations = getModulesLocations(args[0].elements[1]);
100
95
  return;
101
- } // Webpack v4 WebWorkerChunkTemplatePlugin
96
+ }
97
+
98
+ // Webpack v4 WebWorkerChunkTemplatePlugin
102
99
  // globalObject.chunkCallbackName([<chunks>],<modules>, ...);
103
100
  // Both globalObject and chunkCallbackName can be changed through the config, so we can't check them.
104
-
105
-
106
101
  if (isAsyncWebWorkerChunkExpression(node)) {
107
102
  state.locations = getModulesLocations(args[1]);
108
103
  return;
109
- } // Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
110
- // features (e.g. `umd` library output) can wrap modules list into additional IIFE.
111
-
104
+ }
112
105
 
106
+ // Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
107
+ // features (e.g. `umd` library output) can wrap modules list into additional IIFE.
113
108
  args.forEach(arg => c(arg, state));
114
109
  }
115
-
116
110
  });
117
111
  const modules = {};
118
-
119
112
  if (walkState.locations) {
120
113
  Object.entries(walkState.locations).forEach(([id, loc]) => {
121
114
  modules[id] = content.slice(loc.start, loc.end);
122
115
  });
123
116
  }
124
-
125
117
  return {
126
118
  modules,
127
119
  src: content,
128
120
  runtimeSrc: getBundleRuntime(content, walkState.locations)
129
121
  };
130
122
  }
123
+
131
124
  /**
132
125
  * Returns bundle source except modules
133
126
  */
134
-
135
-
136
127
  function getBundleRuntime(content, modulesLocations) {
137
128
  const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
138
129
  let result = '';
139
130
  let lastIndex = 0;
140
-
141
131
  for (const {
142
132
  start,
143
133
  end
@@ -145,14 +135,11 @@ function getBundleRuntime(content, modulesLocations) {
145
135
  result += content.slice(lastIndex, start);
146
136
  lastIndex = end;
147
137
  }
148
-
149
138
  return result + content.slice(lastIndex, content.length);
150
139
  }
151
-
152
140
  function isIIFE(node) {
153
141
  return node.type === 'ExpressionStatement' && (node.expression.type === 'CallExpression' || node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression');
154
142
  }
155
-
156
143
  function getIIFECallExpression(node) {
157
144
  if (node.expression.type === 'UnaryExpression') {
158
145
  return node.expression.argument;
@@ -160,59 +147,59 @@ function getIIFECallExpression(node) {
160
147
  return node.expression;
161
148
  }
162
149
  }
163
-
164
150
  function isModulesList(node) {
165
- return isSimpleModulesList(node) || // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
151
+ return isSimpleModulesList(node) ||
152
+ // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
166
153
  isOptimizedModulesArray(node);
167
154
  }
168
-
169
155
  function isSimpleModulesList(node) {
170
- return (// Modules are contained in hash. Keys are module ids.
171
- isModulesHash(node) || // Modules are contained in array. Indexes are module ids.
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.
172
160
  isModulesArray(node)
173
161
  );
174
162
  }
175
-
176
163
  function isModulesHash(node) {
177
164
  return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
178
165
  }
179
-
180
166
  function isModulesArray(node) {
181
- return node.type === 'ArrayExpression' && node.elements.every(elem => // Some of array items may be skipped because there is no module with such id
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
182
169
  !elem || isModuleWrapper(elem));
183
170
  }
184
-
185
171
  function isOptimizedModulesArray(node) {
186
172
  // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
187
173
  // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
188
174
  // The `<minimum ID>` + array indexes are module ids
189
- return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && // Make sure the object called is `Array(<some number>)`
190
- 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]) && // Make sure the property X called for `Array(<some number>).X` is `concat`
191
- node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' && // Make sure exactly one array is passed in to `concat`
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`
192
181
  node.arguments.length === 1 && isModulesArray(node.arguments[0]);
193
182
  }
194
-
195
183
  function isModuleWrapper(node) {
196
- return (// It's an anonymous function expression that wraps module
197
- (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id || // If `DedupePlugin` is used it can be an ID of duplicated module...
198
- isModuleId(node) || // or an array of shape [<module_id>, ...args]
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]
199
190
  node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0])
200
191
  );
201
192
  }
202
-
203
193
  function isModuleId(node) {
204
194
  return node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string');
205
195
  }
206
-
207
196
  function isNumericId(node) {
208
197
  return node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0;
209
198
  }
210
-
211
199
  function isChunkIds(node) {
212
200
  // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
213
201
  return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
214
202
  }
215
-
216
203
  function isAsyncChunkPushExpression(node) {
217
204
  const {
218
205
  callee,
@@ -220,11 +207,9 @@ function isAsyncChunkPushExpression(node) {
220
207
  } = node;
221
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]);
222
209
  }
223
-
224
210
  function mayBeAsyncChunkArguments(args) {
225
211
  return args.length >= 2 && isChunkIds(args[0]);
226
212
  }
227
-
228
213
  function isAsyncWebWorkerChunkExpression(node) {
229
214
  const {
230
215
  callee,
@@ -233,7 +218,6 @@ function isAsyncWebWorkerChunkExpression(node) {
233
218
  } = node;
234
219
  return type === 'CallExpression' && callee.type === 'MemberExpression' && args.length === 2 && isChunkIds(args[0]) && isModulesList(args[1]);
235
220
  }
236
-
237
221
  function getModulesLocations(node) {
238
222
  if (node.type === 'ObjectExpression') {
239
223
  // Modules hash
@@ -244,28 +228,26 @@ function getModulesLocations(node) {
244
228
  return result;
245
229
  }, {});
246
230
  }
247
-
248
231
  const isOptimizedArray = node.type === 'CallExpression';
249
-
250
232
  if (node.type === 'ArrayExpression' || isOptimizedArray) {
251
233
  // Modules array or optimized array
252
- const minId = isOptimizedArray ? // Get the [minId] value from the Array() call first argument literal value
253
- node.callee.object.arguments[0].value : // `0` for simple 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
254
238
  0;
255
- const modulesNodes = isOptimizedArray ? // The modules reside in the `concat()` function call arguments
239
+ const modulesNodes = isOptimizedArray ?
240
+ // The modules reside in the `concat()` function call arguments
256
241
  node.arguments[0].elements : node.elements;
257
242
  return modulesNodes.reduce((result, moduleNode, i) => {
258
243
  if (moduleNode) {
259
244
  result[i + minId] = getModuleLocation(moduleNode);
260
245
  }
261
-
262
246
  return result;
263
247
  }, {});
264
248
  }
265
-
266
249
  return {};
267
250
  }
268
-
269
251
  function getModuleLocation(node) {
270
252
  return {
271
253
  start: node.start,
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
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}.`);
14
+ }
package/lib/statsUtils.js CHANGED
@@ -3,31 +3,25 @@
3
3
  const {
4
4
  createWriteStream
5
5
  } = require('fs');
6
-
7
6
  const {
8
7
  Readable
9
8
  } = require('stream');
10
-
11
9
  class StatsSerializeStream extends Readable {
12
10
  constructor(stats) {
13
11
  super();
14
12
  this._indentLevel = 0;
15
13
  this._stringifier = this._stringify(stats);
16
14
  }
17
-
18
15
  get _indent() {
19
16
  return ' '.repeat(this._indentLevel);
20
17
  }
21
-
22
18
  _read() {
23
19
  let readMore = true;
24
-
25
20
  while (readMore) {
26
21
  const {
27
22
  value,
28
23
  done
29
24
  } = this._stringifier.next();
30
-
31
25
  if (done) {
32
26
  this.push(null);
33
27
  readMore = false;
@@ -36,7 +30,6 @@ class StatsSerializeStream extends Readable {
36
30
  }
37
31
  }
38
32
  }
39
-
40
33
  *_stringify(obj) {
41
34
  if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
42
35
  yield JSON.stringify(obj);
@@ -44,17 +37,14 @@ class StatsSerializeStream extends Readable {
44
37
  yield '[';
45
38
  this._indentLevel++;
46
39
  let isFirst = true;
47
-
48
40
  for (let item of obj) {
49
41
  if (item === undefined) {
50
42
  item = null;
51
43
  }
52
-
53
44
  yield `${isFirst ? '' : ','}\n${this._indent}`;
54
45
  yield* this._stringify(item);
55
46
  isFirst = false;
56
47
  }
57
-
58
48
  this._indentLevel--;
59
49
  yield obj.length ? `\n${this._indent}]` : ']';
60
50
  } else {
@@ -62,27 +52,21 @@ class StatsSerializeStream extends Readable {
62
52
  this._indentLevel++;
63
53
  let isFirst = true;
64
54
  const entries = Object.entries(obj);
65
-
66
55
  for (const [itemKey, itemValue] of entries) {
67
56
  if (itemValue === undefined) {
68
57
  continue;
69
58
  }
70
-
71
59
  yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
72
60
  yield* this._stringify(itemValue);
73
61
  isFirst = false;
74
62
  }
75
-
76
63
  this._indentLevel--;
77
64
  yield entries.length ? `\n${this._indent}}` : '}';
78
65
  }
79
66
  }
80
-
81
67
  }
82
-
83
68
  exports.StatsSerializeStream = StatsSerializeStream;
84
69
  exports.writeStats = writeStats;
85
-
86
70
  async function writeStats(stats, filepath) {
87
71
  return new Promise((resolve, reject) => {
88
72
  new StatsSerializeStream(stats).on('end', resolve).on('error', reject).pipe(createWriteStream(filepath));
package/lib/template.js CHANGED
@@ -2,38 +2,30 @@
2
2
 
3
3
  /* eslint-disable max-len */
4
4
  const path = require('path');
5
-
6
5
  const fs = require('fs');
7
-
8
6
  const {
9
7
  escape
10
8
  } = require('html-escaper');
11
-
12
9
  const projectRoot = path.resolve(__dirname, '..');
13
10
  const assetsRoot = path.join(projectRoot, 'public');
14
11
  exports.renderViewer = renderViewer;
12
+
15
13
  /**
16
14
  * Escapes `<` characters in JSON to safely use it in `<script>` tag.
17
15
  */
18
-
19
16
  function escapeJson(json) {
20
17
  return JSON.stringify(json).replace(/</gu, '\\u003c');
21
18
  }
22
-
23
19
  function getAssetContent(filename) {
24
20
  const assetPath = path.join(assetsRoot, filename);
25
-
26
21
  if (!assetPath.startsWith(assetsRoot)) {
27
22
  throw new Error(`"${filename}" is outside of the assets root`);
28
23
  }
29
-
30
24
  return fs.readFileSync(assetPath, 'utf8');
31
25
  }
32
-
33
26
  function html(strings, ...values) {
34
27
  return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
35
28
  }
36
-
37
29
  function getScript(filename, mode) {
38
30
  if (mode === 'static') {
39
31
  return `<!-- ${escape(filename)} -->
@@ -42,13 +34,13 @@ function getScript(filename, mode) {
42
34
  return `<script src="${escape(filename)}"></script>`;
43
35
  }
44
36
  }
45
-
46
37
  function renderViewer({
47
38
  title,
48
39
  enableWebSocket,
49
40
  chartData,
50
41
  entrypoints,
51
42
  defaultSizes,
43
+ compressionAlgorithm,
52
44
  mode
53
45
  } = {}) {
54
46
  return html`<!DOCTYPE html>
@@ -71,6 +63,7 @@ function renderViewer({
71
63
  window.chartData = ${escapeJson(chartData)};
72
64
  window.entrypoints = ${escapeJson(entrypoints)};
73
65
  window.defaultSizes = ${escapeJson(defaultSizes)};
66
+ window.compressionAlgorithm = ${escapeJson(compressionAlgorithm)};
74
67
  </script>
75
68
  </body>
76
69
  </html>`;