@@ -867,7 +867,6 @@ async function generateDynamicFlightRenderResultWithStagesInDev(
867867 consoleAsyncStorage . run (
868868 { dim : true } ,
869869 spawnStaticShellValidationInDev ,
870- resolveValidation ,
871870 staticChunks ,
872871 runtimeChunks ,
873872 dynamicChunks ,
@@ -2670,7 +2669,6 @@ async function renderToStream(
26702669 // We only have a Prerender environment for projects opted into cacheComponents
26712670 cacheComponents
26722671 ) {
2673- const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
26742672 let debugChannel : DebugChannelPair | undefined
26752673 const getPayload = async (
26762674 // eslint-disable-next-line @typescript-eslint/no-shadow
@@ -2695,12 +2693,6 @@ async function renderToStream(
26952693 payload . _bypassCachesInDev = createElement ( WarnForBypassCachesInDev , {
26962694 route : workStore . route ,
26972695 } )
2698- } else {
2699- // Placing the validation outlet in the payload is safe
2700- // even if we end up discarding a render and restarting,
2701- // because we're not going to wait for the stream to complete,
2702- // so leaving the validation unresolved is fine.
2703- payload . _validation = validationOutlet
27042696 }
27052697
27062698 return payload
@@ -2743,7 +2735,6 @@ async function renderToStream(
27432735 consoleAsyncStorage . run (
27442736 { dim : true } ,
27452737 spawnStaticShellValidationInDev ,
2746- resolveValidation ,
27472738 staticChunks ,
27482739 runtimeChunks ,
27492740 dynamicChunks ,
@@ -3622,14 +3613,59 @@ function createValidationOutlet() {
36223613 return [ resolveValidation ! , outlet ] as const
36233614}
36243615
3616+ /**
3617+ * Logs the given messages, and sends the error instances to the browser as an
3618+ * RSC stream, where they can be deserialized and logged (or otherwise presented
3619+ * in the devtools), while leveraging React's capabilities to not only
3620+ * source-map the stack frames (via findSourceMapURL), but also create virtual
3621+ * server modules that allow users to inspect the server source code in the
3622+ * browser.
3623+ */
3624+ async function logMessagesAndSendErrorsToBrowser (
3625+ messages : unknown [ ] ,
3626+ ctx : AppRenderContext
3627+ ) : Promise < void > {
3628+ const {
3629+ clientReferenceManifest,
3630+ componentMod : ComponentMod ,
3631+ htmlRequestId,
3632+ renderOpts,
3633+ } = ctx
3634+
3635+ const { sendErrorsToBrowser } = renderOpts
3636+
3637+ const errors : Error [ ] = [ ]
3638+ for ( const message of messages ) {
3639+ console . error ( message )
3640+ if ( message instanceof Error ) {
3641+ errors . push ( message )
3642+ }
3643+ }
3644+
3645+ if ( errors . length > 0 ) {
3646+ if ( ! sendErrorsToBrowser ) {
3647+ throw new InvariantError (
3648+ 'Expected `sendErrorsToBrowser` to be defined in renderOpts.'
3649+ )
3650+ }
3651+
3652+ const errorsRscStream = ComponentMod . renderToReadableStream (
3653+ errors ,
3654+ clientReferenceManifest . clientModules ,
3655+ { filterStackFrame }
3656+ )
3657+
3658+ sendErrorsToBrowser ( errorsRscStream , htmlRequestId )
3659+ }
3660+ }
3661+
36253662/**
36263663 * This function is a fork of prerenderToStream cacheComponents branch.
36273664 * While it doesn't return a stream we want it to have identical
36283665 * prerender semantics to prerenderToStream and should update it
36293666 * in conjunction with any changes to that function.
36303667 */
36313668async function spawnStaticShellValidationInDev (
3632- resolveValidation : ( validatingElement : ReactNode ) => void ,
36333669 staticServerChunks : Array < Uint8Array > ,
36343670 runtimeServerChunks : Array < Uint8Array > ,
36353671 dynamicServerChunks : Array < Uint8Array > ,
@@ -3664,36 +3700,19 @@ async function spawnStaticShellValidationInDev(
36643700 NEXT_HMR_REFRESH_HASH_COOKIE
36653701 ) ?. value
36663702
3667- const { createElement } = ComponentMod
3668-
36693703 // We don't need to continue the prerender process if we already
36703704 // detected invalid dynamic usage in the initial prerender phase.
36713705 const { invalidDynamicUsageError } = workStore
36723706 if ( invalidDynamicUsageError ) {
3673- resolveValidation (
3674- createElement ( ReportValidation , {
3675- messages : [ invalidDynamicUsageError ] ,
3676- } )
3677- )
3678- return
3707+ return logMessagesAndSendErrorsToBrowser ( [ invalidDynamicUsageError ] , ctx )
36793708 }
36803709
36813710 if ( staticInterruptReason ) {
3682- resolveValidation (
3683- createElement ( ReportValidation , {
3684- messages : [ staticInterruptReason ] ,
3685- } )
3686- )
3687- return
3711+ return logMessagesAndSendErrorsToBrowser ( [ staticInterruptReason ] , ctx )
36883712 }
36893713
36903714 if ( runtimeInterruptReason ) {
3691- resolveValidation (
3692- createElement ( ReportValidation , {
3693- messages : [ runtimeInterruptReason ] ,
3694- } )
3695- )
3696- return
3715+ return logMessagesAndSendErrorsToBrowser ( [ runtimeInterruptReason ] , ctx )
36973716 }
36983717
36993718 // First we warmup SSR with the runtime chunks. This ensures that when we do
@@ -3732,10 +3751,7 @@ async function spawnStaticShellValidationInDev(
37323751 if ( runtimeResult . length > 0 ) {
37333752 // We have something to report from the runtime validation
37343753 // We can skip the static validation
3735- resolveValidation (
3736- createElement ( ReportValidation , { messages : runtimeResult } )
3737- )
3738- return
3754+ return logMessagesAndSendErrorsToBrowser ( runtimeResult , ctx )
37393755 }
37403756
37413757 const staticResult = await validateStagedShell (
@@ -3752,11 +3768,7 @@ async function spawnStaticShellValidationInDev(
37523768 trackDynamicHoleInStaticShell
37533769 )
37543770
3755- // We always resolve with whatever results we got. It might be empty in which
3756- // case there will be nothing to report once
3757- resolveValidation ( createElement ( ReportValidation , { messages : staticResult } ) )
3758-
3759- return
3771+ return logMessagesAndSendErrorsToBrowser ( staticResult , ctx )
37603772}
37613773
37623774async function warmupModuleCacheForRuntimeValidationInDev (
@@ -4051,13 +4063,6 @@ async function validateStagedShell(
40514063 }
40524064}
40534065
4054- function ReportValidation ( { messages } : { messages : Array < unknown > } ) : null {
4055- for ( const message of messages ) {
4056- console . error ( message )
4057- }
4058- return null
4059- }
4060-
40614066type PrerenderToStreamResult = {
40624067 stream : ReadableStream < Uint8Array >
40634068 digestErrorsMap : Map < string , DigestedError >
0 commit comments