From 9b5d04ecb1804b2fbe88ff6ebb64783110b119f2 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sat, 5 Dec 2020 23:58:09 +0100 Subject: [PATCH] fix: stringify stats using streaming approach Change the approach for stats stringify to use streams instead of JSON.stringify() + sync write. No API or functionality is changed. It makes stats available for huge projects. Changes has the follow effects: Memory Allows to avoid max string length limit of V8 engine (500MB). In other words, stats becomes available for huge projects, whose stats exceed 500MB. Reduces memory consumption on serialization. Adds time penalty. However, this make sense for huge stats only and seems to be an acceptable, given that otherwise stats may be unavailable (due max string limit hit or out of memory). Adds new dependency "json-ext", which is dependency free and most effective solution for a huge JSON parse/stringify at the moment, and other solutions have troubles to handle huge JSON (see https://github.com/discoveryjs/json-ext/blob/master/benchmarks/README.md#stream-stringifying). --- packages/webpack-cli/lib/webpack-cli.js | 29 ++++++++++++++----------- packages/webpack-cli/package.json | 1 + 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 97aecfda24f..9525ad689a9 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -2,8 +2,9 @@ const path = require('path'); const packageExists = require('./utils/package-exists'); const webpack = packageExists('webpack') ? require('webpack') : undefined; const webpackMerge = require('webpack-merge'); -const { writeFileSync, existsSync } = require('fs'); +const { createWriteStream, existsSync } = require('fs'); const { options: coloretteOptions, yellow } = require('colorette'); +const { stringifyStream: createJsonStringifyStream } = require('@discoveryjs/json-ext'); const logger = require('./utils/logger'); const { cli, flags } = require('./utils/cli-flags'); @@ -485,21 +486,23 @@ class WebpackCLI { const foundStats = compiler.compilers ? { children: compiler.compilers.map(getStatsOptionsFromCompiler) } : getStatsOptionsFromCompiler(compiler); + const handleWriteError = (error) => { + logger.error(error); + process.exit(2); + }; if (args.json === true) { - process.stdout.write(JSON.stringify(stats.toJson(foundStats)) + '\n'); + createJsonStringifyStream(stats.toJson(foundStats)) + .on('error', handleWriteError) + .pipe(process.stdout) + .on('error', handleWriteError) + .on('close', () => process.stdout.write('\n')); } else if (typeof args.json === 'string') { - const JSONStats = JSON.stringify(stats.toJson(foundStats)); - - try { - writeFileSync(args.json, JSONStats); - - logger.success(`stats are successfully stored as json to ${args.json}`); - } catch (error) { - logger.error(error); - - process.exit(2); - } + createJsonStringifyStream(stats.toJson(foundStats)) + .on('error', handleWriteError) + .pipe(createWriteStream(args.json)) + .on('error', handleWriteError) + .on('close', () => logger.success(`stats are successfully stored as json to ${args.json}`)); } else { const printedStats = stats.toString(foundStats); diff --git a/packages/webpack-cli/package.json b/packages/webpack-cli/package.json index a9e87239e16..75bafaf5d32 100644 --- a/packages/webpack-cli/package.json +++ b/packages/webpack-cli/package.json @@ -27,6 +27,7 @@ "lib" ], "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/info": "^1.1.0", "@webpack-cli/serve": "^1.1.0", "colorette": "^1.2.1",