webpack-bundle-analyzer 3.8.0 → 3.9.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/CHANGELOG.md CHANGED
@@ -14,6 +14,14 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
14
14
 
15
15
  <!-- Add changelog entries for new changes under this section -->
16
16
 
17
+ ## 3.9.0
18
+
19
+ * **New Feature**
20
+ * Adds option `reportTitle` to set title in HTML reports; default remains date of report generation ([#354](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/354) by [@eoingroat](https://github.com/eoingroat))
21
+
22
+ * **Improvement**
23
+ * Added capability to parse bundles that have child assets generated ([#376](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/376) by [@masterkidan](https://github.com/masterkidan) and [#378](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/378) by [@https://github.com/dabbott](https://github.com/https://github.com/dabbott))
24
+
17
25
  ## 3.8.0
18
26
 
19
27
  * **Improvement**
package/README.md CHANGED
@@ -60,6 +60,7 @@ new BundleAnalyzerPlugin(options?: object)
60
60
  |**`analyzerHost`**|`{String}`|Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.|
61
61
  |**`analyzerPort`**|`{Number}` or `auto`|Default: `8888`. Port that will be used in `server` mode to start HTTP server.|
62
62
  |**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
63
+ |**`reportTitle`**|`{String\|function}`|Default: function that returns pretty printed current date and time. Content of the HTML `title` element; or a function of the form `() => string` that provides the content.|
63
64
  |**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
64
65
  |**`openAnalyzer`**|`{Boolean}`|Default: `true`. Automatically open report in default browser.|
65
66
  |**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory|
@@ -118,6 +119,7 @@ Directory containing all generated bundles.
118
119
  -h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
119
120
  -p, --port <n> Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
120
121
  -r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
122
+ -t, --title <title> String to use in title element of html report. (default: pretty printed current date)
121
123
  -s, --default-sizes <type> Module sizes to show in treemap by default.
122
124
  Possible values: stat, parsed, gzip (default: parsed)
123
125
  -O, --no-open Don't open report in default browser automatically.
@@ -24,12 +24,15 @@ const Logger = require('./Logger');
24
24
 
25
25
  const viewer = require('./viewer');
26
26
 
27
+ const utils = require('./utils');
28
+
27
29
  class BundleAnalyzerPlugin {
28
30
  constructor(opts = {}) {
29
31
  this.opts = _objectSpread({
30
32
  analyzerMode: 'server',
31
33
  analyzerHost: '127.0.0.1',
32
34
  reportFilename: null,
35
+ reportTitle: utils.defaultTitle,
33
36
  defaultSizes: 'parsed',
34
37
  openAnalyzer: true,
35
38
  generateStatsFile: false,
@@ -128,6 +131,7 @@ class BundleAnalyzerPlugin {
128
131
  openBrowser: _this2.opts.openAnalyzer,
129
132
  host: _this2.opts.analyzerHost,
130
133
  port: _this2.opts.analyzerPort,
134
+ reportTitle: _this2.opts.reportTitle,
131
135
  bundleDir: _this2.getBundleDirFromCompiler(),
132
136
  logger: _this2.logger,
133
137
  defaultSizes: _this2.opts.defaultSizes,
@@ -157,6 +161,7 @@ class BundleAnalyzerPlugin {
157
161
  yield viewer.generateReport(stats, {
158
162
  openBrowser: _this4.opts.openAnalyzer,
159
163
  reportFilename: path.resolve(_this4.compiler.outputPath, _this4.opts.reportFilename || 'report.html'),
164
+ reportTitle: _this4.opts.reportTitle,
160
165
  bundleDir: _this4.getBundleDirFromCompiler(),
161
166
  logger: _this4.logger,
162
167
  defaultSizes: _this4.opts.defaultSizes,
package/lib/analyzer.js CHANGED
@@ -35,7 +35,26 @@ function getViewerData(bundleStats, bundleDir, opts) {
35
35
  const isAssetIncluded = createAssetsFilter(excludeAssets); // Sometimes all the information is located in `children` array (e.g. problem in #10)
36
36
 
37
37
  if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
38
- bundleStats = bundleStats.children[0];
38
+ const {
39
+ children
40
+ } = bundleStats;
41
+ bundleStats = bundleStats.children[0]; // Sometimes if there are additional child chunks produced add them as child assets,
42
+ // leave the 1st one as that is considered the 'root' asset.
43
+
44
+ for (let i = 1; i < children.length; i++) {
45
+ bundleStats.children[i].assets.forEach(asset => {
46
+ asset.isChild = true;
47
+ bundleStats.assets.push(asset);
48
+ });
49
+ }
50
+ } else if (!_.isEmpty(bundleStats.children)) {
51
+ // Sometimes if there are additional child chunks produced add them as child assets
52
+ bundleStats.children.forEach(child => {
53
+ child.assets.forEach(asset => {
54
+ asset.isChild = true;
55
+ bundleStats.assets.push(asset);
56
+ });
57
+ });
39
58
  } // Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
40
59
 
41
60
 
@@ -77,9 +96,11 @@ function getViewerData(bundleStats, bundleDir, opts) {
77
96
  }
78
97
  }
79
98
 
80
- const modules = getBundleModules(bundleStats);
81
-
82
99
  const assets = _.transform(bundleStats.assets, (result, statAsset) => {
100
+ // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
101
+ const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
102
+ const modules = assetBundles ? getBundleModules(assetBundles) : [];
103
+
83
104
  const asset = result[statAsset.name] = _.pick(statAsset, 'size');
84
105
 
85
106
  if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
@@ -116,6 +137,10 @@ function readStatsFromFile(filename) {
116
137
  return JSON.parse(fs.readFileSync(filename, 'utf8'));
117
138
  }
118
139
 
140
+ function getChildAssetBundles(bundleStats, assetName) {
141
+ return _.find(bundleStats.children, c => _(c.assetsByChunkName).values().flatten().includes(assetName));
142
+ }
143
+
119
144
  function getBundleModules(bundleStats) {
120
145
  return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id').value();
121
146
  }
@@ -20,6 +20,8 @@ const viewer = require('../viewer');
20
20
 
21
21
  const Logger = require('../Logger');
22
22
 
23
+ const utils = require('../utils');
24
+
23
25
  const SIZES = new Set(['stat', 'parsed', 'gzip']);
24
26
  const program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
25
27
 
@@ -30,12 +32,13 @@ const program = commander.version(require('../../package.json').version).usage(`
30
32
  You should provided it if you want analyzer to show you the real parsed module sizes.
31
33
  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( // Had to make `host` parameter optional in order to let `-h` flag output help message
32
34
  // Fixes https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/239
33
- '-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('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[...SIZES].join(', ')}`), 'parsed').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);
35
+ '-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('-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
36
  let {
35
37
  mode,
36
38
  host,
37
39
  port,
38
40
  report: reportFilename,
41
+ title: reportTitle,
39
42
  defaultSizes,
40
43
  logLevel,
41
44
  open: openBrowser,
@@ -43,6 +46,11 @@ let {
43
46
  args: [bundleStatsFile, bundleDir]
44
47
  } = program;
45
48
  const logger = new Logger(logLevel);
49
+
50
+ if (typeof reportTitle === 'undefined') {
51
+ reportTitle = utils.defaultTitle;
52
+ }
53
+
46
54
  if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
47
55
 
48
56
  if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
@@ -74,6 +82,7 @@ if (mode === 'server') {
74
82
  port,
75
83
  host,
76
84
  defaultSizes,
85
+ reportTitle,
77
86
  bundleDir,
78
87
  excludeAssets,
79
88
  logger: new Logger(logLevel)
@@ -82,6 +91,7 @@ if (mode === 'server') {
82
91
  viewer.generateReport(bundleStats, {
83
92
  openBrowser,
84
93
  reportFilename: resolve(reportFilename || 'report.html'),
94
+ reportTitle,
85
95
  defaultSizes,
86
96
  bundleDir,
87
97
  excludeAssets,
package/lib/utils.js CHANGED
@@ -40,12 +40,13 @@ function createAssetsFilter(excludePatterns) {
40
40
  * */
41
41
 
42
42
 
43
- exports.getCurrentTime = function () {
43
+ exports.defaultTitle = function () {
44
44
  const time = new Date();
45
45
  const year = time.getFullYear();
46
46
  const month = MONTHS[time.getMonth()];
47
47
  const day = time.getDate();
48
48
  const hour = `0${time.getHours()}`.slice(-2);
49
49
  const minute = `0${time.getMinutes()}`.slice(-2);
50
- return `${day} ${month} ${year} at ${hour}:${minute}`;
50
+ const currentTime = `${day} ${month} ${year} at ${hour}:${minute}`;
51
+ return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`;
51
52
  };
package/lib/viewer.js CHANGED
@@ -26,14 +26,21 @@ const {
26
26
  bold
27
27
  } = require('chalk');
28
28
 
29
- const utils = require('./utils');
30
-
31
29
  const Logger = require('./Logger');
32
30
 
33
31
  const analyzer = require('./analyzer');
34
32
 
35
33
  const projectRoot = path.resolve(__dirname, '..');
36
34
  const assetsRoot = path.join(projectRoot, 'public');
35
+
36
+ function resolveTitle(reportTitle) {
37
+ if (typeof reportTitle === 'function') {
38
+ return reportTitle();
39
+ } else {
40
+ return reportTitle;
41
+ }
42
+ }
43
+
37
44
  module.exports = {
38
45
  startServer,
39
46
  generateReport,
@@ -41,7 +48,6 @@ module.exports = {
41
48
  // deprecated
42
49
  start: startServer
43
50
  };
44
- const title = `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${utils.getCurrentTime()}]`;
45
51
 
46
52
  function startServer(_x, _x2) {
47
53
  return _startServer.apply(this, arguments);
@@ -56,7 +62,8 @@ function _startServer() {
56
62
  bundleDir = null,
57
63
  logger = new Logger(),
58
64
  defaultSizes = 'parsed',
59
- excludeAssets = null
65
+ excludeAssets = null,
66
+ reportTitle
60
67
  } = opts || {};
61
68
  const analyzerOpts = {
62
69
  logger,
@@ -74,7 +81,7 @@ function _startServer() {
74
81
  app.use('/', (req, res) => {
75
82
  res.render('viewer', {
76
83
  mode: 'server',
77
- title,
84
+ title: resolveTitle(reportTitle),
78
85
 
79
86
  get chartData() {
80
87
  return chartData;
@@ -140,6 +147,7 @@ function _generateReport() {
140
147
  const {
141
148
  openBrowser = true,
142
149
  reportFilename,
150
+ reportTitle,
143
151
  bundleDir = null,
144
152
  logger = new Logger(),
145
153
  defaultSizes = 'parsed',
@@ -153,7 +161,7 @@ function _generateReport() {
153
161
  yield new Promise((resolve, reject) => {
154
162
  ejs.renderFile(`${projectRoot}/views/viewer.ejs`, {
155
163
  mode: 'static',
156
- title,
164
+ title: resolveTitle(reportTitle),
157
165
  chartData,
158
166
  defaultSizes,
159
167
  enableWebSocket: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack-bundle-analyzer",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap",
5
5
  "author": "Yury Grunin <grunin.ya@ya.ru>",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  "express": "^4.16.3",
43
43
  "filesize": "^3.6.1",
44
44
  "gzip-size": "^5.0.0",
45
- "lodash": "^4.17.15",
45
+ "lodash": "^4.17.19",
46
46
  "mkdirp": "^0.5.1",
47
47
  "opener": "^1.5.1",
48
48
  "ws": "^6.0.0"
@@ -5,6 +5,7 @@ const {bold} = require('chalk');
5
5
 
6
6
  const Logger = require('./Logger');
7
7
  const viewer = require('./viewer');
8
+ const utils = require('./utils');
8
9
 
9
10
  class BundleAnalyzerPlugin {
10
11
  constructor(opts = {}) {
@@ -12,6 +13,7 @@ class BundleAnalyzerPlugin {
12
13
  analyzerMode: 'server',
13
14
  analyzerHost: '127.0.0.1',
14
15
  reportFilename: null,
16
+ reportTitle: utils.defaultTitle,
15
17
  defaultSizes: 'parsed',
16
18
  openAnalyzer: true,
17
19
  generateStatsFile: false,
@@ -108,6 +110,7 @@ class BundleAnalyzerPlugin {
108
110
  openBrowser: this.opts.openAnalyzer,
109
111
  host: this.opts.analyzerHost,
110
112
  port: this.opts.analyzerPort,
113
+ reportTitle: this.opts.reportTitle,
111
114
  bundleDir: this.getBundleDirFromCompiler(),
112
115
  logger: this.logger,
113
116
  defaultSizes: this.opts.defaultSizes,
@@ -129,6 +132,7 @@ class BundleAnalyzerPlugin {
129
132
  await viewer.generateReport(stats, {
130
133
  openBrowser: this.opts.openAnalyzer,
131
134
  reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
135
+ reportTitle: this.opts.reportTitle,
132
136
  bundleDir: this.getBundleDirFromCompiler(),
133
137
  logger: this.logger,
134
138
  defaultSizes: this.opts.defaultSizes,
package/src/analyzer.js CHANGED
@@ -27,7 +27,24 @@ function getViewerData(bundleStats, bundleDir, opts) {
27
27
 
28
28
  // Sometimes all the information is located in `children` array (e.g. problem in #10)
29
29
  if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
30
+ const {children} = bundleStats;
30
31
  bundleStats = bundleStats.children[0];
32
+ // Sometimes if there are additional child chunks produced add them as child assets,
33
+ // leave the 1st one as that is considered the 'root' asset.
34
+ for (let i = 1; i < children.length; i++) {
35
+ bundleStats.children[i].assets.forEach((asset) => {
36
+ asset.isChild = true;
37
+ bundleStats.assets.push(asset);
38
+ });
39
+ }
40
+ } else if (!_.isEmpty(bundleStats.children)) {
41
+ // Sometimes if there are additional child chunks produced add them as child assets
42
+ bundleStats.children.forEach((child) => {
43
+ child.assets.forEach((asset) => {
44
+ asset.isChild = true;
45
+ bundleStats.assets.push(asset);
46
+ });
47
+ });
31
48
  }
32
49
 
33
50
  // Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
@@ -70,8 +87,10 @@ function getViewerData(bundleStats, bundleDir, opts) {
70
87
  }
71
88
  }
72
89
 
73
- const modules = getBundleModules(bundleStats);
74
90
  const assets = _.transform(bundleStats.assets, (result, statAsset) => {
91
+ // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
92
+ const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats;
93
+ const modules = assetBundles ? getBundleModules(assetBundles) : [];
75
94
  const asset = result[statAsset.name] = _.pick(statAsset, 'size');
76
95
 
77
96
  if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
@@ -113,6 +132,15 @@ function readStatsFromFile(filename) {
113
132
  );
114
133
  }
115
134
 
135
+ function getChildAssetBundles(bundleStats, assetName) {
136
+ return _.find(bundleStats.children, (c) =>
137
+ _(c.assetsByChunkName)
138
+ .values()
139
+ .flatten()
140
+ .includes(assetName)
141
+ );
142
+ }
143
+
116
144
  function getBundleModules(bundleStats) {
117
145
  return _(bundleStats.chunks)
118
146
  .map('modules')
@@ -9,6 +9,7 @@ const {magenta} = require('chalk');
9
9
  const analyzer = require('../analyzer');
10
10
  const viewer = require('../viewer');
11
11
  const Logger = require('../Logger');
12
+ const utils = require('../utils');
12
13
 
13
14
  const SIZES = new Set(['stat', 'parsed', 'gzip']);
14
15
 
@@ -48,6 +49,10 @@ const program = commander
48
49
  '-r, --report <file>',
49
50
  'Path to bundle report file that will be generated in `static` mode.'
50
51
  )
52
+ .option(
53
+ '-t, --title <title>',
54
+ 'String to use in title element of html report.'
55
+ )
51
56
  .option(
52
57
  '-s, --default-sizes <type>',
53
58
  'Module sizes to show in treemap by default.' +
@@ -77,6 +82,7 @@ let {
77
82
  host,
78
83
  port,
79
84
  report: reportFilename,
85
+ title: reportTitle,
80
86
  defaultSizes,
81
87
  logLevel,
82
88
  open: openBrowser,
@@ -85,6 +91,10 @@ let {
85
91
  } = program;
86
92
  const logger = new Logger(logLevel);
87
93
 
94
+ if (typeof reportTitle === 'undefined') {
95
+ reportTitle = utils.defaultTitle;
96
+ }
97
+
88
98
  if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
89
99
  if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
90
100
  showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
@@ -116,6 +126,7 @@ if (mode === 'server') {
116
126
  port,
117
127
  host,
118
128
  defaultSizes,
129
+ reportTitle,
119
130
  bundleDir,
120
131
  excludeAssets,
121
132
  logger: new Logger(logLevel)
@@ -124,6 +135,7 @@ if (mode === 'server') {
124
135
  viewer.generateReport(bundleStats, {
125
136
  openBrowser,
126
137
  reportFilename: resolve(reportFilename || 'report.html'),
138
+ reportTitle,
127
139
  defaultSizes,
128
140
  bundleDir,
129
141
  excludeAssets,
package/src/utils.js CHANGED
@@ -39,12 +39,15 @@ function createAssetsFilter(excludePatterns) {
39
39
  * @desc get string of current time
40
40
  * format: dd/MMM HH:mm
41
41
  * */
42
- exports.getCurrentTime = function () {
42
+ exports.defaultTitle = function () {
43
43
  const time = new Date();
44
44
  const year = time.getFullYear();
45
45
  const month = MONTHS[time.getMonth()];
46
46
  const day = time.getDate();
47
47
  const hour = `0${time.getHours()}`.slice(-2);
48
48
  const minute = `0${time.getMinutes()}`.slice(-2);
49
- return `${day} ${month} ${year} at ${hour}:${minute}`;
49
+
50
+ const currentTime = `${day} ${month} ${year} at ${hour}:${minute}`;
51
+
52
+ return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`;
50
53
  };
package/src/viewer.js CHANGED
@@ -10,13 +10,20 @@ const opener = require('opener');
10
10
  const mkdir = require('mkdirp');
11
11
  const {bold} = require('chalk');
12
12
 
13
- const utils = require('./utils');
14
13
  const Logger = require('./Logger');
15
14
  const analyzer = require('./analyzer');
16
15
 
17
16
  const projectRoot = path.resolve(__dirname, '..');
18
17
  const assetsRoot = path.join(projectRoot, 'public');
19
18
 
19
+ function resolveTitle(reportTitle) {
20
+ if (typeof reportTitle === 'function') {
21
+ return reportTitle();
22
+ } else {
23
+ return reportTitle;
24
+ }
25
+ }
26
+
20
27
  module.exports = {
21
28
  startServer,
22
29
  generateReport,
@@ -25,8 +32,6 @@ module.exports = {
25
32
  start: startServer
26
33
  };
27
34
 
28
- const title = `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${utils.getCurrentTime()}]`;
29
-
30
35
  async function startServer(bundleStats, opts) {
31
36
  const {
32
37
  port = 8888,
@@ -35,7 +40,8 @@ async function startServer(bundleStats, opts) {
35
40
  bundleDir = null,
36
41
  logger = new Logger(),
37
42
  defaultSizes = 'parsed',
38
- excludeAssets = null
43
+ excludeAssets = null,
44
+ reportTitle
39
45
  } = opts || {};
40
46
 
41
47
  const analyzerOpts = {logger, excludeAssets};
@@ -56,7 +62,7 @@ async function startServer(bundleStats, opts) {
56
62
  app.use('/', (req, res) => {
57
63
  res.render('viewer', {
58
64
  mode: 'server',
59
- title,
65
+ title: resolveTitle(reportTitle),
60
66
  get chartData() { return chartData },
61
67
  defaultSizes,
62
68
  enableWebSocket: true,
@@ -123,6 +129,7 @@ async function generateReport(bundleStats, opts) {
123
129
  const {
124
130
  openBrowser = true,
125
131
  reportFilename,
132
+ reportTitle,
126
133
  bundleDir = null,
127
134
  logger = new Logger(),
128
135
  defaultSizes = 'parsed',
@@ -138,7 +145,7 @@ async function generateReport(bundleStats, opts) {
138
145
  `${projectRoot}/views/viewer.ejs`,
139
146
  {
140
147
  mode: 'static',
141
- title,
148
+ title: resolveTitle(reportTitle),
142
149
  chartData,
143
150
  defaultSizes,
144
151
  enableWebSocket: false,