-
-
Notifications
You must be signed in to change notification settings - Fork 489
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
Output large json stats objects #129
Conversation
972ee30
to
132868f
Compare
Thanks for taking a stab at this! I haven't used I discovered that If I modify the assertions to run immediately after const fileContent = fs.readFileSync(statsFilename, 'utf8'); When my code hits this line, it reads a partially written file or one that has no contents at all, and I receive a I played around with adding a That screenshot was me running only the new test a few times with the following diff applied: diff --git a/src/fileUtils.js b/src/fileUtils.js
index e9a17cb..94d70fd 100644
--- a/src/fileUtils.js
+++ b/src/fileUtils.js
@@ -7,19 +7,21 @@ module.exports = {
streamObjectToJson
};
-async function streamObjectToJson(filepath, data) {
- await new Promise((resolve, reject) => {
+function streamObjectToJson(filepath, data) {
+ return new Promise((resolve, reject) => {
mkdir.sync(path.dirname(filepath));
const transformStream = JSONStream.stringifyObject();
const writableStream = fs.createWriteStream(filepath);
transformStream.pipe(writableStream);
transformStream.on('error', (err) => {
- reject(err);
+ setTimeout(() => reject(err), 0);
+ // reject(err);
});
transformStream.on('end', () => {
writableStream.end();
- resolve();
+ setTimeout(() => resolve(), 0);
+ // resolve();
});
Object.keys(data).forEach((key) => {
transformStream.write([key, data[key]]);
diff --git a/test/fileUtils.js b/test/fileUtils.js
index c7ed681..cc59446 100644
--- a/test/fileUtils.js
+++ b/test/fileUtils.js
@@ -6,17 +6,18 @@ const { streamObjectToJson } = require('../lib/fileUtils');
const OUTPUT_DIR = `${__dirname}/output`;
-describe('fileUtils', function () {
+describe.only('fileUtils', function () {
before(function () {
del.sync(`${__dirname}/output`);
});
- afterEach(function () {
- del.sync(`${__dirname}/output`);
- });
+ // afterEach(function () {
+ // del.sync(`${__dirname}/output`);
+ // });
it('should print objects to files', async function () {
const statsFilename = path.resolve(OUTPUT_DIR, 'stats.json');
+
await streamObjectToJson(statsFilename, {
string: 'webpack-bundle-analyzer',
list: ['foo', 'bar', 'baz'],
@@ -26,20 +27,18 @@ describe('fileUtils', function () {
baz: null
}
});
+ expect(fs.existsSync(statsFilename)).to.be.true;
- await setImmediate(() => {
- expect(fs.existsSync(statsFilename)).to.be.true;
-
- const fileContent = fs.readFileSync(statsFilename, 'utf8');
- expect(JSON.parse(fileContent)).to.deep.equal({
- string: 'webpack-bundle-analyzer',
- list: ['foo', 'bar', 'baz'],
- obj: {
- name: 'webpack-bundle-analyzer',
- foo: 'bar',
- baz: null
- }
- });
+ const fileContent = fs.readFileSync(statsFilename, 'utf8');
+ console.log('content:', fileContent);
+ expect(JSON.parse(fileContent)).to.deep.equal({
+ string: 'webpack-bundle-analyzer',
+ list: ['foo', 'bar', 'baz'],
+ obj: {
+ name: 'webpack-bundle-analyzer',
+ foo: 'bar',
+ baz: null
+ }
});
});
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment about the new module and tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I'm actually not sure that this PR in it's current implementation will solve "huge-stats-file" serialization error because according to my experience 99% of size in such stats files takes modules
field (and sometimes chunks
) which you stringify in one chunk.
Actually, it was a surprise to me that streamed stringifying of JSON is such a complex task. Why JSONStream
has to be fed with object keys/values manually? Isn't there any other library that can just take an object as input and convert it to writable stream automatically?
@valscion thanks for digging in to much! I was seeing that @th0r I didn't find too many streaming libraries on npm, nothing jumped out at me claiming to work for complex/nested data structures like we have here. My codebase jump came up against the memory limit this week, so (for now) splitting chunks/modules gives us a little headroom. The redux devtools have a button to show/serialize all the store data, I can open that up and see if they've got something extra, otherwise we can make one. |
132868f
to
366bc70
Compare
@valscion & @th0r I kept looking and found a thing! It's called bfj: https://github.com/philbooth/bfj. Looks like it can just take an object and output json. I haven't wired this up to any big projects yet, but it's a new&simpler diff now, so I wanted to get it up early. |
Oh noes! bfj wants |
@ryan953 - For reference, now that Node 8 is officially LTS the next major version of every loader & plugin in The pull request you are referencing is merging into |
fwiw, there's also a node-4 branch that is published to npm if you need it: |
BFJ looks very promising! Great job with the README over there, @philbooth 👏. Let's try the node-4 branch package as specified above -- we can later use node-6 version when we bump major version |
978d0f6
to
846999a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good to me!
3412b11
to
846999a
Compare
@th0r, would you want to check changes as well? If it's OK to you, I'd like to test drive merging this PR, adding a changelog and releasing new patch version with this fix 😊 |
src/BundleAnalyzerPlugin.js
Outdated
.catch((error) => { | ||
this.logger.error( | ||
`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}.`, | ||
JSON.stringify(error, null, "\n") // eslint-disable-line quotes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'\n'
and "\n"
are the same thing in JavaScript, so instead of disabling the ESLint rule on this line, we could use '\n'
and ESLint would be happy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah, and lets use \t
too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to add a test for this.
We can either borrow some huge stats json from guys that faced this problem (e.g. in #119) or just generate something.
src/BundleAnalyzerPlugin.js
Outdated
iterables: 'ignore', | ||
circular: 'ignore' | ||
}; | ||
return bfj.write(statsFilepath, stats, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's better use async/await
here
src/BundleAnalyzerPlugin.js
Outdated
this.logger.info( | ||
`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}` | ||
); | ||
const options = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline this object into bfj.write
call please
src/BundleAnalyzerPlugin.js
Outdated
.catch((error) => { | ||
this.logger.error( | ||
`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}.`, | ||
JSON.stringify(error, null, '\t') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this is the best way to show error. Have you tried that? It prints {}
for me.
if we want to show stack trace, then let's use error.stack
instead.
If we only want to show error message, then we can simply inline error object into the template string like this: ${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}
.
This solved the problem we were having with builds running out of memory. Good Job! |
@th0r I'm not sure how to write a test for this right now :( I've tried to await on both
|
Ok, let's ship it without tests because I just don't know how to emulate "out of memory" error. |
Thank you @ryan953 for your contributions |
This contains the merge of #129
Fixes #119
I've seen this problem appear in the logs as:
The problem is that we're trying to serialize the whole stats object into JSON at once, which causes memory issues with larger projects. The stats object has a shape including
{"assets": [...], "entrypoints": {...}, "chunks": [...], "modules": [...]}
and it's thechunks
andmodules
fields that are really large lists.To fix this I imported JSONStream and paired it with a writable stream to serialize and flush each key on it's own. For larger projects we might need to go further and iterate/write each item within the chunks/modules/children keys individually.