diff --git a/packages/next/src/build/create-compiler-aliases.ts b/packages/next/src/build/create-compiler-aliases.ts index 154f516fc6870..c47a43e73029c 100644 --- a/packages/next/src/build/create-compiler-aliases.ts +++ b/packages/next/src/build/create-compiler-aliases.ts @@ -255,15 +255,14 @@ export function createRSCAliases( 'react-dom/static$': `next/dist/compiled/react-dom-experimental/static`, 'react-dom/static.edge$': `next/dist/compiled/react-dom-experimental/static.edge`, 'react-dom/static.browser$': `next/dist/compiled/react-dom-experimental/static.browser`, - 'react-dom/server.edge$': `next/dist/compiled/react-dom${bundledReactChannel}/server.edge`, - 'react-dom/server.browser$': `next/dist/compiled/react-dom${bundledReactChannel}/server.browser`, + // optimizations to ignore the legacy build of react-dom/server in `server.browser` build + 'react-dom/server.edge$': `next/dist/build/webpack/alias/react-dom-server-edge${bundledReactChannel}.js`, + 'react-dom/server.browser$': `next/dist/build/webpack/alias/react-dom-server-browser${bundledReactChannel}.js`, + // react-server-dom-webpack alias 'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client`, 'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client.edge`, 'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.edge`, 'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.node`, - // optimisations to ignore the legacy build of react-dom/server - './cjs/react-dom-server-legacy.browser.production.min.js': `next/dist/build/noop-react-dom-server-legacy`, - './cjs/react-dom-server-legacy.browser.development.js': `next/dist/build/noop-react-dom-server-legacy`, } if (!isEdgeServer) { diff --git a/packages/next/src/build/noop-react-dom-server-legacy.ts b/packages/next/src/build/noop-react-dom-server-legacy.ts deleted file mode 100644 index 21d696afc9537..0000000000000 --- a/packages/next/src/build/noop-react-dom-server-legacy.ts +++ /dev/null @@ -1,10 +0,0 @@ -const ERROR_MESSAGE = - 'Internal Error: do not use legacy react-dom/server APIs. If you encountered this error, please open an issue on the Next.js repo.' - -export function renderToString() { - throw new Error(ERROR_MESSAGE) -} - -export function renderToStaticMarkup() { - throw new Error(ERROR_MESSAGE) -} diff --git a/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js b/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js new file mode 100644 index 0000000000000..a5d6643a6c240 --- /dev/null +++ b/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js @@ -0,0 +1,18 @@ +var l, s +if (process.env.NODE_ENV === 'production') { + l = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.min.js') + s = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.min.js') +} else { + l = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js') + s = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js') +} + +exports.version = l.version +exports.renderToString = l.renderToString +exports.renderToStaticMarkup = l.renderToStaticMarkup +exports.renderToNodeStream = l.renderToNodeStream +exports.renderToStaticNodeStream = l.renderToStaticNodeStream +exports.renderToReadableStream = s.renderToReadableStream +if (s.resume) { + exports.resume = s.resume +} diff --git a/packages/next/src/build/webpack/alias/react-dom-server-browser.js b/packages/next/src/build/webpack/alias/react-dom-server-browser.js new file mode 100644 index 0000000000000..f462b442ae41e --- /dev/null +++ b/packages/next/src/build/webpack/alias/react-dom-server-browser.js @@ -0,0 +1,18 @@ +var l, s +if (process.env.NODE_ENV === 'production') { + l = require('next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js') + s = require('next/dist/compiled/react-dom/cjs/react-dom-server.browser.production.min.js') +} else { + l = require('next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js') + s = require('next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js') +} + +exports.version = l.version +exports.renderToString = l.renderToString +exports.renderToStaticMarkup = l.renderToStaticMarkup +exports.renderToNodeStream = l.renderToNodeStream +exports.renderToStaticNodeStream = l.renderToStaticNodeStream +exports.renderToReadableStream = s.renderToReadableStream +if (s.resume) { + exports.resume = s.resume +} diff --git a/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js b/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js new file mode 100644 index 0000000000000..b14af3c288f1c --- /dev/null +++ b/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js @@ -0,0 +1,23 @@ +const ERROR_MESSAGE = + 'Internal Error: do not use legacy react-dom/server APIs. If you encountered this error, please open an issue on the Next.js repo.' + +function error() { + throw new Error(ERROR_MESSAGE) +} + +var b +if (process.env.NODE_ENV === 'production') { + b = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.min.js') +} else { + b = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js') +} + +exports.version = b.version +exports.renderToReadableStream = b.renderToReadableStream +exports.renderToNodeStream = b.renderToNodeStream +exports.renderToStaticNodeStream = b.renderToStaticNodeStream +exports.renderToString = error +exports.renderToStaticMarkup = error +if (b.resume) { + exports.resume = b.resume +} diff --git a/packages/next/src/build/webpack/alias/react-dom-server-edge.js b/packages/next/src/build/webpack/alias/react-dom-server-edge.js new file mode 100644 index 0000000000000..f6b6056bc271e --- /dev/null +++ b/packages/next/src/build/webpack/alias/react-dom-server-edge.js @@ -0,0 +1,23 @@ +const ERROR_MESSAGE = + 'Internal Error: do not use legacy react-dom/server APIs. If you encountered this error, please open an issue on the Next.js repo.' + +function error() { + throw new Error(ERROR_MESSAGE) +} + +var b +if (process.env.NODE_ENV === 'production') { + b = require('next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js') +} else { + b = require('next/dist/compiled/react-dom/cjs/react-dom-server.edge.development.js') +} + +exports.version = b.version +exports.renderToReadableStream = b.renderToReadableStream +exports.renderToNodeStream = b.renderToNodeStream +exports.renderToStaticNodeStream = b.renderToStaticNodeStream +exports.renderToString = error +exports.renderToStaticMarkup = error +if (b.resume) { + exports.resume = b.resume +} diff --git a/packages/next/webpack.config.js b/packages/next/webpack.config.js index 952e8ccba9819..e995e7f9d634b 100644 --- a/packages/next/webpack.config.js +++ b/packages/next/webpack.config.js @@ -20,6 +20,15 @@ const pagesExternals = [ 'react-server-dom-webpack/server.node', ] +const appExternals = [ + // Externalize the react-dom/server legacy implementation outside of the runtime. + // If users are using them and imported from 'react-dom/server' they will get the external asset bundled. + 'next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js', + 'next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js', + 'next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js', + 'next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.min.js', +] + function makeAppAliases(reactChannel = '') { return { react$: `next/dist/compiled/react${reactChannel}`, @@ -30,11 +39,14 @@ function makeAppAliases(reactChannel = '') { 'react/jsx-dev-runtime$': `next/dist/compiled/react${reactChannel}/jsx-dev-runtime`, 'react-dom/client$': `next/dist/compiled/react-dom${reactChannel}/client`, 'react-dom/server$': `next/dist/compiled/react-dom${reactChannel}/server`, - 'react-dom/server.edge$': `next/dist/compiled/react-dom${reactChannel}/server.edge`, - 'react-dom/server.browser$': `next/dist/compiled/react-dom${reactChannel}/server.browser`, 'react-dom/static$': `next/dist/compiled/react-dom-experimental/static`, 'react-dom/static.edge$': `next/dist/compiled/react-dom-experimental/static.edge`, 'react-dom/static.browser$': `next/dist/compiled/react-dom-experimental/static.browser`, + // optimizations to ignore the legacy build of react-dom/server in `server.browser` build + 'react-dom/server.edge$': `next/dist/build/webpack/alias/react-dom-server-edge${reactChannel}.js`, + // In Next.js runtime only use react-dom/server.edge + 'react-dom/server.browser$': 'react-dom/server.edge', + // react-server-dom-webpack alias 'react-server-dom-turbopack/client$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/client`, 'react-server-dom-turbopack/client.edge$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/client.edge`, 'react-server-dom-turbopack/server.edge$': `next/dist/compiled/react-server-dom-turbopack${reactChannel}/server.edge`, @@ -43,9 +55,6 @@ function makeAppAliases(reactChannel = '') { 'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/client.edge`, 'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.edge`, 'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.node`, - // optimisations to ignore the legacy build of react-dom/server - './cjs/react-dom-server-legacy.browser.production.min.js': `next/dist/build/noop-react-dom-server-legacy`, - './cjs/react-dom-server-legacy.browser.development.js': `next/dist/build/noop-react-dom-server-legacy`, } } @@ -252,7 +261,7 @@ module.exports = ({ dev, turbo, bundleType, experimental }) => { }, externals: [ ...sharedExternals, - ...(bundleType === 'pages' ? pagesExternals : []), + ...(bundleType === 'pages' ? pagesExternals : appExternals), externalsMap, externalHandler, ], diff --git a/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js b/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js index 2e5f208139730..751df16075552 100644 --- a/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js +++ b/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js @@ -3,8 +3,13 @@ import React from 'react' import ReactDOM from 'react-dom' import ReactDOMServer from 'react-dom/server.edge' +import { renderToStaticMarkup } from 'react-dom/server' export default function ClientReact() { + const markup = renderToStaticMarkup( +
{'React Static Markup'}
+ ) + return (

{'React.version=' + React.version}

@@ -12,6 +17,7 @@ export default function ClientReact() {

{'ReactDOMServer.version=' + ReactDOMServer.version}

+

{markup}

) } diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts index 3d8b005d2115e..2e98a0f7e99ec 100644 --- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts @@ -544,6 +544,36 @@ createNextDescribe( ) }) + it('should be able to call legacy react-dom/server APIs in client components', async () => { + const $ = await next.render$('/app-react') + const content = $('#markup').text() + expect(content).toBe( + '
React Static Markup
' + ) + + if (isNextDev) { + const filePath = 'app/app-react/client-react.js' + const fileContent = await next.readFile(filePath) + await next.patchFile( + filePath, + fileContent.replace( + `import { renderToStaticMarkup } from 'react-dom/server'`, + `import { renderToStaticMarkup } from 'react-dom/server.browser'` + ) + ) + + const browser = await next.browser('/app-react') + const markupContentInBrowser = await browser + .elementByCss('#markup') + .text() + expect(markupContentInBrowser).toBe( + '
React Static Markup
' + ) + + await next.patchFile(filePath, fileContent) + } + }) + // disable this flaky test it.skip('should support partial hydration with inlined server data in browser', async () => { // Should end up with "next_streaming_data".