-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
Compile invariant directly to throw expressions #15071
Conversation
React: size: -2.6%, gzip: -2.7% ReactDOM: size: -0.3%, gzip: -0.6% Details of bundled changes.Comparing: df7b87d...8e7cb77 react
react-dom
react-art
react-native-renderer
react-test-renderer
react-reconciler
create-subscription
jest-react
Generated by 🚫 dangerJS |
AFAIK, we still need to compile to |
Not sure, I was hoping one of you had more context on that. |
Looks like Logview will still get the messages but our |
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.
Haven't looked at www side yet, will check tomorrow.
|
||
function ReactError(message) { | ||
const error = new Error(message); | ||
error.name = 'Invariant Violation'; |
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.
While we're at it, can we just say "React Error"? Is that a breaking change? 😄
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'd be comfortable changing in a minor
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 think it's a breaking change. It breaks continuity in logs.
scripts/error-codes/__tests__/__snapshots__/minify-error-messages.js.snap
Outdated
Show resolved
Hide resolved
I looked into it more, and in www, the only things the
I'll look into fbsource, too. |
AFAICT the version in fbsource does nothing special at all. |
49f5282
to
96d30de
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.
looks good
This transforms calls to the invariant module: ```js invariant(condition, 'A %s message that contains %s', adj, noun); ``` Into throw statements: ```js if (!condition) { if (__DEV__) { throw ReactError(`A ${adj} message that contains ${noun}`); } else { throw ReactErrorProd(ERR_CODE, [adj, noun]); } } ``` The only thing ReactError does is return an error whose name is set to "Invariant Violation" to match the existing behavior. ReactErrorProd is a special version used in production that throws a minified error code, with a link to see to expanded form. This replaces the reactProdInvariant module. As a next step, I would like to replace our use of the invariant module for user facing errors by transforming normal Error constructors to ReactError and ReactErrorProd. (We can continue using invariant for internal React errors that are meant to be unreachable, which was the original purpose of invariant.)
I wasn't sure about this part so I asked Sebastian, and his rationale was that using arguments will make ReactErrorProd slightly slower, but using an array will likely make all the functions that throw slightly slower to compile, so it's hard to say which way is better. But since ReactErrorProd is in an error path, and fewer bytes is generally better, no array is good.
This is the first of two PRs to improve our error extraction process.
Ok I went down a bit of a rabbit hole.
It started with this todo:
react/packages/react-reconciler/src/ReactFiberUnwindWork.js
Lines 390 to 398 in d0289c7
For some context, production builds of React do not include error messages. We strip them out at compile time and replace them with error codes and a link to a URL that displays the full message. Our error messages can be as verbose as we want without increasing code size. Here's an example: https://reactjs.org/docs/error-decoder.html?invariant=109&args%5B%5D=Foo.
The error code script works by finding calls to our
invariant
module, looking up the message in a map, and replacing it with the corresponding code. Because we useinvariant
throughout our codebase to assert expected behavior, including for user facing errors, this mostly works really well.But error in the excerpted code above doesn't use
invariant
. It's part of React's error handling path; we can't useinvariant
there because invariant will immediately throw. Instead, we create an error object and pass it along to the next step of the error handling algorithm. That means this error is not being replaced with an error code.While thinking about the best way to solve this, I collected some other flaws in the way we do error extraction:
invariant
module is a Facebook-ism that's not commonly used by other developers. It's a marginal burden for first time contributors to learn how to use it. For example,invariant
throws an error when its first argument evaluates tofalse
, but most people are accustomed to checking for the positive case:if (cond) throw Error(msg)
as opposed toinvariant(!cond, msg)
. It's also not obvious that you're supposed to useinvariant
for errors if you've never contributed to React before.invariant
was originally meant to assert some property of the program that is always supposed to be true. These types of errors should only surface to the user if there's a bug in the program itself. Over time, both Facebook and React have started using it for user facing errors, too.invariant
in our codebase. We have our sizebot that reports increases in bundle size, but if a PR already increases bundle size for other reasons, a strayError
could increase it slightly further without being noticed by the reviewer.invariant
can only be used to immediately throw an error. It's not suited for cases where you need to create an error and pass it along.Proposed solution
Compile
invariant
directly to throw expressions.This PR turns
invariant
into a compile-time only module. It turns code like this:into this:
The only thing
ReactError
does is return an error whose name is set to "Invariant Violation" to match the existing behavior.ReactProdError
is a special version used in production that throws a minified error code, with a link to see to expanded form. This replaces thereactProdInvariant
module.The runtime behavior is the same as it is today.
Compile normal
Error
constructors toReactError
andReactErrorProd
I'll open this part as a separate PR.
Use a lint rule to enforce that error messages are written in the correct format
This will go in the next PR, too.