Stacked on #30132. This way we can get parent and owner stacks from the error. This forces us to confront multiple errors and whether or not a Flight error that ends up being unobservable needs to really reject the render. This implements stashing of Flight errors with a digest and only errors if they end up erroring the Fizz render too. At this point they'll have parent stacks so we can surface those.
108 lines
2.7 KiB
JavaScript
108 lines
2.7 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 {ErrorInfo} from 'react-server/src/ReactFizzServer';
|
|
|
|
import ReactVersion from 'shared/ReactVersion';
|
|
|
|
import {
|
|
createRequest as createFizzRequest,
|
|
startWork as startFizzWork,
|
|
startFlowing as startFizzFlowing,
|
|
abort as abortFizz,
|
|
} from 'react-server/src/ReactFizzServer';
|
|
|
|
import {
|
|
createResumableState,
|
|
createRenderState,
|
|
createRootFormatContext,
|
|
} from './ReactFizzConfigHTML';
|
|
|
|
type MarkupOptions = {
|
|
identifierPrefix?: string,
|
|
signal?: AbortSignal,
|
|
onError?: (error: mixed, errorInfo: ErrorInfo) => ?string,
|
|
};
|
|
|
|
export function renderToMarkup(
|
|
children: ReactNodeList,
|
|
options?: MarkupOptions,
|
|
): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
let buffer = '';
|
|
const fizzDestination = {
|
|
push(chunk: string | null): boolean {
|
|
if (chunk !== null) {
|
|
buffer += chunk;
|
|
} else {
|
|
// null indicates that we finished
|
|
resolve(buffer);
|
|
}
|
|
return true;
|
|
},
|
|
destroy(error: mixed) {
|
|
reject(error);
|
|
},
|
|
};
|
|
function handleError(error: mixed, errorInfo: ErrorInfo) {
|
|
// Any error rejects the promise, regardless of where it happened.
|
|
// Unlike other React SSR we don't want to put Suspense boundaries into
|
|
// client rendering mode because there's no client rendering here.
|
|
reject(error);
|
|
|
|
const onError = options && options.onError;
|
|
if (onError) {
|
|
onError(error, errorInfo);
|
|
}
|
|
}
|
|
const resumableState = createResumableState(
|
|
options ? options.identifierPrefix : undefined,
|
|
undefined,
|
|
);
|
|
const fizzRequest = createFizzRequest(
|
|
children,
|
|
resumableState,
|
|
createRenderState(
|
|
resumableState,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
),
|
|
createRootFormatContext(),
|
|
Infinity,
|
|
handleError,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
);
|
|
if (options && options.signal) {
|
|
const signal = options.signal;
|
|
if (signal.aborted) {
|
|
abortFizz(fizzRequest, (signal: any).reason);
|
|
} else {
|
|
const listener = () => {
|
|
abortFizz(fizzRequest, (signal: any).reason);
|
|
signal.removeEventListener('abort', listener);
|
|
};
|
|
signal.addEventListener('abort', listener);
|
|
}
|
|
}
|
|
startFizzWork(fizzRequest);
|
|
startFizzFlowing(fizzRequest, fizzDestination);
|
|
});
|
|
}
|
|
|
|
export {ReactVersion as version};
|