diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 2c62c2d34243c60..12af4c6801ec18e 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -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 diff --git a/packages/next/server/node-polyfill-readable-stream.js b/packages/next/server/node-polyfill-readable-stream.js new file mode 100644 index 000000000000000..6c6c56fd0f4a74e --- /dev/null +++ b/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 +} diff --git a/packages/next/server/render-result.ts b/packages/next/server/render-result.ts index 925f6c1680d5b48..002fd05b0af5e9c 100644 --- a/packages/next/server/render-result.ts +++ b/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 } @@ -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() + } + } + ) }) } diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index ac70a6996431207..e5de330cd9eafde 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1,8 +1,6 @@ import { IncomingMessage, ServerResponse } from 'http' import { ParsedUrlQuery, stringify as stringifyQuery } from 'querystring' -import type { Writable as WritableType } from 'stream' import React from 'react' -import ReactDOMServer from 'react-dom/server' import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' import { StyleRegistry, createStyleRegistry } from 'styled-jsx' @@ -60,12 +58,10 @@ import { Redirect, } from '../lib/load-custom-routes' import { DomainLocale } from './config' -import RenderResult, { NodeWritablePiper } from './render-result' +import RenderResult, { ResultPiper } from './render-result' import isError from '../lib/is-error' import { readableStreamTee } from './web/utils' -let Writable: typeof import('stream').Writable -let Buffer: typeof import('buffer').Buffer let optimizeAmp: typeof import('./optimize-amp').default let getFontDefinitionFromManifest: typeof import('./font-utils').getFontDefinitionFromManifest let tryGetPreviewData: typeof import('./api-utils').tryGetPreviewData @@ -75,8 +71,7 @@ let postProcess: typeof import('../shared/lib/post-process').default const DOCTYPE = '' if (!process.browser) { - Writable = require('stream').Writable - Buffer = require('buffer').Buffer + require('./node-polyfill-readable-stream') optimizeAmp = require('./optimize-amp').default getFontDefinitionFromManifest = require('./font-utils').getFontDefinitionFromManifest @@ -1112,8 +1107,8 @@ export async function renderToHTML( serverComponentManifest ) const reader = stream.getReader() - const piper: NodeWritablePiper = (innerRes, next) => { - bufferedReadFromReadableStream(reader, (val) => innerRes.write(val)).then( + const piper: ResultPiper = (push, next) => { + bufferedReadFromReadableStream(reader, push).then( () => next(), (innerErr) => next(innerErr) ) @@ -1152,6 +1147,10 @@ export async function renderToHTML( return inAmpMode ? children :