react/scripts/jest/setupHostConfigs.js
Sebastian Markbåge 6b4646cbd0
Export captureOwnerStacks() only in DEV "react" builds (#29923)
Only with the enableOwnerStacks flag (which is not on in www).

This is a new DEV-only API to be able to implement what we do for
console.error in user space.

This API does not actually include the current stack that you'd get from
`new Error().stack`. That you'd have to add yourself.

This adds the ability to have conditional development exports because we
plan on eventually having separate ESM builds that use the "development"
or "production" export conditions.

NOTE: This removes the export of `act` from `react` in prod (as opposed
to a function that throws) - inline with what we do with other
conditional exports.
2024-06-19 14:19:48 -04:00

244 lines
8.3 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
// or any of those plus .development.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);
const developmentEntry = resolvedEntry.replace('.js', '.development.js');
if (fs.existsSync(developmentEntry)) {
return developmentEntry;
}
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'
);
const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
if (fs.existsSync(devFBEntry)) {
return devFBEntry;
}
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'
);
const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
if (fs.existsSync(devForkedEntry)) {
return devForkedEntry;
}
if (fs.existsSync(resolvedForkedEntry)) {
return resolvedForkedEntry;
}
const plainDevEntry = resolvedEntry.replace('.js', '.development.js');
if (fs.existsSync(plainDevEntry)) {
return plainDevEntry;
}
// Just use the plain .js one.
return resolvedEntry;
}
function mockReact() {
jest.mock('react', () => {
const resolvedEntryPoint = resolveEntryFork(
require.resolve('react'),
global.__WWW__ || global.__XPLAT__,
global.__DEV__
);
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__ || global.__XPLAT__,
global.__DEV__
);
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__ || global.__XPLAT__,
global.__DEV__
);
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'));