Resolve stream piper on complete shell for renderToReadableStream (#31186)

1. Align `renderToReadableStream` with `renderToNodeStream`, resolve promise of `NodeWritablePiper` only when `onCompleteShell` is called.
2. update webpack to disable chunk loading for web runtime

Item 1 is the preparation for middleware-ssr-loader. Then we can do the following there

```js
try {
   result = await renderToHTML(page)
} catch (e) {
   result = await renderToHTML(errorPage)
}
result.pipe(renderResult)
```
This commit is contained in:
Jiachi Liu 2021-11-12 01:00:54 +01:00 committed by GitHub
parent 4551571615
commit cf206a8392
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 45 deletions

View file

@ -268,6 +268,7 @@ export function finalizeEntrypoint({
type: 'assign',
},
runtime: MIDDLEWARE_SSR_RUNTIME_WEBPACK,
asyncChunks: false,
...entry,
}
return ssrMiddlewareEntry

View file

@ -75,6 +75,11 @@ export default class MiddlewarePlugin {
if (!location) {
continue
}
const entryFiles = entrypoint
.getFiles()
.filter((file: string) => !file.endsWith('.hot-update.js'))
const files = ssrEntryInfo
? [
ssrEntryInfo.requireFlightManifest
@ -82,20 +87,15 @@ export default class MiddlewarePlugin {
: null,
`server/${MIDDLEWARE_BUILD_MANIFEST}.js`,
`server/${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js`,
...entrypoint.getFiles().map((file) => 'server/' + file),
]
.filter(nonNullable)
.filter((file: string) => !file.endsWith('.hot-update.js'))
: entrypoint
.getFiles()
.filter((file: string) => !file.endsWith('.hot-update.js'))
.map((file: string) =>
// we need to use the unminified version of the webpack runtime,
// remove if we do start minifying middleware chunks
file.startsWith('static/chunks/webpack-')
? file.replace('webpack-', 'webpack-middleware-')
: file
)
...entryFiles.map((file) => 'server/' + file),
].filter(nonNullable)
: entryFiles.map((file: string) =>
// we need to use the unminified version of the webpack runtime,
// remove if we do start minifying middleware chunks
file.startsWith('static/chunks/webpack-')
? file.replace('webpack-', 'webpack-middleware-')
: file
)
middlewareManifest.middleware[location] = {
env: envPerRoute.get(entrypoint.name) || [],

View file

@ -1469,39 +1469,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 = () => {
if (resolved) return
resolved = true
const piper: NodeWritablePiper = (res, next) => {
const streamReader: ReadableStreamDefaultReader = reader
const decoder = new TextDecoder()
const process = async () => {
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()
},
})
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 {

View file

@ -4,7 +4,7 @@ import dynamic from 'next/dynamic'
let ssr
const suspense = false
const Hello = dynamic(() => import(/* webpackMode: "eager" */ './hello'), {
const Hello = dynamic(() => import('./hello'), {
ssr,
suspense,
})

View file

@ -162,8 +162,7 @@ describe('concurrentFeatures - dev', () => {
await killApp(context.server)
})
// TODO: re-enabled test when update webpack with chunkLoading support
it.skip('should support React.lazy and dynamic imports', async () => {
it('should support React.lazy and dynamic imports', async () => {
const html = await renderViaHTTP(context.appPort, '/dynamic-imports')
expect(html).toContain('loading...')