webpack-bundle-analyzer 4.1.0 → 4.4.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 +37 -1
- package/README.md +3 -0
- package/lib/BundleAnalyzerPlugin.js +5 -3
- package/lib/analyzer.js +26 -26
- package/lib/bin/analyzer.js +1 -3
- package/lib/parseUtils.js +13 -10
- package/lib/template.js +73 -0
- package/lib/tree/BaseFolder.js +1 -3
- package/lib/tree/ConcatenatedModule.js +2 -4
- package/lib/tree/Folder.js +1 -3
- package/lib/utils.js +5 -4
- package/lib/viewer.js +46 -81
- package/package.json +6 -8
- package/public/viewer.js +4 -5
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +2 -2
- package/src/analyzer.js +26 -22
- package/src/bin/analyzer.js +1 -2
- package/src/parseUtils.js +14 -12
- 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 +4 -4
- package/src/viewer.js +38 -80
- package/views/script.ejs +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -12,7 +12,43 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
|
|
|
12
12
|
|
|
13
13
|
## UNRELEASED
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## 4.4.1
|
|
16
|
+
|
|
17
|
+
* **Bug Fix**
|
|
18
|
+
* Fix missing module chunks ([#433](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/433) by [@deanshub](https://github.com/deanshub))
|
|
19
|
+
|
|
20
|
+
* **Internal**
|
|
21
|
+
* Fix tests timing out in CI ([#435](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/435) by [@deanshub](https://github.com/deanshub))
|
|
22
|
+
* Fix command in issue template ([#428](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/428) by [@cncolder](https://github.com/cncolder))
|
|
23
|
+
|
|
24
|
+
## 4.4.0
|
|
25
|
+
|
|
26
|
+
* **Improvement**
|
|
27
|
+
* Keep treemap labels visible during zooming animations for better user experience ([#414](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/414) by [@stanislawosinski](https://github.com/stanislawosinski))
|
|
28
|
+
|
|
29
|
+
* **Bug Fix**
|
|
30
|
+
* Don't show an empty tooltip when hovering over the FoamTree attribution group or between top-level groups ([#413](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/413) by [@stanislawosinski](https://github.com/stanislawosinski))
|
|
31
|
+
|
|
32
|
+
* **Internal**
|
|
33
|
+
* Upgrade FoamTree to version 3.5.0, replace vendor dependency with an NPM package ([#412](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/412) by [@stanislawosinski](https://github.com/stanislawosinski))
|
|
34
|
+
|
|
35
|
+
## 4.3.0
|
|
36
|
+
|
|
37
|
+
* **Improvement**
|
|
38
|
+
* Replace express with builtin node server, reducing number of dependencies ([#398](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/398) by [@TrySound](https://github.com/TrySound))
|
|
39
|
+
* Move `filesize` to dev dependencies, reducing number of dependencies ([#401](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/401) by [@realityking](https://github.com/realityking))
|
|
40
|
+
|
|
41
|
+
* **Internal**
|
|
42
|
+
* Replace Travis with GitHub actions ([#402](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/402) by [@valscion](https://github.com/valscion))
|
|
43
|
+
|
|
44
|
+
## 4.2.0
|
|
45
|
+
|
|
46
|
+
* **Improvement**
|
|
47
|
+
* 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))
|
|
48
|
+
|
|
49
|
+
* **Bug Fix**
|
|
50
|
+
* 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))
|
|
51
|
+
* Fix `non-asset` assets causing analyze failure. ([#385](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/385) by [@ZKHelloworld](https://github.com/ZKHelloworld))
|
|
16
52
|
|
|
17
53
|
## 4.1.0
|
|
18
54
|
|
package/README.md
CHANGED
|
@@ -178,6 +178,9 @@ Analyzer will use module sizes from stats file.
|
|
|
178
178
|
```
|
|
179
179
|
To get more information about it you can read [issue #147](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/147).
|
|
180
180
|
|
|
181
|
+
<h2 align="center">Other tools</h2>
|
|
182
|
+
|
|
183
|
+
- [Statoscope](https://github.com/smelukov/statoscope/blob/master/packages/ui-webpack/README.md) - Webpack bundle analyzing tool to find out why a certain module was bundled (and more features, including interactive treemap)
|
|
181
184
|
|
|
182
185
|
<h2 align="center">Maintainers</h2>
|
|
183
186
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require('fs');
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
bold
|
|
@@ -90,7 +90,9 @@ class BundleAnalyzerPlugin {
|
|
|
90
90
|
|
|
91
91
|
async generateStatsFile(stats) {
|
|
92
92
|
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
|
|
93
|
-
mkdir
|
|
93
|
+
await fs.promises.mkdir(path.dirname(statsFilepath), {
|
|
94
|
+
recursive: true
|
|
95
|
+
});
|
|
94
96
|
|
|
95
97
|
try {
|
|
96
98
|
await writeStats(stats, statsFilepath);
|
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
|
|
@@ -85,8 +90,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
|
|
88
|
-
|
|
89
|
-
_.assign(parsedModules, bundleInfo.modules);
|
|
93
|
+
Object.assign(parsedModules, bundleInfo.modules);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
if (_.isEmpty(bundlesSources)) {
|
|
@@ -96,7 +100,7 @@ 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) : [];
|
|
@@ -149,22 +153,20 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
149
153
|
|
|
150
154
|
asset.modules = assetModules;
|
|
151
155
|
asset.tree = createModulesTree(asset.modules);
|
|
156
|
+
return result;
|
|
152
157
|
}, {});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
|
166
|
-
});
|
|
167
|
-
}, []);
|
|
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
|
+
}));
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
function readStatsFromFile(filename) {
|
|
@@ -172,7 +174,7 @@ function readStatsFromFile(filename) {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
function getChildAssetBundles(bundleStats, assetName) {
|
|
175
|
-
return
|
|
177
|
+
return (bundleStats.children || []).find(c => _(c.assetsByChunkName).values().flatten().includes(assetName));
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
function getBundleModules(bundleStats) {
|
|
@@ -182,7 +184,7 @@ function getBundleModules(bundleStats) {
|
|
|
182
184
|
|
|
183
185
|
function assetHasModule(statAsset, statModule) {
|
|
184
186
|
// Checking if this module is the part of asset chunks
|
|
185
|
-
return
|
|
187
|
+
return (statModule.chunks || []).some(moduleChunk => statAsset.chunks.includes(moduleChunk));
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
function isEntryModule(statModule) {
|
|
@@ -195,9 +197,7 @@ function isRuntimeModule(statModule) {
|
|
|
195
197
|
|
|
196
198
|
function createModulesTree(modules) {
|
|
197
199
|
const root = new Folder('.');
|
|
198
|
-
|
|
199
|
-
_.each(modules, module => root.addModule(module));
|
|
200
|
-
|
|
200
|
+
modules.forEach(module => root.addModule(module));
|
|
201
201
|
root.mergeNestedFolders();
|
|
202
202
|
return root;
|
|
203
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
|
@@ -112,7 +112,7 @@ function parseBundle(bundlePath) {
|
|
|
112
112
|
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
args.forEach(arg => c(arg, state));
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
});
|
|
@@ -136,8 +136,7 @@ function parseBundle(bundlePath) {
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
function getBundleRuntime(content, modulesLocations) {
|
|
139
|
-
const sortedLocations =
|
|
140
|
-
|
|
139
|
+
const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
|
|
141
140
|
let result = '';
|
|
142
141
|
let lastIndex = 0;
|
|
143
142
|
|
|
@@ -177,11 +176,11 @@ function isSimpleModulesList(node) {
|
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
function isModulesHash(node) {
|
|
180
|
-
return node.type === 'ObjectExpression' &&
|
|
179
|
+
return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
function isModulesArray(node) {
|
|
184
|
-
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
|
|
185
184
|
!elem || isModuleWrapper(elem));
|
|
186
185
|
}
|
|
187
186
|
|
|
@@ -213,7 +212,7 @@ function isNumericId(node) {
|
|
|
213
212
|
|
|
214
213
|
function isChunkIds(node) {
|
|
215
214
|
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
|
216
|
-
return node.type === 'ArrayExpression' &&
|
|
215
|
+
return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
function isAsyncChunkPushExpression(node) {
|
|
@@ -241,9 +240,10 @@ function getModulesLocations(node) {
|
|
|
241
240
|
if (node.type === 'ObjectExpression') {
|
|
242
241
|
// Modules hash
|
|
243
242
|
const modulesNodes = node.properties;
|
|
244
|
-
return
|
|
243
|
+
return modulesNodes.reduce((result, moduleNode) => {
|
|
245
244
|
const moduleId = moduleNode.key.name || moduleNode.key.value;
|
|
246
245
|
result[moduleId] = getModuleLocation(moduleNode.value);
|
|
246
|
+
return result;
|
|
247
247
|
}, {});
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -256,9 +256,12 @@ function getModulesLocations(node) {
|
|
|
256
256
|
0;
|
|
257
257
|
const modulesNodes = isOptimizedArray ? // The modules reside in the `concat()` function call arguments
|
|
258
258
|
node.arguments[0].elements : node.elements;
|
|
259
|
-
return
|
|
260
|
-
if (
|
|
261
|
-
|
|
259
|
+
return modulesNodes.reduce((result, moduleNode, i) => {
|
|
260
|
+
if (moduleNode) {
|
|
261
|
+
result[i + minId] = getModuleLocation(moduleNode);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
262
265
|
}, {});
|
|
263
266
|
}
|
|
264
267
|
|
package/lib/template.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/* eslint-disable max-len */
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
|
|
10
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
11
|
+
const assetsRoot = path.join(projectRoot, 'public');
|
|
12
|
+
exports.renderViewer = renderViewer;
|
|
13
|
+
/**
|
|
14
|
+
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function escapeJson(json) {
|
|
18
|
+
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getAssetContent(filename) {
|
|
22
|
+
const assetPath = path.join(assetsRoot, filename);
|
|
23
|
+
|
|
24
|
+
if (!assetPath.startsWith(assetsRoot)) {
|
|
25
|
+
throw new Error(`"${filename}" is outside of the assets root`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return fs.readFileSync(assetPath, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function html(strings, ...values) {
|
|
32
|
+
return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getScript(filename, mode) {
|
|
36
|
+
if (mode === 'static') {
|
|
37
|
+
return `<!-- ${_.escape(filename)} -->
|
|
38
|
+
<script>${getAssetContent(filename)}</script>`;
|
|
39
|
+
} else {
|
|
40
|
+
return `<script src="${_.escape(filename)}"></script>`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderViewer({
|
|
45
|
+
title,
|
|
46
|
+
enableWebSocket,
|
|
47
|
+
chartData,
|
|
48
|
+
defaultSizes,
|
|
49
|
+
mode
|
|
50
|
+
} = {}) {
|
|
51
|
+
return html`<!DOCTYPE html>
|
|
52
|
+
<html>
|
|
53
|
+
<head>
|
|
54
|
+
<meta charset="UTF-8"/>
|
|
55
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
56
|
+
<title>${_.escape(title)}</title>
|
|
57
|
+
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII=" type="image/x-icon" />
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
window.enableWebSocket = ${escapeJson(enableWebSocket)};
|
|
61
|
+
</script>
|
|
62
|
+
${getScript('viewer.js', mode)}
|
|
63
|
+
</head>
|
|
64
|
+
|
|
65
|
+
<body>
|
|
66
|
+
<div id="app"></div>
|
|
67
|
+
<script>
|
|
68
|
+
window.chartData = ${escapeJson(chartData)};
|
|
69
|
+
window.defaultSizes = ${escapeJson(defaultSizes)};
|
|
70
|
+
</script>
|
|
71
|
+
</body>
|
|
72
|
+
</html>`;
|
|
73
|
+
}
|
package/lib/tree/BaseFolder.js
CHANGED
|
@@ -69,8 +69,7 @@ class BaseFolder extends _Node.default {
|
|
|
69
69
|
|
|
70
70
|
walk(walker, state = {}, deep = true) {
|
|
71
71
|
let stopped = false;
|
|
72
|
-
|
|
73
|
-
_lodash.default.each(this.children, child => {
|
|
72
|
+
Object.values(this.children).forEach(child => {
|
|
74
73
|
if (deep && child.walk) {
|
|
75
74
|
state = child.walk(walker, state, stop);
|
|
76
75
|
} else {
|
|
@@ -79,7 +78,6 @@ class BaseFolder extends _Node.default {
|
|
|
79
78
|
|
|
80
79
|
if (stopped) return false;
|
|
81
80
|
});
|
|
82
|
-
|
|
83
81
|
return state;
|
|
84
82
|
|
|
85
83
|
function stop(finalState) {
|
|
@@ -26,7 +26,7 @@ class ConcatenatedModule extends _Module.default {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
fillContentModules() {
|
|
29
|
-
|
|
29
|
+
this.data.modules.forEach(moduleData => this.addContentModule(moduleData));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
addContentModule(moduleData) {
|
|
@@ -38,8 +38,7 @@ class ConcatenatedModule extends _Module.default {
|
|
|
38
38
|
|
|
39
39
|
const [folders, fileName] = [pathParts.slice(0, -1), _lodash.default.last(pathParts)];
|
|
40
40
|
let currentFolder = this;
|
|
41
|
-
|
|
42
|
-
_lodash.default.each(folders, folderName => {
|
|
41
|
+
folders.forEach(folderName => {
|
|
43
42
|
let childFolder = currentFolder.getChild(folderName);
|
|
44
43
|
|
|
45
44
|
if (!childFolder) {
|
|
@@ -48,7 +47,6 @@ class ConcatenatedModule extends _Module.default {
|
|
|
48
47
|
|
|
49
48
|
currentFolder = childFolder;
|
|
50
49
|
});
|
|
51
|
-
|
|
52
50
|
const module = new _ContentModule.default(fileName, moduleData, this);
|
|
53
51
|
currentFolder.addChildModule(module);
|
|
54
52
|
}
|
package/lib/tree/Folder.js
CHANGED
|
@@ -41,8 +41,7 @@ class Folder extends _BaseFolder.default {
|
|
|
41
41
|
|
|
42
42
|
const [folders, fileName] = [pathParts.slice(0, -1), _lodash.default.last(pathParts)];
|
|
43
43
|
let currentFolder = this;
|
|
44
|
-
|
|
45
|
-
_lodash.default.each(folders, folderName => {
|
|
44
|
+
folders.forEach(folderName => {
|
|
46
45
|
let childNode = currentFolder.getChild(folderName);
|
|
47
46
|
|
|
48
47
|
if ( // Folder is not created yet
|
|
@@ -56,7 +55,6 @@ class Folder extends _BaseFolder.default {
|
|
|
56
55
|
|
|
57
56
|
currentFolder = childNode;
|
|
58
57
|
});
|
|
59
|
-
|
|
60
58
|
const ModuleConstructor = moduleData.modules ? _ConcatenatedModule.default : _Module.default;
|
|
61
59
|
const module = new ModuleConstructor(fileName, moduleData, this);
|
|
62
60
|
currentFolder.addChildModule(module);
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
inspect
|
|
4
|
+
inspect,
|
|
5
|
+
types
|
|
5
6
|
} = require('util');
|
|
6
7
|
|
|
7
8
|
const _ = require('lodash');
|
|
@@ -17,11 +18,11 @@ function createAssetsFilter(excludePatterns) {
|
|
|
17
18
|
pattern = new RegExp(pattern, 'u');
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
if (
|
|
21
|
+
if (types.isRegExp(pattern)) {
|
|
21
22
|
return asset => pattern.test(asset);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
if (
|
|
25
|
+
if (typeof pattern !== 'function') {
|
|
25
26
|
throw new TypeError(`Pattern should be either string, RegExp or a function, but "${inspect(pattern, {
|
|
26
27
|
depth: 0
|
|
27
28
|
})}" got.`);
|
|
@@ -31,7 +32,7 @@ function createAssetsFilter(excludePatterns) {
|
|
|
31
32
|
}).value();
|
|
32
33
|
|
|
33
34
|
if (excludeFunctions.length) {
|
|
34
|
-
return asset =>
|
|
35
|
+
return asset => excludeFunctions.every(fn => fn(asset) !== true);
|
|
35
36
|
} else {
|
|
36
37
|
return () => true;
|
|
37
38
|
}
|
package/lib/viewer.js
CHANGED
|
@@ -8,13 +8,9 @@ const http = require('http');
|
|
|
8
8
|
|
|
9
9
|
const WebSocket = require('ws');
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const express = require('express');
|
|
14
|
-
|
|
15
|
-
const ejs = require('ejs');
|
|
11
|
+
const sirv = require('sirv');
|
|
16
12
|
|
|
17
|
-
const
|
|
13
|
+
const _ = require('lodash');
|
|
18
14
|
|
|
19
15
|
const {
|
|
20
16
|
bold
|
|
@@ -28,8 +24,11 @@ const {
|
|
|
28
24
|
open
|
|
29
25
|
} = require('./utils');
|
|
30
26
|
|
|
27
|
+
const {
|
|
28
|
+
renderViewer
|
|
29
|
+
} = require('./template');
|
|
30
|
+
|
|
31
31
|
const projectRoot = path.resolve(__dirname, '..');
|
|
32
|
-
const assetsRoot = path.join(projectRoot, 'public');
|
|
33
32
|
|
|
34
33
|
function resolveTitle(reportTitle) {
|
|
35
34
|
if (typeof reportTitle === 'function') {
|
|
@@ -64,29 +63,27 @@ async function startServer(bundleStats, opts) {
|
|
|
64
63
|
};
|
|
65
64
|
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
|
66
65
|
if (!chartData) return;
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
66
|
+
const sirvMiddleware = sirv(`${projectRoot}/public`, {
|
|
67
|
+
// disables caching and traverse the file system on every request
|
|
68
|
+
dev: true
|
|
69
|
+
});
|
|
70
|
+
const server = http.createServer((req, res) => {
|
|
71
|
+
if (req.method === 'GET' && req.url === '/') {
|
|
72
|
+
const html = renderViewer({
|
|
73
|
+
mode: 'server',
|
|
74
|
+
title: resolveTitle(reportTitle),
|
|
75
|
+
chartData,
|
|
76
|
+
defaultSizes,
|
|
77
|
+
enableWebSocket: true
|
|
78
|
+
});
|
|
79
|
+
res.writeHead(200, {
|
|
80
|
+
'Content-Type': 'text/html'
|
|
81
|
+
});
|
|
82
|
+
res.end(html);
|
|
83
|
+
} else {
|
|
84
|
+
sirvMiddleware(req, res);
|
|
85
|
+
}
|
|
88
86
|
});
|
|
89
|
-
const server = http.createServer(app);
|
|
90
87
|
await new Promise(resolve => {
|
|
91
88
|
server.listen(port, host, () => {
|
|
92
89
|
resolve();
|
|
@@ -144,39 +141,23 @@ async function generateReport(bundleStats, opts) {
|
|
|
144
141
|
excludeAssets
|
|
145
142
|
}, bundleStats, bundleDir);
|
|
146
143
|
if (!chartData) return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}, (err, reportHtml) => {
|
|
158
|
-
try {
|
|
159
|
-
if (err) {
|
|
160
|
-
logger.error(err);
|
|
161
|
-
reject(err);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
166
|
-
mkdir.sync(path.dirname(reportFilepath));
|
|
167
|
-
fs.writeFileSync(reportFilepath, reportHtml);
|
|
168
|
-
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
169
|
-
|
|
170
|
-
if (openBrowser) {
|
|
171
|
-
open(`file://${reportFilepath}`, logger);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
resolve();
|
|
175
|
-
} catch (e) {
|
|
176
|
-
reject(e);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
144
|
+
const reportHtml = renderViewer({
|
|
145
|
+
mode: 'static',
|
|
146
|
+
title: resolveTitle(reportTitle),
|
|
147
|
+
chartData,
|
|
148
|
+
defaultSizes,
|
|
149
|
+
enableWebSocket: false
|
|
150
|
+
});
|
|
151
|
+
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
152
|
+
fs.mkdirSync(path.dirname(reportFilepath), {
|
|
153
|
+
recursive: true
|
|
179
154
|
});
|
|
155
|
+
fs.writeFileSync(reportFilepath, reportHtml);
|
|
156
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
157
|
+
|
|
158
|
+
if (openBrowser) {
|
|
159
|
+
open(`file://${reportFilepath}`, logger);
|
|
160
|
+
}
|
|
180
161
|
}
|
|
181
162
|
|
|
182
163
|
async function generateJSONReport(bundleStats, opts) {
|
|
@@ -191,29 +172,13 @@ async function generateJSONReport(bundleStats, opts) {
|
|
|
191
172
|
excludeAssets
|
|
192
173
|
}, bundleStats, bundleDir);
|
|
193
174
|
if (!chartData) return;
|
|
194
|
-
mkdir
|
|
195
|
-
|
|
175
|
+
await fs.promises.mkdir(path.dirname(reportFilename), {
|
|
176
|
+
recursive: true
|
|
177
|
+
});
|
|
178
|
+
await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
|
196
179
|
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
|
197
180
|
}
|
|
198
181
|
|
|
199
|
-
function getAssetContent(filename) {
|
|
200
|
-
const assetPath = path.join(assetsRoot, filename);
|
|
201
|
-
|
|
202
|
-
if (!assetPath.startsWith(assetsRoot)) {
|
|
203
|
-
throw new Error(`"${filename}" is outside of the assets root`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return fs.readFileSync(assetPath, 'utf8');
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
|
210
|
-
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
function escapeJson(json) {
|
|
214
|
-
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
182
|
function getChartData(analyzerOpts, ...args) {
|
|
218
183
|
let chartData;
|
|
219
184
|
const {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webpack-bundle-analyzer",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.4.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",
|
|
@@ -30,21 +30,17 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"public",
|
|
32
32
|
"lib",
|
|
33
|
-
"src"
|
|
34
|
-
"views"
|
|
33
|
+
"src"
|
|
35
34
|
],
|
|
36
35
|
"dependencies": {
|
|
37
36
|
"acorn": "^8.0.4",
|
|
38
37
|
"acorn-walk": "^8.0.0",
|
|
39
38
|
"chalk": "^4.1.0",
|
|
40
39
|
"commander": "^6.2.0",
|
|
41
|
-
"
|
|
42
|
-
"express": "^4.17.1",
|
|
43
|
-
"filesize": "^6.1.0",
|
|
44
|
-
"gzip-size": "^5.1.1",
|
|
40
|
+
"gzip-size": "^6.0.0",
|
|
45
41
|
"lodash": "^4.17.20",
|
|
46
|
-
"mkdirp": "^1.0.4",
|
|
47
42
|
"opener": "^1.5.2",
|
|
43
|
+
"sirv": "^1.0.7",
|
|
48
44
|
"ws": "^7.3.1"
|
|
49
45
|
},
|
|
50
46
|
"devDependencies": {
|
|
@@ -56,6 +52,7 @@
|
|
|
56
52
|
"@babel/preset-react": "7.12.5",
|
|
57
53
|
"@babel/register": "7.12.1",
|
|
58
54
|
"@babel/runtime": "7.12.5",
|
|
55
|
+
"@carrotsearch/foamtree": "3.5.0",
|
|
59
56
|
"autoprefixer": "10.0.1",
|
|
60
57
|
"babel-eslint": "10.1.0",
|
|
61
58
|
"babel-loader": "8.1.0",
|
|
@@ -72,6 +69,7 @@
|
|
|
72
69
|
"eslint-config-th0r-react": "2.0.1",
|
|
73
70
|
"eslint-plugin-react": "7.21.5",
|
|
74
71
|
"exports-loader": "1.1.1",
|
|
72
|
+
"filesize": "^6.1.0",
|
|
75
73
|
"globby": "11.0.1",
|
|
76
74
|
"gulp": "4.0.2",
|
|
77
75
|
"gulp-babel": "8.0.0",
|