webpack-bundle-analyzer 3.6.0 → 3.9.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 +37 -0
- package/README.md +6 -3
- package/lib/BundleAnalyzerPlugin.js +28 -10
- package/lib/analyzer.js +29 -4
- package/lib/bin/analyzer.js +25 -5
- package/lib/parseUtils.js +32 -3
- package/lib/tree/ConcatenatedModule.js +1 -1
- package/lib/tree/ContentFolder.js +1 -1
- package/lib/tree/ContentModule.js +1 -1
- package/lib/tree/Folder.js +1 -1
- package/lib/utils.js +5 -4
- package/lib/viewer.js +40 -7
- package/package.json +10 -10
- package/public/viewer.js +4 -18
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +17 -3
- package/src/analyzer.js +29 -1
- package/src/bin/analyzer.js +29 -8
- package/src/parseUtils.js +37 -8
- package/src/utils.js +7 -4
- package/src/viewer.js +29 -10
|
@@ -5,14 +5,15 @@ const {bold} = require('chalk');
|
|
|
5
5
|
|
|
6
6
|
const Logger = require('./Logger');
|
|
7
7
|
const viewer = require('./viewer');
|
|
8
|
+
const utils = require('./utils');
|
|
8
9
|
|
|
9
10
|
class BundleAnalyzerPlugin {
|
|
10
|
-
|
|
11
11
|
constructor(opts = {}) {
|
|
12
12
|
this.opts = {
|
|
13
13
|
analyzerMode: 'server',
|
|
14
14
|
analyzerHost: '127.0.0.1',
|
|
15
|
-
reportFilename:
|
|
15
|
+
reportFilename: null,
|
|
16
|
+
reportTitle: utils.defaultTitle,
|
|
16
17
|
defaultSizes: 'parsed',
|
|
17
18
|
openAnalyzer: true,
|
|
18
19
|
generateStatsFile: false,
|
|
@@ -51,6 +52,8 @@ class BundleAnalyzerPlugin {
|
|
|
51
52
|
actions.push(() => this.startAnalyzerServer(stats.toJson()));
|
|
52
53
|
} else if (this.opts.analyzerMode === 'static') {
|
|
53
54
|
actions.push(() => this.generateStaticReport(stats.toJson()));
|
|
55
|
+
} else if (this.opts.analyzerMode === 'json') {
|
|
56
|
+
actions.push(() => this.generateJSONReport(stats.toJson()));
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
if (actions.length) {
|
|
@@ -107,6 +110,7 @@ class BundleAnalyzerPlugin {
|
|
|
107
110
|
openBrowser: this.opts.openAnalyzer,
|
|
108
111
|
host: this.opts.analyzerHost,
|
|
109
112
|
port: this.opts.analyzerPort,
|
|
113
|
+
reportTitle: this.opts.reportTitle,
|
|
110
114
|
bundleDir: this.getBundleDirFromCompiler(),
|
|
111
115
|
logger: this.logger,
|
|
112
116
|
defaultSizes: this.opts.defaultSizes,
|
|
@@ -115,10 +119,20 @@ class BundleAnalyzerPlugin {
|
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
async generateJSONReport(stats) {
|
|
123
|
+
await viewer.generateJSONReport(stats, {
|
|
124
|
+
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
|
|
125
|
+
bundleDir: this.getBundleDirFromCompiler(),
|
|
126
|
+
logger: this.logger,
|
|
127
|
+
excludeAssets: this.opts.excludeAssets
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
async generateStaticReport(stats) {
|
|
119
132
|
await viewer.generateReport(stats, {
|
|
120
133
|
openBrowser: this.opts.openAnalyzer,
|
|
121
|
-
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
|
|
134
|
+
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
|
|
135
|
+
reportTitle: this.opts.reportTitle,
|
|
122
136
|
bundleDir: this.getBundleDirFromCompiler(),
|
|
123
137
|
logger: this.logger,
|
|
124
138
|
defaultSizes: this.opts.defaultSizes,
|
package/src/analyzer.js
CHANGED
|
@@ -27,7 +27,24 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
27
27
|
|
|
28
28
|
// Sometimes all the information is located in `children` array (e.g. problem in #10)
|
|
29
29
|
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
|
|
30
|
+
const {children} = bundleStats;
|
|
30
31
|
bundleStats = bundleStats.children[0];
|
|
32
|
+
// Sometimes if there are additional child chunks produced add them as child assets,
|
|
33
|
+
// leave the 1st one as that is considered the 'root' asset.
|
|
34
|
+
for (let i = 1; i < children.length; i++) {
|
|
35
|
+
bundleStats.children[i].assets.forEach((asset) => {
|
|
36
|
+
asset.isChild = true;
|
|
37
|
+
bundleStats.assets.push(asset);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} else if (!_.isEmpty(bundleStats.children)) {
|
|
41
|
+
// Sometimes if there are additional child chunks produced add them as child assets
|
|
42
|
+
bundleStats.children.forEach((child) => {
|
|
43
|
+
child.assets.forEach((asset) => {
|
|
44
|
+
asset.isChild = true;
|
|
45
|
+
bundleStats.assets.push(asset);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
// Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
|
|
@@ -70,8 +87,10 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
|
|
73
|
-
const modules = getBundleModules(bundleStats);
|
|
74
90
|
const assets = _.transform(bundleStats.assets, (result, statAsset) => {
|
|
91
|
+
// If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
|
|
92
|
+
const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
|
|
93
|
+
const modules = assetBundles ? getBundleModules(assetBundles) : [];
|
|
75
94
|
const asset = result[statAsset.name] = _.pick(statAsset, 'size');
|
|
76
95
|
|
|
77
96
|
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
|
|
@@ -113,6 +132,15 @@ function readStatsFromFile(filename) {
|
|
|
113
132
|
);
|
|
114
133
|
}
|
|
115
134
|
|
|
135
|
+
function getChildAssetBundles(bundleStats, assetName) {
|
|
136
|
+
return _.find(bundleStats.children, (c) =>
|
|
137
|
+
_(c.assetsByChunkName)
|
|
138
|
+
.values()
|
|
139
|
+
.flatten()
|
|
140
|
+
.includes(assetName)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
116
144
|
function getBundleModules(bundleStats) {
|
|
117
145
|
return _(bundleStats.chunks)
|
|
118
146
|
.map('modules')
|
package/src/bin/analyzer.js
CHANGED
|
@@ -9,6 +9,7 @@ const {magenta} = require('chalk');
|
|
|
9
9
|
const analyzer = require('../analyzer');
|
|
10
10
|
const viewer = require('../viewer');
|
|
11
11
|
const Logger = require('../Logger');
|
|
12
|
+
const utils = require('../utils');
|
|
12
13
|
|
|
13
14
|
const SIZES = new Set(['stat', 'parsed', 'gzip']);
|
|
14
15
|
|
|
@@ -26,9 +27,10 @@ const program = commander
|
|
|
26
27
|
)
|
|
27
28
|
.option(
|
|
28
29
|
'-m, --mode <mode>',
|
|
29
|
-
'Analyzer mode. Should be `server` or `
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
'Analyzer mode. Should be `server`,`static` or `json`.' +
|
|
31
|
+
br('In `server` mode analyzer will start HTTP server to show bundle report.') +
|
|
32
|
+
br('In `static` mode single HTML file with bundle report will be generated.') +
|
|
33
|
+
br('In `json` mode single JSON file with bundle report will be generated.'),
|
|
32
34
|
'server'
|
|
33
35
|
)
|
|
34
36
|
.option(
|
|
@@ -45,8 +47,11 @@ const program = commander
|
|
|
45
47
|
)
|
|
46
48
|
.option(
|
|
47
49
|
'-r, --report <file>',
|
|
48
|
-
'Path to bundle report file that will be generated in `static` mode.'
|
|
49
|
-
|
|
50
|
+
'Path to bundle report file that will be generated in `static` mode.'
|
|
51
|
+
)
|
|
52
|
+
.option(
|
|
53
|
+
'-t, --title <title>',
|
|
54
|
+
'String to use in title element of html report.'
|
|
50
55
|
)
|
|
51
56
|
.option(
|
|
52
57
|
'-s, --default-sizes <type>',
|
|
@@ -77,6 +82,7 @@ let {
|
|
|
77
82
|
host,
|
|
78
83
|
port,
|
|
79
84
|
report: reportFilename,
|
|
85
|
+
title: reportTitle,
|
|
80
86
|
defaultSizes,
|
|
81
87
|
logLevel,
|
|
82
88
|
open: openBrowser,
|
|
@@ -85,8 +91,14 @@ let {
|
|
|
85
91
|
} = program;
|
|
86
92
|
const logger = new Logger(logLevel);
|
|
87
93
|
|
|
94
|
+
if (typeof reportTitle === 'undefined') {
|
|
95
|
+
reportTitle = utils.defaultTitle;
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
|
|
89
|
-
if (mode !== 'server' && mode !== 'static'
|
|
99
|
+
if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
|
|
100
|
+
showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
|
|
101
|
+
}
|
|
90
102
|
if (mode === 'server') {
|
|
91
103
|
if (!host) showHelp('Invalid host name');
|
|
92
104
|
|
|
@@ -114,19 +126,28 @@ if (mode === 'server') {
|
|
|
114
126
|
port,
|
|
115
127
|
host,
|
|
116
128
|
defaultSizes,
|
|
129
|
+
reportTitle,
|
|
117
130
|
bundleDir,
|
|
118
131
|
excludeAssets,
|
|
119
132
|
logger: new Logger(logLevel)
|
|
120
133
|
});
|
|
121
|
-
} else {
|
|
134
|
+
} else if (mode === 'static') {
|
|
122
135
|
viewer.generateReport(bundleStats, {
|
|
123
136
|
openBrowser,
|
|
124
|
-
reportFilename: resolve(reportFilename),
|
|
137
|
+
reportFilename: resolve(reportFilename || 'report.html'),
|
|
138
|
+
reportTitle,
|
|
125
139
|
defaultSizes,
|
|
126
140
|
bundleDir,
|
|
127
141
|
excludeAssets,
|
|
128
142
|
logger: new Logger(logLevel)
|
|
129
143
|
});
|
|
144
|
+
} else if (mode === 'json') {
|
|
145
|
+
viewer.generateJSONReport(bundleStats, {
|
|
146
|
+
reportFilename: resolve(reportFilename || 'report.json'),
|
|
147
|
+
bundleDir,
|
|
148
|
+
excludeAssets,
|
|
149
|
+
logger: new Logger(logLevel)
|
|
150
|
+
});
|
|
130
151
|
}
|
|
131
152
|
|
|
132
153
|
function showHelp(error) {
|
package/src/parseUtils.js
CHANGED
|
@@ -25,6 +25,22 @@ function parseBundle(bundlePath) {
|
|
|
25
25
|
ast,
|
|
26
26
|
walkState,
|
|
27
27
|
{
|
|
28
|
+
AssignmentExpression(node, state) {
|
|
29
|
+
if (state.locations) return;
|
|
30
|
+
|
|
31
|
+
// Modules are stored in exports.modules:
|
|
32
|
+
// exports.modules = {};
|
|
33
|
+
const {left, right} = node;
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
left &&
|
|
37
|
+
left.object && left.object.name === 'exports' &&
|
|
38
|
+
left.property && left.property.name === 'modules' &&
|
|
39
|
+
isModulesHash(right)
|
|
40
|
+
) {
|
|
41
|
+
state.locations = getModulesLocations(right);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
28
44
|
CallExpression(node, state, c) {
|
|
29
45
|
if (state.locations) return;
|
|
30
46
|
|
|
@@ -63,6 +79,15 @@ function parseBundle(bundlePath) {
|
|
|
63
79
|
return;
|
|
64
80
|
}
|
|
65
81
|
|
|
82
|
+
// Webpack v4 WebWorkerChunkTemplatePlugin
|
|
83
|
+
// globalObject.chunkCallbackName([<chunks>],<modules>, ...);
|
|
84
|
+
// Both globalObject and chunkCallbackName can be changed through the config, so we can't check them.
|
|
85
|
+
if (isAsyncWebWorkerChunkExpression(node)) {
|
|
86
|
+
state.locations = getModulesLocations(args[1]);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
66
91
|
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
|
|
67
92
|
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
68
93
|
_.each(args, arg => c(arg, state));
|
|
@@ -182,14 +207,6 @@ function isAsyncChunkPushExpression(node) {
|
|
|
182
207
|
callee.type === 'MemberExpression' &&
|
|
183
208
|
callee.property.name === 'push' &&
|
|
184
209
|
callee.object.type === 'AssignmentExpression' &&
|
|
185
|
-
callee.object.left.object &&
|
|
186
|
-
(
|
|
187
|
-
callee.object.left.object.name === 'window' ||
|
|
188
|
-
// `self` is a common output.globalObject value used to support both workers and browsers
|
|
189
|
-
callee.object.left.object.name === 'self' ||
|
|
190
|
-
// Webpack 4 uses `this` instead of `window`
|
|
191
|
-
callee.object.left.object.type === 'ThisExpression'
|
|
192
|
-
) &&
|
|
193
210
|
args.length === 1 &&
|
|
194
211
|
args[0].type === 'ArrayExpression' &&
|
|
195
212
|
mayBeAsyncChunkArguments(args[0].elements) &&
|
|
@@ -204,6 +221,18 @@ function mayBeAsyncChunkArguments(args) {
|
|
|
204
221
|
);
|
|
205
222
|
}
|
|
206
223
|
|
|
224
|
+
function isAsyncWebWorkerChunkExpression(node) {
|
|
225
|
+
const {callee, type, arguments: args} = node;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
type === 'CallExpression' &&
|
|
229
|
+
callee.type === 'MemberExpression' &&
|
|
230
|
+
args.length === 2 &&
|
|
231
|
+
isChunkIds(args[0]) &&
|
|
232
|
+
isModulesList(args[1])
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
207
236
|
function getModulesLocations(node) {
|
|
208
237
|
if (node.type === 'ObjectExpression') {
|
|
209
238
|
// Modules hash
|
package/src/utils.js
CHANGED
|
@@ -39,12 +39,15 @@ function createAssetsFilter(excludePatterns) {
|
|
|
39
39
|
* @desc get string of current time
|
|
40
40
|
* format: dd/MMM HH:mm
|
|
41
41
|
* */
|
|
42
|
-
exports.
|
|
42
|
+
exports.defaultTitle = function () {
|
|
43
43
|
const time = new Date();
|
|
44
44
|
const year = time.getFullYear();
|
|
45
45
|
const month = MONTHS[time.getMonth()];
|
|
46
46
|
const day = time.getDate();
|
|
47
|
-
const hour = time.getHours();
|
|
48
|
-
const minute = time.getMinutes();
|
|
49
|
-
|
|
47
|
+
const hour = `0${time.getHours()}`.slice(-2);
|
|
48
|
+
const minute = `0${time.getMinutes()}`.slice(-2);
|
|
49
|
+
|
|
50
|
+
const currentTime = `${day} ${month} ${year} at ${hour}:${minute}`;
|
|
51
|
+
|
|
52
|
+
return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`;
|
|
50
53
|
};
|
package/src/viewer.js
CHANGED
|
@@ -10,22 +10,28 @@ const opener = require('opener');
|
|
|
10
10
|
const mkdir = require('mkdirp');
|
|
11
11
|
const {bold} = require('chalk');
|
|
12
12
|
|
|
13
|
-
const utils = require('./utils');
|
|
14
13
|
const Logger = require('./Logger');
|
|
15
14
|
const analyzer = require('./analyzer');
|
|
16
15
|
|
|
17
16
|
const projectRoot = path.resolve(__dirname, '..');
|
|
18
17
|
const assetsRoot = path.join(projectRoot, 'public');
|
|
19
18
|
|
|
19
|
+
function resolveTitle(reportTitle) {
|
|
20
|
+
if (typeof reportTitle === 'function') {
|
|
21
|
+
return reportTitle();
|
|
22
|
+
} else {
|
|
23
|
+
return reportTitle;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
module.exports = {
|
|
21
28
|
startServer,
|
|
22
29
|
generateReport,
|
|
30
|
+
generateJSONReport,
|
|
23
31
|
// deprecated
|
|
24
32
|
start: startServer
|
|
25
33
|
};
|
|
26
34
|
|
|
27
|
-
const title = `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${utils.getCurrentTime()}]`;
|
|
28
|
-
|
|
29
35
|
async function startServer(bundleStats, opts) {
|
|
30
36
|
const {
|
|
31
37
|
port = 8888,
|
|
@@ -34,7 +40,8 @@ async function startServer(bundleStats, opts) {
|
|
|
34
40
|
bundleDir = null,
|
|
35
41
|
logger = new Logger(),
|
|
36
42
|
defaultSizes = 'parsed',
|
|
37
|
-
excludeAssets = null
|
|
43
|
+
excludeAssets = null,
|
|
44
|
+
reportTitle
|
|
38
45
|
} = opts || {};
|
|
39
46
|
|
|
40
47
|
const analyzerOpts = {logger, excludeAssets};
|
|
@@ -55,7 +62,7 @@ async function startServer(bundleStats, opts) {
|
|
|
55
62
|
app.use('/', (req, res) => {
|
|
56
63
|
res.render('viewer', {
|
|
57
64
|
mode: 'server',
|
|
58
|
-
title,
|
|
65
|
+
title: resolveTitle(reportTitle),
|
|
59
66
|
get chartData() { return chartData },
|
|
60
67
|
defaultSizes,
|
|
61
68
|
enableWebSocket: true,
|
|
@@ -121,7 +128,8 @@ async function startServer(bundleStats, opts) {
|
|
|
121
128
|
async function generateReport(bundleStats, opts) {
|
|
122
129
|
const {
|
|
123
130
|
openBrowser = true,
|
|
124
|
-
reportFilename
|
|
131
|
+
reportFilename,
|
|
132
|
+
reportTitle,
|
|
125
133
|
bundleDir = null,
|
|
126
134
|
logger = new Logger(),
|
|
127
135
|
defaultSizes = 'parsed',
|
|
@@ -137,7 +145,7 @@ async function generateReport(bundleStats, opts) {
|
|
|
137
145
|
`${projectRoot}/views/viewer.ejs`,
|
|
138
146
|
{
|
|
139
147
|
mode: 'static',
|
|
140
|
-
title,
|
|
148
|
+
title: resolveTitle(reportTitle),
|
|
141
149
|
chartData,
|
|
142
150
|
defaultSizes,
|
|
143
151
|
enableWebSocket: false,
|
|
@@ -158,9 +166,7 @@ async function generateReport(bundleStats, opts) {
|
|
|
158
166
|
mkdir.sync(path.dirname(reportFilepath));
|
|
159
167
|
fs.writeFileSync(reportFilepath, reportHtml);
|
|
160
168
|
|
|
161
|
-
logger.info(
|
|
162
|
-
`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`
|
|
163
|
-
);
|
|
169
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
|
164
170
|
|
|
165
171
|
if (openBrowser) {
|
|
166
172
|
opener(`file://${reportFilepath}`);
|
|
@@ -174,6 +180,19 @@ async function generateReport(bundleStats, opts) {
|
|
|
174
180
|
});
|
|
175
181
|
}
|
|
176
182
|
|
|
183
|
+
async function generateJSONReport(bundleStats, opts) {
|
|
184
|
+
const {reportFilename, bundleDir = null, logger = new Logger(), excludeAssets = null} = opts || {};
|
|
185
|
+
|
|
186
|
+
const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir);
|
|
187
|
+
|
|
188
|
+
if (!chartData) return;
|
|
189
|
+
|
|
190
|
+
mkdir.sync(path.dirname(reportFilename));
|
|
191
|
+
fs.writeFileSync(reportFilename, JSON.stringify(chartData));
|
|
192
|
+
|
|
193
|
+
logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
177
196
|
function getAssetContent(filename) {
|
|
178
197
|
const assetPath = path.join(assetsRoot, filename);
|
|
179
198
|
|