Skip to content

Commit

Permalink
Resolve stream piper on complete shell for renderToReadableStream
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Nov 9, 2021
1 parent 960298b commit a810b8b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 39 deletions.
65 changes: 37 additions & 28 deletions packages/next/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1468,39 +1468,48 @@ function renderToNodeStream(

function renderToReadableStream(
element: React.ReactElement
): NodeWritablePiper {
return (res, next) => {
let bufferedString = ''
let shellCompleted = false
): Promise<NodeWritablePiper> {
return new Promise((resolve, reject) => {
let reader: any = null
let resolved = false
const doResolve = (getStreamReader: () => any) => {
if (resolved) return
resolved = true
const piper: NodeWritablePiper = (res, next) => {
const process = async () => {
const streamReader: ReadableStreamDefaultReader = getStreamReader()
const decoder = new TextDecoder()
streamReader.read().then(({ done, value }) => {
if (!done) {
const s =
typeof value === 'string' ? value : decoder.decode(value)
res.write(s)
process()
} else {
next()
}
})
}
process()
}
resolve(piper)
}

const readable = (ReactDOMServer as any).renderToReadableStream(element, {
onCompleteShell() {
shellCompleted = true
if (bufferedString) {
res.write(bufferedString)
bufferedString = ''
onError(err: Error) {
if (!resolved) {
resolved = true
reject(err)
}
},
onCompleteShell() {
doResolve(() => reader)
},
})
const reader = readable.getReader()
const decoder = new TextDecoder()
const process = () => {
reader.read().then(({ done, value }: any) => {
if (!done) {
const s = typeof value === 'string' ? value : decoder.decode(value)
if (shellCompleted) {
res.write(s)
} else {
bufferedString += s
}
process()
} else {
next()
}
})
}
process()
}
// Start reader and lock stream immediately to consume readable,
// Otherwise the bytes before `onCompleteShell` will be missed.
reader = readable.getReader()
})
}

function chainPipers(pipers: NodeWritablePiper[]): NodeWritablePiper {
Expand Down
23 changes: 13 additions & 10 deletions test/integration/react-18/app/pages/suspense/no-preload.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { Suspense } from 'react'
import dynamic from 'next/dynamic'

const Bar = dynamic(() => import('../../components/bar'), {
suspense: true,
// Explicitly declare loaded modules.
// For suspense cases, they'll be ignored.
// For loadable component cases, they'll be handled
loadableGenerated: {
modules: ['../../components/bar'],
webpack: [require.resolveWeak('../../components/bar')],
},
})
const Bar = dynamic(
() => import(/* webpackMode: "eager" */ '../../components/bar'),
{
suspense: true,
// Explicitly declare loaded modules.
// For suspense cases, they'll be ignored.
// For loadable component cases, they'll be handled
loadableGenerated: {
modules: ['../../components/bar'],
webpack: [require.resolveWeak('../../components/bar')],
},
}
)

export default function NoPreload() {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { lazy, Suspense } from 'react'

const Foo = lazy(() => import('../components/foo.client'))
// TODO: fix code-splitting chunk loading scripting in web runtime.
const Foo = lazy(() =>
import(/* webpackMode: "eager" */ '../components/foo.client')
)

export default function Page() {
return (
Expand Down

0 comments on commit a810b8b

Please sign in to comment.