Shopify / hydrogen-v1

React-based framework for building dynamic, Shopify-powered custom storefronts.
https://shopify.github.io/hydrogen-v1/
MIT License
3.75k stars 326 forks source link

Build a version of server components that works in Vite and Workers runtimes #249

Open jplhomer opened 2 years ago

jplhomer commented 2 years ago

This might be packaged into React core as a sibling package to react-server-dom-webpack, e.g. react-server-dom-vite. Alternatively, it might be a standalone plugin or package. It depends on what direction the core team decides and what other meta-frameworks do (like Next.js).

Outstanding questions:

Note: This is a bit of a moving target, as we've recently embarked on a mission to help React bundle client components more efficiently: https://github.com/facebook/react/issues/22314

So whatever we build for Vite should match the output of those explorations.

michenly commented 2 years ago

How do we get Vite to behave differently based on react-server context? React uses node --conditions react-server. Do we need two separate server builds or dev processes (this seems inefficient)? Is there a better way to do this?

There are two type of way node --conditions react-server flag is being use

  1. with https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js where there is an inline check in runtime
  2. with https://github.com/facebook/react/blob/main/packages/react-fs/index.js#L12 (and a few others), where it throws an error in default index file unless you load the correct node or browser package. (it is also listed in package.json 's export field)

I am not exactly sure what to do with 1, but for two I am curious to explore loading the correct module base on file post fix. ie. if the file importing it is .server.js, load the node version, if its .client.js, load the browser version. I think this can be done with some sort of Vite plugin.

frandiox commented 2 years ago

So I've been tinkering with the offical RSC and found out it directly crashes when a Provider is used. Since our dev project is full of providers, I'm testing with a dummy app without any providers (we are supposed to remove them anyway). The whole tree looks like this (CartIcon is the only Client Component):

<div>
      <div>hello!</div>
      <CartIcon />
</div>

RSC response using our custom RSC implementation (in main branch):

M1:{"name":"CartIcon","id":"/Users/frandiox/src/github.com/Shopify/hydrogen/packages/dev/src/components/CartIcon.client.jsx","named":false}
J0:["$","div",null,{"children":[["$","div",null,{"key":0,"children":"hello!"}],["$","@1",null,{"children":null}]]}]

RSC response using the official implementation:

M1:{"id":"/Users/frandiox/src/github.com/Shopify/hydrogen/packages/dev/src/components/CartIcon.client.jsx","name":"CartIcon","named":false}
J0:["$","div",null,{"children":[["$","div",null,{"children":"hello!"}],["$","@1",null,{}]]}]

The only different is that the official approach is missing "key": 0.


However, when adding Suspense to the root of the dummy app, this happens.

RSC response using our custom RSC implementation (with Suspense):

M1:{"name":"CartIcon","id":"/Users/frandiox/src/github.com/Shopify/hydrogen/packages/dev/src/components/CartIcon.client.jsx","named":false}
J0:["$","div",null,{"key":1,"children":[["$","div",null,{"key":0,"children":"hello!"}],["$","@1",null,{"children":null}]]}]

RSC response using the official implementation:

S1:"react.suspense"
M2:{"id":"/Users/frandiox/src/github.com/Shopify/hydrogen/packages/dev/src/components/CartIcon.client.jsx","name":"CartIcon","named":false}
J0:["$","$1",null,{"children":["$","div",null,{"children":[["$","div",null,{"children":"hello!"}],["$","@2",null,{}]]}]}]

@wizardlyhel @jplhomer Do you think these differences are normal? Is the Suspense supposed to appear in the payload of the official implementation? 🤔

NOTE: this is still using our custom logic in the browser to parse the RSC response, that's why I'm passing "named" instead of "chunks".

wizardlyhel commented 2 years ago

@frandiox Yup - the S1:"react.suspense" is normal. Think of them are acting like client components waiting to be downloaded.

I'm gonna defer to @jplhomer on how he implemented Suspense with our custom implementation with key

jplhomer commented 2 years ago

@frandiox Yep you're spot-on. key is probably there because of my super hacky parsing mechanism 😬 we should trust whatever the official RSC implementation offers.

Additionally, the official RSC implementation uses name: <name> rather than named: true/false, and that matches how RSC resolves everything in the client. (this is before I learned how all of this worked)

frandiox commented 2 years ago

Blocked by:

frandiox commented 2 years ago

Update: Since react-server and react-client packages won't be published to NPM, we need to make a PR to React repo instead: https://github.com/facebook/react/pull/22952