@shopify/hydrogen 0.25.1 → 0.26.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 (113) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/esnext/components/{CartEstimatedCost/CartEstimatedCost.client.d.ts → CartCost/CartCost.client.d.ts} +3 -3
  3. package/dist/esnext/components/{CartEstimatedCost/CartEstimatedCost.client.js → CartCost/CartCost.client.js} +7 -7
  4. package/dist/esnext/components/CartCost/index.d.ts +1 -0
  5. package/dist/esnext/components/CartCost/index.js +1 -0
  6. package/dist/esnext/components/CartLinePrice/CartLinePrice.client.js +2 -2
  7. package/dist/esnext/components/CartLineProvider/context.d.ts +3 -3
  8. package/dist/esnext/components/CartProvider/CartProvider.client.js +37 -27
  9. package/dist/esnext/components/CartProvider/cart-queries.d.ts +1 -1
  10. package/dist/esnext/components/CartProvider/cart-queries.js +3 -3
  11. package/dist/esnext/components/CartProvider/graphql/CartAttributesUpdateMutation.d.ts +5 -5
  12. package/dist/esnext/components/CartProvider/graphql/CartBuyerIdentityUpdateMutation.d.ts +5 -5
  13. package/dist/esnext/components/CartProvider/graphql/CartCreateMutation.d.ts +5 -5
  14. package/dist/esnext/components/CartProvider/graphql/CartDiscountCodesUpdateMutation.d.ts +5 -5
  15. package/dist/esnext/components/CartProvider/graphql/CartFragment.d.ts +5 -5
  16. package/dist/esnext/components/CartProvider/graphql/CartLineAddMutation.d.ts +5 -5
  17. package/dist/esnext/components/CartProvider/graphql/CartLineRemoveMutation.d.ts +5 -5
  18. package/dist/esnext/components/CartProvider/graphql/CartLineUpdateMutation.d.ts +5 -5
  19. package/dist/esnext/components/CartProvider/graphql/CartNoteUpdateMutation.d.ts +5 -5
  20. package/dist/esnext/components/CartProvider/graphql/CartQuery.d.ts +5 -5
  21. package/dist/esnext/components/CartProvider/hooks.client.d.ts +1 -1
  22. package/dist/esnext/components/CartProvider/hooks.client.js +16 -11
  23. package/dist/esnext/components/CartProvider/types.d.ts +3 -3
  24. package/dist/esnext/components/CartShopPayButton/CartShopPayButton.client.d.ts +1 -3
  25. package/dist/esnext/components/CartShopPayButton/CartShopPayButton.client.js +2 -4
  26. package/dist/esnext/components/Link/Link.client.d.ts +2 -0
  27. package/dist/esnext/components/Link/Link.client.js +8 -4
  28. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.d.ts +2 -2
  29. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +1 -1
  30. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +6 -3
  31. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +8 -20
  32. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +17 -33
  33. package/dist/esnext/components/LocalizationProvider/index.d.ts +0 -1
  34. package/dist/esnext/components/LocalizationProvider/index.js +0 -1
  35. package/dist/esnext/components/Metafield/Metafield.client.js +3 -3
  36. package/dist/esnext/components/Seo/DefaultPageSeo.client.js +3 -3
  37. package/dist/esnext/components/Seo/DescriptionSeo.client.js +1 -1
  38. package/dist/esnext/components/Seo/HomePageSeo.client.d.ts +1 -1
  39. package/dist/esnext/components/Seo/HomePageSeo.client.js +3 -3
  40. package/dist/esnext/components/Seo/ImageSeo.client.js +1 -1
  41. package/dist/esnext/components/Seo/NoIndexSeo.client.js +3 -3
  42. package/dist/esnext/components/Seo/ProductSeo.client.js +2 -2
  43. package/dist/esnext/components/Seo/Seo.client.js +1 -1
  44. package/dist/esnext/components/Seo/TitleSeo.client.js +1 -1
  45. package/dist/esnext/components/Seo/TwitterSeo.client.js +1 -1
  46. package/dist/esnext/components/Seo/seo-types.d.ts +1 -0
  47. package/dist/esnext/components/ShopPayButton/ShopPayButton.client.d.ts +3 -1
  48. package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +8 -3
  49. package/dist/esnext/components/index.d.ts +2 -2
  50. package/dist/esnext/components/index.js +2 -2
  51. package/dist/esnext/constants.d.ts +1 -1
  52. package/dist/esnext/constants.js +1 -1
  53. package/dist/esnext/entry-client.js +7 -5
  54. package/dist/esnext/entry-server.js +21 -34
  55. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetricsDebug.client.js +1 -1
  56. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +1 -1
  57. package/dist/esnext/foundation/FileRoutes/FileRoutes.server.js +1 -1
  58. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +9 -1
  59. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +6 -0
  60. package/dist/esnext/foundation/Route/Route.server.js +1 -1
  61. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +5 -4
  62. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +9 -7
  63. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +4 -0
  64. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +1 -0
  65. package/dist/esnext/foundation/useNavigate/useNavigate.d.ts +2 -0
  66. package/dist/esnext/foundation/useNavigate/useNavigate.js +13 -0
  67. package/dist/esnext/foundation/useQuery/hooks.js +1 -4
  68. package/dist/esnext/foundation/useRouteParams/RouteParamsProvider.client.d.ts +3 -0
  69. package/dist/esnext/foundation/useRouteParams/RouteParamsProvider.client.js +8 -3
  70. package/dist/esnext/framework/cache/in-memory.d.ts +12 -7
  71. package/dist/esnext/framework/cache/in-memory.js +49 -21
  72. package/dist/esnext/framework/load-config.d.ts +6 -0
  73. package/dist/esnext/framework/load-config.js +10 -0
  74. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-client-middleware.js +2 -3
  75. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-virtual-files.js +21 -4
  76. package/dist/esnext/framework/viteception.d.ts +2 -1
  77. package/dist/esnext/framework/viteception.js +2 -1
  78. package/dist/esnext/hooks/useCartLine/useCartLine.d.ts +3 -3
  79. package/dist/esnext/hooks/useLocalization/useLocalization.d.ts +4 -0
  80. package/dist/esnext/hooks/useLocalization/useLocalization.js +14 -0
  81. package/dist/esnext/hooks/useMeasurement/hooks.js +2 -2
  82. package/dist/esnext/hooks/useMoney/hooks.js +2 -2
  83. package/dist/esnext/index.d.ts +3 -0
  84. package/dist/esnext/index.js +1 -0
  85. package/dist/esnext/platforms/node.d.ts +1 -1
  86. package/dist/esnext/platforms/node.js +2 -1
  87. package/dist/esnext/storefront-api-types.d.ts +105 -3
  88. package/dist/esnext/storefront-api-types.js +8 -0
  89. package/dist/esnext/types.d.ts +10 -0
  90. package/dist/esnext/utilities/apiRoutes.d.ts +4 -3
  91. package/dist/esnext/utilities/log/log-cache-api-status.js +3 -0
  92. package/dist/esnext/utilities/storefrontApi.d.ts +1 -0
  93. package/dist/esnext/utilities/storefrontApi.js +1 -1
  94. package/dist/esnext/version.d.ts +1 -1
  95. package/dist/esnext/version.js +1 -1
  96. package/dist/node/framework/cache/in-memory.d.ts +12 -7
  97. package/dist/node/framework/cache/in-memory.js +49 -21
  98. package/dist/node/framework/load-config.d.ts +6 -0
  99. package/dist/node/framework/load-config.js +14 -0
  100. package/dist/node/framework/plugins/vite-plugin-hydrogen-client-middleware.js +2 -3
  101. package/dist/node/framework/plugins/vite-plugin-hydrogen-virtual-files.js +21 -4
  102. package/dist/node/framework/viteception.d.ts +2 -1
  103. package/dist/node/framework/viteception.js +2 -1
  104. package/package.json +13 -3
  105. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +21 -6
  106. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +2 -1
  107. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +21 -6
  108. package/dist/esnext/components/CartEstimatedCost/index.d.ts +0 -1
  109. package/dist/esnext/components/CartEstimatedCost/index.js +0 -1
  110. package/dist/esnext/hooks/useCountry/index.d.ts +0 -1
  111. package/dist/esnext/hooks/useCountry/index.js +0 -1
  112. package/dist/esnext/hooks/useCountry/useCountry.d.ts +0 -7
  113. package/dist/esnext/hooks/useCountry/useCountry.js +0 -17
@@ -6,4 +6,4 @@ export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Pr
6
6
  export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
7
7
  export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
8
8
  export declare const SHOPIFY_STOREFRONT_ID_VARIABLE = "SHOPIFY_STOREFRONT_ID";
9
- export declare const SHOPIFY_STOREFRONT_ID_HEADER = "X-Shopify-Storefront-Id";
9
+ export declare const SHOPIFY_STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
@@ -6,4 +6,4 @@ export const STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-To
6
6
  export const STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
7
7
  export const STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
8
8
  export const SHOPIFY_STOREFRONT_ID_VARIABLE = 'SHOPIFY_STOREFRONT_ID';
9
- export const SHOPIFY_STOREFRONT_ID_HEADER = 'X-Shopify-Storefront-Id';
9
+ export const SHOPIFY_STOREFRONT_ID_HEADER = 'Shopify-Storefront-Id';
@@ -89,11 +89,13 @@ const renderHydrogen = async (ClientWrapper) => {
89
89
  const RootComponent =
90
90
  // Default to StrictMode on, unless explicitly turned off
91
91
  config.strictMode !== false ? StrictMode : Fragment;
92
- hydrateRoot(root, React.createElement(React.Fragment, null,
93
- React.createElement(RootComponent, null,
94
- React.createElement(ErrorBoundary, { FallbackComponent: Error },
95
- React.createElement(Suspense, { fallback: null },
96
- React.createElement(Content, { clientWrapper: ClientWrapper }))))));
92
+ // Fixes hydration in `useId`: https://github.com/Shopify/hydrogen/issues/1589
93
+ const ServerRequestProviderMock = () => null;
94
+ hydrateRoot(root, React.createElement(RootComponent, null,
95
+ React.createElement(ServerRequestProviderMock, null),
96
+ React.createElement(ErrorBoundary, { FallbackComponent: Error },
97
+ React.createElement(Suspense, { fallback: null },
98
+ React.createElement(Content, { clientWrapper: ClientWrapper })))));
97
99
  };
98
100
  export default renderHydrogen;
99
101
  function Content({ clientWrapper: ClientWrapper = ({ children }) => children, }) {
@@ -47,6 +47,10 @@ export const renderHydrogen = (App) => {
47
47
  setLogger(hydrogenConfig.logger);
48
48
  const log = getLoggerWithContext(request);
49
49
  const response = new HydrogenResponse();
50
+ if (hydrogenConfig.poweredByHeader ?? true) {
51
+ // If not defined in the config, always show the header
52
+ response.headers.set('powered-by', 'Shopify-Hydrogen');
53
+ }
50
54
  const sessionApi = hydrogenConfig.session
51
55
  ? hydrogenConfig.session(log)
52
56
  : undefined;
@@ -55,14 +59,6 @@ export const renderHydrogen = (App) => {
55
59
  * Inject the cache & context into the module loader so we can pull it out for subrequests.
56
60
  */
57
61
  request.ctx.runtime = context;
58
- if (!context?.waitUntil) {
59
- const runtimeContext = {
60
- waitUntil: (fn) => {
61
- setTimeout(() => fn, 0);
62
- },
63
- };
64
- request.ctx.runtime = runtimeContext;
65
- }
66
62
  setCache(cache);
67
63
  const builtInRouteResource = getBuiltInRoute(url);
68
64
  if (builtInRouteResource) {
@@ -84,7 +80,7 @@ export const renderHydrogen = (App) => {
84
80
  if (cachedResponse) {
85
81
  if (isStale(request, cachedResponse)) {
86
82
  const lockCacheKey = request.cacheKey(true);
87
- const staleWhileRevalidate = async (lockExists) => {
83
+ const staleWhileRevalidatePromise = getItemFromCache(lockCacheKey).then(async (lockExists) => {
88
84
  if (lockExists)
89
85
  return;
90
86
  try {
@@ -98,13 +94,9 @@ export const renderHydrogen = (App) => {
98
94
  catch (e) {
99
95
  log.error('Cache revalidate error', e);
100
96
  }
101
- finally {
102
- await deleteItemFromCache(lockCacheKey);
103
- }
104
- };
105
- const revalidatingPromise = getItemFromCache(lockCacheKey).then(staleWhileRevalidate);
97
+ });
106
98
  // Asynchronously wait for it in workers
107
- request.ctx.runtime?.waitUntil(revalidatingPromise);
99
+ request.ctx.runtime?.waitUntil(staleWhileRevalidatePromise);
108
100
  }
109
101
  return cachedResponse;
110
102
  }
@@ -195,11 +187,8 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
195
187
  const AppSSR = (React.createElement(Html, { template: response.canStream() ? noScriptTemplate : template, hydrogenConfig: request.ctx.hydrogenConfig },
196
188
  React.createElement(ServerRequestProvider, { request: request },
197
189
  React.createElement(ServerPropsProvider, { initialServerProps: state, setServerPropsForRsc: () => { } },
198
- React.createElement(PreloadQueries, { request: request },
199
- React.createElement(Suspense, { fallback: null },
200
- React.createElement(RscConsumer, null)),
201
- React.createElement(Suspense, { fallback: null },
202
- React.createElement(Analytics, null)))))));
190
+ React.createElement(Suspense, { fallback: null },
191
+ React.createElement(RscConsumer, null))))));
203
192
  log.trace('start ssr');
204
193
  const rscReadable = response.canStream()
205
194
  ? new ReadableStream({
@@ -324,6 +313,9 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
324
313
  }
325
314
  else if (nodeResponse) {
326
315
  const savedChunks = tagOnWrite(nodeResponse);
316
+ nodeResponse.on('finish', () => {
317
+ cacheResponse(response, request, savedChunks, revalidate);
318
+ });
327
319
  const { pipe } = ssrRenderToPipeableStream(AppSSR, {
328
320
  nonce,
329
321
  bootstrapScripts,
@@ -357,7 +349,6 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
357
349
  if (!revalidate &&
358
350
  (response.canStream() || nodeResponse.writableEnded)) {
359
351
  postRequestTasks('str', nodeResponse.statusCode, request, response);
360
- cacheResponse(response, request, savedChunks, revalidate);
361
352
  return;
362
353
  }
363
354
  writeHeadToNodeResponse(nodeResponse, response, log, didError());
@@ -378,7 +369,6 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
378
369
  if (!error) {
379
370
  html = assembleHtml({ ssrHtml, rscPayload, request, template });
380
371
  postRequestTasks('ssr', nodeResponse.statusCode, request, response);
381
- cacheResponse(response, request, [html], revalidate);
382
372
  }
383
373
  nodeResponse.write(html);
384
374
  nodeResponse.end();
@@ -412,13 +402,13 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
412
402
  function runRSC({ App, state, log, request, response }) {
413
403
  const serverProps = { ...state, request, response, log };
414
404
  request.ctx.router.serverProps = serverProps;
405
+ preloadRequestCacheData(request);
415
406
  const AppRSC = (React.createElement(ServerRequestProvider, { request: request },
416
- React.createElement(PreloadQueries, { request: request },
417
- React.createElement(App, { ...serverProps }),
418
- React.createElement(Suspense, { fallback: null },
419
- React.createElement(Analytics, null)),
420
- request.ctx.hydrogenConfig?.__EXPERIMENTAL__devTools && (React.createElement(Suspense, { fallback: null },
421
- React.createElement(DevTools, null))))));
407
+ React.createElement(App, { ...serverProps }),
408
+ React.createElement(Suspense, { fallback: null },
409
+ React.createElement(Analytics, null)),
410
+ request.ctx.hydrogenConfig?.__EXPERIMENTAL__devTools && (React.createElement(Suspense, { fallback: null },
411
+ React.createElement(DevTools, null)))));
422
412
  let rscDidError;
423
413
  const rscReadable = rscRenderToReadableStream(AppRSC, {
424
414
  onError(e) {
@@ -428,11 +418,6 @@ function runRSC({ App, state, log, request, response }) {
428
418
  });
429
419
  return { readable: rscReadable, didError: () => rscDidError };
430
420
  }
431
- function PreloadQueries({ request, children, }) {
432
- const preloadQueries = request.getPreloadQueries();
433
- preloadRequestCacheData(request, preloadQueries);
434
- return React.createElement(React.Fragment, null, children);
435
- }
436
421
  export default renderHydrogen;
437
422
  function startWritingToNodeResponse(nodeResponse, error) {
438
423
  if (!nodeResponse.headersSent) {
@@ -574,7 +559,8 @@ async function cacheResponse(response, request, chunks, revalidate) {
574
559
  await saveCacheResponse(response, request, chunks);
575
560
  }
576
561
  else {
577
- request.ctx.runtime?.waitUntil(Promise.resolve(true).then(() => saveCacheResponse(response, request, chunks)));
562
+ const cachePutPromise = Promise.resolve(true).then(() => saveCacheResponse(response, request, chunks));
563
+ request.ctx.runtime?.waitUntil(cachePutPromise);
578
564
  }
579
565
  }
580
566
  }
@@ -605,5 +591,6 @@ async function saveCacheResponse(response, request, chunks) {
605
591
  statusText,
606
592
  headers,
607
593
  }), response.cache());
594
+ deleteItemFromCache(request.cacheKey(true));
608
595
  }
609
596
  }
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react';
2
- import { ClientAnalytics } from '../../index';
2
+ import { ClientAnalytics } from '../../ClientAnalytics';
3
3
  const PAD = 10;
4
4
  let isInit = false;
5
5
  export function PerformanceMetricsDebug() {
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { parse, stringify } from 'worktop/cookie';
3
- import { ClientAnalytics } from '../../index';
3
+ import { ClientAnalytics } from '../../ClientAnalytics';
4
4
  import { buildUUID, addDataIf } from './utils';
5
5
  import { SHOPIFY_S, SHOPIFY_Y } from './const';
6
6
  const longTermLength = 60 * 60 * 24 * 360 * 2; // ~2 year expiry
@@ -33,7 +33,7 @@ export function FileRoutes({ routes, basePath, dirPrefix }) {
33
33
  if (foundRoute) {
34
34
  request.ctx.router.routeRendered = true;
35
35
  request.ctx.router.routeParams = foundRouteDetails.params;
36
- return (React.createElement(RouteParamsProvider, { routeParams: foundRouteDetails.params },
36
+ return (React.createElement(RouteParamsProvider, { routeParams: foundRouteDetails.params, basePath: basePath },
37
37
  React.createElement(foundRoute.component, { params: foundRouteDetails.params, ...serverProps })));
38
38
  }
39
39
  return null;
@@ -4,9 +4,10 @@ import type { QueryTiming } from '../../utilities/log/log-query-timeline';
4
4
  import type { ResolvedHydrogenConfig, PreloadOptions, QueryKey, RuntimeContext } from '../../types';
5
5
  import { HelmetData as HeadData } from 'react-helmet-async';
6
6
  import { SessionSyncApi } from '../session/session';
7
+ import { LocalizationContextValue } from '../../components/LocalizationProvider/LocalizationContext.client';
7
8
  export declare type PreloadQueryEntry = {
8
9
  key: QueryKey;
9
- fetcher: () => Promise<unknown>;
10
+ fetcher: (request: HydrogenRequest) => Promise<unknown>;
10
11
  preload?: PreloadOptions;
11
12
  };
12
13
  export declare type PreloadQueriesByURL = Map<string, PreloadQueryEntry>;
@@ -24,9 +25,15 @@ export declare type RouterContextData = {
24
25
  * - Adds a static constructor to convert a Node.js `IncomingMessage` to a Request.
25
26
  */
26
27
  export declare class HydrogenRequest extends Request {
28
+ /**
29
+ * A Map of cookies for easy access.
30
+ */
27
31
  cookies: Map<string, string>;
28
32
  id: string;
29
33
  time: number;
34
+ /**
35
+ * Get the canonical URL for the current page, across SSR and RSC requests.
36
+ */
30
37
  normalizedUrl: string;
31
38
  ctx: {
32
39
  cache: Map<string, any>;
@@ -42,6 +49,7 @@ export declare class HydrogenRequest extends Request {
42
49
  session?: SessionSyncApi;
43
50
  runtime?: RuntimeContext;
44
51
  scopes: Map<string, Record<string, any>>;
52
+ localization?: LocalizationContextValue;
45
53
  [key: string]: any;
46
54
  };
47
55
  constructor(input: any);
@@ -22,9 +22,15 @@ const PRELOAD_ALL = '*';
22
22
  * - Adds a static constructor to convert a Node.js `IncomingMessage` to a Request.
23
23
  */
24
24
  export class HydrogenRequest extends Request {
25
+ /**
26
+ * A Map of cookies for easy access.
27
+ */
25
28
  cookies;
26
29
  id;
27
30
  time;
31
+ /**
32
+ * Get the canonical URL for the current page, across SSR and RSC requests.
33
+ */
28
34
  normalizedUrl;
29
35
  // CFW Request has a reserved 'context' property, use 'ctx' instead.
30
36
  ctx;
@@ -22,7 +22,7 @@ export function Route({ path, page }) {
22
22
  if (match) {
23
23
  request.ctx.router.routeRendered = true;
24
24
  request.ctx.router.routeParams = match.params;
25
- return (React.createElement(RouteParamsProvider, { routeParams: match.params }, cloneElement(page, { params: match.params || {}, ...serverProps })));
25
+ return (React.createElement(RouteParamsProvider, { routeParams: match.params, basePath: '/' }, cloneElement(page, { params: match.params || {}, ...serverProps })));
26
26
  }
27
27
  return null;
28
28
  }
@@ -1,8 +1,9 @@
1
- import type { PreloadQueriesByURL, HydrogenRequest } from '../HydrogenRequest/HydrogenRequest.server';
1
+ import React from 'react';
2
+ import type { HydrogenRequest } from '../HydrogenRequest/HydrogenRequest.server';
2
3
  import type { QueryKey } from '../../types';
3
4
  declare type ServerRequestProviderProps = {
4
5
  request: HydrogenRequest;
5
- children: JSX.Element;
6
+ children: React.ReactNode;
6
7
  };
7
8
  export declare function ServerRequestProvider({ request, children, }: ServerRequestProviderProps): JSX.Element;
8
9
  export declare function useServerRequest(): HydrogenRequest;
@@ -17,6 +18,6 @@ declare type RequestCacheResult<T> = {
17
18
  * Returns data stored in the request cache.
18
19
  * It will throw the promise if data is not ready.
19
20
  */
20
- export declare function useRequestCacheData<T>(key: QueryKey, fetcher: () => T | Promise<T>): RequestCacheResult<T>;
21
- export declare function preloadRequestCacheData(request: HydrogenRequest, preloadQueries?: PreloadQueriesByURL): void;
21
+ export declare function useRequestCacheData<T>(key: QueryKey, fetcher: (request: HydrogenRequest) => T | Promise<T>): RequestCacheResult<T>;
22
+ export declare function preloadRequestCacheData(request: HydrogenRequest): void;
22
23
  export {};
@@ -10,9 +10,10 @@ function requestCacheRSC() {
10
10
  }
11
11
  requestCacheRSC.key = Symbol.for('HYDROGEN_REQUEST');
12
12
  function getInternalReactDispatcher() {
13
+ return (
13
14
  // @ts-ignore
14
- return React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
15
- .ReactCurrentDispatcher.current;
15
+ React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
16
+ .ReactCurrentDispatcher.current || {});
16
17
  }
17
18
  function isRsc() {
18
19
  // This flag is added by RSC Vite plugin
@@ -36,7 +37,7 @@ export function ServerRequestProvider({ request, children, }) {
36
37
  // scoped to this current rendering.
37
38
  const requestCache = getCacheForType(requestCacheRSC);
38
39
  requestCache.set(requestCacheRSC.key, request);
39
- return children;
40
+ return React.createElement(React.Fragment, null, children);
40
41
  }
41
42
  // Use a normal provider in SSR to make the request object
42
43
  // available in the current rendering.
@@ -74,7 +75,7 @@ export function useRequestCacheData(key, fetcher) {
74
75
  }
75
76
  if (!promise) {
76
77
  const startApiTime = getTime();
77
- const maybePromise = fetcher();
78
+ const maybePromise = fetcher(request);
78
79
  if (!(maybePromise instanceof Promise)) {
79
80
  result = { data: maybePromise };
80
81
  return result;
@@ -94,8 +95,9 @@ export function useRequestCacheData(key, fetcher) {
94
95
  throw result;
95
96
  return result;
96
97
  }
97
- export function preloadRequestCacheData(request, preloadQueries) {
98
- const cache = request.ctx.cache;
98
+ export function preloadRequestCacheData(request) {
99
+ const preloadQueries = request.getPreloadQueries();
100
+ const { cache } = request.ctx;
99
101
  preloadQueries?.forEach((preloadQuery, cacheKey) => {
100
102
  collectQueryTimings(request, preloadQuery.key, 'preload');
101
103
  if (!cache.has(cacheKey)) {
@@ -108,7 +110,7 @@ export function preloadRequestCacheData(request, preloadQueries) {
108
110
  }
109
111
  if (!promise) {
110
112
  const startApiTime = getTime();
111
- promise = preloadQuery.fetcher().then((data) => {
113
+ promise = preloadQuery.fetcher(request).then((data) => {
112
114
  result = { data };
113
115
  collectQueryTimings(request, preloadQuery.key, 'resolved', getTime() - startApiTime);
114
116
  }, (error) => {
@@ -2,9 +2,12 @@ import React, { useMemo } from 'react';
2
2
  import { ShopifyProviderClient } from './ShopifyProvider.client';
3
3
  import { DEFAULT_LOCALE } from '../constants';
4
4
  import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
5
+ import { getOxygenVariable } from '../../utilities/storefrontApi';
6
+ import { SHOPIFY_STOREFRONT_ID_VARIABLE } from '../../constants';
5
7
  function makeShopifyContext(shopifyConfig) {
6
8
  const locale = shopifyConfig.defaultLocale ?? DEFAULT_LOCALE;
7
9
  const languageCode = locale.split(/[-_]/)[0];
10
+ const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
8
11
  return {
9
12
  locale: locale.toUpperCase(),
10
13
  languageCode: languageCode.toUpperCase(),
@@ -12,6 +15,7 @@ function makeShopifyContext(shopifyConfig) {
12
15
  storefrontToken: shopifyConfig.storefrontToken,
13
16
  storefrontApiVersion: shopifyConfig.storefrontApiVersion,
14
17
  multipassSecret: shopifyConfig.multipassSecret,
18
+ storefrontId,
15
19
  };
16
20
  }
17
21
  export const SHOPIFY_PROVIDER_CONTEXT_KEY = Symbol.for('SHOPIFY_PROVIDER_RSC');
@@ -4,6 +4,7 @@ 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
+ storefrontId: string | null;
7
8
  }
8
9
  export declare type ShopifyProviderProps = {
9
10
  /** Shopify connection information. Defaults to the `shopify` property in the `hydrogen.config.js` file. */
@@ -7,9 +7,11 @@ declare type NavigationOptions = {
7
7
  clientState?: any;
8
8
  /** Whether to emulate natural browser behavior and restore scroll position on navigation. Defaults to true. */
9
9
  scroll?: any;
10
+ basePath?: string;
10
11
  };
11
12
  /**
12
13
  * The useNavigate hook imperatively navigates between routes.
13
14
  */
14
15
  export declare function useNavigate(): (path: string, options?: NavigationOptions) => void;
16
+ export declare function buildPath(basePath: string, path: string): string;
15
17
  export {};
@@ -1,10 +1,13 @@
1
1
  import { useRouter } from '../Router/BrowserRouter.client';
2
+ import { useBasePath } from '../useRouteParams/RouteParamsProvider.client';
2
3
  /**
3
4
  * The useNavigate hook imperatively navigates between routes.
4
5
  */
5
6
  export function useNavigate() {
6
7
  const router = useRouter();
8
+ const routeBasePath = useBasePath();
7
9
  return (path, options = { replace: false, reloadDocument: false }) => {
10
+ path = buildPath(options.basePath ?? routeBasePath, path);
8
11
  const state = {
9
12
  ...options?.clientState,
10
13
  scroll: options?.scroll ?? true,
@@ -18,3 +21,13 @@ export function useNavigate() {
18
21
  }
19
22
  };
20
23
  }
24
+ export function buildPath(basePath, path) {
25
+ let builtPath = path;
26
+ if (basePath !== '/') {
27
+ builtPath =
28
+ path.charAt(0) === '/' && basePath.charAt(0) === '/'
29
+ ? basePath + path.substring(1)
30
+ : basePath + path;
31
+ }
32
+ return builtPath;
33
+ }
@@ -54,10 +54,7 @@ function cachedQueryFnBuilder(key, generateNewOutput, queryOptions) {
54
54
  /**
55
55
  * Attempt to read the query from cache. If it doesn't exist or if it's stale, regenerate it.
56
56
  */
57
- async function useCachedQueryFn() {
58
- // Call this hook before running any async stuff
59
- // to prevent losing the current React cycle.
60
- const request = useServerRequest();
57
+ async function useCachedQueryFn(request) {
61
58
  const log = getLoggerWithContext(request);
62
59
  const cacheResponse = await getItemFromCache(key);
63
60
  if (cacheResponse) {
@@ -1,10 +1,13 @@
1
1
  import React, { FC, ReactNode } from 'react';
2
2
  declare type RouteParamsContextValue = {
3
3
  routeParams: Record<string, string>;
4
+ basePath: string;
4
5
  };
5
6
  export declare const RouteParamsContext: React.Context<RouteParamsContextValue>;
6
7
  export declare const RouteParamsProvider: FC<{
7
8
  routeParams: Record<string, string>;
9
+ basePath: string;
8
10
  children: ReactNode;
9
11
  }>;
12
+ export declare function useBasePath(): string;
10
13
  export {};
@@ -1,7 +1,12 @@
1
- import React, { createContext } from 'react';
1
+ import React, { useContext, createContext } from 'react';
2
2
  export const RouteParamsContext = createContext({
3
3
  routeParams: {},
4
+ basePath: '/',
4
5
  });
5
- export const RouteParamsProvider = ({ children, routeParams }) => {
6
- return (React.createElement(RouteParamsContext.Provider, { value: { routeParams } }, children));
6
+ export const RouteParamsProvider = ({ children, routeParams, basePath }) => {
7
+ return (React.createElement(RouteParamsContext.Provider, { value: { routeParams, basePath } }, children));
7
8
  };
9
+ export function useBasePath() {
10
+ const router = useContext(RouteParamsContext);
11
+ return router.basePath;
12
+ }
@@ -1,12 +1,17 @@
1
1
  /**
2
- * This is an in-memory implementation of `Cache` that *barely*
3
- * works and is only meant to be used during development.
2
+ * This is a limited implementation of an in-memory cache.
3
+ * It only supports the `cache-control` header.
4
+ * It does NOT support `age` or `expires` headers.
5
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Cache
4
6
  */
5
- export declare class InMemoryCache {
6
- private store;
7
+ export declare class InMemoryCache implements Cache {
8
+ #private;
7
9
  constructor();
8
- put(request: Request, response: Response): void;
9
- match(request: Request): Response | undefined;
10
- delete(request: Request): void;
10
+ add(request: RequestInfo): Promise<void>;
11
+ addAll(requests: RequestInfo[]): Promise<void>;
12
+ matchAll(request?: RequestInfo, options?: CacheQueryOptions): Promise<readonly Response[]>;
13
+ put(request: Request, response: Response): Promise<void>;
14
+ match(request: Request): Promise<Response | undefined>;
15
+ delete(request: Request): Promise<boolean>;
11
16
  keys(request?: Request): Promise<Request[]>;
12
17
  }
@@ -1,48 +1,76 @@
1
1
  /**
2
- * This is an in-memory implementation of `Cache` that *barely*
3
- * works and is only meant to be used during development.
2
+ * This is a limited implementation of an in-memory cache.
3
+ * It only supports the `cache-control` header.
4
+ * It does NOT support `age` or `expires` headers.
5
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Cache
4
6
  */
5
7
  export class InMemoryCache {
6
- store;
8
+ #store;
7
9
  constructor() {
8
- this.store = new Map();
10
+ this.#store = new Map();
9
11
  }
10
- put(request, response) {
11
- this.store.set(request.url, {
12
- value: response,
13
- date: new Date(),
12
+ add(request) {
13
+ throw new Error('Method not implemented. Use `put` instead.');
14
+ }
15
+ addAll(requests) {
16
+ throw new Error('Method not implemented. Use `put` instead.');
17
+ }
18
+ matchAll(request, options) {
19
+ throw new Error('Method not implemented. Use `match` instead.');
20
+ }
21
+ async put(request, response) {
22
+ if (request.method !== 'GET') {
23
+ throw new TypeError('Cannot cache response to non-GET request.');
24
+ }
25
+ if (response.status === 206) {
26
+ throw new TypeError('Cannot cache response to a range request (206 Partial Content).');
27
+ }
28
+ if (response.headers.get('vary')?.includes('*')) {
29
+ throw new TypeError("Cannot cache response with 'Vary: *' header.");
30
+ }
31
+ this.#store.set(request.url, {
32
+ body: new Uint8Array(await response.arrayBuffer()),
33
+ status: response.status,
34
+ headers: [...response.headers],
35
+ timestamp: Date.now(),
14
36
  });
15
37
  }
16
- match(request) {
17
- const match = this.store.get(request.url);
38
+ async match(request) {
39
+ if (request.method !== 'GET')
40
+ return;
41
+ const match = this.#store.get(request.url);
18
42
  if (!match) {
19
43
  return;
20
44
  }
21
- const { value, date } = match;
22
- const cacheControl = value.headers.get('cache-control') || '';
45
+ const { body, timestamp, ...metadata } = match;
46
+ const headers = new Headers(metadata.headers);
47
+ const cacheControl = headers.get('cache-control') || '';
23
48
  const maxAge = parseInt(cacheControl.match(/max-age=(\d+)/)?.[1] || '0', 10);
24
49
  const swr = parseInt(cacheControl.match(/stale-while-revalidate=(\d+)/)?.[1] || '0', 10);
25
- const age = (new Date().valueOf() - date.valueOf()) / 1000;
50
+ const age = (Date.now() - timestamp) / 1000;
26
51
  const isMiss = age > maxAge + swr;
27
52
  if (isMiss) {
28
- this.store.delete(request.url);
53
+ this.#store.delete(request.url);
29
54
  return;
30
55
  }
31
56
  const isStale = age > maxAge;
32
- const headers = new Headers(value.headers);
33
57
  headers.set('cache', isStale ? 'STALE' : 'HIT');
34
- headers.set('date', date.toUTCString());
35
- const response = new Response(value.body, {
58
+ headers.set('date', new Date(timestamp).toUTCString());
59
+ return new Response(body, {
60
+ status: metadata.status ?? 200,
36
61
  headers,
37
62
  });
38
- return response;
39
63
  }
40
- delete(request) {
41
- this.store.delete(request.url);
64
+ async delete(request) {
65
+ if (this.#store.has(request.url)) {
66
+ this.#store.delete(request.url);
67
+ return true;
68
+ }
69
+ return false;
42
70
  }
43
71
  keys(request) {
44
72
  const cacheKeys = [];
45
- for (const url of this.store.keys()) {
73
+ for (const url of this.#store.keys()) {
46
74
  if (!request || request.url === url) {
47
75
  cacheKeys.push(new Request(url));
48
76
  }
@@ -0,0 +1,6 @@
1
+ export declare function loadConfig(options?: {
2
+ root: string;
3
+ }): Promise<{
4
+ configuration: any;
5
+ configurationPath: any;
6
+ }>;
@@ -0,0 +1,10 @@
1
+ // Provide Hydrogen config loader to external tools like the CLI
2
+ import { VIRTUAL_PROXY_HYDROGEN_CONFIG_ID } from './plugins/vite-plugin-hydrogen-virtual-files';
3
+ import { viteception } from './viteception';
4
+ export async function loadConfig(options = { root: process.cwd() }) {
5
+ const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], options);
6
+ return {
7
+ configuration: loaded[0].default,
8
+ configurationPath: loaded[0].configPath,
9
+ };
10
+ }
@@ -13,9 +13,8 @@ export default () => {
13
13
  server.middlewares.use(async (req, res, next) => {
14
14
  const url = req.url;
15
15
  try {
16
- if (url.includes('.client.js') &&
17
- url.includes('?v=') &&
18
- !url.includes('node_modules')) {
16
+ if (/\.client\.[jt]sx?\?v=/.test(url) &&
17
+ !/\/node_modules\//.test(url)) {
19
18
  const result = await server.transformRequest(url, { html: false });
20
19
  if (result) {
21
20
  return send(req, res, result.code, 'js', {