From e6875749ec3848996feea88b450faef5e43b05cf Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 18:03:38 +0100 Subject: [PATCH 1/6] Adopt react 18 rc1 --- package.json | 4 +-- .../loaders/next-flight-server-loader.ts | 1 + packages/next/client/index.tsx | 15 +++++----- packages/next/server/render.tsx | 20 ++++--------- .../react-18/test/with-react-18.js | 5 +--- .../app/next.config.js | 1 - .../test/rsc.js | 11 +++---- yarn.lock | 29 +++++++++---------- 8 files changed, 33 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 162cce7f1e6c..0b5117655871 100644 --- a/package.json +++ b/package.json @@ -144,9 +144,9 @@ "pretty-ms": "7.0.0", "random-seed": "0.3.0", "react": "17.0.2", - "react-18": "npm:react@18.0.0-rc.0", + "react-18": "npm:react@18.0.0-rc.1", "react-dom": "17.0.2", - "react-dom-18": "npm:react-dom@18.0.0-rc.0", + "react-dom-18": "npm:react-dom@18.0.0-rc.1", "react-ssr-prepass": "1.0.8", "relay-compiler": "13.0.2", "relay-runtime": "13.0.2", diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index d66405948626..a32baee7a93b 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -73,6 +73,7 @@ async function parseImportsInfo({ // Since we already include React in the SSR runtime, // here we can't create a new module with the ?__rsc_server__ query. if ( + importSource === 'react' || ['react/jsx-runtime', 'react/jsx-dev-runtime'].includes( importSource ) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 6c2de5960162..39174b90bcdb 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -547,9 +547,10 @@ function renderReactElement( const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete) if (process.env.__NEXT_REACT_ROOT) { + const ReactDOMClient = require('react-dom/client') if (!reactRoot) { // Unlike with createRoot, you don't need a separate root.render() call here - reactRoot = (ReactDOM as any).hydrateRoot(domEl, reactEl) + reactRoot = (ReactDOMClient as any).hydrateRoot(domEl, reactEl) // TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing shouldHydrate = false } else { @@ -812,13 +813,11 @@ if (process.env.__NEXT_RSC) { return ( - - - + ) } diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index f86b9c3a10a3..b6dbee176f95 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -395,11 +395,7 @@ function createServerComponentRenderer( } const Component = (props: any) => { - return ( - - - - ) + return } // Although it's not allowed to attach some static methods to Component, @@ -1763,7 +1759,7 @@ function renderToStream({ generateStaticHTML: boolean flushEffectHandler?: () => Promise }): Promise> { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let resolved = false const closeTag = '' @@ -1798,7 +1794,7 @@ function renderToStream({ } } - const renderStream: ReadableStream = ( + const renderStream: ReadableStream = await ( ReactDOMServer as any ).renderToReadableStream(element, { onError(err: Error) { @@ -1807,15 +1803,9 @@ function renderToStream({ reject(err) } }, - onCompleteShell() { - if (!generateStaticHTML) { - doResolve() - } - }, - onCompleteAll() { - doResolve() - }, }) + + doResolve() }) } diff --git a/test/integration/react-18/test/with-react-18.js b/test/integration/react-18/test/with-react-18.js index f754eb801bc9..abbec9e348a4 100644 --- a/test/integration/react-18/test/with-react-18.js +++ b/test/integration/react-18/test/with-react-18.js @@ -1,13 +1,10 @@ module.exports = function withReact18(config) { config.webpack = (webpackConfig) => { const { alias } = webpackConfig.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/client'] = 'react-dom-18/client' alias['react-dom/server'] = 'react-dom-18/server' alias['react-dom/server.browser'] = 'react-dom-18/server.browser' diff --git a/test/integration/react-streaming-and-server-components/app/next.config.js b/test/integration/react-streaming-and-server-components/app/next.config.js index a17299bc8c9e..add280d239bb 100644 --- a/test/integration/react-streaming-and-server-components/app/next.config.js +++ b/test/integration/react-streaming-and-server-components/app/next.config.js @@ -1,7 +1,6 @@ const withReact18 = require('../../react-18/test/with-react-18') module.exports = withReact18({ - trailingSlash: true, reactStrictMode: true, onDemandEntries: { maxInactiveAge: 1000 * 60 * 60, diff --git a/test/integration/react-streaming-and-server-components/test/rsc.js b/test/integration/react-streaming-and-server-components/test/rsc.js index 6e8b76d8c2a4..d56f5dcdd93b 100644 --- a/test/integration/react-streaming-and-server-components/test/rsc.js +++ b/test/integration/react-streaming-and-server-components/test/rsc.js @@ -77,10 +77,7 @@ export default function (context, { runtime, env }) { it('should support next/link in server components', async () => { const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link') - const linkText = getNodeBySelector( - linkHTML, - 'div[hidden] > a[href="/"]' - ).text() + const linkText = getNodeBySelector(linkHTML, '#__next > a[href="/"]').text() expect(linkText).toContain('go home') @@ -114,7 +111,7 @@ export default function (context, { runtime, env }) { const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image') const imageTag = getNodeBySelector( imageHTML, - 'div[hidden] > span > span > img' + '#__next > span > span > img' ) expect(imageTag.attr('src')).toContain('data:image') @@ -176,13 +173,13 @@ export default function (context, { runtime, env }) { expect( getNodeBySelector( clientExportsHTML, - 'div[hidden] > div > #named-exports' + '#__next > div > #named-exports' ).text() ).toBe('abcde') expect( getNodeBySelector( clientExportsHTML, - 'div[hidden] > div > #default-exports-arrow' + '#__next > div > #default-exports-arrow' ).text() ).toBe('client-default-export-arrow') diff --git a/yarn.lock b/yarn.lock index 809d3a4e7732..ecf5d376f8b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17527,22 +17527,20 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -"react-18@npm:react@18.0.0-rc.0": - version "18.0.0-rc.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-rc.0.tgz#60bfcf1edd0b35fbeeeca852515c6cc2ce06a6eb" - integrity sha512-PawosMBgF8k5Nlc3++ibzjFqPvo1XKv80MNtVYqz3abHHB2w3IpU65sSdSmBd2ooCwVhcp9b1vkx/twqhakNtA== +"react-18@npm:react@18.0.0-rc.1": + version "18.0.0-rc.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-rc.1.tgz#fbaef1cbf8b1eddb36a869ee1ea723044aa670c2" + integrity sha512-IdvjOtyeFhITZwDrj+GMvY7kbWnGklRzNByO7/pOVIt0o3VZi++BUybpK3Fen1gDPkOZDP/rag7lSh6RZuWOeQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" -"react-dom-18@npm:react-dom@18.0.0-rc.0": - version "18.0.0-rc.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-rc.0.tgz#aa07044bdd6399ff94c664b2985e2e25948fbf3e" - integrity sha512-tdD1n0svTndHBQvVAq/f2Kx7FgQ30CpSLp87/neQKAHPW5WtdgW1sBSwmFAcMQOrmstTuP0M+zRlH86f9kMX/A== +"react-dom-18@npm:react-dom@18.0.0-rc.1": + version "18.0.0-rc.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-rc.1.tgz#97296d5a5e7582e65ea0d82a9ccd5953aa05e7f6" + integrity sha512-BZ9NEwUp56MEguEwAzuh3u4bYE9Jv3QrzjaTmu11PV4C/lJCARTELEI16vjnHLq184GoJcCHMBrDILqlCrkZFQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.21.0-rc.0" + scheduler "^0.21.0-rc.1" react-dom@17.0.2: version "17.0.2" @@ -18543,13 +18541,12 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.21.0-rc.0: - version "0.21.0-rc.0-next-f2a59df48-20211208" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-rc.0-next-f2a59df48-20211208.tgz#54e18e1d360194fd54b47a00616e46403fcabdf1" - integrity sha512-x0oLd3YIih9GHqWTaFYejVe6Au+4TadOWZciAq8m4+Fuo5qCi4/3M35a9irVSIP3+qcg/fCqHKJETT9G0ejD1A== +scheduler@^0.21.0-rc.1: + version "0.21.0-rc.1-next-f468816ef-20220225" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-rc.1-next-f468816ef-20220225.tgz#1909b9ce9e67de48f5ca06b52d98828401771e66" + integrity sha512-/maKldJ78Ba2o1e9kh2mUpqNZH7XrmSQ1o1aa3HUPN/OMotUB9+pBKX/y3Zihkjco21H4cujG9hK2sJOZUpzJw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" "schema-utils2@npm:schema-utils@2.7.1": version "2.7.1" From 839abed86cad8c846d341ff8185f213c638fb6ec Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 18:06:59 +0100 Subject: [PATCH 2/6] remove generateStaticHTML param --- packages/next/server/render.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index b6dbee176f95..06e4bec57b7f 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1349,7 +1349,6 @@ export async function renderToHTML( ))} ), - generateStaticHTML: true, }) const flushed = await streamToString(flushEffectStream) @@ -1361,7 +1360,6 @@ export async function renderToHTML( element: content, suffix, dataStream: serverComponentsInlinedTransformStream?.readable, - generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures, flushEffectHandler, }) } @@ -1494,7 +1492,6 @@ export async function renderToHTML( const documentStream = await renderToStream({ ReactDOMServer, element: document, - generateStaticHTML: true, }) documentHTML = await streamToString(documentStream) } else { @@ -1749,14 +1746,12 @@ function renderToStream({ element, suffix, dataStream, - generateStaticHTML, flushEffectHandler, }: { ReactDOMServer: typeof import('react-dom/server') element: React.ReactElement suffix?: string dataStream?: ReadableStream - generateStaticHTML: boolean flushEffectHandler?: () => Promise }): Promise> { return new Promise(async (resolve, reject) => { @@ -1765,7 +1760,7 @@ function renderToStream({ const closeTag = '' const suffixUnclosed = suffix ? suffix.split(closeTag)[0] : null - const doResolve = () => { + const doResolve = (renderStream: ReadableStream) => { if (!resolved) { resolved = true @@ -1805,7 +1800,7 @@ function renderToStream({ }, }) - doResolve() + doResolve(renderStream) }) } From 0b55f19716f8f7bf60958a4518c77e28fe7c513f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 18:45:36 +0100 Subject: [PATCH 3/6] update test --- test/production/react-18-streaming-ssr/index.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/production/react-18-streaming-ssr/index.test.ts b/test/production/react-18-streaming-ssr/index.test.ts index 7d1114b36a0b..8188a1971a9f 100644 --- a/test/production/react-18-streaming-ssr/index.test.ts +++ b/test/production/react-18-streaming-ssr/index.test.ts @@ -24,8 +24,8 @@ describe('react 18 streaming SSR in minimal mode', () => { }, }, dependencies: { - react: '18.0.0-rc.0', - 'react-dom': '18.0.0-rc.0', + react: '18.0.0-rc.1', + 'react-dom': '18.0.0-rc.1', }, }) }) @@ -60,8 +60,8 @@ describe('react 18 streaming SSR with custom next configs', () => { }, }, dependencies: { - react: '18.0.0-rc.0', - 'react-dom': '18.0.0-rc.0', + react: '18.0.0-rc.1', + 'react-dom': '18.0.0-rc.1', }, }) }) From ba9f07e60acf11b1d9b4b13b7f9a783319122c0a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 19:26:25 +0100 Subject: [PATCH 4/6] remove invalid test --- .../react-18/app/pages/suspense/unwrapped.js | 19 ------------------- test/integration/react-18/test/index.test.js | 15 --------------- 2 files changed, 34 deletions(-) delete mode 100644 test/integration/react-18/app/pages/suspense/unwrapped.js diff --git a/test/integration/react-18/app/pages/suspense/unwrapped.js b/test/integration/react-18/app/pages/suspense/unwrapped.js deleted file mode 100644 index 93c32bbbc501..000000000000 --- a/test/integration/react-18/app/pages/suspense/unwrapped.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { Suspense } from 'react' -import dynamic from 'next/dynamic' - -// flag for testing -const wrapped = true - -const Hello = dynamic(() => import('../../components/hello'), { - suspense: true, -}) - -export default function Unwrapped() { - if (!wrapped) return - - return ( - - - - ) -} diff --git a/test/integration/react-18/test/index.test.js b/test/integration/react-18/test/index.test.js index 2660a9fd20b1..ab362789579f 100644 --- a/test/integration/react-18/test/index.test.js +++ b/test/integration/react-18/test/index.test.js @@ -25,7 +25,6 @@ const nodeArgs = ['-r', join(__dirname, 'require-hook.js')] const appDir = join(__dirname, '../app') const nextConfig = new File(join(appDir, 'next.config.js')) const dynamicHello = new File(join(appDir, 'components/dynamic-hello.js')) -const unwrappedPage = new File(join(appDir, 'pages/suspense/unwrapped.js')) const invalidPage = new File(join(appDir, 'pages/invalid.js')) const USING_CREATE_ROOT = 'Using the createRoot API for React' @@ -95,20 +94,6 @@ describe('React 18 Support', () => { describe('Basics', () => { runTests('default setting with react 18', (context) => basics(context)) - - it('suspense is not allowed in blocking rendering mode (dev)', async () => { - // set dynamic.suspense = true but not wrapping with - unwrappedPage.replace('wrapped = true', 'wrapped = false') - const appPort = await findPort() - const app = await launchApp(appDir, appPort, { nodeArgs }) - const html = await renderViaHTTP(appPort, '/suspense/unwrapped') - unwrappedPage.restore() - await killApp(app) - - expect(html).toContain( - 'A React component suspended while rendering, but no fallback UI was specified' - ) - }) }) // React 18 with Strict Mode enabled might cause double invocation of lifecycle methods. From 577adeafb094f52c0ac035089bee36f5b40c30f3 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 21:42:02 +0100 Subject: [PATCH 5/6] make production test more solid --- .../react-18-streaming-ssr/index.test.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/production/react-18-streaming-ssr/index.test.ts b/test/production/react-18-streaming-ssr/index.test.ts index 8188a1971a9f..0a8e355a7a49 100644 --- a/test/production/react-18-streaming-ssr/index.test.ts +++ b/test/production/react-18-streaming-ssr/index.test.ts @@ -47,8 +47,8 @@ describe('react 18 streaming SSR with custom next configs', () => { next = await createNext({ files: { 'pages/hello.js': ` - export default function Page() { - return

hello

+ export default function Page() { + return

hello nextjs

} `, }, @@ -69,10 +69,17 @@ describe('react 18 streaming SSR with custom next configs', () => { it('should redirect paths without trailing-slash and render when slash is appended', async () => { const page = '/hello' - const html = await renderViaHTTP(next.url, page + '/') - const res = await fetchViaHTTP(next.url, page, {}, { redirect: 'manual' }) + const redirectRes = await fetchViaHTTP( + next.url, + page, + {}, + { redirect: 'manual' } + ) + const res = await fetchViaHTTP(next.url, page + '/') + const html = await res.text() - expect(html).toContain('hello') - expect(res.status).toBe(308) + expect(redirectRes.status).toBe(308) + expect(res.status).toBe(200) + expect(html).toContain('hello nextjs') }) }) From 147eed9912fe9ebd9f83a22892d2e9aa44d31cc8 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Mar 2022 21:54:01 +0100 Subject: [PATCH 6/6] merge edge cases --- .../build/webpack/loaders/next-flight-server-loader.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index a32baee7a93b..dc8884da6fc3 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -69,12 +69,16 @@ async function parseImportsInfo({ transformedSource += JSON.stringify(`${importSource}?__sc_client__`) imports += `require(${JSON.stringify(importSource)})\n` } else { + // FIXME + // case: 'react' + // Avoid module resolution error like Cannot find `./?__rsc_server__` in react/package.json + + // cases: 'react/jsx-runtime', 'react/jsx-dev-runtime' // This is a special case to avoid the Duplicate React error. // Since we already include React in the SSR runtime, // here we can't create a new module with the ?__rsc_server__ query. if ( - importSource === 'react' || - ['react/jsx-runtime', 'react/jsx-dev-runtime'].includes( + ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime'].includes( importSource ) ) {