Files
react/packages/react-dom/src/server/ReactDOMFizzStaticNode.js
Andrew Clark 681a4aa810 Throw if React and React DOM versions don't match (#29236)
Throw an error during module initialization if the version of the
"react-dom" package does not match the version of "react".

We used to be more relaxed about this, because the "react" package
changed so infrequently. However, we now have many more features that
rely on an internal protocol between the two packages, including Hooks,
Float, and the compiler runtime. So it's important that both packages
are versioned in lockstep.

Before this change, a version mismatch would often result in a cryptic
internal error with no indication of the root cause.

Instead, we will now compare the versions during module initialization
and immediately throw an error to catch mistakes as early as possible
and provide a clear error message.
2024-05-28 14:06:30 -04:00

145 lines
4.1 KiB
JavaScript

/**
* 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 {
BootstrapScriptDescriptor,
HeadersDescriptor,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {
PostponedState,
ErrorInfo,
PostponeInfo,
} from 'react-server/src/ReactFizzServer';
import type {ImportMap} from '../shared/ReactDOMTypes';
import {Writable, Readable} from 'stream';
import ReactVersion from 'shared/ReactVersion';
import {
createPrerenderRequest,
startWork,
startFlowing,
abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer';
import {
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
ensureCorrectIsomorphicReactVersion();
type Options = {
identifierPrefix?: string,
namespaceURI?: string,
bootstrapScriptContent?: string,
bootstrapScripts?: Array<string | BootstrapScriptDescriptor>,
bootstrapModules?: Array<string | BootstrapScriptDescriptor>,
progressiveChunkSize?: number,
signal?: AbortSignal,
onError?: (error: mixed, errorInfo: ErrorInfo) => ?string,
onPostpone?: (reason: string, postponeInfo: PostponeInfo) => void,
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
importMap?: ImportMap,
onHeaders?: (headers: HeadersDescriptor) => void,
maxHeadersLength?: number,
};
type StaticResult = {
postponed: null | PostponedState,
prelude: Readable,
};
function createFakeWritable(readable: any): Writable {
// The current host config expects a Writable so we create
// a fake writable for now to push into the Readable.
return ({
write(chunk) {
return readable.push(chunk);
},
end() {
readable.push(null);
},
destroy(error) {
readable.destroy(error);
},
}: any);
}
function prerenderToNodeStream(
children: ReactNodeList,
options?: Options,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const readable: Readable = new Readable({
read() {
startFlowing(request, writable);
},
});
const writable = createFakeWritable(readable);
const result = {
postponed: getPostponedState(request),
prelude: readable,
};
resolve(result);
}
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createPrerenderRequest(
children,
resumableState,
createRenderState(
resumableState,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
options ? options.onHeaders : undefined,
options ? options.maxHeadersLength : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
onAllReady,
undefined,
undefined,
onFatalError,
options ? options.onPostpone : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
export {prerenderToNodeStream, ReactVersion as version};