rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
85 lines (84 loc) • 4.63 kB
TypeScript
/**
* A utility to orchestrate and interleave two ReadableStreams (a document shell and an app shell)
* based on a set of markers within their content. This is designed to solve a specific
* race condition in streaming Server-Side Rendering (SSR) with Suspense.
*
* The logic is as follows:
* 1. Stream the document until a start marker is found.
* 2. Switch to the app stream and stream it until an end marker is found. This is the non-suspended shell.
* 3. Switch back to the document stream and stream it until the closing body tag. This sends the client script.
* 4. Switch back to the app stream and stream the remainder (the suspended content).
* 5. Switch back to the document stream and stream the remainder (closing body and html tags).
*
* @param outerHtml The stream for the document shell (`<Document>`).
* @param innerHtml The stream for the application's content.
* @param startMarker The marker in the document to start injecting the app.
* @param endMarker The marker in the app stream that signals the end of the initial, non-suspended render.
*/
/**
* A utility that orchestrates and interleaves three ReadableStreams to produce a
* single, valid HTML response stream. It uses two special markers:
*
* - `startMarker`: Placed in the `outerHtml` stream (the document shell) to
* designate where the application's content should be injected.
* - `endMarker`: Injected into the `innerHtml` stream's RSC payload to signal
* the end of the initial, non-suspended render. This marker is needed for
* non-blocking hydration, as it allows the stitching process to send the
* client `<script>` tags before all suspended content has resolved.
*
* It manages three main stream readers:
*
* - `hoistedTagsReader`: Reads from the `hoistedTagsStream`, which contains only
* the hoisted meta tags (e.g., `<title>`, `<meta>`).
* - `outerReader`: Reads from the `outerHtml` stream, which is the server-rendered
* document shell (containing `<html>`, `<head>`, etc.).
* - `innerReader`: Reads from the `appBodyStream`, which contains the main
* application content, stripped of its hoisted tags.
*
* The function proceeds through a multi-phase state machine, managed by the
* `pump` function, to correctly interleave these streams.
*
* The state machine moves through the following phases:
*
* 1. `read-hoisted`:
* - **Goal:** Buffer all hoisted tags from the `hoistedTagsStream`.
* - **Action:** Reads from `hoistedTagsReader` and appends all content into
* the `hoistedTagsBuffer`. Does not enqueue anything yet.
* - **Transition:** Moves to `outer-head` when the stream is exhausted.
*
* 2. `outer-head`:
* - **Goal:** Stream the document up to the closing `</head>` tag, inject the
* hoisted tags, and then continue until the app `startMarker`.
* - **Action:** Reads from `outerReader`. When it finds `</head>`, it enqueues
* the content before it, then enqueues the `hoistedTagsBuffer`, and finally
* enqueues the `</head>` tag itself. It then continues reading from
* `outerReader` until it finds the `startMarker`.
* - **Transition:** Moves to `inner-shell` after finding and discarding the
* `startMarker`.
*
* 3. `inner-shell`:
* - **Goal:** Stream the initial, non-suspended part of the application.
* - **Action:** Switches to `innerReader`. It enqueues chunks until it finds
* the `endMarker`. Any content after the marker is stored in
* `innerSuspendedRemains`.
* - **Transition:** Moves to `outer-tail` after finding the `endMarker`.
*
* 4. `outer-tail`:
* - **Goal:** Stream the rest of the document's `<body>`, including client
* `<script>` tags.
* - **Action:** Switches back to `outerReader` and enqueues chunks until it
* finds the `</body>` tag.
* - **Transition:** Moves to `inner-suspended` after finding `</body>`.
*
* 5. `inner-suspended`:
* - **Goal:** Stream any suspended content from the React app.
* - **Action:** First enqueues any content from `innerSuspendedRemains`, then
* continues reading from `innerReader` until the stream is exhausted.
* - **Transition:** Moves to `outer-end` when the stream is exhausted.
*
* 6. `outer-end`:
* - **Goal:** Finish the document.
* - **Action:** Switches back to `outerReader` for the last time to send the
* closing `</body>` and `</html>` tags.
*/
export declare function stitchDocumentAndAppStreams(outerHtml: ReadableStream<Uint8Array>, innerHtml: ReadableStream<Uint8Array>, startMarker: string, endMarker: string): ReadableStream<Uint8Array>;