webpack-bundle-analyzer 4.1.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 +9 -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 +33 -73
- package/package.json +3 -6
- 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 +25 -71
- package/views/script.ejs +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,15 @@ _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
|
+
|
|
17
26
|
## 4.1.0
|
|
18
27
|
|
|
19
28
|
* **Improvement**
|
|
@@ -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
|
@@ -12,10 +12,6 @@ const _ = require('lodash');
|
|
|
12
12
|
|
|
13
13
|
const express = require('express');
|
|
14
14
|
|
|
15
|
-
const ejs = require('ejs');
|
|
16
|
-
|
|
17
|
-
const mkdir = require('mkdirp');
|
|
18
|
-
|
|
19
15
|
const {
|
|
20
16
|
bold
|
|
21
17
|
} = require('chalk');
|
|
@@ -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,27 +63,20 @@ async function startServer(bundleStats, opts) {
|
|
|
64
63
|
};
|
|
65
64
|
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
|
66
65
|
if (!chartData) return;
|
|
67
|
-
const app = express();
|
|
68
|
-
// Fixes #17
|
|
69
|
-
|
|
70
|
-
app.engine('ejs', require('ejs').renderFile);
|
|
71
|
-
app.set('view engine', 'ejs');
|
|
72
|
-
app.set('views', `${projectRoot}/views`);
|
|
66
|
+
const app = express();
|
|
73
67
|
app.use(express.static(`${projectRoot}/public`));
|
|
74
|
-
app.
|
|
75
|
-
res.
|
|
68
|
+
app.get('/', (req, res) => {
|
|
69
|
+
res.writeHead(200, {
|
|
70
|
+
'Content-Type': 'text/html'
|
|
71
|
+
});
|
|
72
|
+
const html = renderViewer({
|
|
76
73
|
mode: 'server',
|
|
77
74
|
title: resolveTitle(reportTitle),
|
|
78
|
-
|
|
79
|
-
get chartData() {
|
|
80
|
-
return chartData;
|
|
81
|
-
},
|
|
82
|
-
|
|
75
|
+
chartData,
|
|
83
76
|
defaultSizes,
|
|
84
|
-
enableWebSocket: true
|
|
85
|
-
// Helpers
|
|
86
|
-
escapeJson
|
|
77
|
+
enableWebSocket: true
|
|
87
78
|
});
|
|
79
|
+
return res.end(html);
|
|
88
80
|
});
|
|
89
81
|
const server = http.createServer(app);
|
|
90
82
|
await new Promise(resolve => {
|
|
@@ -144,39 +136,23 @@ async function generateReport(bundleStats, opts) {
|
|
|
144
136
|
excludeAssets
|
|
145
137
|
}, bundleStats, bundleDir);
|
|
146
138
|
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
|
-
});
|
|
139
|
+
const reportHtml = renderViewer({
|
|
140
|
+
mode: 'static',
|
|
141
|
+
title: resolveTitle(reportTitle),
|
|
142
|
+
chartData,
|
|
143
|
+
defaultSizes,
|
|
144
|
+
enableWebSocket: false
|
|
145
|
+
});
|
|
146
|
+
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
147
|
+
fs.mkdirSync(path.dirname(reportFilepath), {
|
|
148
|
+
recursive: true
|
|
179
149
|
});
|
|
150
|
+
fs.writeFileSync(reportFilepath, reportHtml);
|
|
151
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
152
|
+
|
|
153
|
+
if (openBrowser) {
|
|
154
|
+
open(`file://${reportFilepath}`, logger);
|
|
155
|
+
}
|
|
180
156
|
}
|
|
181
157
|
|
|
182
158
|
async function generateJSONReport(bundleStats, opts) {
|
|
@@ -191,29 +167,13 @@ async function generateJSONReport(bundleStats, opts) {
|
|
|
191
167
|
excludeAssets
|
|
192
168
|
}, bundleStats, bundleDir);
|
|
193
169
|
if (!chartData) return;
|
|
194
|
-
mkdir
|
|
195
|
-
|
|
170
|
+
await fs.promises.mkdir(path.dirname(reportFilename), {
|
|
171
|
+
recursive: true
|
|
172
|
+
});
|
|
173
|
+
await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
|
196
174
|
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
|
197
175
|
}
|
|
198
176
|
|
|
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
177
|
function getChartData(analyzerOpts, ...args) {
|
|
218
178
|
let chartData;
|
|
219
179
|
const {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webpack-bundle-analyzer",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
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,20 +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
|
-
"ejs": "^3.1.5",
|
|
42
40
|
"express": "^4.17.1",
|
|
43
41
|
"filesize": "^6.1.0",
|
|
44
|
-
"gzip-size": "^
|
|
42
|
+
"gzip-size": "^6.0.0",
|
|
45
43
|
"lodash": "^4.17.20",
|
|
46
|
-
"mkdirp": "^1.0.4",
|
|
47
44
|
"opener": "^1.5.2",
|
|
48
45
|
"ws": "^7.3.1"
|
|
49
46
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
|
-
const mkdir = require('mkdirp');
|
|
3
3
|
const {bold} = require('chalk');
|
|
4
4
|
|
|
5
5
|
const Logger = require('./Logger');
|
|
@@ -80,7 +80,7 @@ class BundleAnalyzerPlugin {
|
|
|
80
80
|
|
|
81
81
|
async generateStatsFile(stats) {
|
|
82
82
|
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
|
|
83
|
-
mkdir
|
|
83
|
+
await fs.promises.mkdir(path.dirname(statsFilepath), {recursive: true});
|
|
84
84
|
|
|
85
85
|
try {
|
|
86
86
|
await writeStats(stats, statsFilepath);
|
package/src/analyzer.js
CHANGED
|
@@ -32,7 +32,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
32
32
|
// Sometimes if there are additional child chunks produced add them as child assets,
|
|
33
33
|
// leave the 1st one as that is considered the 'root' asset.
|
|
34
34
|
for (let i = 1; i < children.length; i++) {
|
|
35
|
-
|
|
35
|
+
children[i].assets.forEach((asset) => {
|
|
36
36
|
asset.isChild = true;
|
|
37
37
|
bundleStats.assets.push(asset);
|
|
38
38
|
});
|
|
@@ -48,7 +48,12 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
|
|
51
|
-
bundleStats.assets =
|
|
51
|
+
bundleStats.assets = bundleStats.assets.filter(asset => {
|
|
52
|
+
// Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
|
|
53
|
+
if (asset.type && asset.type !== 'asset') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
|
|
53
58
|
// See #22
|
|
54
59
|
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
|
|
@@ -77,7 +82,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
|
|
80
|
-
|
|
85
|
+
Object.assign(parsedModules, bundleInfo.modules);
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
if (_.isEmpty(bundlesSources)) {
|
|
@@ -87,7 +92,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
const assets =
|
|
95
|
+
const assets = bundleStats.assets.reduce((result, statAsset) => {
|
|
91
96
|
// If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
|
|
92
97
|
const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
|
|
93
98
|
const modules = assetBundles ? getBundleModules(assetBundles) : [];
|
|
@@ -139,22 +144,21 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
139
144
|
|
|
140
145
|
asset.modules = assetModules;
|
|
141
146
|
asset.tree = createModulesTree(asset.modules);
|
|
147
|
+
return result;
|
|
142
148
|
}, {});
|
|
143
149
|
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
157
|
-
}, []);
|
|
150
|
+
return Object.entries(assets).map(([filename, asset]) => ({
|
|
151
|
+
label: filename,
|
|
152
|
+
isAsset: true,
|
|
153
|
+
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
|
|
154
|
+
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
|
|
155
|
+
// be the size of minified bundle.
|
|
156
|
+
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
|
|
157
|
+
statSize: asset.tree.size || asset.size,
|
|
158
|
+
parsedSize: asset.parsedSize,
|
|
159
|
+
gzipSize: asset.gzipSize,
|
|
160
|
+
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
|
161
|
+
}));
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
function readStatsFromFile(filename) {
|
|
@@ -164,7 +168,7 @@ function readStatsFromFile(filename) {
|
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
function getChildAssetBundles(bundleStats, assetName) {
|
|
167
|
-
return
|
|
171
|
+
return (bundleStats.children || []).find((c) =>
|
|
168
172
|
_(c.assetsByChunkName)
|
|
169
173
|
.values()
|
|
170
174
|
.flatten()
|
|
@@ -186,8 +190,8 @@ function getBundleModules(bundleStats) {
|
|
|
186
190
|
|
|
187
191
|
function assetHasModule(statAsset, statModule) {
|
|
188
192
|
// Checking if this module is the part of asset chunks
|
|
189
|
-
return
|
|
190
|
-
|
|
193
|
+
return statModule.chunks.some(moduleChunk =>
|
|
194
|
+
statAsset.chunks.includes(moduleChunk)
|
|
191
195
|
);
|
|
192
196
|
}
|
|
193
197
|
|
|
@@ -202,7 +206,7 @@ function isRuntimeModule(statModule) {
|
|
|
202
206
|
function createModulesTree(modules) {
|
|
203
207
|
const root = new Folder('.');
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
modules.forEach(module => root.addModule(module));
|
|
206
210
|
root.mergeNestedFolders();
|
|
207
211
|
|
|
208
212
|
return root;
|
package/src/bin/analyzer.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const {resolve, dirname} = require('path');
|
|
4
4
|
|
|
5
|
-
const _ = require('lodash');
|
|
6
5
|
const commander = require('commander');
|
|
7
6
|
const {magenta} = require('chalk');
|
|
8
7
|
|
|
@@ -157,7 +156,7 @@ function showHelp(error) {
|
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
function br(str) {
|
|
160
|
-
return `\n${
|
|
159
|
+
return `\n${' '.repeat(28)}${str}`;
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
function array() {
|
package/src/parseUtils.js
CHANGED
|
@@ -135,7 +135,7 @@ function parseBundle(bundlePath) {
|
|
|
135
135
|
|
|
136
136
|
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
|
|
137
137
|
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
138
|
-
|
|
138
|
+
args.forEach(arg => c(arg, state));
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
);
|
|
@@ -161,9 +161,8 @@ function parseBundle(bundlePath) {
|
|
|
161
161
|
* Returns bundle source except modules
|
|
162
162
|
*/
|
|
163
163
|
function getBundleRuntime(content, modulesLocations) {
|
|
164
|
-
const sortedLocations =
|
|
165
|
-
.
|
|
166
|
-
.sortBy('start');
|
|
164
|
+
const sortedLocations = Object.values(modulesLocations || {})
|
|
165
|
+
.sort((a, b) => a.start - b.start);
|
|
167
166
|
|
|
168
167
|
let result = '';
|
|
169
168
|
let lastIndex = 0;
|
|
@@ -214,8 +213,8 @@ function isSimpleModulesList(node) {
|
|
|
214
213
|
function isModulesHash(node) {
|
|
215
214
|
return (
|
|
216
215
|
node.type === 'ObjectExpression' &&
|
|
217
|
-
|
|
218
|
-
.map(
|
|
216
|
+
node.properties
|
|
217
|
+
.map(node => node.value)
|
|
219
218
|
.every(isModuleWrapper)
|
|
220
219
|
);
|
|
221
220
|
}
|
|
@@ -223,7 +222,7 @@ function isModulesHash(node) {
|
|
|
223
222
|
function isModulesArray(node) {
|
|
224
223
|
return (
|
|
225
224
|
node.type === 'ArrayExpression' &&
|
|
226
|
-
|
|
225
|
+
node.elements.every(elem =>
|
|
227
226
|
// Some of array items may be skipped because there is no module with such id
|
|
228
227
|
!elem ||
|
|
229
228
|
isModuleWrapper(elem)
|
|
@@ -276,7 +275,7 @@ function isChunkIds(node) {
|
|
|
276
275
|
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
|
277
276
|
return (
|
|
278
277
|
node.type === 'ArrayExpression' &&
|
|
279
|
-
|
|
278
|
+
node.elements.every(isModuleId)
|
|
280
279
|
);
|
|
281
280
|
}
|
|
282
281
|
|
|
@@ -321,10 +320,11 @@ function getModulesLocations(node) {
|
|
|
321
320
|
// Modules hash
|
|
322
321
|
const modulesNodes = node.properties;
|
|
323
322
|
|
|
324
|
-
return
|
|
323
|
+
return modulesNodes.reduce((result, moduleNode) => {
|
|
325
324
|
const moduleId = moduleNode.key.name || moduleNode.key.value;
|
|
326
325
|
|
|
327
326
|
result[moduleId] = getModuleLocation(moduleNode.value);
|
|
327
|
+
return result;
|
|
328
328
|
}, {});
|
|
329
329
|
}
|
|
330
330
|
|
|
@@ -342,9 +342,11 @@ function getModulesLocations(node) {
|
|
|
342
342
|
node.arguments[0].elements :
|
|
343
343
|
node.elements;
|
|
344
344
|
|
|
345
|
-
return
|
|
346
|
-
if (
|
|
347
|
-
|
|
345
|
+
return modulesNodes.reduce((result, moduleNode, i) => {
|
|
346
|
+
if (moduleNode) {
|
|
347
|
+
result[i + minId] = getModuleLocation(moduleNode);
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
348
350
|
}, {});
|
|
349
351
|
}
|
|
350
352
|
|
|
@@ -1,22 +1,65 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
|
|
7
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
8
|
+
const assetsRoot = path.join(projectRoot, 'public');
|
|
9
|
+
|
|
10
|
+
exports.renderViewer = renderViewer;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
|
14
|
+
*/
|
|
15
|
+
function escapeJson(json) {
|
|
16
|
+
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getAssetContent(filename) {
|
|
20
|
+
const assetPath = path.join(assetsRoot, filename);
|
|
21
|
+
|
|
22
|
+
if (!assetPath.startsWith(assetsRoot)) {
|
|
23
|
+
throw new Error(`"${filename}" is outside of the assets root`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return fs.readFileSync(assetPath, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function html(strings, ...values) {
|
|
30
|
+
return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getScript(filename, mode) {
|
|
34
|
+
if (mode === 'static') {
|
|
35
|
+
return `<!-- ${_.escape(filename)} -->
|
|
36
|
+
<script>${getAssetContent(filename)}</script>`;
|
|
37
|
+
} else {
|
|
38
|
+
return `<script src="${_.escape(filename)}"></script>`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} = {}) {
|
|
43
|
+
return html`<!DOCTYPE html>
|
|
2
44
|
<html>
|
|
3
45
|
<head>
|
|
4
46
|
<meta charset="UTF-8"/>
|
|
5
47
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
|
-
<title
|
|
48
|
+
<title>${_.escape(title)}</title>
|
|
7
49
|
<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" />
|
|
8
50
|
|
|
9
51
|
<script>
|
|
10
|
-
window.enableWebSocket =
|
|
52
|
+
window.enableWebSocket = ${escapeJson(enableWebSocket)};
|
|
11
53
|
</script>
|
|
12
|
-
|
|
54
|
+
${getScript('viewer.js', mode)}
|
|
13
55
|
</head>
|
|
14
56
|
|
|
15
57
|
<body>
|
|
16
58
|
<div id="app"></div>
|
|
17
59
|
<script>
|
|
18
|
-
window.chartData =
|
|
19
|
-
window.defaultSizes =
|
|
60
|
+
window.chartData = ${escapeJson(chartData)};
|
|
61
|
+
window.defaultSizes = ${escapeJson(defaultSizes)};
|
|
20
62
|
</script>
|
|
21
63
|
</body>
|
|
22
|
-
</html
|
|
64
|
+
</html>`;
|
|
65
|
+
}
|
package/src/tree/BaseFolder.js
CHANGED
|
@@ -62,7 +62,7 @@ export default class BaseFolder extends Node {
|
|
|
62
62
|
walk(walker, state = {}, deep = true) {
|
|
63
63
|
let stopped = false;
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Object.values(this.children).forEach(child => {
|
|
66
66
|
if (deep && child.walk) {
|
|
67
67
|
state = child.walk(walker, state, stop);
|
|
68
68
|
} else {
|
|
@@ -15,7 +15,7 @@ export default class ConcatenatedModule extends Module {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
fillContentModules() {
|
|
18
|
-
|
|
18
|
+
this.data.modules.forEach(moduleData => this.addContentModule(moduleData));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
addContentModule(moduleData) {
|
|
@@ -28,7 +28,7 @@ export default class ConcatenatedModule extends Module {
|
|
|
28
28
|
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
|
|
29
29
|
let currentFolder = this;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
folders.forEach(folderName => {
|
|
32
32
|
let childFolder = currentFolder.getChild(folderName);
|
|
33
33
|
|
|
34
34
|
if (!childFolder) {
|
package/src/tree/Folder.js
CHANGED
|
@@ -30,7 +30,7 @@ export default class Folder extends BaseFolder {
|
|
|
30
30
|
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
|
|
31
31
|
let currentFolder = this;
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
folders.forEach(folderName => {
|
|
34
34
|
let childNode = currentFolder.getChild(folderName);
|
|
35
35
|
|
|
36
36
|
if (
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {inspect} = require('util');
|
|
1
|
+
const {inspect, types} = require('util');
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const opener = require('opener');
|
|
4
4
|
|
|
@@ -15,11 +15,11 @@ function createAssetsFilter(excludePatterns) {
|
|
|
15
15
|
pattern = new RegExp(pattern, 'u');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
if (
|
|
18
|
+
if (types.isRegExp(pattern)) {
|
|
19
19
|
return (asset) => pattern.test(asset);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
if (
|
|
22
|
+
if (typeof pattern !== 'function') {
|
|
23
23
|
throw new TypeError(
|
|
24
24
|
`Pattern should be either string, RegExp or a function, but "${inspect(pattern, {depth: 0})}" got.`
|
|
25
25
|
);
|
|
@@ -30,7 +30,7 @@ function createAssetsFilter(excludePatterns) {
|
|
|
30
30
|
.value();
|
|
31
31
|
|
|
32
32
|
if (excludeFunctions.length) {
|
|
33
|
-
return (asset) =>
|
|
33
|
+
return (asset) => excludeFunctions.every(fn => fn(asset) !== true);
|
|
34
34
|
} else {
|
|
35
35
|
return () => true;
|
|
36
36
|
}
|
package/src/viewer.js
CHANGED
|
@@ -5,16 +5,14 @@ const http = require('http');
|
|
|
5
5
|
const WebSocket = require('ws');
|
|
6
6
|
const _ = require('lodash');
|
|
7
7
|
const express = require('express');
|
|
8
|
-
const ejs = require('ejs');
|
|
9
|
-
const mkdir = require('mkdirp');
|
|
10
8
|
const {bold} = require('chalk');
|
|
11
9
|
|
|
12
10
|
const Logger = require('./Logger');
|
|
13
11
|
const analyzer = require('./analyzer');
|
|
14
12
|
const {open} = require('./utils');
|
|
13
|
+
const {renderViewer} = require('./template');
|
|
15
14
|
|
|
16
15
|
const projectRoot = path.resolve(__dirname, '..');
|
|
17
|
-
const assetsRoot = path.join(projectRoot, 'public');
|
|
18
16
|
|
|
19
17
|
function resolveTitle(reportTitle) {
|
|
20
18
|
if (typeof reportTitle === 'function') {
|
|
@@ -51,24 +49,18 @@ async function startServer(bundleStats, opts) {
|
|
|
51
49
|
if (!chartData) return;
|
|
52
50
|
|
|
53
51
|
const app = express();
|
|
54
|
-
|
|
55
|
-
// Explicitly using our `ejs` dependency to render templates
|
|
56
|
-
// Fixes #17
|
|
57
|
-
app.engine('ejs', require('ejs').renderFile);
|
|
58
|
-
app.set('view engine', 'ejs');
|
|
59
|
-
app.set('views', `${projectRoot}/views`);
|
|
60
52
|
app.use(express.static(`${projectRoot}/public`));
|
|
61
53
|
|
|
62
|
-
app.
|
|
63
|
-
res.
|
|
54
|
+
app.get('/', (req, res) => {
|
|
55
|
+
res.writeHead(200, {'Content-Type': 'text/html'});
|
|
56
|
+
const html = renderViewer({
|
|
64
57
|
mode: 'server',
|
|
65
58
|
title: resolveTitle(reportTitle),
|
|
66
|
-
|
|
59
|
+
chartData,
|
|
67
60
|
defaultSizes,
|
|
68
|
-
enableWebSocket: true
|
|
69
|
-
// Helpers
|
|
70
|
-
escapeJson
|
|
61
|
+
enableWebSocket: true
|
|
71
62
|
});
|
|
63
|
+
return res.end(html);
|
|
72
64
|
});
|
|
73
65
|
|
|
74
66
|
const server = http.createServer(app);
|
|
@@ -140,44 +132,23 @@ async function generateReport(bundleStats, opts) {
|
|
|
140
132
|
|
|
141
133
|
if (!chartData) return;
|
|
142
134
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
chartData,
|
|
150
|
-
defaultSizes,
|
|
151
|
-
enableWebSocket: false,
|
|
152
|
-
// Helpers
|
|
153
|
-
assetContent: getAssetContent,
|
|
154
|
-
escapeJson
|
|
155
|
-
},
|
|
156
|
-
(err, reportHtml) => {
|
|
157
|
-
try {
|
|
158
|
-
if (err) {
|
|
159
|
-
logger.error(err);
|
|
160
|
-
reject(err);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
165
|
-
|
|
166
|
-
mkdir.sync(path.dirname(reportFilepath));
|
|
167
|
-
fs.writeFileSync(reportFilepath, reportHtml);
|
|
168
|
-
|
|
169
|
-
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
170
|
-
|
|
171
|
-
if (openBrowser) {
|
|
172
|
-
open(`file://${reportFilepath}`, logger);
|
|
173
|
-
}
|
|
174
|
-
resolve();
|
|
175
|
-
} catch (e) {
|
|
176
|
-
reject(e);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
);
|
|
135
|
+
const reportHtml = renderViewer({
|
|
136
|
+
mode: 'static',
|
|
137
|
+
title: resolveTitle(reportTitle),
|
|
138
|
+
chartData,
|
|
139
|
+
defaultSizes,
|
|
140
|
+
enableWebSocket: false
|
|
180
141
|
});
|
|
142
|
+
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
143
|
+
|
|
144
|
+
fs.mkdirSync(path.dirname(reportFilepath), {recursive: true});
|
|
145
|
+
fs.writeFileSync(reportFilepath, reportHtml);
|
|
146
|
+
|
|
147
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
148
|
+
|
|
149
|
+
if (openBrowser) {
|
|
150
|
+
open(`file://${reportFilepath}`, logger);
|
|
151
|
+
}
|
|
181
152
|
}
|
|
182
153
|
|
|
183
154
|
async function generateJSONReport(bundleStats, opts) {
|
|
@@ -187,29 +158,12 @@ async function generateJSONReport(bundleStats, opts) {
|
|
|
187
158
|
|
|
188
159
|
if (!chartData) return;
|
|
189
160
|
|
|
190
|
-
mkdir
|
|
191
|
-
fs.
|
|
161
|
+
await fs.promises.mkdir(path.dirname(reportFilename), {recursive: true});
|
|
162
|
+
await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
|
192
163
|
|
|
193
164
|
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
|
194
165
|
}
|
|
195
166
|
|
|
196
|
-
function getAssetContent(filename) {
|
|
197
|
-
const assetPath = path.join(assetsRoot, filename);
|
|
198
|
-
|
|
199
|
-
if (!assetPath.startsWith(assetsRoot)) {
|
|
200
|
-
throw new Error(`"${filename}" is outside of the assets root`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return fs.readFileSync(assetPath, 'utf8');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
|
208
|
-
*/
|
|
209
|
-
function escapeJson(json) {
|
|
210
|
-
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
167
|
function getChartData(analyzerOpts, ...args) {
|
|
214
168
|
let chartData;
|
|
215
169
|
const {logger} = analyzerOpts;
|