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

add react-server-dom-vite impl and fixture #26926

Closed
wants to merge 15 commits into from

Conversation

nksaraf
Copy link

@nksaraf nksaraf commented Jun 11, 2023

Summary

Vite is one of the most popular bundlers used to build React apps. This PR is adding a vite integration for React server components. It would be shame if the usage of React server components is restricted to webpack or Next.js's bundler.

The current experiments that use vite with React server components, all hack into the react-server-dom-webpack package and shim the environment that webpack expects, specifically __webpack_chunk_load__ and __webpack_require__.

While it is okay as a starting point, it would be good to get a deeper integration with vite that recognizes vite's lazy by default approach to doing work, and its ESM first nature. One big issue with the webpack integration is HMR. Right now because it caches the modules it loads with no way to invalidate that cache, performing HMR in vite's style requires some stupid hacks. Ideally this should be handled properly by exposing a way to invalidate the module cache. We do this by exposing __vite_module_cache__. We also expose a __vite_require__ function for the integration, (similar to __webpack_require__). This allows the user to specify how the module loading should behave between dev and prod.

Technical Details

  • SSR Server (global): Uses vite to serve client assets and transformed ESM modules to the browser. Also uses vite to load client components for SSR. We setup the environment with __vite_require__ and __vite_module_cache__ which uses vite during development and the built manifests during production.

  • React Server (region): Uses vite to load the app entry src/App.jsx with the appropriate resolve conditions ("react-server"). It also uses vite to load the server action module when called by the client. We setup the environment with __vite_require__ and __vite_module_cache__ which uses vite during development and the built manifests during production.

A few aspects left to clean up:

  1. Reference IDs, how to represent: I have seen them being represented as module/path#export. I tried saving them as ["module/path","export"]. But it looks like with form actions it treats the string as the ID and so it doesn't work well with the array representation. So might have to change it all to just use the old module/path#export format.
  2. CSS asset handling for vite. By default it loads the CSS using JS in the browser. But this causes FOUC which is undesirable. We need to go through the module tree and track which CSS files are being imported in that tree and add link tags for those. Should be able to be handled at a meta-framework level.

How did you test this change?

Added a fixture flight-vite that's modelled after the flight-esm and flight-webpack fixtures. During development, the global and region servers use vite to parse, load and transform the modules for SSR and creating an ESM dev server for the browser. A build script is added to use vite to build the react server, SSR server and the client bundles. In production, using yarn start, the global and region servers use the manifests produced by vite to know where to load the bundled modules from for the react-server, SSR and browser.

@facebook-github-bot
Copy link

Hi @nksaraf!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@react-sizebot
Copy link

react-sizebot commented Jun 11, 2023

Comparing: ce6842d...979563f

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 164.23 kB 164.23 kB = 51.73 kB 51.73 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 171.67 kB 171.67 kB = 53.97 kB 53.97 kB
facebook-www/ReactDOM-prod.classic.js = 570.12 kB 570.12 kB = 100.58 kB 100.57 kB
facebook-www/ReactDOM-prod.modern.js = 553.90 kB 553.90 kB = 97.75 kB 97.75 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-experimental/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-experimental/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-experimental/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-experimental/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-experimental/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-experimental/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-experimental/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-stable-semver/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-stable-semver/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-stable-semver/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-stable-semver/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-stable-semver/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-stable-semver/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-stable-semver/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-stable/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-stable/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-stable/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-stable/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-stable/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-stable/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-stable/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-experimental/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-experimental/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-experimental/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-experimental/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-experimental/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-experimental/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-experimental/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-experimental/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-experimental/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-stable-semver/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-stable-semver/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-stable-semver/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-stable-semver/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-stable-semver/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-stable-semver/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-stable-semver/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-stable-semver/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-stable-semver/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.29 kB +∞% 0.00 kB 11.46 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 9.22 kB +∞% 0.00 kB 3.65 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.node.development.js +∞% 0.00 kB 45.17 kB +∞% 0.00 kB 11.29 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-client.node.production.min.js +∞% 0.00 kB 8.93 kB +∞% 0.00 kB 3.58 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +∞% 0.00 kB 4.07 kB +∞% 0.00 kB 1.63 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-runtime.development.js +∞% 0.00 kB 7.58 kB +∞% 0.00 kB 2.14 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-runtime.production.min.js +∞% 0.00 kB 2.99 kB +∞% 0.00 kB 1.00 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-server.node.development.js +∞% 0.00 kB 91.13 kB +∞% 0.00 kB 21.66 kB
oss-stable/react-server-dom-vite/cjs/react-server-dom-vite-server.node.production.min.js +∞% 0.00 kB 22.31 kB +∞% 0.00 kB 7.84 kB
oss-stable/react-server-dom-vite/client.browser.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.17 kB
oss-stable/react-server-dom-vite/client.js +∞% 0.00 kB 0.06 kB +∞% 0.00 kB 0.08 kB
oss-stable/react-server-dom-vite/client.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/esm/react-server-dom-vite-client.browser.development.js +∞% 0.00 kB 46.13 kB +∞% 0.00 kB 11.40 kB
oss-stable/react-server-dom-vite/esm/react-server-dom-vite-client.browser.production.min.js +∞% 0.00 kB 33.99 kB +∞% 0.00 kB 8.69 kB
oss-stable/react-server-dom-vite/index.js +∞% 0.00 kB 0.28 kB +∞% 0.00 kB 0.22 kB
oss-stable/react-server-dom-vite/plugin.js +∞% 0.00 kB 0.08 kB +∞% 0.00 kB 0.10 kB
oss-stable/react-server-dom-vite/runtime.js +∞% 0.00 kB 0.24 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/server.js +∞% 0.00 kB 0.19 kB +∞% 0.00 kB 0.16 kB
oss-stable/react-server-dom-vite/server.node.js +∞% 0.00 kB 0.25 kB +∞% 0.00 kB 0.16 kB

Generated by 🚫 dangerJS against 979563f

@nksaraf nksaraf marked this pull request as ready for review June 11, 2023 16:28
handleHotUpdate({file}) {
// clear vite module cache so when its imported again, we will
// get the new version
globalThis.__vite_module_cache__.delete(file);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@gnoff is working on a general solution to this problem as it’s a problem in the Webpack impl too atm. Next.js works around it.

Copy link
Author

Choose a reason for hiding this comment

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

What kind of general solution are we thinking about? Do I need to do something to prepare for that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

React's fast refresh currently runs synchronously but it should probably be a transition. In that transition application code needs to be able to run. For instance refreshing the router. We may need a way to provide modules in a transition compatible way.

I don't yet know what the eventual solution will look like just that there will likely be some API to hook into a refresh and maybe provide a new module cache or something like component families but for modules

You don't need to do anything now to prepare per se, but when I get started on it I'll reach out to figure out what needs to change

Copy link
Collaborator

Choose a reason for hiding this comment

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

It turns out that I think we probably don't need that approach after #26985 since we have another way to avoid the cache.

One possible solution is to use the metadata object as a key (or just stashing the value on the metadata) because that object is unique to an RSC request. It means it'll go through the async pass during new RSC loads even for existing modules but that's only during the preload phase and not rendering.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I get why this is needed for Vite though since Vite adds a timestamp for the new version of the module. If that timestamp is not used by the RSC loader, I suspect that leads to other issues so this might indicate another bug.

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Jun 11, 2023

For context and credit. There was some previous work done on a Vite plugin from @frandiox in #22952 which powered Hydrogen. That was done before we ratified the react-server conventions and ironed some details which is why it wasn't merged - since it wasn't following the new convention.

This new take seems to address those by building on top of the latest stack.

@sebmarkbage
Copy link
Collaborator

Reference IDs, how to represent: I have seen them being represented as module/path#export. I tried saving them as ["module/path","export"]. But it looks like with form actions it treats the string as the ID and so it doesn't work well with the array representation. So might have to change it all to just use the old module/path#export format.

Yea, I'm not too happy about how that turned out. I expect us to do a few more iterations there on the format.

CSS asset handling for vite. By default it loads the CSS using JS in the browser. But this causes FOUC which is undesirable. We need to go through the module tree and track which CSS files are being imported in that tree and add link tags for those. Should be able to be handled at a meta-framework level.

The essence is that we support suspending on the CSS way later than you typically would in a JS module loader. E.g. you'd have to download the CSS before loading the JS. In our Webpack set up and Next.js we can load CSS in parallel while still rendering the next page that needs it. While it's more of a meta-framework concern, it could be good to show how that's intended to work in a meta-framework. E.g. the Webpack fixture still collects the CSS for the entry point and loads it using React at the root. That could all go into the fixture though. We don't really have an equivalent for the plain ESM fixture since there's not a solution to this problem in the pure ESM world that also works on the server. It's also less efficient due to the lack of parallelization.

@nksaraf
Copy link
Author

nksaraf commented Jun 11, 2023

For context and credit. There was some previous work done on a Vite plugin from @frandiox in #22952 which powered Hydrogen. That was done before we ratified the react-server conventions and ironed some details which is why it wasn't merged - since it wasn't following the new convention.

This new take seems to address those by building on top of the latest stack.

I want to give some credit too for all the different references I had to build this up including some direct work from @cyco130 when we worked on cyco130/vite-rsc together

Reference IDs, how to represent: I have seen them being represented as module/path#export. I tried saving them as ["module/path","export"]. But it looks like with form actions it treats the string as the ID and so it doesn't work well with the array representation. So might have to change it all to just use the old module/path#export format.

Yea, I'm not too happy about how that turned out. I expect us to do a few more iterations there on the format.

CSS asset handling for vite. By default it loads the CSS using JS in the browser. But this causes FOUC which is undesirable. We need to go through the module tree and track which CSS files are being imported in that tree and add link tags for those. Should be able to be handled at a meta-framework level.

The essence is that we support suspending on the CSS way later than you typically would in a JS module loader. E.g. you'd have to download the CSS before loading the JS. In our Webpack set up and Next.js we can load CSS in parallel while still rendering the next page that needs it. While it's more of a meta-framework concern, it could be good to show how that's intended to work in a meta-framework. E.g. the Webpack fixture still collects the CSS for the entry point and loads it using React at the root. That could all go into the fixture though. We don't really have an equivalent for the plain ESM fixture since there's not a solution to this problem in the pure ESM world that also works on the server. It's also less efficient due to the lack of parallelization.

Cool will add the CSS support in the fixture (had that in our experiments). I think for the module reference format I will revert to module/path#export for now as that involves less branching.

@nksaraf
Copy link
Author

nksaraf commented Jun 11, 2023

@sebmarkbage added support for CSS files in the fixture. It uses an Async server component to load the css files imported in the module tree under an entry. During production, it uses the manifest to figure out what css files to include.

Also now using the module/path#export syntax for reference IDs

@nksaraf nksaraf requested a review from sebmarkbage June 11, 2023 18:24
@nksaraf
Copy link
Author

nksaraf commented Jun 11, 2023

Blocked by vitejs/vite#13487. Right now vite is not respecting user config's resolve.conditions during its internal nodeResolve function. This PR fixes that.

@sebmarkbage
Copy link
Collaborator

For dev and prod, React already builds two different versions so you can have different implementation details for each one. Another question that came up for Turbopack was whether we should have a 2x2 matrix of HMR/build bundler and development/production mode React, or if it's enough to just have production/development.


function createFromNodeStream<T>(
stream: Readable,
moduleMap: string,
Copy link
Contributor

Choose a reason for hiding this comment

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

@nksaraf
Copy link
Author

nksaraf commented Jun 13, 2023

Made a change to actually move the module caching logic and async/sync distinction to outside the react-server-dom-vite/client bundle. I pushed it to user land to be customized by the framework author. Essentially ReactServerDOM will call vite_preload and vite_require in its preloadModule and requireModule functions respectively.

So we explicitly didn't do that with Webpack and won't do that with Turbopack. After all, we could just expose the methods we currently have in the config as runtime options if we wanted that. However, these boundary are not stable. We've already changed them before and will change them again. There's things missing now that @gnoff is working on like properly preloading integrated with the Fizz runtime so this won't be sufficient anyway.

It also only encourages more hacks on the consuming side and to keep both branches in the runtime for both, instead of tackling the underlying ideal data structure. Really, the goal here is for the bundler to expose more direct data structures for the ideal loading sequence and that way we can iterate on providing the ideal protocol on both ends. As it stands, this just invites all Vite based frameworks to provide a sub-optimal implementation.

For example, there really shouldn't be any need to have a module cache because Vite has one internally. Ideally we should have a way to bridge the gap to get access to an API to synchronously access it or expose the whole thing. Until then, the HMR issue should ideally just work out of the box and if we add hooks to do it in React, then that needs to be under the control of React to clear those entries.

I think we need to have a stronger opinion here and not settle for what's easiest to implement.

Agreed. I think I hold the same viewpoint. The trouble I had was in making the CJS choice a strict opinion and the abstraction started bending there. I think we would need to allow both ESM and CJS output while bundling, and so the runtime needs to allow the consumer to pick which one to use. The other issue was between the browser, edge and node clients. Only the node client will be able to support CJS require based sync initialization. The other would either need to bundle everything together and then just access the relevant thing syncronously. Or have async require using dynamic imports.

Does the react team have an opinion in that space too?

@sebmarkbage
Copy link
Collaborator

There's three parts to the synchronous nature of increasing importance:

  1. Synchronous initialization of the module - CJS style. This could be implemented as CJS or it could be implemented as an initializer inside ESM that sets up a module (nobody does that yet but I think it would be cool). This unlocks the ability to lazily initialize modules which can improve performance - especially selective hydration performance.
  2. Sharing a synchronous cache between requests so that once a module has already been initialized once can be synchronously accessed again. In a system like Webpack that maintains its own module map, this can be implemented using the built-in map in the bundler ideally. With ESM this map isn't exposed to users, which is unfortunate, but it can be implemented using an external map temporarily until newer specs fixes this.
  3. Production builds must also include all recursive chunks you need to load in the "chunks" list for a module to resolve. I.e. we should be able to load all dependencies in a single round trip. There shouldn't be any waterfalls between chunks where we load one async dependency followed by another.
  4. Required: Synchronously access a module that has already been preloaded by this RSC request. This is needed because it's expected that there are a lot of nested Client Components and without it React would create a micro-task waterfall which is really bad for perf based on how React suspense currently works. This can be implement using a global map like (2) but it can also be implemented locally e.g. by stashing the result on the Promise that's imported.

Each platform should implements as many of these as possible.

For the Webpack build we solved all four. For the ESM build I only solved 2 and 4 by using a shared global cache but not 1 and 3. Because it's the best we can do given the underlying system. The Vite version should be able to support at least 2, 3 and 4 in production.

@sebmarkbage
Copy link
Collaborator

It doesn't look like you have a solution to 3 here. I suspect you'll need to have Vite generate a manifest that includes the recursive generated files you'll need for every client module.

@nksaraf
Copy link
Author

nksaraf commented Jun 21, 2023

  1. We could enforce cjs for the node build if the React team recommends it. But not sure how this is to be handled in the edge runtime case then. We will have to do different stuff there.

  2. Vite also has a module graph in hand during dev. During prod, we can maintain a global module cache for this too

  3. It doesn't look like you have a solution to 3 here. I suspect you'll need to have Vite generate a manifest that includes the recursive generated files you'll need for every client module.

Yeah not yet. But yeah I can get it to emit a manifest for client modules and what other client imports it has. This should allow us to do 3. But also if we are doing synchronous loading, using CJS, does this chunks thing matter still?

  1. synchronous access after preload should work using the promise.status === 'fulfilled' trick.

@sebmarkbage
Copy link
Collaborator

But also if we are doing synchronous loading, using CJS, does this chunks thing matter still?

The chunks thing is a prerequisite for doing sync loading since you need to guarantee that you already have all the chunks loaded.

@sebmarkbage
Copy link
Collaborator

We could enforce cjs for the node build if the React team recommends it. But not sure how this is to be handled in the edge runtime case then. We will have to do different stuff there.

We don't have strong recommendation for Node builds. There's different tradeoffs depending on if you use a warm server or cold starts. It's also less sensitive because the downstream effect of a few extra microtasks is much lower in the SSR implementation. So I'd say that's not strictly necessary. Sync init can help cold starts on the server but it's not quite as impactful as on the client. So if you're not going to do it on the client, probably not worth it.

@nksaraf
Copy link
Author

nksaraf commented Jun 21, 2023

There's three parts to the synchronous nature of increasing importance:

  1. Synchronous initialization of the module - CJS style. This could be implemented as CJS or it could be implemented as an initializer inside ESM that sets up a module (nobody does that yet but I think it would be cool). This unlocks the ability to lazily initialize modules which can improve performance - especially selective hydration performance.
  2. Sharing a synchronous cache between requests so that once a module has already been initialized once can be synchronously accessed again. In a system like Webpack that maintains its own module map, this can be implemented using the built-in map in the bundler ideally. With ESM this map isn't exposed to users, which is unfortunate, but it can be implemented using an external map temporarily until newer specs fixes this.
  3. Production builds must also include all recursive chunks you need to load in the "chunks" list for a module to resolve. I.e. we should be able to load all dependencies in a single round trip. There shouldn't be any waterfalls between chunks where we load one async dependency followed by another.
  4. Required: Synchronously access a module that has already been preloaded by this RSC request. This is needed because it's expected that there are a lot of nested Client Components and without it React would create a micro-task waterfall which is really bad for perf based on how React suspense currently works. This can be implement using a global map like (2) but it can also be implemented locally e.g. by stashing the result on the Promise that's imported.

Each platform should implements as many of these as possible.

For the Webpack build we solved all four. For the ESM build I only solved 2 and 4 by using a shared global cache but not 1 and 3. Because it's the best we can do given the underlying system. The Vite version should be able to support at least 2, 3 and 4 in production.

Could you help me out with which ones of these needs to apply to the SSR or the browser or both? Syncronous loading on the client is probably more difficult problem. Will probably need to chunk things better and load those before the hydrating begins. Is that the idea?

@sebmarkbage
Copy link
Collaborator

They're all relevant for both but more so on the client. For SSR it also depends on how you deploy. E.g. if you bundle the whole thing into one bundle then there might not be any chunks to load and so it just never will have an extra chunks.

@nksaraf
Copy link
Author

nksaraf commented Jul 16, 2023

Haven't been updating this PR for a bit to actually explore how to do this kind of sync bundling in vite/rollup. Will probably get some help from the team there but right now going han solo to figure out how everything works.

@cyco130
Copy link

cyco130 commented Jul 25, 2023

3. Production builds must also include all recursive chunks you need to load in the "chunks" list for a module to resolve. I.e. we should be able to load all dependencies in a single round trip. There shouldn't be any waterfalls between chunks where we load one async dependency followed by another.

@nksaraf Sorry if I misunderstood anything but doesn't Vite already do that automatically in production?

Have a look here:

  • The chain of dependency goes like a.js => b.js => c.js.
  • Each of these three files are forced into their own chunks because of the dynamic imports in main.js.
  • When you click "Load A" in dev, it causes a waterfall as you'd expect.
  • But in the production build, (use npm run build and open the index file in dist/assets/) Vite optimizes the dynamic import of a.js into something like this (I disabled minification to make it clearer):
    __vitePreload(
      () => import('./a-14f6e541.js'),
      ['assets/a-14f6e541.js', 'assets/b-85d91b69.js', 'assets/c-5dcf697c.js']
    );
    which loads all transitive dependencies (a.js, b.js, and c.js) in parallel, avoiding waterfalls.

Doesn't this solve 3?

@nksaraf
Copy link
Author

nksaraf commented Jul 25, 2023

Yeah I think you are right, to use this feature we would have to create virtual modules that were dynamically importing the client components and using that for loading.. right now I was looking at a more runtime driven approach using a manifest so that I can import things at will during runtime without worrying. But I think I'm gonna explore how far using bite's default behavior can take us

@cyco130
Copy link

cyco130 commented Jul 25, 2023

Cool. All client components still need to be included in the client build somehow, either by naming them explicitly as entry points or using a virtual module like you mentioned. A virtual module (which would essentially serve as a client components manifest) would solve both problems so I thought it might be the easiest way. Especially since Vite would do all the heavy lifting for you automatically and would do it at build-time which is probably more efficient.

@sebmarkbage
Copy link
Collaborator

Let me know if you need help rebasing since a lot has changed in the internals.

@nksaraf
Copy link
Author

nksaraf commented Sep 22, 2023

Let me know if you need help rebasing since a lot has changed in the internals.

Yeah would love the help

@lazarv
Copy link

lazarv commented Sep 30, 2023

We don't have strong recommendation for Node builds. There's different tradeoffs depending on if you use a warm server or cold starts. It's also less sensitive because the downstream effect of a few extra microtasks is much lower in the SSR implementation. So I'd say that's not strictly necessary. Sync init can help cold starts on the server but it's not quite as impactful as on the client. So if you're not going to do it on the client, probably not worth it.

@sebmarkbage this sounds like a very good reasoning for me why module loading should be the consumers concern to implement and let the consumer decide on which tradeoff to choose.

Vite and esm is fully on the async module loading train, so I don't see the point in taking commonjs and sync module loading into consideration. When someone chose Vite and esm, then async module loading was already chosen as preferred by the developer/consumer.

Is there any stress test or similar for benchmarking performance using sync vs async module loading? So there would be a measurable score for decision making. It would be nice to have benchmarking for webpack vs turbopack vs esm vs vite versions of react-server-dom too and some info on what would be the tradeoff when the module loader would be provided by the consumer in a generic solution.

One big issue with the webpack integration is HMR. Right now because it caches the modules it loads with no way to invalidate that cache, performing HMR in vite's style requires some stupid hacks.

@nksaraf after module caching was removed from react-server-dom-webpack, I needed to implement it again the same way as it was before in the mock __webpack_require__ function where it's using Vite's ssrLoadModule or native import in the browser. I have no issue with HMR at all this way as Vite applies a t timestamp query param to the module URL for the HMR reload. The only issue with this approach is that the previous versions of the module will stay in the global cache, but this could be easily addressed just be removing all previous versions from the cache as the URL path is the same.

In a production build I'm using the manifest files generated by Vite for the client and server builds. I also use the manifest to collect all the CSS files needed and render links for each.

If any of you are curious what I mean, all my work on an experimental framework using Vite is at https://github.com/lazarv/react-server.

@svedova
Copy link

svedova commented Oct 10, 2023

Hey 👋

Thanks for working on this.

Just wanted to say that at Stormkit we develop and maintain a React Monorepo Template which is framework agnostic. It uses Vite to build and run the app on development. It'd be super useful to have Vite support for Server Components.

Therefore just wanted to say that we're ready to help if there is anything we can do to speed up the support for this feature 🙏

Once again thanks a lot!

@nksaraf
Copy link
Author

nksaraf commented Oct 15, 2023

@sebmarkbage should I just start from scratch. Seems like it'll be easier to do it that way than tracking what all has changed in other versions

@jkhaui
Copy link

jkhaui commented Oct 16, 2023

Might be necessary given that vite 5 is also on the horizon? Sounds like a tonne of work though 😟

@dilane3
Copy link

dilane3 commented Feb 1, 2024

Hi there 👋

Happy to know that there is a work which is done to support RSCs in Vite.

I'm building my own Framework on top of Vite actually and I really need to support that feature too.

Hope that all will go well and we will have something working fine.

Good work guys.

Copy link

github-actions bot commented May 1, 2024

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label May 1, 2024
Copy link

github-actions bot commented May 8, 2024

Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!

@github-actions github-actions bot closed this May 8, 2024
@o-alexandrov
Copy link

Could you please consider to either:

  • reopen
  • share future plans on Vite & RSC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Resolution: Stale Automatically closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.