diff --git a/packages/react-server-dom-relay/package.json b/packages/react-server-dom-relay/package.json index 5353bf9a1e5ef..eeceab0da3e04 100644 --- a/packages/react-server-dom-relay/package.json +++ b/packages/react-server-dom-relay/package.json @@ -12,7 +12,6 @@ "scheduler": "^0.11.0" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^17.0.0" } } diff --git a/packages/react-server-dom-relay/src/ReactDOMServerFB.js b/packages/react-server-dom-relay/src/ReactDOMServerFB.js new file mode 100644 index 0000000000000..290a1f227e612 --- /dev/null +++ b/packages/react-server-dom-relay/src/ReactDOMServerFB.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 {Request} from 'react-server/src/ReactFizzServer'; + +import type {Destination} from 'react-server/src/ReactServerStreamConfig'; + +import { + createRequest, + startWork, + performWork, + startFlowing, + abort, +} from 'react-server/src/ReactFizzServer'; + +import { + createResponseState, + createRootFormatContext, +} from 'react-server/src/ReactServerFormatConfig'; + +type Options = { + identifierPrefix?: string, + progressiveChunkSize?: number, + onError: (error: mixed) => void, +}; + +opaque type Stream = { + destination: Destination, + request: Request, +}; + +function renderToStream(children: ReactNodeList, options: Options): Stream { + const destination = { + buffer: '', + done: false, + fatal: false, + error: null, + }; + const request = createRequest( + children, + destination, + createResponseState(options ? options.identifierPrefix : undefined), + createRootFormatContext(undefined), + options ? options.progressiveChunkSize : undefined, + options.onError, + undefined, + undefined, + ); + startWork(request); + if (destination.fatal) { + throw destination.error; + } + return { + destination, + request, + }; +} + +function abortStream(stream: Stream): void { + abort(stream.request); +} + +function renderNextChunk(stream: Stream): string { + const {request, destination} = stream; + performWork(request); + startFlowing(request); + if (destination.fatal) { + throw destination.error; + } + const chunk = destination.buffer; + destination.buffer = ''; + return chunk; +} + +function hasFinished(stream: Stream): boolean { + return stream.destination.done; +} + +export {renderToStream, renderNextChunk, hasFinished, abortStream}; diff --git a/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js b/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js new file mode 100644 index 0000000000000..e0477fd6197e7 --- /dev/null +++ b/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export type Destination = { + buffer: string, + done: boolean, + fatal: boolean, + error: mixed, +}; + +export type PrecomputedChunk = string; +export type Chunk = string; + +export function scheduleWork(callback: () => void) { + // We don't schedule work in this model, and instead expect performWork to always be called repeatedly. +} + +export function flushBuffered(destination: Destination) {} + +export function beginWriting(destination: Destination) {} + +export function writeChunk( + destination: Destination, + chunk: Chunk | PrecomputedChunk, +): boolean { + destination.buffer += chunk; + return true; +} + +export function completeWriting(destination: Destination) {} + +export function close(destination: Destination) { + destination.done = true; +} + +export function stringToChunk(content: string): Chunk { + return content; +} + +export function stringToPrecomputedChunk(content: string): PrecomputedChunk { + return content; +} + +export function closeWithError(destination: Destination, error: mixed): void { + destination.done = true; + destination.fatal = true; + destination.error = error; +} diff --git a/packages/react-server-dom-relay/src/__tests__/ReactDOMServerFB-test.internal.js b/packages/react-server-dom-relay/src/__tests__/ReactDOMServerFB-test.internal.js new file mode 100644 index 0000000000000..f04a030276e76 --- /dev/null +++ b/packages/react-server-dom-relay/src/__tests__/ReactDOMServerFB-test.internal.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactDOMServer; +let Suspense; + +describe('ReactDOMServerFB', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMServer = require('../ReactDOMServerFB'); + Suspense = React.Suspense; + }); + + const theError = new Error('This is an error'); + function Throw() { + throw theError; + } + const theInfinitePromise = new Promise(() => {}); + function InfiniteSuspend() { + throw theInfinitePromise; + } + + function readResult(stream) { + let result = ''; + while (!ReactDOMServer.hasFinished(stream)) { + result += ReactDOMServer.renderNextChunk(stream); + } + return result; + } + + it('should be able to render basic HTML', async () => { + const stream = ReactDOMServer.renderToStream(