From 17806594cc28284fe195f918e8d77de3516848ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 1 Mar 2022 00:13:28 -0500 Subject: [PATCH] Move createRoot/hydrateRoot to react-dom/client (#23385) * Move createRoot/hydrateRoot to /client We want these APIs ideally to be imported separately from things you might use in arbitrary components (like flushSync). Those other methods are "isomorphic" to how the ReactDOM tree is rendered. Similar to hooks. E.g. importing flushSync into a component that only uses it on the client should ideally not also pull in the entry client implementation on the server. This also creates a nicer parity with /server where the roots are in a separate entry point. Unfortunately, I can't quite do this yet because we have some legacy APIs that we plan on removing (like findDOMNode) and we also haven't implemented flushSync using a flag like startTransition does yet. Another problem is that we currently encourage these APIs to be aliased by /profiling (or unstable_testing). In the future you don't have to alias them because you can just change your roots to just import those APIs and they'll still work with the isomorphic forms. Although we might also just use export conditions for them. For that all to work, I went with a different strategy for now where the real API is in / but it comes with a warning if you use it. If you instead import /client it disables the warning in a wrapper. That means that if you alias / then import /client that will inturn import the alias and it'll just work. In a future breaking changes (likely when we switch to ESM) we can just remove createRoot/hydrateRoot from / and move away from the aliasing strategy. * Update tests to import from react-dom/client * Fix fixtures * Update warnings * Add test for the warning * Update devtools * Change order of react-dom, react-dom/client alias I think the order matters here. The first one takes precedence. * Require react-dom through client so it can be aliased Co-authored-by: Andrew Clark --- fixtures/blocks/src/index.js | 2 +- fixtures/concurrent/time-slicing/src/index.js | 2 +- fixtures/ssr/src/index.js | 2 +- fixtures/ssr/yarn.lock | 10 +- fixtures/ssr2/src/index.js | 2 +- .../react-devtools-core/src/standalone.js | 5 +- .../react-devtools-core/webpack.standalone.js | 1 + .../webpack.config.js | 1 + packages/react-devtools-inline/README.md | 8 +- .../react-devtools-inline/webpack.config.js | 1 + .../src/__tests__/TimelineProfiler-test.js | 6 +- .../src/__tests__/console-test.js | 18 +-- .../src/__tests__/inspectedElement-test.js | 14 +- .../src/__tests__/preprocessData-test.js | 49 +++++-- .../src/__tests__/profilingCache-test.js | 8 +- .../profilingCommitTreeBuilder-test.js | 8 +- .../src/__tests__/profilingHostRoot-test.js | 8 +- .../src/__tests__/store-test.js | 16 ++- .../storeStressTestConcurrent-test.js | 44 +++---- .../react-devtools-shell/src/app/index.js | 8 +- packages/react-devtools-shell/src/e2e/app.js | 4 +- .../react-devtools-shell/src/e2e/devtools.js | 3 +- .../react-devtools-shell/webpack.config.js | 1 + packages/react-dom/client.js | 57 +++++++++ packages/react-dom/npm/client.js | 25 ++++ packages/react-dom/package.json | 2 + .../ReactDOMConsoleErrorReporting-test.js | 18 +-- .../src/__tests__/ReactDOMFiberAsync-test.js | 24 ++-- .../src/__tests__/ReactDOMFizzServer-test.js | 41 +++--- .../ReactDOMFizzShellHydration-test.js | 10 +- .../src/__tests__/ReactDOMHooks-test.js | 4 +- .../ReactDOMImageLoad-test.internal.js | 18 +-- .../ReactDOMNativeEventHeuristic-test.js | 18 +-- .../__tests__/ReactDOMNestedEvents-test.js | 6 +- .../src/__tests__/ReactDOMRoot-test.js | 92 +++++++++----- .../ReactDOMSafariMicrotaskBug-test.js | 6 +- ...DOMServerPartialHydration-test.internal.js | 120 ++++++++++-------- ...MServerSelectiveHydration-test.internal.js | 44 ++++--- .../ReactDOMServerSuspense-test.internal.js | 4 +- .../src/__tests__/ReactDOMUseId-test.js | 26 ++-- .../ReactServerRenderingHydration-test.js | 4 +- .../src/__tests__/ReactTestUtilsAct-test.js | 6 +- .../src/__tests__/ReactUpdates-test.js | 6 +- packages/react-dom/src/client/ReactDOM.js | 43 ++++++- .../react-dom/src/client/ReactDOMLegacy.js | 6 +- packages/react-dom/src/client/ReactDOMRoot.js | 6 +- .../DOMPluginEventSystem-test.internal.js | 16 ++- .../__tests__/ChangeEventPlugin-test.js | 14 +- .../__tests__/SimpleEventPlugin-test.js | 7 +- .../__tests__/useFocusWithin-test.internal.js | 6 +- .../src/ReactFiberBeginWork.new.js | 2 +- .../src/ReactFiberBeginWork.old.js | 2 +- .../src/ReactFiberDevToolsHook.new.js | 6 +- .../src/ReactFiberDevToolsHook.old.js | 6 +- .../src/__tests__/ReactScope-test.internal.js | 4 +- .../ReactSuspenseEffectsSemanticsDOM-test.js | 6 +- .../__tests__/ReactUpdaters-test.internal.js | 8 +- .../useMutableSourceHydration-test.js | 16 +-- .../src/__tests__/ReactFresh-test.js | 4 +- .../ReactFlightDOMRelay-test.internal.js | 6 +- .../src/__tests__/ReactFlightDOM-test.js | 14 +- .../__tests__/ReactFlightDOMBrowser-test.js | 6 +- .../ReactCoffeeScriptClass-test.coffee | 4 +- .../react/src/__tests__/ReactES6Class-test.js | 4 +- .../ReactStrictMode-test.internal.js | 12 +- .../src/__tests__/ReactStrictMode-test.js | 13 +- .../__tests__/ReactTypeScriptClass-test.ts | 4 +- .../__tests__/testDefinitions/ReactDOM.d.ts | 1 - .../testDefinitions/ReactDOMClient.d.ts | 18 +++ .../useSyncExternalStoreShared-test.js | 6 +- scripts/jest/typescript/preprocessor.js | 2 +- scripts/shared/inlinedHostConfigs.js | 10 ++ 72 files changed, 648 insertions(+), 356 deletions(-) create mode 100644 packages/react-dom/client.js create mode 100644 packages/react-dom/npm/client.js create mode 100644 packages/react/src/__tests__/testDefinitions/ReactDOMClient.d.ts diff --git a/fixtures/blocks/src/index.js b/fixtures/blocks/src/index.js index be279937f4d3f..5a833ff3f1128 100644 --- a/fixtures/blocks/src/index.js +++ b/fixtures/blocks/src/index.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import {createRoot} from 'react-dom'; +import {createRoot} from 'react-dom/client'; import './index.css'; import Router from './Router'; diff --git a/fixtures/concurrent/time-slicing/src/index.js b/fixtures/concurrent/time-slicing/src/index.js index c871ec6c11227..2b99f803f55c5 100644 --- a/fixtures/concurrent/time-slicing/src/index.js +++ b/fixtures/concurrent/time-slicing/src/index.js @@ -1,5 +1,5 @@ import React, {PureComponent, unstable_startTransition} from 'react'; -import {createRoot} from 'react-dom'; +import {createRoot} from 'react-dom/client'; import _ from 'lodash'; import Charts from './Charts'; import Clock from './Clock'; diff --git a/fixtures/ssr/src/index.js b/fixtures/ssr/src/index.js index 8bef3639ae754..37cc25cb71671 100644 --- a/fixtures/ssr/src/index.js +++ b/fixtures/ssr/src/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import {hydrateRoot} from 'react-dom'; +import {hydrateRoot} from 'react-dom/client'; import App from './components/App'; diff --git a/fixtures/ssr/yarn.lock b/fixtures/ssr/yarn.lock index d3a688915784d..3609381253bce 100644 --- a/fixtures/ssr/yarn.lock +++ b/fixtures/ssr/yarn.lock @@ -4265,7 +4265,7 @@ longest@^1.0.1: resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -5945,6 +5945,14 @@ sax@^1.2.1, sax@~1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +scheduler@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + "semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" diff --git a/fixtures/ssr2/src/index.js b/fixtures/ssr2/src/index.js index adf035ad1a881..10c2898e328b7 100644 --- a/fixtures/ssr2/src/index.js +++ b/fixtures/ssr2/src/index.js @@ -6,7 +6,7 @@ * */ -import {hydrateRoot} from 'react-dom'; +import {hydrateRoot} from 'react-dom/client'; import App from './App'; hydrateRoot(document, ); diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index ab3250d7cff7a..c04cad1ddea08 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -11,9 +11,8 @@ import {createElement} from 'react'; import { // $FlowFixMe Flow does not yet know about flushSync() flushSync, - // $FlowFixMe Flow does not yet know about createRoot() - createRoot, -} from 'react-dom'; +} from 'react-dom/client'; +import {createRoot} from 'react-dom/client'; import Bridge from 'react-devtools-shared/src/bridge'; import Store from 'react-devtools-shared/src/devtools/store'; import { diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js index 234d638353f43..b7732d6641690 100644 --- a/packages/react-devtools-core/webpack.standalone.js +++ b/packages/react-devtools-core/webpack.standalone.js @@ -64,6 +64,7 @@ module.exports = { react: resolve(builtModulesDir, 'react'), 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), 'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget), + 'react-dom/client': resolve(builtModulesDir, 'react-dom/client'), 'react-dom': resolve(builtModulesDir, 'react-dom'), 'react-is': resolve(builtModulesDir, 'react-is'), scheduler: resolve(builtModulesDir, 'scheduler'), diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index ac7c51f3bdc5e..3a913e0373804 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -76,6 +76,7 @@ module.exports = { react: resolve(builtModulesDir, 'react'), 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), 'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget), + 'react-dom/client': resolve(builtModulesDir, 'react-dom/client'), 'react-dom': resolve(builtModulesDir, 'react-dom'), 'react-is': resolve(builtModulesDir, 'react-is'), scheduler: resolve(builtModulesDir, 'scheduler'), diff --git a/packages/react-devtools-inline/README.md b/packages/react-devtools-inline/README.md index fe071633528c6..53edee429bd02 100644 --- a/packages/react-devtools-inline/README.md +++ b/packages/react-devtools-inline/README.md @@ -60,7 +60,7 @@ const contentWindow = iframe.contentWindow; const DevTools = initialize(contentWindow); ``` -3 Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.createRoot` or `ReactDOM.createSyncRoot`. **It should not be rendered with `ReactDOM.render`.** +3 Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using `ReactDOMClient.createRoot`. **It should not be rendered with `ReactDOM.render`.** ## Examples @@ -110,8 +110,7 @@ const DevTools = initializeFrontend(contentWindow); // as setting the src of the