@shopify/hydrogen 0.17.2 → 0.19.0

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 (161) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/config.js +1 -0
  3. package/dist/esnext/client.d.ts +2 -0
  4. package/dist/esnext/client.js +2 -0
  5. package/dist/esnext/components/AddToCartButton/AddToCartButton.client.js +2 -2
  6. package/dist/esnext/components/CartProvider/CartProvider.client.js +15 -14
  7. package/dist/esnext/components/CartProvider/{hooks.d.ts → hooks.client.d.ts} +0 -0
  8. package/dist/esnext/components/CartProvider/{hooks.js → hooks.client.js} +0 -0
  9. package/dist/esnext/components/CartProvider/index.d.ts +1 -1
  10. package/dist/esnext/components/CartProvider/index.js +1 -1
  11. package/dist/esnext/components/{DevTools.d.ts → DevTools.client.d.ts} +0 -0
  12. package/dist/esnext/components/{DevTools.js → DevTools.client.js} +3 -2
  13. package/dist/esnext/components/Image/Image.d.ts +6 -0
  14. package/dist/esnext/components/Image/Image.js +8 -3
  15. package/dist/esnext/components/Link/Link.client.js +11 -2
  16. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +2 -15
  17. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +0 -1
  18. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +2 -6
  19. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +11 -5
  20. package/dist/esnext/components/Metafield/Metafield.client.js +4 -5
  21. package/dist/esnext/components/ModelViewer/ModelViewer.client.js +1 -1
  22. package/dist/esnext/components/Money/Money.client.d.ts +5 -1
  23. package/dist/esnext/components/Money/Money.client.js +16 -3
  24. package/dist/esnext/components/ProductMetafield/ProductMetafield.client.js +1 -1
  25. package/dist/esnext/components/ProductProvider/ProductOptionsProvider.client.js +1 -1
  26. package/dist/esnext/components/ProductProvider/ProductProvider.client.d.ts +7 -3
  27. package/dist/esnext/components/ProductProvider/ProductProvider.client.js +1 -1
  28. package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +6 -2
  29. package/dist/esnext/components/Video/Video.js +3 -1
  30. package/dist/esnext/config.d.ts +3 -0
  31. package/dist/esnext/config.js +1 -0
  32. package/dist/esnext/constants.js +1 -1
  33. package/dist/esnext/entry-client.js +3 -1
  34. package/dist/esnext/entry-server.d.ts +2 -2
  35. package/dist/esnext/entry-server.js +56 -82
  36. package/dist/esnext/foundation/Analytics/ClientAnalytics.d.ts +1 -0
  37. package/dist/esnext/foundation/Analytics/ClientAnalytics.js +7 -1
  38. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.client.d.ts +7 -0
  39. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.client.js +64 -0
  40. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.server.d.ts +1 -0
  41. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.server.js +24 -0
  42. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetricsDebug.client.d.ts +1 -0
  43. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetricsDebug.client.js +23 -0
  44. package/dist/esnext/foundation/Analytics/const.d.ts +1 -0
  45. package/dist/esnext/foundation/Analytics/const.js +1 -0
  46. package/dist/esnext/foundation/Cookie/Cookie.js +2 -1
  47. package/dist/esnext/foundation/FileRoutes/FileRoutes.server.d.ts +4 -4
  48. package/dist/esnext/foundation/FileRoutes/FileRoutes.server.js +17 -4
  49. package/dist/esnext/foundation/FileSessionStorage/FileSessionStorage.js +2 -1
  50. package/dist/esnext/foundation/Redirect/Redirect.client.js +1 -0
  51. package/dist/esnext/foundation/Route/Route.server.js +1 -10
  52. package/dist/esnext/foundation/Router/BrowserRouter.client.js +10 -1
  53. package/dist/esnext/foundation/ServerPropsProvider/ServerPropsProvider.js +5 -3
  54. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +2 -2
  55. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +7 -2
  56. package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.js +8 -1
  57. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.d.ts +8 -1
  58. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +31 -5
  59. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +3 -4
  60. package/dist/esnext/foundation/fetchSync/client/fetchSync.js +2 -1
  61. package/dist/esnext/foundation/fetchSync/server/fetchSync.js +4 -2
  62. package/dist/esnext/foundation/ssr-interop.js +1 -1
  63. package/dist/esnext/foundation/useQuery/hooks.d.ts +1 -1
  64. package/dist/esnext/foundation/useQuery/hooks.js +2 -2
  65. package/dist/esnext/foundation/useSession/useSession.d.ts +1 -1
  66. package/dist/esnext/foundation/useSession/useSession.js +1 -1
  67. package/dist/esnext/foundation/useShop/use-shop.d.ts +3 -1
  68. package/dist/esnext/foundation/useShop/use-shop.js +3 -1
  69. package/dist/esnext/foundation/useUrl/useUrl.js +7 -4
  70. package/dist/esnext/framework/Hydration/Html.js +1 -1
  71. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +3 -2
  72. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +16 -7
  73. package/dist/esnext/framework/Hydration/rsc.d.ts +0 -3
  74. package/dist/esnext/framework/Hydration/rsc.js +0 -20
  75. package/dist/esnext/framework/middleware.d.ts +3 -4
  76. package/dist/esnext/framework/middleware.js +4 -4
  77. package/dist/esnext/framework/plugin.d.ts +2 -2
  78. package/dist/esnext/framework/plugin.js +3 -3
  79. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +1 -1
  80. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +2 -2
  81. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +59 -4
  82. package/dist/esnext/hooks/useCountry/useCountry.d.ts +1 -11
  83. package/dist/esnext/hooks/useCountry/useCountry.js +1 -1
  84. package/dist/esnext/hooks/useLoadScript/index.d.ts +1 -1
  85. package/dist/esnext/hooks/useLoadScript/index.js +1 -1
  86. package/dist/esnext/hooks/useLoadScript/{useLoadScript.d.ts → useLoadScript.client.d.ts} +0 -0
  87. package/dist/esnext/hooks/useLoadScript/{useLoadScript.js → useLoadScript.client.js} +2 -1
  88. package/dist/esnext/hooks/useMoney/hooks.d.ts +13 -1
  89. package/dist/esnext/hooks/useMoney/hooks.js +25 -1
  90. package/dist/esnext/hooks/useParsedMetafields/useParsedMetafields.js +0 -2
  91. package/dist/esnext/hooks/useProduct/useProduct.js +1 -1
  92. package/dist/esnext/hooks/useProductOptions/index.d.ts +1 -1
  93. package/dist/esnext/hooks/useProductOptions/index.js +1 -1
  94. package/dist/esnext/hooks/useProductOptions/{useProductOptions.d.ts → useProductOptions.client.d.ts} +0 -0
  95. package/dist/esnext/hooks/useProductOptions/{useProductOptions.js → useProductOptions.client.js} +6 -23
  96. package/dist/esnext/hooks/useShopQuery/hooks.js +15 -4
  97. package/dist/esnext/index.d.ts +1 -0
  98. package/dist/esnext/index.js +1 -0
  99. package/dist/esnext/node.d.ts +1 -0
  100. package/dist/esnext/node.js +1 -0
  101. package/dist/esnext/storefront-api-types.d.ts +60 -6
  102. package/dist/esnext/storefront-api-types.js +6 -2
  103. package/dist/esnext/types.d.ts +11 -4
  104. package/dist/esnext/utilities/apiRoutes.d.ts +4 -4
  105. package/dist/esnext/utilities/apiRoutes.js +29 -16
  106. package/dist/esnext/utilities/bot-ua.js +4 -0
  107. package/dist/esnext/utilities/empty-hydrogen-config.d.ts +2 -0
  108. package/dist/esnext/utilities/empty-hydrogen-config.js +2 -0
  109. package/dist/esnext/utilities/findRoutePrefix.d.ts +1 -0
  110. package/dist/esnext/utilities/findRoutePrefix.js +17 -0
  111. package/dist/esnext/utilities/log/utils.js +1 -1
  112. package/dist/esnext/utilities/parse.d.ts +1 -0
  113. package/dist/esnext/utilities/parse.js +9 -0
  114. package/dist/esnext/utilities/parseMetafieldValue/parseMetafieldValue.js +2 -1
  115. package/dist/esnext/utilities/storefrontApi.js +1 -0
  116. package/dist/esnext/version.d.ts +1 -1
  117. package/dist/esnext/version.js +1 -1
  118. package/dist/node/constants.js +1 -1
  119. package/dist/node/entry-server.d.ts +2 -2
  120. package/dist/node/entry-server.js +56 -82
  121. package/dist/node/foundation/Analytics/ClientAnalytics.d.ts +1 -0
  122. package/dist/node/foundation/Analytics/ClientAnalytics.js +7 -1
  123. package/dist/node/foundation/Analytics/const.d.ts +1 -0
  124. package/dist/node/foundation/Analytics/const.js +1 -0
  125. package/dist/node/foundation/Redirect/Redirect.client.js +1 -0
  126. package/dist/node/foundation/Router/BrowserRouter.client.js +10 -1
  127. package/dist/node/foundation/ServerPropsProvider/ServerPropsProvider.js +5 -3
  128. package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +2 -2
  129. package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +7 -2
  130. package/dist/node/foundation/ShopifyProvider/types.d.ts +3 -4
  131. package/dist/node/foundation/ssr-interop.js +1 -1
  132. package/dist/node/framework/Hydration/Html.js +1 -1
  133. package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +3 -2
  134. package/dist/node/framework/Hydration/ServerComponentRequest.server.js +16 -7
  135. package/dist/node/framework/Hydration/rsc.d.ts +0 -3
  136. package/dist/node/framework/Hydration/rsc.js +0 -20
  137. package/dist/node/framework/middleware.d.ts +3 -4
  138. package/dist/node/framework/middleware.js +4 -4
  139. package/dist/node/framework/plugin.d.ts +2 -2
  140. package/dist/node/framework/plugin.js +3 -3
  141. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +1 -1
  142. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +2 -2
  143. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +58 -3
  144. package/dist/node/storefront-api-types.d.ts +60 -6
  145. package/dist/node/storefront-api-types.js +6 -2
  146. package/dist/node/types.d.ts +11 -4
  147. package/dist/node/utilities/apiRoutes.d.ts +4 -4
  148. package/dist/node/utilities/apiRoutes.js +29 -16
  149. package/dist/node/utilities/bot-ua.js +4 -0
  150. package/dist/node/utilities/findRoutePrefix.d.ts +1 -0
  151. package/dist/node/utilities/findRoutePrefix.js +21 -0
  152. package/dist/node/utilities/log/utils.js +1 -1
  153. package/dist/node/utilities/parse.d.ts +1 -0
  154. package/dist/node/utilities/parse.js +13 -0
  155. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.js +2 -1
  156. package/dist/node/utilities/storefrontApi.js +1 -0
  157. package/dist/node/version.d.ts +1 -1
  158. package/dist/node/version.js +1 -1
  159. package/package.json +8 -6
  160. package/dist/esnext/foundation/Boomerang/Boomerang.client.d.ts +0 -9
  161. package/dist/esnext/foundation/Boomerang/Boomerang.client.js +0 -66
@@ -1,22 +1,22 @@
1
1
  import type { ImportGlobEagerOutput } from '../../types';
2
2
  interface FileRoutesProps {
3
3
  /** The routes defined by Vite's [import.meta.globEager](https://vitejs.dev/guide/features.html#glob-import) method. */
4
- routes: ImportGlobEagerOutput;
4
+ routes?: ImportGlobEagerOutput;
5
5
  /** A path that's prepended to all file routes. You can modify `basePath` if you want to prefix all file routes. For example, you can prefix all file routes with a locale. */
6
6
  basePath?: string;
7
7
  /** The portion of the file route path that shouldn't be a part of the URL. You need to modify this if you want to import routes from a location other than the default `src/routes`. */
8
- dirPrefix?: string;
8
+ dirPrefix?: string | RegExp;
9
9
  }
10
10
  /**
11
11
  * The `FileRoutes` component builds a set of default Hydrogen routes based on the output provided by Vite's
12
12
  * [import.meta.globEager](https://vitejs.dev/guide/features.html#glob-import) method. You can have multiple
13
13
  * instances of this component to source file routes from multiple locations.
14
14
  */
15
- export declare function FileRoutes({ routes, basePath, dirPrefix, }: FileRoutesProps): JSX.Element | null;
15
+ export declare function FileRoutes({ routes, basePath, dirPrefix }: FileRoutesProps): JSX.Element | null;
16
16
  interface HydrogenRoute {
17
17
  component: any;
18
18
  path: string;
19
19
  exact: boolean;
20
20
  }
21
- export declare function createPageRoutes(pages: ImportGlobEagerOutput, topLevelPath: string | undefined, dirPrefix: string): HydrogenRoute[];
21
+ export declare function createPageRoutes(pages: ImportGlobEagerOutput, topLevelPath?: string, dirPrefix?: string | RegExp): HydrogenRoute[];
22
22
  export {};
@@ -4,17 +4,28 @@ import { log } from '../../utilities/log';
4
4
  import { extractPathFromRoutesKey } from '../../utilities/apiRoutes';
5
5
  import { useServerRequest } from '../ServerRequestProvider';
6
6
  import { RouteParamsProvider } from '../useRouteParams/RouteParamsProvider.client';
7
+ import { findRoutePrefix } from '../../utilities/findRoutePrefix';
7
8
  /**
8
9
  * The `FileRoutes` component builds a set of default Hydrogen routes based on the output provided by Vite's
9
10
  * [import.meta.globEager](https://vitejs.dev/guide/features.html#glob-import) method. You can have multiple
10
11
  * instances of this component to source file routes from multiple locations.
11
12
  */
12
- export function FileRoutes({ routes, basePath = '/', dirPrefix = './routes', }) {
13
+ export function FileRoutes({ routes, basePath, dirPrefix }) {
14
+ var _a;
13
15
  const request = useServerRequest();
14
16
  const { routeRendered, serverProps } = request.ctx.router;
15
17
  if (routeRendered)
16
18
  return null;
17
- const pageRoutes = useMemo(() => createPageRoutes(routes, basePath, dirPrefix), [routes, basePath]);
19
+ if (!routes) {
20
+ const fileRoutes = request.ctx.hydrogenConfig.routes;
21
+ routes = (_a = fileRoutes === null || fileRoutes === void 0 ? void 0 : fileRoutes.files) !== null && _a !== void 0 ? _a : fileRoutes;
22
+ dirPrefix !== null && dirPrefix !== void 0 ? dirPrefix : (dirPrefix = fileRoutes === null || fileRoutes === void 0 ? void 0 : fileRoutes.dirPrefix);
23
+ basePath !== null && basePath !== void 0 ? basePath : (basePath = fileRoutes === null || fileRoutes === void 0 ? void 0 : fileRoutes.basePath);
24
+ }
25
+ basePath !== null && basePath !== void 0 ? basePath : (basePath = '/');
26
+ /* eslint-disable react-hooks/rules-of-hooks */
27
+ const pageRoutes = useMemo(() => createPageRoutes(routes, basePath, dirPrefix), [routes, basePath, dirPrefix]);
28
+ /* eslint-enable react-hooks/rules-of-hooks */
18
29
  let foundRoute, foundRouteDetails;
19
30
  for (let i = 0; i < pageRoutes.length; i++) {
20
31
  foundRouteDetails = matchPath(serverProps.pathname, pageRoutes[i]);
@@ -33,9 +44,11 @@ export function FileRoutes({ routes, basePath = '/', dirPrefix = './routes', })
33
44
  }
34
45
  export function createPageRoutes(pages, topLevelPath = '*', dirPrefix) {
35
46
  const topLevelPrefix = topLevelPath.replace('*', '').replace(/\/$/, '');
36
- const routes = Object.keys(pages)
47
+ const keys = Object.keys(pages);
48
+ const commonRoutePrefix = dirPrefix !== null && dirPrefix !== void 0 ? dirPrefix : findRoutePrefix(keys);
49
+ const routes = keys
37
50
  .map((key) => {
38
- const path = extractPathFromRoutesKey(key, dirPrefix);
51
+ const path = extractPathFromRoutesKey(key, commonRoutePrefix);
39
52
  /**
40
53
  * Catch-all routes [...handle].jsx don't need an exact match
41
54
  * https://reactrouter.com/core/api/Route/exact-bool
@@ -2,6 +2,7 @@ import { Cookie } from '../Cookie/Cookie';
2
2
  import { v4 as uid } from 'uuid';
3
3
  import path from 'path';
4
4
  import { promises as fsp } from 'fs';
5
+ import { parseJSON } from '../../utilities/parse';
5
6
  async function wait() {
6
7
  return new Promise((resolve) => setTimeout(resolve));
7
8
  }
@@ -77,7 +78,7 @@ async function getFile(file, expires, log) {
77
78
  try {
78
79
  const textContent = await fsp.readFile(file, { encoding: 'utf-8' });
79
80
  try {
80
- content = JSON.parse(textContent);
81
+ content = parseJSON(textContent);
81
82
  }
82
83
  catch (error) {
83
84
  log.warn(`Cannot parse existing session file: ${file}`);
@@ -9,6 +9,7 @@ export default function Redirect({ to }) {
9
9
  else {
10
10
  navigate(to);
11
11
  }
12
+ // eslint-disable-next-line react-hooks/exhaustive-deps
12
13
  }, []);
13
14
  return null;
14
15
  }
@@ -1,15 +1,12 @@
1
1
  import React, { cloneElement } from 'react';
2
2
  import { useServerRequest } from '../ServerRequestProvider';
3
3
  import { matchPath } from '../../utilities/matchPath';
4
- import { Boomerang } from '../Boomerang/Boomerang.client';
5
4
  import { RouteParamsProvider } from '../useRouteParams/RouteParamsProvider.client';
6
- import { useServerAnalytics } from '../Analytics';
7
5
  /**
8
6
  * The `Route` component is used to set up a route in Hydrogen that's independent of the file system. Routes are
9
7
  * matched in the order that they're defined.
10
8
  */
11
9
  export function Route({ path, page }) {
12
- var _a;
13
10
  const request = useServerRequest();
14
11
  const { routeRendered, serverProps } = request.ctx.router;
15
12
  if (routeRendered)
@@ -25,13 +22,7 @@ export function Route({ path, page }) {
25
22
  if (match) {
26
23
  request.ctx.router.routeRendered = true;
27
24
  request.ctx.router.routeParams = match.params;
28
- const name = (_a = page === null || page === void 0 ? void 0 : page.type) === null || _a === void 0 ? void 0 : _a.name;
29
- useServerAnalytics({
30
- templateName: name,
31
- });
32
- return (React.createElement(RouteParamsProvider, { routeParams: match.params },
33
- cloneElement(page, { params: match.params || {}, ...serverProps }),
34
- name ? React.createElement(Boomerang, { pageTemplate: name }) : null));
25
+ return (React.createElement(RouteParamsProvider, { routeParams: match.params }, cloneElement(page, { params: match.params || {}, ...serverProps })));
35
26
  }
36
27
  return null;
37
28
  }
@@ -8,6 +8,7 @@ const positions = {};
8
8
  export const BrowserRouter = ({ history: pHistory, children, }) => {
9
9
  if (META_ENV_SSR)
10
10
  return React.createElement(React.Fragment, null, children);
11
+ /* eslint-disable react-hooks/rules-of-hooks */
11
12
  const history = useMemo(() => pHistory || createBrowserHistory(), [pHistory]);
12
13
  const [location, setLocation] = useState(history.location);
13
14
  const [locationChanged, setLocationChanged] = useState(false);
@@ -30,7 +31,14 @@ export const BrowserRouter = ({ history: pHistory, children, }) => {
30
31
  setLocationChanged(true);
31
32
  });
32
33
  return () => unlisten();
33
- }, [history, location, setLocationChanged]);
34
+ }, [
35
+ history,
36
+ location,
37
+ setLocationChanged,
38
+ setLocation,
39
+ setLocationServerProps,
40
+ ]);
41
+ /* eslint-enable react-hooks/rules-of-hooks */
34
42
  return (React.createElement(RouterContext.Provider, { value: {
35
43
  history,
36
44
  location,
@@ -112,6 +120,7 @@ function useScrollRestoration({ location, pending, serverProps, locationChanged,
112
120
  location.pathname,
113
121
  location.search,
114
122
  location.hash,
123
+ location.key,
115
124
  pending,
116
125
  serverProps.pathname,
117
126
  serverProps.search,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable hydrogen/no-state-in-server-components */
1
2
  import React, { createContext, useMemo, useCallback,
2
3
  // @ts-ignore
3
4
  useTransition, useState, } from 'react';
@@ -12,7 +13,7 @@ export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc,
12
13
  setServerProps((prev) => getNewValue(prev, input, propValue));
13
14
  setServerPropsForRsc((prev) => getNewValue(prev, input, propValue));
14
15
  });
15
- }, []);
16
+ }, [setServerProps, setServerPropsForRsc]);
16
17
  const setLocationServerPropsCallback = useCallback((input, propValue) => {
17
18
  // Flush the existing user server state when location changes, leaving only the persisted state
18
19
  startTransition(() => {
@@ -20,7 +21,7 @@ export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc,
20
21
  setServerProps({});
21
22
  setLocationServerProps((prev) => getNewValue(prev, input, propValue));
22
23
  });
23
- }, []);
24
+ }, [setServerProps, setServerPropsForRsc, setLocationServerProps]);
24
25
  const getProposedLocationServerPropsCallback = useCallback((input, propValue) => {
25
26
  return getNewValue(locationServerProps, input, propValue);
26
27
  }, [locationServerProps]);
@@ -48,7 +49,7 @@ export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc,
48
49
  }
49
50
  const value = useMemo(() => ({
50
51
  pending,
51
- locationServerProps: locationServerProps,
52
+ locationServerProps,
52
53
  serverProps,
53
54
  setServerProps: setServerPropsCallback,
54
55
  setLocationServerProps: setLocationServerPropsCallback,
@@ -63,3 +64,4 @@ export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc,
63
64
  ]);
64
65
  return (React.createElement(ServerPropsContext.Provider, { value: value }, children));
65
66
  }
67
+ /* eslint-enable hydrogen/no-state-in-server-components */
@@ -12,12 +12,12 @@ declare type RequestCacheResult<T> = {
12
12
  error?: never;
13
13
  } | {
14
14
  data?: never;
15
- error: Response;
15
+ error: Response | Error;
16
16
  };
17
17
  /**
18
18
  * Returns data stored in the request cache.
19
19
  * It will throw the promise if data is not ready.
20
20
  */
21
- export declare function useRequestCacheData<T>(key: QueryKey, fetcher: () => Promise<T>): RequestCacheResult<T>;
21
+ export declare function useRequestCacheData<T>(key: QueryKey, fetcher: () => T | Promise<T>): RequestCacheResult<T>;
22
22
  export declare function preloadRequestCacheData(request: ServerComponentRequest, preloadQueries?: PreloadQueriesByURL): void;
23
23
  export {};
@@ -33,7 +33,7 @@ export function useServerRequest() {
33
33
  catch (_a) {
34
34
  // If RSC cache failed it means this is not an RSC request.
35
35
  // Try getting SSR context instead:
36
- request = useContext(RequestContextSSR);
36
+ request = useContext(RequestContextSSR); // eslint-disable-line react-hooks/rules-of-hooks
37
37
  }
38
38
  if (!request) {
39
39
  // @ts-ignore
@@ -64,7 +64,12 @@ export function useRequestCacheData(key, fetcher) {
64
64
  }
65
65
  if (!promise) {
66
66
  const startApiTime = getTime();
67
- promise = fetcher().then((data) => {
67
+ const maybePromise = fetcher();
68
+ if (!(maybePromise instanceof Promise)) {
69
+ result = { data: maybePromise };
70
+ return result;
71
+ }
72
+ promise = maybePromise.then((data) => {
68
73
  result = { data };
69
74
  collectQueryTimings(request, key, 'resolved', getTime() - startApiTime);
70
75
  }, (error) => (result = { error }));
@@ -31,6 +31,8 @@ export function ServerStateProvider({ serverState, setServerState, children, })
31
31
  else {
32
32
  newValue = input;
33
33
  }
34
+ if (!newValue)
35
+ return { ...prev };
34
36
  if (__DEV__) {
35
37
  const privateProp = PRIVATE_PROPS.find((prop) => prop in newValue);
36
38
  if (privateProp) {
@@ -47,6 +49,11 @@ export function ServerStateProvider({ serverState, setServerState, children, })
47
49
  serverState,
48
50
  setServerState: setServerStateCallback,
49
51
  getProposedServerState: getProposedServerStateCallback,
50
- }), [serverState, setServerStateCallback, pending]);
52
+ }), [
53
+ serverState,
54
+ getProposedServerStateCallback,
55
+ setServerStateCallback,
56
+ pending,
57
+ ]);
51
58
  return (React.createElement(ServerStateContext.Provider, { value: value }, children));
52
59
  }
@@ -6,4 +6,11 @@ export declare const SHOPIFY_PROVIDER_CONTEXT_KEY: unique symbol;
6
6
  * If you're using the Hydrogen framework, you don't need to add this provider
7
7
  * because it's automatically wrapped around your app in `renderHydrogen()`.
8
8
  */
9
- export declare function ShopifyProvider({ shopifyConfig, children, }: ShopifyProviderProps): JSX.Element;
9
+ export declare function ShopifyProvider({
10
+ /**
11
+ * Shopify connection information. Defaults to
12
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
13
+ */
14
+ shopifyConfig,
15
+ /** Any `ReactNode` elements. */
16
+ children, }: ShopifyProviderProps): JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { ShopifyProviderClient } from './ShopifyProvider.client';
3
3
  import { DEFAULT_LOCALE } from '../constants';
4
- import { useServerRequest } from '../ServerRequestProvider';
4
+ import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
5
5
  function makeShopifyContext(shopifyConfig) {
6
6
  var _a, _b;
7
7
  const locale = (_a = shopifyConfig.defaultLocale) !== null && _a !== void 0 ? _a : DEFAULT_LOCALE;
@@ -21,12 +21,38 @@ export const SHOPIFY_PROVIDER_CONTEXT_KEY = Symbol.for('SHOPIFY_PROVIDER_RSC');
21
21
  * If you're using the Hydrogen framework, you don't need to add this provider
22
22
  * because it's automatically wrapped around your app in `renderHydrogen()`.
23
23
  */
24
- export function ShopifyProvider({ shopifyConfig, children, }) {
24
+ export function ShopifyProvider({
25
+ /**
26
+ * Shopify connection information. Defaults to
27
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
28
+ */
29
+ shopifyConfig,
30
+ /** Any `ReactNode` elements. */
31
+ children, }) {
32
+ var _a;
33
+ const request = useServerRequest();
25
34
  if (!shopifyConfig) {
26
- throw new Error('The `shopifyConfig` prop should be passed to `ShopifyProvider`');
35
+ shopifyConfig = (_a = request.ctx.hydrogenConfig) === null || _a === void 0 ? void 0 : _a.shopify;
36
+ if (!shopifyConfig) {
37
+ throw new Error('The `shopifyConfig` prop should be passed to `ShopifyProvider`');
38
+ }
27
39
  }
28
- const shopifyProviderValue = useMemo(() => makeShopifyContext(shopifyConfig), [shopifyConfig]);
29
- const request = useServerRequest();
40
+ let actualShopifyConfig;
41
+ if (typeof shopifyConfig === 'function') {
42
+ // eslint-disable-next-line react-hooks/rules-of-hooks
43
+ const result = useRequestCacheData(['hydrogen-shopify-config'], () => shopifyConfig(request));
44
+ if (result.error) {
45
+ if (result.error instanceof Error) {
46
+ throw result.error;
47
+ }
48
+ throw new Error(`Failed to load Shopify config: ${result.error.statusText}`);
49
+ }
50
+ actualShopifyConfig = result.data;
51
+ }
52
+ else {
53
+ actualShopifyConfig = shopifyConfig;
54
+ }
55
+ const shopifyProviderValue = useMemo(() => makeShopifyContext(actualShopifyConfig), [actualShopifyConfig]);
30
56
  request.ctx.shopifyConfig = shopifyProviderValue;
31
57
  return (React.createElement(ShopifyProviderClient, { shopifyConfig: shopifyProviderValue }, children));
32
58
  }
@@ -1,14 +1,13 @@
1
1
  import type { CountryCode, LanguageCode } from '../../storefront-api-types';
2
2
  import type { ReactNode } from 'react';
3
- import type { ShopifyConfig } from '../../types';
3
+ import type { ShopifyConfigFetcher, ShopifyConfig } from '../../types';
4
4
  export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLocale'> {
5
5
  locale: `${LanguageCode}-${CountryCode}`;
6
6
  languageCode: `${LanguageCode}`;
7
7
  }
8
8
  export declare type ShopifyProviderProps = {
9
- /** The contents of the `shopify.config.js` file. */
10
- shopifyConfig: ShopifyConfig;
9
+ /** Shopify connection information. Defaults to the `shopify` property in the `hydrogen.config.js` file. */
10
+ shopifyConfig?: ShopifyConfig | ShopifyConfigFetcher;
11
11
  /** Any `ReactNode` elements. */
12
12
  children?: ReactNode;
13
- manager?: any;
14
13
  };
@@ -1,3 +1,4 @@
1
+ import { parseJSON } from '../../../utilities/parse';
1
2
  import { suspendFunction, preloadFunction } from '../../../utilities/suspense';
2
3
  /**
3
4
  * Fetch a URL for use in a client component Suspense boundary.
@@ -10,7 +11,7 @@ export function fetchSync(url, options) {
10
11
  });
11
12
  return {
12
13
  response,
13
- json: () => JSON.parse(text),
14
+ json: () => parseJSON(text),
14
15
  text: () => text,
15
16
  };
16
17
  }
@@ -1,3 +1,4 @@
1
+ import { parseJSON } from '../../../utilities/parse';
1
2
  import { useQuery } from '../../useQuery/hooks';
2
3
  /**
3
4
  * The `fetchSync` hook makes third-party API requests and is the recommended way to make simple fetch calls on the server.
@@ -6,7 +7,8 @@ import { useQuery } from '../../useQuery/hooks';
6
7
  */
7
8
  export function fetchSync(url, options) {
8
9
  const { cache, preload, shouldCacheResponse, ...requestInit } = options !== null && options !== void 0 ? options : {};
9
- const { data: useQueryResponse, error } = useQuery([url, requestInit], async () => {
10
+ const { data: useQueryResponse, error } = useQuery(// eslint-disable-line react-hooks/rules-of-hooks
11
+ [url, requestInit], async () => {
10
12
  const response = await globalThis.fetch(url, requestInit);
11
13
  const text = await response.text();
12
14
  return [text, response];
@@ -21,7 +23,7 @@ export function fetchSync(url, options) {
21
23
  const [data, response] = useQueryResponse;
22
24
  return {
23
25
  response,
24
- json: () => JSON.parse(data),
26
+ json: () => parseJSON(data),
25
27
  text: () => data,
26
28
  };
27
29
  }
@@ -30,6 +30,6 @@ const reactContextType = Symbol.for('react.context');
30
30
  export function useEnvContext(serverGetter, clientFallback) {
31
31
  //@SSR if (META_ENV_SSR) return serverGetter(useServerRequest());
32
32
  return clientFallback && clientFallback.$$typeof === reactContextType
33
- ? useContext(clientFallback)
33
+ ? useContext(clientFallback) // eslint-disable-line react-hooks/rules-of-hooks
34
34
  : clientFallback;
35
35
  }
@@ -29,7 +29,7 @@ queryFn: () => Promise<T>,
29
29
  /** The options to manage the cache behavior of the sub-request. */
30
30
  queryOptions?: HydrogenUseQueryOptions): {
31
31
  data?: undefined;
32
- error: Response;
32
+ error: Error | Response;
33
33
  } | {
34
34
  data: T;
35
35
  error?: undefined;
@@ -43,7 +43,7 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
43
43
  /**
44
44
  * Attempt to read the query from cache. If it doesn't exist or if it's stale, regenerate it.
45
45
  */
46
- async function cachedQueryFn() {
46
+ async function useCachedQueryFn() {
47
47
  // Call this hook before running any async stuff
48
48
  // to prevent losing the current React cycle.
49
49
  const request = useServerRequest();
@@ -94,5 +94,5 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
94
94
  collectQueryCacheControlHeaders(request, key, generateSubRequestCacheControlHeader(resolvedQueryOptions === null || resolvedQueryOptions === void 0 ? void 0 : resolvedQueryOptions.cache));
95
95
  return newOutput;
96
96
  }
97
- return cachedQueryFn;
97
+ return useCachedQueryFn;
98
98
  }
@@ -1,2 +1,2 @@
1
1
  /** The `useSession` hook reads session data in server components. */
2
- export declare const useSession: () => Record<string, string> | undefined;
2
+ export declare const useSession: () => Record<string, string>;
@@ -3,6 +3,6 @@ import { useServerRequest } from '../ServerRequestProvider';
3
3
  export const useSession = function () {
4
4
  var _a;
5
5
  const request = useServerRequest();
6
- const session = (_a = request.ctx.session) === null || _a === void 0 ? void 0 : _a.get();
6
+ const session = ((_a = request.ctx.session) === null || _a === void 0 ? void 0 : _a.get()) || {};
7
7
  return session;
8
8
  };
@@ -1,4 +1,6 @@
1
1
  /**
2
- * The `useShop` hook provides access to values within `shopify.config.js`. It must be a descendent of a `ShopifyProvider` component.
2
+ * The `useShop` hook provides access to values within
3
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
4
+ * The `useShop` hook must be a descendent of a `ShopifyProvider` component.
3
5
  */
4
6
  export declare function useShop(): import("../ShopifyProvider/types").ShopifyContextValue;
@@ -1,7 +1,9 @@
1
1
  import { ShopifyContext } from '../ShopifyProvider';
2
2
  import { useEnvContext } from '../ssr-interop';
3
3
  /**
4
- * The `useShop` hook provides access to values within `shopify.config.js`. It must be a descendent of a `ShopifyProvider` component.
4
+ * The `useShop` hook provides access to values within
5
+ * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
6
+ * The `useShop` hook must be a descendent of a `ShopifyProvider` component.
5
7
  */
6
8
  export function useShop() {
7
9
  const config = useEnvContext((req) => req.ctx.shopifyConfig, ShopifyContext);
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  import { RSC_PATHNAME } from '../../constants';
3
+ import { parseJSON } from '../../utilities/parse';
3
4
  import { useLocation } from '../Router/BrowserRouter.client';
4
5
  import { useEnvContext, META_ENV_SSR } from '../ssr-interop';
5
6
  /**
@@ -8,9 +9,10 @@ import { useEnvContext, META_ENV_SSR } from '../ssr-interop';
8
9
  export function useUrl() {
9
10
  var _a, _b;
10
11
  if (META_ENV_SSR) {
11
- const serverUrl = new URL(useEnvContext((req) => req.url));
12
+ const serverUrl = new URL(useEnvContext((req) => req.url) // eslint-disable-line react-hooks/rules-of-hooks
13
+ );
12
14
  if (serverUrl.pathname === RSC_PATHNAME) {
13
- const state = JSON.parse(serverUrl.searchParams.get('state') || '{}');
15
+ const state = parseJSON(serverUrl.searchParams.get('state') || '{}');
14
16
  const parsedUrl = `${serverUrl.origin}${(_a = state.pathname) !== null && _a !== void 0 ? _a : ''}${(_b = state.search) !== null && _b !== void 0 ? _b : ''}`;
15
17
  return new URL(parsedUrl);
16
18
  }
@@ -20,6 +22,7 @@ export function useUrl() {
20
22
  * We return a `URL` object instead of passing through `location` because
21
23
  * the URL object contains important info like hostname, etc.
22
24
  */
23
- const location = useLocation();
24
- return useMemo(() => new URL(window.location.href), [location]);
25
+ const location = useLocation(); // eslint-disable-line react-hooks/rules-of-hooks
26
+ // eslint-disable-next-line react-hooks/rules-of-hooks
27
+ return useMemo(() => new URL(window.location.href), [location]); // eslint-disable-line react-hooks/exhaustive-deps
25
28
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="vite/client" />
2
2
  import React from 'react';
3
- const HTML_ATTR_SEP_RE = /(?<!\=)"\s+/gim;
3
+ const HTML_ATTR_SEP_RE = /(?<!=)"\s+/gim;
4
4
  const getHtmlAttrs = (template) => { var _a; return ((_a = template.match(/<html\s+([^>]+?)\s*>/s)) === null || _a === void 0 ? void 0 : _a[1]) || ''; };
5
5
  const getBodyAttrs = (template) => { var _a; return ((_a = template.match(/<body\s+([^>]+?)\s*>/s)) === null || _a === void 0 ? void 0 : _a[1]) || ''; };
6
6
  const REACT_ATTR_MAP = Object.create(null);
@@ -1,7 +1,7 @@
1
1
  import type { ShopifyContextValue } from '../../foundation/ShopifyProvider/types';
2
2
  import type { QueryCacheControlHeaders } from '../../utilities/log/log-cache-header';
3
3
  import type { QueryTiming } from '../../utilities/log/log-query-timeline';
4
- import type { PreloadOptions, QueryKey } from '../../types';
4
+ import type { HydrogenConfig, PreloadOptions, QueryKey } from '../../types';
5
5
  import { HelmetData as HeadData } from 'react-helmet-async';
6
6
  import { SessionSyncApi } from '../../foundation/session/session';
7
7
  export declare type PreloadQueryEntry = {
@@ -27,10 +27,11 @@ export declare class ServerComponentRequest extends Request {
27
27
  cookies: Map<string, string>;
28
28
  id: string;
29
29
  time: number;
30
- preloadURL: string;
30
+ normalizedUrl: string;
31
31
  ctx: {
32
32
  cache: Map<string, any>;
33
33
  head: HeadData;
34
+ hydrogenConfig?: HydrogenConfig;
34
35
  shopifyConfig?: ShopifyContextValue;
35
36
  queryCacheControl: Array<QueryCacheControlHeaders>;
36
37
  queryTimings: Array<QueryTiming>;
@@ -2,6 +2,7 @@ import { getTime } from '../../utilities/timing';
2
2
  import { hashKey } from '../../utilities/hash';
3
3
  import { HelmetData as HeadData } from 'react-helmet-async';
4
4
  import { RSC_PATHNAME } from '../../constants';
5
+ import { parseJSON } from '../../utilities/parse';
5
6
  let reqCounter = 0; // For debugging
6
7
  const generateId = typeof crypto !== 'undefined' &&
7
8
  // @ts-ignore
@@ -27,11 +28,11 @@ export class ServerComponentRequest extends Request {
27
28
  else {
28
29
  super(getUrlFromNodeRequest(input), getInitFromNodeRequest(input));
29
30
  }
30
- const referer = this.headers.get('referer');
31
31
  this.time = getTime();
32
32
  this.id = generateId();
33
- this.preloadURL =
34
- this.isRscRequest() && referer && referer !== '' ? referer : this.url;
33
+ this.normalizedUrl = this.isRscRequest()
34
+ ? normalizeUrl(this.url)
35
+ : this.url;
35
36
  this.ctx = {
36
37
  cache: new Map(),
37
38
  head: new HeadData({}),
@@ -44,7 +45,7 @@ export class ServerComponentRequest extends Request {
44
45
  queryTimings: [],
45
46
  analyticsData: {
46
47
  url: this.url,
47
- normalizedRscUrl: this.preloadURL,
48
+ normalizedRscUrl: this.normalizedUrl,
48
49
  },
49
50
  preloadQueries: new Map(),
50
51
  };
@@ -71,9 +72,9 @@ export class ServerComponentRequest extends Request {
71
72
  }
72
73
  }
73
74
  getPreloadQueries() {
74
- if (preloadCache.has(this.preloadURL)) {
75
+ if (preloadCache.has(this.normalizedUrl)) {
75
76
  const combinedPreloadQueries = new Map();
76
- const urlPreloadCache = preloadCache.get(this.preloadURL);
77
+ const urlPreloadCache = preloadCache.get(this.normalizedUrl);
77
78
  mergeMapEntries(combinedPreloadQueries, urlPreloadCache);
78
79
  mergeMapEntries(combinedPreloadQueries, preloadCache.get(PRELOAD_ALL));
79
80
  return combinedPreloadQueries;
@@ -83,7 +84,7 @@ export class ServerComponentRequest extends Request {
83
84
  }
84
85
  }
85
86
  savePreloadQueries() {
86
- preloadCache.set(this.preloadURL, this.ctx.preloadQueries);
87
+ preloadCache.set(this.normalizedUrl, this.ctx.preloadQueries);
87
88
  }
88
89
  /**
89
90
  * Buyer IP varies by hosting provider and runtime. The developer should provide this
@@ -135,3 +136,11 @@ function getInitFromNodeRequest(request) {
135
136
  }
136
137
  return init;
137
138
  }
139
+ function normalizeUrl(rawUrl) {
140
+ var _a, _b;
141
+ const url = new URL(rawUrl);
142
+ const state = parseJSON((_a = url.searchParams.get('state')) !== null && _a !== void 0 ? _a : '');
143
+ const normalizedUrl = new URL((_b = state === null || state === void 0 ? void 0 : state.pathname) !== null && _b !== void 0 ? _b : '', url.origin);
144
+ normalizedUrl.search = state === null || state === void 0 ? void 0 : state.search;
145
+ return normalizedUrl.toString();
146
+ }
@@ -1,6 +1,3 @@
1
- declare global {
2
- var __flight: Array<string>;
3
- }
4
1
  /**
5
2
  * Much of this is borrowed from React's demo implementation:
6
3
  * @see https://github.com/reactjs/server-components-demo/blob/main/src/Cache.client.js
@@ -7,26 +7,6 @@ import { createFromFetch, createFromReadableStream,
7
7
  } from '@shopify/hydrogen/vendor/react-server-dom-vite';
8
8
  import { RSC_PATHNAME } from '../../constants';
9
9
  let rscReader;
10
- if (globalThis.__flight && __flight.length > 0) {
11
- const contentLoaded = new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve));
12
- try {
13
- rscReader = new ReadableStream({
14
- start(controller) {
15
- const encoder = new TextEncoder();
16
- const write = (chunk) => {
17
- controller.enqueue(encoder.encode(chunk));
18
- return 0;
19
- };
20
- __flight.forEach(write);
21
- __flight.push = write;
22
- contentLoaded.then(() => controller.close());
23
- },
24
- });
25
- }
26
- catch (_) {
27
- // Old browser, will try a new hydration request later
28
- }
29
- }
30
10
  function createResponseCache() {
31
11
  return new Map();
32
12
  }
@@ -4,15 +4,14 @@ import type { ViteDevServer } from 'vite';
4
4
  import type { ShopifyConfig } from '../types';
5
5
  declare type HydrogenMiddlewareArgs = {
6
6
  dev?: boolean;
7
- shopifyConfig?: ShopifyConfig;
8
7
  indexTemplate: string | ((url: string) => Promise<string>);
9
8
  getServerEntrypoint: () => any;
10
9
  devServer?: ViteDevServer;
11
10
  cache?: Cache;
12
11
  };
13
- export declare function graphiqlMiddleware({ shopifyConfig, dev, }: {
14
- shopifyConfig: ShopifyConfig;
15
- dev: boolean;
12
+ export declare function graphiqlMiddleware({ getShopifyConfig, dev, }: {
13
+ getShopifyConfig: (request: IncomingMessage) => ShopifyConfig | Promise<ShopifyConfig>;
14
+ dev?: boolean;
16
15
  }): (request: IncomingMessage, response: ServerResponse, next: NextFunction) => Promise<void>;
17
16
  /**
18
17
  * Provides middleware to Node.js Express-like servers. Used by the Hydrogen