[react-dom] move all client code to react-dom/client (#28271)

This PR reorganizes the `react-dom` entrypoint to only pull in code that
is environment agnostic. Previously if you required anything from this
entrypoint in any environment the entire client reconciler was loaded.
In a prior release we added a server rendering stub which you could
alias in server environments to omit this unecessary code. After landing
this change this entrypoint should not load any environment specific
code.

While a few APIs are truly client (browser) only such as createRoot and
hydrateRoot many of the APIs you import from this package are only
useful in the browser but could concievably be imported in shared code
(components running in Fizz or shared components as part of an RSC app).
To avoid making these require opting into the client bundle we are
keeping them in the `react-dom` entrypoint and changing their
implementation so that in environments where they are not particularly
useful they do something benign and expected.

#### Removed APIs
The following APIs are being removed in the next major. Largely they
have all been deprecated already and are part of legacy rendering modes
where concurrent features of React are not available
* `render`
* `hydrate`
* `findDOMNode`
* `unmountComponentAtNode`
* `unstable_createEventHandle`
* `unstable_renderSubtreeIntoContainer`
* `unstable_runWithPrioirty`

#### moved Client APIs
These APIs were available on both `react-dom` (with a warning) and
`react-dom/client`. After this change they are only available on
`react-dom/client`
* `createRoot`
* `hydrateRoot`

#### retained APIs
These APIs still exist on the `react-dom` entrypoint but have normalized
behavior depending on which renderers are currently in scope
* `flushSync`: will execute the function (if provided) inside the
flushSync implemention of FlightServer, Fizz, and Fiber DOM renderers.
* `unstable_batchedUpdates`: This is a noop in concurrent mode because
it is now the only supported behavior because there is no legacy
rendering mode
* `createPortal`: This just produces an object. It can be called from
anywhere but since you will probably not have a handle on a DOM node to
pass to it it will likely warn in environments other than the browser
* preloading APIS such as `preload`: These methods will execute the
preload across all renderers currently in scope. Since we resolve the
Request object on the server using AsyncLocalStorage or the current
function stack in practice only one renderer should act upon the
preload.

In addition to these changes the server rendering stub now just rexports
everything from `react-dom`. In a future minor we will add a warning
when using the stub and in the next major we will remove the stub
altogether
This commit is contained in:
Josh Story 2024-04-24 08:50:32 -07:00 committed by GitHub
parent b039be627d
commit cb151849e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1717 additions and 2054 deletions

File diff suppressed because it is too large Load Diff

View File

@ -841,9 +841,9 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"duration": 0.014,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"timestamp": 0.008,
"type": "render-idle",
},
{
@ -851,15 +851,15 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"timestamp": 0.008,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"duration": 0.010,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"timestamp": 0.012,
"type": "commit",
},
{
@ -867,7 +867,7 @@ describe('Timeline profiler', () => {
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"timestamp": 0.02,
"type": "layout-effects",
},
{
@ -875,7 +875,7 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"timestamp": 0.023,
"type": "passive-effects",
},
],
@ -883,9 +883,9 @@ describe('Timeline profiler', () => {
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"duration": 0.014,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"timestamp": 0.028,
"type": "render-idle",
},
{
@ -893,15 +893,15 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"timestamp": 0.028,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"duration": 0.010,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"timestamp": 0.032,
"type": "commit",
},
{
@ -909,7 +909,7 @@ describe('Timeline profiler', () => {
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"timestamp": 0.04,
"type": "layout-effects",
},
{
@ -917,7 +917,7 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"timestamp": 0.043,
"type": "passive-effects",
},
],
@ -926,33 +926,33 @@ describe('Timeline profiler', () => {
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.007,
"timestamp": 0.009,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.002,
"timestamp": 0.02,
"timestamp": 0.024,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.025,
"timestamp": 0.029,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.038,
"timestamp": 0.044,
"type": "passive-effect-mount",
"warning": null,
},
],
"duration": 0.04,
"duration": 0.046,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
@ -1015,9 +1015,9 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"duration": 0.014,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"timestamp": 0.008,
"type": "render-idle",
},
{
@ -1025,15 +1025,15 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"timestamp": 0.008,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"duration": 0.010,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"timestamp": 0.012,
"type": "commit",
},
{
@ -1041,7 +1041,7 @@ describe('Timeline profiler', () => {
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"timestamp": 0.02,
"type": "layout-effects",
},
{
@ -1049,15 +1049,15 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"timestamp": 0.023,
"type": "passive-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"duration": 0.014,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"timestamp": 0.028,
"type": "render-idle",
},
{
@ -1065,15 +1065,15 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"timestamp": 0.028,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"duration": 0.010,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"timestamp": 0.032,
"type": "commit",
},
{
@ -1081,7 +1081,7 @@ describe('Timeline profiler', () => {
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"timestamp": 0.04,
"type": "layout-effects",
},
{
@ -1089,7 +1089,7 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"timestamp": 0.043,
"type": "passive-effects",
},
],
@ -1126,14 +1126,14 @@ describe('Timeline profiler', () => {
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.005,
"timestamp": 0.007,
"type": "schedule-render",
"warning": null,
},
{
"componentName": "App",
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.021,
"timestamp": 0.025,
"type": "schedule-state-update",
"warning": null,
},

View File

@ -3,8 +3,7 @@
// This test harness mounts each test app as a separate root to test multi-root applications.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client';
import * as ReactDOMClient from 'react-dom/client';
import ListApp from '../e2e-apps/ListApp';
function mountApp(App: () => React$Node) {
@ -12,7 +11,7 @@ function mountApp(App: () => React$Node) {
((document.body: any): HTMLBodyElement).appendChild(container);
const root = createRoot(container);
const root = ReactDOMClient.createRoot(container);
root.render(<App />);
}
function mountTestApp() {
@ -27,5 +26,5 @@ window.parent.REACT_DOM_APP = {
createTestNameSelector: name => `[data-testname="${name}"]`,
findAllNodes: (container, nodes) =>
container.querySelectorAll(nodes.join(' ')),
...ReactDOM,
...ReactDOMClient,
};

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client';
import * as ReactDOMClient from 'react-dom/client';
import {
activate as activateBackend,
initialize as initializeBackend,
@ -32,7 +32,7 @@ function init(appIframe, devtoolsContainer, appSource) {
const DevTools = createDevTools(contentWindow);
inject(contentDocument, appSource, () => {
createRoot(devtoolsContainer).render(
ReactDOMClient.createRoot(devtoolsContainer).render(
<DevTools
hookNamesModuleLoaderFunction={hookNamesModuleLoaderFunction}
showTabBar={true}
@ -55,4 +55,5 @@ init(
);
// ReactDOM Test Selector APIs used by Playwright e2e tests
window.parent.REACT_DOM_DEVTOOLS = ReactDOM;
window.parent.REACT_DOM_DEVTOOLS =
'createTestNameSelector' in ReactDOMClient ? ReactDOMClient : ReactDOM;

View File

@ -3,8 +3,7 @@
// This test harness mounts each test app as a separate root to test multi-root applications.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client';
import * as ReactDOMClient from 'react-dom/client';
const container = document.createElement('div');
@ -14,8 +13,8 @@ const container = document.createElement('div');
// so that it can load things other than just ToDoList.
const App = require('../e2e-apps/ListApp').default;
const root = createRoot(container);
const root = ReactDOMClient.createRoot(container);
root.render(<App />);
// ReactDOM Test Selector APIs used by Playwright e2e tests
window.parent.REACT_DOM_APP = ReactDOM;
window.parent.REACT_DOM_APP = ReactDOMClient;

View File

@ -8,8 +8,7 @@
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client';
import * as ReactDOMClient from 'react-dom/client';
import {
activate as activateBackend,
initialize as initializeBackend,
@ -41,7 +40,7 @@ function init(appIframe, devtoolsContainer, appSource) {
const DevTools = createDevTools(contentWindow);
inject(contentDocument, appSource, () => {
createRoot(devtoolsContainer).render(
ReactDOMClient.createRoot(devtoolsContainer).render(
<DevTools
hookNamesModuleLoaderFunction={hookNamesModuleLoaderFunction}
showTabBar={true}
@ -58,4 +57,4 @@ const devtoolsContainer = document.getElementById('devtools');
init(iframe, devtoolsContainer, 'dist/e2e-app.js');
// ReactDOM Test Selector APIs used by Playwright e2e tests
window.parent.REACT_DOM_DEVTOOLS = ReactDOM;
window.parent.REACT_DOM_DEVTOOLS = ReactDOMClient;

View File

@ -124,6 +124,8 @@ const makeConfig = (entry, alias) => ({
},
});
const clientAsSeparateBuild = semver.gte(REACT_VERSION, '19.0.0');
const app = makeConfig(
{
'app-index': './src/app/index.js',
@ -142,8 +144,14 @@ const app = makeConfig(
react: resolve(builtModulesDir, 'react'),
'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'),
'react-devtools-feature-flags': resolveFeatureFlags('shell'),
'react-dom/client': resolve(builtModulesDir, 'react-dom/client'),
'react-dom': resolve(builtModulesDir, 'react-dom/unstable_testing'),
'react-dom/client': resolve(
builtModulesDir,
clientAsSeparateBuild ? 'react-dom/unstable_testing' : 'react-dom/client',
),
'react-dom': resolve(
builtModulesDir,
clientAsSeparateBuild ? 'react-dom' : 'react-dom/unstable_testing',
),
'react-is': resolve(builtModulesDir, 'react-is'),
scheduler: resolve(builtModulesDir, 'scheduler'),
},

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {disableCommentsAsDOMContainers} from 'shared/ReactFeatureFlags';
import {
ELEMENT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from './HTMLNodeType';
export function isValidContainer(node: any): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(!disableCommentsAsDOMContainers &&
node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
// TODO: Remove this function which also includes comment nodes.
// We only use it in places that are currently more relaxed.
export function isValidContainerLegacy(node: any): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}

View File

@ -7,50 +7,4 @@
* @flow
*/
'use strict';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
RootType,
HydrateRootOptions,
CreateRootOptions,
} from './src/client/ReactDOMRoot';
import {
createRoot as createRootImpl,
hydrateRoot as hydrateRootImpl,
__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE as Internals,
} from './';
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
if (__DEV__) {
Internals.usingClientEntryPoint = true;
}
try {
return createRootImpl(container, options);
} finally {
if (__DEV__) {
Internals.usingClientEntryPoint = false;
}
}
}
export function hydrateRoot(
container: Document | Element,
children: ReactNodeList,
options?: HydrateRootOptions,
): RootType {
if (__DEV__) {
Internals.usingClientEntryPoint = true;
}
try {
return hydrateRootImpl(container, children, options);
} finally {
if (__DEV__) {
Internals.usingClientEntryPoint = false;
}
}
}
export {createRoot, hydrateRoot, version} from './src/client/ReactDOMClient';

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternals';
export {
createPortal,
createRoot,
hydrateRoot,
flushSync,
unstable_batchedUpdates,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
requestFormReset,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
} from './src/client/ReactDOM';

View File

@ -7,25 +7,19 @@
* @flow
*/
// Export all exports so that they're available in tests.
// We can't use export * from in Flow for some reason.
export {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternals';
export {
createPortal,
createRoot,
hydrateRoot,
flushSync,
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
requestFormReset,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
requestFormReset,
unstable_batchedUpdates,
useFormState,
useFormStatus,
version,
} from './src/client/ReactDOM';
} from './src/shared/ReactDOM';

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternals';
export {
createPortal,
createRoot,
hydrateRoot,
flushSync,
unstable_batchedUpdates,
useFormStatus,
useFormState,
requestFormReset,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
} from './src/client/ReactDOM';

View File

@ -1,25 +1,38 @@
'use strict';
var m = require('react-dom');
if (process.env.NODE_ENV === 'production') {
exports.createRoot = m.createRoot;
exports.hydrateRoot = m.hydrateRoot;
} else {
var i = m.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
exports.createRoot = function (c, o) {
i.usingClientEntryPoint = true;
try {
return m.createRoot(c, o);
} finally {
i.usingClientEntryPoint = false;
}
};
exports.hydrateRoot = function (c, h, o) {
i.usingClientEntryPoint = true;
try {
return m.hydrateRoot(c, h, o);
} finally {
i.usingClientEntryPoint = false;
}
};
function checkDCE() {
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined' ||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== 'function'
) {
return;
}
if (process.env.NODE_ENV !== 'production') {
// This branch is unreachable because this function is only called
// in production, but the condition is true only in development.
// Therefore if the branch is still here, dead code elimination wasn't
// properly applied.
// Don't change the message. React DevTools relies on it. Also make sure
// this message doesn't occur elsewhere in this function, or it will cause
// a false positive.
throw new Error('^_^');
}
try {
// Verify that the code above has been dead code eliminated (DCE'd).
__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);
} catch (err) {
// DevTools shouldn't crash React, no matter what.
// We should still report in case we break this code.
console.error(err);
}
}
if (process.env.NODE_ENV === 'production') {
// DCE check should happen before ReactDOM bundle executes so that
// DevTools can report bad minification during injection.
checkDCE();
module.exports = require('./cjs/react-dom-client.production.js');
} else {
module.exports = require('./cjs/react-dom-client.development.js');
}

View File

@ -32,7 +32,7 @@ if (process.env.NODE_ENV === 'production') {
// DCE check should happen before ReactDOM bundle executes so that
// DevTools can report bad minification during injection.
checkDCE();
module.exports = require('./cjs/react-dom.profiling.js');
module.exports = require('./cjs/react-dom-profiling.profiling.js');
} else {
module.exports = require('./cjs/react-dom.development.js');
module.exports = require('./cjs/react-dom-profiling.development.js');
}

View File

@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-dom-server-rendering-stub.production.js');
} else {
module.exports = require('./cjs/react-dom-server-rendering-stub.development.js');
}

View File

@ -31,7 +31,6 @@
"profiling.js",
"profiling.react-server.js",
"react-dom.react-server.js",
"server-rendering-stub.js",
"server.browser.js",
"server.bun.js",
"server.edge.js",
@ -107,7 +106,6 @@
"react-server": "./static.react-server.js",
"default": "./static.node.js"
},
"./server-rendering-stub": "./server-rendering-stub.js",
"./profiling": {
"react-server": "./profiling.react-server.js",
"default": "./profiling.js"

12
packages/react-dom/profiling.js vendored Normal file
View File

@ -0,0 +1,12 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// This entrypoint should track the /client entrypoint
export * from './client';
export * from './index';

View File

@ -1,61 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternals';
export {
createPortal,
flushSync,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
useFormStatus,
useFormState,
unstable_batchedUpdates,
} from './src/server/ReactDOMServerRenderingStub';
import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
import {
useFormStatus,
useFormState,
} from './src/server/ReactDOMServerRenderingStub';
import type {Awaited} from 'shared/ReactTypes';
export function experimental_useFormStatus(): FormStatus {
if (__DEV__) {
console.error(
'useFormStatus is now in canary. Remove the experimental_ prefix. ' +
'The prefixed alias will be removed in an upcoming release.',
);
}
return useFormStatus();
}
export function experimental_useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
if (__DEV__) {
console.error(
'useFormState is now in canary. Remove the experimental_ prefix. ' +
'The prefixed alias will be removed in an upcoming release.',
);
}
return useFormState(action, initialState, permalink);
}

View File

@ -9,7 +9,7 @@
import {isEnabled} from 'react-dom-bindings/src/events/ReactDOMEventListener';
import Internals from './src/ReactDOMSharedInternalsFB';
import Internals from './ReactDOMSharedInternalsFB';
// For classic WWW builds, include a few internals that are already in use.
Object.assign((Internals: any), {
@ -18,6 +18,8 @@ Object.assign((Internals: any), {
},
});
export {Internals as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE};
export {
createPortal,
flushSync,
@ -33,7 +35,7 @@ export {
preinit,
preinitModule,
version,
} from './src/client/ReactDOMFB';
} from './client/ReactDOMClientFB';
export {
createRoot,
@ -43,6 +45,4 @@ export {
findDOMNode,
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './src/client/ReactDOMRootFB';
export {Internals as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE};
} from './client/ReactDOMRootFB';

View File

@ -7,7 +7,8 @@
* @flow
*/
export {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternalsFB';
export {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './ReactDOMSharedInternalsFB';
export {
createPortal,
flushSync,
@ -24,6 +25,6 @@ export {
preinit,
preinitModule,
version,
} from './src/client/ReactDOMFB';
} from './client/ReactDOMClientFB';
export {createRoot, hydrateRoot} from './src/client/ReactDOMRootFB';
export {createRoot, hydrateRoot} from './client/ReactDOMRootFB';

View File

@ -20,11 +20,6 @@ type ReactDOMInternals = {
| ((
componentOrElement: React$Component<any, any>,
) => null | Element | Text),
usingClientEntryPoint: boolean,
};
export type ReactDOMInternalsDev = ReactDOMInternals & {
usingClientEntryPoint: boolean,
};
function noop() {}
@ -52,7 +47,6 @@ const Internals: ReactDOMInternals = {
d /* ReactDOMCurrentDispatcher */: DefaultDispatcher,
p /* currentUpdatePriority */: NoEventPriority,
findDOMNode: null,
usingClientEntryPoint: false,
};
export default Internals;

View File

@ -7,23 +7,8 @@
* @flow
*/
export {
createPortal,
flushSync,
unstable_batchedUpdates,
unstable_createEventHandle,
useFormStatus,
useFormState,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
} from './index.modern.fb.js';
export {createRoot, hydrateRoot} from './client.js';
export * from './ReactDOMFB';
export {
createComponentSelector,
createHasPseudoClassSelector,

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 * from './ReactDOMFB.modern';
export {
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
createTestNameSelector,
createTextSelector,
getFindAllNodesFailureDescription,
findAllNodes,
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';

View File

@ -22,11 +22,11 @@ describe('ReactDOM', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
findDOMNode =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode;
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
act = require('internal-test-utils').act;
});

View File

@ -47,28 +47,6 @@ describe('ReactDOMRoot', () => {
expect(container.textContent).toEqual('Hi');
});
// @gate !www || !__DEV__
it('warns if you import createRoot from react-dom', async () => {
expect(() => ReactDOM.createRoot(container)).toErrorDev(
'You are importing createRoot from "react-dom" which is not supported. ' +
'You should instead import it from "react-dom/client".',
{
withoutStack: true,
},
);
});
// @gate !www || !__DEV__
it('warns if you import hydrateRoot from react-dom', async () => {
expect(() => ReactDOM.hydrateRoot(container, null)).toErrorDev(
'You are importing hydrateRoot from "react-dom" which is not supported. ' +
'You should instead import it from "react-dom/client".',
{
withoutStack: true,
},
);
});
it('warns if a callback parameter is provided to render', async () => {
const callback = jest.fn();
const root = ReactDOMClient.createRoot(container);

View File

@ -1479,7 +1479,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi');
});
// @gate experimental || www
// @gate www
it('blocks updates to hydrate the content first if props changed at idle priority', async () => {
let suspend = false;
let resolve;

View File

@ -1333,7 +1333,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
await waitForAll(['App', 'C', 'B', 'A']);
});
// @gate experimental || www
// @gate www
it('hydrates before an update even if hydration moves away from it', async () => {
function Child({text}) {
Scheduler.log(text);
@ -1677,7 +1677,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
expect(initialSpan).toBe(spanRef);
});
// @gate experimental || www
// @gate www
it('can force hydration in response to continuous update', async () => {
function Child({text}) {
Scheduler.log(`Child ${text}`);
@ -1746,7 +1746,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
expect(initialSpan).toBe(spanRef);
});
// @gate experimental || www
// @gate www
it('regression test: can unwind context on selective hydration interruption', async () => {
const Context = React.createContext('DefaultContext');

View File

@ -26,10 +26,10 @@ describe('ReactDOMSuspensePlaceholder', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
findDOMNode =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode;
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;

View File

@ -26,10 +26,10 @@ describe('ReactEmptyComponent', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
findDOMNode =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode;
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
act = InternalTestUtils.act;

View File

@ -21,10 +21,10 @@ describe('ReactLegacyCompositeComponent', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
findDOMNode =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode;
ReactDOMClient = require('react-dom/client');
PropTypes = require('prop-types');
act = require('internal-test-utils').act;
});

View File

@ -24,10 +24,10 @@ describe('ReactUpdates', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
findDOMNode =
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode;
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
Scheduler = require('scheduler');

View File

@ -1,99 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
.findDOMNode,
).toBe(null);
expect(ReactDOM.hydrate).toBe(undefined);
expect(ReactDOM.render).toBe(undefined);
expect(ReactDOM.unmountComponentAtNode).toBe(undefined);
expect(ReactDOM.unstable_createEventHandle).toBe(undefined);
expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined);
expect(ReactDOM.unstable_runWithPriority).toBe(undefined);
});
it('provides preload, preloadModule, preinit, and preinitModule exports', async () => {
function App() {
ReactDOM.preload('foo', {as: 'style'});
ReactDOM.preloadModule('foomodule');
ReactDOM.preinit('bar', {as: 'style'});
ReactDOM.preinitModule('barmodule');
return <div>foo</div>;
}
const html = ReactDOMFizzServer.renderToString(<App />);
expect(html).toEqual(
'<link rel="stylesheet" href="bar" data-precedence="default"/><script src="barmodule" type="module" async=""></script><link rel="preload" href="foo" as="style"/><link rel="modulepreload" href="foomodule"/><div>foo</div>',
);
});
it('provides preconnect and prefetchDNS exports', async () => {
function App() {
ReactDOM.preconnect('foo', {crossOrigin: 'use-credentials'});
ReactDOM.prefetchDNS('bar');
return <div>foo</div>;
}
const html = ReactDOMFizzServer.renderToString(<App />);
expect(html).toEqual(
'<link rel="preconnect" href="foo" crossorigin="use-credentials"/><link href="bar" rel="dns-prefetch"/><div>foo</div>',
);
});
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);
});
// @gate enableAsyncActions
it('exports useFormStatus', async () => {
function App() {
const {pending} = ReactDOM.useFormStatus();
return 'Pending: ' + pending;
}
const result = await ReactDOMFizzServer.renderToStaticMarkup(<App />);
expect(result).toEqual('Pending: false');
});
});

View File

@ -1,204 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
RootType,
HydrateRootOptions,
CreateRootOptions,
} from './ReactDOMRoot';
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
import {
createRoot as createRootImpl,
hydrateRoot as hydrateRootImpl,
isValidContainer,
} from './ReactDOMRoot';
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
import {runWithPriority} from 'react-dom-bindings/src/client/ReactDOMUpdatePriority';
import {flushSync as flushSyncIsomorphic} from '../shared/ReactDOMFlushSync';
import {
flushSyncFromReconciler as flushSyncWithoutWarningIfAlreadyRendering,
isAlreadyRendering,
injectIntoDevTools,
findHostInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import ReactVersion from 'shared/ReactVersion';
import {getClosestInstanceFromNode} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import Internals from '../ReactDOMSharedInternals';
export {
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
} from '../shared/ReactDOMFloat';
export {
useFormStatus,
useFormState,
requestFormReset,
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
if (__DEV__) {
if (
typeof Map !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Map has no prototype
Map.prototype == null ||
typeof Map.prototype.forEach !== 'function' ||
typeof Set !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype
Set.prototype == null ||
typeof Set.prototype.clear !== 'function' ||
typeof Set.prototype.forEach !== 'function'
) {
console.error(
'React depends on Map and Set built-in types. Make sure that you load a ' +
'polyfill in older browsers. https://react.dev/link/react-polyfills',
);
}
}
function createPortal(
children: ReactNodeList,
container: Element | DocumentFragment,
key: ?string = null,
): React$Portal {
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.');
}
// TODO: pass ReactDOM portal implementation as third argument
// $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it.
return createPortalImpl(children, container, null, key);
}
function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
if (__DEV__) {
if (!Internals.usingClientEntryPoint) {
console.error(
'You are importing createRoot from "react-dom" which is not supported. ' +
'You should instead import it from "react-dom/client".',
);
}
}
return createRootImpl(container, options);
}
function hydrateRoot(
container: Document | Element,
initialChildren: ReactNodeList,
options?: HydrateRootOptions,
): RootType {
if (__DEV__) {
if (!Internals.usingClientEntryPoint) {
console.error(
'You are importing hydrateRoot from "react-dom" which is not supported. ' +
'You should instead import it from "react-dom/client".',
);
}
}
return hydrateRootImpl(container, initialChildren, options);
}
// Overload the definition to the two valid signatures.
// Warning, this opts-out of checking the function body.
declare function flushSyncFromReconciler<R>(fn: () => R): R;
// eslint-disable-next-line no-redeclare
declare function flushSyncFromReconciler(): void;
// eslint-disable-next-line no-redeclare
function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
if (__DEV__) {
if (isAlreadyRendering()) {
console.error(
'flushSync was called from inside a lifecycle method. React cannot ' +
'flush when React is already rendering. Consider moving this call to ' +
'a scheduler task or micro task.',
);
}
}
return flushSyncWithoutWarningIfAlreadyRendering(fn);
}
const flushSync: typeof flushSyncIsomorphic = disableLegacyMode
? flushSyncIsomorphic
: flushSyncFromReconciler;
function findDOMNode(
componentOrElement: React$Component<any, any>,
): null | Element | Text {
return findHostInstance(componentOrElement);
}
// Expose findDOMNode on internals
Internals.findDOMNode = findDOMNode;
function unstable_batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
// batchedUpdates was a legacy mode feature that is a no-op outside of
// legacy mode. In 19, we made it an actual no-op, but we're keeping it
// for now since there may be libraries that still include it.
return fn(a);
}
export {
createPortal,
unstable_batchedUpdates,
flushSync,
ReactVersion as version,
// exposeConcurrentModeAPIs
createRoot,
hydrateRoot,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
// TODO: Remove this once callers migrate to alternatives.
// This should only be used by React internals.
runWithPriority as unstable_runWithPriority,
};
const foundDevTools = injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-dom',
});
if (__DEV__) {
if (!foundDevTools && canUseDOM && window.top === window.self) {
// If we're in Chrome or Firefox, provide a download link if not installed.
if (
(navigator.userAgent.indexOf('Chrome') > -1 &&
navigator.userAgent.indexOf('Edge') === -1) ||
navigator.userAgent.indexOf('Firefox') > -1
) {
const protocol = window.location.protocol;
// Don't warn in exotic cases like chrome-extension://.
if (/^(https?|file):$/.test(protocol)) {
// eslint-disable-next-line react-internal/no-production-logging
console.info(
'%cDownload the React DevTools ' +
'for a better development experience: ' +
'https://react.dev/link/react-devtools' +
(protocol === 'file:'
? '\nYou might need to use a local HTTP server (instead of file://): ' +
'https://react.dev/link/react-devtools-faq'
: ''),
'font-weight:bold',
);
}
}
}
}

View File

@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {createRoot, hydrateRoot} from './ReactDOMRoot';
import {
injectIntoDevTools,
findHostInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import ReactVersion from 'shared/ReactVersion';
import {getClosestInstanceFromNode} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import Internals from 'shared/ReactDOMSharedInternals';
if (__DEV__) {
if (
typeof Map !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Map has no prototype
Map.prototype == null ||
typeof Map.prototype.forEach !== 'function' ||
typeof Set !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype
Set.prototype == null ||
typeof Set.prototype.clear !== 'function' ||
typeof Set.prototype.forEach !== 'function'
) {
console.error(
'React depends on Map and Set built-in types. Make sure that you load a ' +
'polyfill in older browsers. https://react.dev/link/react-polyfills',
);
}
}
function findDOMNode(
componentOrElement: React$Component<any, any>,
): null | Element | Text {
return findHostInstance(componentOrElement);
}
// Expose findDOMNode on internals
Internals.findDOMNode = findDOMNode;
export {ReactVersion as version, createRoot, hydrateRoot};
const foundDevTools = injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-dom',
});
if (__DEV__) {
if (!foundDevTools && canUseDOM && window.top === window.self) {
// If we're in Chrome or Firefox, provide a download link if not installed.
if (
(navigator.userAgent.indexOf('Chrome') > -1 &&
navigator.userAgent.indexOf('Edge') === -1) ||
navigator.userAgent.indexOf('Firefox') > -1
) {
const protocol = window.location.protocol;
// Don't warn in exotic cases like chrome-extension://.
if (/^(https?|file):$/.test(protocol)) {
// eslint-disable-next-line react-internal/no-production-logging
console.info(
'%cDownload the React DevTools ' +
'for a better development experience: ' +
'https://react.dev/link/react-devtools' +
(protocol === 'file:'
? '\nYou might need to use a local HTTP server (instead of file://): ' +
'https://react.dev/link/react-devtools-faq'
: ''),
'font-weight:bold',
);
}
}
}
}

View File

@ -10,7 +10,7 @@
import type {ReactNodeList} from 'shared/ReactTypes';
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
import {isValidContainer} from './ReactDOMRoot';
import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer';
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
import {runWithPriority} from 'react-dom-bindings/src/client/ReactDOMUpdatePriority';
import {flushSync as flushSyncIsomorphic} from '../shared/ReactDOMFlushSync';

View File

@ -13,11 +13,11 @@ import type {
TransitionTracingCallbacks,
} from 'react-reconciler/src/ReactInternalTypes';
import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer';
import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
import {
allowConcurrentByDefault,
disableCommentsAsDOMContainers,
enableAsyncActions,
} from 'shared/ReactFeatureFlags';
@ -82,12 +82,7 @@ import {
unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {
ELEMENT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from 'react-dom-bindings/src/client/HTMLNodeType';
import {COMMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
import {
createContainer,
@ -357,31 +352,6 @@ export function hydrateRoot(
return new ReactDOMHydrationRoot(root);
}
export function isValidContainer(node: any): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(!disableCommentsAsDOMContainers &&
node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
// TODO: Remove this function which also includes comment nodes.
// We only use it in places that are currently more relaxed.
export function isValidContainerLegacy(node: any): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
function warnIfReactDOMContainerInDEV(container: any) {
if (__DEV__) {
if (isContainerMarkedAsRoot(container)) {

View File

@ -36,7 +36,7 @@ import {
unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {isValidContainerLegacy} from './ReactDOMRoot';
import {isValidContainerLegacy} from 'react-dom-bindings/src/client/ReactDOMContainer';
import {
DOCUMENT_NODE,
ELEMENT_NODE,

View File

@ -1,48 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {
preinit,
preinitModule,
preload,
preloadModule,
preconnect,
prefetchDNS,
} from '../shared/ReactDOMFloat';
export {
useFormStatus,
useFormState,
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
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.',
);
}
// on the server we just call the callback because there is
// not update mechanism. Really this should not be called on the
// server but since the semantics are generally clear enough we
// can provide this trivial implementation.
function batchedUpdates<A, R>(fn: A => R, a: A): R {
return fn(a);
}
export {batchedUpdates as unstable_batchedUpdates};

View File

@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import ReactVersion from 'shared/ReactVersion';
import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {flushSync} from './ReactDOMFlushSync';
import {
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
} from './ReactDOMFloat';
import {
requestFormReset,
useFormStatus,
useFormState,
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
if (__DEV__) {
if (
typeof Map !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Map has no prototype
Map.prototype == null ||
typeof Map.prototype.forEach !== 'function' ||
typeof Set !== 'function' ||
// $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype
Set.prototype == null ||
typeof Set.prototype.clear !== 'function' ||
typeof Set.prototype.forEach !== 'function'
) {
console.error(
'React depends on Map and Set built-in types. Make sure that you load a ' +
'polyfill in older browsers. https://reactjs.org/link/react-polyfills',
);
}
}
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
// batchedUpdates is now just a passthrough noop
return fn(a);
}
function createPortal(
children: ReactNodeList,
container: Element | DocumentFragment,
key: ?string = null,
): React$Portal {
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.');
}
// TODO: pass ReactDOM portal implementation as third argument
// $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it.
return createPortalImpl(children, container, null, key);
}
export {
ReactVersion as version,
createPortal,
flushSync,
batchedUpdates as unstable_batchedUpdates,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
requestFormReset,
useFormStatus,
useFormState,
};

View File

@ -1,45 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {
createPortal,
findDOMNode,
flushSync,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
} from './index.classic.fb.js';
export {createRoot, hydrateRoot} from './client.js';
export {
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
createTestNameSelector,
createTextSelector,
getFindAllNodesFailureDescription,
findAllNodes,
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';

View File

@ -7,23 +7,7 @@
* @flow
*/
export {
createPortal,
flushSync,
unstable_batchedUpdates,
useFormStatus,
useFormState,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
} from './index.experimental.js';
export {createRoot, hydrateRoot} from './client.js';
export * from './client.js';
export {
createComponentSelector,

View File

@ -7,34 +7,4 @@
* @flow
*/
export {
createPortal,
flushSync,
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
} from './index.js';
export {createRoot, hydrateRoot} from './client.js';
export {
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
createTestNameSelector,
createTextSelector,
getFindAllNodesFailureDescription,
findAllNodes,
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';
export * from './client.js';

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 {
createPortal,
flushSync,
unstable_batchedUpdates,
useFormStatus,
useFormState,
prefetchDNS,
preconnect,
preload,
preloadModule,
preinit,
preinitModule,
version,
__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
} from './index.stable.js';
export {createRoot, hydrateRoot} from './client.js';

View File

@ -1172,19 +1172,22 @@ describe('ReactFlightDOMBrowser', () => {
root.render(<App />);
});
expect(document.head.innerHTML).toBe(
// Currently the react-dom entrypoint loads the fiber implementation
// even if you never pull in the the client APIs. this causes the fiber
// dispatcher to be present even for Flight ReactDOM calls. This is not what
// you would have in a real application but given we're runnign flight and
// fiber the in the same scope it's unavoidable until we make the entrypoint
// not automatically pull in the fiber implementation. This test currently
// asserts this be demonstrating that the preload call after the await point
// is written to the document before the call before it. We still demonstrate that
// flight handled the sync call because if the fiber implementation did it would appear
// before the after call. In the future we will change this assertion once the fiber
// implementation no long automatically gets pulled in
'<link rel="preload" href="after" as="style"><link rel="preload" href="before" as="style">',
// '<link rel="preload" href="before" as="style">',
gate(f => f.www)
? // The www entrypoints for ReactDOM and ReactDOMClient are unified so even
// when you pull in just the top level the dispatcher for the Document is
// loaded alongside it. In a normal environment there would be nothing to dispatch to
// in a server environment so the preload calls would still only be dispatched to fizz
// or the browser but not both. However in this contrived test environment the preloads
// are being dispatched simultaneously causing an extraneous preload to show up. This test currently
// asserts this be demonstrating that the preload call after the await point
// is written to the document before the call before it. We still demonstrate that
// flight handled the sync call because if the fiber implementation did it would appear
// before the after call. In the future we will change this assertion once the fiber
// implementation no long automatically gets pulled in
'<link rel="preload" href="after" as="style"><link rel="preload" href="before" as="style">'
: // For other release channels the client and isomorphic entrypoints are separate and thus we only
// observe the expected preload from before the first await
'<link rel="preload" href="before" as="style">',
);
expect(container.innerHTML).toBe('<p>hello world</p>');
});

View File

@ -12,8 +12,43 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
// .stable.js
// .experimental.js
// .js
if (isFBBundle) {
// FB builds for react-dom need to alias both react-dom and react-dom/client to the same
// entrypoint since there is only a single build for them.
if (
resolvedEntry.endsWith('react-dom/index.js') ||
resolvedEntry.endsWith('react-dom/client.js') ||
resolvedEntry.endsWith('react-dom/unstable_testing.js')
) {
let specifier;
let entrypoint;
if (resolvedEntry.endsWith('index.js')) {
specifier = 'react-dom';
entrypoint = __EXPERIMENTAL__
? 'src/ReactDOMFB.modern.js'
: 'src/ReactDOMFB.js';
} else if (resolvedEntry.endsWith('client.js')) {
specifier = 'react-dom/client';
entrypoint = __EXPERIMENTAL__
? 'src/ReactDOMFB.modern.js'
: 'src/ReactDOMFB.js';
} else {
// must be unstable_testing
specifier = 'react-dom/unstable_testing';
entrypoint = __EXPERIMENTAL__
? 'src/ReactDOMTestingFB.modern.js'
: 'src/ReactDOMTestingFB.js';
}
resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint);
if (fs.existsSync(resolvedEntry)) {
return resolvedEntry;
}
const fbReleaseChannel = __EXPERIMENTAL__ ? 'www-modern' : 'www-classic';
throw new Error(
`${fbReleaseChannel} tests are expected to alias ${specifier} to ${entrypoint} but this file was not found`
);
}
const resolvedFBEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'

View File

@ -151,14 +151,7 @@ const bundles = [
/******* React DOM *******/
{
bundleTypes: [
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
],
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-dom',
global: 'ReactDOM',
@ -166,12 +159,43 @@ const bundles = [
wrapWithModuleBoundaries: true,
externals: ['react'],
},
/******* React DOM Client *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-dom/client',
global: 'ReactDOM',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: true,
externals: ['react', 'react-dom'],
},
/******* React DOM Profiling (Client) *******/
{
bundleTypes: [NODE_DEV, NODE_PROFILING],
moduleType: RENDERER,
entry: 'react-dom/profiling',
global: 'ReactDOM',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: true,
externals: ['react', 'react-dom'],
},
/******* React DOM FB *******/
{
bundleTypes: [FB_WWW_DEV, FB_WWW_PROD, FB_WWW_PROFILING],
moduleType: RENDERER,
entry: 'react-dom/src/ReactDOMFB.js',
global: 'ReactDOM',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: true,
externals: ['react'],
},
/******* React DOM React Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-dom/src/ReactDOMServer.js',
entry: 'react-dom/src/ReactDOMReactServer.js',
name: 'react-dom.react-server',
condition: 'react-server',
global: 'ReactDOM',
@ -191,13 +215,22 @@ const bundles = [
externals: ['react', 'react-dom'],
},
/******* React DOM - Testing *******/
{
moduleType: RENDERER,
bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD] : [],
entry: 'react-dom/unstable_testing',
global: 'ReactDOMTesting',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
/******* React DOM - www - Testing *******/
{
moduleType: RENDERER,
bundleTypes: __EXPERIMENTAL__
? [FB_WWW_DEV, FB_WWW_PROD, NODE_DEV, NODE_PROD]
: [FB_WWW_DEV, FB_WWW_PROD],
entry: 'react-dom/unstable_testing',
bundleTypes: [FB_WWW_DEV, FB_WWW_PROD],
entry: 'react-dom/src/ReactDOMTestingFB.js',
global: 'ReactDOMTesting',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: false,
@ -306,18 +339,6 @@ const bundles = [
externals: [],
},
/******* React DOM Server Render Stub *******/
{
bundleTypes: [NODE_DEV, NODE_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 Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],

View File

@ -91,9 +91,9 @@ const forks = Object.freeze({
) => {
if (
entry === 'react-dom' ||
entry === 'react-dom/server-rendering-stub' ||
entry === 'react-dom/src/ReactDOMServer.js' ||
entry === 'react-dom/unstable_testing'
entry === 'react-dom/src/ReactDOMFB.js' ||
entry === 'react-dom/src/ReactDOMTestingFB.js' ||
entry === 'react-dom/src/ReactDOMServer.js'
) {
if (
bundleType === FB_WWW_DEV ||

View File

@ -29,7 +29,6 @@ const knownGlobals = Object.freeze({
react: 'React',
'react-dom': 'ReactDOM',
'react-dom/server': 'ReactDOMServer',
'react-interactions/events/tap': 'ReactEventsTap',
scheduler: 'Scheduler',
'scheduler/unstable_mock': 'SchedulerMock',
ReactNativeInternalFeatureFlags: 'ReactNativeInternalFeatureFlags',

View File

@ -11,21 +11,23 @@ module.exports = [
shortName: 'dom-node',
entryPoints: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/client',
'react-dom/profiling',
'react-dom/unstable_testing',
'react-dom/src/ReactDOMReactServer.js',
'react-dom/src/server/react-dom-server.node.js',
'react-dom/static.node',
'react-dom/test-utils',
'react-dom/server-rendering-stub',
'react-dom/unstable_server-external-runtime',
'react-server-dom-webpack/server.node.unbundled',
'react-server-dom-webpack/client.node.unbundled',
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
@ -45,7 +47,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
@ -62,6 +63,7 @@ module.exports = [
'react-dom',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
@ -82,7 +84,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
@ -99,6 +100,7 @@ module.exports = [
'react-dom',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
@ -119,7 +121,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
@ -136,6 +137,7 @@ module.exports = [
'react-dom',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
@ -157,7 +159,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
@ -166,9 +167,17 @@ module.exports = [
},
{
shortName: 'dom-bun',
entryPoints: ['react-dom', 'react-dom/src/server/react-dom-server.bun.js'],
entryPoints: [
'react-dom',
'react-dom/client',
'react-dom/profiling',
'react-dom/unstable_testing',
'react-dom/src/server/react-dom-server.bun.js',
],
paths: [
'react-dom',
'react-dom/client',
'react-dom/profiling',
'react-dom/server.bun',
'react-dom/src/server/react-dom-server.bun',
'react-dom/src/server/ReactDOMFizzServerBun.js',
@ -182,19 +191,21 @@ module.exports = [
shortName: 'dom-browser',
entryPoints: [
'react-dom',
'react-dom/client',
'react-dom/profiling',
'react-dom/unstable_testing',
'react-dom/src/server/react-dom-server.browser.js',
'react-dom/static.browser',
'react-dom/server-rendering-stub',
'react-dom/unstable_server-external-runtime',
'react-server-dom-webpack/server.browser',
'react-server-dom-webpack/client.browser',
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server.browser',
'react-dom/static.browser',
'react-dom/unstable_testing',
@ -223,8 +234,9 @@ module.exports = [
entryPoints: ['react-server-dom-esm/client.browser'],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom-bindings',
@ -236,7 +248,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
],
isFlowTyped: true,
@ -251,6 +262,7 @@ module.exports = [
paths: [
'react-dom',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom-bindings',
@ -266,7 +278,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
],
isFlowTyped: true,
@ -282,9 +293,10 @@ module.exports = [
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server.edge',
'react-dom/static.edge',
'react-dom/unstable_testing',
@ -316,9 +328,10 @@ module.exports = [
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server.edge',
'react-dom/static.edge',
'react-dom/unstable_testing',
@ -350,9 +363,10 @@ module.exports = [
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
@ -370,7 +384,6 @@ module.exports = [
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'react-interactions',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
@ -385,7 +398,7 @@ module.exports = [
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-server-dom-webpack',
'react-dom/src/server/ReactDOMLegacyServerImpl.js', // not an entrypoint, but only usable in *Browser and *Node files
@ -400,10 +413,16 @@ module.exports = [
},
{
shortName: 'dom-fb',
entryPoints: ['react-server-dom-fb/src/ReactDOMServerFB.js'],
entryPoints: [
'react-dom/src/ReactDOMFB.js',
'react-dom/src/ReactDOMTestingFB.js',
'react-server-dom-fb/src/ReactDOMServerFB.js',
],
paths: [
'react-dom',
'react-dom/src/ReactDOMServer.js',
'react-dom/src/ReactDOMFB.js',
'react-dom/src/ReactDOMTestingFB.js',
'react-dom/src/ReactDOMReactServer.js',
'react-dom-bindings',
'react-server-dom-fb',
'shared/ReactDOMSharedInternals',

View File

@ -16,8 +16,6 @@ const esNextPaths = [
// Source files
'packages/*/src/**/*.js',
'packages/dom-event-testing-library/**/*.js',
'packages/react-interactions/**/*.js',
'packages/react-interactions/**/*.js',
'packages/shared/**/*.js',
// Shims and Flow environment
'scripts/flow/*.js',