Skip to content

Commit 75955bf

Browse files
authored
Pass prod error messages directly to constructor (#17063)
* Remove "Invariant Violation" from dev errors When I made the change to compile `invariant` to throw expressions, I left a small runtime to set the error's `name` property to "Invariant Violation" to maintain the existing behavior. I think we can remove it. The argument for keeping it is to preserve continuity in error logs, but this only affects development errors, anyway: production error messages are replaced with error codes. * Pass prod error messages directly to constructor Updates the `invariant` transform to pass an error message string directly to the Error constructor, instead of mutating the message property. Turns this code: ```js invariant(condition, 'A %s message that contains %s', adj, noun); ``` into this: ```js if (!condition) { throw Error( __DEV__ ? `A ${adj} message that contains ${noun}` : formatProdErrorMessage(ERR_CODE, adj, noun) ); } ```
1 parent 0ac8e56 commit 75955bf

File tree

7 files changed

+81
-131
lines changed

7 files changed

+81
-131
lines changed

packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('ReactNativeError', () => {
4040
throw new Error(e.toString());
4141
}
4242
}).toThrow(
43-
'Invariant Violation: View config getter callback for component `View` must be a function (received `null`)',
43+
'View config getter callback for component `View` must be a function (received `null`)',
4444
);
4545
});
4646

packages/shared/ReactError.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

packages/shared/__tests__/ReactError-test.internal.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ describe('ReactError', () => {
3737
});
3838

3939
if (__DEV__) {
40-
it('should throw errors whose name is "Invariant Violation"', () => {
41-
let error;
42-
try {
43-
React.useState();
44-
} catch (e) {
45-
error = e;
46-
}
47-
expect(error.name).toEqual('Invariant Violation');
48-
});
40+
it("empty test so Jest doesn't complain", () => {});
4941
} else {
5042
it('should error with minified error code', () => {
5143
expect(() => ReactDOM.render('Hi', null)).toThrowError(

packages/shared/__tests__/ReactErrorProd-test.internal.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99
'use strict';
1010

11-
let ReactErrorProd;
11+
let formatProdErrorMessage;
1212

1313
describe('ReactErrorProd', () => {
1414
let globalErrorMock;
@@ -25,7 +25,7 @@ describe('ReactErrorProd', () => {
2525
expect(typeof global.Error).toBe('function');
2626
}
2727
jest.resetModules();
28-
ReactErrorProd = require('shared/ReactErrorProd').default;
28+
formatProdErrorMessage = require('shared/formatProdErrorMessage').default;
2929
});
3030

3131
afterEach(() => {
@@ -35,27 +35,21 @@ describe('ReactErrorProd', () => {
3535
});
3636

3737
it('should throw with the correct number of `%s`s in the URL', () => {
38-
expect(function() {
39-
throw ReactErrorProd(Error(124), 'foo', 'bar');
40-
}).toThrowError(
38+
expect(formatProdErrorMessage(124, 'foo', 'bar')).toEqual(
4139
'Minified React error #124; visit ' +
4240
'https://reactjs.org/docs/error-decoder.html?invariant=124&args[]=foo&args[]=bar' +
4341
' for the full message or use the non-minified dev environment' +
4442
' for full errors and additional helpful warnings.',
4543
);
4644

47-
expect(function() {
48-
throw ReactErrorProd(Error(20));
49-
}).toThrowError(
45+
expect(formatProdErrorMessage(20)).toEqual(
5046
'Minified React error #20; visit ' +
5147
'https://reactjs.org/docs/error-decoder.html?invariant=20' +
5248
' for the full message or use the non-minified dev environment' +
5349
' for full errors and additional helpful warnings.',
5450
);
5551

56-
expect(function() {
57-
throw ReactErrorProd(Error(77), '<div>', '&?bar');
58-
}).toThrowError(
52+
expect(formatProdErrorMessage(77, '<div>', '&?bar')).toEqual(
5953
'Minified React error #77; visit ' +
6054
'https://reactjs.org/docs/error-decoder.html?invariant=77&args[]=%3Cdiv%3E&args[]=%26%3Fbar' +
6155
' for the full message or use the non-minified dev environment' +

packages/shared/ReactErrorProd.js renamed to packages/shared/formatProdErrorMessage.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,19 @@
77
*/
88

99
// Do not require this module directly! Use normal `invariant` calls with
10-
// template literal strings. The messages will be converted to ReactError during
11-
// build, and in production they will be minified.
10+
// template literal strings. The messages will be replaced with error codes
11+
// during build.
1212

13-
function ReactErrorProd(error) {
14-
const code = error.message;
13+
function formatProdErrorMessage(code) {
1514
let url = 'https://reactjs.org/docs/error-decoder.html?invariant=' + code;
1615
for (let i = 1; i < arguments.length; i++) {
1716
url += '&args[]=' + encodeURIComponent(arguments[i]);
1817
}
19-
error.message =
18+
return (
2019
`Minified React error #${code}; visit ${url} for the full message or ` +
2120
'use the non-minified dev environment for full errors and additional ' +
22-
'helpful warnings. ';
23-
return error;
21+
'helpful warnings.'
22+
);
2423
}
2524

26-
export default ReactErrorProd;
25+
export default formatProdErrorMessage;
Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,60 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`error transform should correctly transform invariants that are not in the error codes map 1`] = `
4-
"import _ReactError from \\"shared/ReactError\\";
5-
import invariant from 'shared/invariant';
4+
"import invariant from 'shared/invariant';
65
76
/*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
8-
(function () {
9-
if (!condition) {
10-
throw _ReactError(Error(\\"This is not a real error message.\\"));
11-
}
12-
})();"
7+
if (!condition) {
8+
throw Error(\\"This is not a real error message.\\");
9+
}"
1310
`;
1411

1512
exports[`error transform should handle escaped characters 1`] = `
16-
"import _ReactError from \\"shared/ReactError\\";
17-
import invariant from 'shared/invariant';
13+
"import invariant from 'shared/invariant';
1814
1915
/*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
20-
(function () {
21-
if (!condition) {
22-
throw _ReactError(Error(\\"What's up?\\"));
23-
}
24-
})();"
16+
if (!condition) {
17+
throw Error(\\"What's up?\\");
18+
}"
2519
`;
2620

2721
exports[`error transform should replace simple invariant calls 1`] = `
28-
"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
29-
import _ReactError from \\"shared/ReactError\\";
22+
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
3023
import invariant from 'shared/invariant';
3124
3225
if (!condition) {
33-
if (__DEV__) {
34-
throw _ReactError(Error(\\"Do not override existing functions.\\"));
35-
} else {
36-
throw _ReactErrorProd(Error(16));
26+
{
27+
throw Error(__DEV__ ? \\"Do not override existing functions.\\" : _formatProdErrorMessage(16));
3728
}
3829
}"
3930
`;
4031

4132
exports[`error transform should support invariant calls with a concatenated template string and args 1`] = `
42-
"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
43-
import _ReactError from \\"shared/ReactError\\";
33+
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
4434
import invariant from 'shared/invariant';
4535
4636
if (!condition) {
47-
if (__DEV__) {
48-
throw _ReactError(Error(\\"Expected a component class, got \\" + Foo + \\".\\" + Bar));
49-
} else {
50-
throw _ReactErrorProd(Error(18), Foo, Bar);
37+
{
38+
throw Error(__DEV__ ? \\"Expected a component class, got \\" + Foo + \\".\\" + Bar : _formatProdErrorMessage(18, Foo, Bar));
5139
}
5240
}"
5341
`;
5442

5543
exports[`error transform should support invariant calls with args 1`] = `
56-
"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
57-
import _ReactError from \\"shared/ReactError\\";
44+
"import _formatProdErrorMessage from \\"shared/formatProdErrorMessage\\";
5845
import invariant from 'shared/invariant';
5946
6047
if (!condition) {
61-
if (__DEV__) {
62-
throw _ReactError(Error(\\"Expected \\" + foo + \\" target to be an array; got \\" + bar));
63-
} else {
64-
throw _ReactErrorProd(Error(7), foo, bar);
48+
{
49+
throw Error(__DEV__ ? \\"Expected \\" + foo + \\" target to be an array; got \\" + bar : _formatProdErrorMessage(7, foo, bar));
6550
}
6651
}"
6752
`;
6853

6954
exports[`error transform should support noMinify option 1`] = `
70-
"import _ReactError from \\"shared/ReactError\\";
71-
import invariant from 'shared/invariant';
55+
"import invariant from 'shared/invariant';
7256
7357
if (!condition) {
74-
throw _ReactError(Error(\\"Do not override existing functions.\\"));
58+
throw Error(\\"Do not override existing functions.\\");
7559
}"
7660
`;

scripts/error-codes/transform-error-messages.js

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ module.exports = function(babel) {
2929
// into this:
3030
//
3131
// if (!condition) {
32-
// if (__DEV__) {
33-
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
34-
// } else {
35-
// throw ReactErrorProd(Error(ERR_CODE), adj, noun);
36-
// }
32+
// throw Error(
33+
// __DEV__
34+
// ? `A ${adj} message that contains ${noun}`
35+
// : formatProdErrorMessage(ERR_CODE, adj, noun)
36+
// );
3737
// }
3838
//
3939
// where ERR_CODE is an error code: a unique identifier (a number
@@ -46,22 +46,11 @@ module.exports = function(babel) {
4646
.split('%s')
4747
.map(raw => t.templateElement({raw, cooked: String.raw({raw})}));
4848

49-
const reactErrorIdentfier = helperModuleImports.addDefault(
50-
path,
51-
'shared/ReactError',
52-
{
53-
nameHint: 'ReactError',
54-
}
55-
);
56-
5749
// Outputs:
58-
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
59-
const devThrow = t.throwStatement(
60-
t.callExpression(reactErrorIdentfier, [
61-
t.callExpression(t.identifier('Error'), [
62-
t.templateLiteral(errorMsgQuasis, errorMsgExpressions),
63-
]),
64-
])
50+
// `A ${adj} message that contains ${noun}`;
51+
const devMessage = t.templateLiteral(
52+
errorMsgQuasis,
53+
errorMsgExpressions
6554
);
6655

6756
const parentStatementPath = path.parentPath;
@@ -77,12 +66,16 @@ module.exports = function(babel) {
7766
//
7867
// Outputs:
7968
// if (!condition) {
80-
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
69+
// throw Error(`A ${adj} message that contains ${noun}`);
8170
// }
8271
parentStatementPath.replaceWith(
8372
t.ifStatement(
8473
t.unaryExpression('!', condition),
85-
t.blockStatement([devThrow])
74+
t.blockStatement([
75+
t.throwStatement(
76+
t.callExpression(t.identifier('Error'), [devMessage])
77+
),
78+
])
8679
)
8780
);
8881
return;
@@ -104,15 +97,19 @@ module.exports = function(babel) {
10497
// Outputs:
10598
// /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
10699
// if (!condition) {
107-
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
100+
// throw Error(`A ${adj} message that contains ${noun}`);
108101
// }
109-
path.replaceWith(
102+
parentStatementPath.replaceWith(
110103
t.ifStatement(
111104
t.unaryExpression('!', condition),
112-
t.blockStatement([devThrow])
105+
t.blockStatement([
106+
t.throwStatement(
107+
t.callExpression(t.identifier('Error'), [devMessage])
108+
),
109+
])
113110
)
114111
);
115-
path.addComment(
112+
parentStatementPath.addComment(
116113
'leading',
117114
'FIXME (minify-errors-in-prod): Unminified error message in production build!'
118115
);
@@ -121,40 +118,42 @@ module.exports = function(babel) {
121118
prodErrorId = parseInt(prodErrorId, 10);
122119

123120
// Import ReactErrorProd
124-
const reactErrorProdIdentfier = helperModuleImports.addDefault(
121+
const formatProdErrorMessageIdentifier = helperModuleImports.addDefault(
125122
path,
126-
'shared/ReactErrorProd',
127-
{nameHint: 'ReactErrorProd'}
123+
'shared/formatProdErrorMessage',
124+
{nameHint: 'formatProdErrorMessage'}
128125
);
129126

130127
// Outputs:
131-
// throw ReactErrorProd(Error(ERR_CODE), adj, noun);
132-
const prodThrow = t.throwStatement(
133-
t.callExpression(reactErrorProdIdentfier, [
134-
t.callExpression(t.identifier('Error'), [
135-
t.numericLiteral(prodErrorId),
136-
]),
137-
...errorMsgExpressions,
138-
])
128+
// formatProdErrorMessage(ERR_CODE, adj, noun);
129+
const prodMessage = t.callExpression(
130+
formatProdErrorMessageIdentifier,
131+
[t.numericLiteral(prodErrorId), ...errorMsgExpressions]
139132
);
140133

141134
// Outputs:
142-
// if (!condition) {
143-
// if (__DEV__) {
144-
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
145-
// } else {
146-
// throw ReactErrorProd(Error(ERR_CODE), adj, noun);
147-
// }
148-
// }
135+
// if (!condition) {
136+
// throw Error(
137+
// __DEV__
138+
// ? `A ${adj} message that contains ${noun}`
139+
// : formatProdErrorMessage(ERR_CODE, adj, noun)
140+
// );
141+
// }
149142
parentStatementPath.replaceWith(
150143
t.ifStatement(
151144
t.unaryExpression('!', condition),
152145
t.blockStatement([
153-
t.ifStatement(
154-
DEV_EXPRESSION,
155-
t.blockStatement([devThrow]),
156-
t.blockStatement([prodThrow])
157-
),
146+
t.blockStatement([
147+
t.throwStatement(
148+
t.callExpression(t.identifier('Error'), [
149+
t.conditionalExpression(
150+
DEV_EXPRESSION,
151+
devMessage,
152+
prodMessage
153+
),
154+
])
155+
),
156+
]),
158157
])
159158
)
160159
);

0 commit comments

Comments
 (0)