From f8a8952793561c9c6ddcd2751f343ee5fc8c3a4c Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 10:15:42 +0100 Subject: [PATCH 1/6] remove the special _document file --- packages/next/build/entries.ts | 6 +---- packages/next/build/index.ts | 1 - packages/next/pages/_document-concurrent.tsx | 16 ------------ packages/next/pages/_document.tsx | 4 +++ packages/next/server/dev/hot-reloader.ts | 1 - packages/next/server/render.tsx | 26 +++++++++++++++----- 6 files changed, 25 insertions(+), 29 deletions(-) delete mode 100644 packages/next/pages/_document-concurrent.tsx diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 3d0047efc9b..1ac79e357ed 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -39,11 +39,9 @@ export function createPagesMapping( { isDev, hasServerComponents, - globalRuntime, }: { isDev: boolean hasServerComponents: boolean - globalRuntime?: 'nodejs' | 'edge' } ): PagesMapping { const previousPages: PagesMapping = {} @@ -83,7 +81,6 @@ export function createPagesMapping( // we alias these in development and allow webpack to // allow falling back to the correct source file so // that HMR can work properly when a file is added/removed - const documentPage = `_document${globalRuntime ? '-concurrent' : ''}` if (isDev) { pages['/_app'] = `${PAGES_DIR_ALIAS}/_app` pages['/_error'] = `${PAGES_DIR_ALIAS}/_error` @@ -91,8 +88,7 @@ export function createPagesMapping( } else { pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app' pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error' - pages['/_document'] = - pages['/_document'] || `next/dist/pages/${documentPage}` + pages['/_document'] = pages['/_document'] || `next/dist/pages/_document` } return pages } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 25cf163cb6e..b0952a2b276 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -298,7 +298,6 @@ export default async function build( createPagesMapping(pagePaths, config.pageExtensions, { isDev: false, hasServerComponents, - globalRuntime: runtime, }) ) diff --git a/packages/next/pages/_document-concurrent.tsx b/packages/next/pages/_document-concurrent.tsx deleted file mode 100644 index 8275efd253f..00000000000 --- a/packages/next/pages/_document-concurrent.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// Default Document for streaming features - -import React from 'react' -import { Html, Head, Main, NextScript } from './_document' - -export default function Document() { - return ( - - - -
- - - - ) -} diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index 309ae6321dc..e4686083e16 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -184,6 +184,10 @@ export default class Document

extends Component { } } +// Add a speical property to the built-in `Document` component so later we can +// identify if a user customized `Document` is used or not. +;(Document as any).__next_internal_document = true + export function Html( props: React.DetailedHTMLProps< React.HtmlHTMLAttributes, diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index d5e56dbf4f7..bcd58454ab5 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -321,7 +321,6 @@ export default class HotReloader { this.config.pageExtensions, { isDev: true, - globalRuntime: this.runtime, hasServerComponents: this.hasServerComponents, } ) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 679fcde4f62..120831426a4 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -431,7 +431,6 @@ export async function renderToHTML( dev = false, ampPath = '', App, - Document, pageConfig = {}, buildManifest, fontManifest, @@ -457,6 +456,7 @@ export async function renderToHTML( const hasConcurrentFeatures = !!runtime + let Document = renderOpts.Document const OriginalComponent = renderOpts.Component // We don't need to opt-into the flight inlining logic if the page isn't a RSC. @@ -1234,14 +1234,28 @@ export async function renderToHTML( */ const generateStaticHTML = supportsDynamicHTML !== true const renderDocument = async () => { + // For `Document`, there are two cases that we don't support: + // 1. Using `Document.getInitialProps` in the Edge runtime. + // 2. Using the class component `Document` with concurrent features. + + const isBuiltinDocument = !!(Document as any).__next_internal_document + if (runtime === 'edge' && Document.getInitialProps) { - // In the Edge runtime, Document.getInitialProps isn't supported. - throw new Error( - '`getInitialProps` in Document component is not supported with the Edge Runtime.' - ) + // In the Edge runtime, `Document.getInitialProps` isn't supported. + // We throw an error here if it's customized. + if (!isBuiltinDocument) { + throw new Error( + '`getInitialProps` in Document component is not supported with the Edge Runtime.' + ) + } + } + + // We make it a function component to enable streaming. + if (hasConcurrentFeatures && isBuiltinDocument) { + Document = (Document as any).render } - if (!runtime && Document.getInitialProps) { + if (!hasConcurrentFeatures && Document.getInitialProps) { const renderPage: RenderPage = ( options: ComponentsEnhancer = {} ): RenderPageResult | Promise => { From 506d99c504c06be75f78ecf7b9e23b1c2cec6282 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 14:59:46 +0100 Subject: [PATCH 2/6] bug fix --- packages/next/build/webpack-config.ts | 4 +--- packages/next/pages/_document.tsx | 2 +- packages/next/server/base-server.ts | 6 +++++- packages/next/server/dev/next-dev-server.ts | 4 +--- packages/next/server/load-components.ts | 10 ++-------- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 813dbfbdae5..300857be7bd 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -558,9 +558,7 @@ export default async function getBaseWebpackConfig( prev.push(path.join(pagesDir, `_document.${ext}`)) return prev }, [] as string[]), - `next/dist/pages/_document${ - hasConcurrentFeatures ? '-concurrent' : '' - }.js`, + `next/dist/pages/_document.js`, ] } diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index e4686083e16..fd0dcae93b9 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -171,7 +171,7 @@ export default class Document

extends Component { return ctx.defaultGetInitialProps(ctx) } - render() { + static render() { return ( diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 2168e8d163d..01323e9abae 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1176,6 +1176,10 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') + const isSupportedDocument = + (components.Document as any).__next_internal_document || + typeof components.Document?.getInitialProps !== 'function' + // Disable dynamic HTML in cases that we know it won't be generated, // so that we can continue generating a cache key when possible. opts.supportsDynamicHTML = @@ -1183,7 +1187,7 @@ export default abstract class Server { !isLikeServerless && !isBotRequest && !query.amp && - typeof components.Document?.getInitialProps !== 'function' + isSupportedDocument } const defaultLocale = isSSG diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 66ad197086a..15c58725143 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -949,9 +949,7 @@ export default class DevServer extends Server { // Build the error page to ensure the fallback is built too. // TODO: See if this can be moved into hotReloader or removed. await this.hotReloader!.ensurePage('/_error') - return await loadDefaultErrorComponents(this.distDir, { - hasConcurrentFeatures: !!this.renderOpts.runtime, - }) + return await loadDefaultErrorComponents(this.distDir) } protected setImmutableAssetCacheControl(res: BaseNextResponse): void { diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 8ccafc60a75..ae247d9ea66 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -39,14 +39,8 @@ export type LoadComponentsReturnType = { AppMod: any } -export async function loadDefaultErrorComponents( - distDir: string, - { hasConcurrentFeatures }: { hasConcurrentFeatures: boolean } -) { - const Document = interopDefault( - require(`next/dist/pages/_document` + - (hasConcurrentFeatures ? '-concurrent' : '')) - ) +export async function loadDefaultErrorComponents(distDir: string) { + const Document = interopDefault(require('next/dist/pages/_document')) const AppMod = require('next/dist/pages/_app') const App = interopDefault(AppMod) const ComponentMod = require('next/dist/pages/_error') From eb6f002989a9dc56496fd0cac46ac04da8dda3fd Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 15:25:50 +0100 Subject: [PATCH 3/6] fix revert making instance method static --- packages/next/pages/_document.tsx | 12 ++++++++++-- packages/next/server/base-server.ts | 2 +- packages/next/server/render.tsx | 10 ++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index fd0dcae93b9..d9b1c840f67 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -171,7 +171,7 @@ export default class Document

extends Component { return ctx.defaultGetInitialProps(ctx) } - static render() { + render() { return ( @@ -186,7 +186,15 @@ export default class Document

extends Component { // Add a speical property to the built-in `Document` component so later we can // identify if a user customized `Document` is used or not. -;(Document as any).__next_internal_document = true +;(Document as any).__next_internal_document = () => ( + + + +

+ + + +) export function Html( props: React.DetailedHTMLProps< diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 01323e9abae..757be04adaf 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1177,7 +1177,7 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') const isSupportedDocument = - (components.Document as any).__next_internal_document || + !!(components.Document as any).__next_internal_document || typeof components.Document?.getInitialProps !== 'function' // Disable dynamic HTML in cases that we know it won't be generated, diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 120831426a4..8b9eae2fbb6 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1238,12 +1238,14 @@ export async function renderToHTML( // 1. Using `Document.getInitialProps` in the Edge runtime. // 2. Using the class component `Document` with concurrent features. - const isBuiltinDocument = !!(Document as any).__next_internal_document + const builtinDocument = (Document as any).__next_internal_document as + | typeof Document + | undefined if (runtime === 'edge' && Document.getInitialProps) { // In the Edge runtime, `Document.getInitialProps` isn't supported. // We throw an error here if it's customized. - if (!isBuiltinDocument) { + if (!builtinDocument) { throw new Error( '`getInitialProps` in Document component is not supported with the Edge Runtime.' ) @@ -1251,8 +1253,8 @@ export async function renderToHTML( } // We make it a function component to enable streaming. - if (hasConcurrentFeatures && isBuiltinDocument) { - Document = (Document as any).render + if (hasConcurrentFeatures && builtinDocument) { + Document = builtinDocument } if (!hasConcurrentFeatures && Document.getInitialProps) { From 4465c530a820bd044779143da9cbb1cd757d8a96 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 15:46:29 +0100 Subject: [PATCH 4/6] fix test; refine document to have name --- packages/next/pages/_document.tsx | 21 ++++++++++++--------- packages/next/server/base-server.ts | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index d9b1c840f67..43ae26369f8 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -186,15 +186,18 @@ export default class Document

extends Component { // Add a speical property to the built-in `Document` component so later we can // identify if a user customized `Document` is used or not. -;(Document as any).__next_internal_document = () => ( - - - -

- - - -) +;(Document as any).__next_internal_document = + function InternalFunctionDocument() { + return ( + + + +
+ + + + ) + } export function Html( props: React.DetailedHTMLProps< diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 757be04adaf..1146f3e58c0 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1177,7 +1177,7 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') const isSupportedDocument = - !!(components.Document as any).__next_internal_document || + (components.Document as any)?.__next_internal_document || typeof components.Document?.getInitialProps !== 'function' // Disable dynamic HTML in cases that we know it won't be generated, From 9c7716493b7550dc62d5e90b0277f5b5c461212b Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 18:15:33 +0100 Subject: [PATCH 5/6] fix condition --- packages/next/server/base-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 1146f3e58c0..92f555cabff 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1177,7 +1177,7 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') const isSupportedDocument = - (components.Document as any)?.__next_internal_document || + !!(components.Document as any)?.__next_internal_document || typeof components.Document?.getInitialProps !== 'function' // Disable dynamic HTML in cases that we know it won't be generated, From 54da6d0ef6fbfb2a260c884ccdcfc9f37bfc39a4 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 19:36:52 +0100 Subject: [PATCH 6/6] fix dynamic html condition --- packages/next/server/base-server.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 92f555cabff..56f0dedf8a1 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1177,8 +1177,11 @@ export default abstract class Server { if (opts.supportsDynamicHTML === true) { const isBotRequest = isBot(req.headers['user-agent'] || '') const isSupportedDocument = - !!(components.Document as any)?.__next_internal_document || - typeof components.Document?.getInitialProps !== 'function' + typeof components.Document?.getInitialProps !== 'function' || + // When concurrent features is enabled, the built-in `Document` + // component also supports dynamic HTML. + (this.renderOpts.reactRoot && + !!(components.Document as any)?.__next_internal_document) // Disable dynamic HTML in cases that we know it won't be generated, // so that we can continue generating a cache key when possible.