mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
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
223 lines
7.5 KiB
JavaScript
223 lines
7.5 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const nodePath = require('path');
|
|
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
|
|
|
|
function resolveEntryFork(resolvedEntry, isFBBundle) {
|
|
// Pick which entry point fork to use:
|
|
// .modern.fb.js
|
|
// .classic.fb.js
|
|
// .fb.js
|
|
// .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'
|
|
);
|
|
if (fs.existsSync(resolvedFBEntry)) {
|
|
return resolvedFBEntry;
|
|
}
|
|
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
|
|
if (fs.existsSync(resolvedGenericFBEntry)) {
|
|
return resolvedGenericFBEntry;
|
|
}
|
|
// Even if it's a FB bundle we fallthrough to pick stable or experimental if we don't have an FB fork.
|
|
}
|
|
const resolvedForkedEntry = resolvedEntry.replace(
|
|
'.js',
|
|
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
|
|
);
|
|
if (fs.existsSync(resolvedForkedEntry)) {
|
|
return resolvedForkedEntry;
|
|
}
|
|
// Just use the plain .js one.
|
|
return resolvedEntry;
|
|
}
|
|
|
|
function mockReact() {
|
|
jest.mock('react', () => {
|
|
const resolvedEntryPoint = resolveEntryFork(
|
|
require.resolve('react'),
|
|
global.__WWW__
|
|
);
|
|
return jest.requireActual(resolvedEntryPoint);
|
|
});
|
|
// Make it possible to import this module inside
|
|
// the React package itself.
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
return jest.requireActual('react/src/ReactSharedInternalsClient');
|
|
});
|
|
}
|
|
|
|
// When we want to unmock React we really need to mock it again.
|
|
global.__unmockReact = mockReact;
|
|
|
|
mockReact();
|
|
|
|
jest.mock('react/react.react-server', () => {
|
|
// If we're requiring an RSC environment, use those internals instead.
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
return jest.requireActual('react/src/ReactSharedInternalsServer');
|
|
});
|
|
const resolvedEntryPoint = resolveEntryFork(
|
|
require.resolve('react/src/ReactServer'),
|
|
global.__WWW__
|
|
);
|
|
return jest.requireActual(resolvedEntryPoint);
|
|
});
|
|
|
|
// When testing the custom renderer code path through `react-reconciler`,
|
|
// turn the export into a function, and use the argument as host config.
|
|
const shimHostConfigPath = 'react-reconciler/src/ReactFiberConfig';
|
|
jest.mock('react-reconciler', () => {
|
|
return config => {
|
|
jest.mock(shimHostConfigPath, () => config);
|
|
return jest.requireActual('react-reconciler');
|
|
};
|
|
});
|
|
const shimServerStreamConfigPath = 'react-server/src/ReactServerStreamConfig';
|
|
const shimServerConfigPath = 'react-server/src/ReactFizzConfig';
|
|
const shimFlightServerConfigPath = 'react-server/src/ReactFlightServerConfig';
|
|
jest.mock('react-server', () => {
|
|
return config => {
|
|
jest.mock(shimServerStreamConfigPath, () => config);
|
|
jest.mock(shimServerConfigPath, () => config);
|
|
return jest.requireActual('react-server');
|
|
};
|
|
});
|
|
jest.mock('react-server/flight', () => {
|
|
return config => {
|
|
jest.mock(shimServerStreamConfigPath, () => config);
|
|
jest.mock(shimServerConfigPath, () => config);
|
|
jest.mock('react-server/src/ReactFlightServerConfigBundlerCustom', () => ({
|
|
isClientReference: config.isClientReference,
|
|
isServerReference: config.isServerReference,
|
|
getClientReferenceKey: config.getClientReferenceKey,
|
|
resolveClientReferenceMetadata: config.resolveClientReferenceMetadata,
|
|
}));
|
|
jest.mock(shimFlightServerConfigPath, () =>
|
|
jest.requireActual(
|
|
'react-server/src/forks/ReactFlightServerConfig.custom'
|
|
)
|
|
);
|
|
return jest.requireActual('react-server/flight');
|
|
};
|
|
});
|
|
const shimFlightClientConfigPath = 'react-client/src/ReactFlightClientConfig';
|
|
jest.mock('react-client/flight', () => {
|
|
return config => {
|
|
jest.mock(shimFlightClientConfigPath, () => config);
|
|
return jest.requireActual('react-client/flight');
|
|
};
|
|
});
|
|
|
|
const configPaths = [
|
|
'react-reconciler/src/ReactFiberConfig',
|
|
'react-client/src/ReactFlightClientConfig',
|
|
'react-server/src/ReactServerStreamConfig',
|
|
'react-server/src/ReactFizzConfig',
|
|
'react-server/src/ReactFlightServerConfig',
|
|
];
|
|
|
|
function mockAllConfigs(rendererInfo) {
|
|
configPaths.forEach(path => {
|
|
// We want the reconciler to pick up the host config for this renderer.
|
|
jest.mock(path, () => {
|
|
let idx = path.lastIndexOf('/');
|
|
let forkPath = path.slice(0, idx) + '/forks' + path.slice(idx);
|
|
let parts = rendererInfo.shortName.split('-');
|
|
while (parts.length) {
|
|
try {
|
|
const candidate = `${forkPath}.${parts.join('-')}.js`;
|
|
fs.statSync(nodePath.join(process.cwd(), 'packages', candidate));
|
|
return jest.requireActual(candidate);
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
// try without a part
|
|
}
|
|
parts.pop();
|
|
}
|
|
throw new Error(
|
|
`Expected to find a fork for ${path} but did not find one.`
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
// But for inlined host configs (such as React DOM, Native, etc), we
|
|
// mock their named entry points to establish a host config mapping.
|
|
inlinedHostConfigs.forEach(rendererInfo => {
|
|
if (rendererInfo.shortName === 'custom') {
|
|
// There is no inline entry point for the custom renderers.
|
|
// Instead, it's handled by the generic `react-reconciler` entry point above.
|
|
return;
|
|
}
|
|
rendererInfo.entryPoints.forEach(entryPoint => {
|
|
jest.mock(entryPoint, () => {
|
|
mockAllConfigs(rendererInfo);
|
|
const resolvedEntryPoint = resolveEntryFork(
|
|
require.resolve(entryPoint),
|
|
global.__WWW__
|
|
);
|
|
return jest.requireActual(resolvedEntryPoint);
|
|
});
|
|
});
|
|
});
|
|
|
|
jest.mock('react-server/src/ReactFlightServer', () => {
|
|
// If we're requiring an RSC environment, use those internals instead.
|
|
jest.mock('shared/ReactSharedInternals', () => {
|
|
return jest.requireActual('react/src/ReactSharedInternalsServer');
|
|
});
|
|
return jest.requireActual('react-server/src/ReactFlightServer');
|
|
});
|
|
|
|
// Make it possible to import this module inside
|
|
// the ReactDOM package itself.
|
|
jest.mock('shared/ReactDOMSharedInternals', () =>
|
|
jest.requireActual('react-dom/src/ReactDOMSharedInternals')
|
|
);
|
|
|
|
jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));
|