webpack-bundle-analyzer 2.9.2 → 2.11.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 +25 -0
- package/README.md +4 -2
- package/lib/BundleAnalyzerPlugin.js +35 -12
- package/lib/Logger.js +4 -3
- package/lib/analyzer.js +4 -31
- package/lib/bin/analyzer.js +11 -4
- package/lib/parseUtils.js +18 -0
- package/lib/tree/BaseFolder.js +139 -0
- package/lib/tree/ConcatenatedModule.js +122 -0
- package/lib/tree/ContentFolder.js +69 -0
- package/lib/tree/ContentModule.js +67 -0
- package/lib/tree/Folder.js +114 -0
- package/lib/tree/Module.js +101 -0
- package/lib/tree/Node.js +38 -0
- package/lib/tree/utils.js +34 -0
- package/lib/viewer.js +1 -0
- package/package.json +32 -32
- package/public/viewer.js +1 -1
- package/public/viewer.js.map +1 -1
- package/src/BundleAnalyzerPlugin.js +26 -12
- package/src/Logger.js +4 -1
- package/src/analyzer.js +3 -28
- package/src/bin/analyzer.js +20 -13
- package/src/parseUtils.js +30 -0
- package/src/tree/BaseFolder.js +95 -0
- package/src/tree/ConcatenatedModule.js +68 -0
- package/src/tree/ContentFolder.js +35 -0
- package/src/tree/ContentModule.js +33 -0
- package/src/tree/Folder.js +64 -0
- package/src/tree/Module.js +63 -0
- package/src/tree/Node.js +20 -0
- package/src/tree/utils.js +21 -0
- package/src/viewer.js +1 -0
- package/lib/tree.js +0 -304
- package/src/tree.js +0 -248
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const bfj = require('bfj-node4');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const mkdir = require('mkdirp');
|
|
4
4
|
const { bold } = require('chalk');
|
|
@@ -32,7 +32,7 @@ class BundleAnalyzerPlugin {
|
|
|
32
32
|
apply(compiler) {
|
|
33
33
|
this.compiler = compiler;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const done = stats => {
|
|
36
36
|
stats = stats.toJson(this.opts.statsOptions);
|
|
37
37
|
|
|
38
38
|
const actions = [];
|
|
@@ -58,22 +58,36 @@ class BundleAnalyzerPlugin {
|
|
|
58
58
|
actions.forEach(action => action());
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (compiler.hooks) {
|
|
64
|
+
compiler.hooks.done.tap('webpack-bundle-analyzer', done);
|
|
65
|
+
} else {
|
|
66
|
+
compiler.plugin('done', done);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
generateStatsFile(stats) {
|
|
70
|
+
async generateStatsFile(stats) {
|
|
65
71
|
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
|
|
66
|
-
|
|
67
72
|
mkdir.sync(path.dirname(statsFilepath));
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
statsFilepath,
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
try {
|
|
75
|
+
await bfj.write(statsFilepath, stats, {
|
|
76
|
+
promises: 'ignore',
|
|
77
|
+
buffers: 'ignore',
|
|
78
|
+
maps: 'ignore',
|
|
79
|
+
iterables: 'ignore',
|
|
80
|
+
circular: 'ignore'
|
|
81
|
+
});
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
this.logger.info(
|
|
84
|
+
`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
|
|
85
|
+
);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.logger.error(
|
|
88
|
+
`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
async startAnalyzerServer(stats) {
|
package/src/Logger.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const LEVELS = [
|
|
2
|
+
'debug',
|
|
2
3
|
'info',
|
|
3
4
|
'warn',
|
|
4
5
|
'error',
|
|
@@ -6,6 +7,7 @@ const LEVELS = [
|
|
|
6
7
|
];
|
|
7
8
|
|
|
8
9
|
const LEVEL_TO_CONSOLE_METHOD = new Map([
|
|
10
|
+
['debug', 'log'],
|
|
9
11
|
['info', 'log'],
|
|
10
12
|
['warn', 'log']
|
|
11
13
|
]);
|
|
@@ -13,8 +15,9 @@ const LEVEL_TO_CONSOLE_METHOD = new Map([
|
|
|
13
15
|
class Logger {
|
|
14
16
|
|
|
15
17
|
static levels = LEVELS;
|
|
18
|
+
static defaultLevel = 'info';
|
|
16
19
|
|
|
17
|
-
constructor(level =
|
|
20
|
+
constructor(level = Logger.defaultLevel) {
|
|
18
21
|
this.activeLevels = new Set();
|
|
19
22
|
this.setLogLevel(level);
|
|
20
23
|
}
|
package/src/analyzer.js
CHANGED
|
@@ -5,11 +5,10 @@ const _ = require('lodash');
|
|
|
5
5
|
const gzipSize = require('gzip-size');
|
|
6
6
|
|
|
7
7
|
const Logger = require('./Logger');
|
|
8
|
-
const
|
|
9
|
-
const { parseBundle } = require('
|
|
8
|
+
const Folder = require('./tree/Folder').default;
|
|
9
|
+
const { parseBundle } = require('./parseUtils');
|
|
10
10
|
|
|
11
11
|
const FILENAME_QUERY_REGEXP = /\?.*$/;
|
|
12
|
-
const MULTI_MODULE_REGEXP = /^multi /;
|
|
13
12
|
|
|
14
13
|
module.exports = {
|
|
15
14
|
getViewerData,
|
|
@@ -117,31 +116,7 @@ function assetHasModule(statAsset, statModule) {
|
|
|
117
116
|
function createModulesTree(modules) {
|
|
118
117
|
const root = new Folder('.');
|
|
119
118
|
|
|
120
|
-
_.each(modules, module =>
|
|
121
|
-
const path = getModulePath(module);
|
|
122
|
-
|
|
123
|
-
if (path) {
|
|
124
|
-
root.addModuleByPath(path, module);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
119
|
+
_.each(modules, module => root.addModule(module));
|
|
127
120
|
|
|
128
121
|
return root;
|
|
129
122
|
}
|
|
130
|
-
|
|
131
|
-
function getModulePath(module) {
|
|
132
|
-
if (MULTI_MODULE_REGEXP.test(module.identifier)) {
|
|
133
|
-
return [module.identifier];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const parsedPath = _
|
|
137
|
-
// Removing loaders from module path: they're joined by `!` and the last part is a raw module path
|
|
138
|
-
.last(module.name.split('!'))
|
|
139
|
-
// Splitting module path into parts
|
|
140
|
-
.split('/')
|
|
141
|
-
// Removing first `.`
|
|
142
|
-
.slice(1)
|
|
143
|
-
// Replacing `~` with `node_modules`
|
|
144
|
-
.map(part => (part === '~') ? 'node_modules' : part);
|
|
145
|
-
|
|
146
|
-
return parsedPath.length ? parsedPath : null;
|
|
147
|
-
}
|
package/src/bin/analyzer.js
CHANGED
|
@@ -8,6 +8,7 @@ const { magenta } = require('chalk');
|
|
|
8
8
|
|
|
9
9
|
const analyzer = require('../analyzer');
|
|
10
10
|
const viewer = require('../viewer');
|
|
11
|
+
const Logger = require('../Logger');
|
|
11
12
|
|
|
12
13
|
const SIZES = new Set(['stat', 'parsed', 'gzip']);
|
|
13
14
|
|
|
@@ -27,40 +28,41 @@ const program = commander
|
|
|
27
28
|
'-m, --mode <mode>',
|
|
28
29
|
'Analyzer mode. Should be `server` or `static`.' +
|
|
29
30
|
br('In `server` mode analyzer will start HTTP server to show bundle report.') +
|
|
30
|
-
br('In `static` mode single HTML file with bundle report will be generated.')
|
|
31
|
-
br('Default is `server`.'),
|
|
31
|
+
br('In `static` mode single HTML file with bundle report will be generated.'),
|
|
32
32
|
'server'
|
|
33
33
|
)
|
|
34
34
|
.option(
|
|
35
35
|
'-h, --host <host>',
|
|
36
|
-
'Host that will be used in `server` mode to start HTTP server.'
|
|
37
|
-
br('Default is `127.0.0.1`.'),
|
|
36
|
+
'Host that will be used in `server` mode to start HTTP server.',
|
|
38
37
|
'127.0.0.1'
|
|
39
38
|
)
|
|
40
39
|
.option(
|
|
41
40
|
'-p, --port <n>',
|
|
42
|
-
'Port that will be used in `server` mode to start HTTP server.'
|
|
43
|
-
br('Default is 8888.'),
|
|
41
|
+
'Port that will be used in `server` mode to start HTTP server.',
|
|
44
42
|
Number,
|
|
45
43
|
8888
|
|
46
44
|
)
|
|
47
45
|
.option(
|
|
48
46
|
'-r, --report <file>',
|
|
49
|
-
'Path to bundle report file that will be generated in `static` mode.'
|
|
50
|
-
br('Default is `report.html`.'),
|
|
47
|
+
'Path to bundle report file that will be generated in `static` mode.',
|
|
51
48
|
'report.html'
|
|
52
49
|
)
|
|
53
50
|
.option(
|
|
54
51
|
'-s, --default-sizes <type>',
|
|
55
52
|
'Module sizes to show in treemap by default.' +
|
|
56
|
-
br(`Possible values: ${[...SIZES].join(', ')}`)
|
|
57
|
-
br('Default is `parsed`.'),
|
|
53
|
+
br(`Possible values: ${[...SIZES].join(', ')}`),
|
|
58
54
|
'parsed'
|
|
59
55
|
)
|
|
60
56
|
.option(
|
|
61
57
|
'-O, --no-open',
|
|
62
58
|
"Don't open report in default browser automatically."
|
|
63
59
|
)
|
|
60
|
+
.option(
|
|
61
|
+
'-l, --log-level <level>',
|
|
62
|
+
'Log level.' +
|
|
63
|
+
br(`Possible values: ${[...Logger.levels].join(', ')}`),
|
|
64
|
+
Logger.defaultLevel
|
|
65
|
+
)
|
|
64
66
|
.parse(process.argv);
|
|
65
67
|
|
|
66
68
|
let {
|
|
@@ -69,9 +71,11 @@ let {
|
|
|
69
71
|
port,
|
|
70
72
|
report: reportFilename,
|
|
71
73
|
defaultSizes,
|
|
74
|
+
logLevel,
|
|
72
75
|
open: openBrowser,
|
|
73
76
|
args: [bundleStatsFile, bundleDir]
|
|
74
77
|
} = program;
|
|
78
|
+
const logger = new Logger(logLevel);
|
|
75
79
|
|
|
76
80
|
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
|
|
77
81
|
if (mode !== 'server' && mode !== 'static') showHelp('Invalid mode. Should be either `server` or `static`.');
|
|
@@ -87,7 +91,8 @@ let bundleStats;
|
|
|
87
91
|
try {
|
|
88
92
|
bundleStats = analyzer.readStatsFromFile(bundleStatsFile);
|
|
89
93
|
} catch (err) {
|
|
90
|
-
|
|
94
|
+
logger.error(`Could't read webpack bundle stats from "${bundleStatsFile}":\n${err}`);
|
|
95
|
+
logger.debug(err.stack);
|
|
91
96
|
process.exit(1);
|
|
92
97
|
}
|
|
93
98
|
|
|
@@ -97,14 +102,16 @@ if (mode === 'server') {
|
|
|
97
102
|
port,
|
|
98
103
|
host,
|
|
99
104
|
defaultSizes,
|
|
100
|
-
bundleDir
|
|
105
|
+
bundleDir,
|
|
106
|
+
logger: new Logger(logLevel)
|
|
101
107
|
});
|
|
102
108
|
} else {
|
|
103
109
|
viewer.generateReport(bundleStats, {
|
|
104
110
|
openBrowser,
|
|
105
111
|
reportFilename: resolve(reportFilename),
|
|
106
112
|
defaultSizes,
|
|
107
|
-
bundleDir
|
|
113
|
+
bundleDir,
|
|
114
|
+
logger: new Logger(logLevel)
|
|
108
115
|
});
|
|
109
116
|
}
|
|
110
117
|
|
package/src/parseUtils.js
CHANGED
|
@@ -71,6 +71,17 @@ function parseBundle(bundlePath) {
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Additional bundles with webpack 4 are loaded with:
|
|
75
|
+
// (window.webpackJsonp=window.webpackJsonp||[]).push([[chunkId], [<module>, <module>], [[optional_entries]]]);
|
|
76
|
+
if (
|
|
77
|
+
isWindowPropertyPushExpression(node) &&
|
|
78
|
+
args.length === 1 &&
|
|
79
|
+
isArgumentContainingChunkIdsAndModulesList(args[0])
|
|
80
|
+
) {
|
|
81
|
+
state.locations = getModulesLocationFromFunctionArgument(args[0].elements[1]);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
|
|
75
86
|
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
|
|
76
87
|
_.each(args, arg => c(arg, state));
|
|
@@ -115,6 +126,18 @@ function isArgumentContainsModulesList(arg) {
|
|
|
115
126
|
return false;
|
|
116
127
|
}
|
|
117
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;
|
|
139
|
+
}
|
|
140
|
+
|
|
118
141
|
function isArgumentArrayConcatContainingChunks(arg) {
|
|
119
142
|
if (
|
|
120
143
|
arg.type === 'CallExpression' &&
|
|
@@ -141,6 +164,13 @@ function isArgumentArrayConcatContainingChunks(arg) {
|
|
|
141
164
|
return false;
|
|
142
165
|
}
|
|
143
166
|
|
|
167
|
+
function isWindowPropertyPushExpression(node) {
|
|
168
|
+
return node.callee.type === 'MemberExpression' &&
|
|
169
|
+
node.callee.property.name === 'push' &&
|
|
170
|
+
node.callee.object.type === 'AssignmentExpression' &&
|
|
171
|
+
node.callee.object.left.object.name === 'window';
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
function isModuleWrapper(node) {
|
|
145
175
|
return (
|
|
146
176
|
// It's an anonymous function expression that wraps module
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
import Node from './Node';
|
|
4
|
+
|
|
5
|
+
export default class BaseFolder extends Node {
|
|
6
|
+
|
|
7
|
+
constructor(name, parent) {
|
|
8
|
+
super(name, parent);
|
|
9
|
+
this.children = Object.create(null);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get src() {
|
|
13
|
+
if (!_.has(this, '_src')) {
|
|
14
|
+
this._src = this.walk((node, src, stop) => {
|
|
15
|
+
if (node.src === undefined) return stop(undefined);
|
|
16
|
+
return (src += node.src);
|
|
17
|
+
}, '', false);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return this._src;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get size() {
|
|
24
|
+
if (!_.has(this, '_size')) {
|
|
25
|
+
this._size = this.walk((node, size) => (size + node.size), 0, false);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return this._size;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getChild(name) {
|
|
32
|
+
return this.children[name];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
addChildModule(module) {
|
|
36
|
+
const { name } = module;
|
|
37
|
+
const currentChild = this.children[name];
|
|
38
|
+
|
|
39
|
+
// For some reason we already have this node in children and it's a folder.
|
|
40
|
+
if (currentChild && currentChild instanceof BaseFolder) return;
|
|
41
|
+
|
|
42
|
+
if (currentChild) {
|
|
43
|
+
// We already have this node in children and it's a module.
|
|
44
|
+
// Merging it's data.
|
|
45
|
+
currentChild.mergeData(module.data);
|
|
46
|
+
} else {
|
|
47
|
+
// Pushing new module
|
|
48
|
+
module.parent = this;
|
|
49
|
+
this.children[name] = module;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
delete this._size;
|
|
53
|
+
delete this._src;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addChildFolder(folder) {
|
|
57
|
+
folder.parent = this;
|
|
58
|
+
this.children[folder.name] = folder;
|
|
59
|
+
delete this._size;
|
|
60
|
+
delete this._src;
|
|
61
|
+
|
|
62
|
+
return folder;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
walk(walker, state = {}, deep = true) {
|
|
66
|
+
let stopped = false;
|
|
67
|
+
|
|
68
|
+
_.each(this.children, child => {
|
|
69
|
+
if (deep && child.walk) {
|
|
70
|
+
state = child.walk(walker, state, stop);
|
|
71
|
+
} else {
|
|
72
|
+
state = walker(child, state, stop);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (stopped) return false;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return state;
|
|
79
|
+
|
|
80
|
+
function stop(finalState) {
|
|
81
|
+
stopped = true;
|
|
82
|
+
return finalState;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
toChartData() {
|
|
87
|
+
return {
|
|
88
|
+
label: this.name,
|
|
89
|
+
path: this.path,
|
|
90
|
+
statSize: this.size,
|
|
91
|
+
groups: _.invokeMap(this.children, 'toChartData')
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
import Module from './Module';
|
|
4
|
+
import ContentModule from './ContentModule';
|
|
5
|
+
import ContentFolder from './ContentFolder';
|
|
6
|
+
import { getModulePathParts } from './utils';
|
|
7
|
+
|
|
8
|
+
export default class ConcatenatedModule extends Module {
|
|
9
|
+
|
|
10
|
+
constructor(name, data, parent) {
|
|
11
|
+
super(name, data, parent);
|
|
12
|
+
this.name += ' (concatenated)';
|
|
13
|
+
this.children = Object.create(null);
|
|
14
|
+
this.fillContentModules();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fillContentModules() {
|
|
18
|
+
_.each(this.data.modules, moduleData => this.addContentModule(moduleData));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
addContentModule(moduleData) {
|
|
22
|
+
const pathParts = getModulePathParts(moduleData);
|
|
23
|
+
|
|
24
|
+
if (!pathParts) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
|
|
29
|
+
let currentFolder = this;
|
|
30
|
+
|
|
31
|
+
_.each(folders, folderName => {
|
|
32
|
+
let childFolder = currentFolder.getChild(folderName);
|
|
33
|
+
|
|
34
|
+
if (!childFolder) {
|
|
35
|
+
childFolder = currentFolder.addChildFolder(new ContentFolder(folderName, this));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
currentFolder = childFolder;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const module = new ContentModule(fileName, moduleData, this);
|
|
42
|
+
currentFolder.addChildModule(module);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getChild(name) {
|
|
46
|
+
return this.children[name];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
addChildModule(module) {
|
|
50
|
+
module.parent = this;
|
|
51
|
+
this.children[module.name] = module;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
addChildFolder(folder) {
|
|
55
|
+
folder.parent = this;
|
|
56
|
+
this.children[folder.name] = folder;
|
|
57
|
+
return folder;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toChartData() {
|
|
61
|
+
return {
|
|
62
|
+
...super.toChartData(),
|
|
63
|
+
concatenated: true,
|
|
64
|
+
groups: _.invokeMap(this.children, 'toChartData')
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import BaseFolder from './BaseFolder';
|
|
2
|
+
|
|
3
|
+
export default class ContentFolder extends BaseFolder {
|
|
4
|
+
|
|
5
|
+
constructor(name, ownerModule, parent) {
|
|
6
|
+
super(name, parent);
|
|
7
|
+
this.ownerModule = ownerModule;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get parsedSize() {
|
|
11
|
+
return this.getSize('parsedSize');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get gzipSize() {
|
|
15
|
+
return this.getSize('gzipSize');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getSize(sizeType) {
|
|
19
|
+
const ownerModuleSize = this.ownerModule[sizeType];
|
|
20
|
+
|
|
21
|
+
if (ownerModuleSize !== undefined) {
|
|
22
|
+
return Math.floor((this.size / this.ownerModule.size) * ownerModuleSize);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toChartData() {
|
|
27
|
+
return {
|
|
28
|
+
...super.toChartData(),
|
|
29
|
+
parsedSize: this.parsedSize,
|
|
30
|
+
gzipSize: this.gzipSize,
|
|
31
|
+
inaccurateSizes: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Module from './Module';
|
|
2
|
+
|
|
3
|
+
export default class ContentModule extends Module {
|
|
4
|
+
|
|
5
|
+
constructor(name, data, ownerModule, parent) {
|
|
6
|
+
super(name, data, parent);
|
|
7
|
+
this.ownerModule = ownerModule;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get parsedSize() {
|
|
11
|
+
return this.getSize('parsedSize');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get gzipSize() {
|
|
15
|
+
return this.getSize('gzipSize');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getSize(sizeType) {
|
|
19
|
+
const ownerModuleSize = this.ownerModule[sizeType];
|
|
20
|
+
|
|
21
|
+
if (ownerModuleSize !== undefined) {
|
|
22
|
+
return Math.floor((this.size / this.ownerModule.size) * ownerModuleSize);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toChartData() {
|
|
27
|
+
return {
|
|
28
|
+
...super.toChartData(),
|
|
29
|
+
inaccurateSizes: true
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import gzipSize from 'gzip-size';
|
|
3
|
+
|
|
4
|
+
import Module from './Module';
|
|
5
|
+
import BaseFolder from './BaseFolder';
|
|
6
|
+
import ConcatenatedModule from './ConcatenatedModule';
|
|
7
|
+
import { getModulePathParts } from './utils';
|
|
8
|
+
|
|
9
|
+
export default class Folder extends BaseFolder {
|
|
10
|
+
|
|
11
|
+
get parsedSize() {
|
|
12
|
+
return this.src ? this.src.length : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get gzipSize() {
|
|
16
|
+
if (!_.has(this, '_gzipSize')) {
|
|
17
|
+
this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return this._gzipSize;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
addModule(moduleData) {
|
|
24
|
+
const pathParts = getModulePathParts(moduleData);
|
|
25
|
+
|
|
26
|
+
if (!pathParts) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
|
|
31
|
+
let currentFolder = this;
|
|
32
|
+
|
|
33
|
+
_.each(folders, folderName => {
|
|
34
|
+
let childNode = currentFolder.getChild(folderName);
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
// Folder is not created yet
|
|
38
|
+
!childNode ||
|
|
39
|
+
// In some situations (invalid usage of dynamic `require()`) webpack generates a module with empty require
|
|
40
|
+
// context, but it's moduleId points to a directory in filesystem.
|
|
41
|
+
// In this case we replace this `File` node with `Folder`.
|
|
42
|
+
// See `test/stats/with-invalid-dynamic-require.json` as an example.
|
|
43
|
+
!(childNode instanceof Folder)
|
|
44
|
+
) {
|
|
45
|
+
childNode = currentFolder.addChildFolder(new Folder(folderName));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
currentFolder = childNode;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const ModuleConstructor = moduleData.modules ? ConcatenatedModule : Module;
|
|
52
|
+
const module = new ModuleConstructor(fileName, moduleData, this);
|
|
53
|
+
currentFolder.addChildModule(module);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toChartData() {
|
|
57
|
+
return {
|
|
58
|
+
...super.toChartData(),
|
|
59
|
+
parsedSize: this.parsedSize,
|
|
60
|
+
gzipSize: this.gzipSize
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import gzipSize from 'gzip-size';
|
|
3
|
+
|
|
4
|
+
import Node from './Node';
|
|
5
|
+
|
|
6
|
+
export default class Module extends Node {
|
|
7
|
+
|
|
8
|
+
constructor(name, data, parent) {
|
|
9
|
+
super(name, parent);
|
|
10
|
+
this.data = data;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get src() {
|
|
14
|
+
return this.data.parsedSrc;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set src(value) {
|
|
18
|
+
this.data.parsedSrc = value;
|
|
19
|
+
delete this._gzipSize;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get size() {
|
|
23
|
+
return this.data.size;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set size(value) {
|
|
27
|
+
this.data.size = value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get parsedSize() {
|
|
31
|
+
return this.src ? this.src.length : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get gzipSize() {
|
|
35
|
+
if (!_.has(this, '_gzipSize')) {
|
|
36
|
+
this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this._gzipSize;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
mergeData(data) {
|
|
43
|
+
if (data.size) {
|
|
44
|
+
this.size += data.size;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (data.parsedSrc) {
|
|
48
|
+
this.src = (this.src || '') + data.parsedSrc;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toChartData() {
|
|
53
|
+
return {
|
|
54
|
+
id: this.data.id,
|
|
55
|
+
label: this.name,
|
|
56
|
+
path: this.path,
|
|
57
|
+
statSize: this.size,
|
|
58
|
+
parsedSize: this.parsedSize,
|
|
59
|
+
gzipSize: this.gzipSize
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
};
|
package/src/tree/Node.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class Node {
|
|
2
|
+
|
|
3
|
+
constructor(name, parent) {
|
|
4
|
+
this.name = name;
|
|
5
|
+
this.parent = parent;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get path() {
|
|
9
|
+
const path = [];
|
|
10
|
+
let node = this;
|
|
11
|
+
|
|
12
|
+
while (node) {
|
|
13
|
+
path.push(node.name);
|
|
14
|
+
node = node.parent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return path.reverse().join('/');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
const MULTI_MODULE_REGEXP = /^multi /;
|
|
4
|
+
|
|
5
|
+
export function getModulePathParts(moduleData) {
|
|
6
|
+
if (MULTI_MODULE_REGEXP.test(moduleData.identifier)) {
|
|
7
|
+
return [moduleData.identifier];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const parsedPath = _
|
|
11
|
+
// Removing loaders from module path: they're joined by `!` and the last part is a raw module path
|
|
12
|
+
.last(moduleData.name.split('!'))
|
|
13
|
+
// Splitting module path into parts
|
|
14
|
+
.split('/')
|
|
15
|
+
// Removing first `.`
|
|
16
|
+
.slice(1)
|
|
17
|
+
// Replacing `~` with `node_modules`
|
|
18
|
+
.map(part => (part === '~' ? 'node_modules' : part));
|
|
19
|
+
|
|
20
|
+
return parsedPath.length ? parsedPath : null;
|
|
21
|
+
}
|
package/src/viewer.js
CHANGED