Skip to content

Commit

Permalink
fix(stream-ssr): Cancel the timeout when the react stream has finished (
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 committed Oct 20, 2023
1 parent c44a524 commit daaa199
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 23 deletions.
7 changes: 7 additions & 0 deletions packages/vite/src/streaming/createReactStreamingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export const createReactStreamingHandler = async (
},
{
waitForAllReady: isSeoCrawler,
onError: (err) => {
if (!isProd && viteDevServer) {
viteDevServer.ssrFixStacktrace(err)
}

console.error(err)
},
}
)

Expand Down
60 changes: 37 additions & 23 deletions packages/vite/src/streaming/streamHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import path from 'node:path'

import React from 'react'

import type {
RenderToReadableStreamOptions,
ReactDOMServerReadableStream,
} from 'react-dom/server'

import type { TagDescriptor } from '@redwoodjs/web'
// @TODO (ESM), use exports field. Cannot import from web because of index exports
import {
Expand All @@ -10,6 +15,7 @@ import {
} from '@redwoodjs/web/dist/components/ServerInject'

import { createBufferedTransformStream } from './transforms/bufferedTransform'
import { createTimeoutTransform } from './transforms/cancelTimeoutTransform'
import { createServerInjectionTransform } from './transforms/serverInjectionTransform'

interface RenderToStreamArgs {
Expand All @@ -24,6 +30,7 @@ interface RenderToStreamArgs {

interface StreamOptions {
waitForAllReady?: boolean
onError?: (err: Error) => void
}

export async function reactRenderToStreamResponse(
Expand Down Expand Up @@ -55,13 +62,22 @@ export async function reactRenderToStreamResponse(
const { injectionState, injectToPage } = createInjector()

// This makes it safe for us to inject at any point in the stream
const bufferedTransformStream = createBufferedTransformStream()
const bufferTransform = createBufferedTransformStream()

// This is a transformer stream, that will inject all things called with useServerInsertedHtml
const serverInjectionTransformer = createServerInjectionTransform({
const serverInjectionTransform = createServerInjectionTransform({
injectionState,
})

// Timeout after 10 seconds
// @TODO make this configurable
const controller = new AbortController()
const timeoutHandle = setTimeout(() => {
controller.abort()
}, 10000)

const timeoutTransform = createTimeoutTransform(timeoutHandle)

// @ts-expect-error Something in React's packages mean types dont come through
// Possible that we need to upgrade the @types/* packages
const { renderToReadableStream } = await import('react-dom/server.edge')
Expand Down Expand Up @@ -96,29 +112,27 @@ export async function reactRenderToStreamResponse(
// This gets set if there are errors inside Suspense boundaries
let didErrorOutsideShell = false

// Timeout after 10 seconds
// @TODO make this configurable
const controller = new AbortController()
setTimeout(() => {
controller.abort()
}, 10000)
// Assign here so we get types, the dynamic import messes types
const renderToStreamOptions: RenderToReadableStreamOptions = {
...bootstrapOptions,
signal: controller.signal,
onError: (err: any) => {
didErrorOutsideShell = true
console.error('🔻 Caught error outside shell')
streamOptions.onError?.(err)
},
}

const reactStream = await renderToReadableStream(
renderRoot(currentPathName),
{
...bootstrapOptions,
signal: controller.signal,
onError: (err: any) => {
didErrorOutsideShell = true
console.error('🔻 Caught error outside shell')
console.error(err)
},
}
)
const reactStream: ReactDOMServerReadableStream =
await renderToReadableStream(
renderRoot(currentPathName),
renderToStreamOptions
)

const output = reactStream
.pipeThrough(bufferedTransformStream)
.pipeThrough(serverInjectionTransformer)
.pipeThrough(bufferTransform)
.pipeThrough(serverInjectionTransform)
.pipeThrough(timeoutTransform)

if (waitForAllReady) {
await reactStream.allReady
Expand All @@ -130,7 +144,7 @@ export async function reactRenderToStreamResponse(
})
} catch (e) {
console.error('🔻 Failed to render shell')
console.error(e)
streamOptions.onError?.(e as Error)

// @TODO Asking for clarification from React team. Their documentation on this is incomplete I think.
// Having the Document (and bootstrap scripts) here allows client to recover from errors in the shell
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function createTimeoutTransform(timeoutHandle: NodeJS.Timeout) {
return new TransformStream({
flush() {
clearTimeout(timeoutHandle)
},
})
}

0 comments on commit daaa199

Please sign in to comment.