webpack-bundle-analyzer 2.11.2 → 2.13.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.
@@ -19,6 +19,7 @@ class BundleAnalyzerPlugin {
19
19
  generateStatsFile: false,
20
20
  statsFilename: 'stats.json',
21
21
  statsOptions: null,
22
+ excludeAssets: null,
22
23
  logLevel: 'info',
23
24
  // deprecated
24
25
  startAnalyzer: true,
@@ -73,6 +74,7 @@ class BundleAnalyzerPlugin {
73
74
 
74
75
  try {
75
76
  await bfj.write(statsFilepath, stats, {
77
+ space: 2,
76
78
  promises: 'ignore',
77
79
  buffers: 'ignore',
78
80
  maps: 'ignore',
@@ -100,7 +102,8 @@ class BundleAnalyzerPlugin {
100
102
  port: this.opts.analyzerPort,
101
103
  bundleDir: this.getBundleDirFromCompiler(),
102
104
  logger: this.logger,
103
- defaultSizes: this.opts.defaultSizes
105
+ defaultSizes: this.opts.defaultSizes,
106
+ excludeAssets: this.opts.excludeAssets
104
107
  });
105
108
  }
106
109
  }
@@ -111,7 +114,8 @@ class BundleAnalyzerPlugin {
111
114
  reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
112
115
  bundleDir: this.getBundleDirFromCompiler(),
113
116
  logger: this.logger,
114
- defaultSizes: this.opts.defaultSizes
117
+ defaultSizes: this.opts.defaultSizes,
118
+ excludeAssets: this.opts.excludeAssets
115
119
  });
116
120
  }
117
121
 
package/src/analyzer.js CHANGED
@@ -7,6 +7,7 @@ const gzipSize = require('gzip-size');
7
7
  const Logger = require('./Logger');
8
8
  const Folder = require('./tree/Folder').default;
9
9
  const { parseBundle } = require('./parseUtils');
10
+ const { createAssetsFilter } = require('./utils');
10
11
 
11
12
  const FILENAME_QUERY_REGEXP = /\?.*$/;
12
13
 
@@ -17,9 +18,12 @@ module.exports = {
17
18
 
18
19
  function getViewerData(bundleStats, bundleDir, opts) {
19
20
  const {
20
- logger = new Logger()
21
+ logger = new Logger(),
22
+ excludeAssets = null
21
23
  } = opts || {};
22
24
 
25
+ const isAssetIncluded = createAssetsFilter(excludeAssets);
26
+
23
27
  // Sometimes all the information is located in `children` array (e.g. problem in #10)
24
28
  if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
25
29
  bundleStats = bundleStats.children[0];
@@ -31,7 +35,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
31
35
  // See #22
32
36
  asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
33
37
 
34
- return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks);
38
+ return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
35
39
  });
36
40
 
37
41
  // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
@@ -49,29 +53,27 @@ function getViewerData(bundleStats, bundleDir, opts) {
49
53
  try {
50
54
  bundleInfo = parseBundle(assetFile);
51
55
  } catch (err) {
52
- bundleInfo = null;
53
- }
54
-
55
- if (!bundleInfo) {
56
- logger.warn(
57
- `\nCouldn't parse bundle asset "${assetFile}".\n` +
58
- 'Analyzer will use module sizes from stats file.\n'
59
- );
60
- parsedModules = null;
61
- bundlesSources = null;
62
- break;
56
+ const msg = (err.code === 'ENOENT') ? 'no such file' : err.message;
57
+ logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
58
+ continue;
63
59
  }
64
60
 
65
61
  bundlesSources[statAsset.name] = bundleInfo.src;
66
62
  _.assign(parsedModules, bundleInfo.modules);
67
63
  }
64
+
65
+ if (_.isEmpty(bundlesSources)) {
66
+ bundlesSources = null;
67
+ parsedModules = null;
68
+ logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
69
+ }
68
70
  }
69
71
 
70
72
  const modules = getBundleModules(bundleStats);
71
73
  const assets = _.transform(bundleStats.assets, (result, statAsset) => {
72
74
  const asset = result[statAsset.name] = _.pick(statAsset, 'size');
73
75
 
74
- if (bundlesSources) {
76
+ if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
75
77
  asset.parsedSize = bundlesSources[statAsset.name].length;
76
78
  asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
77
79
  }
@@ -94,7 +96,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
94
96
  // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
95
97
  // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
96
98
  // be the size of minified bundle.
97
- statSize: asset.tree.size,
99
+ // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
100
+ statSize: asset.tree.size || asset.size,
98
101
  parsedSize: asset.parsedSize,
99
102
  gzipSize: asset.gzipSize,
100
103
  groups: _.invokeMap(asset.tree.children, 'toChartData')
@@ -119,6 +122,7 @@ function getBundleModules(bundleStats) {
119
122
  }
120
123
 
121
124
  function assetHasModule(statAsset, statModule) {
125
+ // Checking if this module is the part of asset chunks
122
126
  return _.some(statModule.chunks, moduleChunk =>
123
127
  _.includes(statAsset.chunks, moduleChunk)
124
128
  );
@@ -57,6 +57,12 @@ const program = commander
57
57
  '-O, --no-open',
58
58
  "Don't open report in default browser automatically."
59
59
  )
60
+ .option(
61
+ '-e, --exclude <regexp>',
62
+ 'Assets that should be excluded from the report.' +
63
+ br('Can be specified multiple times.'),
64
+ array()
65
+ )
60
66
  .option(
61
67
  '-l, --log-level <level>',
62
68
  'Log level.' +
@@ -73,6 +79,7 @@ let {
73
79
  defaultSizes,
74
80
  logLevel,
75
81
  open: openBrowser,
82
+ exclude: excludeAssets,
76
83
  args: [bundleStatsFile, bundleDir]
77
84
  } = program;
78
85
  const logger = new Logger(logLevel);
@@ -103,6 +110,7 @@ if (mode === 'server') {
103
110
  host,
104
111
  defaultSizes,
105
112
  bundleDir,
113
+ excludeAssets,
106
114
  logger: new Logger(logLevel)
107
115
  });
108
116
  } else {
@@ -111,6 +119,7 @@ if (mode === 'server') {
111
119
  reportFilename: resolve(reportFilename),
112
120
  defaultSizes,
113
121
  bundleDir,
122
+ excludeAssets,
114
123
  logger: new Logger(logLevel)
115
124
  });
116
125
  }
@@ -124,3 +133,11 @@ function showHelp(error) {
124
133
  function br(str) {
125
134
  return `\n${_.repeat(' ', 28)}${str}`;
126
135
  }
136
+
137
+ function array() {
138
+ const arr = [];
139
+ return (val) => {
140
+ arr.push(val);
141
+ return arr;
142
+ };
143
+ }
package/src/parseUtils.js CHANGED
@@ -26,59 +26,40 @@ function parseBundle(bundlePath) {
26
26
  walkState,
27
27
  {
28
28
  CallExpression(node, state, c) {
29
- if (state.sizes) return;
29
+ if (state.locations) return;
30
30
 
31
31
  const args = node.arguments;
32
32
 
33
- // Additional bundle without webpack loader.
34
- // Modules are stored in second argument, after chunk ids:
35
- // webpackJsonp([<chunks>], <modules>, ...)
36
- // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
37
- if (
38
- node.callee.type === 'Identifier' &&
39
- args.length >= 2 &&
40
- isArgumentContainsChunkIds(args[0]) &&
41
- isArgumentContainsModulesList(args[1])
42
- ) {
43
- state.locations = getModulesLocationFromFunctionArgument(args[1]);
44
- return;
45
- }
46
-
47
- // Additional bundle without webpack loader, with module IDs optimized.
48
- // Modules are stored in second arguments Array(n).concat() call
49
- // webpackJsonp([<chunks>], Array([minimum ID]).concat([<module>, <module>, ...]))
50
- // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
51
- if (
52
- node.callee.type === 'Identifier' &&
53
- (args.length === 2 || args.length === 3) &&
54
- isArgumentContainsChunkIds(args[0]) &&
55
- isArgumentArrayConcatContainingChunks(args[1])
56
- ) {
57
- state.locations = getModulesLocationFromArrayConcat(args[1]);
58
- return;
59
- }
60
-
61
- // Main bundle with webpack loader
33
+ // Main chunk with webpack loader.
62
34
  // Modules are stored in first argument:
63
35
  // (function (...) {...})(<modules>)
64
36
  if (
65
37
  node.callee.type === 'FunctionExpression' &&
66
38
  !node.callee.id &&
67
39
  args.length === 1 &&
68
- isArgumentContainsModulesList(args[0])
40
+ isSimpleModulesList(args[0])
69
41
  ) {
70
- state.locations = getModulesLocationFromFunctionArgument(args[0]);
42
+ state.locations = getModulesLocations(args[0]);
71
43
  return;
72
44
  }
73
45
 
74
- // Additional bundles with webpack 4 are loaded with:
75
- // (window.webpackJsonp=window.webpackJsonp||[]).push([[chunkId], [<module>, <module>], [[optional_entries]]]);
46
+ // Async Webpack < v4 chunk without webpack loader.
47
+ // webpackJsonp([<chunks>], <modules>, ...)
48
+ // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
76
49
  if (
77
- isAsyncChunkPushExpression(node) &&
78
- args.length === 1 &&
79
- isArgumentContainingChunkIdsAndModulesList(args[0])
50
+ node.callee.type === 'Identifier' &&
51
+ mayBeAsyncChunkArguments(args) &&
52
+ isModulesList(args[1])
80
53
  ) {
81
- state.locations = getModulesLocationFromFunctionArgument(args[0].elements[1]);
54
+ state.locations = getModulesLocations(args[1]);
55
+ return;
56
+ }
57
+
58
+ // Async Webpack v4 chunk without webpack loader.
59
+ // (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
60
+ // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
61
+ if (isAsyncChunkPushExpression(node)) {
62
+ state.locations = getModulesLocations(args[0].elements[1]);
82
63
  return;
83
64
  }
84
65
 
@@ -89,92 +70,78 @@ function parseBundle(bundlePath) {
89
70
  }
90
71
  );
91
72
 
92
- if (!walkState.locations) {
93
- return null;
73
+ let modules;
74
+
75
+ if (walkState.locations) {
76
+ modules = _.mapValues(walkState.locations,
77
+ loc => content.slice(loc.start, loc.end)
78
+ );
79
+ } else {
80
+ modules = {};
94
81
  }
95
82
 
96
83
  return {
97
84
  src: content,
98
- modules: _.mapValues(walkState.locations,
99
- loc => content.slice(loc.start, loc.end)
100
- )
85
+ modules
101
86
  };
102
87
  }
103
88
 
104
- function isArgumentContainsChunkIds(arg) {
105
- // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
106
- return (arg.type === 'ArrayExpression' && _.every(arg.elements, isModuleId));
89
+ function isModulesList(node) {
90
+ return (
91
+ isSimpleModulesList(node) ||
92
+ // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
93
+ isOptimizedModulesArray(node)
94
+ );
95
+ }
96
+
97
+ function isSimpleModulesList(node) {
98
+ return (
99
+ // Modules are contained in hash. Keys are module ids.
100
+ isModulesHash(node) ||
101
+ // Modules are contained in array. Indexes are module ids.
102
+ isModulesArray(node)
103
+ );
107
104
  }
108
105
 
109
- function isArgumentContainsModulesList(arg) {
110
- if (arg.type === 'ObjectExpression') {
111
- return _(arg.properties)
106
+ function isModulesHash(node) {
107
+ return (
108
+ node.type === 'ObjectExpression' &&
109
+ _(node.properties)
112
110
  .map('value')
113
- .every(isModuleWrapper);
114
- }
111
+ .every(isModuleWrapper)
112
+ );
113
+ }
115
114
 
116
- if (arg.type === 'ArrayExpression') {
117
- // Modules are contained in array.
118
- // Array indexes are module ids
119
- return _.every(arg.elements, elem =>
115
+ function isModulesArray(node) {
116
+ return (
117
+ node.type === 'ArrayExpression' &&
118
+ _.every(node.elements, elem =>
120
119
  // Some of array items may be skipped because there is no module with such id
121
120
  !elem ||
122
121
  isModuleWrapper(elem)
123
- );
124
- }
125
-
126
- return false;
127
- }
128
-
129
- function isArgumentContainingChunkIdsAndModulesList(arg) {
130
- if (
131
- arg.type === 'ArrayExpression' &&
132
- arg.elements.length >= 2 &&
133
- isArgumentContainsChunkIds(arg.elements[0]) &&
134
- isArgumentContainsModulesList(arg.elements[1])
135
- ) {
136
- return true;
137
- }
138
- return false;
122
+ )
123
+ );
139
124
  }
140
125
 
141
- function isArgumentArrayConcatContainingChunks(arg) {
142
- if (
143
- arg.type === 'CallExpression' &&
144
- arg.callee.type === 'MemberExpression' &&
126
+ function isOptimizedModulesArray(node) {
127
+ // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
128
+ // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
129
+ // The `<minimum ID>` + array indexes are module ids
130
+ return (
131
+ node.type === 'CallExpression' &&
132
+ node.callee.type === 'MemberExpression' &&
145
133
  // Make sure the object called is `Array(<some number>)`
146
- arg.callee.object.type === 'CallExpression' &&
147
- arg.callee.object.callee.type === 'Identifier' &&
148
- arg.callee.object.callee.name === 'Array' &&
149
- arg.callee.object.arguments.length === 1 &&
150
- isNumericId(arg.callee.object.arguments[0]) &&
134
+ node.callee.object.type === 'CallExpression' &&
135
+ node.callee.object.callee.type === 'Identifier' &&
136
+ node.callee.object.callee.name === 'Array' &&
137
+ node.callee.object.arguments.length === 1 &&
138
+ isNumericId(node.callee.object.arguments[0]) &&
151
139
  // Make sure the property X called for `Array(<some number>).X` is `concat`
152
- arg.callee.property.type === 'Identifier' &&
153
- arg.callee.property.name === 'concat' &&
140
+ node.callee.property.type === 'Identifier' &&
141
+ node.callee.property.name === 'concat' &&
154
142
  // Make sure exactly one array is passed in to `concat`
155
- arg.arguments.length === 1 &&
156
- arg.arguments[0].type === 'ArrayExpression'
157
- ) {
158
- // Modules are contained in `Array(<minimum ID>).concat(` array:
159
- // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
160
- // The `<minimum ID>` + array indexes are module ids
161
- return true;
162
- }
163
-
164
- return false;
165
- }
166
-
167
- function isAsyncChunkPushExpression(node) {
168
- const { callee } = node;
169
- return (
170
- callee.type === 'MemberExpression' &&
171
- callee.property.name === 'push' &&
172
- callee.object.type === 'AssignmentExpression' &&
173
- (
174
- callee.object.left.object.name === 'window' ||
175
- // Webpack 4 uses `this` instead of `window`
176
- callee.object.left.object.type === 'ThisExpression'
177
- )
143
+ node.arguments.length === 1 &&
144
+ isModulesArray(node.arguments[0])
178
145
  );
179
146
  }
180
147
 
@@ -197,9 +164,48 @@ function isNumericId(node) {
197
164
  return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0);
198
165
  }
199
166
 
200
- function getModulesLocationFromFunctionArgument(arg) {
201
- if (arg.type === 'ObjectExpression') {
202
- const modulesNodes = arg.properties;
167
+ function isChunkIds(node) {
168
+ // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
169
+ return (
170
+ node.type === 'ArrayExpression' &&
171
+ _.every(node.elements, isModuleId)
172
+ );
173
+ }
174
+
175
+ function isAsyncChunkPushExpression(node) {
176
+ const {
177
+ callee,
178
+ arguments: args
179
+ } = node;
180
+
181
+ return (
182
+ callee.type === 'MemberExpression' &&
183
+ callee.property.name === 'push' &&
184
+ callee.object.type === 'AssignmentExpression' &&
185
+ callee.object.left.object &&
186
+ (
187
+ callee.object.left.object.name === 'window' ||
188
+ // Webpack 4 uses `this` instead of `window`
189
+ callee.object.left.object.type === 'ThisExpression'
190
+ ) &&
191
+ args.length === 1 &&
192
+ args[0].type === 'ArrayExpression' &&
193
+ mayBeAsyncChunkArguments(args[0].elements) &&
194
+ isModulesList(args[0].elements[1])
195
+ );
196
+ }
197
+
198
+ function mayBeAsyncChunkArguments(args) {
199
+ return (
200
+ args.length >= 2 &&
201
+ isChunkIds(args[0])
202
+ );
203
+ }
204
+
205
+ function getModulesLocations(node) {
206
+ if (node.type === 'ObjectExpression') {
207
+ // Modules hash
208
+ const modulesNodes = node.properties;
203
209
 
204
210
  return _.transform(modulesNodes, (result, moduleNode) => {
205
211
  const moduleId = moduleNode.key.name || moduleNode.key.value;
@@ -208,35 +214,32 @@ function getModulesLocationFromFunctionArgument(arg) {
208
214
  }, {});
209
215
  }
210
216
 
211
- if (arg.type === 'ArrayExpression') {
212
- const modulesNodes = arg.elements;
217
+ const isOptimizedArray = (node.type === 'CallExpression');
218
+
219
+ if (node.type === 'ArrayExpression' || isOptimizedArray) {
220
+ // Modules array or optimized array
221
+ const minId = isOptimizedArray ?
222
+ // Get the [minId] value from the Array() call first argument literal value
223
+ node.callee.object.arguments[0].value :
224
+ // `0` for simple array
225
+ 0;
226
+ const modulesNodes = isOptimizedArray ?
227
+ // The modules reside in the `concat()` function call arguments
228
+ node.arguments[0].elements :
229
+ node.elements;
213
230
 
214
231
  return _.transform(modulesNodes, (result, moduleNode, i) => {
215
232
  if (!moduleNode) return;
216
-
217
- result[i] = getModuleLocation(moduleNode);
233
+ result[i + minId] = getModuleLocation(moduleNode);
218
234
  }, {});
219
235
  }
220
236
 
221
237
  return {};
222
238
  }
223
239
 
224
- function getModulesLocationFromArrayConcat(arg) {
225
- // arg(CallExpression) =
226
- // Array([minId]).concat([<minId module>, <minId+1 module>, ...])
227
- //
228
- // Get the [minId] value from the Array() call first argument literal value
229
- const minId = arg.callee.object.arguments[0].value;
230
- // The modules reside in the `concat()` function call arguments
231
- const modulesNodes = arg.arguments[0].elements;
232
-
233
- return _.transform(modulesNodes, (result, moduleNode, i) => {
234
- if (!moduleNode) return;
235
-
236
- result[i + minId] = getModuleLocation(moduleNode);
237
- }, {});
238
- }
239
-
240
240
  function getModuleLocation(node) {
241
- return _.pick(node, 'start', 'end');
241
+ return {
242
+ start: node.start,
243
+ end: node.end
244
+ };
242
245
  }
@@ -11,10 +11,7 @@ export default class BaseFolder extends Node {
11
11
 
12
12
  get src() {
13
13
  if (!_.has(this, '_src')) {
14
- this._src = this.walk((node, src, stop) => {
15
- if (node.src === undefined) return stop(undefined);
16
- return (src += node.src);
17
- }, '', false);
14
+ this._src = this.walk((node, src) => (src += node.src || ''), '', false);
18
15
  }
19
16
 
20
17
  return this._src;
@@ -9,12 +9,12 @@ import { getModulePathParts } from './utils';
9
9
  export default class Folder extends BaseFolder {
10
10
 
11
11
  get parsedSize() {
12
- return this.src ? this.src.length : undefined;
12
+ return this.src ? this.src.length : 0;
13
13
  }
14
14
 
15
15
  get gzipSize() {
16
16
  if (!_.has(this, '_gzipSize')) {
17
- this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
17
+ this._gzipSize = this.src ? gzipSize.sync(this.src) : 0;
18
18
  }
19
19
 
20
20
  return this._gzipSize;
package/src/utils.js ADDED
@@ -0,0 +1,34 @@
1
+ const { inspect } = require('util');
2
+ const _ = require('lodash');
3
+
4
+ exports.createAssetsFilter = createAssetsFilter;
5
+
6
+ function createAssetsFilter(excludePatterns) {
7
+ const excludeFunctions = _(excludePatterns)
8
+ .castArray()
9
+ .compact()
10
+ .map(pattern => {
11
+ if (typeof pattern === 'string') {
12
+ pattern = new RegExp(pattern);
13
+ }
14
+
15
+ if (_.isRegExp(pattern)) {
16
+ return (asset) => pattern.test(asset);
17
+ }
18
+
19
+ if (!_.isFunction(pattern)) {
20
+ throw new TypeError(
21
+ `Pattern should be either string, RegExp or a function, but "${inspect(pattern, { depth: 0 })}" got.`
22
+ );
23
+ }
24
+
25
+ return pattern;
26
+ })
27
+ .value();
28
+
29
+ if (excludeFunctions.length) {
30
+ return (asset) => _.every(excludeFunctions, fn => fn(asset) !== true);
31
+ } else {
32
+ return () => true;
33
+ }
34
+ }
package/src/viewer.js CHANGED
@@ -29,10 +29,13 @@ async function startServer(bundleStats, opts) {
29
29
  openBrowser = true,
30
30
  bundleDir = null,
31
31
  logger = new Logger(),
32
- defaultSizes = 'parsed'
32
+ defaultSizes = 'parsed',
33
+ excludeAssets = null
33
34
  } = opts || {};
34
35
 
35
- let chartData = getChartData(logger, bundleStats, bundleDir);
36
+ const analyzerOpts = { logger, excludeAssets };
37
+
38
+ let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
36
39
 
37
40
  if (!chartData) return;
38
41
 
@@ -90,7 +93,7 @@ async function startServer(bundleStats, opts) {
90
93
  };
91
94
 
92
95
  function updateChartData(bundleStats) {
93
- const newChartData = getChartData(logger, bundleStats, bundleDir);
96
+ const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
94
97
 
95
98
  if (!newChartData) return;
96
99
 
@@ -113,10 +116,11 @@ function generateReport(bundleStats, opts) {
113
116
  reportFilename = 'report.html',
114
117
  bundleDir = null,
115
118
  logger = new Logger(),
116
- defaultSizes = 'parsed'
119
+ defaultSizes = 'parsed',
120
+ excludeAssets = null
117
121
  } = opts || {};
118
122
 
119
- const chartData = getChartData(logger, bundleStats, bundleDir);
123
+ const chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir);
120
124
 
121
125
  if (!chartData) return;
122
126
 
@@ -151,18 +155,19 @@ function getAssetContent(filename) {
151
155
  return fs.readFileSync(`${projectRoot}/public/${filename}`, 'utf8');
152
156
  }
153
157
 
154
- function getChartData(logger, ...args) {
158
+ function getChartData(analyzerOpts, ...args) {
155
159
  let chartData;
160
+ const { logger } = analyzerOpts;
156
161
 
157
162
  try {
158
- chartData = analyzer.getViewerData(...args, { logger });
163
+ chartData = analyzer.getViewerData(...args, analyzerOpts);
159
164
  } catch (err) {
160
165
  logger.error(`Could't analyze webpack bundle:\n${err}`);
161
166
  logger.debug(err.stack);
162
167
  chartData = null;
163
168
  }
164
169
 
165
- if (_.isEmpty(chartData)) {
170
+ if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
166
171
  logger.error("Could't find any javascript bundles in provided stats file");
167
172
  chartData = null;
168
173
  }