Skip to content

Commit

Permalink
resolve piper on complete shell
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Nov 8, 2021
1 parent 5c4e2fa commit ae47cd5
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 50 deletions.
1 change: 1 addition & 0 deletions packages/next/build/entries.ts
Expand Up @@ -162,6 +162,7 @@ export function createEntrypoints(
page,
absoluteAppPath: pages['/_app'],
absoluteDocumentPath: pages['/_document'],
absoluteErrorPath: pages['/500'] || pages['/_error'],
absolutePagePath,
isServerComponent: isFlight,
buildId,
Expand Down
Expand Up @@ -5,6 +5,7 @@ export default async function middlewareRSCLoader(this: any) {
absolutePagePath,
absoluteAppPath,
absoluteDocumentPath,
absoluteErrorPath,
basePath,
isServerComponent: isServerComponentQuery,
assetPrefix,
Expand All @@ -14,13 +15,15 @@ export default async function middlewareRSCLoader(this: any) {
const isServerComponent = isServerComponentQuery === 'true'
const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath)
const stringifiedAbsoluteAppPath = stringifyRequest(this, absoluteAppPath)
const stringifiedAbsoluteErrorPath = stringifyRequest(this, absoluteErrorPath)
const stringifiedAbsoluteDocumentPath = stringifyRequest(
this,
absoluteDocumentPath
)

let appDefinition = `const App = require(${stringifiedAbsoluteAppPath}).default`
let documentDefinition = `const Document = require(${stringifiedAbsoluteDocumentPath}).default`
const appDefinition = `const App = require(${stringifiedAbsoluteAppPath}).default`
const documentDefinition = `const Document = require(${stringifiedAbsoluteDocumentPath}).default`
const errorDefinition = `const ErrorPage = require(${stringifiedAbsoluteErrorPath}).default`

const transformed = `
import { adapter } from 'next/dist/server/web/adapter'
Expand All @@ -40,6 +43,7 @@ export default async function middlewareRSCLoader(this: any) {
${appDefinition}
${documentDefinition}
${errorDefinition}
const {
default: Page,
Expand Down Expand Up @@ -134,6 +138,7 @@ export default async function middlewareRSCLoader(this: any) {
: ''
}
const req = { url: pathname }
const renderOpts = {
Component,
pageConfig: config || {},
Expand Down Expand Up @@ -162,32 +167,36 @@ export default async function middlewareRSCLoader(this: any) {
const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()
let result
let statusCode = 200
try {
const result = await renderToHTML(
{ url: pathname },
result = await renderToHTML(
req,
{},
pathname,
query,
renderOpts
)
result.pipe({
write: str => writer.write(encoder.encode(str)),
end: () => writer.close()
})
.catch(() => writer.close())
} catch (err) {
return new Response(
(err || 'An error occurred while rendering ' + pathname + '.').toString(),
{
status: 500,
headers: { 'x-middleware-ssr': '1' }
}
statusCode = 500
const errorRes = { statusCode, err }
result = await renderToHTML(
req,
errorRes,
pathname,
query,
{ ...renderOpts, Component: ErrorPage }
)
}
result.pipe({
write: str => writer.write(encoder.encode(str)),
end: () => writer.close()
})
return new Response(transformStream.readable, {
headers: { 'x-middleware-ssr': '1' }
headers: { 'x-middleware-ssr': '1' },
status: statusCode
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/next/server/dev/hot-reloader.ts
Expand Up @@ -528,6 +528,7 @@ export default class HotReloader {
page,
absoluteAppPath: this.pagesMapping['/_app'],
absoluteDocumentPath: this.pagesMapping['/_document'],
absoluteErrorPath: this.pagesMapping['/_error'],
absolutePagePath,
isServerComponent,
buildId: this.buildId,
Expand Down
70 changes: 37 additions & 33 deletions packages/next/server/render.tsx
Expand Up @@ -1433,44 +1433,48 @@ function renderToNodeStream(

function renderToReadableStream(
element: React.ReactElement
): NodeWritablePiper {
return (res, next) => {
let bufferedString = ''
let shellCompleted = false

const readable = (ReactDOMServer as any).renderToReadableStream(element, {
onCompleteShell() {
shellCompleted = true
if (bufferedString) {
res.write(bufferedString)
bufferedString = ''
}
},
})
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) {
): Promise<NodeWritablePiper> {
return new Promise((resolve, reject) => {
let reader: any = null
let resolved = false
const doResolve = (getReader: () => any) => {
if (resolved) return
resolved = true
const piper: NodeWritablePiper = (res, next) => {
const process = async () => {
const reader: ReadableStreamDefaultReader = getReader()
const decoder = new TextDecoder()
reader.read().then(({ done, value }) => {
if (!done) {
const s =
typeof value === 'string' ? value : decoder.decode(value)
res.write(s)
process()
} else {
bufferedString += s
next()
}
process()
} else {
next()
}
},
(err: any) => {
next(err)
})
}
)
process()
}
resolve(piper)
}
process()
}

const readable = (ReactDOMServer as any).renderToReadableStream(element, {
onError(err: Error) {
if (!resolved) {
resolved = true
reject(err)
}
},
onCompleteShell() {
doResolve(() => reader)
},
})
// 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

0 comments on commit ae47cd5

Please sign in to comment.