1 | const fs = require('fs');
|
2 | const path = require('path');
|
3 |
|
4 | const _ = require('lodash');
|
5 | const gzipSize = require('gzip-size');
|
6 |
|
7 | const Logger = require('./Logger');
|
8 | const { Folder } = require('../lib/tree');
|
9 | const { parseBundle } = require('../lib/parseUtils');
|
10 |
|
11 | const FILENAME_QUERY_REGEXP = /\?.*$/;
|
12 | const MULTI_MODULE_REGEXP = /^multi /;
|
13 |
|
14 | module.exports = {
|
15 | getViewerData,
|
16 | readStatsFromFile
|
17 | };
|
18 |
|
19 | function getViewerData(bundleStats, bundleDir, opts) {
|
20 | const {
|
21 | logger = new Logger()
|
22 | } = opts || {};
|
23 |
|
24 |
|
25 | if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
|
26 | bundleStats = bundleStats.children[0];
|
27 | }
|
28 |
|
29 |
|
30 | bundleStats.assets = _.filter(bundleStats.assets, asset => {
|
31 |
|
32 |
|
33 | asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
|
34 |
|
35 | return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks);
|
36 | });
|
37 |
|
38 |
|
39 | let bundlesSources = null;
|
40 | let parsedModules = null;
|
41 |
|
42 | if (bundleDir) {
|
43 | bundlesSources = {};
|
44 | parsedModules = {};
|
45 |
|
46 | for (const statAsset of bundleStats.assets) {
|
47 | const assetFile = path.join(bundleDir, statAsset.name);
|
48 | let bundleInfo;
|
49 |
|
50 | try {
|
51 | bundleInfo = parseBundle(assetFile);
|
52 | } catch (err) {
|
53 | bundleInfo = null;
|
54 | }
|
55 |
|
56 | if (!bundleInfo) {
|
57 | logger.warn(
|
58 | `\nCouldn't parse bundle asset "${assetFile}".\n` +
|
59 | 'Analyzer will use module sizes from stats file.\n'
|
60 | );
|
61 | parsedModules = null;
|
62 | bundlesSources = null;
|
63 | break;
|
64 | }
|
65 |
|
66 | bundlesSources[statAsset.name] = bundleInfo.src;
|
67 | _.assign(parsedModules, bundleInfo.modules);
|
68 | }
|
69 | }
|
70 |
|
71 | const assets = _.transform(bundleStats.assets, (result, statAsset) => {
|
72 | const asset = result[statAsset.name] = _.pick(statAsset, 'size');
|
73 |
|
74 | if (bundlesSources) {
|
75 | asset.parsedSize = bundlesSources[statAsset.name].length;
|
76 | asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
|
77 | }
|
78 |
|
79 |
|
80 | asset.modules = _(bundleStats.modules)
|
81 | .filter(statModule => assetHasModule(statAsset, statModule))
|
82 | .each(statModule => {
|
83 | if (parsedModules) {
|
84 | statModule.parsedSrc = parsedModules[statModule.id];
|
85 | }
|
86 | });
|
87 |
|
88 | asset.tree = createModulesTree(asset.modules);
|
89 | }, {});
|
90 |
|
91 | return _.transform(assets, (result, asset, filename) => {
|
92 | result.push({
|
93 | label: filename,
|
94 |
|
95 |
|
96 |
|
97 | statSize: asset.tree.size,
|
98 | parsedSize: asset.parsedSize,
|
99 | gzipSize: asset.gzipSize,
|
100 | groups: _.invokeMap(asset.tree.children, 'toChartData')
|
101 | });
|
102 | }, []);
|
103 | }
|
104 |
|
105 | function readStatsFromFile(filename) {
|
106 | return JSON.parse(
|
107 | fs.readFileSync(filename, 'utf8')
|
108 | );
|
109 | }
|
110 |
|
111 | function assetHasModule(statAsset, statModule) {
|
112 | return _.some(statModule.chunks, moduleChunk =>
|
113 | _.includes(statAsset.chunks, moduleChunk)
|
114 | );
|
115 | }
|
116 |
|
117 | function createModulesTree(modules) {
|
118 | const root = new Folder('.');
|
119 |
|
120 | _.each(modules, module => {
|
121 | const path = getModulePath(module);
|
122 |
|
123 | if (path) {
|
124 | root.addModuleByPath(path, module);
|
125 | }
|
126 | });
|
127 |
|
128 | return root;
|
129 | }
|
130 |
|
131 | function getModulePath(module) {
|
132 | if (MULTI_MODULE_REGEXP.test(module.identifier)) {
|
133 | return [module.identifier];
|
134 | }
|
135 |
|
136 | const parsedPath = _
|
137 |
|
138 | .last(module.name.split('!'))
|
139 |
|
140 | .split('/')
|
141 |
|
142 | .slice(1)
|
143 |
|
144 | .map(part => (part === '~') ? 'node_modules' : part);
|
145 |
|
146 | return parsedPath.length ? parsedPath : null;
|
147 | }
|