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