@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.
Files changed (100) hide show
  1. package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.d.ts +1 -1
  2. package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.js +1 -1
  3. package/dist/esnext/components/CartLineQuantityAdjustButton/CartLineQuantityAdjustButton.js +4 -0
  4. package/dist/esnext/components/LocalizationProvider/index.d.ts +1 -0
  5. package/dist/esnext/components/LocalizationProvider/index.js +1 -0
  6. package/dist/esnext/components/ProductPrice/ProductPrice.client.d.ts +1 -3
  7. package/dist/esnext/components/ProductPrice/ProductPrice.client.js +1 -3
  8. package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +1 -1
  9. package/dist/esnext/components/UnitPrice/UnitPrice.client.d.ts +1 -2
  10. package/dist/esnext/components/UnitPrice/UnitPrice.client.js +1 -2
  11. package/dist/esnext/components/index.d.ts +1 -0
  12. package/dist/esnext/components/index.js +1 -0
  13. package/dist/esnext/entry-client.js +4 -6
  14. package/dist/esnext/entry-server.js +70 -47
  15. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.d.ts +2 -0
  16. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.js +4 -0
  17. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.d.ts +2 -0
  18. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.js +5 -0
  19. package/dist/esnext/foundation/RenderCacheProvider/hook.d.ts +11 -0
  20. package/dist/esnext/foundation/RenderCacheProvider/hook.js +34 -0
  21. package/dist/esnext/foundation/RenderCacheProvider/index.d.ts +1 -0
  22. package/dist/esnext/foundation/RenderCacheProvider/index.js +1 -0
  23. package/dist/esnext/foundation/RenderCacheProvider/types.d.ts +18 -0
  24. package/dist/esnext/foundation/RenderCacheProvider/types.js +1 -0
  25. package/dist/esnext/foundation/Router/DefaultRoutes.d.ts +3 -1
  26. package/dist/esnext/foundation/Router/DefaultRoutes.js +2 -2
  27. package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.js +4 -2
  28. package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.d.ts +1 -3
  29. package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.js +2 -4
  30. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +0 -5
  31. package/dist/esnext/foundation/useQuery/hooks.d.ts +8 -6
  32. package/dist/esnext/foundation/useQuery/hooks.js +13 -14
  33. package/dist/esnext/foundation/useQuery/index.d.ts +0 -1
  34. package/dist/esnext/foundation/useQuery/index.js +0 -1
  35. package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
  36. package/dist/esnext/foundation/useShop/use-shop.js +1 -1
  37. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  38. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +2 -0
  39. package/dist/esnext/framework/cache.d.ts +2 -2
  40. package/dist/esnext/framework/cache.js +1 -1
  41. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
  42. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
  43. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
  44. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +15 -1
  45. package/dist/esnext/framework/plugins/vite-plugin-react-server-components-shim.js +1 -13
  46. package/dist/esnext/handle-event.d.ts +1 -1
  47. package/dist/esnext/handle-event.js +68 -10
  48. package/dist/esnext/hooks/index.d.ts +1 -1
  49. package/dist/esnext/hooks/index.js +1 -1
  50. package/dist/esnext/hooks/useShopQuery/hooks.d.ts +4 -4
  51. package/dist/esnext/hooks/useShopQuery/hooks.js +47 -20
  52. package/dist/esnext/index.d.ts +2 -1
  53. package/dist/esnext/index.js +2 -1
  54. package/dist/esnext/types.d.ts +2 -1
  55. package/dist/esnext/utilities/fetch.js +3 -6
  56. package/dist/esnext/utilities/index.d.ts +1 -0
  57. package/dist/esnext/utilities/index.js +1 -0
  58. package/dist/esnext/utilities/log/index.d.ts +1 -0
  59. package/dist/esnext/utilities/log/index.js +1 -0
  60. package/dist/esnext/utilities/log/log.d.ts +17 -0
  61. package/dist/esnext/utilities/log/log.js +75 -0
  62. package/dist/esnext/utilities/suspense.d.ts +2 -2
  63. package/dist/esnext/utilities/suspense.js +0 -6
  64. package/dist/esnext/utilities/timing.d.ts +7 -0
  65. package/dist/esnext/utilities/timing.js +14 -0
  66. package/dist/esnext/version.d.ts +1 -1
  67. package/dist/esnext/version.js +1 -1
  68. package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  69. package/dist/node/framework/Hydration/ServerComponentRequest.server.js +2 -0
  70. package/dist/node/framework/cache.d.ts +2 -2
  71. package/dist/node/framework/cache.js +2 -1
  72. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
  73. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
  74. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
  75. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +15 -1
  76. package/dist/node/framework/plugins/vite-plugin-react-server-components-shim.js +1 -13
  77. package/dist/node/handle-event.d.ts +1 -1
  78. package/dist/node/handle-event.js +68 -10
  79. package/dist/node/types.d.ts +2 -1
  80. package/dist/node/utilities/fetch.js +3 -6
  81. package/dist/node/utilities/index.d.ts +1 -0
  82. package/dist/node/utilities/index.js +3 -1
  83. package/dist/node/utilities/suspense.d.ts +2 -2
  84. package/dist/node/utilities/suspense.js +0 -6
  85. package/dist/node/utilities/timing.d.ts +7 -0
  86. package/dist/node/utilities/timing.js +18 -0
  87. package/dist/node/version.d.ts +1 -1
  88. package/dist/node/version.js +1 -1
  89. package/dist/worker/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  90. package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +2 -0
  91. package/dist/worker/framework/cache.d.ts +2 -2
  92. package/dist/worker/framework/cache.js +1 -1
  93. package/dist/worker/handle-event.d.ts +1 -1
  94. package/dist/worker/handle-event.js +68 -10
  95. package/dist/worker/types.d.ts +2 -1
  96. package/dist/worker/utilities/timing.d.ts +7 -0
  97. package/dist/worker/utilities/timing.js +14 -0
  98. package/package.json +10 -9
  99. package/dist/esnext/foundation/useQuery/QueryProvider.d.ts +0 -6
  100. 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 [CartEstimatedCost](/api/storefront/reference/cart/cartestimatedcost) component renders a `Money` component with 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 [CartEstimatedCost](/api/storefront/reference/cart/cartestimatedcost) component renders a `Money` component with 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
  }
@@ -1,2 +1,3 @@
1
+ export { LocalizationProvider } from './LocalizationProvider.server';
1
2
  export { useCountry } from '../../hooks/useCountry/useCountry';
2
3
  export { useAvailableCountries } from '../../hooks/useAvailableCountries/useAvailableCountries';
@@ -1,2 +1,3 @@
1
+ export { LocalizationProvider } from './LocalizationProvider.server';
1
2
  export { useCountry } from '../../hooks/useCountry/useCountry';
2
3
  export { useAvailableCountries } from '../../hooks/useAvailableCountries/useAvailableCountries';
@@ -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 = atob(gid).split('/').pop();
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(QueryProvider, null,
29
- React.createElement(HelmetProvider, null,
30
- React.createElement(BrowserRouter, null,
31
- React.createElement(ServerStateRouter, null),
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 { renderToReadableStream, pipeToNodeWritable,
2
+ import {
3
3
  // @ts-ignore
4
- } from 'react-dom/unstable-fizz';
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
- console.error('Fatal', error);
83
+ log.fatal(error);
75
84
  });
76
85
  let didError;
77
86
  const head = template.match(/<head>(.+?)<\/head>/s)[1];
78
- const { startWriting, abort } = pipeToNodeWritable(React.createElement(Html, { head: head },
79
- React.createElement(ReactApp, { ...state })), response, {
80
- onReadyToStream() {
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, startWriting, dev ? didError : undefined);
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, startWriting, dev ? didError : undefined);
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
- console.error(error);
133
+ log.error(error);
124
134
  },
125
135
  });
126
- setTimeout(abort, STREAM_ABORT_TIMEOUT_MS);
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
- console.error('Fatal', error);
156
+ log.fatal(error);
142
157
  });
143
158
  let didError;
144
159
  const writer = new HydrationWriter();
145
- const { startWriting, abort } = pipeToNodeWritable(React.createElement(HydrationContext.Provider, { value: true },
146
- React.createElement(ReactApp, { ...state })), writer, {
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
- startWriting();
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
- console.error(error);
179
+ log.error(error);
163
180
  },
164
181
  });
165
- setTimeout(abort, STREAM_ABORT_TIMEOUT_MS);
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 ReactApp = (props) => (React.createElement(StaticRouter, { location: { pathname: state.pathname, search: state.search }, context: context },
177
- React.createElement(HelmetProvider, { context: helmetContext },
178
- React.createElement(App, { ...props, request: request, response: componentResponse }))));
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
- console.error(error);
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 { startWriting } = pipeToNodeWritable(app, writer, {
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
- startWriting();
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
- console.error(error);
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, { hydrationContext: hydrationContext, ...state }))) : (React.createElement(ReactApp, { hydrationContext: hydrationContext, ...state }));
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, startWriting, error) {
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
- startWriting();
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,2 @@
1
+ import type { RenderCacheProviderProps } from './types';
2
+ export declare const RenderCacheContext: import("react").Context<RenderCacheProviderProps>;
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+ export const RenderCacheContext = createContext({
3
+ cache: {},
4
+ });
@@ -0,0 +1,2 @@
1
+ import type { RenderCacheProviderProps } from './types';
2
+ export declare function RenderCacheProvider({ cache, children, }: RenderCacheProviderProps): JSX.Element;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { RenderCacheContext } from './RenderCacheContext';
3
+ export function RenderCacheProvider({ cache, children, }) {
4
+ return (React.createElement(RenderCacheContext.Provider, { value: { cache } }, children));
5
+ }
@@ -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 {};
@@ -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
- if ('request' in newValue || 'response' in newValue) {
29
- console.warn(`Custom "request" and "response" properties in server state are ignored. Use a different name.`);
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
- import { ReactQueryHydrationContext } from './types';
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
- import { QueryProvider } from '../../foundation/useQuery';
4
- export function ShopifyServerProvider({ children, shopifyConfig, hydrationContext, }) {
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
  }