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 +28 -0
- package/README.md +11 -11
- package/lib/BundleAnalyzerPlugin.js +6 -2
- package/lib/analyzer.js +22 -14
- package/lib/bin/analyzer.js +12 -1
- package/lib/parseUtils.js +93 -97
- package/lib/tree/BaseFolder.js +2 -3
- package/lib/tree/Folder.js +2 -2
- package/lib/utils.js +40 -0
- package/lib/viewer.js +16 -8
- package/package.json +1 -1
- package/public/viewer.js +1 -1
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +6 -2
- package/src/analyzer.js +19 -15
- package/src/bin/analyzer.js +17 -0
- package/src/parseUtils.js +132 -129
- package/src/tree/BaseFolder.js +1 -4
- package/src/tree/Folder.js +2 -2
- package/src/utils.js +34 -0
- package/src/viewer.js +13 -8
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
|
-
|
|
115
|
-
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|
package/lib/bin/analyzer.js
CHANGED
|
@@ -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.
|
|
28
|
+
if (state.locations) return;
|
|
29
29
|
|
|
30
30
|
var args = node.arguments;
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
// Modules are stored in
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
42
|
-
//
|
|
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
|
|
46
|
-
state.locations =
|
|
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
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
if (node
|
|
54
|
-
state.locations =
|
|
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
|
-
|
|
74
|
-
|
|
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
|
|
80
|
-
return content.slice(loc.start, loc.end);
|
|
81
|
-
})
|
|
76
|
+
modules
|
|
82
77
|
};
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
95
|
+
function isModulesHash(node) {
|
|
96
|
+
return node.type === 'ObjectExpression' && _(node.properties).map('value').every(isModuleWrapper);
|
|
107
97
|
}
|
|
108
98
|
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' &&
|
|
122
117
|
// Make sure exactly one array is passed in to `concat`
|
|
123
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
|
194
|
+
return {
|
|
195
|
+
start: node.start,
|
|
196
|
+
end: node.end
|
|
197
|
+
};
|
|
202
198
|
}
|
package/lib/tree/BaseFolder.js
CHANGED
|
@@ -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
|
|
115
|
-
|
|
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
|
|
package/lib/tree/Folder.js
CHANGED
|
@@ -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 :
|
|
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) :
|
|
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
|
|
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(
|
|
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(
|
|
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([
|
|
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.
|
|
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",
|