mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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.
153 lines
4.8 KiB
JavaScript
153 lines
4.8 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, ReactFormState} from 'shared/ReactTypes';
|
|
import type {
|
|
BootstrapScriptDescriptor,
|
|
HeadersDescriptor,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
import type {ImportMap} from '../shared/ReactDOMTypes';
|
|
import type {ErrorInfo, PostponeInfo} from 'react-server/src/ReactFizzServer';
|
|
|
|
import ReactVersion from 'shared/ReactVersion';
|
|
|
|
import {
|
|
createRequest,
|
|
startWork,
|
|
startFlowing,
|
|
stopFlowing,
|
|
abort,
|
|
} 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,
|
|
nonce?: 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,
|
|
formState?: ReactFormState<any, any> | null,
|
|
onHeaders?: (headers: Headers) => void,
|
|
maxHeadersLength?: number,
|
|
};
|
|
|
|
// TODO: Move to sub-classing ReadableStream.
|
|
type ReactDOMServerReadableStream = ReadableStream & {
|
|
allReady: Promise<void>,
|
|
};
|
|
|
|
function renderToReadableStream(
|
|
children: ReactNodeList,
|
|
options?: Options,
|
|
): Promise<ReactDOMServerReadableStream> {
|
|
return new Promise((resolve, reject) => {
|
|
let onFatalError;
|
|
let onAllReady;
|
|
const allReady = new Promise<void>((res, rej) => {
|
|
onAllReady = res;
|
|
onFatalError = rej;
|
|
});
|
|
|
|
function onShellReady() {
|
|
const stream: ReactDOMServerReadableStream = (new ReadableStream(
|
|
{
|
|
type: 'direct',
|
|
pull: (controller): ?Promise<void> => {
|
|
// $FlowIgnore
|
|
startFlowing(request, controller);
|
|
},
|
|
cancel: (reason): ?Promise<void> => {
|
|
stopFlowing(request);
|
|
abort(request, reason);
|
|
},
|
|
},
|
|
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
|
{highWaterMark: 2048},
|
|
): any);
|
|
// TODO: Move to sub-classing ReadableStream.
|
|
stream.allReady = allReady;
|
|
resolve(stream);
|
|
}
|
|
function onShellError(error: mixed) {
|
|
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
|
|
// However, `allReady` will be rejected by `onFatalError` as well.
|
|
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
|
|
allReady.catch(() => {});
|
|
reject(error);
|
|
}
|
|
|
|
const onHeaders = options ? options.onHeaders : undefined;
|
|
let onHeadersImpl;
|
|
if (onHeaders) {
|
|
onHeadersImpl = (headersDescriptor: HeadersDescriptor) => {
|
|
onHeaders(new Headers(headersDescriptor));
|
|
};
|
|
}
|
|
|
|
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 = createRequest(
|
|
children,
|
|
resumableState,
|
|
createRenderState(
|
|
resumableState,
|
|
options ? options.nonce : undefined,
|
|
options ? options.unstable_externalRuntimeSrc : undefined,
|
|
options ? options.importMap : undefined,
|
|
onHeadersImpl,
|
|
options ? options.maxHeadersLength : undefined,
|
|
),
|
|
createRootFormatContext(options ? options.namespaceURI : undefined),
|
|
options ? options.progressiveChunkSize : undefined,
|
|
options ? options.onError : undefined,
|
|
onAllReady,
|
|
onShellReady,
|
|
onShellError,
|
|
onFatalError,
|
|
options ? options.onPostpone : undefined,
|
|
options ? options.formState : 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 {renderToReadableStream, ReactVersion as version};
|