From eb3a1a2b34bda45fb0bb8b37eb56758674f1ce5b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Nov 2021 11:50:46 -0600 Subject: [PATCH 1/5] Fix non-concurrent function _document --- packages/next/server/render.tsx | 33 ++++++++++-------- test/e2e/next-head/app/components/meta.js | 12 +++++++ test/e2e/next-head/app/pages/_document.js | 13 +++++++ test/e2e/next-head/app/pages/index.js | 15 +++++++++ test/e2e/next-head/index.test.ts | 41 +++++++++++++++++++++++ 5 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 test/e2e/next-head/app/components/meta.js create mode 100644 test/e2e/next-head/app/pages/_document.js create mode 100644 test/e2e/next-head/app/pages/index.js create mode 100644 test/e2e/next-head/index.test.ts diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 91fdb62bd369..d5673177ce02 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1148,24 +1148,29 @@ export async function renderToHTML( styles: docProps.styles, } } else { - const bodyResult = async () => { - const content = ( - - {ctx.err && ErrorDebug ? ( - - ) : ( - getWrappedApp( - - ) - )} - + const content = + ctx.err && ErrorDebug ? ( + + ) : ( + getWrappedApp( + + ) ) - return concurrentFeatures - ? process.browser + let bodyResult + + if (concurrentFeatures) { + bodyResult = async () => { + return process.browser ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) - : piperFromArray([ReactDOMServer.renderToString(content)]) + } + } else { + // for non-concurrent rendering we need to ensure App is rendered + // before _document so that updateHead is called/collected before + // rendering _document's head + const result = piperFromArray([ReactDOMServer.renderToString(content)]) + bodyResult = () => result } return { diff --git a/test/e2e/next-head/app/components/meta.js b/test/e2e/next-head/app/components/meta.js new file mode 100644 index 000000000000..3a121375fd3f --- /dev/null +++ b/test/e2e/next-head/app/components/meta.js @@ -0,0 +1,12 @@ +import Head from 'next/head' + +export function Meta(props) { + return ( + <> + + + + + + ) +} diff --git a/test/e2e/next-head/app/pages/_document.js b/test/e2e/next-head/app/pages/_document.js new file mode 100644 index 000000000000..7ee4a282756f --- /dev/null +++ b/test/e2e/next-head/app/pages/_document.js @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function MyDocument() { + return ( + + + +
+ + + + ) +} diff --git a/test/e2e/next-head/app/pages/index.js b/test/e2e/next-head/app/pages/index.js new file mode 100644 index 000000000000..c887c8738691 --- /dev/null +++ b/test/e2e/next-head/app/pages/index.js @@ -0,0 +1,15 @@ +import Head from 'next/head' +import { Meta } from '../components/meta' + +export default function Page(props) { + return ( + <> + + + + + +

index page

+ + ) +} diff --git a/test/e2e/next-head/index.test.ts b/test/e2e/next-head/index.test.ts new file mode 100644 index 000000000000..479a67a63bc7 --- /dev/null +++ b/test/e2e/next-head/index.test.ts @@ -0,0 +1,41 @@ +import { createNext, FileRef } from 'e2e-utils' +import { renderViaHTTP } from 'next-test-utils' +import cheerio from 'cheerio' +import webdriver from 'next-webdriver' +import { NextInstance } from 'test/lib/next-modes/base' +import { join } from 'path' + +describe('should set-up next', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'app/pages')), + components: new FileRef(join(__dirname, 'app/components')), + }, + }) + }) + afterAll(() => next.destroy()) + + it('should have correct head tags in initial document', async () => { + const html = await renderViaHTTP(next.url, '/') + const $ = cheerio.load(html) + + for (let i = 1; i < 5; i++) { + expect($(`meta[name="test-head-${i}"]`).attr()['content']).toBe('hello') + } + }) + + it('should have correct head tags after hydration', async () => { + const browser = await webdriver(next.url, '/') + + for (let i = 1; i < 5; i++) { + expect( + await browser + .elementByCss(`meta[name="test-head-${i}"]`) + .getAttribute('content') + ).toBe('hello') + } + }) +}) From 0c637558c4cddbdc41b869c398bab3cf21eb4bc6 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Nov 2021 12:01:19 -0600 Subject: [PATCH 2/5] ensure wrapper is added for non-concurrent --- packages/next/server/render.tsx | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index d5673177ce02..c77ef5085487 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1148,24 +1148,36 @@ export async function renderToHTML( styles: docProps.styles, } } else { - const content = - ctx.err && ErrorDebug ? ( - - ) : ( - getWrappedApp( - - ) - ) - let bodyResult if (concurrentFeatures) { bodyResult = async () => { + const content = + ctx.err && ErrorDebug ? ( + + ) : ( + getWrappedApp( + + ) + ) + return process.browser ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) } } else { + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) // for non-concurrent rendering we need to ensure App is rendered // before _document so that updateHead is called/collected before // rendering _document's head From d967c9920915feab5cfa14c225aff029da038327 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Nov 2021 12:04:22 -0600 Subject: [PATCH 3/5] ensure __next div is present --- packages/next/server/render.tsx | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index c77ef5085487..d6de774a554c 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1149,35 +1149,26 @@ export async function renderToHTML( } } else { let bodyResult + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) if (concurrentFeatures) { bodyResult = async () => { - const content = - ctx.err && ErrorDebug ? ( - - ) : ( - getWrappedApp( - - ) - ) - return process.browser ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) } } else { - const content = - ctx.err && ErrorDebug ? ( - - - - ) : ( - - {getWrappedApp( - - )} - - ) // for non-concurrent rendering we need to ensure App is rendered // before _document so that updateHead is called/collected before // rendering _document's head From 491dcf5de12c3c3c966d78cebfe36b89520f3410 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Nov 2021 12:18:42 -0600 Subject: [PATCH 4/5] lint-fix --- packages/next/server/render.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index d6de774a554c..0f29fc05f020 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1179,7 +1179,7 @@ export async function renderToHTML( return { bodyResult, documentElement: () => (Document as any)(), - useMainContent: (fn?: (content: JSX.Element) => JSX.Element) => { + useMainContent: (fn?: (_content: JSX.Element) => JSX.Element) => { if (fn) { appWrappers.push(fn) } From 3c256defa4d7ea7fcb339cc8827b0cbd0caaff88 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Nov 2021 12:58:42 -0600 Subject: [PATCH 5/5] Update previous test --- packages/next/server/render.tsx | 38 +++++++++++++------ .../{ => app}/lib/context.js | 0 .../app/next.config.js | 19 ++++++++++ .../app/package.json | 12 ++++++ .../{ => app}/pages/_document.js | 0 .../{ => app}/pages/index.js | 0 .../tests/index.test.js | 5 ++- 7 files changed, 60 insertions(+), 14 deletions(-) rename test/integration/document-functional-render-prop/{ => app}/lib/context.js (100%) create mode 100644 test/integration/document-functional-render-prop/app/next.config.js create mode 100644 test/integration/document-functional-render-prop/app/package.json rename test/integration/document-functional-render-prop/{ => app}/pages/_document.js (100%) rename test/integration/document-functional-render-prop/{ => app}/pages/index.js (100%) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 0f29fc05f020..c5af9cdc527e 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1149,26 +1149,40 @@ export async function renderToHTML( } } else { let bodyResult - const content = - ctx.err && ErrorDebug ? ( - - - - ) : ( - - {getWrappedApp( - - )} - - ) if (concurrentFeatures) { bodyResult = async () => { + // this must be called inside bodyResult so appWrappers is + // up to date when getWrappedApp is called + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) return process.browser ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) } } else { + const content = + ctx.err && ErrorDebug ? ( + + + + ) : ( + + {getWrappedApp( + + )} + + ) // for non-concurrent rendering we need to ensure App is rendered // before _document so that updateHead is called/collected before // rendering _document's head diff --git a/test/integration/document-functional-render-prop/lib/context.js b/test/integration/document-functional-render-prop/app/lib/context.js similarity index 100% rename from test/integration/document-functional-render-prop/lib/context.js rename to test/integration/document-functional-render-prop/app/lib/context.js diff --git a/test/integration/document-functional-render-prop/app/next.config.js b/test/integration/document-functional-render-prop/app/next.config.js new file mode 100644 index 000000000000..a866ec0085c1 --- /dev/null +++ b/test/integration/document-functional-render-prop/app/next.config.js @@ -0,0 +1,19 @@ +module.exports = { + experimental: { + reactRoot: true, + concurrentFeatures: true, + }, + webpack(config) { + const { alias } = config.resolve + // FIXME: resolving react/jsx-runtime https://github.com/facebook/react/issues/20235 + alias['react/jsx-dev-runtime'] = 'react/jsx-dev-runtime.js' + alias['react/jsx-runtime'] = 'react/jsx-runtime.js' + + // Use react 18 + alias['react'] = 'react-18' + alias['react-dom'] = 'react-dom-18' + alias['react-dom/server'] = 'react-dom-18/server' + + return config + }, +} diff --git a/test/integration/document-functional-render-prop/app/package.json b/test/integration/document-functional-render-prop/app/package.json new file mode 100644 index 000000000000..f9dafc993a79 --- /dev/null +++ b/test/integration/document-functional-render-prop/app/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "next": "node -r ../test/require-hook.js ../../../../packages/next/dist/bin/next", + "dev": "yarn next dev", + "build": "yarn next build", + "start": "yarn next start" + }, + "dependencies": { + "react": "*", + "react-dom": "*" + } +} diff --git a/test/integration/document-functional-render-prop/pages/_document.js b/test/integration/document-functional-render-prop/app/pages/_document.js similarity index 100% rename from test/integration/document-functional-render-prop/pages/_document.js rename to test/integration/document-functional-render-prop/app/pages/_document.js diff --git a/test/integration/document-functional-render-prop/pages/index.js b/test/integration/document-functional-render-prop/app/pages/index.js similarity index 100% rename from test/integration/document-functional-render-prop/pages/index.js rename to test/integration/document-functional-render-prop/app/pages/index.js diff --git a/test/integration/document-functional-render-prop/tests/index.test.js b/test/integration/document-functional-render-prop/tests/index.test.js index d7cb027d41f7..c843082feed9 100644 --- a/test/integration/document-functional-render-prop/tests/index.test.js +++ b/test/integration/document-functional-render-prop/tests/index.test.js @@ -3,7 +3,8 @@ import { join } from 'path' import { findPort, launchApp, killApp, renderViaHTTP } from 'next-test-utils' -const appDir = join(__dirname, '..') +const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')] +const appDir = join(__dirname, '../app') let appPort let app @@ -11,7 +12,7 @@ describe('Functional Custom Document', () => { describe('development mode', () => { beforeAll(async () => { appPort = await findPort() - app = await launchApp(appDir, appPort) + app = await launchApp(appDir, appPort, { nodeArgs }) }) afterAll(() => killApp(app))