From c1e9aa25094931c7fa932ed8cd08da8a3100d5de Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 15:54:36 +0200 Subject: [PATCH 1/9] wip --- packages/next/build/swc/options.js | 2 +- packages/next/server/render.tsx | 29 ++++++++++++-- .../basic/styled-components.test.ts | 13 +++++++ .../basic/styled-components/pages/index.js | 38 ------------------- .../react-18/app/pages/_document.js | 0 5 files changed, 39 insertions(+), 43 deletions(-) create mode 100644 test/integration/react-18/app/pages/_document.js diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 33ed7b9d00eaa72..dd98795412ecf2c 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -104,7 +104,7 @@ function getBaseSWCOptions({ reactRemoveProperties: nextConfig?.compiler?.reactRemoveProperties, modularizeImports: nextConfig?.experimental?.modularizeImports, relay: nextConfig?.compiler?.relay, - emotion: getEmotionOptions(nextConfig, development), + // emotion: getEmotionOptions(nextConfig, development), } } diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index fd045c7dcb6f8b6..f725df302673fef 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1269,11 +1269,11 @@ export async function renderToHTML( } // We make it a function component to enable streaming. - if (hasConcurrentFeatures && builtinDocument) { - Document = builtinDocument + if (hasConcurrentFeatures && builtinDocument && isServerComponent) { + // Document = builtinDocument } - if (!hasConcurrentFeatures && Document.getInitialProps) { + async function documentInitialProps() { const renderPage: RenderPage = ( options: ComponentsEnhancer = {} ): RenderPageResult | Promise => { @@ -1323,6 +1323,14 @@ export async function renderToHTML( throw new Error(message) } + return { docProps, documentCtx } + } + + if (!hasConcurrentFeatures && Document.getInitialProps) { + const res = await documentInitialProps() + if (!res) return null + const { docProps, documentCtx } = res + return { bodyResult: (suffix: string) => streamFromArray([docProps.html, suffix]), @@ -1410,9 +1418,22 @@ export async function renderToHTML( const styles = jsxStyleRegistry.styles() jsxStyleRegistry.flush() + const docRes = + isServerComponent || process.browser ? {} : await documentInitialProps() + if (docRes === null) return null + + const documentElement = () => { + if (isServerComponent || process.browser) { + return (Document as any)() + } + + const { docProps } = docRes as any + return + } + return { bodyResult, - documentElement: () => (Document as any)(), + documentElement, head, headTags: [], styles, diff --git a/test/development/basic/styled-components.test.ts b/test/development/basic/styled-components.test.ts index c17cc8f7be609d5..7c136f7877c7a59 100644 --- a/test/development/basic/styled-components.test.ts +++ b/test/development/basic/styled-components.test.ts @@ -17,6 +17,8 @@ describe('styled-components SWC transform', () => { }, dependencies: { 'styled-components': '5.3.3', + react: 'latest', + 'react-dom': 'latest', }, }) }) @@ -34,6 +36,7 @@ describe('styled-components SWC transform', () => { }) return foundLog } + it('should not have hydration mismatch with styled-components transform enabled', async () => { let browser try { @@ -56,4 +59,14 @@ describe('styled-components SWC transform', () => { } } }) + + it.only('should render the page', async () => { + const browser = await webdriver(next.appPort, '/') + + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('#btn')).color` + ) + ).toBe('white') + }) }) diff --git a/test/development/basic/styled-components/pages/index.js b/test/development/basic/styled-components/pages/index.js index 31bd1226e76891b..e69de29bb2d1d64 100644 --- a/test/development/basic/styled-components/pages/index.js +++ b/test/development/basic/styled-components/pages/index.js @@ -1,38 +0,0 @@ -import styled, { css } from 'styled-components' -const Button = styled.a` - /* This renders the buttons above... Edit me! */ - display: inline-block; - border-radius: 3px; - padding: 0.5rem 0; - margin: 0.5rem 1rem; - width: 11rem; - background: transparent; - color: white; - border: 2px solid white; - - /* The GitHub button is a primary button - * edit this to target it specifically! */ - ${(props) => - props.primary && - css` - background: white; - color: black; - `} -` - -export default function Home() { - return ( -
- - - -
- ) -} diff --git a/test/integration/react-18/app/pages/_document.js b/test/integration/react-18/app/pages/_document.js new file mode 100644 index 000000000000000..e69de29bb2d1d64 From 00ca236b2a03e80c6b258494485d4d3784cc482a Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 16:01:46 +0200 Subject: [PATCH 2/9] update test --- packages/next/build/swc/options.js | 2 +- .../basic/styled-components.test.ts | 2 +- .../basic/styled-components/next.config.js | 16 +++++++- .../basic/styled-components/pages/index.js | 40 +++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index dd98795412ecf2c..33ed7b9d00eaa72 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -104,7 +104,7 @@ function getBaseSWCOptions({ reactRemoveProperties: nextConfig?.compiler?.reactRemoveProperties, modularizeImports: nextConfig?.experimental?.modularizeImports, relay: nextConfig?.compiler?.relay, - // emotion: getEmotionOptions(nextConfig, development), + emotion: getEmotionOptions(nextConfig, development), } } diff --git a/test/development/basic/styled-components.test.ts b/test/development/basic/styled-components.test.ts index 7c136f7877c7a59..bd1be8072a64155 100644 --- a/test/development/basic/styled-components.test.ts +++ b/test/development/basic/styled-components.test.ts @@ -60,7 +60,7 @@ describe('styled-components SWC transform', () => { } }) - it.only('should render the page', async () => { + it('should render the page with correct styles', async () => { const browser = await webdriver(next.appPort, '/') expect( diff --git a/test/development/basic/styled-components/next.config.js b/test/development/basic/styled-components/next.config.js index 91f693fda5258b9..b03e60f7ad06dbd 100644 --- a/test/development/basic/styled-components/next.config.js +++ b/test/development/basic/styled-components/next.config.js @@ -1,5 +1,17 @@ -module.exports = { +const path = require('path') + +let withReact18 = (config) => config + +try { + // only used when running inside of the monorepo not when isolated + withReact18 = require(path.join( + __dirname, + '../../../integration/react-18/test/with-react-18' + )) +} catch (_) {} + +module.exports = withReact18({ compiler: { styledComponents: true, }, -} +}) diff --git a/test/development/basic/styled-components/pages/index.js b/test/development/basic/styled-components/pages/index.js index e69de29bb2d1d64..31c5716fbf10b95 100644 --- a/test/development/basic/styled-components/pages/index.js +++ b/test/development/basic/styled-components/pages/index.js @@ -0,0 +1,40 @@ +import styled, { css } from 'styled-components' +const Button = styled.a` + /* This renders the buttons above... Edit me! */ + display: inline-block; + border-radius: 3px; + padding: 0.5rem 0; + margin: 0.5rem 1rem; + width: 11rem; + background: transparent; + color: white; + border: 2px solid white; + + /* The GitHub button is a primary button + * edit this to target it specifically! */ + ${(props) => + props.primary && + css` + background: white; + color: black; + `} +` + +export default function Home() { + return ( +
+ + + +
+ ) +} From 0dabce5f6e221ee41c66b603551774ab239ea8a0 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 16:11:38 +0200 Subject: [PATCH 3/9] fix _document logic for edge runtime and rsc --- packages/next/server/render.tsx | 96 ++++++++++++++++----------------- 1 file changed, 46 insertions(+), 50 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index f725df302673fef..7423cda815c3134 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1268,9 +1268,14 @@ export async function renderToHTML( } } - // We make it a function component to enable streaming. - if (hasConcurrentFeatures && builtinDocument && isServerComponent) { - // Document = builtinDocument + if ((isServerComponent || process.browser) && Document.getInitialProps) { + if (builtinDocument) { + Document = builtinDocument + } else { + throw new Error( + '`getInitialProps` in Document component is not supported with React Server Components.' + ) + } } async function documentInitialProps() { @@ -1326,7 +1331,7 @@ export async function renderToHTML( return { docProps, documentCtx } } - if (!hasConcurrentFeatures && Document.getInitialProps) { + if (!hasConcurrentFeatures) { const res = await documentInitialProps() if (!res) return null const { docProps, documentCtx } = res @@ -1363,56 +1368,47 @@ export async function renderToHTML( ) } - if (hasConcurrentFeatures) { - let renderStream: any + let renderStream: any - // We start rendering the shell earlier, before returning the head tags - // to `documentResult`. - const content = renderContent() - renderStream = await renderToInitialStream({ - ReactDOMServer, - element: content, - }) - - bodyResult = async (suffix: string) => { - // this must be called inside bodyResult so appWrappers is - // up to date when getWrappedApp is called - - const flushEffectHandler = async () => { - const allFlushEffects = [ - styledJsxFlushEffect, - ...(flushEffects || []), - ] - const flushEffectStream = await renderToStream({ - ReactDOMServer, - element: ( - <> - {allFlushEffects.map((flushEffect, i) => ( - {flushEffect()} - ))} - - ), - generateStaticHTML: true, - }) - const flushed = await streamToString(flushEffectStream) - return flushed - } + // We start rendering the shell earlier, before returning the head tags + // to `documentResult`. + const content = renderContent() + renderStream = await renderToInitialStream({ + ReactDOMServer, + element: content, + }) - return await continueFromInitialStream({ - renderStream, - suffix, - dataStream: serverComponentsInlinedTransformStream?.readable, - generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures, - flushEffectHandler, + bodyResult = async (suffix: string) => { + // this must be called inside bodyResult so appWrappers is + // up to date when getWrappedApp is called + + const flushEffectHandler = async () => { + const allFlushEffects = [ + styledJsxFlushEffect, + ...(flushEffects || []), + ] + const flushEffectStream = await renderToStream({ + ReactDOMServer, + element: ( + <> + {allFlushEffects.map((flushEffect, i) => ( + {flushEffect()} + ))} + + ), + generateStaticHTML: true, }) + const flushed = await streamToString(flushEffectStream) + return flushed } - } else { - const content = renderContent() - // 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 = ReactDOMServer.renderToString(content) - bodyResult = (suffix: string) => streamFromArray([result, suffix]) + + return await continueFromInitialStream({ + renderStream, + suffix, + dataStream: serverComponentsInlinedTransformStream?.readable, + generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures, + flushEffectHandler, + }) } const styles = jsxStyleRegistry.styles() From 87f9569a5ac485fe0a168a85f6061d85415aa0a4 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 16:14:17 +0200 Subject: [PATCH 4/9] revert deleted file --- test/integration/react-18/app/pages/_document.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/integration/react-18/app/pages/_document.js diff --git a/test/integration/react-18/app/pages/_document.js b/test/integration/react-18/app/pages/_document.js deleted file mode 100644 index e69de29bb2d1d64..000000000000000 From 3b785696def7fc0bb94baed2df0d71dc691af242 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 16:24:37 +0200 Subject: [PATCH 5/9] fix lint error --- packages/next/server/render.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 7423cda815c3134..bde7a7986e9c7d3 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1332,9 +1332,9 @@ export async function renderToHTML( } if (!hasConcurrentFeatures) { - const res = await documentInitialProps() - if (!res) return null - const { docProps, documentCtx } = res + const documentInitialPropsRes = await documentInitialProps() + if (!documentInitialPropsRes) return null + const { docProps, documentCtx } = documentInitialPropsRes return { bodyResult: (suffix: string) => @@ -1414,16 +1414,16 @@ export async function renderToHTML( const styles = jsxStyleRegistry.styles() jsxStyleRegistry.flush() - const docRes = + const documentInitialPropsRes = isServerComponent || process.browser ? {} : await documentInitialProps() - if (docRes === null) return null + if (documentInitialPropsRes === null) return null const documentElement = () => { if (isServerComponent || process.browser) { return (Document as any)() } - const { docProps } = docRes as any + const { docProps } = documentInitialPropsRes as any return } From a5fd1d7e58463b8dd92df111bc2ae71a34224ad1 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 16:58:41 +0200 Subject: [PATCH 6/9] fix --- packages/next/server/render.tsx | 90 ++++++++++++------- .../basic/styled-components.test.ts | 2 +- 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index bde7a7986e9c7d3..25b1dffb72871ec 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1331,43 +1331,63 @@ export async function renderToHTML( return { docProps, documentCtx } } - if (!hasConcurrentFeatures) { - const documentInitialPropsRes = await documentInitialProps() - if (!documentInitialPropsRes) return null - const { docProps, documentCtx } = documentInitialPropsRes + const renderContent = () => { + return ctx.err && ErrorDebug ? ( + + + + ) : ( + + + {isServerComponent && AppMod.__next_rsc__ ? ( + // _app.server.js is used. + + ) : ( + + )} + + + ) + } - return { - bodyResult: (suffix: string) => - streamFromArray([docProps.html, suffix]), - documentElement: (htmlProps: HtmlProps) => ( - - ), - head: docProps.head, - headTags: await headTags(documentCtx), - styles: docProps.styles, + if (!hasConcurrentFeatures) { + if (Document.getInitialProps) { + const documentInitialPropsRes = await documentInitialProps() + if (documentInitialPropsRes === null) return null + const { docProps, documentCtx } = documentInitialPropsRes + + return { + bodyResult: (suffix: string) => + streamFromArray([docProps.html, suffix]), + documentElement: (htmlProps: HtmlProps) => ( + + ), + head: docProps.head, + headTags: await headTags(documentCtx), + styles: docProps.styles, + } + } else { + const content = renderContent() + // 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 = ReactDOMServer.renderToString(content) + const bodyResult = (suffix: string) => streamFromArray([result, suffix]) + + const styles = jsxStyleRegistry.styles() + jsxStyleRegistry.flush() + + return { + bodyResult, + documentElement: () => (Document as any)(), + head, + headTags: [], + styles, + } } } else { let bodyResult - const renderContent = () => { - return ctx.err && ErrorDebug ? ( - - - - ) : ( - - - {isServerComponent && AppMod.__next_rsc__ ? ( - // _app.server.js is used. - - ) : ( - - )} - - - ) - } - let renderStream: any // We start rendering the shell earlier, before returning the head tags @@ -1415,7 +1435,9 @@ export async function renderToHTML( jsxStyleRegistry.flush() const documentInitialPropsRes = - isServerComponent || process.browser ? {} : await documentInitialProps() + isServerComponent || process.browser || !Document.getInitialProps + ? {} + : await documentInitialProps() if (documentInitialPropsRes === null) return null const documentElement = () => { @@ -1423,7 +1445,7 @@ export async function renderToHTML( return (Document as any)() } - const { docProps } = documentInitialPropsRes as any + const { docProps } = (documentInitialPropsRes as any) || {} return } diff --git a/test/development/basic/styled-components.test.ts b/test/development/basic/styled-components.test.ts index bd1be8072a64155..6e350c3c473a05d 100644 --- a/test/development/basic/styled-components.test.ts +++ b/test/development/basic/styled-components.test.ts @@ -67,6 +67,6 @@ describe('styled-components SWC transform', () => { await browser.eval( `window.getComputedStyle(document.querySelector('#btn')).color` ) - ).toBe('white') + ).toBe('rgb(255, 255, 255)') }) }) From 9ec6b75291f34c98bc86ea5e2bce8351c165ad94 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 17:26:30 +0200 Subject: [PATCH 7/9] remove doc gip test --- .../test/index.test.js | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 4bdd329708b9e27..de64a925ff76df9 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -12,7 +12,6 @@ import { appDir, nativeModuleTestAppDir, distDir, - documentPage, appPage, appServerPage, error500Page, @@ -25,26 +24,6 @@ import streaming from './streaming' import basic from './basic' import runtime from './runtime' -const documentWithGip = ` -import { Html, Head, Main, NextScript } from 'next/document' - -export default function Document() { - return ( - - - -
- - - - ) -} - -Document.getInitialProps = (ctx) => { - return ctx.defaultGetInitialProps(ctx) -} -` - const rscAppPage = ` import Container from '../components/container.server' export default function App({children}) { @@ -240,17 +219,6 @@ const cssSuite = { afterAll: () => appPage.delete(), } -const documentSuite = { - runTests: (context) => { - it('should error when custom _document has getInitialProps method', async () => { - const res = await fetchViaHTTP(context.appPort, '/') - expect(res.status).toBe(500) - }) - }, - beforeAll: () => documentPage.write(documentWithGip), - afterAll: () => documentPage.delete(), -} - runSuite('Node.js runtime', 'dev', nodejsRuntimeBasicSuite) runSuite('Node.js runtime', 'prod', nodejsRuntimeBasicSuite) @@ -260,9 +228,6 @@ runSuite('Custom App', 'prod', customAppPageSuite) runSuite('CSS', 'dev', cssSuite) runSuite('CSS', 'prod', cssSuite) -runSuite('Custom Document', 'dev', documentSuite) -runSuite('Custom Document', 'prod', documentSuite) - function runSuite(suiteName, env, options) { const context = { appDir, distDir } describe(`${suiteName} ${env}`, () => { From 00f408b7733647f95de852d252a69f3f5e7d15dd Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 17:28:34 +0200 Subject: [PATCH 8/9] Revert "remove doc gip test" This reverts commit a5fd1d7e58463b8dd92df111bc2ae71a34224ad1. --- .../test/index.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index de64a925ff76df9..4bdd329708b9e27 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -12,6 +12,7 @@ import { appDir, nativeModuleTestAppDir, distDir, + documentPage, appPage, appServerPage, error500Page, @@ -24,6 +25,26 @@ import streaming from './streaming' import basic from './basic' import runtime from './runtime' +const documentWithGip = ` +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} + +Document.getInitialProps = (ctx) => { + return ctx.defaultGetInitialProps(ctx) +} +` + const rscAppPage = ` import Container from '../components/container.server' export default function App({children}) { @@ -219,6 +240,17 @@ const cssSuite = { afterAll: () => appPage.delete(), } +const documentSuite = { + runTests: (context) => { + it('should error when custom _document has getInitialProps method', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + expect(res.status).toBe(500) + }) + }, + beforeAll: () => documentPage.write(documentWithGip), + afterAll: () => documentPage.delete(), +} + runSuite('Node.js runtime', 'dev', nodejsRuntimeBasicSuite) runSuite('Node.js runtime', 'prod', nodejsRuntimeBasicSuite) @@ -228,6 +260,9 @@ runSuite('Custom App', 'prod', customAppPageSuite) runSuite('CSS', 'dev', cssSuite) runSuite('CSS', 'prod', cssSuite) +runSuite('Custom Document', 'dev', documentSuite) +runSuite('Custom Document', 'prod', documentSuite) + function runSuite(suiteName, env, options) { const context = { appDir, distDir } describe(`${suiteName} ${env}`, () => { From 6d40e6e09d18a78a784f9d9ae516ca3dc59b85a1 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 30 Mar 2022 17:49:28 +0200 Subject: [PATCH 9/9] fix test --- .../test/index.test.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 4bdd329708b9e27..bb52a23a2b83389 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -241,11 +241,17 @@ const cssSuite = { } const documentSuite = { - runTests: (context) => { - it('should error when custom _document has getInitialProps method', async () => { - const res = await fetchViaHTTP(context.appPort, '/') - expect(res.status).toBe(500) - }) + runTests: (context, env) => { + if (env === 'dev') { + it('should error when custom _document has getInitialProps method', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + expect(res.status).toBe(500) + }) + } else { + it('should failed building', async () => { + expect(context.code).toBe(1) + }) + } }, beforeAll: () => documentPage.write(documentWithGip), afterAll: () => documentPage.delete(), @@ -270,7 +276,7 @@ function runSuite(suiteName, env, options) { options.beforeAll?.() if (env === 'prod') { context.appPort = await findPort() - await nextBuild(context.appDir) + context.code = (await nextBuild(context.appDir)).code context.server = await nextStart(context.appDir, context.appPort) } if (env === 'dev') {