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.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,34 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
14
14
 
15
15
  <!-- Add changelog entries for new changes under this section -->
16
16
 
17
+ ## 2.13.1
18
+
19
+ * **Improvement**
20
+ * Pretty-format the generated stats.json ([#180](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/180)) [@edmorley](https://github.com/edmorley))
21
+
22
+ * **Bug Fix**
23
+ * Properly parse Webpack 4 async chunk with `Array.concat` optimization ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184), fixes [#183](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/183))
24
+
25
+ * **Internal**
26
+ * Refactor bundle parsing logic ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184))
27
+
28
+ ## 2.13.0
29
+
30
+ * **Improvement**
31
+ * Loosen bundle parsing logic ([#181](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/181)). Now analyzer will still show parsed sizes even if:
32
+ * It can't parse some bundle chunks. Those chunks just won't have content in the report. Fixes issues like [#160](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/160).
33
+ * Some bundle chunks are missing (it couldn't find files to parse). Those chunks just won't be visible in the report for parsed/gzipped sizes.
34
+
35
+ ## 2.12.0
36
+
37
+ * **New Feature**
38
+ * Add option that allows to exclude assets from the report ([#178](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/178))
39
+
40
+ ## 2.11.3
41
+
42
+ * **Bug Fix**
43
+ * Filter out modules that weren't found during bundles parsing ([#177](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/177))
44
+
17
45
  ## 2.11.2
18
46
 
19
47
  * **Bug Fix**
package/README.md CHANGED
@@ -62,6 +62,7 @@ new BundleAnalyzerPlugin(options?: object)
62
62
  |**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory|
63
63
  |**`statsFilename`**|`{String}`|Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. Relative to bundle output directory.|
64
64
  |**`statsOptions`**|`null` or `{Object}`|Default: `null`. Options for `stats.toJson()` method. For example you can exclude sources of your modules from stats file with `source: false` option. [See more options here](https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21). |
65
+ |**`excludeAssets`**|`{null\|pattern\|pattern[]}` where `pattern` equals to `{String\|RegExp\|function}`|Default: `null`. Patterns that will be used to match against asset names to exclude them from the report. If pattern is a string it will be converted to RegExp via `new RegExp(str)`. If pattern is a function it should have the following signature `(assetName: string) => boolean` and should return `true` to *exclude* matching asset. If multiple patterns are provided asset should match at least one of them to be excluded. |
65
66
  |**`logLevel`**|One of: `info`, `warn`, `error`, `silent`|Default: `info`. Used to control how much details the plugin outputs.|
66
67
 
67
68
  <h2 align="center">Usage (as a CLI utility)</h2>
@@ -106,22 +107,21 @@ Directory containing all generated bundles.
106
107
  ### `options`
107
108
 
108
109
  ```
109
- -h, --help output usage information
110
110
  -V, --version output the version number
111
111
  -m, --mode <mode> Analyzer mode. Should be `server` or `static`.
112
112
  In `server` mode analyzer will start HTTP server to show bundle report.
113
- In `static` mode single HTML file with bundle report will be generated.
114
- Default is `server`.
115
- -h, --host <host> Host that will be used in `server` mode to start HTTP server.
116
- Default is `127.0.0.1`.
117
- -p, --port <n> Port that will be used in `server` mode to start HTTP server.
118
- Default is 8888.
119
- -r, --report <file> Path to bundle report file that will be generated in `static` mode.
120
- Default is `report.html`.
113
+ In `static` mode single HTML file with bundle report will be generated. (default: server)
114
+ -h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
115
+ -p, --port <n> Port that will be used in `server` mode to start HTTP server. (default: 8888)
116
+ -r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
121
117
  -s, --default-sizes <type> Module sizes to show in treemap by default.
122
- Possible values: stat, parsed, gzip
123
- Default is `parsed`.
118
+ Possible values: stat, parsed, gzip (default: parsed)
124
119
  -O, --no-open Don't open report in default browser automatically.
120
+ -e, --exclude <regexp> Assets that should be excluded from the report.
121
+ Can be specified multiple times.
122
+ -l, --log-level <level> Log level.
123
+ Possible values: debug, info, warn, error, silent (default: info)
124
+ -h, --help output usage information
125
125
  ```
126
126
 
127
127
  <h2 align="center" id="size-definitions">Size definitions</h2>
@@ -30,6 +30,7 @@ var BundleAnalyzerPlugin = function () {
30
30
  generateStatsFile: false,
31
31
  statsFilename: 'stats.json',
32
32
  statsOptions: null,
33
+ excludeAssets: null,
33
34
  logLevel: 'info',
34
35
  // deprecated
35
36
  startAnalyzer: true
@@ -97,6 +98,7 @@ var BundleAnalyzerPlugin = function () {
97
98
 
98
99
  try {
99
100
  yield bfj.write(statsFilepath, stats, {
101
+ space: 2,
100
102
  promises: 'ignore',
101
103
  buffers: 'ignore',
102
104
  maps: 'ignore',
@@ -129,7 +131,8 @@ var BundleAnalyzerPlugin = function () {
129
131
  port: this.opts.analyzerPort,
130
132
  bundleDir: this.getBundleDirFromCompiler(),
131
133
  logger: this.logger,
132
- defaultSizes: this.opts.defaultSizes
134
+ defaultSizes: this.opts.defaultSizes,
135
+ excludeAssets: this.opts.excludeAssets
133
136
  });
134
137
  }
135
138
  });
@@ -148,7 +151,8 @@ var BundleAnalyzerPlugin = function () {
148
151
  reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
149
152
  bundleDir: this.getBundleDirFromCompiler(),
150
153
  logger: this.logger,
151
- defaultSizes: this.opts.defaultSizes
154
+ defaultSizes: this.opts.defaultSizes,
155
+ excludeAssets: this.opts.excludeAssets
152
156
  });
153
157
  }
154
158
  }, {
package/lib/analyzer.js CHANGED
@@ -12,6 +12,9 @@ var Folder = require('./tree/Folder').default;
12
12
  var _require = require('./parseUtils'),
13
13
  parseBundle = _require.parseBundle;
14
14
 
15
+ var _require2 = require('./utils'),
16
+ createAssetsFilter = _require2.createAssetsFilter;
17
+
15
18
  var FILENAME_QUERY_REGEXP = /\?.*$/;
16
19
 
17
20
  module.exports = {
@@ -22,11 +25,13 @@ module.exports = {
22
25
  function getViewerData(bundleStats, bundleDir, opts) {
23
26
  var _ref = opts || {},
24
27
  _ref$logger = _ref.logger,
25
- logger = _ref$logger === undefined ? new Logger() : _ref$logger;
26
-
27
- // Sometimes all the information is located in `children` array (e.g. problem in #10)
28
+ logger = _ref$logger === undefined ? new Logger() : _ref$logger,
29
+ _ref$excludeAssets = _ref.excludeAssets,
30
+ excludeAssets = _ref$excludeAssets === undefined ? null : _ref$excludeAssets;
28
31
 
32
+ var isAssetIncluded = createAssetsFilter(excludeAssets);
29
33
 
34
+ // Sometimes all the information is located in `children` array (e.g. problem in #10)
30
35
  if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
31
36
  bundleStats = bundleStats.children[0];
32
37
  }
@@ -37,7 +42,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
37
42
  // See #22
38
43
  asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
39
44
 
40
- return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks);
45
+ return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
41
46
  });
42
47
 
43
48
  // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
@@ -62,14 +67,9 @@ function getViewerData(bundleStats, bundleDir, opts) {
62
67
  try {
63
68
  bundleInfo = parseBundle(assetFile);
64
69
  } catch (err) {
65
- bundleInfo = null;
66
- }
67
-
68
- if (!bundleInfo) {
69
- logger.warn(`\nCouldn't parse bundle asset "${assetFile}".\n` + 'Analyzer will use module sizes from stats file.\n');
70
- parsedModules = null;
71
- bundlesSources = null;
72
- break;
70
+ var msg = err.code === 'ENOENT' ? 'no such file' : err.message;
71
+ logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
72
+ continue;
73
73
  }
74
74
 
75
75
  bundlesSources[statAsset.name] = bundleInfo.src;
@@ -89,13 +89,19 @@ function getViewerData(bundleStats, bundleDir, opts) {
89
89
  }
90
90
  }
91
91
  }
92
+
93
+ if (_.isEmpty(bundlesSources)) {
94
+ bundlesSources = null;
95
+ parsedModules = null;
96
+ logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
97
+ }
92
98
  }
93
99
 
94
100
  var modules = getBundleModules(bundleStats);
95
101
  var assets = _.transform(bundleStats.assets, function (result, statAsset) {
96
102
  var asset = result[statAsset.name] = _.pick(statAsset, 'size');
97
103
 
98
- if (bundlesSources) {
104
+ if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
99
105
  asset.parsedSize = bundlesSources[statAsset.name].length;
100
106
  asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
101
107
  }
@@ -118,7 +124,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
118
124
  // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
119
125
  // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
120
126
  // be the size of minified bundle.
121
- statSize: asset.tree.size,
127
+ // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
128
+ statSize: asset.tree.size || asset.size,
122
129
  parsedSize: asset.parsedSize,
123
130
  gzipSize: asset.gzipSize,
124
131
  groups: _.invokeMap(asset.tree.children, 'toChartData')
@@ -135,6 +142,7 @@ function getBundleModules(bundleStats) {
135
142
  }
136
143
 
137
144
  function assetHasModule(statAsset, statModule) {
145
+ // Checking if this module is the part of asset chunks
138
146
  return _.some(statModule.chunks, function (moduleChunk) {
139
147
  return _.includes(statAsset.chunks, moduleChunk);
140
148
  });
@@ -28,7 +28,7 @@ var program = commander.version(require('../../package.json').version).usage(`<b
28
28
  bundleStatsFile Path to Webpack Stats JSON file.
29
29
  bundleDir Directory containing all generated bundles.
30
30
  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` or `static`.' + 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.'), 'server').option('-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.', Number, 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.', 'report.html').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[].concat(_toConsumableArray(SIZES)).join(', ')}`), 'parsed').option('-O, --no-open', "Don't open report in default browser automatically.").option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[].concat(_toConsumableArray(Logger.levels)).join(', ')}`), Logger.defaultLevel).parse(process.argv);
31
+ By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server` or `static`.' + 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.'), 'server').option('-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.', Number, 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.', 'report.html').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[].concat(_toConsumableArray(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: ${[].concat(_toConsumableArray(Logger.levels)).join(', ')}`), Logger.defaultLevel).parse(process.argv);
32
32
 
33
33
  var mode = program.mode,
34
34
  host = program.host,
@@ -37,6 +37,7 @@ var mode = program.mode,
37
37
  defaultSizes = program.defaultSizes,
38
38
  logLevel = program.logLevel,
39
39
  openBrowser = program.open,
40
+ excludeAssets = program.exclude,
40
41
  _program$args = _slicedToArray(program.args, 2),
41
42
  bundleStatsFile = _program$args[0],
42
43
  bundleDir = _program$args[1];
@@ -69,6 +70,7 @@ if (mode === 'server') {
69
70
  host,
70
71
  defaultSizes,
71
72
  bundleDir,
73
+ excludeAssets,
72
74
  logger: new Logger(logLevel)
73
75
  });
74
76
  } else {
@@ -77,6 +79,7 @@ if (mode === 'server') {
77
79
  reportFilename: resolve(reportFilename),
78
80
  defaultSizes,
79
81
  bundleDir,
82
+ excludeAssets,
80
83
  logger: new Logger(logLevel)
81
84
  });
82
85
  }
@@ -89,4 +92,12 @@ function showHelp(error) {
89
92
 
90
93
  function br(str) {
91
94
  return `\n${_.repeat(' ', 28)}${str}`;
95
+ }
96
+
97
+ function array() {
98
+ var arr = [];
99
+ return function (val) {
100
+ arr.push(val);
101
+ return arr;
102
+ };
92
103
  }
package/lib/parseUtils.js CHANGED
@@ -25,40 +25,31 @@ function parseBundle(bundlePath) {
25
25
 
26
26
  walk.recursive(ast, walkState, {
27
27
  CallExpression(node, state, c) {
28
- if (state.sizes) return;
28
+ if (state.locations) return;
29
29
 
30
30
  var args = node.arguments;
31
31
 
32
- // Additional bundle without webpack loader.
33
- // Modules are stored in second argument, after chunk ids:
34
- // webpackJsonp([<chunks>], <modules>, ...)
35
- // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
36
- if (node.callee.type === 'Identifier' && args.length >= 2 && isArgumentContainsChunkIds(args[0]) && isArgumentContainsModulesList(args[1])) {
37
- state.locations = getModulesLocationFromFunctionArgument(args[1]);
32
+ // Main chunk with webpack loader.
33
+ // Modules are stored in first argument:
34
+ // (function (...) {...})(<modules>)
35
+ if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
36
+ state.locations = getModulesLocations(args[0]);
38
37
  return;
39
38
  }
40
39
 
41
- // Additional bundle without webpack loader, with module IDs optimized.
42
- // Modules are stored in second arguments Array(n).concat() call
43
- // webpackJsonp([<chunks>], Array([minimum ID]).concat([<module>, <module>, ...]))
40
+ // Async Webpack < v4 chunk without webpack loader.
41
+ // webpackJsonp([<chunks>], <modules>, ...)
44
42
  // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
45
- if (node.callee.type === 'Identifier' && (args.length === 2 || args.length === 3) && isArgumentContainsChunkIds(args[0]) && isArgumentArrayConcatContainingChunks(args[1])) {
46
- state.locations = getModulesLocationFromArrayConcat(args[1]);
43
+ if (node.callee.type === 'Identifier' && mayBeAsyncChunkArguments(args) && isModulesList(args[1])) {
44
+ state.locations = getModulesLocations(args[1]);
47
45
  return;
48
46
  }
49
47
 
50
- // Main bundle with webpack loader
51
- // Modules are stored in first argument:
52
- // (function (...) {...})(<modules>)
53
- if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isArgumentContainsModulesList(args[0])) {
54
- state.locations = getModulesLocationFromFunctionArgument(args[0]);
55
- return;
56
- }
57
-
58
- // Additional bundles with webpack 4 are loaded with:
59
- // (window.webpackJsonp=window.webpackJsonp||[]).push([[chunkId], [<module>, <module>], [[optional_entries]]]);
60
- if (isAsyncChunkPushExpression(node) && args.length === 1 && isArgumentContainingChunkIdsAndModulesList(args[0])) {
61
- state.locations = getModulesLocationFromFunctionArgument(args[0].elements[1]);
48
+ // Async Webpack v4 chunk without webpack loader.
49
+ // (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
50
+ // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
51
+ if (isAsyncChunkPushExpression(node)) {
52
+ state.locations = getModulesLocations(args[0].elements[1]);
62
53
  return;
63
54
  }
64
55
 
@@ -70,72 +61,61 @@ function parseBundle(bundlePath) {
70
61
  }
71
62
  });
72
63
 
73
- if (!walkState.locations) {
74
- return null;
64
+ var modules = void 0;
65
+
66
+ if (walkState.locations) {
67
+ modules = _.mapValues(walkState.locations, function (loc) {
68
+ return content.slice(loc.start, loc.end);
69
+ });
70
+ } else {
71
+ modules = {};
75
72
  }
76
73
 
77
74
  return {
78
75
  src: content,
79
- modules: _.mapValues(walkState.locations, function (loc) {
80
- return content.slice(loc.start, loc.end);
81
- })
76
+ modules
82
77
  };
83
78
  }
84
79
 
85
- function isArgumentContainsChunkIds(arg) {
86
- // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
87
- return arg.type === 'ArrayExpression' && _.every(arg.elements, isModuleId);
80
+ function isModulesList(node) {
81
+ return isSimpleModulesList(node) ||
82
+ // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
83
+ isOptimizedModulesArray(node);
88
84
  }
89
85
 
90
- function isArgumentContainsModulesList(arg) {
91
- if (arg.type === 'ObjectExpression') {
92
- return _(arg.properties).map('value').every(isModuleWrapper);
93
- }
94
-
95
- if (arg.type === 'ArrayExpression') {
96
- // Modules are contained in array.
97
- // Array indexes are module ids
98
- return _.every(arg.elements, function (elem) {
99
- return (
100
- // Some of array items may be skipped because there is no module with such id
101
- !elem || isModuleWrapper(elem)
102
- );
103
- });
104
- }
86
+ function isSimpleModulesList(node) {
87
+ return (
88
+ // Modules are contained in hash. Keys are module ids.
89
+ isModulesHash(node) ||
90
+ // Modules are contained in array. Indexes are module ids.
91
+ isModulesArray(node)
92
+ );
93
+ }
105
94
 
106
- return false;
95
+ function isModulesHash(node) {
96
+ return node.type === 'ObjectExpression' && _(node.properties).map('value').every(isModuleWrapper);
107
97
  }
108
98
 
109
- function isArgumentContainingChunkIdsAndModulesList(arg) {
110
- if (arg.type === 'ArrayExpression' && arg.elements.length >= 2 && isArgumentContainsChunkIds(arg.elements[0]) && isArgumentContainsModulesList(arg.elements[1])) {
111
- return true;
112
- }
113
- return false;
99
+ function isModulesArray(node) {
100
+ return node.type === 'ArrayExpression' && _.every(node.elements, function (elem) {
101
+ return (
102
+ // Some of array items may be skipped because there is no module with such id
103
+ !elem || isModuleWrapper(elem)
104
+ );
105
+ });
114
106
  }
115
107
 
116
- function isArgumentArrayConcatContainingChunks(arg) {
117
- if (arg.type === 'CallExpression' && arg.callee.type === 'MemberExpression' &&
108
+ function isOptimizedModulesArray(node) {
109
+ // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
110
+ // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
111
+ // The `<minimum ID>` + array indexes are module ids
112
+ return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
118
113
  // Make sure the object called is `Array(<some number>)`
119
- arg.callee.object.type === 'CallExpression' && arg.callee.object.callee.type === 'Identifier' && arg.callee.object.callee.name === 'Array' && arg.callee.object.arguments.length === 1 && isNumericId(arg.callee.object.arguments[0]) &&
114
+ 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]) &&
120
115
  // Make sure the property X called for `Array(<some number>).X` is `concat`
121
- arg.callee.property.type === 'Identifier' && arg.callee.property.name === 'concat' &&
116
+ node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' &&
122
117
  // Make sure exactly one array is passed in to `concat`
123
- arg.arguments.length === 1 && arg.arguments[0].type === 'ArrayExpression') {
124
- // Modules are contained in `Array(<minimum ID>).concat(` array:
125
- // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
126
- // The `<minimum ID>` + array indexes are module ids
127
- return true;
128
- }
129
-
130
- return false;
131
- }
132
-
133
- function isAsyncChunkPushExpression(node) {
134
- var callee = node.callee;
135
-
136
- return callee.type === 'MemberExpression' && callee.property.name === 'push' && callee.object.type === 'AssignmentExpression' && (callee.object.left.object.name === 'window' ||
137
- // Webpack 4 uses `this` instead of `window`
138
- callee.object.left.object.type === 'ThisExpression');
118
+ node.arguments.length === 1 && isModulesArray(node.arguments[0]);
139
119
  }
140
120
 
141
121
  function isModuleWrapper(node) {
@@ -157,9 +137,29 @@ function isNumericId(node) {
157
137
  return node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0;
158
138
  }
159
139
 
160
- function getModulesLocationFromFunctionArgument(arg) {
161
- if (arg.type === 'ObjectExpression') {
162
- var modulesNodes = arg.properties;
140
+ function isChunkIds(node) {
141
+ // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
142
+ return node.type === 'ArrayExpression' && _.every(node.elements, isModuleId);
143
+ }
144
+
145
+ function isAsyncChunkPushExpression(node) {
146
+ var callee = node.callee,
147
+ args = node.arguments;
148
+
149
+
150
+ return callee.type === 'MemberExpression' && callee.property.name === 'push' && callee.object.type === 'AssignmentExpression' && callee.object.left.object && (callee.object.left.object.name === 'window' ||
151
+ // Webpack 4 uses `this` instead of `window`
152
+ callee.object.left.object.type === 'ThisExpression') && args.length === 1 && args[0].type === 'ArrayExpression' && mayBeAsyncChunkArguments(args[0].elements) && isModulesList(args[0].elements[1]);
153
+ }
154
+
155
+ function mayBeAsyncChunkArguments(args) {
156
+ return args.length >= 2 && isChunkIds(args[0]);
157
+ }
158
+
159
+ function getModulesLocations(node) {
160
+ if (node.type === 'ObjectExpression') {
161
+ // Modules hash
162
+ var modulesNodes = node.properties;
163
163
 
164
164
  return _.transform(modulesNodes, function (result, moduleNode) {
165
165
  var moduleId = moduleNode.key.name || moduleNode.key.value;
@@ -168,35 +168,31 @@ function getModulesLocationFromFunctionArgument(arg) {
168
168
  }, {});
169
169
  }
170
170
 
171
- if (arg.type === 'ArrayExpression') {
172
- var _modulesNodes = arg.elements;
171
+ var isOptimizedArray = node.type === 'CallExpression';
172
+
173
+ if (node.type === 'ArrayExpression' || isOptimizedArray) {
174
+ // Modules array or optimized array
175
+ var minId = isOptimizedArray ?
176
+ // Get the [minId] value from the Array() call first argument literal value
177
+ node.callee.object.arguments[0].value :
178
+ // `0` for simple array
179
+ 0;
180
+ var _modulesNodes = isOptimizedArray ?
181
+ // The modules reside in the `concat()` function call arguments
182
+ node.arguments[0].elements : node.elements;
173
183
 
174
184
  return _.transform(_modulesNodes, function (result, moduleNode, i) {
175
185
  if (!moduleNode) return;
176
-
177
- result[i] = getModuleLocation(moduleNode);
186
+ result[i + minId] = getModuleLocation(moduleNode);
178
187
  }, {});
179
188
  }
180
189
 
181
190
  return {};
182
191
  }
183
192
 
184
- function getModulesLocationFromArrayConcat(arg) {
185
- // arg(CallExpression) =
186
- // Array([minId]).concat([<minId module>, <minId+1 module>, ...])
187
- //
188
- // Get the [minId] value from the Array() call first argument literal value
189
- var minId = arg.callee.object.arguments[0].value;
190
- // The modules reside in the `concat()` function call arguments
191
- var modulesNodes = arg.arguments[0].elements;
192
-
193
- return _.transform(modulesNodes, function (result, moduleNode, i) {
194
- if (!moduleNode) return;
195
-
196
- result[i + minId] = getModuleLocation(moduleNode);
197
- }, {});
198
- }
199
-
200
193
  function getModuleLocation(node) {
201
- return _.pick(node, 'start', 'end');
194
+ return {
195
+ start: node.start,
196
+ end: node.end
197
+ };
202
198
  }
@@ -111,9 +111,8 @@ var BaseFolder = function (_Node) {
111
111
  key: 'src',
112
112
  get: function get() {
113
113
  if (!_lodash2.default.has(this, '_src')) {
114
- this._src = this.walk(function (node, src, stop) {
115
- if (node.src === undefined) return stop(undefined);
116
- return src += node.src;
114
+ this._src = this.walk(function (node, src) {
115
+ return src += node.src || '';
117
116
  }, '', false);
118
117
  }
119
118
 
@@ -94,13 +94,13 @@ var Folder = function (_BaseFolder) {
94
94
  }, {
95
95
  key: 'parsedSize',
96
96
  get: function get() {
97
- return this.src ? this.src.length : undefined;
97
+ return this.src ? this.src.length : 0;
98
98
  }
99
99
  }, {
100
100
  key: 'gzipSize',
101
101
  get: function get() {
102
102
  if (!_lodash2.default.has(this, '_gzipSize')) {
103
- this._gzipSize = this.src ? _gzipSize2.default.sync(this.src) : undefined;
103
+ this._gzipSize = this.src ? _gzipSize2.default.sync(this.src) : 0;
104
104
  }
105
105
 
106
106
  return this._gzipSize;
package/lib/utils.js ADDED
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var _require = require('util'),
4
+ inspect = _require.inspect;
5
+
6
+ var _ = require('lodash');
7
+
8
+ exports.createAssetsFilter = createAssetsFilter;
9
+
10
+ function createAssetsFilter(excludePatterns) {
11
+ var excludeFunctions = _(excludePatterns).castArray().compact().map(function (pattern) {
12
+ if (typeof pattern === 'string') {
13
+ pattern = new RegExp(pattern);
14
+ }
15
+
16
+ if (_.isRegExp(pattern)) {
17
+ return function (asset) {
18
+ return pattern.test(asset);
19
+ };
20
+ }
21
+
22
+ if (!_.isFunction(pattern)) {
23
+ throw new TypeError(`Pattern should be either string, RegExp or a function, but "${inspect(pattern, { depth: 0 })}" got.`);
24
+ }
25
+
26
+ return pattern;
27
+ }).value();
28
+
29
+ if (excludeFunctions.length) {
30
+ return function (asset) {
31
+ return _.every(excludeFunctions, function (fn) {
32
+ return fn(asset) !== true;
33
+ });
34
+ };
35
+ } else {
36
+ return function () {
37
+ return true;
38
+ };
39
+ }
40
+ }
package/lib/viewer.js CHANGED
@@ -14,9 +14,13 @@ var startServer = function () {
14
14
  _ref2$logger = _ref2.logger,
15
15
  logger = _ref2$logger === undefined ? new Logger() : _ref2$logger,
16
16
  _ref2$defaultSizes = _ref2.defaultSizes,
17
- defaultSizes = _ref2$defaultSizes === undefined ? 'parsed' : _ref2$defaultSizes;
17
+ defaultSizes = _ref2$defaultSizes === undefined ? 'parsed' : _ref2$defaultSizes,
18
+ _ref2$excludeAssets = _ref2.excludeAssets,
19
+ excludeAssets = _ref2$excludeAssets === undefined ? null : _ref2$excludeAssets;
18
20
 
19
- var chartData = getChartData(logger, bundleStats, bundleDir);
21
+ var analyzerOpts = { logger, excludeAssets };
22
+
23
+ var chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
20
24
 
21
25
  if (!chartData) return;
22
26
 
@@ -73,7 +77,7 @@ var startServer = function () {
73
77
  };
74
78
 
75
79
  function updateChartData(bundleStats) {
76
- var newChartData = getChartData(logger, bundleStats, bundleDir);
80
+ var newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
77
81
 
78
82
  if (!newChartData) return;
79
83
 
@@ -134,9 +138,11 @@ function generateReport(bundleStats, opts) {
134
138
  _ref3$logger = _ref3.logger,
135
139
  logger = _ref3$logger === undefined ? new Logger() : _ref3$logger,
136
140
  _ref3$defaultSizes = _ref3.defaultSizes,
137
- defaultSizes = _ref3$defaultSizes === undefined ? 'parsed' : _ref3$defaultSizes;
141
+ defaultSizes = _ref3$defaultSizes === undefined ? 'parsed' : _ref3$defaultSizes,
142
+ _ref3$excludeAssets = _ref3.excludeAssets,
143
+ excludeAssets = _ref3$excludeAssets === undefined ? null : _ref3$excludeAssets;
138
144
 
139
- var chartData = getChartData(logger, bundleStats, bundleDir);
145
+ var chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir);
140
146
 
141
147
  if (!chartData) return;
142
148
 
@@ -165,22 +171,24 @@ function getAssetContent(filename) {
165
171
  return fs.readFileSync(`${projectRoot}/public/${filename}`, 'utf8');
166
172
  }
167
173
 
168
- function getChartData(logger) {
174
+ function getChartData(analyzerOpts) {
169
175
  var chartData = void 0;
176
+ var logger = analyzerOpts.logger;
177
+
170
178
 
171
179
  try {
172
180
  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
173
181
  args[_key - 1] = arguments[_key];
174
182
  }
175
183
 
176
- chartData = analyzer.getViewerData.apply(analyzer, args.concat([{ logger }]));
184
+ chartData = analyzer.getViewerData.apply(analyzer, args.concat([analyzerOpts]));
177
185
  } catch (err) {
178
186
  logger.error(`Could't analyze webpack bundle:\n${err}`);
179
187
  logger.debug(err.stack);
180
188
  chartData = null;
181
189
  }
182
190
 
183
- if (_.isEmpty(chartData)) {
191
+ if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
184
192
  logger.error("Could't find any javascript bundles in provided stats file");
185
193
  chartData = null;
186
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack-bundle-analyzer",
3
- "version": "2.11.2",
3
+ "version": "2.13.1",
4
4
  "description": "Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap",
5
5
  "author": "Yury Grunin <grunin.ya@ya.ru>",
6
6
  "license": "MIT",