webpack-bundle-analyzer 2.11.2 → 2.13.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/CHANGELOG.md +28 -0
- package/README.md +11 -11
- package/lib/BundleAnalyzerPlugin.js +6 -2
- package/lib/analyzer.js +22 -14
- package/lib/bin/analyzer.js +12 -1
- package/lib/parseUtils.js +93 -97
- package/lib/tree/BaseFolder.js +2 -3
- package/lib/tree/Folder.js +2 -2
- package/lib/utils.js +40 -0
- package/lib/viewer.js +16 -8
- package/package.json +1 -1
- package/public/viewer.js +1 -1
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +6 -2
- package/src/analyzer.js +19 -15
- package/src/bin/analyzer.js +17 -0
- package/src/parseUtils.js +132 -129
- package/src/tree/BaseFolder.js +1 -4
- package/src/tree/Folder.js +2 -2
- package/src/utils.js +34 -0
- package/src/viewer.js +13 -8
|
@@ -19,6 +19,7 @@ class BundleAnalyzerPlugin {
|
|
|
19
19
|
generateStatsFile: false,
|
|
20
20
|
statsFilename: 'stats.json',
|
|
21
21
|
statsOptions: null,
|
|
22
|
+
excludeAssets: null,
|
|
22
23
|
logLevel: 'info',
|
|
23
24
|
// deprecated
|
|
24
25
|
startAnalyzer: true,
|
|
@@ -73,6 +74,7 @@ class BundleAnalyzerPlugin {
|
|
|
73
74
|
|
|
74
75
|
try {
|
|
75
76
|
await bfj.write(statsFilepath, stats, {
|
|
77
|
+
space: 2,
|
|
76
78
|
promises: 'ignore',
|
|
77
79
|
buffers: 'ignore',
|
|
78
80
|
maps: 'ignore',
|
|
@@ -100,7 +102,8 @@ class BundleAnalyzerPlugin {
|
|
|
100
102
|
port: this.opts.analyzerPort,
|
|
101
103
|
bundleDir: this.getBundleDirFromCompiler(),
|
|
102
104
|
logger: this.logger,
|
|
103
|
-
defaultSizes: this.opts.defaultSizes
|
|
105
|
+
defaultSizes: this.opts.defaultSizes,
|
|
106
|
+
excludeAssets: this.opts.excludeAssets
|
|
104
107
|
});
|
|
105
108
|
}
|
|
106
109
|
}
|
|
@@ -111,7 +114,8 @@ class BundleAnalyzerPlugin {
|
|
|
111
114
|
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
|
|
112
115
|
bundleDir: this.getBundleDirFromCompiler(),
|
|
113
116
|
logger: this.logger,
|
|
114
|
-
defaultSizes: this.opts.defaultSizes
|
|
117
|
+
defaultSizes: this.opts.defaultSizes,
|
|
118
|
+
excludeAssets: this.opts.excludeAssets
|
|
115
119
|
});
|
|
116
120
|
}
|
|
117
121
|
|
package/src/analyzer.js
CHANGED
|
@@ -7,6 +7,7 @@ const gzipSize = require('gzip-size');
|
|
|
7
7
|
const Logger = require('./Logger');
|
|
8
8
|
const Folder = require('./tree/Folder').default;
|
|
9
9
|
const { parseBundle } = require('./parseUtils');
|
|
10
|
+
const { createAssetsFilter } = require('./utils');
|
|
10
11
|
|
|
11
12
|
const FILENAME_QUERY_REGEXP = /\?.*$/;
|
|
12
13
|
|
|
@@ -17,9 +18,12 @@ module.exports = {
|
|
|
17
18
|
|
|
18
19
|
function getViewerData(bundleStats, bundleDir, opts) {
|
|
19
20
|
const {
|
|
20
|
-
logger = new Logger()
|
|
21
|
+
logger = new Logger(),
|
|
22
|
+
excludeAssets = null
|
|
21
23
|
} = opts || {};
|
|
22
24
|
|
|
25
|
+
const isAssetIncluded = createAssetsFilter(excludeAssets);
|
|
26
|
+
|
|
23
27
|
// Sometimes all the information is located in `children` array (e.g. problem in #10)
|
|
24
28
|
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
|
|
25
29
|
bundleStats = bundleStats.children[0];
|
|
@@ -31,7 +35,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
31
35
|
// See #22
|
|
32
36
|
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
|
|
33
37
|
|
|
34
|
-
return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks);
|
|
38
|
+
return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
|
|
35
39
|
});
|
|
36
40
|
|
|
37
41
|
// Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
|
|
@@ -49,29 +53,27 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
49
53
|
try {
|
|
50
54
|
bundleInfo = parseBundle(assetFile);
|
|
51
55
|
} catch (err) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!bundleInfo) {
|
|
56
|
-
logger.warn(
|
|
57
|
-
`\nCouldn't parse bundle asset "${assetFile}".\n` +
|
|
58
|
-
'Analyzer will use module sizes from stats file.\n'
|
|
59
|
-
);
|
|
60
|
-
parsedModules = null;
|
|
61
|
-
bundlesSources = null;
|
|
62
|
-
break;
|
|
56
|
+
const msg = (err.code === 'ENOENT') ? 'no such file' : err.message;
|
|
57
|
+
logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
|
|
58
|
+
continue;
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
bundlesSources[statAsset.name] = bundleInfo.src;
|
|
66
62
|
_.assign(parsedModules, bundleInfo.modules);
|
|
67
63
|
}
|
|
64
|
+
|
|
65
|
+
if (_.isEmpty(bundlesSources)) {
|
|
66
|
+
bundlesSources = null;
|
|
67
|
+
parsedModules = null;
|
|
68
|
+
logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
|
|
69
|
+
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const modules = getBundleModules(bundleStats);
|
|
71
73
|
const assets = _.transform(bundleStats.assets, (result, statAsset) => {
|
|
72
74
|
const asset = result[statAsset.name] = _.pick(statAsset, 'size');
|
|
73
75
|
|
|
74
|
-
if (bundlesSources) {
|
|
76
|
+
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
|
|
75
77
|
asset.parsedSize = bundlesSources[statAsset.name].length;
|
|
76
78
|
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
|
|
77
79
|
}
|
|
@@ -94,7 +96,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
|
|
|
94
96
|
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
|
|
95
97
|
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
|
|
96
98
|
// be the size of minified bundle.
|
|
97
|
-
|
|
99
|
+
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
|
|
100
|
+
statSize: asset.tree.size || asset.size,
|
|
98
101
|
parsedSize: asset.parsedSize,
|
|
99
102
|
gzipSize: asset.gzipSize,
|
|
100
103
|
groups: _.invokeMap(asset.tree.children, 'toChartData')
|
|
@@ -119,6 +122,7 @@ function getBundleModules(bundleStats) {
|
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
function assetHasModule(statAsset, statModule) {
|
|
125
|
+
// Checking if this module is the part of asset chunks
|
|
122
126
|
return _.some(statModule.chunks, moduleChunk =>
|
|
123
127
|
_.includes(statAsset.chunks, moduleChunk)
|
|
124
128
|
);
|
package/src/bin/analyzer.js
CHANGED
|
@@ -57,6 +57,12 @@ const program = commander
|
|
|
57
57
|
'-O, --no-open',
|
|
58
58
|
"Don't open report in default browser automatically."
|
|
59
59
|
)
|
|
60
|
+
.option(
|
|
61
|
+
'-e, --exclude <regexp>',
|
|
62
|
+
'Assets that should be excluded from the report.' +
|
|
63
|
+
br('Can be specified multiple times.'),
|
|
64
|
+
array()
|
|
65
|
+
)
|
|
60
66
|
.option(
|
|
61
67
|
'-l, --log-level <level>',
|
|
62
68
|
'Log level.' +
|
|
@@ -73,6 +79,7 @@ let {
|
|
|
73
79
|
defaultSizes,
|
|
74
80
|
logLevel,
|
|
75
81
|
open: openBrowser,
|
|
82
|
+
exclude: excludeAssets,
|
|
76
83
|
args: [bundleStatsFile, bundleDir]
|
|
77
84
|
} = program;
|
|
78
85
|
const logger = new Logger(logLevel);
|
|
@@ -103,6 +110,7 @@ if (mode === 'server') {
|
|
|
103
110
|
host,
|
|
104
111
|
defaultSizes,
|
|
105
112
|
bundleDir,
|
|
113
|
+
excludeAssets,
|
|
106
114
|
logger: new Logger(logLevel)
|
|
107
115
|
});
|
|
108
116
|
} else {
|
|
@@ -111,6 +119,7 @@ if (mode === 'server') {
|
|
|
111
119
|
reportFilename: resolve(reportFilename),
|
|
112
120
|
defaultSizes,
|
|
113
121
|
bundleDir,
|
|
122
|
+
excludeAssets,
|
|
114
123
|
logger: new Logger(logLevel)
|
|
115
124
|
});
|
|
116
125
|
}
|
|
@@ -124,3 +133,11 @@ function showHelp(error) {
|
|
|
124
133
|
function br(str) {
|
|
125
134
|
return `\n${_.repeat(' ', 28)}${str}`;
|
|
126
135
|
}
|
|
136
|
+
|
|
137
|
+
function array() {
|
|
138
|
+
const arr = [];
|
|
139
|
+
return (val) => {
|
|
140
|
+
arr.push(val);
|
|
141
|
+
return arr;
|
|
142
|
+
};
|
|
143
|
+
}
|
package/src/parseUtils.js
CHANGED
|
@@ -26,59 +26,40 @@ function parseBundle(bundlePath) {
|
|
|
26
26
|
walkState,
|
|
27
27
|
{
|
|
28
28
|
CallExpression(node, state, c) {
|
|
29
|
-
if (state.
|
|
29
|
+
if (state.locations) return;
|
|
30
30
|
|
|
31
31
|
const args = node.arguments;
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
// Modules are stored in second argument, after chunk ids:
|
|
35
|
-
// webpackJsonp([<chunks>], <modules>, ...)
|
|
36
|
-
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
37
|
-
if (
|
|
38
|
-
node.callee.type === 'Identifier' &&
|
|
39
|
-
args.length >= 2 &&
|
|
40
|
-
isArgumentContainsChunkIds(args[0]) &&
|
|
41
|
-
isArgumentContainsModulesList(args[1])
|
|
42
|
-
) {
|
|
43
|
-
state.locations = getModulesLocationFromFunctionArgument(args[1]);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Additional bundle without webpack loader, with module IDs optimized.
|
|
48
|
-
// Modules are stored in second arguments Array(n).concat() call
|
|
49
|
-
// webpackJsonp([<chunks>], Array([minimum ID]).concat([<module>, <module>, ...]))
|
|
50
|
-
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
51
|
-
if (
|
|
52
|
-
node.callee.type === 'Identifier' &&
|
|
53
|
-
(args.length === 2 || args.length === 3) &&
|
|
54
|
-
isArgumentContainsChunkIds(args[0]) &&
|
|
55
|
-
isArgumentArrayConcatContainingChunks(args[1])
|
|
56
|
-
) {
|
|
57
|
-
state.locations = getModulesLocationFromArrayConcat(args[1]);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Main bundle with webpack loader
|
|
33
|
+
// Main chunk with webpack loader.
|
|
62
34
|
// Modules are stored in first argument:
|
|
63
35
|
// (function (...) {...})(<modules>)
|
|
64
36
|
if (
|
|
65
37
|
node.callee.type === 'FunctionExpression' &&
|
|
66
38
|
!node.callee.id &&
|
|
67
39
|
args.length === 1 &&
|
|
68
|
-
|
|
40
|
+
isSimpleModulesList(args[0])
|
|
69
41
|
) {
|
|
70
|
-
state.locations =
|
|
42
|
+
state.locations = getModulesLocations(args[0]);
|
|
71
43
|
return;
|
|
72
44
|
}
|
|
73
45
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
46
|
+
// Async Webpack < v4 chunk without webpack loader.
|
|
47
|
+
// webpackJsonp([<chunks>], <modules>, ...)
|
|
48
|
+
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
76
49
|
if (
|
|
77
|
-
|
|
78
|
-
args
|
|
79
|
-
|
|
50
|
+
node.callee.type === 'Identifier' &&
|
|
51
|
+
mayBeAsyncChunkArguments(args) &&
|
|
52
|
+
isModulesList(args[1])
|
|
80
53
|
) {
|
|
81
|
-
state.locations =
|
|
54
|
+
state.locations = getModulesLocations(args[1]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Async Webpack v4 chunk without webpack loader.
|
|
59
|
+
// (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
|
|
60
|
+
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
|
|
61
|
+
if (isAsyncChunkPushExpression(node)) {
|
|
62
|
+
state.locations = getModulesLocations(args[0].elements[1]);
|
|
82
63
|
return;
|
|
83
64
|
}
|
|
84
65
|
|
|
@@ -89,92 +70,78 @@ function parseBundle(bundlePath) {
|
|
|
89
70
|
}
|
|
90
71
|
);
|
|
91
72
|
|
|
92
|
-
|
|
93
|
-
|
|
73
|
+
let modules;
|
|
74
|
+
|
|
75
|
+
if (walkState.locations) {
|
|
76
|
+
modules = _.mapValues(walkState.locations,
|
|
77
|
+
loc => content.slice(loc.start, loc.end)
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
modules = {};
|
|
94
81
|
}
|
|
95
82
|
|
|
96
83
|
return {
|
|
97
84
|
src: content,
|
|
98
|
-
modules
|
|
99
|
-
loc => content.slice(loc.start, loc.end)
|
|
100
|
-
)
|
|
85
|
+
modules
|
|
101
86
|
};
|
|
102
87
|
}
|
|
103
88
|
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
function isModulesList(node) {
|
|
90
|
+
return (
|
|
91
|
+
isSimpleModulesList(node) ||
|
|
92
|
+
// Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
|
|
93
|
+
isOptimizedModulesArray(node)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isSimpleModulesList(node) {
|
|
98
|
+
return (
|
|
99
|
+
// Modules are contained in hash. Keys are module ids.
|
|
100
|
+
isModulesHash(node) ||
|
|
101
|
+
// Modules are contained in array. Indexes are module ids.
|
|
102
|
+
isModulesArray(node)
|
|
103
|
+
);
|
|
107
104
|
}
|
|
108
105
|
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
function isModulesHash(node) {
|
|
107
|
+
return (
|
|
108
|
+
node.type === 'ObjectExpression' &&
|
|
109
|
+
_(node.properties)
|
|
112
110
|
.map('value')
|
|
113
|
-
.every(isModuleWrapper)
|
|
114
|
-
|
|
111
|
+
.every(isModuleWrapper)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
function isModulesArray(node) {
|
|
116
|
+
return (
|
|
117
|
+
node.type === 'ArrayExpression' &&
|
|
118
|
+
_.every(node.elements, elem =>
|
|
120
119
|
// Some of array items may be skipped because there is no module with such id
|
|
121
120
|
!elem ||
|
|
122
121
|
isModuleWrapper(elem)
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function isArgumentContainingChunkIdsAndModulesList(arg) {
|
|
130
|
-
if (
|
|
131
|
-
arg.type === 'ArrayExpression' &&
|
|
132
|
-
arg.elements.length >= 2 &&
|
|
133
|
-
isArgumentContainsChunkIds(arg.elements[0]) &&
|
|
134
|
-
isArgumentContainsModulesList(arg.elements[1])
|
|
135
|
-
) {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
122
|
+
)
|
|
123
|
+
);
|
|
139
124
|
}
|
|
140
125
|
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
function isOptimizedModulesArray(node) {
|
|
127
|
+
// Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
|
|
128
|
+
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
|
|
129
|
+
// The `<minimum ID>` + array indexes are module ids
|
|
130
|
+
return (
|
|
131
|
+
node.type === 'CallExpression' &&
|
|
132
|
+
node.callee.type === 'MemberExpression' &&
|
|
145
133
|
// Make sure the object called is `Array(<some number>)`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
isNumericId(
|
|
134
|
+
node.callee.object.type === 'CallExpression' &&
|
|
135
|
+
node.callee.object.callee.type === 'Identifier' &&
|
|
136
|
+
node.callee.object.callee.name === 'Array' &&
|
|
137
|
+
node.callee.object.arguments.length === 1 &&
|
|
138
|
+
isNumericId(node.callee.object.arguments[0]) &&
|
|
151
139
|
// Make sure the property X called for `Array(<some number>).X` is `concat`
|
|
152
|
-
|
|
153
|
-
|
|
140
|
+
node.callee.property.type === 'Identifier' &&
|
|
141
|
+
node.callee.property.name === 'concat' &&
|
|
154
142
|
// Make sure exactly one array is passed in to `concat`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
) {
|
|
158
|
-
// Modules are contained in `Array(<minimum ID>).concat(` array:
|
|
159
|
-
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
|
|
160
|
-
// The `<minimum ID>` + array indexes are module ids
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function isAsyncChunkPushExpression(node) {
|
|
168
|
-
const { callee } = node;
|
|
169
|
-
return (
|
|
170
|
-
callee.type === 'MemberExpression' &&
|
|
171
|
-
callee.property.name === 'push' &&
|
|
172
|
-
callee.object.type === 'AssignmentExpression' &&
|
|
173
|
-
(
|
|
174
|
-
callee.object.left.object.name === 'window' ||
|
|
175
|
-
// Webpack 4 uses `this` instead of `window`
|
|
176
|
-
callee.object.left.object.type === 'ThisExpression'
|
|
177
|
-
)
|
|
143
|
+
node.arguments.length === 1 &&
|
|
144
|
+
isModulesArray(node.arguments[0])
|
|
178
145
|
);
|
|
179
146
|
}
|
|
180
147
|
|
|
@@ -197,9 +164,48 @@ function isNumericId(node) {
|
|
|
197
164
|
return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0);
|
|
198
165
|
}
|
|
199
166
|
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
167
|
+
function isChunkIds(node) {
|
|
168
|
+
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
|
|
169
|
+
return (
|
|
170
|
+
node.type === 'ArrayExpression' &&
|
|
171
|
+
_.every(node.elements, isModuleId)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isAsyncChunkPushExpression(node) {
|
|
176
|
+
const {
|
|
177
|
+
callee,
|
|
178
|
+
arguments: args
|
|
179
|
+
} = node;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
callee.type === 'MemberExpression' &&
|
|
183
|
+
callee.property.name === 'push' &&
|
|
184
|
+
callee.object.type === 'AssignmentExpression' &&
|
|
185
|
+
callee.object.left.object &&
|
|
186
|
+
(
|
|
187
|
+
callee.object.left.object.name === 'window' ||
|
|
188
|
+
// Webpack 4 uses `this` instead of `window`
|
|
189
|
+
callee.object.left.object.type === 'ThisExpression'
|
|
190
|
+
) &&
|
|
191
|
+
args.length === 1 &&
|
|
192
|
+
args[0].type === 'ArrayExpression' &&
|
|
193
|
+
mayBeAsyncChunkArguments(args[0].elements) &&
|
|
194
|
+
isModulesList(args[0].elements[1])
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function mayBeAsyncChunkArguments(args) {
|
|
199
|
+
return (
|
|
200
|
+
args.length >= 2 &&
|
|
201
|
+
isChunkIds(args[0])
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getModulesLocations(node) {
|
|
206
|
+
if (node.type === 'ObjectExpression') {
|
|
207
|
+
// Modules hash
|
|
208
|
+
const modulesNodes = node.properties;
|
|
203
209
|
|
|
204
210
|
return _.transform(modulesNodes, (result, moduleNode) => {
|
|
205
211
|
const moduleId = moduleNode.key.name || moduleNode.key.value;
|
|
@@ -208,35 +214,32 @@ function getModulesLocationFromFunctionArgument(arg) {
|
|
|
208
214
|
}, {});
|
|
209
215
|
}
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
const isOptimizedArray = (node.type === 'CallExpression');
|
|
218
|
+
|
|
219
|
+
if (node.type === 'ArrayExpression' || isOptimizedArray) {
|
|
220
|
+
// Modules array or optimized array
|
|
221
|
+
const minId = isOptimizedArray ?
|
|
222
|
+
// Get the [minId] value from the Array() call first argument literal value
|
|
223
|
+
node.callee.object.arguments[0].value :
|
|
224
|
+
// `0` for simple array
|
|
225
|
+
0;
|
|
226
|
+
const modulesNodes = isOptimizedArray ?
|
|
227
|
+
// The modules reside in the `concat()` function call arguments
|
|
228
|
+
node.arguments[0].elements :
|
|
229
|
+
node.elements;
|
|
213
230
|
|
|
214
231
|
return _.transform(modulesNodes, (result, moduleNode, i) => {
|
|
215
232
|
if (!moduleNode) return;
|
|
216
|
-
|
|
217
|
-
result[i] = getModuleLocation(moduleNode);
|
|
233
|
+
result[i + minId] = getModuleLocation(moduleNode);
|
|
218
234
|
}, {});
|
|
219
235
|
}
|
|
220
236
|
|
|
221
237
|
return {};
|
|
222
238
|
}
|
|
223
239
|
|
|
224
|
-
function getModulesLocationFromArrayConcat(arg) {
|
|
225
|
-
// arg(CallExpression) =
|
|
226
|
-
// Array([minId]).concat([<minId module>, <minId+1 module>, ...])
|
|
227
|
-
//
|
|
228
|
-
// Get the [minId] value from the Array() call first argument literal value
|
|
229
|
-
const minId = arg.callee.object.arguments[0].value;
|
|
230
|
-
// The modules reside in the `concat()` function call arguments
|
|
231
|
-
const modulesNodes = arg.arguments[0].elements;
|
|
232
|
-
|
|
233
|
-
return _.transform(modulesNodes, (result, moduleNode, i) => {
|
|
234
|
-
if (!moduleNode) return;
|
|
235
|
-
|
|
236
|
-
result[i + minId] = getModuleLocation(moduleNode);
|
|
237
|
-
}, {});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
240
|
function getModuleLocation(node) {
|
|
241
|
-
return
|
|
241
|
+
return {
|
|
242
|
+
start: node.start,
|
|
243
|
+
end: node.end
|
|
244
|
+
};
|
|
242
245
|
}
|
package/src/tree/BaseFolder.js
CHANGED
|
@@ -11,10 +11,7 @@ export default class BaseFolder extends Node {
|
|
|
11
11
|
|
|
12
12
|
get src() {
|
|
13
13
|
if (!_.has(this, '_src')) {
|
|
14
|
-
this._src = this.walk((node, src
|
|
15
|
-
if (node.src === undefined) return stop(undefined);
|
|
16
|
-
return (src += node.src);
|
|
17
|
-
}, '', false);
|
|
14
|
+
this._src = this.walk((node, src) => (src += node.src || ''), '', false);
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
return this._src;
|
package/src/tree/Folder.js
CHANGED
|
@@ -9,12 +9,12 @@ import { getModulePathParts } from './utils';
|
|
|
9
9
|
export default class Folder extends BaseFolder {
|
|
10
10
|
|
|
11
11
|
get parsedSize() {
|
|
12
|
-
return this.src ? this.src.length :
|
|
12
|
+
return this.src ? this.src.length : 0;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
get gzipSize() {
|
|
16
16
|
if (!_.has(this, '_gzipSize')) {
|
|
17
|
-
this._gzipSize = this.src ? gzipSize.sync(this.src) :
|
|
17
|
+
this._gzipSize = this.src ? gzipSize.sync(this.src) : 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
return this._gzipSize;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { inspect } = require('util');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
exports.createAssetsFilter = createAssetsFilter;
|
|
5
|
+
|
|
6
|
+
function createAssetsFilter(excludePatterns) {
|
|
7
|
+
const excludeFunctions = _(excludePatterns)
|
|
8
|
+
.castArray()
|
|
9
|
+
.compact()
|
|
10
|
+
.map(pattern => {
|
|
11
|
+
if (typeof pattern === 'string') {
|
|
12
|
+
pattern = new RegExp(pattern);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (_.isRegExp(pattern)) {
|
|
16
|
+
return (asset) => pattern.test(asset);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!_.isFunction(pattern)) {
|
|
20
|
+
throw new TypeError(
|
|
21
|
+
`Pattern should be either string, RegExp or a function, but "${inspect(pattern, { depth: 0 })}" got.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return pattern;
|
|
26
|
+
})
|
|
27
|
+
.value();
|
|
28
|
+
|
|
29
|
+
if (excludeFunctions.length) {
|
|
30
|
+
return (asset) => _.every(excludeFunctions, fn => fn(asset) !== true);
|
|
31
|
+
} else {
|
|
32
|
+
return () => true;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/viewer.js
CHANGED
|
@@ -29,10 +29,13 @@ async function startServer(bundleStats, opts) {
|
|
|
29
29
|
openBrowser = true,
|
|
30
30
|
bundleDir = null,
|
|
31
31
|
logger = new Logger(),
|
|
32
|
-
defaultSizes = 'parsed'
|
|
32
|
+
defaultSizes = 'parsed',
|
|
33
|
+
excludeAssets = null
|
|
33
34
|
} = opts || {};
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const analyzerOpts = { logger, excludeAssets };
|
|
37
|
+
|
|
38
|
+
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
|
36
39
|
|
|
37
40
|
if (!chartData) return;
|
|
38
41
|
|
|
@@ -90,7 +93,7 @@ async function startServer(bundleStats, opts) {
|
|
|
90
93
|
};
|
|
91
94
|
|
|
92
95
|
function updateChartData(bundleStats) {
|
|
93
|
-
const newChartData = getChartData(
|
|
96
|
+
const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
|
94
97
|
|
|
95
98
|
if (!newChartData) return;
|
|
96
99
|
|
|
@@ -113,10 +116,11 @@ function generateReport(bundleStats, opts) {
|
|
|
113
116
|
reportFilename = 'report.html',
|
|
114
117
|
bundleDir = null,
|
|
115
118
|
logger = new Logger(),
|
|
116
|
-
defaultSizes = 'parsed'
|
|
119
|
+
defaultSizes = 'parsed',
|
|
120
|
+
excludeAssets = null
|
|
117
121
|
} = opts || {};
|
|
118
122
|
|
|
119
|
-
const chartData = getChartData(logger, bundleStats, bundleDir);
|
|
123
|
+
const chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir);
|
|
120
124
|
|
|
121
125
|
if (!chartData) return;
|
|
122
126
|
|
|
@@ -151,18 +155,19 @@ function getAssetContent(filename) {
|
|
|
151
155
|
return fs.readFileSync(`${projectRoot}/public/${filename}`, 'utf8');
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
function getChartData(
|
|
158
|
+
function getChartData(analyzerOpts, ...args) {
|
|
155
159
|
let chartData;
|
|
160
|
+
const { logger } = analyzerOpts;
|
|
156
161
|
|
|
157
162
|
try {
|
|
158
|
-
chartData = analyzer.getViewerData(...args,
|
|
163
|
+
chartData = analyzer.getViewerData(...args, analyzerOpts);
|
|
159
164
|
} catch (err) {
|
|
160
165
|
logger.error(`Could't analyze webpack bundle:\n${err}`);
|
|
161
166
|
logger.debug(err.stack);
|
|
162
167
|
chartData = null;
|
|
163
168
|
}
|
|
164
169
|
|
|
165
|
-
if (_.isEmpty(chartData)) {
|
|
170
|
+
if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
|
|
166
171
|
logger.error("Could't find any javascript bundles in provided stats file");
|
|
167
172
|
chartData = null;
|
|
168
173
|
}
|