Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up Document in preparation for streaming #28032

Merged
merged 10 commits into from Aug 13, 2021
48 changes: 17 additions & 31 deletions packages/next/pages/_document.tsx
@@ -1,14 +1,15 @@
import React, { Component, ReactElement, ReactNode, useContext } from 'react'
import flush from 'styled-jsx/server'
import {
AMP_RENDER_TARGET,
BODY_RENDER_TARGET,
OPTIMIZED_FONT_PROVIDERS,
} from '../shared/lib/constants'
import { DocumentContext as DocumentComponentContext } from '../shared/lib/document-context'
import {
DocumentContext,
DocumentInitialProps,
DocumentProps,
HtmlContext,
HtmlProps,
} from '../shared/lib/utils'
import { BuildManifest, getPageFiles } from '../server/get-page-files'
import { cleanAmpPath } from '../server/utils'
Expand Down Expand Up @@ -45,7 +46,7 @@ function getDocumentFiles(
}
}

function getPolyfillScripts(context: DocumentProps, props: OriginProps) {
function getPolyfillScripts(context: HtmlProps, props: OriginProps) {
// polyfills.js has to be rendered as nomodule without async
// It also has to be the first script to load
const {
Expand All @@ -71,7 +72,7 @@ function getPolyfillScripts(context: DocumentProps, props: OriginProps) {
))
}

function getPreNextScripts(context: DocumentProps, props: OriginProps) {
function getPreNextScripts(context: HtmlProps, props: OriginProps) {
const { scriptLoader, disableOptimizedLoading } = context

return (scriptLoader.beforeInteractive || []).map(
Expand All @@ -91,7 +92,7 @@ function getPreNextScripts(context: DocumentProps, props: OriginProps) {
}

function getDynamicChunks(
context: DocumentProps,
context: HtmlProps,
props: OriginProps,
files: DocumentFiles
) {
Expand Down Expand Up @@ -122,7 +123,7 @@ function getDynamicChunks(
}

function getScripts(
context: DocumentProps,
context: HtmlProps,
props: OriginProps,
files: DocumentFiles
) {
Expand Down Expand Up @@ -176,17 +177,6 @@ export default class Document<P = {}> extends Component<DocumentProps & P> {
return { html, head, styles }
}

static renderDocument<Y>(
DocumentComponent: new () => Document<Y>,
props: DocumentProps & Y
): React.ReactElement {
return (
<DocumentComponentContext.Provider value={props}>
<DocumentComponent {...props} />
</DocumentComponentContext.Provider>
)
}

render() {
return (
<Html>
Expand All @@ -206,9 +196,7 @@ export function Html(
HTMLHtmlElement
>
) {
const { inAmpMode, docComponentsRendered, locale } = useContext(
DocumentComponentContext
)
const { inAmpMode, docComponentsRendered, locale } = useContext(HtmlContext)

docComponentsRendered.Html = true

Expand All @@ -231,9 +219,9 @@ export class Head extends Component<
HTMLHeadElement
>
> {
static contextType = DocumentComponentContext
static contextType = HtmlContext

context!: React.ContextType<typeof DocumentComponentContext>
context!: React.ContextType<typeof HtmlContext>

getCssLinks(files: DocumentFiles): JSX.Element[] | null {
const {
Expand Down Expand Up @@ -738,20 +726,18 @@ export class Head extends Component<
}

export function Main() {
const { inAmpMode, html, docComponentsRendered } = useContext(
DocumentComponentContext
)
const { inAmpMode, docComponentsRendered } = useContext(HtmlContext)

docComponentsRendered.Main = true

if (inAmpMode) return <>{AMP_RENDER_TARGET}</>
return <div id="__next" dangerouslySetInnerHTML={{ __html: html }} />
if (inAmpMode) return <>{BODY_RENDER_TARGET}</>
return <div id="__next">{BODY_RENDER_TARGET}</div>
}

export class NextScript extends Component<OriginProps> {
static contextType = DocumentComponentContext
static contextType = HtmlContext

context!: React.ContextType<typeof DocumentComponentContext>
context!: React.ContextType<typeof HtmlContext>

// Source: https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
static safariNomoduleFix =
Expand All @@ -773,8 +759,8 @@ export class NextScript extends Component<OriginProps> {
return getPolyfillScripts(this.context, this.props)
}

static getInlineScriptSource(documentProps: Readonly<DocumentProps>): string {
const { __NEXT_DATA__ } = documentProps
static getInlineScriptSource(context: Readonly<HtmlProps>): string {
const { __NEXT_DATA__ } = context
try {
const data = JSON.stringify(__NEXT_DATA__)
return htmlEscapeJsonString(data)
Expand Down
112 changes: 58 additions & 54 deletions packages/next/server/render.tsx
Expand Up @@ -20,7 +20,7 @@ import { GetServerSideProps, GetStaticProps, PreviewData } from '../types'
import { isInAmpMode } from '../shared/lib/amp'
import { AmpStateContext } from '../shared/lib/amp-context'
import {
AMP_RENDER_TARGET,
BODY_RENDER_TARGET,
SERVER_PROPS_ID,
STATIC_PROPS_ID,
STATIC_STATUS_PAGES,
Expand All @@ -39,6 +39,7 @@ import {
DocumentInitialProps,
DocumentProps,
DocumentType,
HtmlContext,
getDisplayName,
isResSent,
loadGetInitialProps,
Expand Down Expand Up @@ -264,54 +265,58 @@ function renderDocument(
autoExport?: boolean
}
): string {
const htmlProps = {
__NEXT_DATA__: {
props, // The result of getInitialProps
page: pathname, // The rendered page
query, // querystring parsed / passed by the user
buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport, // If this is a page exported by `next export`
autoExport, // If this is an auto exported page
isFallback,
dynamicIds:
dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
gsp, // whether the page is getStaticProps
gssp, // whether the page is getServerSideProps
customServer, // whether the user is using a custom server
gip, // whether the page has getInitialProps
appGip, // whether the _app has getInitialProps
locale,
locales,
defaultLocale,
domainLocales,
isPreview,
},
buildManifest,
docComponentsRendered,
dangerousAsPath,
canonicalBase,
ampPath,
inAmpMode,
isDevelopment: !!dev,
hybridAmp,
dynamicImports,
assetPrefix,
headTags,
unstable_runtimeJS,
unstable_JsPreload,
devOnlyCacheBusterQueryString,
scriptLoader,
locale,
disableOptimizedLoading,
styles: docProps.styles,
head: docProps.head,
}
return (
'<!DOCTYPE html>' +
ReactDOMServer.renderToStaticMarkup(
<AmpStateContext.Provider value={ampState}>
{Document.renderDocument(Document, {
__NEXT_DATA__: {
props, // The result of getInitialProps
page: pathname, // The rendered page
query, // querystring parsed / passed by the user
buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport, // If this is a page exported by `next export`
autoExport, // If this is an auto exported page
isFallback,
dynamicIds:
dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
gsp, // whether the page is getStaticProps
gssp, // whether the page is getServerSideProps
customServer, // whether the user is using a custom server
gip, // whether the page has getInitialProps
appGip, // whether the _app has getInitialProps
locale,
locales,
defaultLocale,
domainLocales,
isPreview,
},
buildManifest,
docComponentsRendered,
dangerousAsPath,
canonicalBase,
ampPath,
inAmpMode,
isDevelopment: !!dev,
hybridAmp,
dynamicImports,
assetPrefix,
headTags,
unstable_runtimeJS,
unstable_JsPreload,
devOnlyCacheBusterQueryString,
scriptLoader,
locale,
disableOptimizedLoading,
...docProps,
})}
<HtmlContext.Provider value={htmlProps}>
<Document {...htmlProps} {...docProps} />
</HtmlContext.Provider>
</AmpStateContext.Provider>
)
)
Expand Down Expand Up @@ -1155,16 +1160,15 @@ export async function renderToHTML(
}
}

if (inAmpMode && html) {
// inject HTML to AMP_RENDER_TARGET to allow rendering
// directly to body in AMP mode
const ampRenderIndex = html.indexOf(AMP_RENDER_TARGET)
html =
html.substring(0, ampRenderIndex) +
`<!-- __NEXT_DATA__ -->${docProps.html}` +
html.substring(ampRenderIndex + AMP_RENDER_TARGET.length)
html = await optimizeAmp(html, renderOpts.ampOptimizerConfig)
const bodyRenderIdx = html.indexOf(BODY_RENDER_TARGET)
html =
html.substring(0, bodyRenderIdx) +
(inAmpMode ? '<!-- __NEXT_DATA__ -->' : '') +
docProps.html +
html.substring(bodyRenderIdx + BODY_RENDER_TARGET.length)

if (inAmpMode) {
html = await optimizeAmp(html, renderOpts.ampOptimizerConfig)
if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
await renderOpts.ampValidator(html, pathname)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next/shared/lib/constants.ts
Expand Up @@ -21,7 +21,7 @@ export const BLOCKED_PAGES = ['/_document', '/_app', '/_error']
export const CLIENT_PUBLIC_FILES_PATH = 'public'
export const CLIENT_STATIC_FILES_PATH = 'static'
export const CLIENT_STATIC_FILES_RUNTIME = 'runtime'
export const AMP_RENDER_TARGET = '__NEXT_AMP_RENDER_TARGET__'
export const BODY_RENDER_TARGET = '__NEXT_BODY_RENDER_TARGET__'
export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
// static/runtime/main.js
export const CLIENT_STATIC_FILES_RUNTIME_MAIN = `main`
Expand Down
8 changes: 0 additions & 8 deletions packages/next/shared/lib/document-context.ts

This file was deleted.

19 changes: 12 additions & 7 deletions packages/next/shared/lib/utils.ts
Expand Up @@ -8,6 +8,7 @@ import type { NextRouter } from './router/router'
import type { ParsedUrlQuery } from 'querystring'
import type { PreviewData } from 'next/types'
import type { UrlObject } from 'url'
import { createContext } from 'react'

export type NextComponentType<
C extends BaseContext = NextPageContext,
Expand All @@ -26,12 +27,7 @@ export type DocumentType = NextComponentType<
DocumentContext,
DocumentInitialProps,
DocumentProps
> & {
renderDocument(
Document: DocumentType,
props: DocumentProps
): React.ReactElement
}
>

export type AppType = NextComponentType<
AppContextType,
Expand Down Expand Up @@ -188,7 +184,9 @@ export type DocumentInitialProps = RenderPageResult & {
styles?: React.ReactElement[] | React.ReactFragment
}

export type DocumentProps = DocumentInitialProps & {
export type DocumentProps = DocumentInitialProps & HtmlProps

export type HtmlProps = {
__NEXT_DATA__: NEXT_DATA
dangerousAsPath: string
docComponentsRendered: {
Expand All @@ -212,6 +210,8 @@ export type DocumentProps = DocumentInitialProps & {
scriptLoader: { afterInteractive?: string[]; beforeInteractive?: any[] }
locale?: string
disableOptimizedLoading?: boolean
styles?: React.ReactElement[] | React.ReactFragment
head?: Array<JSX.Element | null>
}

/**
Expand Down Expand Up @@ -432,3 +432,8 @@ export const ST =
typeof performance.measure === 'function'

export class DecodeError extends Error {}

export const HtmlContext = createContext<HtmlProps>(null as any)
if (process.env.NODE_ENV !== 'production') {
HtmlContext.displayName = 'HtmlContext'
}