Skip to content

Commit a0b354f

Browse files
authored
Adds reportTitle option (allow reproducible build) (#354)
Adds CLI options '-t' and '--title' taking strings Adds api option 'reportTitle' taking strings and functions Retains existing title behaviours as a default function Updates README Adds tests for plugin option reportTitle as string, function, errored function and default value Adds tests for cli option title as string and default value
1 parent 14e3313 commit a0b354f

File tree

8 files changed

+139
-12
lines changed

8 files changed

+139
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
1414

1515
<!-- Add changelog entries for new changes under this section -->
1616

17+
* **New Feature**
18+
* 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)
19+
1720
## 3.8.0
1821

1922
* **Improvement**

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ new BundleAnalyzerPlugin(options?: object)
6060
|**`analyzerHost`**|`{String}`|Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.|
6161
|**`analyzerPort`**|`{Number}` or `auto`|Default: `8888`. Port that will be used in `server` mode to start HTTP server.|
6262
|**`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.|
6364
|**`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.|
6465
|**`openAnalyzer`**|`{Boolean}`|Default: `true`. Automatically open report in default browser.|
6566
|**`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.
118119
-h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
119120
-p, --port <n> Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
120121
-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)
121123
-s, --default-sizes <type> Module sizes to show in treemap by default.
122124
Possible values: stat, parsed, gzip (default: parsed)
123125
-O, --no-open Don't open report in default browser automatically.

src/BundleAnalyzerPlugin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ const {bold} = require('chalk');
55

66
const Logger = require('./Logger');
77
const viewer = require('./viewer');
8+
const utils = require('./utils');
89

910
class BundleAnalyzerPlugin {
1011
constructor(opts = {}) {
1112
this.opts = {
1213
analyzerMode: 'server',
1314
analyzerHost: '127.0.0.1',
1415
reportFilename: null,
16+
reportTitle: utils.defaultTitle,
1517
defaultSizes: 'parsed',
1618
openAnalyzer: true,
1719
generateStatsFile: false,
@@ -108,6 +110,7 @@ class BundleAnalyzerPlugin {
108110
openBrowser: this.opts.openAnalyzer,
109111
host: this.opts.analyzerHost,
110112
port: this.opts.analyzerPort,
113+
reportTitle: this.opts.reportTitle,
111114
bundleDir: this.getBundleDirFromCompiler(),
112115
logger: this.logger,
113116
defaultSizes: this.opts.defaultSizes,
@@ -129,6 +132,7 @@ class BundleAnalyzerPlugin {
129132
await viewer.generateReport(stats, {
130133
openBrowser: this.opts.openAnalyzer,
131134
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
135+
reportTitle: this.opts.reportTitle,
132136
bundleDir: this.getBundleDirFromCompiler(),
133137
logger: this.logger,
134138
defaultSizes: this.opts.defaultSizes,

src/bin/analyzer.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {magenta} = require('chalk');
99
const analyzer = require('../analyzer');
1010
const viewer = require('../viewer');
1111
const Logger = require('../Logger');
12+
const utils = require('../utils');
1213

1314
const SIZES = new Set(['stat', 'parsed', 'gzip']);
1415

@@ -48,6 +49,10 @@ const program = commander
4849
'-r, --report <file>',
4950
'Path to bundle report file that will be generated in `static` mode.'
5051
)
52+
.option(
53+
'-t, --title <title>',
54+
'String to use in title element of html report.'
55+
)
5156
.option(
5257
'-s, --default-sizes <type>',
5358
'Module sizes to show in treemap by default.' +
@@ -77,6 +82,7 @@ let {
7782
host,
7883
port,
7984
report: reportFilename,
85+
title: reportTitle,
8086
defaultSizes,
8187
logLevel,
8288
open: openBrowser,
@@ -85,6 +91,10 @@ let {
8591
} = program;
8692
const logger = new Logger(logLevel);
8793

94+
if (typeof reportTitle === 'undefined') {
95+
reportTitle = utils.defaultTitle;
96+
}
97+
8898
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
8999
if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
90100
showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
@@ -116,6 +126,7 @@ if (mode === 'server') {
116126
port,
117127
host,
118128
defaultSizes,
129+
reportTitle,
119130
bundleDir,
120131
excludeAssets,
121132
logger: new Logger(logLevel)
@@ -124,6 +135,7 @@ if (mode === 'server') {
124135
viewer.generateReport(bundleStats, {
125136
openBrowser,
126137
reportFilename: resolve(reportFilename || 'report.html'),
138+
reportTitle,
127139
defaultSizes,
128140
bundleDir,
129141
excludeAssets,

src/utils.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ function createAssetsFilter(excludePatterns) {
3939
* @desc get string of current time
4040
* format: dd/MMM HH:mm
4141
* */
42-
exports.getCurrentTime = function () {
42+
exports.defaultTitle = function () {
4343
const time = new Date();
4444
const year = time.getFullYear();
4545
const month = MONTHS[time.getMonth()];
4646
const day = time.getDate();
4747
const hour = `0${time.getHours()}`.slice(-2);
4848
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}]`;
5053
};

src/viewer.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ const opener = require('opener');
1010
const mkdir = require('mkdirp');
1111
const {bold} = require('chalk');
1212

13-
const utils = require('./utils');
1413
const Logger = require('./Logger');
1514
const analyzer = require('./analyzer');
1615

1716
const projectRoot = path.resolve(__dirname, '..');
1817
const assetsRoot = path.join(projectRoot, 'public');
1918

19+
function resolveTitle(reportTitle) {
20+
if (typeof reportTitle === 'function') {
21+
return reportTitle();
22+
} else {
23+
return reportTitle;
24+
}
25+
}
26+
2027
module.exports = {
2128
startServer,
2229
generateReport,
@@ -25,8 +32,6 @@ module.exports = {
2532
start: startServer
2633
};
2734

28-
const title = `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${utils.getCurrentTime()}]`;
29-
3035
async function startServer(bundleStats, opts) {
3136
const {
3237
port = 8888,
@@ -35,7 +40,8 @@ async function startServer(bundleStats, opts) {
3540
bundleDir = null,
3641
logger = new Logger(),
3742
defaultSizes = 'parsed',
38-
excludeAssets = null
43+
excludeAssets = null,
44+
reportTitle
3945
} = opts || {};
4046

4147
const analyzerOpts = {logger, excludeAssets};
@@ -56,7 +62,7 @@ async function startServer(bundleStats, opts) {
5662
app.use('/', (req, res) => {
5763
res.render('viewer', {
5864
mode: 'server',
59-
title,
65+
title: resolveTitle(reportTitle),
6066
get chartData() { return chartData },
6167
defaultSizes,
6268
enableWebSocket: true,
@@ -123,6 +129,7 @@ async function generateReport(bundleStats, opts) {
123129
const {
124130
openBrowser = true,
125131
reportFilename,
132+
reportTitle,
126133
bundleDir = null,
127134
logger = new Logger(),
128135
defaultSizes = 'parsed',
@@ -138,7 +145,7 @@ async function generateReport(bundleStats, opts) {
138145
`${projectRoot}/views/viewer.ejs`,
139146
{
140147
mode: 'static',
141-
title,
148+
title: resolveTitle(reportTitle),
142149
chartData,
143150
defaultSizes,
144151
enableWebSocket: false,

test/analyzer.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ describe('Analyzer', function () {
114114
const chartData = require(path.resolve(__dirname, 'output/report.json'));
115115
expect(chartData).to.containSubset(require('./stats/with-modules-in-chunks/expected-chart-data'));
116116
});
117+
118+
describe('options', function () {
119+
describe('title', function () {
120+
it('should take the --title option', async function () {
121+
const reportTitle = 'A string report title';
122+
generateReportFrom('with-modules-chunk.json', `--title "${reportTitle}"`);
123+
124+
const generatedReportTitle = await getTitleFromReport();
125+
126+
expect(generatedReportTitle).to.equal(reportTitle);
127+
});
128+
it('should take the -t option', async function () {
129+
const reportTitle = 'A string report title';
130+
131+
generateReportFrom('with-modules-chunk.json', `-t "${reportTitle}"`);
132+
133+
const generatedReportTitle = await getTitleFromReport();
134+
135+
expect(generatedReportTitle).to.equal(reportTitle);
136+
});
137+
it('should use a suitable default title', async function () {
138+
generateReportFrom('with-modules-chunk.json');
139+
140+
const generatedReportTitle = await getTitleFromReport();
141+
142+
expect(generatedReportTitle).to.match(/^webpack-bundle-analyzer \[.* at \d{2}:\d{2}\]/u);
143+
});
144+
});
145+
});
117146
});
118147

119148
function generateJSONReportFrom(statsFilename) {
@@ -122,10 +151,16 @@ function generateJSONReportFrom(statsFilename) {
122151
});
123152
}
124153

125-
function generateReportFrom(statsFilename) {
126-
childProcess.execSync(`../lib/bin/analyzer.js -m static -r output/report.html -O stats/${statsFilename}`, {
127-
cwd: __dirname
128-
});
154+
function generateReportFrom(statsFilename, additionalOptions = '') {
155+
childProcess.execSync(
156+
`../lib/bin/analyzer.js ${additionalOptions} -m static -r output/report.html -O stats/${statsFilename}`,
157+
{
158+
cwd: __dirname
159+
});
160+
}
161+
162+
async function getTitleFromReport() {
163+
return await nightmare.goto(`file://${__dirname}/output/report.html`).title();
129164
}
130165

131166
async function getChartData() {

test/plugin.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,63 @@ describe('Plugin', function () {
9696
expect(_.map(chartData, 'label')).to.deep.equal(['bundle.js']);
9797
});
9898
});
99+
100+
describe('reportTitle', function () {
101+
it('should have a sensible default', async function () {
102+
const config = makeWebpackConfig();
103+
104+
await webpackCompile(config);
105+
106+
const generatedReportTitle = await getTitleFromReport();
107+
108+
expect(generatedReportTitle).to.match(/^webpack-bundle-analyzer \[.* at \d{2}:\d{2}\]/u);
109+
});
110+
it('should use a string', async function () {
111+
const reportTitle = 'A string report title';
112+
const config = makeWebpackConfig({
113+
analyzerOpts: {
114+
reportTitle
115+
}
116+
});
117+
118+
await webpackCompile(config);
119+
120+
const generatedReportTitle = await getTitleFromReport();
121+
122+
expect(generatedReportTitle).to.equal(reportTitle);
123+
});
124+
it('should use a function', async function () {
125+
const reportTitleResult = 'A string report title';
126+
const config = makeWebpackConfig({
127+
analyzerOpts: {
128+
reportTitle: () => reportTitleResult
129+
}
130+
});
131+
132+
await webpackCompile(config);
133+
134+
const generatedReportTitle = await getTitleFromReport();
135+
136+
expect(generatedReportTitle).to.equal(reportTitleResult);
137+
});
138+
it('should propogate an error in a function', async function () {
139+
const reportTitleError = new Error();
140+
const config = makeWebpackConfig({
141+
analyzerOpts: {
142+
reportTitle: () => {throw reportTitleError}
143+
}
144+
});
145+
146+
let error = null;
147+
try {
148+
await webpackCompile(config);
149+
} catch (e) {
150+
error = e;
151+
}
152+
153+
expect(error).to.equal(reportTitleError);
154+
});
155+
});
99156
});
100157

101158
async function expectValidReport(opts) {
@@ -123,6 +180,10 @@ describe('Plugin', function () {
123180
return require(path.resolve(__dirname, `output/${reportFilename}`));
124181
}
125182

183+
async function getTitleFromReport(reportFilename = 'report.html') {
184+
return await nightmare.goto(`file://${__dirname}/output/${reportFilename}`).title();
185+
}
186+
126187
async function getChartDataFromReport(reportFilename = 'report.html') {
127188
return await nightmare.goto(`file://${__dirname}/output/${reportFilename}`).evaluate(() => window.chartData);
128189
}

0 commit comments

Comments
 (0)