2019-10-30 05:45:47 +08:00
/ * *
* 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 .
*
* @ flow
* /
2020-03-19 03:18:34 +08:00
import type {
Destination ,
Chunk ,
BundlerConfig ,
2020-03-24 08:53:45 +08:00
ModuleMetaData ,
ModuleReference ,
2020-11-11 11:58:58 +08:00
ModuleKey ,
2020-03-19 03:18:34 +08:00
} from './ReactFlightServerConfig' ;
2022-03-08 20:55:32 +08:00
import type { ContextSnapshot } from './ReactFlightNewContext' ;
2022-09-08 10:27:04 +08:00
import type { ThenableState } from './ReactFlightWakeable' ;
2022-03-08 20:55:32 +08:00
import type {
ReactProviderType ,
ServerContextJSONValue ,
2022-09-08 10:27:04 +08:00
Wakeable ,
2022-03-08 20:55:32 +08:00
} from 'shared/ReactTypes' ;
2019-10-30 05:45:47 +08:00
import {
scheduleWork ,
beginWriting ,
2022-02-24 00:35:21 +08:00
writeChunkAndReturn ,
2019-10-30 05:45:47 +08:00
completeWriting ,
flushBuffered ,
close ,
2021-03-30 10:36:16 +08:00
closeWithError ,
2020-03-11 05:55:04 +08:00
processModelChunk ,
2020-10-30 08:57:31 +08:00
processModuleChunk ,
2022-03-08 20:55:32 +08:00
processProviderChunk ,
2020-11-11 11:56:50 +08:00
processSymbolChunk ,
2022-09-24 04:19:29 +08:00
processErrorChunkProd ,
processErrorChunkDev ,
2022-06-19 23:05:41 +08:00
processReferenceChunk ,
2020-03-24 08:53:45 +08:00
resolveModuleMetaData ,
2020-11-11 11:58:58 +08:00
getModuleKey ,
2020-10-30 08:57:31 +08:00
isModuleReference ,
2020-03-11 05:55:04 +08:00
} from './ReactFlightServerConfig' ;
2020-03-12 00:48:02 +08:00
2022-06-01 05:53:32 +08:00
import {
2022-10-13 11:13:39 +08:00
HooksDispatcher ,
2022-06-01 05:53:32 +08:00
prepareToUseHooksForRequest ,
2022-09-08 10:27:04 +08:00
prepareToUseHooksForComponent ,
getThenableStateAfterSuspending ,
2022-06-01 05:53:32 +08:00
resetHooksForRequest ,
} from './ReactFlightHooks' ;
2022-10-13 11:13:39 +08:00
import {
DefaultCacheDispatcher ,
getCurrentCache ,
setCurrentCache ,
} from './ReactFlightCache' ;
2022-03-08 20:55:32 +08:00
import {
pushProvider ,
popProvider ,
switchContext ,
getActiveContext ,
rootContextSnapshot ,
} from './ReactFlightNewContext' ;
2022-09-08 10:27:04 +08:00
import { trackSuspendedWakeable } from './ReactFlightWakeable' ;
2022-03-08 20:55:32 +08:00
2020-03-24 08:53:45 +08:00
import {
REACT _ELEMENT _TYPE ,
2020-08-28 03:19:13 +08:00
REACT _FORWARD _REF _TYPE ,
2020-03-24 08:53:45 +08:00
REACT _FRAGMENT _TYPE ,
REACT _LAZY _TYPE ,
2020-08-28 03:19:13 +08:00
REACT _MEMO _TYPE ,
2022-03-08 20:55:32 +08:00
REACT _PROVIDER _TYPE ,
2020-03-24 08:53:45 +08:00
} from 'shared/ReactSymbols' ;
2022-03-08 20:55:32 +08:00
import { getOrCreateServerContext } from 'shared/ReactServerContextRegistry' ;
2020-08-28 03:19:13 +08:00
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
2021-04-07 22:57:43 +08:00
import isArray from 'shared/isArray' ;
2020-10-09 02:11:15 +08:00
2020-03-11 05:55:04 +08:00
type ReactJSONValue =
2019-10-30 05:45:47 +08:00
| string
| boolean
| number
| null
2020-03-24 08:53:45 +08:00
| $ReadOnlyArray < ReactJSONValue >
2019-10-30 05:45:47 +08:00
| ReactModelObject ;
2020-03-11 05:55:04 +08:00
export type ReactModel =
| React$Element < any >
2019-10-30 05:45:47 +08:00
| string
| boolean
| number
2022-09-08 23:46:07 +08:00
| symbol
2019-10-30 05:45:47 +08:00
| null
2020-03-11 05:55:04 +08:00
| Iterable < ReactModel >
2019-10-30 05:45:47 +08:00
| ReactModelObject ;
2022-09-10 04:03:48 +08:00
type ReactModelObject = { + [ key : string ] : ReactModel } ;
2019-10-30 05:45:47 +08:00
2022-06-19 23:05:41 +08:00
const PENDING = 0 ;
const COMPLETED = 1 ;
const ABORTED = 3 ;
const ERRORED = 4 ;
2022-09-10 04:03:48 +08:00
type Task = {
2019-11-07 01:48:34 +08:00
id : number ,
2022-06-19 23:05:41 +08:00
status : 0 | 1 | 3 | 4 ,
2021-01-26 05:04:36 +08:00
model : ReactModel ,
2019-11-07 01:48:34 +08:00
ping : ( ) => void ,
2022-03-08 20:55:32 +08:00
context : ContextSnapshot ,
2022-09-08 10:27:04 +08:00
thenableState : ThenableState | null ,
2022-09-10 04:03:48 +08:00
} ;
2019-11-07 01:48:34 +08:00
2022-09-10 04:03:48 +08:00
export type Request = {
2021-09-29 06:32:09 +08:00
status : 0 | 1 | 2 ,
fatalError : mixed ,
destination : null | Destination ,
2020-03-19 03:18:34 +08:00
bundlerConfig : BundlerConfig ,
2020-12-03 11:44:56 +08:00
cache : Map < Function , mixed > ,
2019-11-07 01:48:34 +08:00
nextChunkId : number ,
pendingChunks : number ,
2022-06-19 23:05:41 +08:00
abortableTasks : Set < Task > ,
2022-06-19 03:02:11 +08:00
pingedTasks : Array < Task > ,
2020-10-30 08:57:31 +08:00
completedModuleChunks : Array < Chunk > ,
2020-03-11 05:55:04 +08:00
completedJSONChunks : Array < Chunk > ,
completedErrorChunks : Array < Chunk > ,
2022-09-08 23:46:07 +08:00
writtenSymbols : Map < symbol , number > ,
2020-11-11 11:58:58 +08:00
writtenModules : Map < ModuleKey , number > ,
2022-03-08 20:55:32 +08:00
writtenProviders : Map < string , number > ,
2022-06-01 05:53:32 +08:00
identifierPrefix : string ,
identifierCount : number ,
2022-09-24 04:19:29 +08:00
onError : ( error : mixed ) => ? string ,
2019-11-07 01:48:34 +08:00
toJSON : ( key : string , value : ReactModel ) => ReactJSONValue ,
2022-09-10 04:03:48 +08:00
} ;
2019-10-30 05:45:47 +08:00
2020-08-28 03:19:13 +08:00
const ReactCurrentDispatcher = ReactSharedInternals . ReactCurrentDispatcher ;
2022-10-13 11:13:39 +08:00
const ReactCurrentCache = ReactSharedInternals . ReactCurrentCache ;
2020-08-28 03:19:13 +08:00
2021-03-30 10:39:55 +08:00
function defaultErrorHandler ( error : mixed ) {
2021-09-29 06:32:09 +08:00
console [ 'error' ] ( error ) ;
// Don't transform to our wrapper
2021-03-30 10:39:55 +08:00
}
2021-03-30 10:36:16 +08:00
2021-09-29 06:32:09 +08:00
const OPEN = 0 ;
const CLOSING = 1 ;
const CLOSED = 2 ;
2019-10-30 05:45:47 +08:00
export function createRequest (
model : ReactModel ,
2020-03-19 03:18:34 +08:00
bundlerConfig : BundlerConfig ,
2022-09-24 04:19:29 +08:00
onError : void | ( ( error : mixed ) => ? string ) ,
2022-03-08 20:55:32 +08:00
context ? : Array < [ string , ServerContextJSONValue ] > ,
2022-06-01 05:53:32 +08:00
identifierPrefix ? : string ,
2020-03-11 05:55:04 +08:00
) : Request {
2022-06-19 23:05:41 +08:00
const abortSet : Set < Task > = new Set ( ) ;
2022-06-19 03:02:11 +08:00
const pingedTasks = [ ] ;
2020-04-02 03:35:52 +08:00
const request = {
2021-09-29 06:32:09 +08:00
status : OPEN ,
fatalError : null ,
destination : null ,
2020-03-19 03:18:34 +08:00
bundlerConfig ,
2020-12-03 11:44:56 +08:00
cache : new Map ( ) ,
2019-11-07 01:48:34 +08:00
nextChunkId : 0 ,
pendingChunks : 0 ,
2022-06-19 23:05:41 +08:00
abortableTasks : abortSet ,
2022-06-19 03:02:11 +08:00
pingedTasks : pingedTasks ,
2020-10-30 08:57:31 +08:00
completedModuleChunks : [ ] ,
2019-11-07 01:48:34 +08:00
completedJSONChunks : [ ] ,
completedErrorChunks : [ ] ,
2020-11-11 11:56:50 +08:00
writtenSymbols : new Map ( ) ,
2020-11-11 11:58:58 +08:00
writtenModules : new Map ( ) ,
2022-03-08 20:55:32 +08:00
writtenProviders : new Map ( ) ,
2022-06-01 05:53:32 +08:00
identifierPrefix : identifierPrefix || '' ,
identifierCount : 1 ,
2021-06-15 06:28:20 +08:00
onError : onError === undefined ? defaultErrorHandler : onError ,
2020-03-24 08:53:45 +08:00
toJSON : function ( key : string , value : ReactModel ) : ReactJSONValue {
return resolveModelToJSON ( request , this , key , value ) ;
} ,
2019-11-07 01:48:34 +08:00
} ;
request . pendingChunks ++ ;
2022-03-08 20:55:32 +08:00
const rootContext = createRootContext ( context ) ;
2022-06-19 23:05:41 +08:00
const rootTask = createTask ( request , model , rootContext , abortSet ) ;
2022-06-19 03:02:11 +08:00
pingedTasks . push ( rootTask ) ;
2019-11-07 01:48:34 +08:00
return request ;
2019-10-30 05:45:47 +08:00
}
2022-03-08 20:55:32 +08:00
function createRootContext (
reqContext ? : Array < [ string , ServerContextJSONValue ] > ,
) {
return importServerContexts ( reqContext ) ;
}
const POP = { } ;
2020-11-11 11:56:50 +08:00
function attemptResolveElement (
type : any ,
key : null | React$Key ,
ref : mixed ,
props : any ,
2022-09-08 10:27:04 +08:00
prevThenableState : ThenableState | null ,
2020-11-11 11:56:50 +08:00
) : ReactModel {
if ( ref !== null && ref !== undefined ) {
2020-10-09 08:02:23 +08:00
// When the ref moves to the regular props object this will implicitly
// throw for functions. We could probably relax it to a DEV warning for other
// cases.
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
2020-10-09 08:02:23 +08:00
'Refs cannot be used in server components, nor passed to client components.' ,
) ;
}
2019-11-07 01:48:34 +08:00
if ( typeof type === 'function' ) {
2022-08-25 19:47:38 +08:00
if ( isModuleReference ( type ) ) {
// This is a reference to a client component.
return [ REACT _ELEMENT _TYPE , type , key , props ] ;
}
2020-03-24 08:53:45 +08:00
// This is a server-side component.
2022-09-08 10:27:04 +08:00
prepareToUseHooksForComponent ( prevThenableState ) ;
2019-11-07 01:48:34 +08:00
return type ( props ) ;
} else if ( typeof type === 'string' ) {
// This is a host element. E.g. HTML.
2020-11-11 11:56:50 +08:00
return [ REACT _ELEMENT _TYPE , type , key , props ] ;
} else if ( typeof type === 'symbol' ) {
if ( type === REACT _FRAGMENT _TYPE ) {
// For key-less fragments, we add a small optimization to avoid serializing
// it as a wrapper.
// TODO: If a key is specified, we should propagate its key to any children.
// Same as if a server component has a key.
return props . children ;
}
// This might be a built-in React component. We'll let the client decide.
// Any built-in works as long as its props are serializable.
return [ REACT _ELEMENT _TYPE , type , key , props ] ;
2020-08-28 03:19:13 +08:00
} else if ( type != null && typeof type === 'object' ) {
2020-10-30 08:57:31 +08:00
if ( isModuleReference ( type ) ) {
// This is a reference to a client component.
2020-11-11 11:56:50 +08:00
return [ REACT _ELEMENT _TYPE , type , key , props ] ;
2020-10-30 08:57:31 +08:00
}
2020-08-28 03:19:13 +08:00
switch ( type . $$typeof ) {
2022-03-11 03:18:54 +08:00
case REACT _LAZY _TYPE : {
const payload = type . _payload ;
const init = type . _init ;
const wrappedType = init ( payload ) ;
2022-09-08 10:27:04 +08:00
return attemptResolveElement (
wrappedType ,
key ,
ref ,
props ,
prevThenableState ,
) ;
2022-03-11 03:18:54 +08:00
}
2020-08-28 03:19:13 +08:00
case REACT _FORWARD _REF _TYPE : {
const render = type . render ;
2022-09-08 10:27:04 +08:00
prepareToUseHooksForComponent ( prevThenableState ) ;
2020-08-28 03:19:13 +08:00
return render ( props , undefined ) ;
}
case REACT _MEMO _TYPE : {
2022-09-08 10:27:04 +08:00
return attemptResolveElement (
type . type ,
key ,
ref ,
props ,
prevThenableState ,
) ;
2020-08-28 03:19:13 +08:00
}
2022-03-08 20:55:32 +08:00
case REACT _PROVIDER _TYPE : {
pushProvider ( type . _context , props . value ) ;
if ( _ _DEV _ _ ) {
const extraKeys = Object . keys ( props ) . filter ( value => {
if ( value === 'children' || value === 'value' ) {
return false ;
}
return true ;
} ) ;
if ( extraKeys . length !== 0 ) {
console . error (
'ServerContext can only have a value prop and children. Found: %s' ,
JSON . stringify ( extraKeys ) ,
) ;
}
}
return [
REACT _ELEMENT _TYPE ,
type ,
key ,
// Rely on __popProvider being serialized last to pop the provider.
{ value : props . value , children : props . children , _ _pop : POP } ,
] ;
}
2020-08-28 03:19:13 +08:00
}
2019-11-07 01:48:34 +08:00
}
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
` Unsupported server component type: ${ describeValueForErrorMessage ( type ) } ` ,
2020-10-09 08:02:23 +08:00
) ;
2019-11-07 01:48:34 +08:00
}
2022-06-19 03:02:11 +08:00
function pingTask ( request : Request , task : Task ) : void {
const pingedTasks = request . pingedTasks ;
pingedTasks . push ( task ) ;
if ( pingedTasks . length === 1 ) {
2019-11-07 01:48:34 +08:00
scheduleWork ( ( ) => performWork ( request ) ) ;
}
}
2022-06-19 03:02:11 +08:00
function createTask (
2022-03-08 20:55:32 +08:00
request : Request ,
model : ReactModel ,
context : ContextSnapshot ,
2022-06-19 23:05:41 +08:00
abortSet : Set < Task > ,
2022-06-19 03:02:11 +08:00
) : Task {
2020-04-02 03:35:52 +08:00
const id = request . nextChunkId ++ ;
2022-06-19 03:02:11 +08:00
const task = {
2019-11-07 01:48:34 +08:00
id ,
2022-06-19 23:05:41 +08:00
status : PENDING ,
2021-01-26 05:04:36 +08:00
model ,
2022-03-08 20:55:32 +08:00
context ,
2022-06-19 03:02:11 +08:00
ping : ( ) => pingTask ( request , task ) ,
2022-09-08 10:27:04 +08:00
thenableState : null ,
2019-11-07 01:48:34 +08:00
} ;
2022-06-19 23:05:41 +08:00
abortSet . add ( task ) ;
2022-06-19 03:02:11 +08:00
return task ;
2019-11-07 01:48:34 +08:00
}
2020-10-31 04:02:03 +08:00
function serializeByValueID ( id : number ) : string {
2019-11-07 01:48:34 +08:00
return '$' + id . toString ( 16 ) ;
}
2020-10-31 04:02:03 +08:00
function serializeByRefID ( id : number ) : string {
return '@' + id . toString ( 16 ) ;
}
2022-08-25 19:47:38 +08:00
function serializeModuleReference (
request : Request ,
2022-09-10 04:03:48 +08:00
parent : { + [ key : string | number ] : ReactModel } | $ReadOnlyArray < ReactModel > ,
2022-08-25 19:47:38 +08:00
key : string ,
moduleReference : ModuleReference < any > ,
) : string {
const moduleKey : ModuleKey = getModuleKey ( moduleReference ) ;
const writtenModules = request . writtenModules ;
const existingId = writtenModules . get ( moduleKey ) ;
if ( existingId !== undefined ) {
if ( parent [ 0 ] === REACT _ELEMENT _TYPE && key === '1' ) {
// If we're encoding the "type" of an element, we can refer
// to that by a lazy reference instead of directly since React
// knows how to deal with lazy values. This lets us suspend
// on this component rather than its parent until the code has
// loaded.
return serializeByRefID ( existingId ) ;
}
return serializeByValueID ( existingId ) ;
}
try {
const moduleMetaData : ModuleMetaData = resolveModuleMetaData (
request . bundlerConfig ,
moduleReference ,
) ;
request . pendingChunks ++ ;
const moduleId = request . nextChunkId ++ ;
emitModuleChunk ( request , moduleId , moduleMetaData ) ;
writtenModules . set ( moduleKey , moduleId ) ;
if ( parent [ 0 ] === REACT _ELEMENT _TYPE && key === '1' ) {
// If we're encoding the "type" of an element, we can refer
// to that by a lazy reference instead of directly since React
// knows how to deal with lazy values. This lets us suspend
// on this component rather than its parent until the code has
// loaded.
return serializeByRefID ( moduleId ) ;
}
return serializeByValueID ( moduleId ) ;
} catch ( x ) {
request . pendingChunks ++ ;
const errorId = request . nextChunkId ++ ;
2022-09-24 04:19:29 +08:00
const digest = logRecoverableError ( request , x ) ;
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( x ) ;
emitErrorChunkDev ( request , errorId , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , errorId , digest ) ;
}
2022-08-25 19:47:38 +08:00
return serializeByValueID ( errorId ) ;
}
}
2019-11-07 01:48:34 +08:00
function escapeStringValue ( value : string ) : string {
2020-03-24 08:53:45 +08:00
if ( value [ 0 ] === '$' || value [ 0 ] === '@' ) {
// We need to escape $ or @ prefixed strings since we use those to encode
// references to IDs and as special symbol values.
2019-11-07 01:48:34 +08:00
return '$' + value ;
} else {
return value ;
}
}
2020-10-09 02:11:15 +08:00
function isObjectPrototype ( object ) : boolean {
if ( ! object ) {
return false ;
}
const ObjectPrototype = Object . prototype ;
if ( object === ObjectPrototype ) {
return true ;
}
// It might be an object from a different Realm which is
// still just a plain simple object.
if ( Object . getPrototypeOf ( object ) ) {
return false ;
}
const names = Object . getOwnPropertyNames ( object ) ;
for ( let i = 0 ; i < names . length ; i ++ ) {
if ( ! ( names [ i ] in ObjectPrototype ) ) {
return false ;
}
}
return true ;
}
function isSimpleObject ( object ) : boolean {
if ( ! isObjectPrototype ( Object . getPrototypeOf ( object ) ) ) {
return false ;
}
const names = Object . getOwnPropertyNames ( object ) ;
for ( let i = 0 ; i < names . length ; i ++ ) {
const descriptor = Object . getOwnPropertyDescriptor ( object , names [ i ] ) ;
2020-10-09 08:02:23 +08:00
if ( ! descriptor ) {
return false ;
}
if ( ! descriptor . enumerable ) {
if (
( names [ i ] === 'key' || names [ i ] === 'ref' ) &&
typeof descriptor . get === 'function'
) {
// React adds key and ref getters to props objects to issue warnings.
// Those getters will not be transferred to the client, but that's ok,
// so we'll special case them.
continue ;
}
2020-10-09 02:11:15 +08:00
return false ;
}
}
return true ;
}
function objectName ( object ) : string {
2022-10-04 23:04:47 +08:00
// $FlowFixMe[method-unbinding]
2020-10-09 02:11:15 +08:00
const name = Object . prototype . toString . call ( object ) ;
return name . replace ( /^\[object (.*)\]$/ , function ( m , p0 ) {
return p0 ;
} ) ;
}
function describeKeyForErrorMessage ( key : string ) : string {
const encodedKey = JSON . stringify ( key ) ;
return '"' + key + '"' === encodedKey ? key : encodedKey ;
}
function describeValueForErrorMessage ( value : ReactModel ) : string {
switch ( typeof value ) {
case 'string' : {
return JSON . stringify (
value . length <= 10 ? value : value . substr ( 0 , 10 ) + '...' ,
) ;
}
case 'object' : {
if ( isArray ( value ) ) {
return '[...]' ;
}
const name = objectName ( value ) ;
2020-10-09 08:02:23 +08:00
if ( name === 'Object' ) {
2020-10-09 02:11:15 +08:00
return '{...}' ;
}
return name ;
}
case 'function' :
return 'function' ;
default :
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064)
* Revise ESLint rules for string coercion
Currently, react uses `'' + value` to coerce mixed values to strings.
This code will throw for Temporal objects or symbols.
To make string-coercion safer and to improve user-facing error messages,
This commit adds a new ESLint rule called `safe-string-coercion`.
This rule has two modes: a production mode and a non-production mode.
* If the `isProductionUserAppCode` option is true, then `'' + value`
coercions are allowed (because they're faster, although they may
throw) and `String(value)` coercions are disallowed. Exception:
when building error messages or running DEV-only code in prod
files, `String()` should be used because it won't throw.
* If the `isProductionUserAppCode` option is false, then `'' + value`
coercions are disallowed (because they may throw, and in non-prod
code it's not worth the risk) and `String(value)` are allowed.
Production mode is used for all files which will be bundled with
developers' userland apps. Non-prod mode is used for all other React
code: tests, DEV blocks, devtools extension, etc.
In production mode, in addiiton to flagging `String(value)` calls,
the rule will also flag `'' + value` or `value + ''` coercions that may
throw. The rule is smart enough to silence itself in the following
"will never throw" cases:
* When the coercion is wrapped in a `typeof` test that restricts to safe
(non-symbol, non-object) types. Example:
if (typeof value === 'string' || typeof value === 'number') {
thisWontReport('' + value);
}
* When what's being coerced is a unary function result, because unary
functions never return an object or a symbol.
* When the coerced value is a commonly-used numeric identifier:
`i`, `idx`, or `lineNumber`.
* When the statement immeidately before the coercion is a DEV-only
call to a function from shared/CheckStringCoercion.js. This call is a
no-op in production, but in DEV it will show a console error
explaining the problem, then will throw right after a long explanatory
code comment so that debugger users will have an idea what's going on.
The check function call must be in the following format:
if (__DEV__) {
checkXxxxxStringCoercion(value);
};
Manually disabling the rule is usually not necessary because almost all
prod use of the `'' + value` pattern falls into one of the categories
above. But in the rare cases where the rule isn't smart enough to detect
safe usage (e.g. when a coercion is inside a nested ternary operator),
manually disabling the rule will be needed.
The rule should also be manually disabled in prod error handling code
where `String(value)` should be used for coercions, because it'd be
bad to throw while building an error message or stack trace!
The prod and non-prod modes have differentiated error messages to
explain how to do a proper coercion in that mode.
If a production check call is needed but is missing or incorrect
(e.g. not in a DEV block or not immediately before the coercion), then
a context-sensitive error message will be reported so that developers
can figure out what's wrong and how to fix the problem.
Because string coercions are now handled by the `safe-string-coercion`
rule, the `no-primitive-constructor` rule no longer flags `String()`
usage. It still flags `new String(value)` because that usage is almost
always a bug.
* Add DEV-only string coercion check functions
This commit adds DEV-only functions to check whether coercing
values to strings using the `'' + value` pattern will throw. If it will
throw, these functions will:
1. Display a console error with a friendly error message describing
the problem and the developer can fix it.
2. Perform the coercion, which will throw. Right before the line where
the throwing happens, there's a long code comment that will help
debugger users (or others looking at the exception call stack) figure
out what happened and how to fix the problem.
One of these check functions should be called before all string coercion
of user-provided values, except when the the coercion is guaranteed not
to throw, e.g.
* if inside a typeof check like `if (typeof value === 'string')`
* if coercing the result of a unary function like `+value` or `value++`
* if coercing a variable named in a whitelist of numeric identifiers:
`i`, `idx`, or `lineNumber`.
The new `safe-string-coercion` internal ESLint rule enforces that
these check functions are called when they are required.
Only use these check functions in production code that will be bundled
with user apps. For non-prod code (and for production error-handling
code), use `String(value)` instead which may be a little slower but will
never throw.
* Add failing tests for string coercion
Added failing tests to verify:
* That input, select, and textarea elements with value and defaultValue
set to Temporal-like objects which will throw when coerced to string
using the `'' + value` pattern.
* That text elements will throw for Temporal-like objects
* That dangerouslySetInnerHTML will *not* throw for Temporal-like
objects because this value is not cast to a string before passing to
the DOM.
* That keys that are Temporal-like objects will throw
All tests above validate the friendly error messages thrown.
* Use `String(value)` for coercion in non-prod files
This commit switches non-production code from `'' + value` (which
throws for Temporal objects and symbols) to instead use `String(value)`
which won't throw for these or other future plus-phobic types.
"Non-produciton code" includes anything not bundled into user apps:
* Tests and test utilities. Note that I didn't change legacy React
test fixtures because I assumed it was good for those files to
act just like old React, including coercion behavior.
* Build scripts
* Dev tools package - In addition to switching to `String`, I also
removed special-case code for coercing symbols which is now
unnecessary.
* Add DEV-only string coercion checks to prod files
This commit adds DEV-only function calls to to check if string coercion
using `'' + value` will throw, which it will if the value is a Temporal
object or a symbol because those types can't be added with `+`.
If it will throw, then in DEV these checks will show a console error
to help the user undertsand what went wrong and how to fix the
problem. After emitting the console error, the check functions will
retry the coercion which will throw with a call stack that's easy (or
at least easier!) to troubleshoot because the exception happens right
after a long comment explaining the issue. So whether the user is in
a debugger, looking at the browser console, or viewing the in-browser
DEV call stack, it should be easy to understand and fix the problem.
In most cases, the safe-string-coercion ESLint rule is smart enough to
detect when a coercion is safe. But in rare cases (e.g. when a coercion
is inside a ternary) this rule will have to be manually disabled.
This commit also switches error-handling code to use `String(value)`
for coercion, because it's bad to crash when you're trying to build
an error message or a call stack! Because `String()` is usually
disallowed by the `safe-string-coercion` ESLint rule in production
code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
// eslint-disable-next-line react-internal/safe-string-coercion
2020-10-09 02:11:15 +08:00
return String ( value ) ;
}
}
function describeObjectForErrorMessage (
objectOrArray :
2022-09-09 22:13:58 +08:00
| { + [ key : string | number ] : ReactModel , ... }
2020-10-09 02:11:15 +08:00
| $ReadOnlyArray < ReactModel > ,
2020-10-09 08:02:23 +08:00
expandedName ? : string ,
2020-10-09 02:11:15 +08:00
) : string {
if ( isArray ( objectOrArray ) ) {
let str = '[' ;
const array : $ReadOnlyArray < ReactModel > = objectOrArray ;
for ( let i = 0 ; i < array . length ; i ++ ) {
if ( i > 0 ) {
str += ', ' ;
}
if ( i > 6 ) {
str += '...' ;
break ;
}
2020-10-09 08:02:23 +08:00
const value = array [ i ] ;
if (
'' + i === expandedName &&
typeof value === 'object' &&
value !== null
) {
2022-10-04 23:04:47 +08:00
// $FlowFixMe[incompatible-call] found when upgrading Flow
2020-10-09 08:02:23 +08:00
str += describeObjectForErrorMessage ( value ) ;
} else {
str += describeValueForErrorMessage ( value ) ;
}
2020-10-09 02:11:15 +08:00
}
str += ']' ;
return str ;
} else {
let str = '{' ;
2022-09-09 22:13:58 +08:00
const object : { + [ key : string | number ] : ReactModel , ... } = objectOrArray ;
2020-10-09 02:11:15 +08:00
const names = Object . keys ( object ) ;
for ( let i = 0 ; i < names . length ; i ++ ) {
if ( i > 0 ) {
str += ', ' ;
}
if ( i > 6 ) {
str += '...' ;
break ;
}
const name = names [ i ] ;
2020-10-09 08:02:23 +08:00
str += describeKeyForErrorMessage ( name ) + ': ' ;
const value = object [ name ] ;
if (
name === expandedName &&
typeof value === 'object' &&
value !== null
) {
2022-10-04 23:04:47 +08:00
// $FlowFixMe[incompatible-call] found when upgrading Flow
2020-10-09 08:02:23 +08:00
str += describeObjectForErrorMessage ( value ) ;
} else {
str += describeValueForErrorMessage ( value ) ;
}
2020-10-09 02:11:15 +08:00
}
str += '}' ;
return str ;
}
}
2022-03-08 20:55:32 +08:00
let insideContextProps = null ;
let isInsideContextValue = false ;
2020-03-11 05:55:04 +08:00
export function resolveModelToJSON (
request : Request ,
2022-09-10 04:03:48 +08:00
parent : { + [ key : string | number ] : ReactModel } | $ReadOnlyArray < ReactModel > ,
2020-03-24 08:53:45 +08:00
key : string ,
2019-11-07 01:48:34 +08:00
value : ReactModel ,
) : ReactJSONValue {
2020-10-09 02:11:15 +08:00
if ( _ _DEV _ _ ) {
// $FlowFixMe
const originalValue = parent [ key ] ;
if ( typeof originalValue === 'object' && originalValue !== value ) {
console . error (
'Only plain objects can be passed to client components from server components. ' +
'Objects with toJSON methods are not supported. Convert it manually ' +
'to a simple value before passing it to props. ' +
'Remove %s from these props: %s' ,
describeKeyForErrorMessage ( key ) ,
describeObjectForErrorMessage ( parent ) ,
) ;
}
}
2020-03-24 08:53:45 +08:00
// Special Symbols
switch ( value ) {
case REACT _ELEMENT _TYPE :
return '$' ;
2019-11-07 01:48:34 +08:00
}
2022-03-08 20:55:32 +08:00
if ( _ _DEV _ _ ) {
if (
parent [ 0 ] === REACT _ELEMENT _TYPE &&
parent [ 1 ] &&
parent [ 1 ] . $$typeof === REACT _PROVIDER _TYPE &&
key === '3'
) {
insideContextProps = value ;
} else if ( insideContextProps === parent && key === 'value' ) {
isInsideContextValue = true ;
} else if ( insideContextProps === parent && key === 'children' ) {
isInsideContextValue = false ;
}
}
2020-03-24 08:53:45 +08:00
// Resolve server components.
2019-11-07 01:48:34 +08:00
while (
typeof value === 'object' &&
value !== null &&
2022-03-11 03:18:54 +08:00
( ( value : any ) . $$typeof === REACT _ELEMENT _TYPE ||
( value : any ) . $$typeof === REACT _LAZY _TYPE )
2019-11-07 01:48:34 +08:00
) {
2022-03-08 20:55:32 +08:00
if ( _ _DEV _ _ ) {
if ( isInsideContextValue ) {
console . error ( 'React elements are not allowed in ServerContext' ) ;
}
}
2022-03-11 03:18:54 +08:00
2020-04-04 05:58:02 +08:00
try {
2022-03-11 03:18:54 +08:00
switch ( ( value : any ) . $$typeof ) {
case REACT _ELEMENT _TYPE : {
// TODO: Concatenate keys of parents onto children.
const element : React$Element < any > = ( value : any ) ;
// Attempt to render the server component.
value = attemptResolveElement (
element . type ,
element . key ,
element . ref ,
element . props ,
2022-09-08 10:27:04 +08:00
null ,
2022-03-11 03:18:54 +08:00
) ;
break ;
}
case REACT _LAZY _TYPE : {
const payload = ( value : any ) . _payload ;
const init = ( value : any ) . _init ;
value = init ( payload ) ;
break ;
}
}
2020-04-04 05:58:02 +08:00
} catch ( x ) {
if ( typeof x === 'object' && x !== null && typeof x . then === 'function' ) {
2022-06-19 03:02:11 +08:00
// Something suspended, we'll need to create a new task and resolve it later.
2020-04-04 05:58:02 +08:00
request . pendingChunks ++ ;
2022-06-19 23:05:41 +08:00
const newTask = createTask (
request ,
value ,
getActiveContext ( ) ,
request . abortableTasks ,
) ;
2022-06-19 03:02:11 +08:00
const ping = newTask . ping ;
2020-04-04 05:58:02 +08:00
x . then ( ping , ping ) ;
2022-09-08 10:27:04 +08:00
const wakeable : Wakeable = x ;
trackSuspendedWakeable ( wakeable ) ;
newTask . thenableState = getThenableStateAfterSuspending ( ) ;
2022-06-19 03:02:11 +08:00
return serializeByRefID ( newTask . id ) ;
2020-04-04 05:58:02 +08:00
} else {
Add onRecoverableError option to hydrateRoot, createRoot (#23207)
* [RFC] Add onHydrationError option to hydrateRoot
This is not the final API but I'm pushing it for discussion purposes.
When an error is thrown during hydration, we fallback to client
rendering, without triggering an error boundary. This is good because,
in many cases, the UI will recover and the user won't even notice that
something has gone wrong behind the scenes.
However, we shouldn't recover from these errors silently, because the
underlying cause might be pretty serious. Server-client mismatches are
not supposed to happen, even if UI doesn't break from the users
perspective. Ignoring them could lead to worse problems later. De-opting
from server to client rendering could also be a significant performance
regression, depending on the scope of the UI it affects.
So we need a way to log when hydration errors occur.
This adds a new option for `hydrateRoot` called `onHydrationError`. It's
symmetrical to the server renderer's `onError` option, and serves the
same purpose.
When no option is provided, the default behavior is to schedule a
browser task and rethrow the error. This will trigger the normal browser
behavior for errors, including dispatching an error event. If the app
already has error monitoring, this likely will just work as expected
without additional configuration.
However, we can also expose additional metadata about these errors, like
which Suspense boundaries were affected by the de-opt to client
rendering. (I have not exposed any metadata in this commit; API needs
more design work.)
There are other situations besides hydration where we recover from an
error without surfacing it to the user, or notifying an error boundary.
For example, if an error occurs during a concurrent render, it could be
due to a data race, so we try again synchronously in case that fixes it.
We should probably expose a way to log these types of errors, too. (Also
not implemented in this commit.)
* Log all recoverable errors
This expands the scope of onHydrationError to include all errors that
are not surfaced to the UI (an error boundary). In addition to errors
that occur during hydration, this also includes errors that recoverable
by de-opting to synchronous rendering. Typically (or really, by
definition) these errors are the result of a concurrent data race;
blocking the main thread fixes them by prevents subsequent races.
The logic for de-opting to synchronous rendering already existed. The
only thing that has changed is that we now log the errors instead of
silently proceeding.
The logging API has been renamed from onHydrationError
to onRecoverableError.
* Don't log recoverable errors until commit phase
If the render is interrupted and restarts, we don't want to log the
errors multiple times.
This change only affects errors that are recovered by de-opting to
synchronous rendering; we'll have to do something else for errors
during hydration, since they use a different recovery path.
* Only log hydration error if client render succeeds
Similar to previous step.
When an error occurs during hydration, we only want to log it if falling
back to client rendering _succeeds_. If client rendering fails,
the error will get reported to the nearest error boundary, so there's
no need for a duplicate log.
To implement this, I added a list of errors to the hydration context.
If the Suspense boundary successfully completes, they are added to
the main recoverable errors queue (the one I added in the
previous step.)
* Log error with queueMicrotask instead of Scheduler
If onRecoverableError is not provided, we default to rethrowing the
error in a separate task. Originally, I scheduled the task with
idle priority, but @sebmarkbage made the good point that if there are
multiple errors logs, we want to preserve the original order. So I've
switched it to a microtask. The priority can be lowered in userspace
by scheduling an additional task inside onRecoverableError.
* Only use host config method for default behavior
Redefines the contract of the host config's logRecoverableError method
to be a default implementation for onRecoverableError if a user-provided
one is not provided when the root is created.
* Log with reportError instead of rethrowing
In modern browsers, reportError will dispatch an error event, emulating
an uncaught JavaScript error. We can do this instead of rethrowing
recoverable errors in a microtask, which is nice because it avoids any
subtle ordering issues.
In older browsers and test environments, we'll fall back
to console.error.
* Naming nits
queueRecoverableHydrationErrors -> upgradeHydrationErrorsToRecoverable
2022-02-04 23:57:33 +08:00
logRecoverableError ( request , x ) ;
2020-11-11 08:35:27 +08:00
// Something errored. We'll still send everything we have up until this point.
// We'll replace this element with a lazy reference that throws on the client
// once it gets rendered.
request . pendingChunks ++ ;
const errorId = request . nextChunkId ++ ;
2022-09-24 04:19:29 +08:00
const digest = logRecoverableError ( request , x ) ;
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( x ) ;
emitErrorChunkDev ( request , errorId , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , errorId , digest ) ;
}
2020-11-11 08:35:27 +08:00
return serializeByRefID ( errorId ) ;
2020-04-04 05:58:02 +08:00
}
}
2019-10-30 05:45:47 +08:00
}
2019-11-07 01:48:34 +08:00
2020-10-30 08:57:31 +08:00
if ( value === null ) {
return null ;
}
2020-10-09 02:11:15 +08:00
if ( typeof value === 'object' ) {
2020-10-30 08:57:31 +08:00
if ( isModuleReference ( value ) ) {
2022-08-25 19:47:38 +08:00
return serializeModuleReference ( request , parent , key , ( value : any ) ) ;
2022-03-08 20:55:32 +08:00
} else if ( ( value : any ) . $$typeof === REACT _PROVIDER _TYPE ) {
const providerKey = ( ( value : any ) : ReactProviderType < any > ) . _context
. _globalName ;
const writtenProviders = request . writtenProviders ;
let providerId = writtenProviders . get ( key ) ;
if ( providerId === undefined ) {
request . pendingChunks ++ ;
providerId = request . nextChunkId ++ ;
writtenProviders . set ( providerKey , providerId ) ;
emitProviderChunk ( request , providerId , providerKey ) ;
}
return serializeByValueID ( providerId ) ;
} else if ( value === POP ) {
popProvider ( ) ;
if ( _ _DEV _ _ ) {
insideContextProps = null ;
isInsideContextValue = false ;
}
return ( undefined : any ) ;
2020-10-30 08:57:31 +08:00
}
2020-10-09 02:11:15 +08:00
if ( _ _DEV _ _ ) {
if ( value !== null && ! isArray ( value ) ) {
// Verify that this is a simple plain object.
if ( objectName ( value ) !== 'Object' ) {
console . error (
'Only plain objects can be passed to client components from server components. ' +
'Built-ins like %s are not supported. ' +
'Remove %s from these props: %s' ,
objectName ( value ) ,
describeKeyForErrorMessage ( key ) ,
describeObjectForErrorMessage ( parent ) ,
) ;
} else if ( ! isSimpleObject ( value ) ) {
console . error (
'Only plain objects can be passed to client components from server components. ' +
'Classes or other objects with methods are not supported. ' +
'Remove %s from these props: %s' ,
describeKeyForErrorMessage ( key ) ,
2020-10-09 08:02:23 +08:00
describeObjectForErrorMessage ( parent , key ) ,
2020-10-09 02:11:15 +08:00
) ;
} else if ( Object . getOwnPropertySymbols ) {
const symbols = Object . getOwnPropertySymbols ( value ) ;
if ( symbols . length > 0 ) {
console . error (
'Only plain objects can be passed to client components from server components. ' +
'Objects with symbol properties like %s are not supported. ' +
'Remove %s from these props: %s' ,
symbols [ 0 ] . description ,
describeKeyForErrorMessage ( key ) ,
2020-10-09 08:02:23 +08:00
describeObjectForErrorMessage ( parent , key ) ,
2020-10-09 02:11:15 +08:00
) ;
}
}
}
}
2022-03-08 20:55:32 +08:00
2022-09-09 22:13:58 +08:00
// $FlowFixMe
2020-10-09 02:11:15 +08:00
return value ;
}
if ( typeof value === 'string' ) {
return escapeStringValue ( value ) ;
}
if (
typeof value === 'boolean' ||
typeof value === 'number' ||
typeof value === 'undefined'
) {
return value ;
}
if ( typeof value === 'function' ) {
2022-08-25 19:47:38 +08:00
if ( isModuleReference ( value ) ) {
return serializeModuleReference ( request , parent , key , ( value : any ) ) ;
}
2020-10-09 02:11:15 +08:00
if ( /^on[A-Z]/ . test ( key ) ) {
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
2020-10-09 02:11:15 +08:00
'Event handlers cannot be passed to client component props. ' +
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
` Remove ${ describeKeyForErrorMessage (
key ,
) } from these props if possible : $ { describeObjectForErrorMessage (
parent ,
) }
` +
2020-10-09 02:11:15 +08:00
'If you need interactivity, consider converting part of this to a client component.' ,
) ;
} else {
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
2020-10-09 02:11:15 +08:00
'Functions cannot be passed directly to client components ' +
"because they're not serializable. " +
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
` Remove ${ describeKeyForErrorMessage ( key ) } ( ${ value . displayName ||
value . name ||
'function' } ) from this object , or avoid the entire object : $ { describeObjectForErrorMessage (
parent ,
) } ` ,
2020-10-09 02:11:15 +08:00
) ;
}
}
if ( typeof value === 'symbol' ) {
2020-11-11 11:56:50 +08:00
const writtenSymbols = request . writtenSymbols ;
const existingId = writtenSymbols . get ( value ) ;
if ( existingId !== undefined ) {
return serializeByValueID ( existingId ) ;
}
2022-09-08 23:46:07 +08:00
// $FlowFixMe `description` might be undefined
const name : string = value . description ;
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if ( Symbol . for ( name ) !== value ) {
throw new Error (
'Only global symbols received from Symbol.for(...) can be passed to client components. ' +
2022-09-08 23:46:07 +08:00
` The symbol Symbol.for( ${
// $FlowFixMe `description` might be undefined
value . description
} ) cannot be found among global symbols . ` +
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
` Remove ${ describeKeyForErrorMessage (
key ,
) } from this object , or avoid the entire object : $ { describeObjectForErrorMessage (
parent ,
) } ` ,
) ;
}
2020-11-11 11:56:50 +08:00
request . pendingChunks ++ ;
const symbolId = request . nextChunkId ++ ;
emitSymbolChunk ( request , symbolId , name ) ;
writtenSymbols . set ( value , symbolId ) ;
return serializeByValueID ( symbolId ) ;
2020-10-09 02:11:15 +08:00
}
// $FlowFixMe: bigint isn't added to Flow yet.
if ( typeof value === 'bigint' ) {
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
` BigInt ( ${ value } ) is not yet supported in client component props. ` +
` Remove ${ describeKeyForErrorMessage (
key ,
) } from this object or use a plain number instead : $ { describeObjectForErrorMessage (
parent ,
) } ` ,
2020-10-09 02:11:15 +08:00
) ;
}
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error (
` Type ${ typeof value } is not supported in client component props. ` +
` Remove ${ describeKeyForErrorMessage (
key ,
) } from this object , or avoid the entire object : $ { describeObjectForErrorMessage (
parent ,
) } ` ,
2020-10-09 02:11:15 +08:00
) ;
2019-10-30 05:45:47 +08:00
}
2022-09-24 04:19:29 +08:00
function logRecoverableError ( request : Request , error : mixed ) : string {
2021-04-01 23:43:12 +08:00
const onError = request . onError ;
2022-09-24 04:19:29 +08:00
const errorDigest = onError ( error ) ;
if ( errorDigest != null && typeof errorDigest !== 'string' ) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error (
` onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type " ${ typeof errorDigest } " instead ` ,
) ;
}
return errorDigest || '' ;
}
function getErrorMessageAndStackDev (
error : mixed ,
) : { message : string , stack : string } {
if ( _ _DEV _ _ ) {
let message ;
let stack = '' ;
try {
if ( error instanceof Error ) {
// eslint-disable-next-line react-internal/safe-string-coercion
message = String ( error . message ) ;
// eslint-disable-next-line react-internal/safe-string-coercion
stack = String ( error . stack ) ;
} else {
message = 'Error: ' + ( error : any ) ;
}
} catch ( x ) {
message = 'An error occurred but serializing the error message failed.' ;
}
return {
message ,
stack ,
} ;
} else {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error (
'getErrorMessageAndStackDev should never be called from production mode. This is a bug in React.' ,
) ;
}
2021-03-30 10:36:16 +08:00
}
function fatalError ( request : Request , error : mixed ) : void {
// This is called outside error handling code such as if an error happens in React internals.
2021-09-29 06:32:09 +08:00
if ( request . destination !== null ) {
request . status = CLOSED ;
closeWithError ( request . destination , error ) ;
} else {
request . status = CLOSING ;
request . fatalError = error ;
}
2021-03-30 10:36:16 +08:00
}
2022-09-24 04:19:29 +08:00
function emitErrorChunkProd (
request : Request ,
id : number ,
digest : string ,
) : void {
const processedChunk = processErrorChunkProd ( request , id , digest ) ;
request . completedErrorChunks . push ( processedChunk ) ;
}
2020-03-11 05:55:04 +08:00
2022-09-24 04:19:29 +08:00
function emitErrorChunkDev (
request : Request ,
id : number ,
digest : string ,
message : string ,
stack : string ,
) : void {
const processedChunk = processErrorChunkDev (
request ,
id ,
digest ,
message ,
stack ,
) ;
2020-03-11 05:55:04 +08:00
request . completedErrorChunks . push ( processedChunk ) ;
2019-11-07 01:48:34 +08:00
}
2020-10-30 08:57:31 +08:00
function emitModuleChunk (
request : Request ,
id : number ,
moduleMetaData : ModuleMetaData ,
) : void {
const processedChunk = processModuleChunk ( request , id , moduleMetaData ) ;
request . completedModuleChunks . push ( processedChunk ) ;
}
2020-11-11 11:56:50 +08:00
function emitSymbolChunk ( request : Request , id : number , name : string ) : void {
const processedChunk = processSymbolChunk ( request , id , name ) ;
request . completedModuleChunks . push ( processedChunk ) ;
}
2022-03-08 20:55:32 +08:00
function emitProviderChunk (
request : Request ,
id : number ,
contextName : string ,
) : void {
const processedChunk = processProviderChunk ( request , id , contextName ) ;
request . completedJSONChunks . push ( processedChunk ) ;
}
2022-06-19 03:02:11 +08:00
function retryTask ( request : Request , task : Task ) : void {
2022-06-19 23:05:41 +08:00
if ( task . status !== PENDING ) {
// We completed this by other means before we had a chance to retry it.
return ;
}
2022-09-08 10:27:04 +08:00
2022-06-19 03:02:11 +08:00
switchContext ( task . context ) ;
2019-11-07 01:48:34 +08:00
try {
2022-06-19 03:02:11 +08:00
let value = task . model ;
2022-09-08 10:27:04 +08:00
if (
2020-04-04 05:58:02 +08:00
typeof value === 'object' &&
value !== null &&
2022-03-08 20:55:32 +08:00
( value : any ) . $$typeof === REACT _ELEMENT _TYPE
2020-04-04 05:58:02 +08:00
) {
// TODO: Concatenate keys of parents onto children.
const element : React$Element < any > = ( value : any ) ;
2022-09-08 10:27:04 +08:00
// When retrying a component, reuse the thenableState from the
// previous attempt.
const prevThenableState = task . thenableState ;
2020-04-04 05:58:02 +08:00
// Attempt to render the server component.
2022-06-19 03:02:11 +08:00
// Doing this here lets us reuse this same task if the next component
2020-04-04 05:58:02 +08:00
// also suspends.
2022-06-19 03:02:11 +08:00
task . model = value ;
2020-11-11 11:56:50 +08:00
value = attemptResolveElement (
element . type ,
element . key ,
element . ref ,
element . props ,
2022-09-08 10:27:04 +08:00
prevThenableState ,
2020-11-11 11:56:50 +08:00
) ;
2022-09-08 10:27:04 +08:00
// Successfully finished this component. We're going to keep rendering
// using the same task, but we reset its thenable state before continuing.
task . thenableState = null ;
// Keep rendering and reuse the same task. This inner loop is separate
// from the render above because we don't need to reset the thenable state
// until the next time something suspends and retries.
while (
typeof value === 'object' &&
value !== null &&
( value : any ) . $$typeof === REACT _ELEMENT _TYPE
) {
// TODO: Concatenate keys of parents onto children.
const nextElement : React$Element < any > = ( value : any ) ;
task . model = value ;
value = attemptResolveElement (
nextElement . type ,
nextElement . key ,
nextElement . ref ,
nextElement . props ,
null ,
) ;
}
2020-04-04 05:58:02 +08:00
}
2022-09-08 10:27:04 +08:00
2022-06-19 03:02:11 +08:00
const processedChunk = processModelChunk ( request , task . id , value ) ;
2020-03-11 05:55:04 +08:00
request . completedJSONChunks . push ( processedChunk ) ;
2022-06-19 23:05:41 +08:00
request . abortableTasks . delete ( task ) ;
task . status = COMPLETED ;
2019-11-07 01:48:34 +08:00
} catch ( x ) {
if ( typeof x === 'object' && x !== null && typeof x . then === 'function' ) {
// Something suspended again, let's pick it back up later.
2022-06-19 03:02:11 +08:00
const ping = task . ping ;
2019-11-07 01:48:34 +08:00
x . then ( ping , ping ) ;
2022-09-08 10:27:04 +08:00
const wakeable : Wakeable = x ;
trackSuspendedWakeable ( wakeable ) ;
task . thenableState = getThenableStateAfterSuspending ( ) ;
2019-11-07 01:48:34 +08:00
return ;
} else {
2022-06-19 23:05:41 +08:00
request . abortableTasks . delete ( task ) ;
task . status = ERRORED ;
2022-09-24 04:19:29 +08:00
const digest = logRecoverableError ( request , x ) ;
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( x ) ;
emitErrorChunkDev ( request , task . id , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , task . id , digest ) ;
}
2019-11-07 01:48:34 +08:00
}
}
}
2020-03-11 05:55:04 +08:00
function performWork ( request : Request ) : void {
2020-09-08 07:41:22 +08:00
const prevDispatcher = ReactCurrentDispatcher . current ;
2022-10-13 11:13:39 +08:00
const prevCacheDispatcher = ReactCurrentCache . current ;
2022-03-08 20:55:32 +08:00
const prevCache = getCurrentCache ( ) ;
2022-10-13 11:13:39 +08:00
ReactCurrentDispatcher . current = HooksDispatcher ;
ReactCurrentCache . current = DefaultCacheDispatcher ;
2022-03-08 20:55:32 +08:00
setCurrentCache ( request . cache ) ;
2022-06-01 05:53:32 +08:00
prepareToUseHooksForRequest ( request ) ;
2020-09-08 07:41:22 +08:00
2021-03-30 10:36:16 +08:00
try {
2022-06-19 03:02:11 +08:00
const pingedTasks = request . pingedTasks ;
request . pingedTasks = [ ] ;
for ( let i = 0 ; i < pingedTasks . length ; i ++ ) {
const task = pingedTasks [ i ] ;
retryTask ( request , task ) ;
2021-03-30 10:36:16 +08:00
}
2021-09-29 06:32:09 +08:00
if ( request . destination !== null ) {
flushCompletedChunks ( request , request . destination ) ;
2021-03-30 10:36:16 +08:00
}
} catch ( error ) {
Add onRecoverableError option to hydrateRoot, createRoot (#23207)
* [RFC] Add onHydrationError option to hydrateRoot
This is not the final API but I'm pushing it for discussion purposes.
When an error is thrown during hydration, we fallback to client
rendering, without triggering an error boundary. This is good because,
in many cases, the UI will recover and the user won't even notice that
something has gone wrong behind the scenes.
However, we shouldn't recover from these errors silently, because the
underlying cause might be pretty serious. Server-client mismatches are
not supposed to happen, even if UI doesn't break from the users
perspective. Ignoring them could lead to worse problems later. De-opting
from server to client rendering could also be a significant performance
regression, depending on the scope of the UI it affects.
So we need a way to log when hydration errors occur.
This adds a new option for `hydrateRoot` called `onHydrationError`. It's
symmetrical to the server renderer's `onError` option, and serves the
same purpose.
When no option is provided, the default behavior is to schedule a
browser task and rethrow the error. This will trigger the normal browser
behavior for errors, including dispatching an error event. If the app
already has error monitoring, this likely will just work as expected
without additional configuration.
However, we can also expose additional metadata about these errors, like
which Suspense boundaries were affected by the de-opt to client
rendering. (I have not exposed any metadata in this commit; API needs
more design work.)
There are other situations besides hydration where we recover from an
error without surfacing it to the user, or notifying an error boundary.
For example, if an error occurs during a concurrent render, it could be
due to a data race, so we try again synchronously in case that fixes it.
We should probably expose a way to log these types of errors, too. (Also
not implemented in this commit.)
* Log all recoverable errors
This expands the scope of onHydrationError to include all errors that
are not surfaced to the UI (an error boundary). In addition to errors
that occur during hydration, this also includes errors that recoverable
by de-opting to synchronous rendering. Typically (or really, by
definition) these errors are the result of a concurrent data race;
blocking the main thread fixes them by prevents subsequent races.
The logic for de-opting to synchronous rendering already existed. The
only thing that has changed is that we now log the errors instead of
silently proceeding.
The logging API has been renamed from onHydrationError
to onRecoverableError.
* Don't log recoverable errors until commit phase
If the render is interrupted and restarts, we don't want to log the
errors multiple times.
This change only affects errors that are recovered by de-opting to
synchronous rendering; we'll have to do something else for errors
during hydration, since they use a different recovery path.
* Only log hydration error if client render succeeds
Similar to previous step.
When an error occurs during hydration, we only want to log it if falling
back to client rendering _succeeds_. If client rendering fails,
the error will get reported to the nearest error boundary, so there's
no need for a duplicate log.
To implement this, I added a list of errors to the hydration context.
If the Suspense boundary successfully completes, they are added to
the main recoverable errors queue (the one I added in the
previous step.)
* Log error with queueMicrotask instead of Scheduler
If onRecoverableError is not provided, we default to rethrowing the
error in a separate task. Originally, I scheduled the task with
idle priority, but @sebmarkbage made the good point that if there are
multiple errors logs, we want to preserve the original order. So I've
switched it to a microtask. The priority can be lowered in userspace
by scheduling an additional task inside onRecoverableError.
* Only use host config method for default behavior
Redefines the contract of the host config's logRecoverableError method
to be a default implementation for onRecoverableError if a user-provided
one is not provided when the root is created.
* Log with reportError instead of rethrowing
In modern browsers, reportError will dispatch an error event, emulating
an uncaught JavaScript error. We can do this instead of rethrowing
recoverable errors in a microtask, which is nice because it avoids any
subtle ordering issues.
In older browsers and test environments, we'll fall back
to console.error.
* Naming nits
queueRecoverableHydrationErrors -> upgradeHydrationErrorsToRecoverable
2022-02-04 23:57:33 +08:00
logRecoverableError ( request , error ) ;
2021-03-30 10:36:16 +08:00
fatalError ( request , error ) ;
} finally {
ReactCurrentDispatcher . current = prevDispatcher ;
2022-10-13 11:13:39 +08:00
ReactCurrentCache . current = prevCacheDispatcher ;
2022-03-08 20:55:32 +08:00
setCurrentCache ( prevCache ) ;
2022-06-01 05:53:32 +08:00
resetHooksForRequest ( ) ;
2019-10-30 05:45:47 +08:00
}
}
2022-06-19 23:05:41 +08:00
function abortTask ( task : Task , request : Request , errorId : number ) : void {
task . status = ABORTED ;
// Instead of emitting an error per task.id, we emit a model that only
// has a single value referencing the error.
const ref = serializeByValueID ( errorId ) ;
const processedChunk = processReferenceChunk ( request , task . id , ref ) ;
2022-09-01 06:40:17 +08:00
request . completedErrorChunks . push ( processedChunk ) ;
2022-06-19 23:05:41 +08:00
}
2021-09-29 06:32:09 +08:00
function flushCompletedChunks (
request : Request ,
destination : Destination ,
) : void {
2019-10-30 05:45:47 +08:00
beginWriting ( destination ) ;
try {
2020-10-30 08:57:31 +08:00
// We emit module chunks first in the stream so that
// they can be preloaded as early as possible.
const moduleChunks = request . completedModuleChunks ;
2019-11-07 01:48:34 +08:00
let i = 0 ;
2020-10-30 08:57:31 +08:00
for ( ; i < moduleChunks . length ; i ++ ) {
request . pendingChunks -- ;
const chunk = moduleChunks [ i ] ;
2022-02-24 00:35:21 +08:00
const keepWriting : boolean = writeChunkAndReturn ( destination , chunk ) ;
if ( ! keepWriting ) {
2021-09-29 06:32:09 +08:00
request . destination = null ;
2020-10-30 08:57:31 +08:00
i ++ ;
break ;
}
}
2020-10-31 08:19:46 +08:00
moduleChunks . splice ( 0 , i ) ;
2020-10-30 08:57:31 +08:00
// Next comes model data.
const jsonChunks = request . completedJSONChunks ;
i = 0 ;
2019-11-07 01:48:34 +08:00
for ( ; i < jsonChunks . length ; i ++ ) {
request . pendingChunks -- ;
2020-04-02 03:35:52 +08:00
const chunk = jsonChunks [ i ] ;
2022-02-24 00:35:21 +08:00
const keepWriting : boolean = writeChunkAndReturn ( destination , chunk ) ;
if ( ! keepWriting ) {
2021-09-29 06:32:09 +08:00
request . destination = null ;
2019-11-07 01:48:34 +08:00
i ++ ;
break ;
}
}
jsonChunks . splice ( 0 , i ) ;
2020-10-30 08:57:31 +08:00
// Finally, errors are sent. The idea is that it's ok to delay
// any error messages and prioritize display of other parts of
// the page.
2020-04-02 03:35:52 +08:00
const errorChunks = request . completedErrorChunks ;
2019-11-07 01:48:34 +08:00
i = 0 ;
for ( ; i < errorChunks . length ; i ++ ) {
request . pendingChunks -- ;
2020-04-02 03:35:52 +08:00
const chunk = errorChunks [ i ] ;
2022-02-24 00:35:21 +08:00
const keepWriting : boolean = writeChunkAndReturn ( destination , chunk ) ;
if ( ! keepWriting ) {
2021-09-29 06:32:09 +08:00
request . destination = null ;
2019-11-07 01:48:34 +08:00
i ++ ;
break ;
}
2019-10-30 05:45:47 +08:00
}
2019-11-07 01:48:34 +08:00
errorChunks . splice ( 0 , i ) ;
2019-10-30 05:45:47 +08:00
} finally {
completeWriting ( destination ) ;
}
2019-11-07 01:48:34 +08:00
flushBuffered ( destination ) ;
if ( request . pendingChunks === 0 ) {
// We're done.
close ( destination ) ;
}
2019-10-30 05:45:47 +08:00
}
2020-03-11 05:55:04 +08:00
export function startWork ( request : Request ) : void {
2019-10-30 05:45:47 +08:00
scheduleWork ( ( ) => performWork ( request ) ) ;
}
2021-09-29 06:32:09 +08:00
export function startFlowing ( request : Request , destination : Destination ) : void {
if ( request . status === CLOSING ) {
request . status = CLOSED ;
closeWithError ( destination , request . fatalError ) ;
return ;
}
if ( request . status === CLOSED ) {
return ;
}
2022-02-23 13:33:41 +08:00
if ( request . destination !== null ) {
// We're already flowing.
return ;
}
2021-09-29 06:32:09 +08:00
request . destination = destination ;
2021-03-30 10:36:16 +08:00
try {
2021-09-29 06:32:09 +08:00
flushCompletedChunks ( request , destination ) ;
2021-03-30 10:36:16 +08:00
} catch ( error ) {
Add onRecoverableError option to hydrateRoot, createRoot (#23207)
* [RFC] Add onHydrationError option to hydrateRoot
This is not the final API but I'm pushing it for discussion purposes.
When an error is thrown during hydration, we fallback to client
rendering, without triggering an error boundary. This is good because,
in many cases, the UI will recover and the user won't even notice that
something has gone wrong behind the scenes.
However, we shouldn't recover from these errors silently, because the
underlying cause might be pretty serious. Server-client mismatches are
not supposed to happen, even if UI doesn't break from the users
perspective. Ignoring them could lead to worse problems later. De-opting
from server to client rendering could also be a significant performance
regression, depending on the scope of the UI it affects.
So we need a way to log when hydration errors occur.
This adds a new option for `hydrateRoot` called `onHydrationError`. It's
symmetrical to the server renderer's `onError` option, and serves the
same purpose.
When no option is provided, the default behavior is to schedule a
browser task and rethrow the error. This will trigger the normal browser
behavior for errors, including dispatching an error event. If the app
already has error monitoring, this likely will just work as expected
without additional configuration.
However, we can also expose additional metadata about these errors, like
which Suspense boundaries were affected by the de-opt to client
rendering. (I have not exposed any metadata in this commit; API needs
more design work.)
There are other situations besides hydration where we recover from an
error without surfacing it to the user, or notifying an error boundary.
For example, if an error occurs during a concurrent render, it could be
due to a data race, so we try again synchronously in case that fixes it.
We should probably expose a way to log these types of errors, too. (Also
not implemented in this commit.)
* Log all recoverable errors
This expands the scope of onHydrationError to include all errors that
are not surfaced to the UI (an error boundary). In addition to errors
that occur during hydration, this also includes errors that recoverable
by de-opting to synchronous rendering. Typically (or really, by
definition) these errors are the result of a concurrent data race;
blocking the main thread fixes them by prevents subsequent races.
The logic for de-opting to synchronous rendering already existed. The
only thing that has changed is that we now log the errors instead of
silently proceeding.
The logging API has been renamed from onHydrationError
to onRecoverableError.
* Don't log recoverable errors until commit phase
If the render is interrupted and restarts, we don't want to log the
errors multiple times.
This change only affects errors that are recovered by de-opting to
synchronous rendering; we'll have to do something else for errors
during hydration, since they use a different recovery path.
* Only log hydration error if client render succeeds
Similar to previous step.
When an error occurs during hydration, we only want to log it if falling
back to client rendering _succeeds_. If client rendering fails,
the error will get reported to the nearest error boundary, so there's
no need for a duplicate log.
To implement this, I added a list of errors to the hydration context.
If the Suspense boundary successfully completes, they are added to
the main recoverable errors queue (the one I added in the
previous step.)
* Log error with queueMicrotask instead of Scheduler
If onRecoverableError is not provided, we default to rethrowing the
error in a separate task. Originally, I scheduled the task with
idle priority, but @sebmarkbage made the good point that if there are
multiple errors logs, we want to preserve the original order. So I've
switched it to a microtask. The priority can be lowered in userspace
by scheduling an additional task inside onRecoverableError.
* Only use host config method for default behavior
Redefines the contract of the host config's logRecoverableError method
to be a default implementation for onRecoverableError if a user-provided
one is not provided when the root is created.
* Log with reportError instead of rethrowing
In modern browsers, reportError will dispatch an error event, emulating
an uncaught JavaScript error. We can do this instead of rethrowing
recoverable errors in a microtask, which is nice because it avoids any
subtle ordering issues.
In older browsers and test environments, we'll fall back
to console.error.
* Naming nits
queueRecoverableHydrationErrors -> upgradeHydrationErrorsToRecoverable
2022-02-04 23:57:33 +08:00
logRecoverableError ( request , error ) ;
2021-03-30 10:36:16 +08:00
fatalError ( request , error ) ;
}
2019-10-30 05:45:47 +08:00
}
2020-08-28 03:19:13 +08:00
2022-06-19 23:05:41 +08:00
// This is called to early terminate a request. It creates an error at all pending tasks.
export function abort ( request : Request , reason : mixed ) : void {
try {
const abortableTasks = request . abortableTasks ;
if ( abortableTasks . size > 0 ) {
// We have tasks to abort. We'll emit one error row and then emit a reference
// to that row from every row that's still remaining.
const error =
reason === undefined
? new Error ( 'The render was aborted by the server without a reason.' )
: reason ;
2022-09-24 04:19:29 +08:00
const digest = logRecoverableError ( request , error ) ;
2022-06-19 23:05:41 +08:00
request . pendingChunks ++ ;
const errorId = request . nextChunkId ++ ;
2022-09-24 04:19:29 +08:00
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( error ) ;
emitErrorChunkDev ( request , errorId , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , errorId , digest ) ;
}
2022-06-19 23:05:41 +08:00
abortableTasks . forEach ( task => abortTask ( task , request , errorId ) ) ;
abortableTasks . clear ( ) ;
}
if ( request . destination !== null ) {
flushCompletedChunks ( request , request . destination ) ;
}
} catch ( error ) {
logRecoverableError ( request , error ) ;
fatalError ( request , error ) ;
}
}
2022-03-08 20:55:32 +08:00
function importServerContexts (
contexts ? : Array < [ string , ServerContextJSONValue ] > ,
) {
if ( contexts ) {
const prevContext = getActiveContext ( ) ;
switchContext ( rootContextSnapshot ) ;
for ( let i = 0 ; i < contexts . length ; i ++ ) {
const [ name , value ] = contexts [ i ] ;
const context = getOrCreateServerContext ( name ) ;
pushProvider ( context , value ) ;
}
const importedContext = getActiveContext ( ) ;
switchContext ( prevContext ) ;
return importedContext ;
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope
When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.
Because `codes.json` is now checked into source, we can import it a
single time in module scope.
* Minify error constructors in production
We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92
This enables us to write helpful error messages without increasing the
bundle size.
Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.
So, I've updated the script to minify error constructors, too:
Input:
Error(`A ${adj} message that contains ${noun}`);
Output:
Error(formatProdErrorMessage(ERR_CODE, adj, noun));
It only works for constructors that are literally named Error, though we
could add support for other names, too.
As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.
* Minify "no fallback UI specified" error in prod
This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.
Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.
To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.
* Lint rule to detect unminified errors
Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.
We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.
There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.
* Temporarily add invariant codemod script
I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.
* [Automated] Codemod invariant -> Error
This commit contains only automated changes:
npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier
I will do any manual touch ups in separate commits so they're easier
to review.
* Remove temporary codemod script
This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.
* Manual touch ups
A few manual changes I made after the codemod ran.
* Enable error code transform per package
Currently we're not consistent about which packages should have their
errors minified in production and which ones should.
This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
}
2022-03-08 20:55:32 +08:00
return rootContextSnapshot ;
2020-12-19 02:57:24 +08:00
}