webpack-bundle-analyzer 4.10.2 → 5.0.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/README.md +30 -23
- package/lib/BundleAnalyzerPlugin.js +6 -27
- package/lib/Logger.js +2 -9
- package/lib/analyzer.js +53 -80
- package/lib/bin/analyzer.js +13 -21
- package/lib/index.js +0 -1
- package/lib/parseUtils.js +56 -74
- package/lib/sizeUtils.js +14 -0
- package/lib/statsUtils.js +0 -16
- package/lib/template.js +3 -10
- package/lib/tree/BaseFolder.js +3 -25
- package/lib/tree/ConcatenatedModule.js +11 -34
- package/lib/tree/ContentFolder.js +7 -12
- package/lib/tree/ContentModule.js +6 -12
- package/lib/tree/Folder.js +26 -28
- package/lib/tree/Module.js +20 -25
- package/lib/tree/Node.js +0 -7
- package/lib/tree/utils.js +8 -7
- package/lib/utils.js +2 -12
- package/lib/viewer.js +24 -34
- package/package.json +21 -22
- package/public/viewer.js +3 -3
- package/public/viewer.js.map +1 -1
package/lib/bin/analyzer.js
CHANGED
|
@@ -5,22 +5,16 @@ const {
|
|
|
5
5
|
resolve,
|
|
6
6
|
dirname
|
|
7
7
|
} = require('path');
|
|
8
|
-
|
|
9
8
|
const commander = require('commander');
|
|
10
|
-
|
|
11
9
|
const {
|
|
12
10
|
magenta
|
|
13
11
|
} = require('picocolors');
|
|
14
|
-
|
|
15
12
|
const analyzer = require('../analyzer');
|
|
16
|
-
|
|
17
13
|
const viewer = require('../viewer');
|
|
18
|
-
|
|
19
14
|
const Logger = require('../Logger');
|
|
20
|
-
|
|
21
15
|
const utils = require('../utils');
|
|
22
|
-
|
|
23
16
|
const SIZES = new Set(['stat', 'parsed', 'gzip']);
|
|
17
|
+
const COMPRESSION_ALGORITHMS = new Set(['gzip', 'brotli']);
|
|
24
18
|
const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
|
|
25
19
|
|
|
26
20
|
Arguments:
|
|
@@ -28,9 +22,10 @@ const program = commander.version(require('../../package.json').version).usage(`
|
|
|
28
22
|
bundleStatsFile Path to Webpack Stats JSON file.
|
|
29
23
|
bundleDir Directory containing all generated bundles.
|
|
30
24
|
You should provided it if you want analyzer to show you the real parsed module sizes.
|
|
31
|
-
By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server`,`static` or `json`.' + br('In `server` mode analyzer will start HTTP server to show bundle report.') + br('In `static` mode single HTML file with bundle report will be generated.') + br('In `json` mode single JSON file with bundle report will be generated.'), 'server').option(
|
|
32
|
-
//
|
|
33
|
-
|
|
25
|
+
By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server`,`static` or `json`.' + br('In `server` mode analyzer will start HTTP server to show bundle report.') + br('In `static` mode single HTML file with bundle report will be generated.') + br('In `json` mode single JSON file with bundle report will be generated.'), 'server').option(
|
|
26
|
+
// Had to make `host` parameter optional in order to let `-h` flag output help message
|
|
27
|
+
// Fixes https://github.com/webpack/webpack-bundle-analyzer/issues/239
|
|
28
|
+
'-h, --host [host]', 'Host that will be used in `server` mode to start HTTP server.', '127.0.0.1').option('-p, --port <n>', 'Port that will be used in `server` mode to start HTTP server.', 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.').option('-t, --title <title>', 'String to use in title element of html report.').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[...SIZES].join(', ')}`), 'parsed').option('--compression-algorithm <type>', 'Compression algorithm that will be used to calculate the compressed module sizes.' + br(`Possible values: ${[...COMPRESSION_ALGORITHMS].join(', ')}`), 'gzip').option('-O, --no-open', "Don't open report in default browser automatically.").option('-e, --exclude <regexp>', 'Assets that should be excluded from the report.' + br('Can be specified multiple times.'), array()).option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[...Logger.levels].join(', ')}`), Logger.defaultLevel).parse(process.argv);
|
|
34
29
|
let [bundleStatsFile, bundleDir] = program.args;
|
|
35
30
|
let {
|
|
36
31
|
mode,
|
|
@@ -39,43 +34,41 @@ let {
|
|
|
39
34
|
report: reportFilename,
|
|
40
35
|
title: reportTitle,
|
|
41
36
|
defaultSizes,
|
|
37
|
+
compressionAlgorithm,
|
|
42
38
|
logLevel,
|
|
43
39
|
open: openBrowser,
|
|
44
40
|
exclude: excludeAssets
|
|
45
41
|
} = program.opts();
|
|
46
42
|
const logger = new Logger(logLevel);
|
|
47
|
-
|
|
48
43
|
if (typeof reportTitle === 'undefined') {
|
|
49
44
|
reportTitle = utils.defaultTitle;
|
|
50
45
|
}
|
|
51
|
-
|
|
52
46
|
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
|
|
53
|
-
|
|
54
47
|
if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
|
|
55
48
|
showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
|
|
56
49
|
}
|
|
57
|
-
|
|
58
50
|
if (mode === 'server') {
|
|
59
51
|
if (!host) showHelp('Invalid host name');
|
|
60
52
|
port = port === 'auto' ? 0 : Number(port);
|
|
61
53
|
if (isNaN(port)) showHelp('Invalid port. Should be a number or `auto`');
|
|
62
54
|
}
|
|
63
|
-
|
|
55
|
+
if (!COMPRESSION_ALGORITHMS.has(compressionAlgorithm)) {
|
|
56
|
+
showHelp(`Invalid compression algorithm option. Possible values are: ${[...COMPRESSION_ALGORITHMS].join(', ')}`);
|
|
57
|
+
}
|
|
64
58
|
if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(', ')}`);
|
|
65
59
|
bundleStatsFile = resolve(bundleStatsFile);
|
|
66
60
|
if (!bundleDir) bundleDir = dirname(bundleStatsFile);
|
|
67
61
|
parseAndAnalyse(bundleStatsFile);
|
|
68
|
-
|
|
69
62
|
async function parseAndAnalyse(bundleStatsFile) {
|
|
70
63
|
try {
|
|
71
64
|
const bundleStats = await analyzer.readStatsFromFile(bundleStatsFile);
|
|
72
|
-
|
|
73
65
|
if (mode === 'server') {
|
|
74
66
|
viewer.startServer(bundleStats, {
|
|
75
67
|
openBrowser,
|
|
76
68
|
port,
|
|
77
69
|
host,
|
|
78
70
|
defaultSizes,
|
|
71
|
+
compressionAlgorithm,
|
|
79
72
|
reportTitle,
|
|
80
73
|
bundleDir,
|
|
81
74
|
excludeAssets,
|
|
@@ -88,6 +81,7 @@ async function parseAndAnalyse(bundleStatsFile) {
|
|
|
88
81
|
reportFilename: resolve(reportFilename || 'report.html'),
|
|
89
82
|
reportTitle,
|
|
90
83
|
defaultSizes,
|
|
84
|
+
compressionAlgorithm,
|
|
91
85
|
bundleDir,
|
|
92
86
|
excludeAssets,
|
|
93
87
|
logger: new Logger(logLevel)
|
|
@@ -95,6 +89,7 @@ async function parseAndAnalyse(bundleStatsFile) {
|
|
|
95
89
|
} else if (mode === 'json') {
|
|
96
90
|
viewer.generateJSONReport(bundleStats, {
|
|
97
91
|
reportFilename: resolve(reportFilename || 'report.json'),
|
|
92
|
+
compressionAlgorithm,
|
|
98
93
|
bundleDir,
|
|
99
94
|
excludeAssets,
|
|
100
95
|
logger: new Logger(logLevel)
|
|
@@ -106,17 +101,14 @@ async function parseAndAnalyse(bundleStatsFile) {
|
|
|
106
101
|
process.exit(1);
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
|
-
|
|
110
104
|
function showHelp(error) {
|
|
111
105
|
if (error) console.log(`\n ${magenta(error)}\n`);
|
|
112
106
|
program.outputHelp();
|
|
113
107
|
process.exit(1);
|
|
114
108
|
}
|
|
115
|
-
|
|
116
109
|
function br(str) {
|
|
117
|
-
return `\n${' '.repeat(
|
|
110
|
+
return `\n${' '.repeat(32)}${str}`;
|
|
118
111
|
}
|
|
119
|
-
|
|
120
112
|
function array() {
|
|
121
113
|
const arr = [];
|
|
122
114
|
return val => {
|
package/lib/index.js
CHANGED
package/lib/parseUtils.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
|
|
5
4
|
const acorn = require('acorn');
|
|
6
|
-
|
|
7
5
|
const walk = require('acorn-walk');
|
|
8
|
-
|
|
9
6
|
module.exports = {
|
|
10
7
|
parseBundle
|
|
11
8
|
};
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
function parseBundle(bundlePath, opts) {
|
|
10
|
+
const {
|
|
11
|
+
sourceType = 'script'
|
|
12
|
+
} = opts || {};
|
|
14
13
|
const content = fs.readFileSync(bundlePath, 'utf8');
|
|
15
14
|
const ast = acorn.parse(content, {
|
|
16
|
-
sourceType
|
|
15
|
+
sourceType,
|
|
17
16
|
// I believe in a bright future of ECMAScript!
|
|
18
17
|
// Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
|
|
19
18
|
// Seems like `acorn` supports such weird option value.
|
|
@@ -27,22 +26,21 @@ function parseBundle(bundlePath) {
|
|
|
27
26
|
ExpressionStatement(node, state, c) {
|
|
28
27
|
if (state.locations) return;
|
|
29
28
|
state.expressionStatementDepth++;
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (
|
|
30
|
+
// Webpack 5 stores modules in the the top-level IIFE
|
|
32
31
|
state.expressionStatementDepth === 1 && ast.body.includes(node) && isIIFE(node)) {
|
|
33
32
|
const fn = getIIFECallExpression(node);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
fn.arguments.length === 0 &&
|
|
33
|
+
if (
|
|
34
|
+
// It should not contain neither arguments
|
|
35
|
+
fn.arguments.length === 0 &&
|
|
36
|
+
// ...nor parameters
|
|
37
37
|
fn.callee.params.length === 0) {
|
|
38
38
|
// Modules are stored in the very first variable declaration as hash
|
|
39
39
|
const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
|
|
40
|
-
|
|
41
40
|
if (firstVariableDeclaration) {
|
|
42
41
|
for (const declaration of firstVariableDeclaration.declarations) {
|
|
43
42
|
if (declaration.init) {
|
|
44
43
|
state.locations = getModulesLocations(declaration.init);
|
|
45
|
-
|
|
46
44
|
if (state.locations) {
|
|
47
45
|
break;
|
|
48
46
|
}
|
|
@@ -51,93 +49,85 @@ function parseBundle(bundlePath) {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
|
-
|
|
55
52
|
if (!state.locations) {
|
|
56
53
|
c(node.expression, state);
|
|
57
54
|
}
|
|
58
|
-
|
|
59
55
|
state.expressionStatementDepth--;
|
|
60
56
|
},
|
|
61
|
-
|
|
62
57
|
AssignmentExpression(node, state) {
|
|
63
|
-
if (state.locations) return;
|
|
64
|
-
// exports.modules = {};
|
|
58
|
+
if (state.locations) return;
|
|
65
59
|
|
|
60
|
+
// Modules are stored in exports.modules:
|
|
61
|
+
// exports.modules = {};
|
|
66
62
|
const {
|
|
67
63
|
left,
|
|
68
64
|
right
|
|
69
65
|
} = node;
|
|
70
|
-
|
|
71
66
|
if (left && left.object && left.object.name === 'exports' && left.property && left.property.name === 'modules' && isModulesHash(right)) {
|
|
72
67
|
state.locations = getModulesLocations(right);
|
|
73
68
|
}
|
|
74
69
|
},
|
|
75
|
-
|
|
76
70
|
CallExpression(node, state, c) {
|
|
77
71
|
if (state.locations) return;
|
|
78
|
-
const args = node.arguments;
|
|
72
|
+
const args = node.arguments;
|
|
73
|
+
|
|
74
|
+
// Main chunk with webpack loader.
|
|
79
75
|
// Modules are stored in first argument:
|
|
80
76
|
// (function (...) {...})(<modules>)
|
|
81
|
-
|
|
82
77
|
if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
|
|
83
78
|
state.locations = getModulesLocations(args[0]);
|
|
84
79
|
return;
|
|
85
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Async Webpack < v4 chunk without webpack loader.
|
|
86
83
|
// webpackJsonp([<chunks>], <modules>, ...)
|
|
87
84
|
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
88
|
-
|
|
89
|
-
|
|
90
85
|
if (node.callee.type === 'Identifier' && mayBeAsyncChunkArguments(args) && isModulesList(args[1])) {
|
|
91
86
|
state.locations = getModulesLocations(args[1]);
|
|
92
87
|
return;
|
|
93
|
-
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Async Webpack v4 chunk without webpack loader.
|
|
94
91
|
// (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
|
|
95
92
|
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
96
|
-
|
|
97
|
-
|
|
98
93
|
if (isAsyncChunkPushExpression(node)) {
|
|
99
94
|
state.locations = getModulesLocations(args[0].elements[1]);
|
|
100
95
|
return;
|
|
101
|
-
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Webpack v4 WebWorkerChunkTemplatePlugin
|
|
102
99
|
// globalObject.chunkCallbackName([<chunks>],<modules>, ...);
|
|
103
100
|
// Both globalObject and chunkCallbackName can be changed through the config, so we can't check them.
|
|
104
|
-
|
|
105
|
-
|
|
106
101
|
if (isAsyncWebWorkerChunkExpression(node)) {
|
|
107
102
|
state.locations = getModulesLocations(args[1]);
|
|
108
103
|
return;
|
|
109
|
-
}
|
|
110
|
-
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
111
|
-
|
|
104
|
+
}
|
|
112
105
|
|
|
106
|
+
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
|
|
107
|
+
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
113
108
|
args.forEach(arg => c(arg, state));
|
|
114
109
|
}
|
|
115
|
-
|
|
116
110
|
});
|
|
117
111
|
const modules = {};
|
|
118
|
-
|
|
119
112
|
if (walkState.locations) {
|
|
120
113
|
Object.entries(walkState.locations).forEach(([id, loc]) => {
|
|
121
114
|
modules[id] = content.slice(loc.start, loc.end);
|
|
122
115
|
});
|
|
123
116
|
}
|
|
124
|
-
|
|
125
117
|
return {
|
|
126
118
|
modules,
|
|
127
119
|
src: content,
|
|
128
120
|
runtimeSrc: getBundleRuntime(content, walkState.locations)
|
|
129
121
|
};
|
|
130
122
|
}
|
|
123
|
+
|
|
131
124
|
/**
|
|
132
125
|
* Returns bundle source except modules
|
|
133
126
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
127
|
function getBundleRuntime(content, modulesLocations) {
|
|
137
128
|
const sortedLocations = Object.values(modulesLocations || {}).sort((a, b) => a.start - b.start);
|
|
138
129
|
let result = '';
|
|
139
130
|
let lastIndex = 0;
|
|
140
|
-
|
|
141
131
|
for (const {
|
|
142
132
|
start,
|
|
143
133
|
end
|
|
@@ -145,14 +135,11 @@ function getBundleRuntime(content, modulesLocations) {
|
|
|
145
135
|
result += content.slice(lastIndex, start);
|
|
146
136
|
lastIndex = end;
|
|
147
137
|
}
|
|
148
|
-
|
|
149
138
|
return result + content.slice(lastIndex, content.length);
|
|
150
139
|
}
|
|
151
|
-
|
|
152
140
|
function isIIFE(node) {
|
|
153
141
|
return node.type === 'ExpressionStatement' && (node.expression.type === 'CallExpression' || node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression');
|
|
154
142
|
}
|
|
155
|
-
|
|
156
143
|
function getIIFECallExpression(node) {
|
|
157
144
|
if (node.expression.type === 'UnaryExpression') {
|
|
158
145
|
return node.expression.argument;
|
|
@@ -160,59 +147,59 @@ function getIIFECallExpression(node) {
|
|
|
160
147
|
return node.expression;
|
|
161
148
|
}
|
|
162
149
|
}
|
|
163
|
-
|
|
164
150
|
function isModulesList(node) {
|
|
165
|
-
return isSimpleModulesList(node) ||
|
|
151
|
+
return isSimpleModulesList(node) ||
|
|
152
|
+
// Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
|
|
166
153
|
isOptimizedModulesArray(node);
|
|
167
154
|
}
|
|
168
|
-
|
|
169
155
|
function isSimpleModulesList(node) {
|
|
170
|
-
return (
|
|
171
|
-
|
|
156
|
+
return (
|
|
157
|
+
// Modules are contained in hash. Keys are module ids.
|
|
158
|
+
isModulesHash(node) ||
|
|
159
|
+
// Modules are contained in array. Indexes are module ids.
|
|
172
160
|
isModulesArray(node)
|
|
173
161
|
);
|
|
174
162
|
}
|
|
175
|
-
|
|
176
163
|
function isModulesHash(node) {
|
|
177
164
|
return node.type === 'ObjectExpression' && node.properties.map(node => node.value).every(isModuleWrapper);
|
|
178
165
|
}
|
|
179
|
-
|
|
180
166
|
function isModulesArray(node) {
|
|
181
|
-
return node.type === 'ArrayExpression' && node.elements.every(elem =>
|
|
167
|
+
return node.type === 'ArrayExpression' && node.elements.every(elem =>
|
|
168
|
+
// Some of array items may be skipped because there is no module with such id
|
|
182
169
|
!elem || isModuleWrapper(elem));
|
|
183
170
|
}
|
|
184
|
-
|
|
185
171
|
function isOptimizedModulesArray(node) {
|
|
186
172
|
// Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
|
|
187
173
|
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
|
|
188
174
|
// The `<minimum ID>` + array indexes are module ids
|
|
189
|
-
return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
|
|
190
|
-
|
|
191
|
-
node.callee.
|
|
175
|
+
return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
|
|
176
|
+
// Make sure the object called is `Array(<some number>)`
|
|
177
|
+
node.callee.object.type === 'CallExpression' && node.callee.object.callee.type === 'Identifier' && node.callee.object.callee.name === 'Array' && node.callee.object.arguments.length === 1 && isNumericId(node.callee.object.arguments[0]) &&
|
|
178
|
+
// Make sure the property X called for `Array(<some number>).X` is `concat`
|
|
179
|
+
node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' &&
|
|
180
|
+
// Make sure exactly one array is passed in to `concat`
|
|
192
181
|
node.arguments.length === 1 && isModulesArray(node.arguments[0]);
|
|
193
182
|
}
|
|
194
|
-
|
|
195
183
|
function isModuleWrapper(node) {
|
|
196
|
-
return (
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
return (
|
|
185
|
+
// It's an anonymous function expression that wraps module
|
|
186
|
+
(node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id ||
|
|
187
|
+
// If `DedupePlugin` is used it can be an ID of duplicated module...
|
|
188
|
+
isModuleId(node) ||
|
|
189
|
+
// or an array of shape [<module_id>, ...args]
|
|
199
190
|
node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0])
|
|
200
191
|
);
|
|
201
192
|
}
|
|
202
|
-
|
|
203
193
|
function isModuleId(node) {
|
|
204
194
|
return node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string');
|
|
205
195
|
}
|
|
206
|
-
|
|
207
196
|
function isNumericId(node) {
|
|
208
197
|
return node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0;
|
|
209
198
|
}
|
|
210
|
-
|
|
211
199
|
function isChunkIds(node) {
|
|
212
200
|
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
|
213
201
|
return node.type === 'ArrayExpression' && node.elements.every(isModuleId);
|
|
214
202
|
}
|
|
215
|
-
|
|
216
203
|
function isAsyncChunkPushExpression(node) {
|
|
217
204
|
const {
|
|
218
205
|
callee,
|
|
@@ -220,11 +207,9 @@ function isAsyncChunkPushExpression(node) {
|
|
|
220
207
|
} = node;
|
|
221
208
|
return callee.type === 'MemberExpression' && callee.property.name === 'push' && callee.object.type === 'AssignmentExpression' && args.length === 1 && args[0].type === 'ArrayExpression' && mayBeAsyncChunkArguments(args[0].elements) && isModulesList(args[0].elements[1]);
|
|
222
209
|
}
|
|
223
|
-
|
|
224
210
|
function mayBeAsyncChunkArguments(args) {
|
|
225
211
|
return args.length >= 2 && isChunkIds(args[0]);
|
|
226
212
|
}
|
|
227
|
-
|
|
228
213
|
function isAsyncWebWorkerChunkExpression(node) {
|
|
229
214
|
const {
|
|
230
215
|
callee,
|
|
@@ -233,7 +218,6 @@ function isAsyncWebWorkerChunkExpression(node) {
|
|
|
233
218
|
} = node;
|
|
234
219
|
return type === 'CallExpression' && callee.type === 'MemberExpression' && args.length === 2 && isChunkIds(args[0]) && isModulesList(args[1]);
|
|
235
220
|
}
|
|
236
|
-
|
|
237
221
|
function getModulesLocations(node) {
|
|
238
222
|
if (node.type === 'ObjectExpression') {
|
|
239
223
|
// Modules hash
|
|
@@ -244,28 +228,26 @@ function getModulesLocations(node) {
|
|
|
244
228
|
return result;
|
|
245
229
|
}, {});
|
|
246
230
|
}
|
|
247
|
-
|
|
248
231
|
const isOptimizedArray = node.type === 'CallExpression';
|
|
249
|
-
|
|
250
232
|
if (node.type === 'ArrayExpression' || isOptimizedArray) {
|
|
251
233
|
// Modules array or optimized array
|
|
252
|
-
const minId = isOptimizedArray ?
|
|
253
|
-
|
|
234
|
+
const minId = isOptimizedArray ?
|
|
235
|
+
// Get the [minId] value from the Array() call first argument literal value
|
|
236
|
+
node.callee.object.arguments[0].value :
|
|
237
|
+
// `0` for simple array
|
|
254
238
|
0;
|
|
255
|
-
const modulesNodes = isOptimizedArray ?
|
|
239
|
+
const modulesNodes = isOptimizedArray ?
|
|
240
|
+
// The modules reside in the `concat()` function call arguments
|
|
256
241
|
node.arguments[0].elements : node.elements;
|
|
257
242
|
return modulesNodes.reduce((result, moduleNode, i) => {
|
|
258
243
|
if (moduleNode) {
|
|
259
244
|
result[i + minId] = getModuleLocation(moduleNode);
|
|
260
245
|
}
|
|
261
|
-
|
|
262
246
|
return result;
|
|
263
247
|
}, {});
|
|
264
248
|
}
|
|
265
|
-
|
|
266
249
|
return {};
|
|
267
250
|
}
|
|
268
|
-
|
|
269
251
|
function getModuleLocation(node) {
|
|
270
252
|
return {
|
|
271
253
|
start: node.start,
|
package/lib/sizeUtils.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getCompressedSize = getCompressedSize;
|
|
7
|
+
const zlib = require('zlib');
|
|
8
|
+
function getCompressedSize(compressionAlgorithm, input) {
|
|
9
|
+
if (compressionAlgorithm === 'gzip') return zlib.gzipSync(input, {
|
|
10
|
+
level: 9
|
|
11
|
+
}).length;
|
|
12
|
+
if (compressionAlgorithm === 'brotli') return zlib.brotliCompressSync(input).length;
|
|
13
|
+
throw new Error(`Unsupported compression algorithm: ${compressionAlgorithm}.`);
|
|
14
|
+
}
|
package/lib/statsUtils.js
CHANGED
|
@@ -3,31 +3,25 @@
|
|
|
3
3
|
const {
|
|
4
4
|
createWriteStream
|
|
5
5
|
} = require('fs');
|
|
6
|
-
|
|
7
6
|
const {
|
|
8
7
|
Readable
|
|
9
8
|
} = require('stream');
|
|
10
|
-
|
|
11
9
|
class StatsSerializeStream extends Readable {
|
|
12
10
|
constructor(stats) {
|
|
13
11
|
super();
|
|
14
12
|
this._indentLevel = 0;
|
|
15
13
|
this._stringifier = this._stringify(stats);
|
|
16
14
|
}
|
|
17
|
-
|
|
18
15
|
get _indent() {
|
|
19
16
|
return ' '.repeat(this._indentLevel);
|
|
20
17
|
}
|
|
21
|
-
|
|
22
18
|
_read() {
|
|
23
19
|
let readMore = true;
|
|
24
|
-
|
|
25
20
|
while (readMore) {
|
|
26
21
|
const {
|
|
27
22
|
value,
|
|
28
23
|
done
|
|
29
24
|
} = this._stringifier.next();
|
|
30
|
-
|
|
31
25
|
if (done) {
|
|
32
26
|
this.push(null);
|
|
33
27
|
readMore = false;
|
|
@@ -36,7 +30,6 @@ class StatsSerializeStream extends Readable {
|
|
|
36
30
|
}
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
|
-
|
|
40
33
|
*_stringify(obj) {
|
|
41
34
|
if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
|
|
42
35
|
yield JSON.stringify(obj);
|
|
@@ -44,17 +37,14 @@ class StatsSerializeStream extends Readable {
|
|
|
44
37
|
yield '[';
|
|
45
38
|
this._indentLevel++;
|
|
46
39
|
let isFirst = true;
|
|
47
|
-
|
|
48
40
|
for (let item of obj) {
|
|
49
41
|
if (item === undefined) {
|
|
50
42
|
item = null;
|
|
51
43
|
}
|
|
52
|
-
|
|
53
44
|
yield `${isFirst ? '' : ','}\n${this._indent}`;
|
|
54
45
|
yield* this._stringify(item);
|
|
55
46
|
isFirst = false;
|
|
56
47
|
}
|
|
57
|
-
|
|
58
48
|
this._indentLevel--;
|
|
59
49
|
yield obj.length ? `\n${this._indent}]` : ']';
|
|
60
50
|
} else {
|
|
@@ -62,27 +52,21 @@ class StatsSerializeStream extends Readable {
|
|
|
62
52
|
this._indentLevel++;
|
|
63
53
|
let isFirst = true;
|
|
64
54
|
const entries = Object.entries(obj);
|
|
65
|
-
|
|
66
55
|
for (const [itemKey, itemValue] of entries) {
|
|
67
56
|
if (itemValue === undefined) {
|
|
68
57
|
continue;
|
|
69
58
|
}
|
|
70
|
-
|
|
71
59
|
yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
|
|
72
60
|
yield* this._stringify(itemValue);
|
|
73
61
|
isFirst = false;
|
|
74
62
|
}
|
|
75
|
-
|
|
76
63
|
this._indentLevel--;
|
|
77
64
|
yield entries.length ? `\n${this._indent}}` : '}';
|
|
78
65
|
}
|
|
79
66
|
}
|
|
80
|
-
|
|
81
67
|
}
|
|
82
|
-
|
|
83
68
|
exports.StatsSerializeStream = StatsSerializeStream;
|
|
84
69
|
exports.writeStats = writeStats;
|
|
85
|
-
|
|
86
70
|
async function writeStats(stats, filepath) {
|
|
87
71
|
return new Promise((resolve, reject) => {
|
|
88
72
|
new StatsSerializeStream(stats).on('end', resolve).on('error', reject).pipe(createWriteStream(filepath));
|
package/lib/template.js
CHANGED
|
@@ -2,38 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint-disable max-len */
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
6
5
|
const fs = require('fs');
|
|
7
|
-
|
|
8
6
|
const {
|
|
9
7
|
escape
|
|
10
8
|
} = require('html-escaper');
|
|
11
|
-
|
|
12
9
|
const projectRoot = path.resolve(__dirname, '..');
|
|
13
10
|
const assetsRoot = path.join(projectRoot, 'public');
|
|
14
11
|
exports.renderViewer = renderViewer;
|
|
12
|
+
|
|
15
13
|
/**
|
|
16
14
|
* Escapes `<` characters in JSON to safely use it in `<script>` tag.
|
|
17
15
|
*/
|
|
18
|
-
|
|
19
16
|
function escapeJson(json) {
|
|
20
17
|
return JSON.stringify(json).replace(/</gu, '\\u003c');
|
|
21
18
|
}
|
|
22
|
-
|
|
23
19
|
function getAssetContent(filename) {
|
|
24
20
|
const assetPath = path.join(assetsRoot, filename);
|
|
25
|
-
|
|
26
21
|
if (!assetPath.startsWith(assetsRoot)) {
|
|
27
22
|
throw new Error(`"${filename}" is outside of the assets root`);
|
|
28
23
|
}
|
|
29
|
-
|
|
30
24
|
return fs.readFileSync(assetPath, 'utf8');
|
|
31
25
|
}
|
|
32
|
-
|
|
33
26
|
function html(strings, ...values) {
|
|
34
27
|
return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
|
|
35
28
|
}
|
|
36
|
-
|
|
37
29
|
function getScript(filename, mode) {
|
|
38
30
|
if (mode === 'static') {
|
|
39
31
|
return `<!-- ${escape(filename)} -->
|
|
@@ -42,13 +34,13 @@ function getScript(filename, mode) {
|
|
|
42
34
|
return `<script src="${escape(filename)}"></script>`;
|
|
43
35
|
}
|
|
44
36
|
}
|
|
45
|
-
|
|
46
37
|
function renderViewer({
|
|
47
38
|
title,
|
|
48
39
|
enableWebSocket,
|
|
49
40
|
chartData,
|
|
50
41
|
entrypoints,
|
|
51
42
|
defaultSizes,
|
|
43
|
+
compressionAlgorithm,
|
|
52
44
|
mode
|
|
53
45
|
} = {}) {
|
|
54
46
|
return html`<!DOCTYPE html>
|
|
@@ -71,6 +63,7 @@ function renderViewer({
|
|
|
71
63
|
window.chartData = ${escapeJson(chartData)};
|
|
72
64
|
window.entrypoints = ${escapeJson(entrypoints)};
|
|
73
65
|
window.defaultSizes = ${escapeJson(defaultSizes)};
|
|
66
|
+
window.compressionAlgorithm = ${escapeJson(compressionAlgorithm)};
|
|
74
67
|
</script>
|
|
75
68
|
</body>
|
|
76
69
|
</html>`;
|