Skip to content

Commit

Permalink
[Flight] Add initial readme to react-server package
Browse files Browse the repository at this point in the history
This readme documents React Server Components from `react-server` package enough to get an implementor started. It's not comphrensive but it's a beginning point and crucially adds documentation for the `prerender` API for Fligth.
  • Loading branch information
gnoff committed Nov 8, 2024
1 parent 989af12 commit edc8b8f
Showing 1 changed file with 213 additions and 0 deletions.
213 changes: 213 additions & 0 deletions packages/react-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,216 @@ This is an experimental package for creating custom React streaming server rende
**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**

**Use it at your own risk.**

## Usage

`react-server` is a package implementing various Server Rendering capabilities. The two implementation are codenamed `Fizz` and `Flight`.

`Fizz` is a renderer for Server Side Rendering React. The same code that runs in the client (browser or native) is run on the server to produce an initial view to send to the client before it has to download and run React and all the user code to produce that view on the client.

`Flight` is a renderer for React Server Components. These are components that never run on a client. The output of a React Server Component render can be a React tree that can run on the client or be SSR'd using `Fizz`.

## `Fizz` Usage

This part of the Readme is not fully developed yet

## `Flight` Usage

To use `react-server` for React Server Components you must set up an implementation package alongside `react-client`. Use an existing implementation such as `react-server-dom-webpack` as a guide.

You might implement a render function like

```js
import {
createRequest,
startWork,
startFlowing,
stopFlowing,
abort,
} from 'react-server/src/ReactFlightServer'

function render(
model: ReactClientValue,
clientManifest: ClientManifest,
options?: Options,
): ReadableStream {
const request = createRequest(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
{highWaterMark: 0},
);
return stream;
}

```
### Rendering
`react-server` implements the React Server Components rendering implementation. React Server Components is in essence a general purpose serialization and deserialization capability with support for some built-in React primitives such as Suspense and Lazy.
The renderable type is a superset of `structuredClone()`. In addition to all the cloneable types `react-server` can render Symbols, Promises, Iterators and Iterables, async Iterators and Iterables.
Here are some examples of what can be rendered
```js
// primitives
createResponse(123, ...)

// objects and Arrays
createResponse({ messages: ['hello', 'react'] }, ...)

// Maps, Sets, and more
createResponse({ m: Map(['k', 'v'])}, ...)
```
Additionally React built ins can be rendered including Function Components
Function Component are called and the return value can be any renderable type. Since `react-server` supports Promises, Function Components can be async functions.
Here are some examples of what can be rendered
```js

async function App({ children }) {
return children
}

createResponse(<App ><Children /></App>, ...)
```
Finally, There are two types of references in `react-server` that can be rendered
#### Client References
When implementing React Server Components imports from files with `"use client"` directive get turned into Client References. Client references will serialize as a reference to some code that should be loaded on the client.
```js
'use client'

export function alert(message) {
alert(message)
}
```
```js
'use client'

export function ClientComp({ onClick, message }) {
return <button onClick={onClick}>Alert</button>
}
```
```js

// client references don't have to just be components, anything can be
// a reference, in this case we're importing a function that will be
// passed to the ClientComp component
import { alert } from '...'
import { ClientComp } from '...'

async function App({ children }) {
return children
}

createResponse(
<App >
<ClientComp onClick={alert} message={"hello world"} />
</App>,
...)
```
#### Server References
Similarly functions marked with `"use server"` directive are turned into Server References. Server references will serialize as a function that calls back to the server when called from the client.
```js

async function logOnServer(message) {
"use server"
console.log(message)
}

async function App({ children }) {
// logOnServer can be used in a Server Component
logOnServer('used from server')
return children
}

createResponse(
<App >
<ClientComp onClick={logOnServer} message={"used from client"} />
</App>,
...)
```
### Prerendering
When rendering with `react-server` there are two broad contexts when this might happen. Realtime when responding to a user request and ahead of time when prerendering a page that can later be used more than once.
While the core rendering implementation is the same in both cases there are subtle differences we can adopt that take advantage of the context. For instance while rendering in response to a real user request we want to stream eagerly if the consumer is requesting information. This allows us to stream content to the consumer as it becomes available but might have implications for the stability of the serialized format. When prerendering we assume there is not urgency to producing a partial result as quickly as possible so we can alter the internal implementation take advantage of this. To implement a prerender API use `createPrerenderRequest` in place of `createRequest`.
One key semantic change prerendering has with rendering is how errors are handled. When rendering an error is embedded into the output and must be handled by the consumer such as an SSR render or on the client. However with prerendering there is an expectation that if the prerender errors then the entire prerender will be discarded or it will be used but the consumer will attempt to recover that error by asking for a dynamic render. This is analogous to how errors during SSR aren't immediately handled they are actually encoded as requests for client recovery. The error only is observed if the retry on the client actually fails. To account for this prerenders simply omit parts of the model that errored. you can use the `onError` argument in `createPrerenderRequest` to observe if an error occurred and users of your `prerender` implementation can choose whether to abandon the prerender or implement dynamic recovery when an error occurs.
Existing implementations only return the stream containing the output of the prerender once it has completed. In the future we may introduce a `resume` API similar to the one that exists for `Fizz`. In anticipation of such an API it is expected that implementations of `prerender` return the type `Promise<{ prelude: <Host Appropriate Stream Type> }>`
```js
function prerender(
model: ReactClientValue,
clientManifest: ClientManifest,
options?: Options,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
resolve({prelude: stream});
}
const request = createPrerenderRequest(
model,
webpackMap,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.onPostpone : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
);
startWork(request);
});
}
```

0 comments on commit edc8b8f

Please sign in to comment.