From aa9988e5e669122ed0d76c72f2dea159eef872b4 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 10 Oct 2022 11:06:22 -0700 Subject: [PATCH] Server render fork for react-dom (#25436) Publish an aliasable entry for `react-dom` top level package exports for use in server environments. This is a stub containing only the exports that we expect to retain in the top level once 19 is released --- .../react-dom/npm/server-rendering-stub.js | 7 ++ packages/react-dom/package.json | 2 + .../server-rendering-stub.experimental.js | 22 ++++++ packages/react-dom/server-rendering-stub.js | 21 ++++++ .../react-dom-server-rendering-stub-test.js | 74 +++++++++++++++++++ .../src/server/ReactDOMServerRenderingStub.js | 25 +++++++ scripts/error-codes/codes.json | 4 +- scripts/rollup/bundles.js | 12 +++ scripts/rollup/forks.js | 2 +- scripts/shared/inlinedHostConfigs.js | 2 + 10 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 packages/react-dom/npm/server-rendering-stub.js create mode 100644 packages/react-dom/server-rendering-stub.experimental.js create mode 100644 packages/react-dom/server-rendering-stub.js create mode 100644 packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js create mode 100644 packages/react-dom/src/server/ReactDOMServerRenderingStub.js diff --git a/packages/react-dom/npm/server-rendering-stub.js b/packages/react-dom/npm/server-rendering-stub.js new file mode 100644 index 0000000000000..fa2464a3e59fe --- /dev/null +++ b/packages/react-dom/npm/server-rendering-stub.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-dom-server-rendering-stub.production.min.js'); +} else { + module.exports = require('./cjs/react-dom-server-rendering-stub.development.js'); +} diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index ca84673501ffd..c39432aa32046 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -35,6 +35,7 @@ "static.js", "static.browser.js", "static.node.js", + "server-rendering-stub.js", "test-utils.js", "unstable_testing.js", "cjs/", @@ -59,6 +60,7 @@ }, "./static.browser": "./static.browser.js", "./static.node": "./static.node.js", + "./server-rendering-stub": "./server-rendering-stub.js", "./profiling": "./profiling.js", "./test-utils": "./test-utils.js", "./unstable_testing": "./unstable_testing.js", diff --git a/packages/react-dom/server-rendering-stub.experimental.js b/packages/react-dom/server-rendering-stub.experimental.js new file mode 100644 index 0000000000000..d87679dac0c06 --- /dev/null +++ b/packages/react-dom/server-rendering-stub.experimental.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// Export all exports so that they're available in tests. +// We can't use export * from in Flow for some reason. + +import ReactVersion from 'shared/ReactVersion'; +export {ReactVersion as version}; + +export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; + +export { + createPortal, + flushSync, +} from './src/server/ReactDOMServerRenderingStub'; +export {preinit, preload} from 'react-dom-bindings/src/shared/ReactDOMFloat'; diff --git a/packages/react-dom/server-rendering-stub.js b/packages/react-dom/server-rendering-stub.js new file mode 100644 index 0000000000000..7b673e903c987 --- /dev/null +++ b/packages/react-dom/server-rendering-stub.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// Export all exports so that they're available in tests. +// We can't use export * from in Flow for some reason. + +import ReactVersion from 'shared/ReactVersion'; +export {ReactVersion as version}; + +export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; + +export { + createPortal, + flushSync, +} from './src/server/ReactDOMServerRenderingStub'; diff --git a/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js b/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js new file mode 100644 index 0000000000000..1cf80ebf1dec4 --- /dev/null +++ b/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactDOM; +let ReactDOMFizzServer; + +describe('react-dom-server-rendering-stub', () => { + beforeEach(() => { + jest.mock('react-dom', () => require('react-dom/server-rendering-stub')); + + React = require('react'); + ReactDOM = require('react-dom'); + ReactDOMFizzServer = require('react-dom/server'); + }); + + it('exports a version', () => { + expect(ReactDOM.version).toBeTruthy(); + }); + + it('exports that are expected to be client only in the future are not exported', () => { + expect(ReactDOM.createRoot).toBe(undefined); + expect(ReactDOM.hydrateRoot).toBe(undefined); + expect(ReactDOM.findDOMNode).toBe(undefined); + expect(ReactDOM.hydrate).toBe(undefined); + expect(ReactDOM.render).toBe(undefined); + expect(ReactDOM.unmountComponentAtNode).toBe(undefined); + expect(ReactDOM.unstable_batchedUpdates).toBe(undefined); + expect(ReactDOM.unstable_createEventHandle).toBe(undefined); + expect(ReactDOM.unstable_flushControlled).toBe(undefined); + expect(ReactDOM.unstable_isNewReconciler).toBe(undefined); + expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined); + expect(ReactDOM.unstable_runWithPriority).toBe(undefined); + }); + + // @gate enableFloat + it('provides preload and preinit exports', async () => { + function App() { + ReactDOM.preload('foo', {as: 'style'}); + ReactDOM.preinit('bar', {as: 'style'}); + return
foo
; + } + const html = ReactDOMFizzServer.renderToString(); + expect(html).toEqual( + '
foo
', + ); + }); + + it('provides a stub for createPortal', async () => { + expect(() => { + ReactDOM.createPortal(); + }).toThrow( + 'createPortal was called on the server. Portals are not currently supported on the server. Update your program to conditionally call createPortal on the client only.', + ); + }); + + it('provides a stub for flushSync', async () => { + let x = false; + expect(() => { + ReactDOM.flushSync(() => (x = true)); + }).toThrow( + 'flushSync was called on the server. This is likely caused by a function being called during render or in module scope that was intended to be called from an effect or event handler. Update your to not call flushSync no the server.', + ); + expect(x).toBe(false); + }); +}); diff --git a/packages/react-dom/src/server/ReactDOMServerRenderingStub.js b/packages/react-dom/src/server/ReactDOMServerRenderingStub.js new file mode 100644 index 0000000000000..7d29f2aaabf16 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMServerRenderingStub.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export function createPortal() { + throw new Error( + 'createPortal was called on the server. Portals are not currently' + + ' supported on the server. Update your program to conditionally call' + + ' createPortal on the client only.', + ); +} + +export function flushSync() { + throw new Error( + 'flushSync was called on the server. This is likely caused by a' + + ' function being called during render or in module scope that was' + + ' intended to be called from an effect or event handler. Update your' + + ' to not call flushSync no the server.', + ); +} diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 48fcf61717273..9995cc0456835 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -432,5 +432,7 @@ "444": "getResource encountered a resource type it did not expect: \"%s\". this is a bug in React.", "445": "\"currentResources\" was expected to exist. This is a bug in React.", "446": "\"resourceRoot\" was expected to exist. This is a bug in React.", - "447": "While attempting to insert a Resource, React expected the Document to contain a head element but it was not found." + "447": "While attempting to insert a Resource, React expected the Document to contain a head element but it was not found.", + "448": "createPortal was called on the server. Portals are not currently supported on the server. Update your program to conditionally call createPortal on the client only.", + "449": "flushSync was called on the server. This is likely caused by a function being called during render or in module scope that was intended to be called from an effect or event handler. Update your to not call flushSync no the server." } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index df67873c8c485..1f7297a8f2174 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -351,6 +351,18 @@ const bundles = [ externals: ['react', 'util', 'stream', 'react-dom'], }, + /******* React DOM Server Render Stub *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], + moduleType: RENDERER, + entry: 'react-dom/server-rendering-stub', + name: 'react-dom-server-rendering-stub', + global: 'ReactDOMServerRenderingStub', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: false, + externals: ['react'], + }, + /******* React Server DOM Webpack Writer *******/ { bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index b745918acfa3d..15856230bacc7 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -74,7 +74,7 @@ const forks = Object.freeze({ entry, dependencies ) => { - if (entry === 'react-dom') { + if (entry === 'react-dom' || entry === 'react-dom/server-rendering-stub') { return './packages/react-dom/src/ReactDOMSharedInternals.js'; } if ( diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 2cd0f8400341e..1113bad1247d0 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -14,6 +14,7 @@ module.exports = [ 'react-dom/unstable_testing', 'react-dom/src/server/ReactDOMFizzServerNode.js', 'react-dom/static.node', + 'react-dom/server-rendering-stub', 'react-server-dom-webpack/writer.node.server', 'react-server-dom-webpack', ], @@ -49,6 +50,7 @@ module.exports = [ 'react-dom/unstable_testing', 'react-dom/src/server/ReactDOMFizzServerBrowser.js', 'react-dom/static.browser', + 'react-dom/server-rendering-stub', 'react-server-dom-webpack/writer.browser.server', 'react-server-dom-webpack', ],