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:
parent
4551571615
commit
cf206a8392
5 changed files with 54 additions and 45 deletions
|
@ -268,6 +268,7 @@ export function finalizeEntrypoint({
|
|||
type: 'assign',
|
||||
},
|
||||
runtime: MIDDLEWARE_SSR_RUNTIME_WEBPACK,
|
||||
asyncChunks: false,
|
||||
...entry,
|
||||
}
|
||||
return ssrMiddlewareEntry
|
||||
|
|
|
@ -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) || [],
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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...')
|
||||
|
||||
|
|
Loading…
Reference in a new issue