Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added JSON output to the cli and webpack plugin #341

Merged
merged 18 commits into from Apr 10, 2020
Merged
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -56,7 +56,7 @@ new BundleAnalyzerPlugin(options?: object)

|Name|Type|Description|
|:--:|:--:|:----------|
|**`analyzerMode`**|One of: `server`, `static`, `disabled`|Default: `server`. In `server` mode analyzer will start HTTP server to show bundle report. In `static` mode single HTML file with bundle report will be generated. In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`. |
|**`analyzerMode`**|One of: `server`, `static`, `json`, `disabled`|Default: `server`. In `server` mode analyzer will start HTTP server to show bundle report. In `static` mode single HTML file with bundle report will be generated. In `json` mode single JSON file with bundle report will be generated. In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`. |
|**`analyzerHost`**|`{String}`|Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.|
|**`analyzerPort`**|`{Number}` or `auto`|Default: `8888`. Port that will be used in `server` mode to start HTTP server.|
|**`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).|
Expand Down Expand Up @@ -111,9 +111,10 @@ Directory containing all generated bundles.

```
-V, --version output the version number
-m, --mode <mode> Analyzer mode. Should be `server` or `static`.
-m, --mode <mode> Analyzer mode. Should be `server`, `static` or `json`.
In `server` mode analyzer will start HTTP server to show bundle report.
In `static` mode single HTML file with bundle report will be generated. (default: server)
In `static` mode single HTML file with bundle report will be generated.
In `json` mode single JSON file with bundle report will be generated. (default: server)
-h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
-p, --port <n> Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
-r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
Expand Down
16 changes: 13 additions & 3 deletions src/BundleAnalyzerPlugin.js
Expand Up @@ -7,12 +7,11 @@ const Logger = require('./Logger');
const viewer = require('./viewer');

class BundleAnalyzerPlugin {

constructor(opts = {}) {
this.opts = {
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
reportFilename: 'report.html',
reportFilename: null,
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
Expand Down Expand Up @@ -51,6 +50,8 @@ class BundleAnalyzerPlugin {
actions.push(() => this.startAnalyzerServer(stats.toJson()));
} else if (this.opts.analyzerMode === 'static') {
actions.push(() => this.generateStaticReport(stats.toJson()));
} else if (this.opts.analyzerMode === 'json') {
actions.push(() => this.generateJSONReport(stats.toJson()));
}

if (actions.length) {
Expand Down Expand Up @@ -115,10 +116,19 @@ class BundleAnalyzerPlugin {
}
}

async generateJSONReport(stats) {
await viewer.generateJSONReport(stats, {
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
excludeAssets: this.opts.excludeAssets
});
}

async generateStaticReport(stats) {
await viewer.generateReport(stats, {
openBrowser: this.opts.openAnalyzer,
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
defaultSizes: this.opts.defaultSizes,
Expand Down
25 changes: 17 additions & 8 deletions src/bin/analyzer.js
Expand Up @@ -26,9 +26,10 @@ const program = commander
)
.option(
'-m, --mode <mode>',
'Analyzer mode. Should be `server` or `static`.' +
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.'),
'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(
Expand All @@ -45,8 +46,7 @@ const program = commander
)
.option(
'-r, --report <file>',
'Path to bundle report file that will be generated in `static` mode.',
'report.html'
'Path to bundle report file that will be generated in `static` mode.'
)
.option(
'-s, --default-sizes <type>',
Expand Down Expand Up @@ -86,7 +86,9 @@ let {
const logger = new Logger(logLevel);

if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
if (mode !== 'server' && mode !== 'static') showHelp('Invalid mode. Should be either `server` or `static`.');
if (mode !== 'server' && mode !== 'static' && mode !== 'json') {
showHelp('Invalid mode. Should be either `server`, `static` or `json`.');
}
if (mode === 'server') {
if (!host) showHelp('Invalid host name');

Expand Down Expand Up @@ -118,15 +120,22 @@ if (mode === 'server') {
excludeAssets,
logger: new Logger(logLevel)
});
} else {
} else if (mode === 'static') {
viewer.generateReport(bundleStats, {
openBrowser,
reportFilename: resolve(reportFilename),
reportFilename: resolve(reportFilename || 'report.html'),
defaultSizes,
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
} else if (mode === 'json') {
viewer.generateJSONReport(bundleStats, {
reportFilename: resolve(reportFilename || 'report.json'),
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
}

function showHelp(error) {
Expand Down
20 changes: 16 additions & 4 deletions src/viewer.js
Expand Up @@ -20,6 +20,7 @@ const assetsRoot = path.join(projectRoot, 'public');
module.exports = {
startServer,
generateReport,
generateJSONReport,
// deprecated
start: startServer
};
Expand Down Expand Up @@ -121,7 +122,7 @@ async function startServer(bundleStats, opts) {
async function generateReport(bundleStats, opts) {
const {
openBrowser = true,
reportFilename = 'report.html',
reportFilename,
bundleDir = null,
logger = new Logger(),
defaultSizes = 'parsed',
Expand Down Expand Up @@ -158,9 +159,7 @@ async function generateReport(bundleStats, opts) {
mkdir.sync(path.dirname(reportFilepath));
fs.writeFileSync(reportFilepath, reportHtml);

logger.info(
`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`
);
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);

if (openBrowser) {
opener(`file://${reportFilepath}`);
Expand All @@ -174,6 +173,19 @@ async function generateReport(bundleStats, opts) {
});
}

async function generateJSONReport(bundleStats, opts) {
const {reportFilename, bundleDir = null, logger = new Logger(), excludeAssets = null} = opts || {};

const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir);

if (!chartData) return;

mkdir.sync(path.dirname(reportFilename));
fs.writeFileSync(reportFilename, JSON.stringify(chartData));

logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
}

function getAssetContent(filename) {
const assetPath = path.join(assetsRoot, filename);

Expand Down
18 changes: 15 additions & 3 deletions test/analyzer.js
@@ -1,5 +1,6 @@
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const del = require('del');
const childProcess = require('child_process');

Expand Down Expand Up @@ -106,18 +107,29 @@ describe('Analyzer', function () {
generateReportFrom('with-modules-chunk.json');
await expectValidReport({bundleLabel: 'bundle.mjs'});
});

it('should support generating JSON output for the report', async function () {
generateJSONReportFrom('with-modules-in-chunks/stats.json');

const chartData = require(path.resolve(__dirname, 'output/report.json'));
expect(chartData).to.containSubset(require('./stats/with-modules-in-chunks/expected-chart-data'));
});
});

function generateJSONReportFrom(statsFilename) {
childProcess.execSync(`../lib/bin/analyzer.js -m json -r output/report.json stats/${statsFilename}`, {
cwd: __dirname
});
}

function generateReportFrom(statsFilename) {
childProcess.execSync(`../lib/bin/analyzer.js -m static -r output/report.html -O stats/${statsFilename}`, {
cwd: __dirname
});
}

async function getChartData() {
return await nightmare
.goto(`file://${__dirname}/output/report.html`)
.evaluate(() => window.chartData);
return await nightmare.goto(`file://${__dirname}/output/report.html`).evaluate(() => window.chartData);
}

function forEachChartItem(chartData, cb) {
Expand Down
35 changes: 26 additions & 9 deletions test/plugin.js
@@ -1,7 +1,7 @@
const fs = require('fs');
const del = require('del');
const _ = require('lodash');

const path = require('path');
const BundleAnalyzerPlugin = require('../lib/BundleAnalyzerPlugin');

describe('Plugin', function () {
Expand Down Expand Up @@ -47,6 +47,19 @@ describe('Plugin', function () {
});
});

it('should allow to generate json report', async function () {
const config = makeWebpackConfig({
analyzerOpts: {
analyzerMode: 'json'
}
});

await webpackCompile(config);

const chartData = await getChartDataFromJSONReport();
expect(chartData).to.exist;
});

it('should support webpack config with `multi` module', async function () {
const config = makeWebpackConfig();

Expand All @@ -58,11 +71,13 @@ describe('Plugin', function () {
await webpackCompile(config);

const chartData = await getChartDataFromReport();
expect(chartData[0].groups).to.containSubset([{
label: 'multi ./src/a.js ./src/b.js',
path: './multi ./src/a.js ./src/b.js',
groups: undefined
}]);
expect(chartData[0].groups).to.containSubset([
{
label: 'multi ./src/a.js ./src/b.js',
path: './multi ./src/a.js ./src/b.js',
groups: undefined
}
]);
});

describe('options', function () {
Expand Down Expand Up @@ -104,9 +119,11 @@ describe('Plugin', function () {
});
}

function getChartDataFromJSONReport(reportFilename = 'report.json') {
return require(path.resolve(__dirname, `output/${reportFilename}`));
}

async function getChartDataFromReport(reportFilename = 'report.html') {
return await nightmare
.goto(`file://${__dirname}/output/${reportFilename}`)
.evaluate(() => window.chartData);
return await nightmare.goto(`file://${__dirname}/output/${reportFilename}`).evaluate(() => window.chartData);
}
});