Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve stream piper on complete shell for renderToReadableStream #31186

Merged
merged 3 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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