From 0307ba0d1d6e4a39d64438bf2cf4f2def2c25e58 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 5 Nov 2021 22:51:10 +0100 Subject: [PATCH] Optimize the SSR middleware runtime size (#30906) By conditionally importing `react-dom/server` under the web runtime and reusing `renderToReadableStream` instead of `renderToStaticMarkup`, we can get rid of the legacy browser React DOM server from the runtime. ~Furthermore we can make the build target `es6` for the SSR middleware, and make some code paths tree-shakable (done in another PR).~ Together this makes the runtime ~32kb smaller. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- packages/next/build/webpack-config.ts | 8 ++++++ packages/next/server/render.tsx | 36 ++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index eab85535f3556a3..71174550d9c0f25 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -676,6 +676,14 @@ export default async function getBaseWebpackConfig( false, } : {}), + + ...(webServerRuntime + ? { + 'react-dom/server': dev + ? 'react-dom/cjs/react-dom-server.browser.development' + : 'react-dom/cjs/react-dom-server.browser.production.min', + } + : {}), }, ...(targetWeb ? { diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 8059b75c172c0e2..53fa578287d73ab 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -2,7 +2,7 @@ import { IncomingMessage, ServerResponse } from 'http' import { ParsedUrlQuery } from 'querystring' import type { Writable as WritableType } from 'stream' import React from 'react' -import * as ReactDOMServer from 'react-dom/server' +import ReactDOMServer from 'react-dom/server' import { StyleRegistry, createStyleRegistry } from 'styled-jsx' import { UnwrapPromise } from '../lib/coalesced-function' import { @@ -950,7 +950,12 @@ export async function renderToHTML( */ const generateStaticHTML = supportsDynamicHTML !== true const renderDocument = async () => { - if (Document.getInitialProps) { + if (process.browser && Document.getInitialProps) { + throw new Error( + '`getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.' + ) + } + if (!process.browser && Document.getInitialProps) { const renderPage: RenderPage = ( options: ComponentsEnhancer = {} ): RenderPageResult | Promise => { @@ -1120,7 +1125,8 @@ export async function renderToHTML( styles: documentResult.styles, useMaybeDeferContent, } - const documentHTML = ReactDOMServer.renderToStaticMarkup( + + const document = ( {documentResult.documentElement(htmlProps)} @@ -1128,6 +1134,30 @@ export async function renderToHTML( ) + let documentHTML: string + if (process.browser) { + // There is no `renderToStaticMarkup` exposed in the web environment, use + // blocking `renderToReadableStream` to get the similar result. + let result = '' + const readable = (ReactDOMServer as any).renderToReadableStream(document, { + onError: (e: any) => { + throw e + }, + }) + const reader = readable.getReader() + const decoder = new TextDecoder() + while (true) { + const { done, value } = await reader.read() + if (done) { + break + } + result += typeof value === 'string' ? value : decoder.decode(value) + } + documentHTML = result + } else { + documentHTML = ReactDOMServer.renderToStaticMarkup(document) + } + if (process.env.NODE_ENV !== 'production') { const nonRenderedComponents = [] const expectedDocComponents = ['Main', 'Head', 'NextScript', 'Html']