-
Notifications
You must be signed in to change notification settings - Fork 48.5k
feat: add react-server-dom-vite #33152
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
base: main
Are you sure you want to change the base?
Conversation
wip: copy parcel to vite wip: add __vite_rsc_preload__ and __vite_rsc_require__ wip: more rename wip: dev only wip: rename wip: remove parcelRequire wip: build prod wip: move findSourceMapURL to client option chore: comment chore: use __vite_rsc_preload__ only wip: fix findSourceMapURL wip: add fixtures/flight-vite wip: build fix: ssr client reference modulepreload wip: add setPreloadModule API refactor: abstract runtime wip: rename createClientReference to registerClientReference like webpack wip: tweak createServerReference refactor: remove unused wip: remove setServerCallback and align with webpack chore: comment chore: rename parcel to vite chore: prettier chore: lint chore: fix flow fix: global async reference cache wip: add ClientManifest type chore: lint chore: add css example wip: remove caching chore: add suspense example chore: rename mini to basic chore: fix streaming in vite preview feat: support prepare destination chore: cleanup test: tweak feat: add findSourceMapURL test: tweak fixture chore: cleanup test: normalize reference key chore: fix actions imported from client + preserve `createServerReference` position chore: temporary references chore: use `@hiogawa/transforms` in fixture chore: test temporary reference + early hydration chore: move code chore: test nonce in fixtures chore: fix deps chore: comment fix: allow unsafe-eval during dev for `createFakeFunction` chore: add inline action chore: copy transform utils to fixture chore: comment and readme
I initially didn't have css import handling, but now I just pushed one approach I've been thinking. This requires "prepare destination" to also handle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, excellent PR description. The fact that you've clearly thought of many cases and reviewed the previous PR discussions and pointed out potential gaps makes it so much easier to iterate because it reads like a possible todo for follow ups.
I'm not quite sure how this follows. Can you explain a bit for why this injection is needed as opposed to just hard coding the various branches into the Config? Other than the environments, which should be statically branched, what kind of customization do you expect at this level? We try really hard to avoid dynamic dependency injection and specially module level ones. React doesn't have any other APIs like this.
The important semantic is that you can branch the module graph between the For example, it seems like
A simple approach is just compiling However, what Vite can expose is a way to register that entry point with the client to be reachable by the client to load. Basically you need some way to on-the-fly detect a new |
Hi, thanks for the review!
I think this was partly for my convenience to quickly iterate on async module loding logic and at this point, I think I'm satisfied and there shouldn't be customization required (unless other framework authors or I discover something lacks with approach later), but there are some technical concerns, which makes moving For example, one question I have is that I think the logic like Also if we have Another minor thing is that I didn't put custom public path support in That said, I'm inclined to move more code to
This might not be an interest to React team, but let me clarify on this point in case it helps other readers as well. Vite frameworks already processes/compiles all the code (or at least the dependencies which has The difficultly arises due to the specific dev-only strategy Vite employs when handling dependency package import on browser (which is called "deps optimization" or "pre-bundling"). Though I haven't fully explored the solution yet, current Vite inner working expects that browser module graph to process the exact import like |
I do feel it's a bit against the grain of RSC since it didn't allow infinite scaling. There are also other implications like that you cannot rely on not sending a file as a security mechanism. This is helpful for example to allow prereleases of a/b test feature to not leak to hackers. It also works as a secondary protection for Server Actions that if you can't find them then you can't invoke them (if something went wrong with authentication earlier). However, I have to be careful about how much we put my opinions into the "spec". The important part of the spec is to preserve interoperability - not that it's performance or scalable or has extra security features. There's a fine line because there might be implications for what kind of RSC library you can publish in a cross-framework compatible way. I don't think this reaches the level of interop incompatible. The important part of the implementation is that we can maintain it. I don't think this is a blocker. This is just something for the Vite community to evaluate within itself. Technically it's not even a blocker for interoperability that you can preload the chunks during SSR if you're ok living with bad performance. The important thing there is that there are not other alternative solutions to solve the performance issues that end up incompatible. Loading CSS at the right timing is important for semantics though.
It's not a blocker to land per se but I think we'd land as a
It makes the timing of resolution more resilient to moving module initialization around. However, the goal should be to minimize the configurability so there's less surface area to maintain and consider for changes. |
I believe that this is not done with a module transform. This is the The way this is wired up is a little confusing because there are several indirections but the end result is that the builds for SSR includes an implementation for this and the builds for clients make this a noop so it gets compiled out. A couple downsides of the function Component({ clientModule }) {
useEffect(() => clientModule.hello());
}
|
That's an incredible PR @hi-ogawa 👏🏽 Good job to you. Supporting RSC into Vite is an excellent idea, as many Frameworks are still using it as bundler. I had like to test that plugin into my framework rasengan.js and see how it behave and plan to support RSC in the future. |
Thanks for clarifying it. Yes, my goal is to have a enough discussion here for stable API so it's maintain-able and you are comfortable publishing it on npm. I'm going to rework API this week.
Can you elaborate regarding this issue? From what I understand, client references in rsc payload are always deserialized through
import { Client1 } from './client1';
import * as client2 from "./client2"
export function Root() {
// passing module itself is not supported since esm is [Module: null prototype]
// > Error: Only plain objects, and a few built-ins,
// > can be passed to Client Components from Server Components.
// > Classes or null prototypes are not supported.
// return <Client1 clientModule={client2} />
// thus destructure it for example
return <Client1 clientModules={{ ...client2 }}>
}
"use client"
import React from "react"
export function Client1({ clientModule }: { clientModule: { hello: () => void } }) {
React.useEffect(() => clientModule.hello());
return null;
}
"use client"
export function hello() {
console.log("hello")
} |
Summary
As a continuation from the discussion on Jacob's PR #31768, this is a new PR to add the
react-server-dom-vite
package andfixtures/flight-vite
. To begin with, thank you to the React team for the patience with multiple PR iterations and for providing reviews. I also want to thank the Vite ecosystem for demonstrating Vite RSC integrations. I've learned fundamental concepts from existing Vite RSC frameworks and they are essential to reach this PR.Firstly, let me clarify a bundler-level characteristic that affects how RSC integration is approached for Vite. (This has been raised in past PRs as well, but I want to provide fuller context.)
modulepreload
. On the server, there's no alternative other than importing the chunk itself. (These are bundler-level and ESM runtime-level design concerns, and we assume Vite users are aware of them as a trade-off. It might still be interesting to find ways to circumvent and optimize this in userland, potentially suggest them as general bundler-level features, or employ new ESM specifications, but that's not the current focus for Vite RSC adoption.)modulepreload
injection of dependency chunks at runtime in the browser. Therefore, encodingchunks
into the RSC payload seems unnecessary, also with the fact that there's no way to preload ESM chunks on the server. (However, relying on Vite's preload feature might be considered an "anti-RSC" concept, since it essentially bakes the entire client manifest into the main browser bundle instead of sending chunk metadata as needed from the server. My current take (and thus this PR) is that since most Vite apps (both SPA and SSR) assume this feature, we can just use it, but challenging Vite's default mechanism might be intersting. Further discussion is very welcome.)fixtures/flight-vite
assumes that both SSR and RSC run on the same Node.js runtime as the main CLI process during development. However, this doesn't have to be the case in actual frameworks (for example, Jacob and RedwoodJS uses Cloudflare: https://github.com/redwoodjs/sdk). Therefore, a certain degree of generalization is required to makereact-server-dom-vite
portable for such usages (though this is mostly a concern for the plugin API, not the runtime API, and I haven't yet put any plugins at thereact-server-dom-vite
level).Given this background, notable differences of
react-server-dom-vite
compared to otherreact-server-dom
integrations are:setPreloadModule(loadModule: (id: string) => Promise<unknown>)
API.Add(EDIT: removed sincemoduleLoading.prepareDestinationManifest
to pass browser chunk mapping to SSR directly for "prepare destination".setPreloadModule(...)
alone can handle the same logic on user land)setPreloadModule
. Also, chunks do not need to be encoded in the RSC payload.?t=
for proper client reference HMR, but clearing the cache requires more logic, so I haven't attempted this at the moment.)Although this is the API I've currently implemented for the
fixtures/flight-vite
demo, I'm happy to iterate on the exact shape through discussions with the React team and Vite framework maintainers.In terms of "RSC spec" compliance,
fixtures/flight-vite
might look like it's achieving proper semantics. However, for transparency, I'm mentioning again here that supporting"use client"
insidenode_modules
can be difficult in some cases during dev. Here is an example repository to demonstrate such behaviors: https://github.com/hi-ogawa/rsc-tests. Supporting it case by case based on framework-side heuristics or additional user-side configuration is likely possible (for example, moving a client boundary fromnode_modules
to "user code" by re-exporting locally is an obvious workaround), but handling all cases uniformly behind the scenes as per the "RSC spec" is a challenge with the current unbundled dev. Eventually, this issue should disappear when we build RSC on top of a fully bundled development server with Vite/Rolldown. So, at the moment, I'd like each framework (and myself) to continue exploring approaches to support each case as best as possible.In parallel with addressing any feedback here, I'm going to test
react-server-dom-vite
in existing Vite RSC frameworks. For example, I have PRs on Waku (wakujs/waku#1393), RedwoodJS (redwoodjs/sdk#360), and my own plugin (hi-ogawa/vite-plugins#768) to preliminarily test with a local build of the package. My local build is pushed to my repository, so it can be installed via"react-server-dom-vite": "https://github.com/hi-ogawa/vite-plugins/raw/refs/heads/04-24-refactor_rsc_use_react-server-dom-vite/react-server-dom-vite-19.1.0.tgz"
inpackage.json
. Anyone interested is welcome to test the package. It's normally hard to implement "prepare destination" usingreact-server-dom-webpack
, so it might be interesting to see how switching toreact-server-dom-vite
helps.My further plan for
react-server-dom-vite
is to publish a polished version offixtures/flight-vite/basic
to provide an out-of-the-box, "framework-less" RSC experience on Vite (something similar to Parcel). This is intended to be "framework-less", but it's likely not directly reusable for more opinionated frameworks (for example, a framework might assume a custom environment like RedwoodJS and Cloudflare, or employ its own file system conventions and transforms such as "use cache"). However, certain core aspects of the currentfixtures/flight-vite/basic
should be reusable (such asvite-utils.ts
,findSourceMapURL
, and a bunch of virtual modules). I'll try to find a way to extract the common parts and provide a helper package for Vite RSC frameworks (or eventually move them intoreact-server-dom-vite
).How did you test this change?
Integration tests are included in
fixtures/flight-vite
. Please take a lookfixtures/flight-vite/README.md
for the detail.