@shopify/hydrogen 0.7.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.d.ts +1 -1
- package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.js +1 -1
- package/dist/esnext/components/CartLineQuantityAdjustButton/CartLineQuantityAdjustButton.js +4 -0
- package/dist/esnext/components/LocalizationProvider/index.d.ts +1 -0
- package/dist/esnext/components/LocalizationProvider/index.js +1 -0
- package/dist/esnext/components/ProductPrice/ProductPrice.client.d.ts +1 -3
- package/dist/esnext/components/ProductPrice/ProductPrice.client.js +1 -3
- package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +1 -1
- package/dist/esnext/components/UnitPrice/UnitPrice.client.d.ts +1 -2
- package/dist/esnext/components/UnitPrice/UnitPrice.client.js +1 -2
- package/dist/esnext/components/index.d.ts +1 -0
- package/dist/esnext/components/index.js +1 -0
- package/dist/esnext/entry-client.js +4 -6
- package/dist/esnext/entry-server.js +70 -47
- package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.d.ts +2 -0
- package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.js +4 -0
- package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.d.ts +2 -0
- package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.js +5 -0
- package/dist/esnext/foundation/RenderCacheProvider/hook.d.ts +11 -0
- package/dist/esnext/foundation/RenderCacheProvider/hook.js +34 -0
- package/dist/esnext/foundation/RenderCacheProvider/index.d.ts +1 -0
- package/dist/esnext/foundation/RenderCacheProvider/index.js +1 -0
- package/dist/esnext/foundation/RenderCacheProvider/types.d.ts +18 -0
- package/dist/esnext/foundation/RenderCacheProvider/types.js +1 -0
- package/dist/esnext/foundation/Router/DefaultRoutes.d.ts +3 -1
- package/dist/esnext/foundation/Router/DefaultRoutes.js +2 -2
- package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.js +4 -2
- package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.d.ts +1 -3
- package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.js +2 -4
- package/dist/esnext/foundation/ShopifyProvider/types.d.ts +0 -5
- package/dist/esnext/foundation/useQuery/hooks.d.ts +8 -6
- package/dist/esnext/foundation/useQuery/hooks.js +13 -14
- package/dist/esnext/foundation/useQuery/index.d.ts +0 -1
- package/dist/esnext/foundation/useQuery/index.js +0 -1
- package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
- package/dist/esnext/foundation/useShop/use-shop.js +1 -1
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +2 -0
- package/dist/esnext/framework/cache.d.ts +2 -2
- package/dist/esnext/framework/cache.js +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +15 -1
- package/dist/esnext/framework/plugins/vite-plugin-react-server-components-shim.js +1 -13
- package/dist/esnext/handle-event.d.ts +1 -1
- package/dist/esnext/handle-event.js +68 -10
- package/dist/esnext/hooks/index.d.ts +1 -1
- package/dist/esnext/hooks/index.js +1 -1
- package/dist/esnext/hooks/useShopQuery/hooks.d.ts +4 -4
- package/dist/esnext/hooks/useShopQuery/hooks.js +47 -20
- package/dist/esnext/index.d.ts +2 -1
- package/dist/esnext/index.js +2 -1
- package/dist/esnext/types.d.ts +2 -1
- package/dist/esnext/utilities/fetch.js +3 -6
- package/dist/esnext/utilities/index.d.ts +1 -0
- package/dist/esnext/utilities/index.js +1 -0
- package/dist/esnext/utilities/log/index.d.ts +1 -0
- package/dist/esnext/utilities/log/index.js +1 -0
- package/dist/esnext/utilities/log/log.d.ts +17 -0
- package/dist/esnext/utilities/log/log.js +75 -0
- package/dist/esnext/utilities/suspense.d.ts +2 -2
- package/dist/esnext/utilities/suspense.js +0 -6
- package/dist/esnext/utilities/timing.d.ts +7 -0
- package/dist/esnext/utilities/timing.js +14 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +2 -0
- package/dist/node/framework/cache.d.ts +2 -2
- package/dist/node/framework/cache.js +2 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +15 -1
- package/dist/node/framework/plugins/vite-plugin-react-server-components-shim.js +1 -13
- package/dist/node/handle-event.d.ts +1 -1
- package/dist/node/handle-event.js +68 -10
- package/dist/node/types.d.ts +2 -1
- package/dist/node/utilities/fetch.js +3 -6
- package/dist/node/utilities/index.d.ts +1 -0
- package/dist/node/utilities/index.js +3 -1
- package/dist/node/utilities/suspense.d.ts +2 -2
- package/dist/node/utilities/suspense.js +0 -6
- package/dist/node/utilities/timing.d.ts +7 -0
- package/dist/node/utilities/timing.js +18 -0
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +2 -0
- package/dist/worker/framework/cache.d.ts +2 -2
- package/dist/worker/framework/cache.js +1 -1
- package/dist/worker/handle-event.d.ts +1 -1
- package/dist/worker/handle-event.js +68 -10
- package/dist/worker/types.d.ts +2 -1
- package/dist/worker/utilities/timing.d.ts +7 -0
- package/dist/worker/utilities/timing.js +14 -0
- package/package.json +10 -9
- package/dist/esnext/foundation/useQuery/QueryProvider.d.ts +0 -6
- package/dist/esnext/foundation/useQuery/QueryProvider.js +0 -13
|
@@ -7,7 +7,7 @@ export interface CartEstimatedCostProps {
|
|
|
7
7
|
children?: React.ReactNode;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* The
|
|
10
|
+
* The `CartEstimatedCost` component renders a `Money` component with the
|
|
11
11
|
* cost associated with the `amountType` prop. If no `amountType` prop is specified, then it defaults to `totalAmount`.
|
|
12
12
|
* If `children` is a function, then it will pass down the render props provided by the parent component.
|
|
13
13
|
*/
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useCart } from '../CartProvider';
|
|
3
3
|
import { Money } from '../Money';
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
5
|
+
* The `CartEstimatedCost` component renders a `Money` component with the
|
|
6
6
|
* cost associated with the `amountType` prop. If no `amountType` prop is specified, then it defaults to `totalAmount`.
|
|
7
7
|
* If `children` is a function, then it will pass down the render props provided by the parent component.
|
|
8
8
|
*/
|
|
@@ -17,6 +17,10 @@ export function CartLineQuantityAdjustButton(props) {
|
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
const quantity = adjust === 'decrease' ? cartLine.quantity - 1 : cartLine.quantity + 1;
|
|
20
|
+
if (quantity <= 0) {
|
|
21
|
+
removeLines([cartLine.id]);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
updateLines([{ id: cartLine.id, quantity }]);
|
|
21
25
|
}, ...passthroughProps }, children));
|
|
22
26
|
}
|
|
@@ -8,8 +8,6 @@ export interface ProductPriceProps {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* The `ProductPrice` component renders a `Money` component with the product
|
|
11
|
-
* [`priceRange`](/api/storefront/reference/products/productpricerange)'s `maxVariantPrice`
|
|
12
|
-
* or `minVariantPrice`, for either the regular price or compare at price range.
|
|
13
|
-
* It must be a descendent of the `ProductProvider` component.
|
|
11
|
+
* [`priceRange`](/api/storefront/reference/products/productpricerange)'s `maxVariantPrice` or `minVariantPrice`, for either the regular price or compare at price range. It must be a descendent of the `ProductProvider` component.
|
|
14
12
|
*/
|
|
15
13
|
export declare function ProductPrice<TTag extends ElementType>(props: Props<TTag> & ProductPriceProps): JSX.Element | null;
|
|
@@ -3,9 +3,7 @@ import { Money } from '../Money';
|
|
|
3
3
|
import { useProduct } from '../ProductProvider';
|
|
4
4
|
/**
|
|
5
5
|
* The `ProductPrice` component renders a `Money` component with the product
|
|
6
|
-
* [`priceRange`](/api/storefront/reference/products/productpricerange)'s `maxVariantPrice`
|
|
7
|
-
* or `minVariantPrice`, for either the regular price or compare at price range.
|
|
8
|
-
* It must be a descendent of the `ProductProvider` component.
|
|
6
|
+
* [`priceRange`](/api/storefront/reference/products/productpricerange)'s `maxVariantPrice` or `minVariantPrice`, for either the regular price or compare at price range. It must be a descendent of the `ProductProvider` component.
|
|
9
7
|
*/
|
|
10
8
|
export function ProductPrice(props) {
|
|
11
9
|
var _a, _b, _c, _d;
|
|
@@ -11,7 +11,7 @@ export function ShopPayButton({ variantIds, className }) {
|
|
|
11
11
|
const { storeDomain } = useShop();
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
const ids = variantIds.reduce((accumulator, gid) => {
|
|
14
|
-
const id =
|
|
14
|
+
const id = gid.split('/').pop();
|
|
15
15
|
if (id) {
|
|
16
16
|
accumulator.push(id);
|
|
17
17
|
}
|
|
@@ -13,8 +13,7 @@ export interface UnitPriceProps {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* The `UnitPrice` component renders a string with a [UnitPrice](/themes/pricing-payments/unit-pricing) as the
|
|
16
|
-
* [Storefront API's `MoneyV2` object](/api/storefront/reference/common-objects/moneyv2) with a reference unit from
|
|
17
|
-
* the [Storefront API's `UnitPriceMeasurement` object](/api/storefront/reference/products/unitpricemeasurement).
|
|
16
|
+
* [Storefront API's `MoneyV2` object](/api/storefront/reference/common-objects/moneyv2) with a reference unit from the [Storefront API's `UnitPriceMeasurement` object](/api/storefront/reference/products/unitpricemeasurement).
|
|
18
17
|
*
|
|
19
18
|
* If `children` is a function, then it will provide render props for the `children` corresponding to the object
|
|
20
19
|
* returned by the `useMoney` hook and the `UnitPriceMeasurement` object.
|
|
@@ -4,8 +4,7 @@ import { Money } from '../Money';
|
|
|
4
4
|
import { UnitPriceFragment as Fragment } from '../../graphql/graphql-constants';
|
|
5
5
|
/**
|
|
6
6
|
* The `UnitPrice` component renders a string with a [UnitPrice](/themes/pricing-payments/unit-pricing) as the
|
|
7
|
-
* [Storefront API's `MoneyV2` object](/api/storefront/reference/common-objects/moneyv2) with a reference unit from
|
|
8
|
-
* the [Storefront API's `UnitPriceMeasurement` object](/api/storefront/reference/products/unitpricemeasurement).
|
|
7
|
+
* [Storefront API's `MoneyV2` object](/api/storefront/reference/common-objects/moneyv2) with a reference unit from the [Storefront API's `UnitPriceMeasurement` object](/api/storefront/reference/products/unitpricemeasurement).
|
|
9
8
|
*
|
|
10
9
|
* If `children` is a function, then it will provide render props for the `children` corresponding to the object
|
|
11
10
|
* returned by the `useMoney` hook and the `UnitPriceMeasurement` object.
|
|
@@ -28,6 +28,7 @@ export { CartLines } from './CartLines';
|
|
|
28
28
|
export { CartCheckoutButton } from './CartCheckoutButton';
|
|
29
29
|
export { CartShopPayButton } from './CartShopPayButton';
|
|
30
30
|
export { CartEstimatedCost } from './CartEstimatedCost';
|
|
31
|
+
export { CartLineSelectedOptions } from './CartLineSelectedOptions';
|
|
31
32
|
export { CartProvider, useCart, useCartAttributesUpdateCallback, useCartBuyerIdentityUpdateCallback, useCartNoteUpdateCallback, useCartCheckoutUrl, useCartCreateCallback, useCartDiscountCodesUpdateCallback, useCartFetch, useCartLinesAddCallback, useCartLinesRemoveCallback, useCartLinesTotalQuantity, useCartLinesUpdateCallback, useInstantCheckout, } from './CartProvider';
|
|
32
33
|
export type { State, Status, Cart, CartWithActions, CartAction, } from './CartProvider';
|
|
33
34
|
export { ProductProvider, useProduct, ProductProviderFragment, } from './ProductProvider';
|
|
@@ -19,6 +19,7 @@ export { CartLines } from './CartLines';
|
|
|
19
19
|
export { CartCheckoutButton } from './CartCheckoutButton';
|
|
20
20
|
export { CartShopPayButton } from './CartShopPayButton';
|
|
21
21
|
export { CartEstimatedCost } from './CartEstimatedCost';
|
|
22
|
+
export { CartLineSelectedOptions } from './CartLineSelectedOptions';
|
|
22
23
|
export { CartProvider, useCart, useCartAttributesUpdateCallback, useCartBuyerIdentityUpdateCallback, useCartNoteUpdateCallback, useCartCheckoutUrl, useCartCreateCallback, useCartDiscountCodesUpdateCallback, useCartFetch, useCartLinesAddCallback, useCartLinesRemoveCallback, useCartLinesTotalQuantity, useCartLinesUpdateCallback, useInstantCheckout, } from './CartProvider';
|
|
23
24
|
export { ProductProvider, useProduct, ProductProviderFragment, } from './ProductProvider';
|
|
24
25
|
export { ProductDescription } from './ProductDescription';
|
|
@@ -6,7 +6,6 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|
|
6
6
|
import { HelmetProvider } from 'react-helmet-async';
|
|
7
7
|
import { useServerResponse } from './framework/Hydration/Cache.client';
|
|
8
8
|
import { ServerStateProvider, ServerStateRouter } from './client';
|
|
9
|
-
import { QueryProvider } from './hooks';
|
|
10
9
|
const renderHydrogen = async (ClientWrapper) => {
|
|
11
10
|
const root = document.getElementById('root');
|
|
12
11
|
if (!root) {
|
|
@@ -25,11 +24,10 @@ function Content({ clientWrapper: ClientWrapper }) {
|
|
|
25
24
|
});
|
|
26
25
|
const response = useServerResponse(serverState);
|
|
27
26
|
return (React.createElement(ServerStateProvider, { serverState: serverState, setServerState: setServerState },
|
|
28
|
-
React.createElement(
|
|
29
|
-
React.createElement(
|
|
30
|
-
React.createElement(
|
|
31
|
-
|
|
32
|
-
React.createElement(ClientWrapper, null, response.read()))))));
|
|
27
|
+
React.createElement(HelmetProvider, null,
|
|
28
|
+
React.createElement(BrowserRouter, null,
|
|
29
|
+
React.createElement(ServerStateRouter, null),
|
|
30
|
+
React.createElement(ClientWrapper, null, response.read())))));
|
|
33
31
|
}
|
|
34
32
|
function Error({ error }) {
|
|
35
33
|
return (React.createElement("div", { style: { padding: '1em' } },
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
3
|
// @ts-ignore
|
|
4
|
-
|
|
4
|
+
renderToPipeableStream, // Only available in Node context
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
renderToReadableStream, // Only available in Browser/Worker context
|
|
7
|
+
} from 'react-dom/server';
|
|
8
|
+
import { logServerResponse, getLoggerFromContext, } from './utilities/log/log';
|
|
5
9
|
import { renderToString } from 'react-dom/server';
|
|
6
10
|
import { getErrorMarkup } from './utilities/error';
|
|
7
11
|
import ssrPrepass from 'react-ssr-prepass';
|
|
@@ -12,8 +16,8 @@ import { HelmetProvider } from 'react-helmet-async';
|
|
|
12
16
|
import { Html } from './framework/Hydration/Html';
|
|
13
17
|
import { HydrationWriter } from './framework/Hydration/writer.server';
|
|
14
18
|
import { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
|
|
15
|
-
import { dehydrate } from 'react-query/hydration';
|
|
16
19
|
import { getCacheControlHeader } from './framework/cache';
|
|
20
|
+
import { RenderCacheProvider } from './foundation/RenderCacheProvider';
|
|
17
21
|
/**
|
|
18
22
|
* react-dom/unstable-fizz provides different entrypoints based on runtime:
|
|
19
23
|
* - `renderToReadableStream` for "browser" (aka worker)
|
|
@@ -33,7 +37,8 @@ const renderHydrogen = (App, hook) => {
|
|
|
33
37
|
* NOTE: This is currently only used for SEO bots or Worker runtime (where Stream is not yet supported).
|
|
34
38
|
*/
|
|
35
39
|
const render = async function (url, { context, request, isReactHydrationRequest, dev }) {
|
|
36
|
-
var _a, _b;
|
|
40
|
+
var _a, _b, _c, _d, _f;
|
|
41
|
+
const log = getLoggerFromContext(request);
|
|
37
42
|
const state = isReactHydrationRequest
|
|
38
43
|
? JSON.parse((_b = (_a = url.searchParams) === null || _a === void 0 ? void 0 : _a.get('state')) !== null && _b !== void 0 ? _b : '{}')
|
|
39
44
|
: { pathname: url.pathname, search: url.search };
|
|
@@ -43,8 +48,10 @@ const renderHydrogen = (App, hook) => {
|
|
|
43
48
|
context,
|
|
44
49
|
request,
|
|
45
50
|
dev,
|
|
51
|
+
log,
|
|
46
52
|
});
|
|
47
|
-
const body = await renderApp(ReactApp, state, isReactHydrationRequest);
|
|
53
|
+
const body = await renderApp(ReactApp, state, log, isReactHydrationRequest);
|
|
54
|
+
logServerResponse('ssr', log, request, (_f = (_d = (_c = componentResponse.customStatus) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : componentResponse.status) !== null && _f !== void 0 ? _f : 200);
|
|
48
55
|
if (componentResponse.customBody) {
|
|
49
56
|
return { body: await componentResponse.customBody, url, componentResponse };
|
|
50
57
|
}
|
|
@@ -62,6 +69,7 @@ const renderHydrogen = (App, hook) => {
|
|
|
62
69
|
* information, so this method should not be used by crawlers.
|
|
63
70
|
*/
|
|
64
71
|
const stream = function (url, { context, request, response, template, dev }) {
|
|
72
|
+
const log = getLoggerFromContext(request);
|
|
65
73
|
const state = { pathname: url.pathname, search: url.search };
|
|
66
74
|
const { ReactApp, componentResponse } = buildReactApp({
|
|
67
75
|
App,
|
|
@@ -69,34 +77,36 @@ const renderHydrogen = (App, hook) => {
|
|
|
69
77
|
context,
|
|
70
78
|
request,
|
|
71
79
|
dev,
|
|
80
|
+
log,
|
|
72
81
|
});
|
|
73
82
|
response.socket.on('error', (error) => {
|
|
74
|
-
|
|
83
|
+
log.fatal(error);
|
|
75
84
|
});
|
|
76
85
|
let didError;
|
|
77
86
|
const head = template.match(/<head>(.+?)<\/head>/s)[1];
|
|
78
|
-
const {
|
|
79
|
-
React.createElement(ReactApp, { ...state })),
|
|
80
|
-
|
|
87
|
+
const { pipe } = renderToPipeableStream(React.createElement(Html, { head: head },
|
|
88
|
+
React.createElement(ReactApp, { ...state })), {
|
|
89
|
+
onCompleteShell() {
|
|
81
90
|
/**
|
|
82
91
|
* TODO: This assumes `response.cache()` has been called _before_ any
|
|
83
92
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
84
93
|
* additional checks downstream?
|
|
85
94
|
*/
|
|
86
95
|
response.setHeader(getCacheControlHeader({ dev }), componentResponse.cacheControlHeader);
|
|
87
|
-
writeHeadToServerResponse(response, componentResponse, didError);
|
|
96
|
+
writeHeadToServerResponse(request, response, componentResponse, log, didError);
|
|
88
97
|
if (isRedirect(response)) {
|
|
89
98
|
// Return redirects early without further rendering/streaming
|
|
90
99
|
return response.end();
|
|
91
100
|
}
|
|
92
101
|
if (!componentResponse.canStream())
|
|
93
102
|
return;
|
|
94
|
-
startWritingHtmlToServerResponse(response,
|
|
103
|
+
startWritingHtmlToServerResponse(response, pipe, dev ? didError : undefined);
|
|
95
104
|
},
|
|
96
105
|
onCompleteAll() {
|
|
106
|
+
clearTimeout(streamTimeout);
|
|
97
107
|
if (componentResponse.canStream() || response.writableEnded)
|
|
98
108
|
return;
|
|
99
|
-
writeHeadToServerResponse(response, componentResponse, didError);
|
|
109
|
+
writeHeadToServerResponse(request, response, componentResponse, log, didError);
|
|
100
110
|
if (isRedirect(response)) {
|
|
101
111
|
// Redirects found after any async code
|
|
102
112
|
return response.end();
|
|
@@ -110,7 +120,7 @@ const renderHydrogen = (App, hook) => {
|
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
122
|
else {
|
|
113
|
-
startWritingHtmlToServerResponse(response,
|
|
123
|
+
startWritingHtmlToServerResponse(response, pipe, dev ? didError : undefined);
|
|
114
124
|
}
|
|
115
125
|
},
|
|
116
126
|
onError(error) {
|
|
@@ -120,15 +130,19 @@ const renderHydrogen = (App, hook) => {
|
|
|
120
130
|
// Delay this error until headers are properly sent.
|
|
121
131
|
response.write(getErrorMarkup(error));
|
|
122
132
|
}
|
|
123
|
-
|
|
133
|
+
log.error(error);
|
|
124
134
|
},
|
|
125
135
|
});
|
|
126
|
-
setTimeout(
|
|
136
|
+
const streamTimeout = setTimeout(() => {
|
|
137
|
+
const errorMessage = `The app failed to stream after ${STREAM_ABORT_TIMEOUT_MS} ms`;
|
|
138
|
+
log.warn(errorMessage);
|
|
139
|
+
}, STREAM_ABORT_TIMEOUT_MS);
|
|
127
140
|
};
|
|
128
141
|
/**
|
|
129
142
|
* Stream a hydration response to the client.
|
|
130
143
|
*/
|
|
131
144
|
const hydrate = function (url, { context, request, response, dev }) {
|
|
145
|
+
const log = getLoggerFromContext(request);
|
|
132
146
|
const state = JSON.parse(url.searchParams.get('state') || '{}');
|
|
133
147
|
const { ReactApp, componentResponse } = buildReactApp({
|
|
134
148
|
App,
|
|
@@ -136,33 +150,38 @@ const renderHydrogen = (App, hook) => {
|
|
|
136
150
|
context,
|
|
137
151
|
request,
|
|
138
152
|
dev,
|
|
153
|
+
log,
|
|
139
154
|
});
|
|
140
155
|
response.socket.on('error', (error) => {
|
|
141
|
-
|
|
156
|
+
log.fatal(error);
|
|
142
157
|
});
|
|
143
158
|
let didError;
|
|
144
159
|
const writer = new HydrationWriter();
|
|
145
|
-
const {
|
|
146
|
-
React.createElement(ReactApp, { ...state })),
|
|
160
|
+
const { pipe } = renderToPipeableStream(React.createElement(HydrationContext.Provider, { value: true },
|
|
161
|
+
React.createElement(ReactApp, { ...state })), {
|
|
147
162
|
/**
|
|
148
163
|
* When hydrating, we have to wait until `onCompleteAll` to avoid having
|
|
149
164
|
* `template` and `script` tags inserted and rendered as part of the hydration response.
|
|
150
165
|
*/
|
|
151
166
|
onCompleteAll() {
|
|
167
|
+
clearTimeout(renderTimeout);
|
|
152
168
|
// Tell React to start writing to the writer
|
|
153
|
-
|
|
169
|
+
pipe(writer);
|
|
154
170
|
// Tell React that the writer is ready to drain, which sometimes results in a last "chunk" being written.
|
|
155
171
|
writer.drain();
|
|
156
172
|
response.statusCode = didError ? 500 : 200;
|
|
157
173
|
response.setHeader(getCacheControlHeader({ dev }), componentResponse.cacheControlHeader);
|
|
158
174
|
response.end(generateWireSyntaxFromRenderedHtml(writer.toString()));
|
|
175
|
+
logServerResponse('rsc', log, request, response.statusCode);
|
|
159
176
|
},
|
|
160
177
|
onError(error) {
|
|
161
178
|
didError = error;
|
|
162
|
-
|
|
179
|
+
log.error(error);
|
|
163
180
|
},
|
|
164
181
|
});
|
|
165
|
-
setTimeout(
|
|
182
|
+
const renderTimeout = setTimeout(() => {
|
|
183
|
+
log.error(`The app failed to render RSC after ${STREAM_ABORT_TIMEOUT_MS} ms`);
|
|
184
|
+
}, STREAM_ABORT_TIMEOUT_MS);
|
|
166
185
|
};
|
|
167
186
|
return {
|
|
168
187
|
render,
|
|
@@ -170,12 +189,19 @@ const renderHydrogen = (App, hook) => {
|
|
|
170
189
|
hydrate,
|
|
171
190
|
};
|
|
172
191
|
};
|
|
173
|
-
function buildReactApp({ App, state, context, request, dev, }) {
|
|
192
|
+
function buildReactApp({ App, state, context, request, dev, log, }) {
|
|
193
|
+
const renderCache = {};
|
|
174
194
|
const helmetContext = {};
|
|
175
195
|
const componentResponse = new ServerComponentResponse();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
const hydrogenServerProps = {
|
|
197
|
+
request,
|
|
198
|
+
response: componentResponse,
|
|
199
|
+
log,
|
|
200
|
+
};
|
|
201
|
+
const ReactApp = (props) => (React.createElement(RenderCacheProvider, { cache: renderCache },
|
|
202
|
+
React.createElement(StaticRouter, { location: { pathname: state.pathname, search: state.search }, context: context },
|
|
203
|
+
React.createElement(HelmetProvider, { context: helmetContext },
|
|
204
|
+
React.createElement(App, { ...props, ...hydrogenServerProps })))));
|
|
179
205
|
return { helmetContext, ReactApp, componentResponse };
|
|
180
206
|
}
|
|
181
207
|
function extractHeadElements(helmetContext) {
|
|
@@ -201,27 +227,31 @@ function supportsReadableStream() {
|
|
|
201
227
|
return false;
|
|
202
228
|
}
|
|
203
229
|
}
|
|
204
|
-
async function renderApp(ReactApp, state, isReactHydrationRequest) {
|
|
230
|
+
async function renderApp(ReactApp, state, log, isReactHydrationRequest) {
|
|
205
231
|
/**
|
|
206
232
|
* Temporary workaround until all Worker runtimes support ReadableStream
|
|
207
233
|
*/
|
|
208
234
|
if (isWorker && !supportsReadableStream()) {
|
|
209
|
-
return renderAppFromStringWithPrepass(ReactApp, state, isReactHydrationRequest);
|
|
235
|
+
return renderAppFromStringWithPrepass(ReactApp, state, log, isReactHydrationRequest);
|
|
210
236
|
}
|
|
211
237
|
const app = isReactHydrationRequest ? (React.createElement(HydrationContext.Provider, { value: true },
|
|
212
238
|
React.createElement(ReactApp, { ...state }))) : (React.createElement(ReactApp, { ...state }));
|
|
213
|
-
return renderAppFromBufferedStream(app, isReactHydrationRequest);
|
|
239
|
+
return renderAppFromBufferedStream(app, log, isReactHydrationRequest);
|
|
214
240
|
}
|
|
215
|
-
function renderAppFromBufferedStream(app, isReactHydrationRequest) {
|
|
241
|
+
function renderAppFromBufferedStream(app, log, isReactHydrationRequest) {
|
|
216
242
|
return new Promise((resolve, reject) => {
|
|
243
|
+
const errorTimeout = setTimeout(() => {
|
|
244
|
+
log.warn(`The app failed to SSR after ${STREAM_ABORT_TIMEOUT_MS} ms`);
|
|
245
|
+
}, STREAM_ABORT_TIMEOUT_MS);
|
|
217
246
|
if (isWorker) {
|
|
218
247
|
let isComplete = false;
|
|
219
248
|
const stream = renderToReadableStream(app, {
|
|
220
249
|
onCompleteAll() {
|
|
250
|
+
clearTimeout(errorTimeout);
|
|
221
251
|
isComplete = true;
|
|
222
252
|
},
|
|
223
253
|
onError(error) {
|
|
224
|
-
|
|
254
|
+
log.error(error);
|
|
225
255
|
reject(error);
|
|
226
256
|
},
|
|
227
257
|
});
|
|
@@ -252,14 +282,15 @@ function renderAppFromBufferedStream(app, isReactHydrationRequest) {
|
|
|
252
282
|
}
|
|
253
283
|
else {
|
|
254
284
|
const writer = new HydrationWriter();
|
|
255
|
-
const {
|
|
285
|
+
const { pipe } = renderToPipeableStream(app, {
|
|
256
286
|
/**
|
|
257
287
|
* When hydrating, we have to wait until `onCompleteAll` to avoid having
|
|
258
288
|
* `template` and `script` tags inserted and rendered as part of the hydration response.
|
|
259
289
|
*/
|
|
260
290
|
onCompleteAll() {
|
|
291
|
+
clearTimeout(errorTimeout);
|
|
261
292
|
// Tell React to start writing to the writer
|
|
262
|
-
|
|
293
|
+
pipe(writer);
|
|
263
294
|
// Tell React that the writer is ready to drain, which sometimes results in a last "chunk" being written.
|
|
264
295
|
writer.drain();
|
|
265
296
|
if (isReactHydrationRequest) {
|
|
@@ -270,7 +301,7 @@ function renderAppFromBufferedStream(app, isReactHydrationRequest) {
|
|
|
270
301
|
}
|
|
271
302
|
},
|
|
272
303
|
onError(error) {
|
|
273
|
-
|
|
304
|
+
log.error(error);
|
|
274
305
|
reject(error);
|
|
275
306
|
},
|
|
276
307
|
});
|
|
@@ -286,37 +317,28 @@ function renderAppFromBufferedStream(app, isReactHydrationRequest) {
|
|
|
286
317
|
* use ssr-prepass to fetch all the queries once, store
|
|
287
318
|
* the results in a context object, and re-render.
|
|
288
319
|
*/
|
|
289
|
-
async function renderAppFromStringWithPrepass(ReactApp, state, isReactHydrationRequest) {
|
|
290
|
-
const hydrationContext = {};
|
|
320
|
+
async function renderAppFromStringWithPrepass(ReactApp, state, log, isReactHydrationRequest) {
|
|
291
321
|
const app = isReactHydrationRequest ? (React.createElement(HydrationContext.Provider, { value: true },
|
|
292
|
-
React.createElement(ReactApp, {
|
|
322
|
+
React.createElement(ReactApp, { ...state }))) : (React.createElement(ReactApp, { ...state }));
|
|
293
323
|
await ssrPrepass(app);
|
|
294
|
-
/**
|
|
295
|
-
* Dehydrate all the queries made during the prepass above and store
|
|
296
|
-
* them in the context object to be used for the next render pass.
|
|
297
|
-
* This prevents rendering the Suspense fallback in `renderToString`.
|
|
298
|
-
*/
|
|
299
|
-
if (hydrationContext.queryClient) {
|
|
300
|
-
hydrationContext.dehydratedState = dehydrate(hydrationContext.queryClient);
|
|
301
|
-
}
|
|
302
324
|
const body = renderToString(app);
|
|
303
325
|
return isReactHydrationRequest
|
|
304
326
|
? generateWireSyntaxFromRenderedHtml(body)
|
|
305
327
|
: body;
|
|
306
328
|
}
|
|
307
329
|
export default renderHydrogen;
|
|
308
|
-
function startWritingHtmlToServerResponse(response,
|
|
330
|
+
function startWritingHtmlToServerResponse(response, pipe, error) {
|
|
309
331
|
if (!response.headersSent) {
|
|
310
332
|
response.setHeader('Content-type', 'text/html');
|
|
311
333
|
response.write('<!DOCTYPE html>');
|
|
312
334
|
}
|
|
313
|
-
|
|
335
|
+
pipe(response);
|
|
314
336
|
if (error) {
|
|
315
337
|
// This error was delayed until the headers were properly sent.
|
|
316
338
|
response.write(getErrorMarkup(error));
|
|
317
339
|
}
|
|
318
340
|
}
|
|
319
|
-
function writeHeadToServerResponse(response, { headers, status, customStatus }, error) {
|
|
341
|
+
function writeHeadToServerResponse(request, response, { headers, status, customStatus }, log, error) {
|
|
320
342
|
var _a, _b;
|
|
321
343
|
if (response.headersSent)
|
|
322
344
|
return;
|
|
@@ -330,6 +352,7 @@ function writeHeadToServerResponse(response, { headers, status, customStatus },
|
|
|
330
352
|
response.statusMessage = customStatus.text;
|
|
331
353
|
}
|
|
332
354
|
}
|
|
355
|
+
logServerResponse('str', log, request, response.statusCode);
|
|
333
356
|
}
|
|
334
357
|
function isRedirect(response) {
|
|
335
358
|
return response.statusCode >= 300 && response.statusCode < 400;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { QueryKey } from '../../types';
|
|
2
|
+
import type { RenderCacheProviderProps, RenderCacheResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the unique identifier for the current rendering request
|
|
5
|
+
*/
|
|
6
|
+
export declare function useRenderCache(): RenderCacheProviderProps;
|
|
7
|
+
/**
|
|
8
|
+
* Returns data stored in the render cache
|
|
9
|
+
* It will throw the promise if data is not ready
|
|
10
|
+
*/
|
|
11
|
+
export declare function useRenderCacheData<T>(key: QueryKey, fetcher: () => Promise<T>): RenderCacheResult<T>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { RenderCacheContext } from './RenderCacheContext';
|
|
3
|
+
import { hashKey } from '../../framework/cache';
|
|
4
|
+
/**
|
|
5
|
+
* Returns the unique identifier for the current rendering request
|
|
6
|
+
*/
|
|
7
|
+
export function useRenderCache() {
|
|
8
|
+
const context = useContext(RenderCacheContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error('No RenderCache Context found');
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns data stored in the render cache
|
|
16
|
+
* It will throw the promise if data is not ready
|
|
17
|
+
*/
|
|
18
|
+
export function useRenderCacheData(key, fetcher) {
|
|
19
|
+
const cacheKey = hashKey(key);
|
|
20
|
+
const { cache } = useRenderCache();
|
|
21
|
+
if (!cache[cacheKey]) {
|
|
22
|
+
let data;
|
|
23
|
+
let promise;
|
|
24
|
+
cache[cacheKey] = () => {
|
|
25
|
+
if (data !== undefined)
|
|
26
|
+
return data;
|
|
27
|
+
if (!promise) {
|
|
28
|
+
promise = fetcher().then((r) => (data = { data: r }), (e) => (data = { error: e }));
|
|
29
|
+
}
|
|
30
|
+
throw promise;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return cache[cacheKey]();
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RenderCacheProvider } from './RenderCacheProvider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RenderCacheProvider } from './RenderCacheProvider';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare type RenderCache = {
|
|
2
|
+
[key: string]: () => unknown;
|
|
3
|
+
};
|
|
4
|
+
export declare type RenderCacheProviderProps = {
|
|
5
|
+
/** A cache to hold all queries performed within a render request */
|
|
6
|
+
cache: RenderCache;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare type RenderCacheResult<T> = RenderCacheResultSuccess<T> | RenderCacheResultError;
|
|
10
|
+
declare type RenderCacheResultSuccess<T> = {
|
|
11
|
+
data: T;
|
|
12
|
+
error?: never;
|
|
13
|
+
};
|
|
14
|
+
declare type RenderCacheResultError = {
|
|
15
|
+
data?: never;
|
|
16
|
+
error: Response;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
+
import type { Logger } from '../../utilities/log/log';
|
|
2
3
|
export declare type ImportGlobEagerOutput = Record<string, Record<'default', any>>;
|
|
3
4
|
/**
|
|
4
5
|
* Build a set of default Hydrogen routes based on the output provided by Vite's
|
|
@@ -6,10 +7,11 @@ export declare type ImportGlobEagerOutput = Record<string, Record<'default', any
|
|
|
6
7
|
*
|
|
7
8
|
* @see https://vitejs.dev/guide/features.html#glob-import
|
|
8
9
|
*/
|
|
9
|
-
export declare function DefaultRoutes({ pages, serverState, fallback, }: {
|
|
10
|
+
export declare function DefaultRoutes({ pages, serverState, fallback, log, }: {
|
|
10
11
|
pages: ImportGlobEagerOutput;
|
|
11
12
|
serverState: Record<string, any>;
|
|
12
13
|
fallback?: ReactElement;
|
|
14
|
+
log: Logger;
|
|
13
15
|
}): JSX.Element;
|
|
14
16
|
interface HydrogenRoute {
|
|
15
17
|
component: any;
|
|
@@ -6,12 +6,12 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://vitejs.dev/guide/features.html#glob-import
|
|
8
8
|
*/
|
|
9
|
-
export function DefaultRoutes({ pages, serverState, fallback, }) {
|
|
9
|
+
export function DefaultRoutes({ pages, serverState, fallback, log, }) {
|
|
10
10
|
const { path } = useRouteMatch();
|
|
11
11
|
const routes = useMemo(() => createRoutesFromPages(pages, path), [pages, path]);
|
|
12
12
|
return (React.createElement(Switch, null,
|
|
13
13
|
routes.map((route) => (React.createElement(Route, { key: route.path, exact: route.exact, path: route.path },
|
|
14
|
-
React.createElement(route.component, { ...serverState })))),
|
|
14
|
+
React.createElement(route.component, { ...serverState, log: log })))),
|
|
15
15
|
fallback && React.createElement(Route, { path: "*" }, fallback)));
|
|
16
16
|
}
|
|
17
17
|
export function createRoutesFromPages(pages, topLevelPath = '*') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { createContext, useMemo, useCallback,
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
useTransition, } from 'react';
|
|
4
|
+
const PRIVATE_PROPS = ['request', 'response'];
|
|
4
5
|
export const ServerStateContext = createContext(null);
|
|
5
6
|
export function ServerStateProvider({ serverState, setServerState, children, }) {
|
|
6
7
|
const [pending, startTransition] = useTransition();
|
|
@@ -25,8 +26,9 @@ export function ServerStateProvider({ serverState, setServerState, children, })
|
|
|
25
26
|
newValue = input;
|
|
26
27
|
}
|
|
27
28
|
if (__DEV__) {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const privateProp = PRIVATE_PROPS.find((prop) => prop in newValue);
|
|
30
|
+
if (privateProp) {
|
|
31
|
+
console.warn(`Custom "${privateProp}" property in server state is ignored. Use a different name.`);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
return {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { ShopifyConfig } from '../../types';
|
|
3
|
-
|
|
4
|
-
export declare function ShopifyServerProvider({ children, shopifyConfig, hydrationContext, }: {
|
|
3
|
+
export declare function ShopifyServerProvider({ children, shopifyConfig, }: {
|
|
5
4
|
children: ReactElement;
|
|
6
5
|
shopifyConfig: ShopifyConfig;
|
|
7
|
-
hydrationContext: ReactQueryHydrationContext;
|
|
8
6
|
}): JSX.Element;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ShopifyProvider } from './ShopifyProvider';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return (React.createElement(ShopifyProvider, { shopifyConfig: shopifyConfig },
|
|
6
|
-
React.createElement(QueryProvider, { hydrationContext: hydrationContext }, children)));
|
|
3
|
+
export function ShopifyServerProvider({ children, shopifyConfig, }) {
|
|
4
|
+
return (React.createElement(ShopifyProvider, { shopifyConfig: shopifyConfig }, children));
|
|
7
5
|
}
|