Skip to content

Commit

Permalink
feat: Consume RSC response in the server (#463)
Browse files Browse the repository at this point in the history
* wip: Create custom flight renderer with local version of react-server

* wip: mock client manifest and add module reference to client components

* wip: hack RSC resolution

* wip: wrap component in Proxy to access original properties

* refactor: rename RSC server files

* wip: add official RSC hydrator

* fix: Rename response.readRoot and remove explicit hydration

* wip: remove RR and Helmet providers from the server

* wip: Add test app

* wip: remove hydration providers

* wip: add renderToReadableStream for RSC

* refactor: cleanup custom RSC code

* refactor: move and rename files

* wip: fix test app

* refactor: cleanup custom RSC code

* refactor: do not pass named boolean in RSC

* refactor: simplify ClientMarker

* test: fix ClientMarker tests

* fix: delay throwing error when client component is missing

* refactor: simplify import globs code

* feat: provide request object to rendering tree

* wip: example of useServerRequest

* feat: inline RSC response in the SSR HTML response

* fix: add default value to the SSR provider

* feat: stream rsc in script tags

* feat: update the starter template to work

* fix: lint errors

* refactor: move code around and cleanup

* chore: upgrade Vite to 2.7.0

* chore: add react-server-dom-vite as vendor

* feat: replace local react-server and react-client with react-server-dom-vite vendor

* fix: react-server-dom-vite allow exporting hooks from client components temporarily

* fix: move the server logging locations

* feat: Implement ReadableStream branch in SSR and RSC

* test: fix playground apps

* fix: avoid importing undefined exports depending on the running environment

* feat: Buffer RSC response if it cannot be streamed

* fix: tests

* fix: e2e test

* fix: linting

* fix: rename test helper

* fix: update hydrogen template files

* feat: remove react-ssr-prepass. RSC already makes ReadableStream required

* refactor: simplify hydration calls

* feat: remove old dependencies

* feat: enable tree shaking in worker build

* fix: Release stream lock before piping

* chore: fix formatting

* refactor: move code to entry-server to be consistent

* refactor: remove HydrationWriter in favor of Node native PassThrough

* fix: move constant to a separate file to avoid importing app logic in GraphiQL

* refactor: remove old code

* fix: maybe fix tests for windows

* fix: stream import in workers

* fix: flush RSC right after writing head

* feat: replace RenderCacheProvider with new ServerRequestProvider cache

* refactor: cleanup

* fix: suspense breaking hydration

* fix: normalize RSC chunks

* refactor: simplify customBody check

* feat: minor tree-shaking improvements

* fix: enable browser hydration

* fix: replace TransformStream with ReadableStream to support Firefox

* refactor: cleanup and rename variables

* feat: Make ReadableStream globally available

* wip: consume RSC response in server

* wip: do not close stream connection

* wip: fix reentrant error with ReadableStreams in RSC

* fix: import Node RSC logic dynamically

* fix: rename Cache.client.js to avoid bundling it in workers build

* fix: add temporary monkey patch for React Fizz in workers build

* feat: Combine SSR and RSC responses in Node

* fix: write RSC before ending response when not streaming

* feat: Combine SSR and RSC responses in Worker

* feat: Use ReadableStream in CFW and stream if supported

* fix: add another temporary monkey patch for React Fizz/Flight in workers build

* fix: close writable on redirects

* fix: Fix hydration during dev by using more Suspense boundaries

* fix: add back response.socket listener

* feat: support script nonce and shorten access to __flight

* feat: avoid writing fractional chunks in SSR

* fix: initialize flight container in buffered rendering

* fix: ensure flight container is added in Node

* refactor: `readable` to `ssrReadable` for clarity

Co-authored-by: Bret Little <bret.little@shopify.com>
Co-authored-by: Josh Larson <josh.larson@shopify.com>
  • Loading branch information
3 people authored Jan 21, 2022
1 parent d8c87eb commit 871b892
Show file tree
Hide file tree
Showing 12 changed files with 387 additions and 216 deletions.
54 changes: 31 additions & 23 deletions packages/dev/src/components/Welcome.server.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {useShopQuery, flattenConnection, Link} from '@shopify/hydrogen';
import gql from 'graphql-tag';
import {Suspense} from 'react';

function ExternalIcon() {
return (
Expand Down Expand Up @@ -31,7 +32,20 @@ function DocsButton({url, label}) {
);
}

function StorefrontInfo({shopName, totalProducts, totalCollections}) {
function BoxFallback() {
return (
<div className="bg-white p-12 shadow-xl rounded-xl text-gray-900 h-60"></div>
);
}

function StorefrontInfo() {
const {data} = useShopQuery({query: QUERY});
const shopName = data ? data.shop.name : '';
const products = data && flattenConnection(data.products);
const collections = data && flattenConnection(data.collections);
const totalProducts = products && products.length;
const totalCollections = collections && collections.length;

const pluralize = (count, noun, suffix = 's') =>
`${count} ${noun}${count === 1 ? '' : suffix}`;
return (
Expand Down Expand Up @@ -71,7 +85,14 @@ function StorefrontInfo({shopName, totalProducts, totalCollections}) {
);
}

function TemplateLinks({firstProductPath, firstCollectionPath}) {
function TemplateLinks() {
const {data} = useShopQuery({query: QUERY});
const products = data && flattenConnection(data.products);
const collections = data && flattenConnection(data.collections);

const firstProduct = products && products.length ? products[0].handle : '';
const firstCollection = collections[0] ? collections[0].handle : '';

return (
<div className="bg-white p-12 md:p-12 shadow-xl rounded-xl text-gray-900">
<p className="text-md font-medium uppercase mb-4">
Expand All @@ -80,15 +101,15 @@ function TemplateLinks({firstProductPath, firstCollectionPath}) {
<ul>
<li className="mb-4">
<Link
to={`/collections/${firstCollectionPath}`}
to={`/collections/${firstCollection}`}
className="text-md font-medium text-blue-700 hover:underline"
>
Collection template
</Link>
</li>
<li className="mb-4">
<Link
to={`/products/${firstProductPath}`}
to={`/products/${firstProduct}`}
className="text-md font-medium text-blue-700 hover:underline"
>
Product template
Expand All @@ -111,16 +132,6 @@ function TemplateLinks({firstProductPath, firstCollectionPath}) {
* A server component that displays the content on the homepage of the Hydrogen app
*/
export default function Welcome() {
const {data} = useShopQuery({query: QUERY});
const shopName = data ? data.shop.name : '';
const products = data && flattenConnection(data.products);
const collections = data && flattenConnection(data.collections);

const firstProduct = products && products.length ? products[0].handle : '';
const totalProducts = products && products.length;
const firstCollection = collections[0] ? collections[0].handle : '';
const totalCollections = collections && collections.length;

return (
<div className="text-gray-900 pt-16 rounded-[40px] my-16 px-4 xl:px-12 bg-gradient-to-b from-white -mx-4 xl:-mx-12">
<div className="text-center mb-16">
Expand All @@ -143,15 +154,12 @@ export default function Welcome() {
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16">
<StorefrontInfo
shopName={shopName}
totalProducts={totalProducts}
totalCollections={totalCollections}
/>
<TemplateLinks
firstProductPath={firstProduct}
firstCollectionPath={firstCollection}
/>
<Suspense fallback={<BoxFallback />}>
<StorefrontInfo />
</Suspense>
<Suspense fallback={<BoxFallback />}>
<TemplateLinks />
</Suspense>
</div>
</div>
);
Expand Down
148 changes: 88 additions & 60 deletions packages/dev/src/pages/index.server.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,94 @@ import Layout from '../components/Layout.server';
import FeaturedCollection from '../components/FeaturedCollection';
import ProductCard from '../components/ProductCard';
import Welcome from '../components/Welcome.server';
import {Suspense} from 'react';

export default function Index({country = {isoCode: 'US'}}) {
return (
<Layout hero={<GradientBackground />}>
<div className="relative mb-12">
<Welcome />
<Suspense fallback={<BoxFallback />}>
<FeaturedProductsBox country={country} />
</Suspense>
<Suspense fallback={<BoxFallback />}>
<FeaturedCollectionBox country={country} />
</Suspense>
</div>
</Layout>
);
}

function BoxFallback() {
return <div className="bg-white p-12 shadow-xl rounded-xl mb-10 h-40"></div>;
}

function FeaturedProductsBox({country}) {
const {data} = useShopQuery({
query: QUERY,
variables: {
country: country.isoCode,
},
});

const collections = data ? flattenConnection(data.collections) : [];
const featuredProductsCollection = collections[0];
const featuredProducts = featuredProductsCollection
? flattenConnection(featuredProductsCollection.products)
: null;

return (
<div className="bg-white p-12 shadow-xl rounded-xl mb-10">
{featuredProductsCollection ? (
<>
<div className="flex justify-between items-center mb-8 text-md font-medium">
<span className="text-black uppercase">
{featuredProductsCollection.title}
</span>
<span className="hidden md:inline-flex">
<Link
to={`/collections/${featuredProductsCollection.handle}`}
className="text-blue-600 hover:underline"
>
Shop all
</Link>
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-8">
{featuredProducts.map((product) => (
<div key={product.id}>
<ProductCard product={product} />
</div>
))}
</div>
<div className="md:hidden text-center">
<Link
to={`/collections/${featuredProductsCollection.handle}`}
className="text-blue-600"
>
Shop all
</Link>
</div>
</>
) : null}
</div>
);
}

function FeaturedCollectionBox({country}) {
const {data} = useShopQuery({
query: QUERY,
variables: {
country: country.isoCode,
},
});

const collections = data ? flattenConnection(data.collections) : [];
const featuredCollection =
collections && collections.length > 1 ? collections[1] : collections[0];

return <FeaturedCollection collection={featuredCollection} />;
}

function GradientBackground() {
return (
Expand Down Expand Up @@ -67,66 +155,6 @@ function GradientBackground() {
);
}

export default function Index({country = {isoCode: 'US'}}) {
const {data} = useShopQuery({
query: QUERY,
variables: {
country: country.isoCode,
},
});

const collections = data ? flattenConnection(data.collections) : [];
const featuredProductsCollection = collections[0];
const featuredProducts = featuredProductsCollection
? flattenConnection(featuredProductsCollection.products)
: null;
const featuredCollection =
collections && collections.length > 1 ? collections[1] : collections[0];

return (
<Layout hero={<GradientBackground />}>
<div className="relative mb-12">
<Welcome />
<div className="bg-white p-12 shadow-xl rounded-xl mb-10">
{featuredProductsCollection ? (
<>
<div className="flex justify-between items-center mb-8 text-md font-medium">
<span className="text-black uppercase">
{featuredProductsCollection.title}
</span>
<span className="hidden md:inline-flex">
<Link
to={`/collections/${featuredProductsCollection.handle}`}
className="text-blue-600 hover:underline"
>
Shop all
</Link>
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-8">
{featuredProducts.map((product) => (
<div key={product.id}>
<ProductCard product={product} />
</div>
))}
</div>
<div className="md:hidden text-center">
<Link
to={`/collections/${featuredCollection.handle}`}
className="text-blue-600"
>
Shop all
</Link>
</div>
</>
) : null}
</div>
<FeaturedCollection collection={featuredCollection} />
</div>
</Layout>
);
}

const QUERY = gql`
query indexContent(
$country: CountryCode
Expand Down
2 changes: 1 addition & 1 deletion packages/hydrogen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@rollup/plugin-graphql": "^1.0.0",
"@types/connect": "^3.4.34",
"@types/graphql": "^14.5.0",
"@types/node": "^15.12.4",
"@types/node": "^16.11.7",
"@types/node-fetch": "^2.5.9",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/hydrogen/src/entry-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {createRoot} from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import type {ClientHandler, ShopifyConfig} from './types';
import {ErrorBoundary} from 'react-error-boundary';
import {useServerResponse} from './framework/Hydration/Cache.client';
import {useServerResponse} from './framework/Hydration/rsc';
import {
ServerStateProvider,
ServerStateRouter,
Expand Down
Loading

0 comments on commit 871b892

Please sign in to comment.