2019-10-30 05:45:47 +08:00
/ * *
2022-10-18 23:19:24 +08:00
* Copyright ( c ) Meta Platforms , Inc . and affiliates .
2019-10-30 05:45:47 +08:00
*
* 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 ,
2023-03-09 12:45:55 +08:00
ClientManifest ,
2023-02-10 08:45:05 +08:00
ClientReferenceMetadata ,
2023-01-28 09:08:26 +08:00
ClientReference ,
ClientReferenceKey ,
2023-02-10 08:45:05 +08:00
ServerReference ,
2023-03-05 08:51:34 +08:00
ServerReferenceId ,
2023-04-22 11:45:51 +08:00
Hints ,
HintModel ,
2020-03-19 03:18:34 +08:00
} from './ReactFlightServerConfig' ;
2022-03-08 20:55:32 +08:00
import type { ContextSnapshot } from './ReactFlightNewContext' ;
2022-10-23 05:52:20 +08:00
import type { ThenableState } from './ReactFlightThenable' ;
2022-03-08 20:55:32 +08:00
import type {
ReactProviderType ,
ServerContextJSONValue ,
2022-09-08 10:27:04 +08:00
Wakeable ,
2022-10-15 03:09:33 +08:00
Thenable ,
2022-10-23 10:52:39 +08:00
PendingThenable ,
FulfilledThenable ,
RejectedThenable ,
2023-03-09 12:45:55 +08:00
ReactServerContext ,
2022-03-08 20:55:32 +08:00
} from 'shared/ReactTypes' ;
2022-10-15 03:09:33 +08:00
import type { LazyComponent } from 'react/src/ReactLazy' ;
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 ,
2023-02-10 08:45:05 +08:00
processImportChunk ,
2022-09-24 04:19:29 +08:00
processErrorChunkProd ,
processErrorChunkDev ,
2022-06-19 23:05:41 +08:00
processReferenceChunk ,
2023-04-22 11:45:51 +08:00
processHintChunk ,
2023-02-10 08:45:05 +08:00
resolveClientReferenceMetadata ,
2023-03-09 12:45:55 +08:00
getServerReferenceId ,
getServerReferenceBoundArguments ,
2023-01-28 09:08:26 +08:00
getClientReferenceKey ,
isClientReference ,
2023-02-10 08:45:05 +08:00
isServerReference ,
2022-10-23 13:06:58 +08:00
supportsRequestStorage ,
requestStorage ,
2023-04-22 11:45:51 +08:00
prepareHostDispatcher ,
createHints ,
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' ;
2023-04-22 11:45:51 +08:00
import { DefaultCacheDispatcher } from './flight/ReactFlightServerCache' ;
2022-03-08 20:55:32 +08:00
import {
pushProvider ,
popProvider ,
switchContext ,
getActiveContext ,
rootContextSnapshot ,
} from './ReactFlightNewContext' ;
2020-03-24 08:53:45 +08:00
import {
2023-03-06 02:18:54 +08:00
getIteratorFn ,
2020-03-24 08:53:45 +08:00
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' ;
2023-03-11 00:36:15 +08:00
import {
describeValueForErrorMessage ,
describeObjectForErrorMessage ,
isSimpleObject ,
jsxPropsParents ,
jsxChildrenParents ,
objectName ,
} from 'shared/ReactSerializationErrors' ;
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' ;
2022-10-29 05:46:03 +08:00
import { SuspenseException , getSuspendedThenable } from './ReactFlightThenable' ;
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 >
2023-03-09 12:45:55 +08:00
| ReactClientObject ;
// Serializable values
export type ReactClientValue =
// Server Elements and Lazy Components are unwrapped on the Server
| React$Element < React$AbstractComponent < any , any >>
| LazyComponent < ReactClientValue , any >
// References are passed by their value
| ClientReference < any >
| ServerReference < any >
// The rest are passed as is. Sub-types can be passed in but lose their
// subtype, so the receiver can only accept once of these.
| React$Element < string >
| React$Element < ClientReference < any > & any >
| ReactServerContext < 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
2023-03-10 05:18:52 +08:00
| void
2023-03-09 12:45:55 +08:00
| Iterable < ReactClientValue >
| Array < ReactClientValue >
| ReactClientObject
| Promise < ReactClientValue > ; // Thenable<ReactClientValue>
2019-10-30 05:45:47 +08:00
2023-03-09 12:45:55 +08:00
type ReactClientObject = { + [ key : string ] : ReactClientValue } ;
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 ,
2023-03-09 12:45:55 +08:00
model : ReactClientValue ,
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 ,
2023-04-22 11:45:51 +08:00
flushScheduled : boolean ,
2021-09-29 06:32:09 +08:00
fatalError : mixed ,
destination : null | Destination ,
2023-03-09 12:45:55 +08:00
bundlerConfig : ClientManifest ,
2020-12-03 11:44:56 +08:00
cache : Map < Function , mixed > ,
2019-11-07 01:48:34 +08:00
nextChunkId : number ,
pendingChunks : number ,
2023-04-22 11:45:51 +08:00
hints : Hints ,
2022-06-19 23:05:41 +08:00
abortableTasks : Set < Task > ,
2022-06-19 03:02:11 +08:00
pingedTasks : Array < Task > ,
2023-02-10 08:45:05 +08:00
completedImportChunks : Array < Chunk > ,
2023-04-22 11:45:51 +08:00
completedHintChunks : 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 > ,
2023-02-10 08:45:05 +08:00
writtenClientReferences : Map < ClientReferenceKey , number > ,
writtenServerReferences : Map < ServerReference < any > , 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 ,
2023-03-09 12:45:55 +08:00
toJSON : ( key : string , value : ReactClientValue ) => 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 (
2023-03-09 12:45:55 +08:00
model : ReactClientValue ,
bundlerConfig : ClientManifest ,
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-10-23 13:06:58 +08:00
if (
ReactCurrentCache . current !== null &&
ReactCurrentCache . current !== DefaultCacheDispatcher
) {
throw new Error (
'Currently React only supports one RSC renderer at a time.' ,
) ;
}
2023-04-22 11:45:51 +08:00
prepareHostDispatcher ( ) ;
2022-10-23 13:06:58 +08:00
ReactCurrentCache . current = DefaultCacheDispatcher ;
2022-06-19 23:05:41 +08:00
const abortSet : Set < Task > = new Set ( ) ;
2023-02-10 06:07:39 +08:00
const pingedTasks : Array < Task > = [ ] ;
2023-04-22 11:45:51 +08:00
const hints = createHints ( ) ;
2023-02-10 06:07:39 +08:00
const request : Request = {
2021-09-29 06:32:09 +08:00
status : OPEN ,
2023-04-22 11:45:51 +08:00
flushScheduled : false ,
2021-09-29 06:32:09 +08:00
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 ,
2023-04-22 11:45:51 +08:00
hints ,
2022-06-19 23:05:41 +08:00
abortableTasks : abortSet ,
2022-06-19 03:02:11 +08:00
pingedTasks : pingedTasks ,
2023-02-10 08:45:05 +08:00
completedImportChunks : ( [ ] : Array < Chunk > ) ,
2023-04-22 11:45:51 +08:00
completedHintChunks : ( [ ] : Array < Chunk > ) ,
2023-02-10 08:45:05 +08:00
completedJSONChunks : ( [ ] : Array < Chunk > ) ,
completedErrorChunks : ( [ ] : Array < Chunk > ) ,
2020-11-11 11:56:50 +08:00
writtenSymbols : new Map ( ) ,
2023-02-10 08:45:05 +08:00
writtenClientReferences : new Map ( ) ,
writtenServerReferences : 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 ,
2023-01-10 04:46:48 +08:00
// $FlowFixMe[missing-this-annot]
2023-03-09 12:45:55 +08:00
toJSON : function ( key : string , value : ReactClientValue ) : ReactJSONValue {
2020-03-24 08:53:45 +08:00
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
}
2023-04-22 11:45:51 +08:00
let currentRequest : null | Request = null ;
export function resolveRequest ( ) : null | Request {
if ( currentRequest ) return currentRequest ;
if ( supportsRequestStorage ) {
const store = requestStorage . getStore ( ) ;
if ( store ) return store ;
}
return null ;
}
2022-03-08 20:55:32 +08:00
function createRootContext (
reqContext ? : Array < [ string , ServerContextJSONValue ] > ,
) {
return importServerContexts ( reqContext ) ;
}
const POP = { } ;
2023-02-02 01:56:53 +08:00
function serializeThenable ( request : Request , thenable : Thenable < any > ) : number {
request . pendingChunks ++ ;
const newTask = createTask (
request ,
null ,
getActiveContext ( ) ,
request . abortableTasks ,
) ;
switch ( thenable . status ) {
case 'fulfilled' : {
// We have the resolved value, we can go ahead and schedule it for serialization.
newTask . model = thenable . value ;
pingTask ( request , newTask ) ;
return newTask . id ;
}
case 'rejected' : {
const x = thenable . reason ;
const digest = logRecoverableError ( request , x ) ;
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( x ) ;
emitErrorChunkDev ( request , newTask . id , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , newTask . id , digest ) ;
}
return newTask . id ;
}
default : {
if ( typeof thenable . status === 'string' ) {
// Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation. We treat it as "pending".
break ;
}
const pendingThenable : PendingThenable < mixed > = ( thenable : any ) ;
pendingThenable . status = 'pending' ;
pendingThenable . then (
fulfilledValue => {
if ( thenable . status === 'pending' ) {
const fulfilledThenable : FulfilledThenable < mixed > = ( thenable : any ) ;
fulfilledThenable . status = 'fulfilled' ;
fulfilledThenable . value = fulfilledValue ;
}
} ,
( error : mixed ) => {
if ( thenable . status === 'pending' ) {
const rejectedThenable : RejectedThenable < mixed > = ( thenable : any ) ;
rejectedThenable . status = 'rejected' ;
rejectedThenable . reason = error ;
}
} ,
) ;
break ;
}
}
thenable . then (
value => {
newTask . model = value ;
pingTask ( request , newTask ) ;
} ,
reason => {
2023-03-05 11:56:19 +08:00
newTask . status = ERRORED ;
// TODO: We should ideally do this inside performWork so it's scheduled
2023-02-02 01:56:53 +08:00
const digest = logRecoverableError ( request , reason ) ;
if ( _ _DEV _ _ ) {
const { message , stack } = getErrorMessageAndStackDev ( reason ) ;
emitErrorChunkDev ( request , newTask . id , digest , message , stack ) ;
} else {
emitErrorChunkProd ( request , newTask . id , digest ) ;
}
2023-03-05 11:56:19 +08:00
if ( request . destination !== null ) {
flushCompletedChunks ( request , request . destination ) ;
}
2023-02-02 01:56:53 +08:00
} ,
) ;
return newTask . id ;
}
2023-04-22 11:45:51 +08:00
export function emitHint (
request : Request ,
code : string ,
model : HintModel ,
) : void {
emitHintChunk ( request , code , model ) ;
enqueueFlush ( request ) ;
}
export function getHints ( request : Request ) : Hints {
return request . hints ;
}
export function getCache ( request : Request ) : Map < Function , mixed > {
return request . cache ;
}
2022-10-15 03:09:33 +08:00
function readThenable < T > ( thenable : Thenable < T > ) : T {
if ( thenable . status === 'fulfilled' ) {
return thenable . value ;
} else if ( thenable . status === 'rejected' ) {
throw thenable . reason ;
}
throw thenable ;
}
function createLazyWrapperAroundWakeable ( wakeable : Wakeable ) {
2022-10-23 10:52:39 +08:00
// This is a temporary fork of the `use` implementation until we accept
// promises everywhere.
const thenable : Thenable < mixed > = ( wakeable : any ) ;
switch ( thenable . status ) {
case 'fulfilled' :
case 'rejected' :
break ;
default : {
if ( typeof thenable . status === 'string' ) {
// Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation. We treat it as "pending".
break ;
}
const pendingThenable : PendingThenable < mixed > = ( thenable : any ) ;
pendingThenable . status = 'pending' ;
pendingThenable . then (
fulfilledValue => {
if ( thenable . status === 'pending' ) {
const fulfilledThenable : FulfilledThenable < mixed > = ( thenable : any ) ;
fulfilledThenable . status = 'fulfilled' ;
fulfilledThenable . value = fulfilledValue ;
}
} ,
( error : mixed ) => {
if ( thenable . status === 'pending' ) {
const rejectedThenable : RejectedThenable < mixed > = ( thenable : any ) ;
rejectedThenable . status = 'rejected' ;
rejectedThenable . reason = error ;
}
} ,
) ;
break ;
}
}
2022-10-15 03:09:33 +08:00
const lazyType : LazyComponent < any , Thenable < any >> = {
$$typeof : REACT _LAZY _TYPE ,
2022-10-23 10:52:39 +08:00
_payload : thenable ,
2022-10-15 03:09:33 +08:00
_init : readThenable ,
} ;
return lazyType ;
}
2020-11-11 11:56:50 +08:00
function attemptResolveElement (
2023-02-02 01:56:53 +08:00
request : Request ,
2020-11-11 11:56:50 +08:00
type : any ,
key : null | React$Key ,
ref : mixed ,
props : any ,
2022-09-08 10:27:04 +08:00
prevThenableState : ThenableState | null ,
2023-03-09 12:45:55 +08:00
) : ReactClientValue {
2020-11-11 11:56:50 +08:00
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 (
2022-10-17 09:49:17 +08:00
'Refs cannot be used in Server Components, nor passed to Client Components.' ,
2020-10-09 08:02:23 +08:00
) ;
}
2022-10-17 09:49:17 +08:00
if ( _ _DEV _ _ ) {
jsxPropsParents . set ( props , type ) ;
2022-11-01 05:29:01 +08:00
if ( typeof props . children === 'object' && props . children !== null ) {
2022-10-17 09:49:17 +08:00
jsxChildrenParents . set ( props . children , type ) ;
}
}
2019-11-07 01:48:34 +08:00
if ( typeof type === 'function' ) {
2023-01-28 09:08:26 +08:00
if ( isClientReference ( type ) ) {
2022-10-17 09:49:17 +08:00
// This is a reference to a Client Component.
2022-08-25 19:47:38 +08:00
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 ) ;
2022-10-15 03:09:33 +08:00
const result = type ( props ) ;
if (
typeof result === 'object' &&
result !== null &&
typeof result . then === 'function'
) {
2023-02-02 01:56:53 +08:00
// When the return value is in children position we can resolve it immediately,
// to its value without a wrapper if it's synchronously available.
const thenable : Thenable < any > = result ;
if ( thenable . status === 'fulfilled' ) {
return thenable . value ;
}
// TODO: Once we accept Promises as children on the client, we can just return
// the thenable here.
2022-10-15 03:09:33 +08:00
return createLazyWrapperAroundWakeable ( result ) ;
}
return result ;
2019-11-07 01:48:34 +08:00
} 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.
2022-10-17 09:49:17 +08:00
// Same as if a Server Component has a key.
2020-11-11 11:56:50 +08:00
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' ) {
2023-01-28 09:08:26 +08:00
if ( isClientReference ( type ) ) {
2022-10-17 09:49:17 +08:00
// 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 (
2023-02-02 01:56:53 +08:00
request ,
2022-09-08 10:27:04 +08:00
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 (
2023-02-02 01:56:53 +08:00
request ,
2022-09-08 10:27:04 +08:00
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 (
2022-10-17 09:49:17 +08:00
` 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 ) {
2023-04-22 11:45:51 +08:00
request . flushScheduled = request . destination !== null ;
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 ,
2023-03-09 12:45:55 +08:00
model : ReactClientValue ,
2022-03-08 20:55:32 +08:00
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 ++ ;
2023-02-10 06:07:39 +08:00
const task : 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 ) ;
}
2023-02-02 01:56:53 +08:00
function serializeLazyID ( id : number ) : string {
2023-02-01 01:41:36 +08:00
return '$L' + id . toString ( 16 ) ;
}
2023-02-02 01:56:53 +08:00
function serializePromiseID ( id : number ) : string {
return '$@' + id . toString ( 16 ) ;
}
2023-02-10 08:45:05 +08:00
function serializeServerReferenceID ( id : number ) : string {
return '$F' + id . toString ( 16 ) ;
}
2023-02-01 01:41:36 +08:00
function serializeSymbolReference ( name : string ) : string {
return '$S' + name ;
}
function serializeProviderReference ( name : string ) : string {
return '$P' + name ;
2020-10-31 04:02:03 +08:00
}
2023-04-15 00:28:48 +08:00
function serializeNumber ( number : number ) : string | number {
if ( Number . isFinite ( number ) ) {
if ( number === 0 && 1 / number === - Infinity ) {
return '$-0' ;
} else {
return number ;
}
} else {
if ( number === Infinity ) {
return '$Infinity' ;
} else if ( number === - Infinity ) {
return '$-Infinity' ;
} else {
return '$NaN' ;
}
}
}
2023-03-10 05:18:52 +08:00
function serializeUndefined ( ) : string {
return '$undefined' ;
}
2023-04-19 11:52:03 +08:00
function serializeDateFromDateJSON ( dateJSON : string ) : string {
// JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
// We need only tack on a $D prefix.
return '$D' + dateJSON ;
}
2023-03-30 00:23:43 +08:00
function serializeBigInt ( n : bigint ) : string {
return '$n' + n . toString ( 10 ) ;
}
2023-01-28 09:08:26 +08:00
function serializeClientReference (
2022-08-25 19:47:38 +08:00
request : Request ,
2023-03-09 12:45:55 +08:00
parent :
| { + [ key : string | number ] : ReactClientValue }
| $ReadOnlyArray < ReactClientValue > ,
2022-08-25 19:47:38 +08:00
key : string ,
2023-02-10 08:45:05 +08:00
clientReference : ClientReference < any > ,
2022-08-25 19:47:38 +08:00
) : string {
2023-02-10 08:45:05 +08:00
const clientReferenceKey : ClientReferenceKey =
getClientReferenceKey ( clientReference ) ;
const writtenClientReferences = request . writtenClientReferences ;
const existingId = writtenClientReferences . get ( clientReferenceKey ) ;
2022-08-25 19:47:38 +08:00
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.
2023-02-02 01:56:53 +08:00
return serializeLazyID ( existingId ) ;
2022-08-25 19:47:38 +08:00
}
return serializeByValueID ( existingId ) ;
}
try {
2023-02-10 08:45:05 +08:00
const clientReferenceMetadata : ClientReferenceMetadata =
resolveClientReferenceMetadata ( request . bundlerConfig , clientReference ) ;
2022-08-25 19:47:38 +08:00
request . pendingChunks ++ ;
2023-02-10 08:45:05 +08:00
const importId = request . nextChunkId ++ ;
emitImportChunk ( request , importId , clientReferenceMetadata ) ;
writtenClientReferences . set ( clientReferenceKey , importId ) ;
2022-08-25 19:47:38 +08:00
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.
2023-02-10 08:45:05 +08:00
return serializeLazyID ( importId ) ;
2022-08-25 19:47:38 +08:00
}
2023-02-10 08:45:05 +08:00
return serializeByValueID ( importId ) ;
2022-08-25 19:47:38 +08:00
} 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 ) ;
}
}
2023-02-10 08:45:05 +08:00
function serializeServerReference (
request : Request ,
2023-03-09 12:45:55 +08:00
parent :
| { + [ key : string | number ] : ReactClientValue }
| $ReadOnlyArray < ReactClientValue > ,
2023-02-10 08:45:05 +08:00
key : string ,
serverReference : ServerReference < any > ,
) : string {
const writtenServerReferences = request . writtenServerReferences ;
const existingId = writtenServerReferences . get ( serverReference ) ;
if ( existingId !== undefined ) {
return serializeServerReferenceID ( existingId ) ;
}
2023-03-09 12:45:55 +08:00
const bound : null | Array < any > = getServerReferenceBoundArguments (
request . bundlerConfig ,
serverReference ,
) ;
2023-03-05 08:51:34 +08:00
const serverReferenceMetadata : {
id : ServerReferenceId ,
2023-03-09 12:45:55 +08:00
bound : null | Promise < Array < any >> ,
} = {
id : getServerReferenceId ( request . bundlerConfig , serverReference ) ,
bound : bound ? Promise . resolve ( bound ) : null ,
} ;
2023-02-10 08:45:05 +08:00
request . pendingChunks ++ ;
const metadataId = request . nextChunkId ++ ;
// We assume that this object doesn't suspend.
const processedChunk = processModelChunk (
request ,
metadataId ,
serverReferenceMetadata ,
) ;
request . completedJSONChunks . push ( processedChunk ) ;
writtenServerReferences . set ( serverReference , metadataId ) ;
return serializeServerReferenceID ( metadataId ) ;
}
2019-11-07 01:48:34 +08:00
function escapeStringValue ( value : string ) : string {
2023-02-01 01:41:36 +08:00
if ( value [ 0 ] === '$' ) {
2023-03-11 00:36:15 +08:00
// We need to escape $ prefixed strings since we use those to encode
2020-03-24 08:53:45 +08:00
// references to IDs and as special symbol values.
2019-11-07 01:48:34 +08:00
return '$' + value ;
} else {
return value ;
}
}
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 ,
2023-03-09 12:45:55 +08:00
parent :
| { + [ key : string | number ] : ReactClientValue }
| $ReadOnlyArray < ReactClientValue > ,
2020-03-24 08:53:45 +08:00
key : string ,
2023-03-09 12:45:55 +08:00
value : ReactClientValue ,
2019-11-07 01:48:34 +08:00
) : ReactJSONValue {
2023-04-19 11:52:03 +08:00
// Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
2020-10-09 02:11:15 +08:00
if ( _ _DEV _ _ ) {
2023-03-27 19:43:04 +08:00
// $FlowFixMe[incompatible-use]
2020-10-09 02:11:15 +08:00
const originalValue = parent [ key ] ;
2023-04-19 11:52:03 +08:00
if (
typeof originalValue === 'object' &&
originalValue !== value &&
! ( originalValue instanceof Date )
) {
2022-10-17 09:49:17 +08:00
if ( objectName ( originalValue ) !== 'Object' ) {
const jsxParentType = jsxChildrenParents . get ( parent ) ;
if ( typeof jsxParentType === 'string' ) {
console . error (
'%s objects cannot be rendered as text children. Try formatting it using toString().%s' ,
objectName ( originalValue ) ,
describeObjectForErrorMessage ( parent , key ) ,
) ;
} else {
console . error (
'Only plain objects can be passed to Client Components from Server Components. ' +
'%s objects are not supported.%s' ,
objectName ( originalValue ) ,
describeObjectForErrorMessage ( parent , key ) ,
) ;
}
} else {
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.%s' ,
describeObjectForErrorMessage ( parent , key ) ,
) ;
}
2020-10-09 02:11:15 +08:00
}
}
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 ] &&
2023-03-09 12:45:55 +08:00
( parent [ 1 ] : any ) . $$typeof === REACT _PROVIDER _TYPE &&
2022-03-08 20:55:32 +08:00
key === '3'
) {
insideContextProps = value ;
} else if ( insideContextProps === parent && key === 'value' ) {
isInsideContextValue = true ;
} else if ( insideContextProps === parent && key === 'children' ) {
isInsideContextValue = false ;
}
}
2022-10-17 09:49:17 +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 ) ;
2022-10-17 09:49:17 +08:00
// Attempt to render the Server Component.
2022-03-11 03:18:54 +08:00
value = attemptResolveElement (
2023-02-02 01:56:53 +08:00
request ,
2022-03-11 03:18:54 +08:00
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 ;
}
}
2022-10-29 05:46:03 +08:00
} catch ( thrownValue ) {
const x =
thrownValue === SuspenseException
? // This is a special type of exception used for Suspense. For historical
// reasons, the rest of the Suspense implementation expects the thrown
// value to be a thenable, because before `use` existed that was the
// (unstable) API for suspending. This implementation detail can change
// later, once we deprecate the old API in favor of `use`.
getSuspendedThenable ( )
: thrownValue ;
// $FlowFixMe[method-unbinding]
2020-04-04 05:58:02 +08:00
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
newTask . thenableState = getThenableStateAfterSuspending ( ) ;
2023-02-02 01:56:53 +08:00
return serializeLazyID ( newTask . id ) ;
2020-04-04 05:58:02 +08:00
} else {
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 ) ;
}
2023-02-02 01:56:53 +08:00
return serializeLazyID ( 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' ) {
2023-01-28 09:08:26 +08:00
if ( isClientReference ( value ) ) {
return serializeClientReference ( request , parent , key , ( value : any ) ) ;
2023-02-10 08:45:05 +08:00
// $FlowFixMe[method-unbinding]
2023-02-02 01:56:53 +08:00
} else if ( typeof value . then === 'function' ) {
// We assume that any object with a .then property is a "Thenable" type,
// or a Promise type. Either of which can be represented by a Promise.
const promiseId = serializeThenable ( request , ( value : any ) ) ;
return serializePromiseID ( promiseId ) ;
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
}
2023-03-06 02:18:54 +08:00
if ( ! isArray ( value ) ) {
const iteratorFn = getIteratorFn ( value ) ;
if ( iteratorFn ) {
return Array . from ( ( value : 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 (
2022-10-17 09:49:17 +08:00
'Only plain objects can be passed to Client Components from Server Components. ' +
'%s objects are not supported.%s' ,
2020-10-09 02:11:15 +08:00
objectName ( value ) ,
2022-10-17 09:49:17 +08:00
describeObjectForErrorMessage ( parent , key ) ,
2020-10-09 02:11:15 +08:00
) ;
} else if ( ! isSimpleObject ( value ) ) {
console . error (
2022-10-17 09:49:17 +08:00
'Only plain objects can be passed to Client Components from Server Components. ' +
'Classes or other objects with methods are not supported.%s' ,
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 (
2022-10-17 09:49:17 +08:00
'Only plain objects can be passed to Client Components from Server Components. ' +
'Objects with symbol properties like %s are not supported.%s' ,
2020-10-09 02:11:15 +08:00
symbols [ 0 ] . description ,
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
2023-03-27 19:43:04 +08:00
// $FlowFixMe[incompatible-return]
2020-10-09 02:11:15 +08:00
return value ;
}
if ( typeof value === 'string' ) {
2023-04-19 11:52:03 +08:00
// TODO: Maybe too clever. If we support URL there's no similar trick.
if ( value [ value . length - 1 ] === 'Z' ) {
// Possibly a Date, whose toJSON automatically calls toISOString
// $FlowFixMe[incompatible-use]
const originalValue = parent [ key ] ;
// $FlowFixMe[method-unbinding]
if ( originalValue instanceof Date ) {
return serializeDateFromDateJSON ( value ) ;
}
}
2020-10-09 02:11:15 +08:00
return escapeStringValue ( value ) ;
}
2023-04-15 00:28:48 +08:00
if ( typeof value === 'boolean' ) {
2020-10-09 02:11:15 +08:00
return value ;
}
2023-04-15 00:28:48 +08:00
if ( typeof value === 'number' ) {
return serializeNumber ( value ) ;
}
2023-03-10 05:18:52 +08:00
if ( typeof value === 'undefined' ) {
return serializeUndefined ( ) ;
}
2020-10-09 02:11:15 +08:00
if ( typeof value === 'function' ) {
2023-01-28 09:08:26 +08:00
if ( isClientReference ( value ) ) {
return serializeClientReference ( request , parent , key , ( value : any ) ) ;
2022-08-25 19:47:38 +08:00
}
2023-02-10 08:45:05 +08:00
if ( isServerReference ( value ) ) {
return serializeServerReference ( 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 (
2022-10-17 09:49:17 +08:00
'Event handlers cannot be passed to Client Component props.' +
describeObjectForErrorMessage ( parent , key ) +
'\nIf you need interactivity, consider converting part of this to a Client Component.' ,
2020-10-09 02:11:15 +08:00
) ;
} 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 (
2022-10-17 09:49:17 +08:00
'Functions cannot be passed directly to Client Components ' +
2023-02-10 08:45:05 +08:00
'unless you explicitly expose it by marking it with "use server".' +
2022-10-17 09:49:17 +08:00
describeObjectForErrorMessage ( parent , key ) ,
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 ) ;
}
2023-03-27 19:43:04 +08:00
// $FlowFixMe[incompatible-type] `description` might be undefined
2022-09-08 23:46:07 +08:00
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 (
2022-10-17 09:49:17 +08:00
'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( ${
2023-03-27 19:43:04 +08:00
// $FlowFixMe[incompatible-type] `description` might be undefined
2022-09-08 23:46:07 +08:00
value . description
2022-10-17 09:49:17 +08:00
} ) cannot be found among global symbols . ` +
describeObjectForErrorMessage ( parent , 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
) ;
}
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
}
if ( typeof value === 'bigint' ) {
2023-03-30 00:23:43 +08:00
return serializeBigInt ( value ) ;
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 (
2022-10-17 09:49:17 +08:00
` Type ${ typeof value } is not supported in Client Component props. ` +
describeObjectForErrorMessage ( parent , key ) ,
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 || '' ;
}
2023-01-31 21:25:05 +08:00
function getErrorMessageAndStackDev ( error : mixed ) : {
message : string ,
stack : string ,
} {
2022-09-24 04:19:29 +08:00
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
}
2023-02-10 08:45:05 +08:00
function emitImportChunk (
2020-10-30 08:57:31 +08:00
request : Request ,
id : number ,
2023-02-10 08:45:05 +08:00
clientReferenceMetadata : ClientReferenceMetadata ,
2020-10-30 08:57:31 +08:00
) : void {
2023-02-10 08:45:05 +08:00
const processedChunk = processImportChunk (
request ,
id ,
clientReferenceMetadata ,
) ;
request . completedImportChunks . push ( processedChunk ) ;
2020-10-30 08:57:31 +08:00
}
2023-04-22 11:45:51 +08:00
function emitHintChunk ( request : Request , code : string , model : HintModel ) : void {
const processedChunk = processHintChunk (
request ,
request . nextChunkId ++ ,
code ,
model ,
) ;
request . completedHintChunks . push ( processedChunk ) ;
}
2020-11-11 11:56:50 +08:00
function emitSymbolChunk ( request : Request , id : number , name : string ) : void {
2023-02-01 01:41:36 +08:00
const symbolReference = serializeSymbolReference ( name ) ;
const processedChunk = processReferenceChunk ( request , id , symbolReference ) ;
2023-02-10 08:45:05 +08:00
request . completedImportChunks . push ( processedChunk ) ;
2020-11-11 11:56:50 +08:00
}
2022-03-08 20:55:32 +08:00
function emitProviderChunk (
request : Request ,
id : number ,
contextName : string ,
) : void {
2023-02-01 01:41:36 +08:00
const contextReference = serializeProviderReference ( contextName ) ;
const processedChunk = processReferenceChunk ( request , id , contextReference ) ;
2022-03-08 20:55:32 +08:00
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 ;
2022-10-17 09:49:17 +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 (
2023-02-02 01:56:53 +08:00
request ,
2020-11-11 11:56:50 +08:00
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 (
2023-02-02 01:56:53 +08:00
request ,
2022-09-08 10:27:04 +08:00
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 ;
2022-10-29 05:46:03 +08:00
} catch ( thrownValue ) {
const x =
thrownValue === SuspenseException
? // This is a special type of exception used for Suspense. For historical
// reasons, the rest of the Suspense implementation expects the thrown
// value to be a thenable, because before `use` existed that was the
// (unstable) API for suspending. This implementation detail can change
// later, once we deprecate the old API in favor of `use`.
getSuspendedThenable ( )
: thrownValue ;
// $FlowFixMe[method-unbinding]
2019-11-07 01:48:34 +08:00
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
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
ReactCurrentDispatcher . current = HooksDispatcher ;
2023-04-22 11:45:51 +08:00
const prevRequest = currentRequest ;
currentRequest = request ;
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-06-01 05:53:32 +08:00
resetHooksForRequest ( ) ;
2023-04-22 11:45:51 +08:00
currentRequest = prevRequest ;
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.
2023-02-10 08:45:05 +08:00
const importsChunks = request . completedImportChunks ;
2019-11-07 01:48:34 +08:00
let i = 0 ;
2023-02-10 08:45:05 +08:00
for ( ; i < importsChunks . length ; i ++ ) {
2020-10-30 08:57:31 +08:00
request . pendingChunks -- ;
2023-02-10 08:45:05 +08:00
const chunk = importsChunks [ 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 ;
}
}
2023-02-10 08:45:05 +08:00
importsChunks . splice ( 0 , i ) ;
2023-04-22 11:45:51 +08:00
// Next comes hints.
const hintChunks = request . completedHintChunks ;
i = 0 ;
for ( ; i < hintChunks . length ; i ++ ) {
const chunk = hintChunks [ i ] ;
const keepWriting : boolean = writeChunkAndReturn ( destination , chunk ) ;
if ( ! keepWriting ) {
request . destination = null ;
i ++ ;
break ;
}
}
hintChunks . 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 ) ;
2023-04-22 11:45:51 +08:00
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 {
2023-04-22 11:45:51 +08:00
request . flushScheduled = false ;
2019-10-30 05:45:47 +08:00
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 {
2023-04-22 11:45:51 +08:00
request . flushScheduled = request . destination !== null ;
2022-10-23 13:06:58 +08:00
if ( supportsRequestStorage ) {
2023-04-22 11:45:51 +08:00
scheduleWork ( ( ) => requestStorage . run ( request , performWork , request ) ) ;
2022-10-23 13:06:58 +08:00
} else {
scheduleWork ( ( ) => performWork ( request ) ) ;
}
2019-10-30 05:45:47 +08:00
}
2023-04-22 11:45:51 +08:00
function enqueueFlush ( request : Request ) : void {
if (
request . flushScheduled === false &&
// If there are pinged tasks we are going to flush anyway after work completes
request . pingedTasks . length === 0 &&
// If there is no destination there is nothing we can flush to. A flush will
// happen when we start flowing again
request . destination !== null
) {
const destination = request . destination ;
request . flushScheduled = true ;
scheduleWork ( ( ) => flushCompletedChunks ( request , destination ) ) ;
}
}
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
}