webpack-bundle-analyzer 3.9.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/lib/BundleAnalyzerPlugin.js +55 -85
- package/lib/analyzer.js +78 -35
- package/lib/bin/analyzer.js +1 -3
- package/lib/parseUtils.js +83 -10
- package/lib/statsUtils.js +90 -0
- package/lib/template.js +73 -0
- package/lib/tree/BaseFolder.js +1 -3
- package/lib/tree/ConcatenatedModule.js +4 -12
- package/lib/tree/ContentFolder.js +2 -8
- package/lib/tree/ContentModule.js +2 -8
- package/lib/tree/Folder.js +3 -11
- package/lib/utils.js +19 -4
- package/lib/viewer.js +121 -184
- package/package.json +47 -49
- package/public/viewer.js +4 -14
- package/public/viewer.js.LICENSE.txt +5 -0
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +4 -11
- package/src/analyzer.js +76 -31
- package/src/bin/analyzer.js +1 -2
- package/src/parseUtils.js +96 -11
- package/src/statsUtils.js +82 -0
- package/{views/viewer.ejs → src/template.js} +50 -7
- package/src/tree/BaseFolder.js +1 -1
- package/src/tree/ConcatenatedModule.js +2 -2
- package/src/tree/Folder.js +1 -1
- package/src/utils.js +16 -4
- package/src/viewer.js +27 -73
- package/views/script.ejs +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,35 @@ _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
|
+
## 4.2.0
|
|
18
|
+
|
|
19
|
+
* **Improvement**
|
|
20
|
+
* A number of improvements to reduce the number of dependencies ([#391](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/391), [#396](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/396), [#397](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/397))
|
|
21
|
+
|
|
22
|
+
* **Bug Fix**
|
|
23
|
+
* Prevent crashes for bundles generated from webpack array configs. ([#394](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/394) by [@ctavan](https://github.com/ctavan))
|
|
24
|
+
* Fix `non-asset` assets causing analyze failure. ([#385](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/385) by [@ZKHelloworld](https://github.com/ZKHelloworld))
|
|
25
|
+
|
|
26
|
+
## 4.1.0
|
|
27
|
+
|
|
28
|
+
* **Improvement**
|
|
29
|
+
* Significantly speed up generation of `stats.json` file (see `generateStatsFile` option).
|
|
30
|
+
|
|
31
|
+
## 4.0.0
|
|
32
|
+
|
|
33
|
+
* **Breaking change**
|
|
34
|
+
* Dropped support for Node.js 6 and 8. Minimal required version now is v10.13.0
|
|
35
|
+
|
|
36
|
+
* **Improvement**
|
|
37
|
+
* Support for Webpack 5
|
|
38
|
+
|
|
39
|
+
* **Bug Fix**
|
|
40
|
+
* Prevent crashes when `openAnalyzer` was set to true in environments where there's no program to handle opening. ([#382](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/382) by [@wbobeirne](https://github.com/wbobeirne))
|
|
41
|
+
|
|
42
|
+
* **Internal**
|
|
43
|
+
* Updated dependencies
|
|
44
|
+
* Added support for multiple Webpack versions in tests
|
|
45
|
+
|
|
17
46
|
## 3.9.0
|
|
18
47
|
|
|
19
48
|
* **New Feature**
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
6
|
-
|
|
7
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
8
|
-
|
|
9
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
10
|
-
|
|
11
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
12
|
-
|
|
13
|
-
const bfj = require('bfj');
|
|
3
|
+
const fs = require('fs');
|
|
14
4
|
|
|
15
5
|
const path = require('path');
|
|
16
6
|
|
|
17
|
-
const mkdir = require('mkdirp');
|
|
18
|
-
|
|
19
7
|
const {
|
|
20
8
|
bold
|
|
21
9
|
} = require('chalk');
|
|
@@ -26,9 +14,13 @@ const viewer = require('./viewer');
|
|
|
26
14
|
|
|
27
15
|
const utils = require('./utils');
|
|
28
16
|
|
|
17
|
+
const {
|
|
18
|
+
writeStats
|
|
19
|
+
} = require('./statsUtils');
|
|
20
|
+
|
|
29
21
|
class BundleAnalyzerPlugin {
|
|
30
22
|
constructor(opts = {}) {
|
|
31
|
-
this.opts =
|
|
23
|
+
this.opts = {
|
|
32
24
|
analyzerMode: 'server',
|
|
33
25
|
analyzerHost: '127.0.0.1',
|
|
34
26
|
reportFilename: null,
|
|
@@ -41,10 +33,10 @@ class BundleAnalyzerPlugin {
|
|
|
41
33
|
excludeAssets: null,
|
|
42
34
|
logLevel: 'info',
|
|
43
35
|
// deprecated
|
|
44
|
-
startAnalyzer: true
|
|
45
|
-
|
|
36
|
+
startAnalyzer: true,
|
|
37
|
+
...opts,
|
|
46
38
|
analyzerPort: 'analyzerPort' in opts ? opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort : 8888
|
|
47
|
-
}
|
|
39
|
+
};
|
|
48
40
|
this.server = null;
|
|
49
41
|
this.logger = new Logger(this.opts.logLevel);
|
|
50
42
|
}
|
|
@@ -76,14 +68,14 @@ class BundleAnalyzerPlugin {
|
|
|
76
68
|
|
|
77
69
|
if (actions.length) {
|
|
78
70
|
// Making analyzer logs to be after all webpack logs in the console
|
|
79
|
-
setImmediate(
|
|
71
|
+
setImmediate(async () => {
|
|
80
72
|
try {
|
|
81
|
-
|
|
73
|
+
await Promise.all(actions.map(action => action()));
|
|
82
74
|
callback();
|
|
83
75
|
} catch (e) {
|
|
84
76
|
callback(e);
|
|
85
77
|
}
|
|
86
|
-
})
|
|
78
|
+
});
|
|
87
79
|
} else {
|
|
88
80
|
callback();
|
|
89
81
|
}
|
|
@@ -96,78 +88,56 @@ class BundleAnalyzerPlugin {
|
|
|
96
88
|
}
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
generateStatsFile(stats) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
mkdir.sync(path.dirname(statsFilepath));
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
yield bfj.write(statsFilepath, stats, {
|
|
108
|
-
space: 2,
|
|
109
|
-
promises: 'ignore',
|
|
110
|
-
buffers: 'ignore',
|
|
111
|
-
maps: 'ignore',
|
|
112
|
-
iterables: 'ignore',
|
|
113
|
-
circular: 'ignore'
|
|
114
|
-
});
|
|
91
|
+
async generateStatsFile(stats) {
|
|
92
|
+
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
|
|
93
|
+
await fs.promises.mkdir(path.dirname(statsFilepath), {
|
|
94
|
+
recursive: true
|
|
95
|
+
});
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
97
|
+
try {
|
|
98
|
+
await writeStats(stats, statsFilepath);
|
|
99
|
+
this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
|
|
102
|
+
}
|
|
121
103
|
}
|
|
122
104
|
|
|
123
|
-
startAnalyzerServer(stats) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
excludeAssets: _this2.opts.excludeAssets
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
})();
|
|
105
|
+
async startAnalyzerServer(stats) {
|
|
106
|
+
if (this.server) {
|
|
107
|
+
(await this.server).updateChartData(stats);
|
|
108
|
+
} else {
|
|
109
|
+
this.server = viewer.startServer(stats, {
|
|
110
|
+
openBrowser: this.opts.openAnalyzer,
|
|
111
|
+
host: this.opts.analyzerHost,
|
|
112
|
+
port: this.opts.analyzerPort,
|
|
113
|
+
reportTitle: this.opts.reportTitle,
|
|
114
|
+
bundleDir: this.getBundleDirFromCompiler(),
|
|
115
|
+
logger: this.logger,
|
|
116
|
+
defaultSizes: this.opts.defaultSizes,
|
|
117
|
+
excludeAssets: this.opts.excludeAssets
|
|
118
|
+
});
|
|
119
|
+
}
|
|
142
120
|
}
|
|
143
121
|
|
|
144
|
-
generateJSONReport(stats) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
logger: _this3.logger,
|
|
152
|
-
excludeAssets: _this3.opts.excludeAssets
|
|
153
|
-
});
|
|
154
|
-
})();
|
|
122
|
+
async generateJSONReport(stats) {
|
|
123
|
+
await viewer.generateJSONReport(stats, {
|
|
124
|
+
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
|
|
125
|
+
bundleDir: this.getBundleDirFromCompiler(),
|
|
126
|
+
logger: this.logger,
|
|
127
|
+
excludeAssets: this.opts.excludeAssets
|
|
128
|
+
});
|
|
155
129
|
}
|
|
156
130
|
|
|
157
|
-
generateStaticReport(stats) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
defaultSizes: _this4.opts.defaultSizes,
|
|
168
|
-
excludeAssets: _this4.opts.excludeAssets
|
|
169
|
-
});
|
|
170
|
-
})();
|
|
131
|
+
async generateStaticReport(stats) {
|
|
132
|
+
await viewer.generateReport(stats, {
|
|
133
|
+
openBrowser: this.opts.openAnalyzer,
|
|
134
|
+
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
|
|
135
|
+
reportTitle: this.opts.reportTitle,
|
|
136
|
+
bundleDir: this.getBundleDirFromCompiler(),
|
|
137
|
+
logger: this.logger,
|
|
138
|
+
defaultSizes: this.opts.defaultSizes,
|
|
139
|
+
excludeAssets: this.opts.excludeAssets
|
|
140
|
+
});
|
|
171
141
|
}
|
|
172
142
|
|
|
173
143
|
getBundleDirFromCompiler() {
|
package/lib/analyzer.js
CHANGED
|
@@ -42,7 +42,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
42
42
|
// leave the 1st one as that is considered the 'root' asset.
|
|
43
43
|
|
|
44
44
|
for (let i = 1; i < children.length; i++) {
|
|
45
|
-
|
|
45
|
+
children[i].assets.forEach(asset => {
|
|
46
46
|
asset.isChild = true;
|
|
47
47
|
bundleStats.assets.push(asset);
|
|
48
48
|
});
|
|
@@ -58,9 +58,14 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
58
58
|
} // Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
bundleStats.assets =
|
|
62
|
-
//
|
|
61
|
+
bundleStats.assets = bundleStats.assets.filter(asset => {
|
|
62
|
+
// Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
|
|
63
|
+
if (asset.type && asset.type !== 'asset') {
|
|
64
|
+
return false;
|
|
65
|
+
} // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
|
|
63
66
|
// See #22
|
|
67
|
+
|
|
68
|
+
|
|
64
69
|
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
|
|
65
70
|
return FILENAME_EXTENSIONS.test(asset.name) && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
|
|
66
71
|
}); // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
|
|
@@ -84,9 +89,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
84
89
|
continue;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
bundlesSources[statAsset.name] = bundleInfo
|
|
88
|
-
|
|
89
|
-
_.assign(parsedModules, bundleInfo.modules);
|
|
92
|
+
bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
|
|
93
|
+
Object.assign(parsedModules, bundleInfo.modules);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
if (_.isEmpty(bundlesSources)) {
|
|
@@ -96,41 +100,73 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
const assets =
|
|
103
|
+
const assets = bundleStats.assets.reduce((result, statAsset) => {
|
|
100
104
|
// If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
|
|
101
105
|
const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
|
|
102
106
|
const modules = assetBundles ? getBundleModules(assetBundles) : [];
|
|
103
107
|
|
|
104
108
|
const asset = result[statAsset.name] = _.pick(statAsset, 'size');
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
const assetSources = bundlesSources && _.has(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null;
|
|
111
|
+
|
|
112
|
+
if (assetSources) {
|
|
113
|
+
asset.parsedSize = Buffer.byteLength(assetSources.src);
|
|
114
|
+
asset.gzipSize = gzipSize.sync(assetSources.src);
|
|
109
115
|
} // Picking modules from current bundle script
|
|
110
116
|
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
const assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule)); // Adding parsed sources
|
|
119
|
+
|
|
120
|
+
if (parsedModules) {
|
|
121
|
+
const unparsedEntryModules = [];
|
|
122
|
+
|
|
123
|
+
for (const statModule of assetModules) {
|
|
124
|
+
if (parsedModules[statModule.id]) {
|
|
125
|
+
statModule.parsedSrc = parsedModules[statModule.id];
|
|
126
|
+
} else if (isEntryModule(statModule)) {
|
|
127
|
+
unparsedEntryModules.push(statModule);
|
|
128
|
+
}
|
|
129
|
+
} // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
|
|
130
|
+
// Because of this they basically become a concatenated module, for which we can't even precisely determine its
|
|
131
|
+
// parsed source as it's located in the same scope as all Webpack runtime helpers.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if (unparsedEntryModules.length && assetSources) {
|
|
135
|
+
if (unparsedEntryModules.length === 1) {
|
|
136
|
+
// So if there is only one entry we consider its parsed source to be all the bundle code excluding code
|
|
137
|
+
// from parsed modules.
|
|
138
|
+
unparsedEntryModules[0].parsedSrc = assetSources.runtimeSrc;
|
|
139
|
+
} else {
|
|
140
|
+
// If there are multiple entry points we move all of them under synthetic concatenated module.
|
|
141
|
+
_.pullAll(assetModules, unparsedEntryModules);
|
|
142
|
+
|
|
143
|
+
assetModules.unshift({
|
|
144
|
+
identifier: './entry modules',
|
|
145
|
+
name: './entry modules',
|
|
146
|
+
modules: unparsedEntryModules,
|
|
147
|
+
size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
|
|
148
|
+
parsedSrc: assetSources.runtimeSrc
|
|
149
|
+
});
|
|
150
|
+
}
|
|
115
151
|
}
|
|
116
|
-
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
asset.modules = assetModules;
|
|
117
155
|
asset.tree = createModulesTree(asset.modules);
|
|
156
|
+
return result;
|
|
118
157
|
}, {});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
|
132
|
-
});
|
|
133
|
-
}, []);
|
|
158
|
+
return Object.entries(assets).map(([filename, asset]) => ({
|
|
159
|
+
label: filename,
|
|
160
|
+
isAsset: true,
|
|
161
|
+
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
|
|
162
|
+
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
|
|
163
|
+
// be the size of minified bundle.
|
|
164
|
+
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
|
|
165
|
+
statSize: asset.tree.size || asset.size,
|
|
166
|
+
parsedSize: asset.parsedSize,
|
|
167
|
+
gzipSize: asset.gzipSize,
|
|
168
|
+
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
|
169
|
+
}));
|
|
134
170
|
}
|
|
135
171
|
|
|
136
172
|
function readStatsFromFile(filename) {
|
|
@@ -138,23 +174,30 @@ function readStatsFromFile(filename) {
|
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
function getChildAssetBundles(bundleStats, assetName) {
|
|
141
|
-
return
|
|
177
|
+
return (bundleStats.children || []).find(c => _(c.assetsByChunkName).values().flatten().includes(assetName));
|
|
142
178
|
}
|
|
143
179
|
|
|
144
180
|
function getBundleModules(bundleStats) {
|
|
145
|
-
return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id')
|
|
181
|
+
return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id') // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
|
|
182
|
+
.reject(isRuntimeModule).value();
|
|
146
183
|
}
|
|
147
184
|
|
|
148
185
|
function assetHasModule(statAsset, statModule) {
|
|
149
186
|
// Checking if this module is the part of asset chunks
|
|
150
|
-
return
|
|
187
|
+
return statModule.chunks.some(moduleChunk => statAsset.chunks.includes(moduleChunk));
|
|
151
188
|
}
|
|
152
189
|
|
|
153
|
-
function
|
|
154
|
-
|
|
190
|
+
function isEntryModule(statModule) {
|
|
191
|
+
return statModule.depth === 0;
|
|
192
|
+
}
|
|
155
193
|
|
|
156
|
-
|
|
194
|
+
function isRuntimeModule(statModule) {
|
|
195
|
+
return statModule.moduleType === 'runtime';
|
|
196
|
+
}
|
|
157
197
|
|
|
198
|
+
function createModulesTree(modules) {
|
|
199
|
+
const root = new Folder('.');
|
|
200
|
+
modules.forEach(module => root.addModule(module));
|
|
158
201
|
root.mergeNestedFolders();
|
|
159
202
|
return root;
|
|
160
203
|
}
|
package/lib/bin/analyzer.js
CHANGED
|
@@ -6,8 +6,6 @@ const {
|
|
|
6
6
|
dirname
|
|
7
7
|
} = require('path');
|
|
8
8
|
|
|
9
|
-
const _ = require('lodash');
|
|
10
|
-
|
|
11
9
|
const commander = require('commander');
|
|
12
10
|
|
|
13
11
|
const {
|
|
@@ -113,7 +111,7 @@ function showHelp(error) {
|
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
function br(str) {
|
|
116
|
-
return `\n${
|
|
114
|
+
return `\n${' '.repeat(28)}${str}`;
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
function array() {
|
package/lib/parseUtils.js
CHANGED
|
@@ -22,9 +22,45 @@ function parseBundle(bundlePath) {
|
|
|
22
22
|
ecmaVersion: 2050
|
|
23
23
|
});
|
|
24
24
|
const walkState = {
|
|
25
|
-
locations: null
|
|
25
|
+
locations: null,
|
|
26
|
+
expressionStatementDepth: 0
|
|
26
27
|
};
|
|
27
28
|
walk.recursive(ast, walkState, {
|
|
29
|
+
ExpressionStatement(node, state, c) {
|
|
30
|
+
if (state.locations) return;
|
|
31
|
+
state.expressionStatementDepth++;
|
|
32
|
+
|
|
33
|
+
if ( // Webpack 5 stores modules in the the top-level IIFE
|
|
34
|
+
state.expressionStatementDepth === 1 && ast.body.includes(node) && isIIFE(node)) {
|
|
35
|
+
const fn = getIIFECallExpression(node);
|
|
36
|
+
|
|
37
|
+
if ( // It should not contain neither arguments
|
|
38
|
+
fn.arguments.length === 0 && // ...nor parameters
|
|
39
|
+
fn.callee.params.length === 0) {
|
|
40
|
+
// Modules are stored in the very first variable declaration as hash
|
|
41
|
+
const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
|
|
42
|
+
|
|
43
|
+
if (firstVariableDeclaration) {
|
|
44
|
+
for (const declaration of firstVariableDeclaration.declarations) {
|
|
45
|
+
if (declaration.init) {
|
|
46
|
+
state.locations = getModulesLocations(declaration.init);
|
|
47
|
+
|
|
48
|
+
if (state.locations) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!state.locations) {
|
|
58
|
+
c(node.expression, state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
state.expressionStatementDepth--;
|
|
62
|
+
},
|
|
63
|
+
|
|
28
64
|
AssignmentExpression(node, state) {
|
|
29
65
|
if (state.locations) return; // Modules are stored in exports.modules:
|
|
30
66
|
// exports.modules = {};
|
|
@@ -76,7 +112,7 @@ function parseBundle(bundlePath) {
|
|
|
76
112
|
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
77
113
|
|
|
78
114
|
|
|
79
|
-
|
|
115
|
+
args.forEach(arg => c(arg, state));
|
|
80
116
|
}
|
|
81
117
|
|
|
82
118
|
});
|
|
@@ -89,10 +125,43 @@ function parseBundle(bundlePath) {
|
|
|
89
125
|
}
|
|
90
126
|
|
|
91
127
|
return {
|
|
128
|
+
modules,
|
|
92
129
|
src: content,
|
|
93
|
-
|
|
130
|
+
runtimeSrc: getBundleRuntime(content, walkState.locations)
|
|
94
131
|
};
|
|
95
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Returns bundle source except modules
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
function getBundleRuntime(content, modulesLocations) {
|
|
139
|
+
const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
|
|
140
|
+
let result = '';
|
|
141
|
+
let lastIndex = 0;
|
|
142
|
+
|
|
143
|
+
for (const {
|
|
144
|
+
start,
|
|
145
|
+
end
|
|
146
|
+
} of sortedLocations) {
|
|
147
|
+
result += content.slice(lastIndex, start);
|
|
148
|
+
lastIndex = end;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result + content.slice(lastIndex, content.length);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isIIFE(node) {
|
|
155
|
+
return node.type === 'ExpressionStatement' && (node.expression.type === 'CallExpression' || node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getIIFECallExpression(node) {
|
|
159
|
+
if (node.expression.type === 'UnaryExpression') {
|
|
160
|
+
return node.expression.argument;
|
|
161
|
+
} else {
|
|
162
|
+
return node.expression;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
96
165
|
|
|
97
166
|
function isModulesList(node) {
|
|
98
167
|
return isSimpleModulesList(node) || // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
|
|
@@ -107,11 +176,11 @@ function isSimpleModulesList(node) {
|
|
|
107
176
|
}
|
|
108
177
|
|
|
109
178
|
function isModulesHash(node) {
|
|
110
|
-
return node.type === 'ObjectExpression' &&
|
|
179
|
+
return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
|
|
111
180
|
}
|
|
112
181
|
|
|
113
182
|
function isModulesArray(node) {
|
|
114
|
-
return node.type === 'ArrayExpression' &&
|
|
183
|
+
return node.type === 'ArrayExpression' && node.elements.every(elem => // Some of array items may be skipped because there is no module with such id
|
|
115
184
|
!elem || isModuleWrapper(elem));
|
|
116
185
|
}
|
|
117
186
|
|
|
@@ -143,7 +212,7 @@ function isNumericId(node) {
|
|
|
143
212
|
|
|
144
213
|
function isChunkIds(node) {
|
|
145
214
|
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
|
146
|
-
return node.type === 'ArrayExpression' &&
|
|
215
|
+
return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
|
|
147
216
|
}
|
|
148
217
|
|
|
149
218
|
function isAsyncChunkPushExpression(node) {
|
|
@@ -171,9 +240,10 @@ function getModulesLocations(node) {
|
|
|
171
240
|
if (node.type === 'ObjectExpression') {
|
|
172
241
|
// Modules hash
|
|
173
242
|
const modulesNodes = node.properties;
|
|
174
|
-
return
|
|
243
|
+
return modulesNodes.reduce((result, moduleNode) => {
|
|
175
244
|
const moduleId = moduleNode.key.name || moduleNode.key.value;
|
|
176
245
|
result[moduleId] = getModuleLocation(moduleNode.value);
|
|
246
|
+
return result;
|
|
177
247
|
}, {});
|
|
178
248
|
}
|
|
179
249
|
|
|
@@ -186,9 +256,12 @@ function getModulesLocations(node) {
|
|
|
186
256
|
0;
|
|
187
257
|
const modulesNodes = isOptimizedArray ? // The modules reside in the `concat()` function call arguments
|
|
188
258
|
node.arguments[0].elements : node.elements;
|
|
189
|
-
return
|
|
190
|
-
if (
|
|
191
|
-
|
|
259
|
+
return modulesNodes.reduce((result, moduleNode, i) => {
|
|
260
|
+
if (moduleNode) {
|
|
261
|
+
result[i + minId] = getModuleLocation(moduleNode);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
192
265
|
}, {});
|
|
193
266
|
}
|
|
194
267
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createWriteStream
|
|
5
|
+
} = require('fs');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
Readable
|
|
9
|
+
} = require('stream');
|
|
10
|
+
|
|
11
|
+
class StatsSerializeStream extends Readable {
|
|
12
|
+
constructor(stats) {
|
|
13
|
+
super();
|
|
14
|
+
this._indentLevel = 0;
|
|
15
|
+
this._stringifier = this._stringify(stats);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get _indent() {
|
|
19
|
+
return ' '.repeat(this._indentLevel);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_read() {
|
|
23
|
+
let readMore = true;
|
|
24
|
+
|
|
25
|
+
while (readMore) {
|
|
26
|
+
const {
|
|
27
|
+
value,
|
|
28
|
+
done
|
|
29
|
+
} = this._stringifier.next();
|
|
30
|
+
|
|
31
|
+
if (done) {
|
|
32
|
+
this.push(null);
|
|
33
|
+
readMore = false;
|
|
34
|
+
} else {
|
|
35
|
+
readMore = this.push(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
*_stringify(obj) {
|
|
41
|
+
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
|
|
42
|
+
yield JSON.stringify(obj);
|
|
43
|
+
} else if (Array.isArray(obj)) {
|
|
44
|
+
yield '[';
|
|
45
|
+
this._indentLevel++;
|
|
46
|
+
let isFirst = true;
|
|
47
|
+
|
|
48
|
+
for (let item of obj) {
|
|
49
|
+
if (item === undefined) {
|
|
50
|
+
item = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
yield `${isFirst ? '' : ','}\n${this._indent}`;
|
|
54
|
+
yield* this._stringify(item);
|
|
55
|
+
isFirst = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._indentLevel--;
|
|
59
|
+
yield obj.length ? `\n${this._indent}]` : ']';
|
|
60
|
+
} else {
|
|
61
|
+
yield '{';
|
|
62
|
+
this._indentLevel++;
|
|
63
|
+
let isFirst = true;
|
|
64
|
+
const entries = Object.entries(obj);
|
|
65
|
+
|
|
66
|
+
for (const [itemKey, itemValue] of entries) {
|
|
67
|
+
if (itemValue === undefined) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
|
|
72
|
+
yield* this._stringify(itemValue);
|
|
73
|
+
isFirst = false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._indentLevel--;
|
|
77
|
+
yield entries.length ? `\n${this._indent}}` : '}';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
exports.StatsSerializeStream = StatsSerializeStream;
|
|
84
|
+
exports.writeStats = writeStats;
|
|
85
|
+
|
|
86
|
+
async function writeStats(stats, filepath) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
new StatsSerializeStream(stats).on('end', resolve).on('error', reject).pipe(createWriteStream(filepath));
|
|
89
|
+
});
|
|
90
|
+
}
|