Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React server dom vite #31768

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

jacob-ebey
Copy link

Summary

This adds a new react-server-dom-vite package implementing the RSC touch points in a way that is suitable for use with the Vite bundler.

Differences from other implementations:

  • No prescribed manifest formats
  • Client reference encoding details are left up to the Vite plugin author.
  • Client reference loading is left up to the Vite plugin author.

Other implementations prescribe a "manifest" format that plugin authors must implement and react-server-dom-xyz packages accept to know how to do bundler specific things (__webpack_require__, etc.).

Instead, this package implements the second argument to the encode and decode bookends with an API that puts plugin authors in control of encoding and loading references.

This runtime-manifest APIs looks like this:

export type ServerManifest = {
  resolveClientReference<T>(
    metadata: ClientReferenceMetadata
  ): ClientReference<T>,
  resolveServerReference<T>(id: ServerReferenceId): ClientReference<T>,
}; // API for loading references

export opaque type ClientReference<T> = {
  get(): T,
  preload(): null | Promise<void>,
};

export opaque type ClientReferenceMetadata = mixed;

export type ServerReferenceId = string;

//--------------------------------------------------

export type ClientManifest = {
  resolveClientReferenceMetadata<T>(
    clientReference: ClientReference<T>
  ): ClientReferenceMetadata,
  resolveServerReference<T>(id: ServerReferenceId): ClientReference<T>,
}; // API for loading client reference metadata and server references

export type ServerReferenceId = string;

export type ClientReferenceMetadata = mixed;

export type ClientReferenceKey = string;

An example implementation supporting arbitrary Vite runtimes through the Environments API can be found here: https://github.com/jacob-ebey/vite-plugins/blob/2ffebb24e284a4bb809cc6cbc0fcbc094b136c4a/packages/vite-react-server-dom/src/index.ts#L233

Peeking at the Plugin implementation, you'll notice the different shapes of the ClientReferenceMetadata between development and production, as well as the different implementations of ClientReference's get and preload.

This is partially due to Vite's development philosophy, as well as the lack of a global module management system such as the webpack or parcel caches.

Note: I've published an experimental version of this package under @jacob-ebey/react-server-dom-vite along with a Vite plugin implementation for experimentation and use in the fixture.

Side note: I believe the implementation of a package like this would allow the webpack, parcel, esm, etc. implementations to simplify and focus their concerns on implementing this layer. It would also make switching bundlers / environments more: reactFunc(node, SWAP_ME()) instead of SWAP_ME(node, ALSO_SWAP_ME).

How did you test this change?

I'm implemented a fixture that can be found and ran at fixtures/flight-vite. I've also implemented basic unit tests that can be found at packages/react-server-dom-vite/src/__tests__/ReactFlightViteDOM-test.js.

Copy link

vercel bot commented Dec 13, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 18, 2024 1:45am

},
"dependencies": {
"@jacob-ebey/react-server-dom-vite": "19.0.0-experimental.14",
"@jacob-ebey/vite-react-server-dom": "0.0.6",
Copy link
Collaborator

@sebmarkbage sebmarkbage Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally it should be possible to install only react-server-dom-vite (from React) and the official vite stuff. At least for the bare minimum. You can always have special stuff in bigger frameworks but just for the core integration. As it is right now you need to install this third party thing.

It also makes it hard for us to make suggestions for how to improve the layering between the packages (and the dependency injection protocol). E.g. we can't change anything in React without also changing this package in lock step.

Can this package (@jacob-ebey/vite-react-server-dom) just be inlined into the React repo?

Either into the fixture for things related to just wiring up the host, or if they're related to the vite plugin itself then it can move into react-server-dom-vite.

For example, all that virtual file and manifest generating stuff seems like it should be living inside react-server-dom-vite rather than every user have to reimplement in different ways.

Copy link
Collaborator

@sebmarkbage sebmarkbage Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation inside react-server-dom-vite can be completely different between development and production if needed. We can compile a version for each.

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Dec 13, 2024

It would also make switching bundlers / environments more: reactFunc(node, SWAP_ME()) instead of SWAP_ME(node, ALSO_SWAP_ME).

I believe this is possible one day but we're not there yet. I'm not happy with the protocol for any of the existing bundlers an we're still adding and changing features. There's also certain things like prepareDestinationForModule that should be more automatic but requires unfortunate configuration and not ever framework might need the same configuration depending on how it's hosted. So I get the philosophy but we're not there yet.

The idea is that also ideally there wouldn't be a need for a plugin author if it was just natively built-in (or official plugin) to Vite like it is in Parcel. So the idea is that if code starts off living in react-server-dom-vite it can gradually move into Vite itself as new features are added natively.

One day I hope we can standardize on a protocol just like how require became a pseudo-standard and it can just be reactFunc(node) where the bundler just natively has support for the concept of client and server references in the same way. For example I don't think the ClientReference proxy data type should be something React specific. In the Meta implementation of RSC this was its own type which is why there's hooks like isClientReference() to support a generic ClientReference type provided by the bundler - which ideally would be standardized.

@jacob-ebey jacob-ebey closed this Dec 14, 2024
@jacob-ebey jacob-ebey reopened this Dec 14, 2024
@jacob-ebey
Copy link
Author

jacob-ebey commented Dec 14, 2024

@sebmarkbage in reply to both comments:

I could inline the plugin, but like the webpack version what is the point if it's not the implementation used outside the fixture? Just like the webpack implementation, no one actually uses it.

All the different react-server-dom-xyz packages just implement the "manifest" API exposed here behind an opaque layer exposed as the "same" API with different signatures? Why not standardize the public bundler API on the "react" details instead of requiring implementing these "touch points" in this repo?

If the "touch points" were exposed as they are here, there is no need or bundler specific packages, and you (react) could simplify and say "RSC is ready for everyone who is willing to implement this API" instead of "RSC is ready for framework authors".

Why compile a version for each instead of exposing a generic API that allows me, or react-server-dom-webpack to implement what's needed on top of core supported concepts?

I hope I'm not missing something, but the two concepts here are "info to dynamically load something", and "how to dynamically load that thing". That's what I've implemented and hope it wasn't out of naivety.

@jacob-ebey
Copy link
Author

I'll roll the plugin into here as the next step, but exposing a programatic manifest is the piece I'd like to see as part of core React. It feels very limiting to restrict "how to load things" behind a layer that specific to whoever decided to implement the bundler plugin.

…hide internal module loading / encoding API behind a more base plugin
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants