webpack-bundle-analyzer 4.0.0 → 4.4.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 +36 -1
- package/README.md +3 -0
- package/lib/BundleAnalyzerPlugin.js +9 -12
- package/lib/analyzer.js +26 -26
- package/lib/bin/analyzer.js +1 -3
- package/lib/parseUtils.js +13 -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 +2 -4
- package/lib/tree/Folder.js +1 -3
- package/lib/utils.js +5 -4
- package/lib/viewer.js +46 -81
- package/package.json +7 -9
- package/public/viewer.js +4 -5
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +4 -11
- package/src/analyzer.js +26 -22
- package/src/bin/analyzer.js +1 -2
- package/src/parseUtils.js +14 -12
- 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 +4 -4
- package/src/viewer.js +38 -80
- package/views/script.ejs +0 -8
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const mkdir = require('mkdirp');
|
|
4
3
|
const {bold} = require('chalk');
|
|
5
4
|
|
|
6
5
|
const Logger = require('./Logger');
|
|
7
6
|
const viewer = require('./viewer');
|
|
8
7
|
const utils = require('./utils');
|
|
8
|
+
const {writeStats} = require('./statsUtils');
|
|
9
9
|
|
|
10
10
|
class BundleAnalyzerPlugin {
|
|
11
11
|
constructor(opts = {}) {
|
|
@@ -80,17 +80,10 @@ 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
|
-
await
|
|
87
|
-
space: 2,
|
|
88
|
-
promises: 'ignore',
|
|
89
|
-
buffers: 'ignore',
|
|
90
|
-
maps: 'ignore',
|
|
91
|
-
iterables: 'ignore',
|
|
92
|
-
circular: 'ignore'
|
|
93
|
-
});
|
|
86
|
+
await writeStats(stats, statsFilepath);
|
|
94
87
|
|
|
95
88
|
this.logger.info(
|
|
96
89
|
`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(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
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const {createWriteStream} = require('fs');
|
|
2
|
+
const {Readable} = require('stream');
|
|
3
|
+
|
|
4
|
+
class StatsSerializeStream extends Readable {
|
|
5
|
+
constructor(stats) {
|
|
6
|
+
super();
|
|
7
|
+
this._indentLevel = 0;
|
|
8
|
+
this._stringifier = this._stringify(stats);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get _indent() {
|
|
12
|
+
return ' '.repeat(this._indentLevel);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_read() {
|
|
16
|
+
let readMore = true;
|
|
17
|
+
|
|
18
|
+
while (readMore) {
|
|
19
|
+
const {value, done} = this._stringifier.next();
|
|
20
|
+
|
|
21
|
+
if (done) {
|
|
22
|
+
this.push(null);
|
|
23
|
+
readMore = false;
|
|
24
|
+
} else {
|
|
25
|
+
readMore = this.push(value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
* _stringify(obj) {
|
|
31
|
+
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
|
|
32
|
+
yield JSON.stringify(obj);
|
|
33
|
+
} else if (Array.isArray(obj)) {
|
|
34
|
+
yield '[';
|
|
35
|
+
this._indentLevel++;
|
|
36
|
+
|
|
37
|
+
let isFirst = true;
|
|
38
|
+
for (let item of obj) {
|
|
39
|
+
if (item === undefined) {
|
|
40
|
+
item = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
yield `${isFirst ? '' : ','}\n${this._indent}`;
|
|
44
|
+
yield* this._stringify(item);
|
|
45
|
+
isFirst = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this._indentLevel--;
|
|
49
|
+
yield obj.length ? `\n${this._indent}]` : ']';
|
|
50
|
+
} else {
|
|
51
|
+
yield '{';
|
|
52
|
+
this._indentLevel++;
|
|
53
|
+
|
|
54
|
+
let isFirst = true;
|
|
55
|
+
const entries = Object.entries(obj);
|
|
56
|
+
for (const [itemKey, itemValue] of entries) {
|
|
57
|
+
if (itemValue === undefined) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
|
|
62
|
+
yield* this._stringify(itemValue);
|
|
63
|
+
isFirst = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._indentLevel--;
|
|
67
|
+
yield entries.length ? `\n${this._indent}}` : '}';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
exports.StatsSerializeStream = StatsSerializeStream;
|
|
73
|
+
exports.writeStats = writeStats;
|
|
74
|
+
|
|
75
|
+
async function writeStats(stats, filepath) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
new StatsSerializeStream(stats)
|
|
78
|
+
.on('end', resolve)
|
|
79
|
+
.on('error', reject)
|
|
80
|
+
.pipe(createWriteStream(filepath));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -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
|
@@ -3,18 +3,16 @@ const fs = require('fs');
|
|
|
3
3
|
const http = require('http');
|
|
4
4
|
|
|
5
5
|
const WebSocket = require('ws');
|
|
6
|
+
const sirv = require('sirv');
|
|
6
7
|
const _ = require('lodash');
|
|
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') {
|
|
@@ -50,28 +48,26 @@ async function startServer(bundleStats, opts) {
|
|
|
50
48
|
|
|
51
49
|
if (!chartData) return;
|
|
52
50
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Fixes #17
|
|
57
|
-
app.engine('ejs', require('ejs').renderFile);
|
|
58
|
-
app.set('view engine', 'ejs');
|
|
59
|
-
app.set('views', `${projectRoot}/views`);
|
|
60
|
-
app.use(express.static(`${projectRoot}/public`));
|
|
61
|
-
|
|
62
|
-
app.use('/', (req, res) => {
|
|
63
|
-
res.render('viewer', {
|
|
64
|
-
mode: 'server',
|
|
65
|
-
title: resolveTitle(reportTitle),
|
|
66
|
-
get chartData() { return chartData },
|
|
67
|
-
defaultSizes,
|
|
68
|
-
enableWebSocket: true,
|
|
69
|
-
// Helpers
|
|
70
|
-
escapeJson
|
|
71
|
-
});
|
|
51
|
+
const sirvMiddleware = sirv(`${projectRoot}/public`, {
|
|
52
|
+
// disables caching and traverse the file system on every request
|
|
53
|
+
dev: true
|
|
72
54
|
});
|
|
73
55
|
|
|
74
|
-
const server = http.createServer(
|
|
56
|
+
const server = http.createServer((req, res) => {
|
|
57
|
+
if (req.method === 'GET' && req.url === '/') {
|
|
58
|
+
const html = renderViewer({
|
|
59
|
+
mode: 'server',
|
|
60
|
+
title: resolveTitle(reportTitle),
|
|
61
|
+
chartData,
|
|
62
|
+
defaultSizes,
|
|
63
|
+
enableWebSocket: true
|
|
64
|
+
});
|
|
65
|
+
res.writeHead(200, {'Content-Type': 'text/html'});
|
|
66
|
+
res.end(html);
|
|
67
|
+
} else {
|
|
68
|
+
sirvMiddleware(req, res);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
75
71
|
|
|
76
72
|
await new Promise(resolve => {
|
|
77
73
|
server.listen(port, host, () => {
|
|
@@ -140,44 +136,23 @@ async function generateReport(bundleStats, opts) {
|
|
|
140
136
|
|
|
141
137
|
if (!chartData) return;
|
|
142
138
|
|
|
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
|
-
);
|
|
139
|
+
const reportHtml = renderViewer({
|
|
140
|
+
mode: 'static',
|
|
141
|
+
title: resolveTitle(reportTitle),
|
|
142
|
+
chartData,
|
|
143
|
+
defaultSizes,
|
|
144
|
+
enableWebSocket: false
|
|
180
145
|
});
|
|
146
|
+
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
|
147
|
+
|
|
148
|
+
fs.mkdirSync(path.dirname(reportFilepath), {recursive: true});
|
|
149
|
+
fs.writeFileSync(reportFilepath, reportHtml);
|
|
150
|
+
|
|
151
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
152
|
+
|
|
153
|
+
if (openBrowser) {
|
|
154
|
+
open(`file://${reportFilepath}`, logger);
|
|
155
|
+
}
|
|
181
156
|
}
|
|
182
157
|
|
|
183
158
|
async function generateJSONReport(bundleStats, opts) {
|
|
@@ -187,29 +162,12 @@ async function generateJSONReport(bundleStats, opts) {
|
|
|
187
162
|
|
|
188
163
|
if (!chartData) return;
|
|
189
164
|
|
|
190
|
-
mkdir
|
|
191
|
-
fs.
|
|
165
|
+
await fs.promises.mkdir(path.dirname(reportFilename), {recursive: true});
|
|
166
|
+
await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
|
192
167
|
|
|
193
168
|
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
|
194
169
|
}
|
|
195
170
|
|
|
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
171
|
function getChartData(analyzerOpts, ...args) {
|
|
214
172
|
let chartData;
|
|
215
173
|
const {logger} = analyzerOpts;
|