webpack-bundle-analyzer 3.9.0 → 4.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.
@@ -1,11 +1,11 @@
1
- const bfj = require('bfj');
1
+ const fs = require('fs');
2
2
  const path = require('path');
3
- const mkdir = require('mkdirp');
4
3
  const {bold} = require('chalk');
5
4
 
6
5
  const Logger = require('./Logger');
7
6
  const viewer = require('./viewer');
8
7
  const utils = require('./utils');
8
+ const {writeStats} = require('./statsUtils');
9
9
 
10
10
  class BundleAnalyzerPlugin {
11
11
  constructor(opts = {}) {
@@ -80,17 +80,10 @@ class BundleAnalyzerPlugin {
80
80
 
81
81
  async generateStatsFile(stats) {
82
82
  const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
83
- mkdir.sync(path.dirname(statsFilepath));
83
+ await fs.promises.mkdir(path.dirname(statsFilepath), {recursive: true});
84
84
 
85
85
  try {
86
- await bfj.write(statsFilepath, stats, {
87
- space: 2,
88
- promises: 'ignore',
89
- buffers: 'ignore',
90
- maps: 'ignore',
91
- iterables: 'ignore',
92
- circular: 'ignore'
93
- });
86
+ await writeStats(stats, statsFilepath);
94
87
 
95
88
  this.logger.info(
96
89
  `${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
package/src/analyzer.js CHANGED
@@ -32,7 +32,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
32
32
  // Sometimes if there are additional child chunks produced add them as child assets,
33
33
  // leave the 1st one as that is considered the 'root' asset.
34
34
  for (let i = 1; i < children.length; i++) {
35
- bundleStats.children[i].assets.forEach((asset) => {
35
+ children[i].assets.forEach((asset) => {
36
36
  asset.isChild = true;
37
37
  bundleStats.assets.push(asset);
38
38
  });
@@ -48,7 +48,12 @@ function getViewerData(bundleStats, bundleDir, opts) {
48
48
  }
49
49
 
50
50
  // Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
51
- bundleStats.assets = _.filter(bundleStats.assets, asset => {
51
+ bundleStats.assets = bundleStats.assets.filter(asset => {
52
+ // Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
53
+ if (asset.type && asset.type !== 'asset') {
54
+ return false;
55
+ }
56
+
52
57
  // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
53
58
  // See #22
54
59
  asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
@@ -76,8 +81,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
76
81
  continue;
77
82
  }
78
83
 
79
- bundlesSources[statAsset.name] = bundleInfo.src;
80
- _.assign(parsedModules, bundleInfo.modules);
84
+ bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
85
+ Object.assign(parsedModules, bundleInfo.modules);
81
86
  }
82
87
 
83
88
  if (_.isEmpty(bundlesSources)) {
@@ -87,43 +92,73 @@ function getViewerData(bundleStats, bundleDir, opts) {
87
92
  }
88
93
  }
89
94
 
90
- const assets = _.transform(bundleStats.assets, (result, statAsset) => {
95
+ const assets = bundleStats.assets.reduce((result, statAsset) => {
91
96
  // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
92
97
  const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
93
98
  const modules = assetBundles ? getBundleModules(assetBundles) : [];
94
99
  const asset = result[statAsset.name] = _.pick(statAsset, 'size');
100
+ const assetSources = bundlesSources && _.has(bundlesSources, statAsset.name) ?
101
+ bundlesSources[statAsset.name] : null;
95
102
 
96
- if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
97
- asset.parsedSize = Buffer.byteLength(bundlesSources[statAsset.name]);
98
- asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
103
+ if (assetSources) {
104
+ asset.parsedSize = Buffer.byteLength(assetSources.src);
105
+ asset.gzipSize = gzipSize.sync(assetSources.src);
99
106
  }
100
107
 
101
108
  // Picking modules from current bundle script
102
- asset.modules = _(modules)
103
- .filter(statModule => assetHasModule(statAsset, statModule))
104
- .each(statModule => {
105
- if (parsedModules) {
109
+ const assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule));
110
+
111
+ // Adding parsed sources
112
+ if (parsedModules) {
113
+ const unparsedEntryModules = [];
114
+
115
+ for (const statModule of assetModules) {
116
+ if (parsedModules[statModule.id]) {
106
117
  statModule.parsedSrc = parsedModules[statModule.id];
118
+ } else if (isEntryModule(statModule)) {
119
+ unparsedEntryModules.push(statModule);
107
120
  }
108
- });
121
+ }
109
122
 
123
+ // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
124
+ // Because of this they basically become a concatenated module, for which we can't even precisely determine its
125
+ // parsed source as it's located in the same scope as all Webpack runtime helpers.
126
+ if (unparsedEntryModules.length && assetSources) {
127
+ if (unparsedEntryModules.length === 1) {
128
+ // So if there is only one entry we consider its parsed source to be all the bundle code excluding code
129
+ // from parsed modules.
130
+ unparsedEntryModules[0].parsedSrc = assetSources.runtimeSrc;
131
+ } else {
132
+ // If there are multiple entry points we move all of them under synthetic concatenated module.
133
+ _.pullAll(assetModules, unparsedEntryModules);
134
+ assetModules.unshift({
135
+ identifier: './entry modules',
136
+ name: './entry modules',
137
+ modules: unparsedEntryModules,
138
+ size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
139
+ parsedSrc: assetSources.runtimeSrc
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+ asset.modules = assetModules;
110
146
  asset.tree = createModulesTree(asset.modules);
147
+ return result;
111
148
  }, {});
112
149
 
113
- return _.transform(assets, (result, asset, filename) => {
114
- result.push({
115
- label: filename,
116
- isAsset: true,
117
- // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
118
- // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
119
- // be the size of minified bundle.
120
- // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
121
- statSize: asset.tree.size || asset.size,
122
- parsedSize: asset.parsedSize,
123
- gzipSize: asset.gzipSize,
124
- groups: _.invokeMap(asset.tree.children, 'toChartData')
125
- });
126
- }, []);
150
+ return Object.entries(assets).map(([filename, asset]) => ({
151
+ label: filename,
152
+ isAsset: true,
153
+ // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
154
+ // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
155
+ // be the size of minified bundle.
156
+ // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
157
+ statSize: asset.tree.size || asset.size,
158
+ parsedSize: asset.parsedSize,
159
+ gzipSize: asset.gzipSize,
160
+ groups: _.invokeMap(asset.tree.children, 'toChartData')
161
+ }));
127
162
  }
128
163
 
129
164
  function readStatsFromFile(filename) {
@@ -133,7 +168,7 @@ function readStatsFromFile(filename) {
133
168
  }
134
169
 
135
170
  function getChildAssetBundles(bundleStats, assetName) {
136
- return _.find(bundleStats.children, (c) =>
171
+ return (bundleStats.children || []).find((c) =>
137
172
  _(c.assetsByChunkName)
138
173
  .values()
139
174
  .flatten()
@@ -148,20 +183,30 @@ function getBundleModules(bundleStats) {
148
183
  .compact()
149
184
  .flatten()
150
185
  .uniqBy('id')
186
+ // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
187
+ .reject(isRuntimeModule)
151
188
  .value();
152
189
  }
153
190
 
154
191
  function assetHasModule(statAsset, statModule) {
155
192
  // Checking if this module is the part of asset chunks
156
- return _.some(statModule.chunks, moduleChunk =>
157
- _.includes(statAsset.chunks, moduleChunk)
193
+ return statModule.chunks.some(moduleChunk =>
194
+ statAsset.chunks.includes(moduleChunk)
158
195
  );
159
196
  }
160
197
 
198
+ function isEntryModule(statModule) {
199
+ return statModule.depth === 0;
200
+ }
201
+
202
+ function isRuntimeModule(statModule) {
203
+ return statModule.moduleType === 'runtime';
204
+ }
205
+
161
206
  function createModulesTree(modules) {
162
207
  const root = new Folder('.');
163
208
 
164
- _.each(modules, module => root.addModule(module));
209
+ modules.forEach(module => root.addModule(module));
165
210
  root.mergeNestedFolders();
166
211
 
167
212
  return root;
@@ -2,7 +2,6 @@
2
2
 
3
3
  const {resolve, dirname} = require('path');
4
4
 
5
- const _ = require('lodash');
6
5
  const commander = require('commander');
7
6
  const {magenta} = require('chalk');
8
7
 
@@ -157,7 +156,7 @@ function showHelp(error) {
157
156
  }
158
157
 
159
158
  function br(str) {
160
- return `\n${_.repeat(' ', 28)}${str}`;
159
+ return `\n${' '.repeat(28)}${str}`;
161
160
  }
162
161
 
163
162
  function array() {
package/src/parseUtils.js CHANGED
@@ -18,13 +18,57 @@ function parseBundle(bundlePath) {
18
18
  });
19
19
 
20
20
  const walkState = {
21
- locations: null
21
+ locations: null,
22
+ expressionStatementDepth: 0
22
23
  };
23
24
 
24
25
  walk.recursive(
25
26
  ast,
26
27
  walkState,
27
28
  {
29
+ ExpressionStatement(node, state, c) {
30
+ if (state.locations) return;
31
+
32
+ state.expressionStatementDepth++;
33
+
34
+ if (
35
+ // Webpack 5 stores modules in the the top-level IIFE
36
+ state.expressionStatementDepth === 1 &&
37
+ ast.body.includes(node) &&
38
+ isIIFE(node)
39
+ ) {
40
+ const fn = getIIFECallExpression(node);
41
+
42
+ if (
43
+ // It should not contain neither arguments
44
+ fn.arguments.length === 0 &&
45
+ // ...nor parameters
46
+ fn.callee.params.length === 0
47
+ ) {
48
+ // Modules are stored in the very first variable declaration as hash
49
+ const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');
50
+
51
+ if (firstVariableDeclaration) {
52
+ for (const declaration of firstVariableDeclaration.declarations) {
53
+ if (declaration.init) {
54
+ state.locations = getModulesLocations(declaration.init);
55
+
56
+ if (state.locations) {
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ if (!state.locations) {
66
+ c(node.expression, state);
67
+ }
68
+
69
+ state.expressionStatementDepth--;
70
+ },
71
+
28
72
  AssignmentExpression(node, state) {
29
73
  if (state.locations) return;
30
74
 
@@ -41,6 +85,7 @@ function parseBundle(bundlePath) {
41
85
  state.locations = getModulesLocations(right);
42
86
  }
43
87
  },
88
+
44
89
  CallExpression(node, state, c) {
45
90
  if (state.locations) return;
46
91
 
@@ -90,7 +135,7 @@ function parseBundle(bundlePath) {
90
135
 
91
136
  // Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
92
137
  // features (e.g. `umd` library output) can wrap modules list into additional IIFE.
93
- _.each(args, arg => c(arg, state));
138
+ args.forEach(arg => c(arg, state));
94
139
  }
95
140
  }
96
141
  );
@@ -106,11 +151,48 @@ function parseBundle(bundlePath) {
106
151
  }
107
152
 
108
153
  return {
154
+ modules,
109
155
  src: content,
110
- modules
156
+ runtimeSrc: getBundleRuntime(content, walkState.locations)
111
157
  };
112
158
  }
113
159
 
160
+ /**
161
+ * Returns bundle source except modules
162
+ */
163
+ function getBundleRuntime(content, modulesLocations) {
164
+ const sortedLocations = Object.values(modulesLocations || {})
165
+ .sort((a, b) => a.start - b.start);
166
+
167
+ let result = '';
168
+ let lastIndex = 0;
169
+
170
+ for (const {start, end} of sortedLocations) {
171
+ result += content.slice(lastIndex, start);
172
+ lastIndex = end;
173
+ }
174
+
175
+ return result + content.slice(lastIndex, content.length);
176
+ }
177
+
178
+ function isIIFE(node) {
179
+ return (
180
+ node.type === 'ExpressionStatement' &&
181
+ (
182
+ node.expression.type === 'CallExpression' ||
183
+ (node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression')
184
+ )
185
+ );
186
+ }
187
+
188
+ function getIIFECallExpression(node) {
189
+ if (node.expression.type === 'UnaryExpression') {
190
+ return node.expression.argument;
191
+ } else {
192
+ return node.expression;
193
+ }
194
+ }
195
+
114
196
  function isModulesList(node) {
115
197
  return (
116
198
  isSimpleModulesList(node) ||
@@ -131,8 +213,8 @@ function isSimpleModulesList(node) {
131
213
  function isModulesHash(node) {
132
214
  return (
133
215
  node.type === 'ObjectExpression' &&
134
- _(node.properties)
135
- .map('value')
216
+ node.properties
217
+ .map(node => node.value)
136
218
  .every(isModuleWrapper)
137
219
  );
138
220
  }
@@ -140,7 +222,7 @@ function isModulesHash(node) {
140
222
  function isModulesArray(node) {
141
223
  return (
142
224
  node.type === 'ArrayExpression' &&
143
- _.every(node.elements, elem =>
225
+ node.elements.every(elem =>
144
226
  // Some of array items may be skipped because there is no module with such id
145
227
  !elem ||
146
228
  isModuleWrapper(elem)
@@ -193,7 +275,7 @@ function isChunkIds(node) {
193
275
  // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
194
276
  return (
195
277
  node.type === 'ArrayExpression' &&
196
- _.every(node.elements, isModuleId)
278
+ node.elements.every(isModuleId)
197
279
  );
198
280
  }
199
281
 
@@ -238,10 +320,11 @@ function getModulesLocations(node) {
238
320
  // Modules hash
239
321
  const modulesNodes = node.properties;
240
322
 
241
- return _.transform(modulesNodes, (result, moduleNode) => {
323
+ return modulesNodes.reduce((result, moduleNode) => {
242
324
  const moduleId = moduleNode.key.name || moduleNode.key.value;
243
325
 
244
326
  result[moduleId] = getModuleLocation(moduleNode.value);
327
+ return result;
245
328
  }, {});
246
329
  }
247
330
 
@@ -259,9 +342,11 @@ function getModulesLocations(node) {
259
342
  node.arguments[0].elements :
260
343
  node.elements;
261
344
 
262
- return _.transform(modulesNodes, (result, moduleNode, i) => {
263
- if (!moduleNode) return;
264
- result[i + minId] = getModuleLocation(moduleNode);
345
+ return modulesNodes.reduce((result, moduleNode, i) => {
346
+ if (moduleNode) {
347
+ result[i + minId] = getModuleLocation(moduleNode);
348
+ }
349
+ return result;
265
350
  }, {});
266
351
  }
267
352
 
@@ -0,0 +1,82 @@
1
+ const {createWriteStream} = require('fs');
2
+ const {Readable} = require('stream');
3
+
4
+ class StatsSerializeStream extends Readable {
5
+ constructor(stats) {
6
+ super();
7
+ this._indentLevel = 0;
8
+ this._stringifier = this._stringify(stats);
9
+ }
10
+
11
+ get _indent() {
12
+ return ' '.repeat(this._indentLevel);
13
+ }
14
+
15
+ _read() {
16
+ let readMore = true;
17
+
18
+ while (readMore) {
19
+ const {value, done} = this._stringifier.next();
20
+
21
+ if (done) {
22
+ this.push(null);
23
+ readMore = false;
24
+ } else {
25
+ readMore = this.push(value);
26
+ }
27
+ }
28
+ }
29
+
30
+ * _stringify(obj) {
31
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) {
32
+ yield JSON.stringify(obj);
33
+ } else if (Array.isArray(obj)) {
34
+ yield '[';
35
+ this._indentLevel++;
36
+
37
+ let isFirst = true;
38
+ for (let item of obj) {
39
+ if (item === undefined) {
40
+ item = null;
41
+ }
42
+
43
+ yield `${isFirst ? '' : ','}\n${this._indent}`;
44
+ yield* this._stringify(item);
45
+ isFirst = false;
46
+ }
47
+
48
+ this._indentLevel--;
49
+ yield obj.length ? `\n${this._indent}]` : ']';
50
+ } else {
51
+ yield '{';
52
+ this._indentLevel++;
53
+
54
+ let isFirst = true;
55
+ const entries = Object.entries(obj);
56
+ for (const [itemKey, itemValue] of entries) {
57
+ if (itemValue === undefined) {
58
+ continue;
59
+ }
60
+
61
+ yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `;
62
+ yield* this._stringify(itemValue);
63
+ isFirst = false;
64
+ }
65
+
66
+ this._indentLevel--;
67
+ yield entries.length ? `\n${this._indent}}` : '}';
68
+ }
69
+ }
70
+ }
71
+
72
+ exports.StatsSerializeStream = StatsSerializeStream;
73
+ exports.writeStats = writeStats;
74
+
75
+ async function writeStats(stats, filepath) {
76
+ return new Promise((resolve, reject) => {
77
+ new StatsSerializeStream(stats)
78
+ .on('end', resolve)
79
+ .on('error', reject)
80
+ .pipe(createWriteStream(filepath));
81
+ });
82
+ }
@@ -1,22 +1,65 @@
1
- <!DOCTYPE html>
1
+ /* eslint-disable max-len */
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ const _ = require('lodash');
6
+
7
+ const projectRoot = path.resolve(__dirname, '..');
8
+ const assetsRoot = path.join(projectRoot, 'public');
9
+
10
+ exports.renderViewer = renderViewer;
11
+
12
+ /**
13
+ * Escapes `<` characters in JSON to safely use it in `<script>` tag.
14
+ */
15
+ function escapeJson(json) {
16
+ return JSON.stringify(json).replace(/</gu, '\\u003c');
17
+ }
18
+
19
+ function getAssetContent(filename) {
20
+ const assetPath = path.join(assetsRoot, filename);
21
+
22
+ if (!assetPath.startsWith(assetsRoot)) {
23
+ throw new Error(`"${filename}" is outside of the assets root`);
24
+ }
25
+
26
+ return fs.readFileSync(assetPath, 'utf8');
27
+ }
28
+
29
+ function html(strings, ...values) {
30
+ return strings.map((string, index) => `${string}${values[index] || ''}`).join('');
31
+ }
32
+
33
+ function getScript(filename, mode) {
34
+ if (mode === 'static') {
35
+ return `<!-- ${_.escape(filename)} -->
36
+ <script>${getAssetContent(filename)}</script>`;
37
+ } else {
38
+ return `<script src="${_.escape(filename)}"></script>`;
39
+ }
40
+ }
41
+
42
+ function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} = {}) {
43
+ return html`<!DOCTYPE html>
2
44
  <html>
3
45
  <head>
4
46
  <meta charset="UTF-8"/>
5
47
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
6
- <title><%= title %></title>
48
+ <title>${_.escape(title)}</title>
7
49
  <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" />
8
50
 
9
51
  <script>
10
- window.enableWebSocket = <%- escapeJson(enableWebSocket) %>;
52
+ window.enableWebSocket = ${escapeJson(enableWebSocket)};
11
53
  </script>
12
- <%- include('script', { filename: 'viewer.js' }) %>
54
+ ${getScript('viewer.js', mode)}
13
55
  </head>
14
56
 
15
57
  <body>
16
58
  <div id="app"></div>
17
59
  <script>
18
- window.chartData = <%- escapeJson(chartData) %>;
19
- window.defaultSizes = <%- escapeJson(defaultSizes) %>;
60
+ window.chartData = ${escapeJson(chartData)};
61
+ window.defaultSizes = ${escapeJson(defaultSizes)};
20
62
  </script>
21
63
  </body>
22
- </html>
64
+ </html>`;
65
+ }
@@ -62,7 +62,7 @@ export default class BaseFolder extends Node {
62
62
  walk(walker, state = {}, deep = true) {
63
63
  let stopped = false;
64
64
 
65
- _.each(this.children, child => {
65
+ Object.values(this.children).forEach(child => {
66
66
  if (deep && child.walk) {
67
67
  state = child.walk(walker, state, stop);
68
68
  } else {
@@ -15,7 +15,7 @@ export default class ConcatenatedModule extends Module {
15
15
  }
16
16
 
17
17
  fillContentModules() {
18
- _.each(this.data.modules, moduleData => this.addContentModule(moduleData));
18
+ this.data.modules.forEach(moduleData => this.addContentModule(moduleData));
19
19
  }
20
20
 
21
21
  addContentModule(moduleData) {
@@ -28,7 +28,7 @@ export default class ConcatenatedModule extends Module {
28
28
  const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
29
29
  let currentFolder = this;
30
30
 
31
- _.each(folders, folderName => {
31
+ folders.forEach(folderName => {
32
32
  let childFolder = currentFolder.getChild(folderName);
33
33
 
34
34
  if (!childFolder) {
@@ -30,7 +30,7 @@ export default class Folder extends BaseFolder {
30
30
  const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
31
31
  let currentFolder = this;
32
32
 
33
- _.each(folders, folderName => {
33
+ folders.forEach(folderName => {
34
34
  let childNode = currentFolder.getChild(folderName);
35
35
 
36
36
  if (
package/src/utils.js CHANGED
@@ -1,5 +1,6 @@
1
- const {inspect} = require('util');
1
+ const {inspect, types} = require('util');
2
2
  const _ = require('lodash');
3
+ const opener = require('opener');
3
4
 
4
5
  const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
5
6
 
@@ -14,11 +15,11 @@ function createAssetsFilter(excludePatterns) {
14
15
  pattern = new RegExp(pattern, 'u');
15
16
  }
16
17
 
17
- if (_.isRegExp(pattern)) {
18
+ if (types.isRegExp(pattern)) {
18
19
  return (asset) => pattern.test(asset);
19
20
  }
20
21
 
21
- if (!_.isFunction(pattern)) {
22
+ if (typeof pattern !== 'function') {
22
23
  throw new TypeError(
23
24
  `Pattern should be either string, RegExp or a function, but "${inspect(pattern, {depth: 0})}" got.`
24
25
  );
@@ -29,7 +30,7 @@ function createAssetsFilter(excludePatterns) {
29
30
  .value();
30
31
 
31
32
  if (excludeFunctions.length) {
32
- return (asset) => _.every(excludeFunctions, fn => fn(asset) !== true);
33
+ return (asset) => excludeFunctions.every(fn => fn(asset) !== true);
33
34
  } else {
34
35
  return () => true;
35
36
  }
@@ -51,3 +52,14 @@ exports.defaultTitle = function () {
51
52
 
52
53
  return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`;
53
54
  };
55
+
56
+ /**
57
+ * Calls opener on a URI, but silently try / catches it.
58
+ */
59
+ exports.open = function (uri, logger) {
60
+ try {
61
+ opener(uri);
62
+ } catch (err) {
63
+ logger.debug(`Opener failed to open "${uri}":\n${err}`);
64
+ }
65
+ };