Skip to content

Commit 6a67468

Browse files
authored
Merge pull request #390 from webpack-contrib/faster-stats-json-writer
Replace `bfj` module with custom `stats.json` writer
2 parents ee6c7a9 + 06da220 commit 6a67468

6 files changed

Lines changed: 164 additions & 41 deletions

File tree

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+
* **Improvement**
18+
* Significantly speed up generation of `stats.json` file (see `generateStatsFile` option).
19+
1720
## 4.0.0
1821

1922
* **Breaking change**

package-lock.json

Lines changed: 0 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
"dependencies": {
3737
"acorn": "^8.0.4",
3838
"acorn-walk": "^8.0.0",
39-
"bfj": "^7.0.2",
4039
"chalk": "^4.1.0",
4140
"commander": "^6.2.0",
4241
"ejs": "^3.1.5",
@@ -73,6 +72,7 @@
7372
"eslint-config-th0r-react": "2.0.1",
7473
"eslint-plugin-react": "7.21.5",
7574
"exports-loader": "1.1.1",
75+
"globby": "11.0.1",
7676
"gulp": "4.0.2",
7777
"gulp-babel": "8.0.0",
7878
"mobx": "5.15.7",

src/BundleAnalyzerPlugin.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
const bfj = require('bfj');
21
const path = require('path');
32
const mkdir = require('mkdirp');
43
const {bold} = require('chalk');
54

65
const Logger = require('./Logger');
76
const viewer = require('./viewer');
87
const utils = require('./utils');
8+
const {writeStats} = require('./statsUtils');
99

1010
class BundleAnalyzerPlugin {
1111
constructor(opts = {}) {
@@ -83,14 +83,7 @@ class BundleAnalyzerPlugin {
8383
mkdir.sync(path.dirname(statsFilepath));
8484

8585
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);
9487

9588
this.logger.info(
9689
`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`

src/statsUtils.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

test/statsUtils.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const path = require('path');
2+
const {readFileSync} = require('fs');
3+
const globby = require('globby');
4+
5+
const {StatsSerializeStream} = require('../lib/statsUtils');
6+
7+
describe('StatsSerializeStream', () => {
8+
it('should properly stringify primitives', function () {
9+
expectProperJson(0);
10+
expectProperJson(1);
11+
expectProperJson(-1);
12+
expectProperJson(42.42);
13+
expectProperJson(-42.42);
14+
expectProperJson(false);
15+
expectProperJson(true);
16+
expectProperJson(null);
17+
expectProperJson(null);
18+
expectProperJson('');
19+
expectProperJson('"');
20+
expectProperJson('foo bar');
21+
expectProperJson('"foo bar"');
22+
expectProperJson('Вива Лас-Вегас!');
23+
});
24+
25+
it('should properly stringify simple arrays', function () {
26+
expectProperJson([]);
27+
expectProperJson([1, undefined, 2]);
28+
// eslint-disable-next-line
29+
expectProperJson([1, , 2]);
30+
expectProperJson([false, 'f\'o"o', -1, 42.42]);
31+
});
32+
33+
it('should properly stringify objects', function () {
34+
expectProperJson({});
35+
expectProperJson({a: 1, 'foo-bar': null, undef: undefined, '"Гусь!"': true});
36+
});
37+
38+
it('should properly stringify complex structures', function () {
39+
expectProperJson({
40+
foo: [],
41+
bar: {
42+
baz: [
43+
1,
44+
{a: 1, b: ['foo', 'bar'], c: []},
45+
'foo',
46+
{a: 1, b: undefined, c: [{d: true}]},
47+
null,
48+
undefined
49+
]
50+
}
51+
});
52+
});
53+
54+
globby.sync('stats/**/*.json', {cwd: __dirname}).forEach(filepath => {
55+
it(`should properly stringify JSON from "${filepath}"`, function () {
56+
const content = readFileSync(path.resolve(__dirname, filepath), 'utf8');
57+
const json = JSON.parse(content);
58+
expectProperJson(json);
59+
});
60+
});
61+
});
62+
63+
async function expectProperJson(json) {
64+
expect(await stringify(json)).to.equal(JSON.stringify(json, null, 2));
65+
}
66+
67+
async function stringify(json) {
68+
return new Promise((resolve, reject) => {
69+
let result = '';
70+
71+
new StatsSerializeStream(json)
72+
.on('data', chunk => result += chunk)
73+
.on('end', () => resolve(result))
74+
.on('error', reject);
75+
});
76+
}

0 commit comments

Comments
 (0)