diff --git a/fixtures/flight-browser/index.html b/fixtures/flight-browser/index.html index e00e78dd48..5d7501bb4a 100644 --- a/fixtures/flight-browser/index.html +++ b/fixtures/flight-browser/index.html @@ -70,20 +70,20 @@ let blob = await responseToDisplay.blob(); let url = URL.createObjectURL(blob); - let data = ReactFlightDOMClient.readFromFetch( + let data = ReactFlightDOMClient.createFromFetch( fetch(url) ); // The client also supports XHR streaming. // var xhr = new XMLHttpRequest(); // xhr.open('GET', url); - // let data = ReactFlightDOMClient.readFromXHR(xhr); + // let data = ReactFlightDOMClient.createFromXHR(xhr); // xhr.send(); renderResult(data); } function Shell({ data }) { - let model = data.model; + let model = data.readRoot(); return

{model.title}

diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index 2b177b61c9..e3a7b558b3 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -1,7 +1,7 @@ import React, {Suspense} from 'react'; function Content({data}) { - return data.model.content; + return data.readRoot().content; } function App({data}) { diff --git a/fixtures/flight/src/index.js b/fixtures/flight/src/index.js index b46341250c..7b3922e3c0 100644 --- a/fixtures/flight/src/index.js +++ b/fixtures/flight/src/index.js @@ -3,5 +3,6 @@ import ReactDOM from 'react-dom'; import ReactFlightDOMClient from 'react-flight-dom-webpack'; import App from './App'; -let data = ReactFlightDOMClient.readFromFetch(fetch('http://localhost:3001')); +let data = ReactFlightDOMClient.createFromFetch(fetch('http://localhost:3001')); + ReactDOM.render(, document.getElementById('root')); diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index f4e9bfb006..ee6275361e 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -27,10 +27,6 @@ import { REACT_ELEMENT_TYPE, } from 'shared/ReactSymbols'; -export type ReactModelRoot = {| - model: T, -|}; - export type JSONValue = | number | null @@ -65,22 +61,32 @@ type ErroredChunk = {| |}; type Chunk = PendingChunk | ResolvedChunk | ErroredChunk; -export type Response = { +export type Response = { partialRow: string, - modelRoot: ReactModelRoot, + rootChunk: Chunk, chunks: Map>, + readRoot(): T, }; -export function createResponse(): Response { - let modelRoot: ReactModelRoot = ({}: any); +function readRoot(): T { + let response: Response = this; + let rootChunk = response.rootChunk; + if (rootChunk.status === RESOLVED) { + return rootChunk.value; + } else { + throw rootChunk.value; + } +} + +export function createResponse(): Response { let rootChunk: Chunk = createPendingChunk(); - definePendingProperty(modelRoot, 'model', rootChunk); let chunks: Map> = new Map(); chunks.set(0, rootChunk); let response = { partialRow: '', - modelRoot, + rootChunk, chunks: chunks, + readRoot: readRoot, }; return response; } @@ -142,7 +148,10 @@ function resolveChunk(chunk: Chunk, value: T): void { // Report that any missing chunks in the model is now going to throw this // error upon read. Also notify any pending promises. -export function reportGlobalError(response: Response, error: Error): void { +export function reportGlobalError( + response: Response, + error: Error, +): void { response.chunks.forEach(chunk => { // If this chunk was already resolved or errored, it won't // trigger an error but if it wasn't then we need to @@ -164,24 +173,6 @@ function readMaybeChunk(maybeChunk: Chunk | T): T { } } -function definePendingProperty( - object: Object, - key: string, - chunk: Chunk, -): void { - Object.defineProperty(object, key, { - configurable: false, - enumerable: true, - get() { - if (chunk.status === RESOLVED) { - return chunk.value; - } else { - throw chunk.value; - } - }, - }); -} - function createElement(type, key, props): React$Element { const element: any = { // This tag allows us to uniquely identify this as a React Element @@ -272,8 +263,8 @@ function createLazyBlock( return lazyType; } -export function parseModelFromJSON( - response: Response, +export function parseModelFromJSON( + response: Response, targetObj: Object, key: string, value: JSONValue, @@ -317,10 +308,10 @@ export function parseModelFromJSON( return value; } -export function resolveModelChunk( - response: Response, +export function resolveModelChunk( + response: Response, id: number, - model: T, + model: M, ): void { let chunks = response.chunks; let chunk = chunks.get(id); @@ -331,8 +322,8 @@ export function resolveModelChunk( } } -export function resolveErrorChunk( - response: Response, +export function resolveErrorChunk( + response: Response, id: number, message: string, stack: string, @@ -348,14 +339,10 @@ export function resolveErrorChunk( } } -export function close(response: Response): void { +export function close(response: Response): void { // In case there are any remaining unresolved chunks, they won't // be resolved now. So we need to issue an error to those. // Ideally we should be able to early bail out if we kept a // ref count of pending chunks. reportGlobalError(response, new Error('Connection closed.')); } - -export function getModelRoot(response: Response): ReactModelRoot { - return response.modelRoot; -} diff --git a/packages/react-client/src/ReactFlightClientStream.js b/packages/react-client/src/ReactFlightClientStream.js index 27e5eabaa8..b7fc86469f 100644 --- a/packages/react-client/src/ReactFlightClientStream.js +++ b/packages/react-client/src/ReactFlightClientStream.js @@ -25,17 +25,13 @@ import { readFinalStringChunk, } from './ReactFlightClientHostConfig'; -export type ReactModelRoot = {| - model: T, -|}; - -type Response = ResponseBase & { +export type Response = ResponseBase & { fromJSON: (key: string, value: JSONValue) => any, stringDecoder: StringDecoder, }; -export function createResponse(): Response { - let response: Response = (createResponseImpl(): any); +export function createResponse(): Response { + let response: Response = (createResponseImpl(): any); response.fromJSON = function(key: string, value: JSONValue) { return parseModelFromJSON(response, this, key, value); }; @@ -45,7 +41,7 @@ export function createResponse(): Response { return response; } -function processFullRow(response: Response, row: string): void { +function processFullRow(response: Response, row: string): void { if (row === '') { return; } @@ -76,8 +72,8 @@ function processFullRow(response: Response, row: string): void { } } -export function processStringChunk( - response: Response, +export function processStringChunk( + response: Response, chunk: string, offset: number, ): void { @@ -92,8 +88,8 @@ export function processStringChunk( response.partialRow += chunk.substring(offset); } -export function processBinaryChunk( - response: Response, +export function processBinaryChunk( + response: Response, chunk: Uint8Array, ): void { if (!supportsBinaryStreams) { @@ -113,4 +109,4 @@ export function processBinaryChunk( response.partialRow += readPartialStringChunk(stringDecoder, chunk); } -export {reportGlobalError, close, getModelRoot} from './ReactFlightClient'; +export {reportGlobalError, close} from './ReactFlightClient'; diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 446a774f70..458d5a036c 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -57,8 +57,7 @@ describe('ReactFlight', () => { let transport = ReactNoopFlightServer.render({ foo: , }); - let root = ReactNoopFlightClient.read(transport); - let model = root.model; + let model = ReactNoopFlightClient.read(transport); expect(model).toEqual({ foo: { bar: ( @@ -87,10 +86,10 @@ describe('ReactFlight', () => { }; let transport = ReactNoopFlightServer.render(model); - let root = ReactNoopFlightClient.read(transport); act(() => { - let UserClient = root.model.User; + let rootModel = ReactNoopFlightClient.read(transport); + let UserClient = rootModel.User; ReactNoop.render(); }); @@ -114,10 +113,10 @@ describe('ReactFlight', () => { }; let transport = ReactNoopFlightServer.render(model); - let root = ReactNoopFlightClient.read(transport); act(() => { - let UserClient = root.model.User; + let rootModel = ReactNoopFlightClient.read(transport); + let UserClient = rootModel.User; ReactNoop.render(); }); diff --git a/packages/react-flight-dom-relay/src/ReactFlightDOMRelayClient.js b/packages/react-flight-dom-relay/src/ReactFlightDOMRelayClient.js index 47bd68c818..11207ba4f8 100644 --- a/packages/react-flight-dom-relay/src/ReactFlightDOMRelayClient.js +++ b/packages/react-flight-dom-relay/src/ReactFlightDOMRelayClient.js @@ -11,14 +11,13 @@ import type {Response, JSONValue} from 'react-client/src/ReactFlightClient'; import { createResponse, - getModelRoot, parseModelFromJSON, resolveModelChunk, resolveErrorChunk, close, } from 'react-client/src/ReactFlightClient'; -function parseModel(response, targetObj, key, value) { +function parseModel(response: Response, targetObj, key, value) { if (typeof value === 'object' && value !== null) { if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { @@ -38,14 +37,18 @@ function parseModel(response, targetObj, key, value) { return parseModelFromJSON(response, targetObj, key, value); } -export {createResponse, getModelRoot, close}; +export {createResponse, close}; -export function resolveModel(response: Response, id: number, json: JSONValue) { +export function resolveModel( + response: Response, + id: number, + json: JSONValue, +) { resolveModelChunk(response, id, parseModel(response, {}, '', json)); } -export function resolveError( - response: Response, +export function resolveError( + response: Response, id: number, message: string, stack: string, diff --git a/packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js b/packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js index c157e3741e..78bcf5c035 100644 --- a/packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js +++ b/packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js @@ -39,8 +39,8 @@ describe('ReactFlightDOMRelay', () => { ); } } - let model = ReactDOMFlightRelayClient.getModelRoot(response).model; ReactDOMFlightRelayClient.close(response); + let model = response.readRoot(); return model; } diff --git a/packages/react-flight-dom-webpack/src/ReactFlightDOMClient.js b/packages/react-flight-dom-webpack/src/ReactFlightDOMClient.js index 38bdec83ba..f7d92f07bf 100644 --- a/packages/react-flight-dom-webpack/src/ReactFlightDOMClient.js +++ b/packages/react-flight-dom-webpack/src/ReactFlightDOMClient.js @@ -7,18 +7,20 @@ * @flow */ -import type {ReactModelRoot} from 'react-client/src/ReactFlightClientStream'; +import type {Response as FlightResponse} from 'react-client/src/ReactFlightClientStream'; import { createResponse, - getModelRoot, reportGlobalError, processStringChunk, processBinaryChunk, close, } from 'react-client/src/ReactFlightClientStream'; -function startReadingFromStream(response, stream: ReadableStream): void { +function startReadingFromStream( + response: FlightResponse, + stream: ReadableStream, +): void { let reader = stream.getReader(); function progress({done, value}) { if (done) { @@ -35,16 +37,18 @@ function startReadingFromStream(response, stream: ReadableStream): void { reader.read().then(progress, error); } -function readFromReadableStream(stream: ReadableStream): ReactModelRoot { - let response = createResponse(); +function createFromReadableStream( + stream: ReadableStream, +): FlightResponse { + let response: FlightResponse = createResponse(); startReadingFromStream(response, stream); - return getModelRoot(response); + return response; } -function readFromFetch( +function createFromFetch( promiseForResponse: Promise, -): ReactModelRoot { - let response = createResponse(); +): FlightResponse { + let response: FlightResponse = createResponse(); promiseForResponse.then( function(r) { startReadingFromStream(response, (r.body: any)); @@ -53,11 +57,11 @@ function readFromFetch( reportGlobalError(response, e); }, ); - return getModelRoot(response); + return response; } -function readFromXHR(request: XMLHttpRequest): ReactModelRoot { - let response = createResponse(); +function createFromXHR(request: XMLHttpRequest): FlightResponse { + let response: FlightResponse = createResponse(); let processedLength = 0; function progress(e: ProgressEvent): void { let chunk = request.responseText; @@ -76,7 +80,7 @@ function readFromXHR(request: XMLHttpRequest): ReactModelRoot { request.addEventListener('error', error); request.addEventListener('abort', error); request.addEventListener('timeout', error); - return getModelRoot(response); + return response; } -export {readFromXHR, readFromFetch, readFromReadableStream}; +export {createFromXHR, createFromFetch, createFromReadableStream}; diff --git a/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 468545277e..9dd5133353 100644 --- a/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -119,9 +119,10 @@ describe('ReactFlightDOM', () => { let {writable, readable} = getTestStream(); ReactFlightDOMServer.pipeToNodeWritable(, writable, webpackMap); - let result = ReactFlightDOMClient.readFromReadableStream(readable); + let response = ReactFlightDOMClient.createFromReadableStream(readable); await waitForSuspense(() => { - expect(result.model).toEqual({ + let model = response.readRoot(); + expect(model).toEqual({ html: (
hello @@ -154,13 +155,13 @@ describe('ReactFlightDOM', () => { } // View - function Message({result}) { - return
{result.model.html}
; + function Message({response}) { + return
{response.readRoot().html}
; } - function App({result}) { + function App({response}) { return ( Loading...}> - + ); } @@ -171,12 +172,12 @@ describe('ReactFlightDOM', () => { writable, webpackMap, ); - let result = ReactFlightDOMClient.readFromReadableStream(readable); + let response = ReactFlightDOMClient.createFromReadableStream(readable); let container = document.createElement('div'); let root = ReactDOM.createRoot(container); await act(async () => { - root.render(); + root.render(); }); expect(container.innerHTML).toBe( '
helloworld
', @@ -192,13 +193,13 @@ describe('ReactFlightDOM', () => { } // View - function Message({result}) { - return

{result.model.text}

; + function Message({response}) { + return

{response.readRoot().text}

; } - function App({result}) { + function App({response}) { return ( Loading...}> - + ); } @@ -209,12 +210,12 @@ describe('ReactFlightDOM', () => { writable, webpackMap, ); - let result = ReactFlightDOMClient.readFromReadableStream(readable); + let response = ReactFlightDOMClient.createFromReadableStream(readable); let container = document.createElement('div'); let root = ReactDOM.createRoot(container); await act(async () => { - root.render(); + root.render(); }); expect(container.innerHTML).toBe('

$1

'); }); @@ -228,13 +229,13 @@ describe('ReactFlightDOM', () => { } // View - function Message({result}) { - return

{result.model.text}

; + function Message({response}) { + return

{response.readRoot().text}

; } - function App({result}) { + function App({response}) { return ( Loading...}> - + ); } @@ -245,12 +246,12 @@ describe('ReactFlightDOM', () => { writable, webpackMap, ); - let result = ReactFlightDOMClient.readFromReadableStream(readable); + let response = ReactFlightDOMClient.createFromReadableStream(readable); let container = document.createElement('div'); let root = ReactDOM.createRoot(container); await act(async () => { - root.render(); + root.render(); }); expect(container.innerHTML).toBe('

@div

'); }); @@ -327,42 +328,44 @@ describe('ReactFlightDOM', () => { }; // View - function ProfileDetails({result}) { + function ProfileDetails({response}) { + let model = response.readRoot(); return (
- {result.model.name} - {result.model.more.avatar} + {model.name} + {model.more.avatar}
); } - function ProfileSidebar({result}) { + function ProfileSidebar({response}) { + let model = response.readRoot(); return (
- {result.model.photos} - {result.model.more.friends} + {model.photos} + {model.more.friends}
); } - function ProfilePosts({result}) { - return
{result.model.more.posts}
; + function ProfilePosts({response}) { + return
{response.readRoot().more.posts}
; } - function ProfileGames({result}) { - return
{result.model.more.games}
; + function ProfileGames({response}) { + return
{response.readRoot().more.games}
; } - function ProfilePage({result}) { + function ProfilePage({response}) { return ( <> (loading)

}> - + (loading sidebar)

}> - +
(loading posts)

}> - +

{e.message}

}> (loading games)

}> - +
@@ -372,12 +375,12 @@ describe('ReactFlightDOM', () => { let {writable, readable} = getTestStream(); ReactFlightDOMServer.pipeToNodeWritable(profileModel, writable, webpackMap); - let result = ReactFlightDOMClient.readFromReadableStream(readable); + let response = ReactFlightDOMClient.createFromReadableStream(readable); let container = document.createElement('div'); let root = ReactDOM.createRoot(container); await act(async () => { - root.render(); + root.render(); }); expect(container.innerHTML).toBe('

(loading)

'); diff --git a/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index dd99f31cdb..d33f965253 100644 --- a/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -62,9 +62,10 @@ describe('ReactFlightDOMBrowser', () => { } let stream = ReactFlightDOMServer.renderToReadableStream(); - let result = ReactFlightDOMClient.readFromReadableStream(stream); + let response = ReactFlightDOMClient.createFromReadableStream(stream); await waitForSuspense(() => { - expect(result.model).toEqual({ + let model = response.readRoot(); + expect(model).toEqual({ html: (
hello diff --git a/packages/react-noop-renderer/src/ReactNoopFlightClient.js b/packages/react-noop-renderer/src/ReactNoopFlightClient.js index e2f9ad0df2..f92f24577f 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightClient.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightClient.js @@ -14,20 +14,13 @@ * environment. */ -import type {ReactModelRoot} from 'react-client/flight'; - import {readModule} from 'react-noop-renderer/flight-modules'; import ReactFlightClient from 'react-client/flight'; type Source = Array; -const { - createResponse, - getModelRoot, - processStringChunk, - close, -} = ReactFlightClient({ +const {createResponse, processStringChunk, close} = ReactFlightClient({ supportsBinaryStreams: false, resolveModuleReference(idx: string) { return idx; @@ -38,13 +31,13 @@ const { }, }); -function read(source: Source): ReactModelRoot { +function read(source: Source): T { let response = createResponse(source); for (let i = 0; i < source.length; i++) { processStringChunk(response, source[i], 0); } close(response); - return getModelRoot(response); + return response.readRoot(); } export {read};