-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Throw error to warn of mistaken loading of prod + dev React bundles #10446
Throw error to warn of mistaken loading of prod + dev React bundles #10446
Conversation
**what is the change?:** Credit to @gaearon for coming up with a clever way to check for this. :) I mainly just did the manual testing and fixed a couple of typos in his idea. I'd like to do a follow-up PR to add a link and a page explaining this issue more and suggesting how to fix it. **why make this change?:** We want to warn for an unfortunate gotcha for the following situation - 1. Wanting to shrink their JS bundle, an engineer runs it through 'uglifyjs' or some other minifier. They assume this will also do dead-code-elimination, but the 'prod' and 'dev' checks are not envified yet and dev-only code does not get removed. 2. Then they run it through browserify's 'envify' or some other tool to change all calls to 'process.env.NODE_ENV' to "production". This makes their code ready to ship, except the 'dev' only dead code is still in the bundle. Their bundle is twice as large as it needs to be, due to the dead code. This was a problem with the old build system before, but with our new build system output it's possible to detect and throw an informative error in this case. **test plan:** 1. run the build in react as usual; `yarn build` 2. manually run 'uglifyjs -mt' on 'build/packages/react/index.js' to simulate mistakenly minifying React before env variables are resolved, which skips dead code elimination. 3. run the fixtures build - `cd fixtures/packaging && node ./build-all.js && serve ../..` 4. Visit just the production browserify fixture - http://localhost:5000/fixtures/packaging/browserify/prod/ 5. You should see the error thrown indicating this problem has occurred. (Flarnie will insert a screenshot) 6. Do the above steps with NO uglifyjs step, and verify that no error is thrown. When there is no minification applied, we don't assume that this mix-up has occurred. (Flarnie will insert a screenshot) **issue:** fixes facebook#9589
Should we create a specific fixture for this case, where |
I like that, and we could even point to that if we add docs about this issue in the future. Would rather merge this and add that as a follow-up, so that it unblocks the RC. But I'll work on adding a fixture over the weekend if I can. |
packages/react/index.js
Outdated
// because it is impossible to reach in production. | ||
setTimeout(function() { | ||
// Ensure it gets reported to production logging | ||
throw new Error('React is running in production mode, but dead code elimination has not been applied.'); |
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 should probably include "Read how to correctly configure React for production: " and an fame link to "Use the Production Build" in "Optimizing Performance" doc.
packages/react/index.js
Outdated
// This might be a Node environment where DCE is not expected anyway. | ||
return; | ||
} | ||
if (process.env.NODE_ENV === 'production') { |
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 outer wrapper is probably unnecessary since we are already calling it from the same condition.
**what is the change?:** Based on helpful feedback from @gaearon - removed outer check that `process.env.NODE_ENV` is "production" since we are only calling the `testMinification` method inside of another check for "production" environment. - Added an fburl that points to [our current docs on using the production version of React](https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build) **why make this change?:** To save an extra layer of conditionals and to make the error message more clear. **test plan:** Same test plan as earlier - 1. run the build in react as usual; yarn build 2. manually run 'uglifyjs -mt' on 'build/packages/react/index.js' to simulate mistakenly minifying React before env variables are resolved, which skips dead code elimination. 3. run the fixtures build - cd fixtures/packaging && node ./build-all.js && serve ../.. 4. Visit just the production browserify fixture - http://localhost:5000/fixtures/packaging/browserify/prod/ You should see the error thrown indicating this problem has occurred. (Flarnie will insert a screenshot in comments on the PR) 6. Do the above steps with NO uglifyjs step, and verify that no error is thrown. When there is no minification applied, we don't assume that this mix-up has occurred. (Flarnie will insert a screenshot in the comments on the PR.) **issue:** facebook#9589
packages/react/index.js
Outdated
// Therefore the browser gave us invalid source. | ||
return; | ||
} | ||
if (source.indexOf('unreachable') !== -1) { |
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 always true because it will find its own search string, i.e.
(function foo() { return foo.toString().indexOf('any arbitrary string') !== -1; })();
so this error will be thrown in correctly minified code.
Extracting it to a variable might work:
var searchString = 'any arbitrary string';
(function foo() { return foo.toString().indexOf(searchString) !== -1; })();
but Babili will inline it, recreating the original problem.
'UNREACHABLE'.toLowerCase()
appears to work for now, but probably not in the future, as Babili currently evaluates some other String.prototype
methods.
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.
! Good catch! I'll update it. Maybe we can have it look for two occurrences of the search string.
packages/react/index.js
Outdated
} | ||
try { | ||
const source = testMinification.toString(); | ||
if (source.indexOf('toString') === -1) { |
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.
Similar thing here, this works, but is kinda misleading.
const source = testMinification.toString();
could be replaced with const source = String(testMinification);
and this check would still work.
I'm thinking of pulling That seems like it would add a bit of complexity to the build process - right now we don't expect this file to have access to anything except the two react bundles. I don't know if we want to maintain another file alongside the 'index.js' that gets copied over in the same way. I think it would be worthwhile to have a unit test for this logic though. For now I'm just writing/running the unit test locally to verify this change, and if this makes sense I can figure out how to make this change to the build process. |
+1 to making this a unit test |
But I actually don't mind if it runs with Jest. If it's a separate command, that's fine, as long as it's automated and is part of CI. |
**what is the change?:** - Instead of looking for one match when having the method inspect it's own source, we look for two matches, because the search itself will be a match. - WIP moving this method into another file and testing it. Next steps: - Figure out why the babel.transform call is not actually minifying the code - Add tests for uglifyjs - Update build process so that this file can be accessed from `packages/react/index.js` **why make this change?:** - We had a bug in the 'testMinification' method, and I thought the name could be more clear. I fixed the code and also changed the name. - In order to avoid other bugs and keep this code working in the future we'd like to add a unit test for this method. Using the npm modules for 'uglifyjs' and 'babel'/'babili' we should be able to actually test how this method will work when minified with different configurations. **test plan:** `yarn test` **issue:** facebook#9589
Pushed the WIP test - I'm not sure what config I'm missing to get |
**what is the change?:** See commit title **why make this change?:** In order to test that we are correctly detecting different minification situations, we are using these modules in a test. **test plan:** NA **issue:** facebook#9589
**what is the change?:** There was a mix-up in the conditional in 'testMinificationUsedDCE' and this fixes it. The great new tests we added caught the typo. :) Next steps: - get the tests for 'babili' working - update build scripts so that this the 'testMinificationUsedDCE' module is available from 'packages/react/index.js' **why make this change?:** We want to modularize 'testMinificationUsedDCE' and test it. This verifies that the method warns when `uglifyjs` is used to minify but dead code elimination is not done, in a production environment. Generally this is a 'gotcha' which we want to warn folks aboug. **test plan:** `yarn test src/shared/utils/__tests__/testMinificationUsedDCE-test.js` **issue:** facebook#9589
I could use some context on the build process and where to start when making a change to it in order to add a new file to the |
**what is the change?:** Removed the '^' from the npm package requirement **why make this change?:** I am seeing failures in CI that show `uglify-js` is returning different output there from my local environment. To further debug this I'd like to run CI with the exact same version of `uglify-js` that I am using locally. **test plan:** push and see what CI does **issue:** facebook#9589
…act/cj **what is the change?:** This is a first step - we still need (I think) to process this file to get it's contents wrapped in an 'iffe'. Added a step to the build script which copies the source file for the 'testMinificationUsedDCE' module into the 'cjs' directory of our react package build. **why make this change?:** We want this module to be available to the 'index.js' module in this build. **test plan:** Will do manual testing once the build stuff is fully set up. **issue:**
The last commit doesn't feel right to me. IMO we don't want to add more special cases like this that make our bundling more complicated. Especially since this particular file is not part of bundling process. Wouldn't it be enough to add this file to Although at that point it seems like a lot of complexity just to test something. IMO this is one of those cases where we need to get the semantics right, document it, and we'll likely not need to touch it again. I don't think it's worth complicating the build process or test infra to verify this function works as expected. |
Yea, that was what I was thinking when I commented earlier asking whether this was a good direction to go in - "That seems like it would add a bit of complexity to the build process - right now we don't expect this file to have access to anything except the two react bundles." Writing the test in the first place helped catch another bug and validate that the current version works, so I think was worthwhile to write it, whether we decide to keep it or not.
Let me see if that works, and if not then let's consider landing this without the unit test and revisiting whether it's worth adding extra complexity. |
Either of two approaches would be easier to understand to me:
|
Totally agree! I just don't see us changing this code often once we've ironed out the first mistakes. We have similar (well, not quite as sophisticated) code for detecting some other minification mistake in Stack, and we also don't have unit tests for it. So far we've never changed it since it was written. |
Nearly have an update ready but I'm switching to getting #10385 landed and running a sync. Will come right back to this. |
What: - Inlines the 'testMinificationUsedDCE' method into 'packages/react/index.js' - Removes unit test for 'testMinififcationUsedDCE' - Puts dependencies back the way that they were; should remove extra dependencies that were added for the unit test. Why: - It would add complexity to the build process to add another file to the 'build/packages/react/cjs' folder, and that is the only way to pull this out and test it. So instead we are inlining this.
yarn.lock
Outdated
@@ -783,6 +813,14 @@ babel-preset-jest@20.1.0-delta.1: | |||
dependencies: | |||
babel-plugin-jest-hoist "20.1.0-delta.1" | |||
|
|||
babel-preset-minify@0.0.0: | |||
version "0.0.0" |
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.
Should we revert changes to Yarn lock? Seems like Uglify update could also go into a separate PR with just necessary lockfile changes.
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.
Ack - yes, both of those should be removed from this diff. Will fix that.
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 would still be nice to update it separately though :-) maybe it has improved in the meantime.
packages/react/index.js
Outdated
function testMinificationUsedDCE() { | ||
// use scoped variable for our initial test, in case | ||
// 'top-level' mangling is not enabled. | ||
const source = testMinificationUsedDCE.toString(); |
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.
I wanted this line to be inside try catch in case toString throws. I think this can happen in restrictive environments although not sure where I saw that.
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.
Sure thing - I forget why I moved it out, will put it back.
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.
Looks good, thanks for fixing bugs!
Accepting with changes:
- Reverting unintentional changes
- Moving toString back into try catch
Thanks!
**what is the change?:** - We had updated and added some dependencies, but ended up reverting this in a previous commit. The `yarn.lock` and `package.json` needed updated still though. - There is a call to `Function.toString` which we wanted to wrap in a `try/catch` block. **why make this change?:** To remove unrelated dependency changes and increase the safety of the `Function.toString` call. **test plan:** Manual testing again **issue:** facebook#9589
what is the change?:
Adds an error indicating a gotcha where users end up loading both the 'prod' and 'dev' bundles in production.
Credit to @gaearon for coming up with a clever way to check for this. :)
I mainly just did the manual testing and fixed a couple of typos in his
idea.
I'd like to do a follow-up PR to add a link and a page explaining this
issue more and suggesting how to fix it. Would folks support that?
why make this change?:
We want to warn for an unfortunate gotcha for
the following situation -
'uglifyjs' or some other minifier. They assume this will also do
dead-code-elimination, but the 'prod' and 'dev' checks are not
envified yet and dev-only code does not get removed.
change all calls to 'process.env.NODE_ENV' to "production". This
makes their code ready to ship, except the 'dev' only dead code is
still in the bundle. Their bundle is twice as large as it needs to
be, due to the dead code.
This was a problem with the old build system before, but with our new
build system output it's possible to detect and throw an informative
error in this case.
test plan:
yarn build
simulate mistakenly minifying React before env variables are
resolved, which skips dead code elimination.
cd fixtures/packaging && node ./build-all.js && serve ../..
http://localhost:5000/fixtures/packaging/browserify/prod/
6. Do the above steps with NO uglifyjs step, and verify that no error is thrown. When there is no minification applied, we don't assume that this mix-up has occurred.
issue:
fixes #9589