Skip to content

Commit

Permalink
Resolve stream piper on complete shell for renderToReadableStream (#3…
Browse files Browse the repository at this point in the history
…1186)

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)
```
  • Loading branch information
huozhi authored Nov 12, 2021
1 parent 4551571 commit cf206a8
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 45 deletions.
1 change: 1 addition & 0 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export function finalizeEntrypoint({
type: 'assign',
},
runtime: MIDDLEWARE_SSR_RUNTIME_WEBPACK,
asyncChunks: false,
...entry,
}
return ssrMiddlewareEntry
Expand Down
28 changes: 14 additions & 14 deletions packages/next/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,27 @@ export default class MiddlewarePlugin {
if (!location) {
continue
}

const entryFiles = entrypoint
.getFiles()
.filter((file: string) => !file.endsWith('.hot-update.js'))

const files = ssrEntryInfo
? [
ssrEntryInfo.requireFlightManifest
? `server/${MIDDLEWARE_FLIGHT_MANIFEST}.js`
: 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) || [],
Expand Down
65 changes: 37 additions & 28 deletions packages/next/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/react-18/app/components/dynamic-hello.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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...')

Expand Down

0 comments on commit cf206a8

Please sign in to comment.