react icon indicating copy to clipboard operation
react copied to clipboard

add react-server-dom-vite impl and fixture

Open nksaraf opened this issue 2 years ago • 30 comments

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.

nksaraf avatar Jun 11 '23 15:06 nksaraf

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 [email protected]. Thanks!

facebook-github-bot avatar Jun 11 '23 15:06 facebook-github-bot

Comparing: ce6842d8f528977119b80d969306c8475099f66e...979563fa0e8c8980733463b6e46fccdcba87ed74

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 :no_entry_sign: dangerJS against 979563fa0e8c8980733463b6e46fccdcba87ed74

react-sizebot avatar Jun 11 '23 15:06 react-sizebot

For context and credit. There was some previous work done on a Vite plugin from @frandiox in https://github.com/facebook/react/pull/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 avatar Jun 11 '23 17:06 sebmarkbage

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.

sebmarkbage avatar Jun 11 '23 17:06 sebmarkbage

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 avatar Jun 11 '23 17:06 nksaraf

@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 avatar Jun 11 '23 18:06 nksaraf

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

nksaraf avatar Jun 11 '23 19:06 nksaraf

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. They will directly pass the reference metadata (specifier, name) to the vite functions which will do their thing based on whether its on the browser, or node, or edge, and whether its in prod or development. This allows for much easier handling accounting for how vite has different mechanics during dev and prod.

Also changed the fixture to output CJS for the SSR and react-server chunks so that they can be sync required and initialized at the point they are needed.

@sebmarkbage would love your thoughts on this

nksaraf avatar Jun 12 '23 18:06 nksaraf

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.

sebmarkbage avatar Jun 12 '23 18:06 sebmarkbage

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.

sebmarkbage avatar Jun 12 '23 18:06 sebmarkbage

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?

nksaraf avatar Jun 13 '23 13:06 nksaraf

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 avatar Jun 20 '23 20:06 sebmarkbage

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.

sebmarkbage avatar Jun 20 '23 21:06 sebmarkbage

  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.

nksaraf avatar Jun 21 '23 10:06 nksaraf

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 avatar Jun 21 '23 15:06 sebmarkbage

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.

sebmarkbage avatar Jun 21 '23 15:06 sebmarkbage

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?

nksaraf avatar Jun 21 '23 17:06 nksaraf

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.

sebmarkbage avatar Jun 21 '23 18:06 sebmarkbage

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.

nksaraf avatar Jul 16 '23 15:07 nksaraf

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?

cyco130 avatar Jul 25 '23 06:07 cyco130

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

nksaraf avatar Jul 25 '23 12:07 nksaraf

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.

cyco130 avatar Jul 25 '23 12:07 cyco130

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

sebmarkbage avatar Sep 06 '23 02:09 sebmarkbage

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

Yeah would love the help

nksaraf avatar Sep 22 '23 21:09 nksaraf

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.

lazarv avatar Sep 30 '23 18:09 lazarv

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!

svedova avatar Oct 10 '23 08:10 svedova

@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

nksaraf avatar Oct 15 '23 21:10 nksaraf

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

jkhaui avatar Oct 16 '23 01:10 jkhaui

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.

dilane3 avatar Feb 01 '24 12:02 dilane3

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[bot] avatar May 01 '24 13:05 github-actions[bot]