Skip to content

Commit

Permalink
Use react-dom/server.browser in Node.js (vercel#33950)
Browse files Browse the repository at this point in the history
Instead of branching rendering based on Node.js and browser/web runtimes, we should just use the web version for now, which can run as-is on versions >=16.5.0 of Node.js, polyfilling `ReadableStream` on older versions when necessary.

There are a few potential downsides to this, as React is less able to optimize flushing and execution. We can revisit that in the future though if desired.
  • Loading branch information
devknoll authored and natew committed Feb 16, 2022
1 parent 5cecd10 commit 7a6f5a3
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 286 deletions.
8 changes: 0 additions & 8 deletions packages/next/build/webpack-config.ts
Expand Up @@ -610,14 +610,6 @@ export default async function getBaseWebpackConfig(
}
: {}),

...(webServerRuntime
? {
'react-dom/server': dev
? 'react-dom/cjs/react-dom-server.browser.development'
: 'react-dom/cjs/react-dom-server.browser.production.min',
}
: {}),

setimmediate: 'next/dist/compiled/setimmediate',
},
...(targetWeb
Expand Down
6 changes: 6 additions & 0 deletions packages/next/server/node-polyfill-readable-stream.js
@@ -0,0 +1,6 @@
import { ReadableStream } from './web/sandbox/readable-stream'

// Polyfill ReadableStream in the Node.js environment
if (!global.ReadableStream) {
global.ReadableStream = ReadableStream
}
38 changes: 32 additions & 6 deletions packages/next/server/render-result.ts
@@ -1,15 +1,14 @@
import type { ServerResponse } from 'http'
import type { Writable } from 'stream'

export type NodeWritablePiper = (
res: Writable,
export type ResultPiper = (
push: (chunks: Uint8Array[]) => void,
next: (err?: Error) => void
) => void

export default class RenderResult {
_result: string | NodeWritablePiper
_result: string | ResultPiper

constructor(response: string | NodeWritablePiper) {
constructor(response: string | ResultPiper) {
this._result = response
}

Expand All @@ -29,8 +28,35 @@ export default class RenderResult {
)
}
const response = this._result
const flush =
typeof (res as any).flush === 'function'
? () => (res as any).flush()
: () => {}

return new Promise((resolve, reject) => {
response(res, (err) => (err ? reject(err) : resolve()))
let fatalError = false
response(
(chunks) => {
// The state of the stream is non-deterministic after
// writing, so any error becomes fatal.
fatalError = true
res.cork()
chunks.forEach((chunk) => res.write(chunk))
res.uncork()
flush()
},
(err) => {
if (err) {
if (fatalError) {
res.destroy(err)
}
reject(err)
} else {
res.end()
resolve()
}
}
)
})
}

Expand Down

0 comments on commit 7a6f5a3

Please sign in to comment.