React DOM fizz server node
Node.js 环境的 SSR 实现。如传统 Node.js 服务(Express/Koa 搭建的服务)。
一、作用
二、渲染为可管道流
备注
startWork()由 ReactFizzServer#startWork 实现prepareForStartFlowingIfBeforeAllReady()由 ReactFizzServer#prepareForStartFlowingIfBeforeAllReady 实现startFlowing()由 ReactFizzServer#startFlowing 实现abort()由 ReactFizzServer#abort 实现
function renderToPipeableStream(
children: ReactNodeList,
options?: Options,
): PipeableStream {
const request = createRequestImpl(children, options);
let hasStartedFlowing = false;
startWork(request);
return {
pipe<T extends Writable>(destination: T): T {
if (hasStartedFlowing) {
throw new Error(
'React currently only supports piping to one writable stream.',
);
}
hasStartedFlowing = true;
prepareForStartFlowingIfBeforeAllReady(request);
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on(
'error',
createCancelHandler(
request,
'The destination stream errored while writing data.',
),
);
destination.on(
'close',
createCancelHandler(request, 'The destination stream closed early.'),
);
return destination;
},
abort(reason: mixed) {
abort(request, reason);
},
};
}
三、渲染为可读流
备注
startFlowing()由 ReactFizzServer#startFlowing 实现stopFlowing()由 ReactFizzServer#stopFlowing 实现abort()由 ReactFizzServer#abort 实现createResumableState()由 ReactFizzConfigDOM#createResumableState 实现createRequest()由 ReactFizzServer#createRequest 实现createRenderState()由 ReactFizzConfigDOM#createRenderState 实现createRootFormatContext()由 ReactFizzConfigDOM#createRootFormatContext 实现startWork()由 ReactFizzServer#startWork 实现
function renderToReadableStream(
children: ReactNodeList,
options?: WebStreamsOptions,
): Promise<ReactDOMServerReadableStream> {
return new Promise((resolve, reject) => {
let onFatalError;
let onAllReady;
const allReady = new Promise<void>((res, rej) => {
onAllReady = res;
onFatalError = rej;
});
function onShellReady() {
let writable: Writable;
const stream: ReactDOMServerReadableStream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
writable =
createFakeWritableFromReadableStreamController(controller);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, writable);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
{ highWaterMark: 0 },
) as any;
// TODO: Move to sub-classing ReadableStream.
// 待办事项:迁移到 ReadableStream 的子类化。
stream.allReady = allReady;
resolve(stream);
}
function onShellError(error: mixed) {
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
// 如果 shell 出错,'renderToReadableStream' 的调用者将无法访问 'allReady'。
// However, `allReady` will be rejected by `onFatalError` as well.
// 然而,'allReady' 也会被 'onFatalError' 拒绝。
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
// 因此,我们需要捕捉“allReady”中重复且无法捕获的致命错误,以阻止
// “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.formState : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal as any).reason);
} else {
const listener = () => {
abort(request, (signal as any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
四、恢复为可管道流
备注
startWork()由 ReactFizzServer#startWork 实现startFlowing()由 ReactFizzServer#startFlowing 实现abort()由 ReactFizzServer#abort 实现
function resumeToPipeableStream(
children: ReactNodeList,
postponedState: PostponedState,
options?: ResumeOptions,
): PipeableStream {
const request = resumeRequestImpl(children, postponedState, options);
let hasStartedFlowing = false;
startWork(request);
return {
pipe<T extends Writable>(destination: T): T {
if (hasStartedFlowing) {
throw new Error(
'React currently only supports piping to one writable stream.',
);
}
hasStartedFlowing = true;
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on(
'error',
createCancelHandler(
request,
'The destination stream errored while writing data.',
),
);
destination.on(
'close',
createCancelHandler(request, 'The destination stream closed early.'),
);
return destination;
},
abort(reason: mixed) {
abort(request, reason);
},
};
}
五、恢复
备注
startFlowing()由 ReactFizzServer#startFlowing 实现stopFlowing()由 ReactFizzServer#stopFlowing 实现abort()由 ReactFizzServer#abort 实现resumeRequest()由 ReactFizzServer#resumeRequest 实现startWork()由 ReactFizzServer#startWork 实现
function resume(
children: ReactNodeList,
postponedState: PostponedState,
options?: WebStreamsResumeOptions,
): Promise<ReactDOMServerReadableStream> {
return new Promise((resolve, reject) => {
let onFatalError;
let onAllReady;
const allReady = new Promise<void>((res, rej) => {
onAllReady = res;
onFatalError = rej;
});
function onShellReady() {
let writable: Writable;
const stream: ReactDOMServerReadableStream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
writable =
createFakeWritableFromReadableStreamController(controller);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, writable);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
{ highWaterMark: 0 },
) as any;
// TODO: Move to sub-classing ReadableStream.
// 待办事项:迁移到 ReadableStream 的子类化。
stream.allReady = allReady;
resolve(stream);
}
function onShellError(error: mixed) {
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
// 如果 shell 出错,`renderToReadableStream` 的调用者将无法访问 `allReady`。
// However, `allReady` will be rejected by `onFatalError` as well.
// 然而,`allReady` 也会被 `onFatalError` 拒绝。
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
// 因此我们需要在 `allReady` 中捕获重复的、无法捕获的致命错误,以阻止
// `UnhandledPromiseRejection`。
allReady.catch(() => {});
reject(error);
}
const request = resumeRequest(
children,
postponedState,
resumeRenderState(
postponedState.resumableState,
options ? options.nonce : undefined,
),
options ? options.onError : undefined,
onAllReady,
onShellReady,
onShellError,
onFatalError,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal as any).reason);
} else {
const listener = () => {
abort(request, (signal as any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
六、工具
1. 确保正确的同构 React 版本
在代码的顶部执行了确认版本的全局副作用。
ensureCorrectIsomorphicReactVersion();
2. 创建排水处理器
备注
startFlowing()由 ReactFizzServer#startFlowing 实现
function createDrainHandler(destination: Destination, request: Request) {
return () => startFlowing(request, destination);
}
3. 创建取消处理程序
备注
stopFlowing()由 ReactFizzServer#stopFlowing 实现abort()由 ReactFizzServer#abort 实现
function createCancelHandler(request: Request, reason: string) {
return () => {
stopFlowing(request);
abort(request, new Error(reason));
};
}
4. 创建请求实现
备注
createResumableState()由 ReactFizzConfigDOM#createResumableState 实现createRequest()由 ReactFizzServer#createRequest 实现createRenderState()由 ReactFizzConfigDOM#createRenderState 实现createRootFormatContext()由 ReactFizzConfigDOM#createRootFormatContext 实现
function createRequestImpl(children: ReactNodeList, options: void | Options) {
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
return createRequest(
children,
resumableState,
createRenderState(
resumableState,
options ? options.nonce : undefined,
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,
options ? options.onAllReady : undefined,
options ? options.onShellReady : undefined,
options ? options.onShellError : undefined,
undefined,
options ? options.formState : undefined,
);
}
5. 从可读流控制器创建假可写对象
备注
textEncoder()由 ReactServerStreamConfigNode#textEncoder 实现
function createFakeWritableFromReadableStreamController(
controller: ReadableStreamController,
): Writable {
// The current host config expects a Writable so we create
// a fake writable for now to push into the Readable.
// 当前宿主配置需要一个可写流,所以我们暂时创建一个假的可写流来推入可读流
return {
write(chunk: string | Uint8Array) {
if (typeof chunk === 'string') {
chunk = textEncoder.encode(chunk);
}
controller.enqueue(chunk);
// in web streams there is no backpressure so we can alwas write more
// 在网络流中没有背压,所以我们总是可以写入更多
return true;
},
end() {
controller.close();
},
destroy(error) {
if (typeof controller.error === 'function') {
controller.error(error);
} else {
controller.close();
}
},
} as any;
}
6. 恢复请求实现
备注
resumeRequest()由 ReactFizzServer#resumeRequest 实现resumeRenderState()由 ReactFizzConfigDOM#resumeRenderState 实现
function resumeRequestImpl(
children: ReactNodeList,
postponedState: PostponedState,
options: void | ResumeOptions,
) {
return resumeRequest(
children,
postponedState,
resumeRenderState(
postponedState.resumableState,
options ? options.nonce : undefined,
),
options ? options.onError : undefined,
options ? options.onAllReady : undefined,
options ? options.onShellReady : undefined,
options ? options.onShellError : undefined,
undefined,
);
}
七、类型
1. 一次性选项
// 一次性选项
type NonceOption =
| string
| {
script?: string;
style?: string;
};
// 选项
type Options = {
identifierPrefix?: string;
namespaceURI?: string;
nonce?: NonceOption;
bootstrapScriptContent?: string;
bootstrapScripts?: Array<string | BootstrapScriptDescriptor>;
bootstrapModules?: Array<string | BootstrapScriptDescriptor>;
progressiveChunkSize?: number;
onShellReady?: () => void;
onShellError?: (error: mixed) => void;
onAllReady?: () => void;
onError?: (error: mixed, errorInfo: ErrorInfo) => string | void;
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor;
importMap?: ImportMap;
formState?: ReactFormState<any, any> | null;
onHeaders?: (headers: HeadersDescriptor) => void;
maxHeadersLength?: number;
};
// 恢复选项
type ResumeOptions = {
nonce?: NonceOption;
onShellReady?: () => void;
onShellError?: (error: mixed) => void;
onAllReady?: () => void;
onError?: (error: mixed, errorInfo: ErrorInfo) => string | void;
};
// 可管道流
type PipeableStream = {
// Cancel any pending I/O and put anything remaining into
// client rendered mode.
// 取消任何挂起的 I/O 并将剩余的内容置于客户端渲染模式。
abort(reason: mixed): void;
pipe<T extends Writable>(destination: T): T;
};
2. ReactDOM 服务器可读流
// TODO: Move to sub-classing ReadableStream.
// 待办事项:迁移到 ReadableStream 的子类化。
// ReactDOM 服务器可读流
type ReactDOMServerReadableStream = ReadableStream & {
allReady: Promise<void>;
};
// 网络流选项
type WebStreamsOptions = Omit<
Options,
'onShellReady' | 'onShellError' | 'onAllReady' | 'onHeaders'
> & { signal: AbortSignal; onHeaders?: (headers: Headers) => void };
3. Web 流恢复选项
type WebStreamsResumeOptions = Omit<
Options,
'onShellReady' | 'onShellError' | 'onAllReady'
> & { signal: AbortSignal };
八、转导
- 从 ReactVersion 转导了
ReactVersion as version