const {createWriteStream} = require('fs'); const {Readable} = require('stream'); class StatsSerializeStream extends Readable { constructor(stats) { super(); this._indentLevel = 0; this._stringifier = this._stringify(stats); } get _indent() { return ' '.repeat(this._indentLevel); } _read() { let readMore = true; while (readMore) { const {value, done} = this._stringifier.next(); if (done) { this.push(null); readMore = false; } else { readMore = this.push(value); } } } * _stringify(obj) { if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null) { yield JSON.stringify(obj); } else if (Array.isArray(obj)) { yield '['; this._indentLevel++; let isFirst = true; for (let item of obj) { if (item === undefined) { item = null; } yield `${isFirst ? '' : ','}\n${this._indent}`; yield* this._stringify(item); isFirst = false; } this._indentLevel--; yield obj.length ? `\n${this._indent}]` : ']'; } else { yield '{'; this._indentLevel++; let isFirst = true; const entries = Object.entries(obj); for (const [itemKey, itemValue] of entries) { if (itemValue === undefined) { continue; } yield `${isFirst ? '' : ','}\n${this._indent}${JSON.stringify(itemKey)}: `; yield* this._stringify(itemValue); isFirst = false; } this._indentLevel--; yield entries.length ? `\n${this._indent}}` : '}'; } } } exports.StatsSerializeStream = StatsSerializeStream; exports.writeStats = writeStats; async function writeStats(stats, filepath) { return new Promise((resolve, reject) => { new StatsSerializeStream(stats) .on('end', resolve) .on('error', reject) .pipe(createWriteStream(filepath)); }); }