We are interested in building a react-server integration for Remix (and Vite). We've noticed a coupling between the React internals and the bundler API, and we’d like to propose a few small changes that would invert control to provide hooks that can be used by any flight runtime. As a result, all `react-server-dom-*` runtimes should be able to implement the touchpoints in a concise manner allowing the individual packages to focus on what to serialize for references, and how to deserialize/load references.
Today, each individual `react-server-dom-*` package (`react-server-dom-webpack`, `react-server-dom-turbopack`, etc.) essentially concatenates shared runtime modules with the entry points for that package. Each package implements a few functions that are opaquely called in the shared runtime source, including `resolveClientReferenceMetadata`, `resolveServerReference`, `preloadModule`, `requireModule`, etc.
This makes implementing a new `react-server-dom-*` package difficult for someone who is not intimately familiar with the codebase. How do you hook into the build system? What needs to be implemented? What functions are opaquely called by the `react-server` package?
There is already a potential "public API" defined here by the interplay between `react-server` and each individual `react-server-dom-*` implementation.
Let's imagine a new package called `react-server-dom-core` (which could also just be `react-server-dom`). This package exposes the same API's that `react-server-dom-webpack` does for both `/client` and `/server`, but instead of requiring a bundler-specific manifest argument in methods like `renderToReadableStream` it instead provides low-level hooks for doing things like resolving client references and loading/requiring modules.
When using this low-level API directly, it could look like this:
```js
import { renderToReadableStream } from "react-server-dom-core/server";
const stream = renderToReadableStream(element, {
resolveClientReferenceMetadata(reference) {
return ["modId", ["chunk1", "chunk2"]];
},
// the reset of the implementation
});
```
And on the client side:
```js
import { createFromFetch } from "react-server-dom-core/client";
const element = createFromFetch(fetchPromise, {
resolveClientReference(serializedInfo) {
return {
preloadModule() {
// return a promise
},
requireModule() {
// return the module sync
}
}
}
})
```
The implementation of `renderToPipeableStream` in `react-server-dom-webpack` could look something like this:
```js
import { renderToPipeableStream as renderToPipeableStreamCore } from "react-server-dom-core/server";
function createCoreManifest() {
return {
resolveClientReferenceMetadata() {
// Existing implementation
}
}
}
function renderToPipeableStream(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: Options,
): PipeableStream {
return renderToPipeableStreamCore(
model,
createCoreManifest(webpackMap),
options
);
}
```
In this model, the `react-server-dom-core` package is completely ignorant of any details of webpack or its manifest format.
In addition to simplifying the implementation of `react-server-dom-*` packages, this proposal should provide a few additional benefits.
First, since `react-server-dom-*` packages can now rely on a shared `react-server-dom-core` package they can treat it as a regular dependency instead of concatenating modules during the build process. This should make it easier for newcomers to the React repo to understand how code is reused in these packages.
Second, it should be possible to test `react-server-dom-core` without any bundler integration whatsoever. At present, the majority of tests live in `react-server-dom-webpack`. This change should allow these tests to move into `react-server-dom-core`.