-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
|
||
// Do not require this module directly! Use a normal error constructor with | ||
// template literal strings. The messages will be converted to ReactError during | ||
// build, and in production they will be minified. | ||
|
||
function ReactError(message) { | ||
const error = new Error(message); | ||
error.name = 'Invariant Violation'; | ||
return error; | ||
} | ||
|
||
export default ReactError; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
|
||
// Do not require this module directly! Use a normal error constructor with | ||
// template literal strings. The messages will be converted to ReactError during | ||
// build, and in production they will be minified. | ||
|
||
function ReactErrorProd(code) { | ||
let url = 'https://reactjs.org/docs/error-decoder.html?invariant=' + code; | ||
for (let i = 1; i < arguments.length; i++) { | ||
url += '&args[]=' + encodeURIComponent(arguments[i]); | ||
} | ||
return new Error( | ||
`Minified React error #${code}; visit ${url} for the full message or ` + | ||
'use the non-minified dev environment for full errors and additional ' + | ||
'helpful warnings. ', | ||
); | ||
} | ||
|
||
export default ReactErrorProd; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails react-core | ||
*/ | ||
'use strict'; | ||
|
||
let React; | ||
let ReactDOM; | ||
|
||
describe('ReactError', () => { | ||
let globalErrorMock; | ||
|
||
beforeEach(() => { | ||
if (!__DEV__) { | ||
// In production, our Jest environment overrides the global Error | ||
// class in order to decode error messages automatically. However | ||
// this is a single test where we actually *don't* want to decode | ||
// them. So we assert that the OriginalError exists, and temporarily | ||
// set the global Error object back to it. | ||
globalErrorMock = global.Error; | ||
global.Error = globalErrorMock.OriginalError; | ||
expect(typeof global.Error).toBe('function'); | ||
} | ||
jest.resetModules(); | ||
React = require('react'); | ||
ReactDOM = require('react-dom'); | ||
}); | ||
|
||
afterEach(() => { | ||
if (!__DEV__) { | ||
global.Error = globalErrorMock; | ||
} | ||
}); | ||
|
||
if (__DEV__) { | ||
it('should throw errors whose name is "Invariant Violation"', () => { | ||
let error; | ||
try { | ||
React.useState(); | ||
} catch (e) { | ||
error = e; | ||
} | ||
expect(error.name).toEqual('Invariant Violation'); | ||
}); | ||
} else { | ||
it('should error with minified error code', () => { | ||
expect(() => ReactDOM.render('Hi', null)).toThrowError( | ||
'Minified React error #200; visit ' + | ||
'https://reactjs.org/docs/error-decoder.html?invariant=200' + | ||
' for the full message or use the non-minified dev environment' + | ||
' for full errors and additional helpful warnings.', | ||
); | ||
}); | ||
it('should serialize arguments', () => { | ||
function Oops() { | ||
return; | ||
} | ||
Oops.displayName = '#wtf'; | ||
const container = document.createElement('div'); | ||
expect(() => ReactDOM.render(<Oops />, container)).toThrowError( | ||
'Minified React error #152; visit ' + | ||
'https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=%23wtf' + | ||
' for the full message or use the non-minified dev environment' + | ||
' for full errors and additional helpful warnings.', | ||
); | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
The error code system substitutes React's invariant error messages with error IDs to provide a better debugging support in production. Check out the blog post [here](https://reactjs.org/blog/2016/07/11/introducing-reacts-error-code-system.html). | ||
The error code system substitutes React's error messages with error IDs to | ||
provide a better debugging support in production. Check out the blog post | ||
[here](https://reactjs.org/blog/2016/07/11/introducing-reacts-error-code-system.html). | ||
|
||
## Note for cutting a new React release | ||
1. For each release, we run `yarn build -- --extract-errors` to update the error codes before calling `yarn build`. The build step uses `codes.json` for a production (minified) build; there should be no warning like `Error message "foo" cannot be found` for a successful release. | ||
2. The updated `codes.json` file should be synced back to the master branch. The error decoder page in our documentation site uses `codes.json` from master; if the json file has been updated, the docs site should also be rebuilt (`rake copy_error_codes` is included in the default `rake release` task). | ||
3. Be certain to run `yarn build -- --extract-errors` directly in the release branch (if not master) to ensure the correct error codes are generated. These error messages might be changed/removed before cutting a new release, and we don't want to add intermediate/temporary error messages to `codes.json`. However, if a PR changes an existing error message and there's a specific production test (which is rare), it's ok to update `codes.json` for that. Please use `yarn build -- --extract-errors` and don't edit the file manually. | ||
|
||
## Structure | ||
The error code system consists of 5 parts: | ||
- [`codes.json`](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json) contains the mapping from IDs to error messages. This file is generated by the Gulp plugin and is used by both the Babel plugin and the error decoder page in our documentation. This file is append-only, which means an existing code in the file will never be changed/removed. | ||
- [`extract-errors.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/extract-errors.js) is an node script that traverses our codebase and updates `codes.json`. Use it by calling `yarn build -- --extract-errors`. | ||
- [`replace-invariant-error-codes.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/replace-invariant-error-codes.js) is a Babel pass that rewrites error messages to IDs for a production (minified) build. | ||
- [`reactProdInvariant.js`](https://github.com/facebook/react/blob/master/src/shared/utils/reactProdInvariant.js) is the replacement for `invariant` in production. This file gets imported by the Babel plugin and should _not_ be used manually. | ||
- [`ErrorDecoderComponent`](https://github.com/facebook/react/blob/master/docs/_js/ErrorDecoderComponent.js) is a React component that lives at https://reactjs.org/docs/error-decoder.html. This page takes parameters like `?invariant=109&args[]=Foo` and displays a corresponding error message. Our documentation site's [`Rakefile`](https://github.com/facebook/react/blob/master/docs/Rakefile#L64-L69) has a task (`bundle exec rake copy_error_codes`) for adding the latest `codes.json` to the error decoder page. This task is included in the default `bundle exec rake release` task. | ||
- [`codes.json`](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json) | ||
contains the mapping from IDs to error messages. This file is generated by the | ||
Gulp plugin and is used by both the Babel plugin and the error decoder page in | ||
our documentation. This file is append-only, which means an existing code in | ||
the file will never be changed/removed. | ||
- [`extract-errors.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/extract-errors.js) | ||
is an node script that traverses our codebase and updates `codes.json`. You | ||
can test it by running `yarn build -- --extract-errors`, but you should only | ||
commit changes to this file when running a release. (The release tool will | ||
perform this step automatically.) | ||
- [`minify-error-codes`](https://github.com/facebook/react/blob/master/scripts/error-codes/minify-error-codes) | ||
is a Babel pass that rewrites error messages to IDs for a production | ||
(minified) build. |
96 changes: 96 additions & 0 deletions
96
scripts/error-codes/__tests__/__snapshots__/minify-error-messages.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`error transform should correctly transform invariants that are not in the error codes map 1`] = ` | ||
"import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
throw _ReactError(\`This is not a real error message.\`); | ||
} | ||
})();" | ||
`; | ||
|
||
exports[`error transform should handle escaped characters 1`] = ` | ||
"import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
throw _ReactError(\`What's up?\`); | ||
} | ||
})();" | ||
`; | ||
|
||
exports[`error transform should only add \`ReactError\` and \`ReactErrorProd\` once each 1`] = ` | ||
"import _ReactErrorProd from 'shared/ReactErrorProd'; | ||
import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
if (__DEV__) { | ||
throw _ReactError(\`Do not override existing functions.\`); | ||
} else { | ||
throw _ReactErrorProd(16); | ||
} | ||
} | ||
})(); | ||
(function () { | ||
if (!condition) { | ||
if (__DEV__) { | ||
throw _ReactError(\`Do not override existing functions.\`); | ||
} else { | ||
throw _ReactErrorProd(16); | ||
} | ||
} | ||
})();" | ||
`; | ||
|
||
exports[`error transform should replace simple invariant calls 1`] = ` | ||
"import _ReactErrorProd from 'shared/ReactErrorProd'; | ||
import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
if (__DEV__) { | ||
throw _ReactError(\`Do not override existing functions.\`); | ||
} else { | ||
throw _ReactErrorProd(16); | ||
} | ||
} | ||
})();" | ||
`; | ||
|
||
exports[`error transform should support invariant calls with a concatenated template string and args 1`] = ` | ||
"import _ReactErrorProd from 'shared/ReactErrorProd'; | ||
import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
if (__DEV__) { | ||
throw _ReactError(\`Expected a component class, got \${Foo}.\${Bar}\`); | ||
} else { | ||
throw _ReactErrorProd(18, Foo, Bar); | ||
} | ||
} | ||
})();" | ||
`; | ||
|
||
exports[`error transform should support invariant calls with args 1`] = ` | ||
"import _ReactErrorProd from 'shared/ReactErrorProd'; | ||
import _ReactError from 'shared/ReactError'; | ||
|
||
import invariant from 'shared/invariant'; | ||
(function () { | ||
if (!condition) { | ||
if (__DEV__) { | ||
throw _ReactError(\`Expected \${foo} target to be an array; got \${bar}\`); | ||
} else { | ||
throw _ReactErrorProd(7, foo, bar); | ||
} | ||
} | ||
})();" | ||
`; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.