@shopify/hydrogen 1.0.0 → 1.1.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 (75) hide show
  1. package/dist/esnext/client.d.ts +4 -4
  2. package/dist/esnext/client.js +4 -4
  3. package/dist/esnext/components/AddToCartButton/AddToCartButton.client.js +1 -1
  4. package/dist/esnext/components/CartProvider/hooks.client.js +8 -1
  5. package/dist/esnext/components/Image/Image.js +22 -13
  6. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.d.ts +1 -1
  7. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +1 -1
  8. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +3 -2
  9. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +6 -10
  10. package/dist/esnext/components/ProductOptionsProvider/context.d.ts +1 -1
  11. package/dist/esnext/components/ProductOptionsProvider/index.d.ts +0 -1
  12. package/dist/esnext/components/ProductOptionsProvider/index.js +0 -1
  13. package/dist/esnext/components/index.d.ts +1 -2
  14. package/dist/esnext/components/index.js +1 -2
  15. package/dist/esnext/constants.d.ts +4 -0
  16. package/dist/esnext/constants.js +4 -0
  17. package/dist/esnext/entry-server.js +10 -9
  18. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.client.js +4 -2
  19. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +2 -2
  20. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server.js +1 -1
  21. package/dist/esnext/foundation/Analytics/connectors/Shopify/const.d.ts +0 -2
  22. package/dist/esnext/foundation/Analytics/connectors/Shopify/const.js +0 -2
  23. package/dist/esnext/foundation/DevTools/components/Heading.js +1 -1
  24. package/dist/esnext/foundation/DevTools/components/Panels.js +25 -19
  25. package/dist/esnext/foundation/DevTools/components/Performance.client.js +0 -1
  26. package/dist/esnext/foundation/DevTools/components/Settings.client.js +1 -4
  27. package/dist/esnext/foundation/DevTools/components/Table.js +3 -3
  28. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +1 -2
  29. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +2 -4
  30. package/dist/esnext/foundation/ServerPropsProvider/ServerPropsProvider.js +3 -3
  31. package/dist/esnext/foundation/ServerPropsProvider/index.d.ts +1 -2
  32. package/dist/esnext/foundation/ServerPropsProvider/index.js +1 -1
  33. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.client.d.ts +4 -2
  34. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.client.js +4 -2
  35. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.d.ts +4 -2
  36. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +19 -2
  37. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +17 -0
  38. package/dist/esnext/foundation/fetchSync/ResponseSync.d.ts +14 -0
  39. package/dist/esnext/foundation/fetchSync/ResponseSync.js +38 -0
  40. package/dist/esnext/foundation/fetchSync/client/fetchSync.d.ts +2 -2
  41. package/dist/esnext/foundation/fetchSync/client/fetchSync.js +5 -10
  42. package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +2 -2
  43. package/dist/esnext/foundation/fetchSync/server/fetchSync.js +9 -12
  44. package/dist/esnext/framework/graphiql.js +26 -30
  45. package/dist/esnext/framework/plugins/vite-plugin-platform-entry.js +35 -6
  46. package/dist/esnext/hooks/index.d.ts +1 -0
  47. package/dist/esnext/hooks/index.js +1 -0
  48. package/dist/esnext/hooks/useLocalization/useLocalization.d.ts +3 -2
  49. package/dist/esnext/hooks/useLocalization/useLocalization.js +2 -7
  50. package/dist/esnext/hooks/useProductOptions/helpers.js +1 -1
  51. package/dist/esnext/hooks/useShopQuery/hooks.js +9 -6
  52. package/dist/esnext/index.d.ts +6 -5
  53. package/dist/esnext/index.js +6 -5
  54. package/dist/esnext/platforms/index.d.ts +1 -0
  55. package/dist/esnext/platforms/index.js +1 -0
  56. package/dist/esnext/platforms/node.js +2 -8
  57. package/dist/esnext/platforms/virtual.d.ts +8 -0
  58. package/dist/esnext/platforms/virtual.js +14 -0
  59. package/dist/esnext/platforms/worker.js +7 -7
  60. package/dist/esnext/utilities/image_size.d.ts +1 -0
  61. package/dist/esnext/utilities/image_size.js +27 -26
  62. package/dist/esnext/utilities/index.d.ts +1 -1
  63. package/dist/esnext/utilities/index.js +1 -1
  64. package/dist/esnext/version.d.ts +1 -1
  65. package/dist/esnext/version.js +1 -1
  66. package/dist/node/framework/graphiql.js +26 -30
  67. package/dist/node/framework/plugins/vite-plugin-platform-entry.js +35 -6
  68. package/package.json +7 -2
  69. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +26 -12
  70. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +26 -12
  71. package/CHANGELOG.md +0 -2198
  72. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +0 -9
  73. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.js +0 -2
  74. package/dist/esnext/foundation/fetchSync/types.d.ts +0 -5
  75. package/dist/esnext/foundation/fetchSync/types.js +0 -1
@@ -1,9 +1,9 @@
1
1
  export * from './components';
2
2
  export * from './hooks';
3
- export * from './foundation/useServerProps';
4
- export * from './foundation/useShop';
5
- export * from './foundation/ServerPropsProvider';
6
- export * from './foundation/useUrl';
3
+ export { useServerProps } from './foundation/useServerProps';
4
+ export { useShop } from './foundation/useShop';
5
+ export { ServerPropsProvider, ServerPropsContext, type ServerProps, type ServerPropsContextValue, } from './foundation/ServerPropsProvider';
6
+ export { useUrl } from './foundation/useUrl';
7
7
  export { Head } from './foundation/Head';
8
8
  export * from './utilities';
9
9
  export { gql } from './utilities/graphql-tag';
@@ -1,9 +1,9 @@
1
1
  export * from './components';
2
2
  export * from './hooks';
3
- export * from './foundation/useServerProps';
4
- export * from './foundation/useShop';
5
- export * from './foundation/ServerPropsProvider';
6
- export * from './foundation/useUrl';
3
+ export { useServerProps } from './foundation/useServerProps';
4
+ export { useShop } from './foundation/useShop';
5
+ export { ServerPropsProvider, ServerPropsContext, } from './foundation/ServerPropsProvider';
6
+ export { useUrl } from './foundation/useUrl';
7
7
  export { Head } from './foundation/Head';
8
8
  export * from './utilities';
9
9
  export { gql } from './utilities/graphql-tag';
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { useCart } from '../CartProvider';
3
- import { useProductOptions } from '../ProductOptionsProvider';
3
+ import { useProductOptions } from '../../hooks/useProductOptions';
4
4
  import { BaseButton } from '../BaseButton';
5
5
  /**
6
6
  * The `AddToCartButton` component renders a button that adds an item to the cart when pressed.
@@ -2,7 +2,8 @@ import React, { useState } from 'react';
2
2
  import { useShop } from '../../foundation';
3
3
  import { flattenConnection } from '../../utilities';
4
4
  import { CartCreate, defaultCartFragment } from './cart-queries';
5
- import { SHOPIFY_STOREFRONT_ID_HEADER, STOREFRONT_API_PUBLIC_TOKEN_HEADER, } from '../../constants';
5
+ import { SHOPIFY_STOREFRONT_ID_HEADER, STOREFRONT_API_PUBLIC_TOKEN_HEADER, SHOPIFY_STOREFRONT_Y_HEADER, SHOPIFY_STOREFRONT_S_HEADER, SHOPIFY_Y, SHOPIFY_S, } from '../../constants';
6
+ import { parse } from 'worktop/cookie';
6
7
  export function useCartFetch() {
7
8
  const { storeDomain, storefrontApiVersion, storefrontToken, storefrontId } = useShop();
8
9
  return React.useCallback(({ query, variables, }) => {
@@ -15,6 +16,12 @@ export function useCartFetch() {
15
16
  if (storefrontId) {
16
17
  headers[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
17
18
  }
19
+ // Find Shopify cookies
20
+ const cookieData = parse(document.cookie);
21
+ if (cookieData[SHOPIFY_Y] && cookieData[SHOPIFY_S]) {
22
+ headers[SHOPIFY_STOREFRONT_Y_HEADER] = cookieData[SHOPIFY_Y];
23
+ headers[SHOPIFY_STOREFRONT_S_HEADER] = cookieData[SHOPIFY_S];
24
+ }
18
25
  return fetch(`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`, {
19
26
  method: 'POST',
20
27
  headers,
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { getShopifyImageDimensions, shopifyImageLoader, addImageSizeParametersToUrl, } from '../../utilities';
2
+ import { getShopifyImageDimensions, shopifyImageLoader, addImageSizeParametersToUrl, IMG_SRC_SET_SIZES, } from '../../utilities';
3
3
  /**
4
4
  * The `Image` component renders an image for the Storefront API's
5
5
  * [Image object](https://shopify.dev/api/storefront/reference/common-objects/image) by using the `data` prop, or a custom location by using the `src` prop. You can [customize this component](https://shopify.dev/api/hydrogen/components#customizing-hydrogen-components) using passthrough props.
@@ -34,7 +34,7 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
34
34
  if (__HYDROGEN_DEV__ && !data.altText && !rest.alt) {
35
35
  console.warn(`<Image/>: the 'data' prop should have the 'altText' property, or the 'alt' prop, and one of them should not be empty. ${`Image: ${data.id ?? data.url}`}`);
36
36
  }
37
- const { width: finalWidth, height: finalHeight } = getShopifyImageDimensions({
37
+ const { width: imgElementWidth, height: imgElementHeight } = getShopifyImageDimensions({
38
38
  data,
39
39
  loaderOptions,
40
40
  elementProps: {
@@ -42,7 +42,7 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
42
42
  height,
43
43
  },
44
44
  });
45
- if (__HYDROGEN_DEV__ && (!finalWidth || !finalHeight)) {
45
+ if (__HYDROGEN_DEV__ && (!imgElementWidth || !imgElementHeight)) {
46
46
  console.warn(`<Image/>: the 'data' prop requires either 'width' or 'data.width', and 'height' or 'data.height' properties. ${`Image: ${data.id ?? data.url}`}`);
47
47
  }
48
48
  let finalSrc = data.url;
@@ -50,8 +50,8 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
50
50
  finalSrc = loader({
51
51
  ...loaderOptions,
52
52
  src: data.url,
53
- width: finalWidth,
54
- height: finalHeight,
53
+ width: imgElementWidth,
54
+ height: imgElementHeight,
55
55
  });
56
56
  if (typeof finalSrc !== 'string' || !finalSrc) {
57
57
  throw new Error(`<Image/>: 'loader' did not return a valid string. ${`Image: ${data.id ?? data.url}`}`);
@@ -59,17 +59,20 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
59
59
  }
60
60
  // determining what the intended width of the image is. For example, if the width is specified and lower than the image width, then that is the maximum image width
61
61
  // to prevent generating a srcset with widths bigger than needed or to generate images that would distort because of being larger than original
62
- const maxWidth = width && finalWidth && width < finalWidth ? width : finalWidth;
62
+ const maxWidth = width && imgElementWidth && width < imgElementWidth
63
+ ? width
64
+ : imgElementWidth;
63
65
  const finalSrcset = rest.srcSet ??
64
66
  internalImageSrcSet({
65
67
  ...loaderOptions,
66
68
  widths,
67
69
  src: data.url,
68
70
  width: maxWidth,
71
+ height: imgElementHeight,
69
72
  loader,
70
73
  });
71
74
  /* eslint-disable hydrogen/prefer-image-component */
72
- return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: finalWidth ?? undefined, height: finalHeight ?? undefined, srcSet: finalSrcset }));
75
+ return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: imgElementWidth ?? undefined, height: imgElementHeight ?? undefined, srcSet: finalSrcset }));
73
76
  /* eslint-enable hydrogen/prefer-image-component */
74
77
  }
75
78
  function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths, loading, ...rest }) {
@@ -112,23 +115,29 @@ function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths,
112
115
  height: loaderOptions?.height ?? height, alt: alt ?? '', loading: loading ?? 'lazy', srcSet: finalSrcset }));
113
116
  /* eslint-enable hydrogen/prefer-image-component */
114
117
  }
115
- // based on the default width sizes used by the Shopify liquid HTML tag img_tag plus a 2560 width to account for 2k resolutions
116
- // reference: https://shopify.dev/api/liquid/filters/html-filters#image_tag
117
- const IMG_SRC_SET_SIZES = [352, 832, 1200, 1920, 2560];
118
- function internalImageSrcSet({ src, width, crop, scale, widths, loader, }) {
118
+ function internalImageSrcSet({ src, width, crop, scale, widths, loader, height, }) {
119
119
  const hasCustomWidths = widths && Array.isArray(widths);
120
- if (hasCustomWidths && widths.some((size) => isNaN(size)))
120
+ if (hasCustomWidths && widths.some((size) => isNaN(size))) {
121
121
  throw new Error(`<Image/>: the 'widths' must be an array of numbers`);
122
+ }
123
+ let aspectRatio = 1;
124
+ if (width && height) {
125
+ aspectRatio = Number(height) / Number(width);
126
+ }
122
127
  let setSizes = hasCustomWidths ? widths : IMG_SRC_SET_SIZES;
123
128
  if (!hasCustomWidths &&
124
129
  width &&
125
- width < IMG_SRC_SET_SIZES[IMG_SRC_SET_SIZES.length - 1])
130
+ width < IMG_SRC_SET_SIZES[IMG_SRC_SET_SIZES.length - 1]) {
126
131
  setSizes = IMG_SRC_SET_SIZES.filter((size) => size <= width);
132
+ }
127
133
  const srcGenerator = loader ? loader : addImageSizeParametersToUrl;
128
134
  return setSizes
129
135
  .map((size) => `${srcGenerator({
130
136
  src,
131
137
  width: size,
138
+ // height is not applied if there is no crop
139
+ // if there is crop, then height is applied as a ratio of the original width + height aspect ratio * size
140
+ height: crop ? Number(size) * aspectRatio : undefined,
132
141
  crop,
133
142
  scale,
134
143
  })} ${size}w`)
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { LocalizationContextValue } from './LocalizationContext.client';
2
+ import type { LocalizationContextValue } from '../../foundation/ShopifyProvider/types';
3
3
  export default function LocalizationClientProvider({ localization, children, }: {
4
4
  children: ReactNode;
5
5
  localization: LocalizationContextValue;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { LocalizationContext, } from './LocalizationContext.client';
2
+ import { LocalizationContext } from '../../foundation/ShopifyProvider/ShopifyProvider.client';
3
3
  export default function LocalizationClientProvider({ localization, children, }) {
4
4
  return (React.createElement(LocalizationContext.Provider, { value: localization }, children));
5
5
  }
@@ -1,15 +1,16 @@
1
1
  import { ReactNode } from 'react';
2
+ import { CountryCode, LanguageCode } from '../../storefront-api-types';
2
3
  export interface LocalizationProviderProps {
3
4
  /** A `ReactNode` element. */
4
5
  children: ReactNode;
5
6
  /**
6
7
  * Override the `isoCode` to define the active country
7
8
  */
8
- countryCode?: string;
9
+ countryCode?: CountryCode;
9
10
  /**
10
11
  * Override the `languageCode` to define the active language
11
12
  */
12
- languageCode?: string;
13
+ languageCode?: LanguageCode;
13
14
  }
14
15
  /**
15
16
  * The `LocalizationProvider` component automatically queries the Storefront API's
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import LocalizationClientProvider from './LocalizationClientProvider.client';
3
3
  import { useShop } from '../../foundation/useShop';
4
4
  import { useServerRequest } from '../../foundation/ServerRequestProvider';
5
+ import { log } from '../../utilities/log';
6
+ import { getLocalizationContextValue } from '../../foundation/ShopifyProvider/ShopifyProvider.server';
5
7
  /**
6
8
  * The `LocalizationProvider` component automatically queries the Storefront API's
7
9
  * [`localization`](https://shopify.dev/api/storefront/reference/common-objects/queryroot) field
@@ -10,18 +12,12 @@ import { useServerRequest } from '../../foundation/ServerRequestProvider';
10
12
  * Any descendents of this provider can use the `useLocalization` hook.
11
13
  */
12
14
  export function LocalizationProvider(props) {
15
+ if (import.meta.env.DEV) {
16
+ log.warn('<LocalizationProvider> is no longer necessary. Pass localization props directly to `<ShopifyProvider>` instead.');
17
+ }
13
18
  const { defaultLanguageCode, defaultCountryCode } = useShop();
14
- const languageCode = (props.languageCode ?? defaultLanguageCode).toUpperCase();
15
- const countryCode = (props.countryCode ?? defaultCountryCode).toUpperCase();
16
19
  const request = useServerRequest();
17
- const localization = {
18
- country: {
19
- isoCode: countryCode,
20
- },
21
- language: {
22
- isoCode: languageCode,
23
- },
24
- };
20
+ const localization = getLocalizationContextValue(defaultLanguageCode, defaultCountryCode, props.languageCode, props.countryCode);
25
21
  request.ctx.localization = localization;
26
22
  return (React.createElement(LocalizationClientProvider, { localization: localization }, props.children));
27
23
  }
@@ -1,2 +1,2 @@
1
- import { ProductOptionsHookValue } from '../../hooks';
1
+ import type { ProductOptionsHookValue } from '../../hooks';
2
2
  export declare const ProductOptionsContext: import("react").Context<ProductOptionsHookValue | null>;
@@ -1,2 +1 @@
1
1
  export { ProductOptionsProvider } from './ProductOptionsProvider.client';
2
- export { useProductOptions } from '../../hooks/useProductOptions/useProductOptions.client';
@@ -1,2 +1 @@
1
1
  export { ProductOptionsProvider } from './ProductOptionsProvider.client';
2
- export { useProductOptions } from '../../hooks/useProductOptions/useProductOptions.client';
@@ -20,9 +20,8 @@ export { CartShopPayButton } from './CartShopPayButton';
20
20
  export { CartCost } from './CartCost';
21
21
  export { CartProvider, useCart, useInstantCheckout } from './CartProvider';
22
22
  export type { State, Status, Cart, CartWithActions, CartAction, } from './CartProvider';
23
- export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
23
+ export { ProductOptionsProvider } from './ProductOptionsProvider';
24
24
  export { ProductPrice } from './ProductPrice';
25
25
  export { BuyNowButton } from './BuyNowButton';
26
26
  export { ShopPayButton } from './ShopPayButton';
27
- export { useLocalization } from '../hooks/useLocalization/useLocalization';
28
27
  export { Seo } from './Seo';
@@ -18,9 +18,8 @@ export { CartCheckoutButton } from './CartCheckoutButton';
18
18
  export { CartShopPayButton } from './CartShopPayButton';
19
19
  export { CartCost } from './CartCost';
20
20
  export { CartProvider, useCart, useInstantCheckout } from './CartProvider';
21
- export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
21
+ export { ProductOptionsProvider } from './ProductOptionsProvider';
22
22
  export { ProductPrice } from './ProductPrice';
23
23
  export { BuyNowButton } from './BuyNowButton';
24
24
  export { ShopPayButton } from './ShopPayButton';
25
- export { useLocalization } from '../hooks/useLocalization/useLocalization';
26
25
  export { Seo } from './Seo';
@@ -7,3 +7,7 @@ export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-
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
9
  export declare const SHOPIFY_STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
10
+ export declare const SHOPIFY_STOREFRONT_Y_HEADER = "Shopify-Storefront-Y";
11
+ export declare const SHOPIFY_STOREFRONT_S_HEADER = "Shopify-Storefront-S";
12
+ export declare const SHOPIFY_Y = "_shopify_y";
13
+ export declare const SHOPIFY_S = "_shopify_s";
@@ -7,3 +7,7 @@ export const STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-T
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
9
  export const SHOPIFY_STOREFRONT_ID_HEADER = 'Shopify-Storefront-Id';
10
+ export const SHOPIFY_STOREFRONT_Y_HEADER = 'Shopify-Storefront-Y';
11
+ export const SHOPIFY_STOREFRONT_S_HEADER = 'Shopify-Storefront-S';
12
+ export const SHOPIFY_Y = '_shopify_y';
13
+ export const SHOPIFY_S = '_shopify_s';
@@ -10,7 +10,6 @@ import { ServerPropsProvider } from './foundation/ServerPropsProvider';
10
10
  import { isBotUA } from './utilities/bot-ua';
11
11
  import { getCache, setCache } from './foundation/runtime';
12
12
  import { ssrRenderToPipeableStream, ssrRenderToReadableStream, rscRenderToReadableStream, createFromReadableStream, bufferReadableStream, } from './streaming.server';
13
- import { RSC_PATHNAME } from './constants';
14
13
  import { stripScriptsFromTemplate } from './utilities/template';
15
14
  import { setLogger } from './utilities/log/log';
16
15
  import { Analytics } from './foundation/Analytics/Analytics.server';
@@ -110,7 +109,7 @@ export const renderHydrogen = (App) => {
110
109
  async function processRequest(handleRequest, App, url, request, sessionApi, options, response, hydrogenConfig, revalidate = false) {
111
110
  const { dev, nonce, indexTemplate, streamableResponse: nodeResponse } = options;
112
111
  const log = getLoggerWithContext(request);
113
- const isRSCRequest = url.pathname === RSC_PATHNAME;
112
+ const isRSCRequest = request.isRscRequest();
114
113
  const apiRoute = !isRSCRequest && getApiRoute(url, hydrogenConfig.routes);
115
114
  // The API Route might have a default export, making it also a server component
116
115
  // If it does, only render the API route if the request method is GET
@@ -123,8 +122,11 @@ async function processRequest(handleRequest, App, url, request, sessionApi, opti
123
122
  : apiResponse;
124
123
  }
125
124
  const state = isRSCRequest
126
- ? parseJSON(url.searchParams.get('state') || '{}')
127
- : { pathname: url.pathname, search: url.search };
125
+ ? parseJSON(decodeURIComponent(url.searchParams.get('state') || '{}'))
126
+ : {
127
+ pathname: decodeURIComponent(url.pathname),
128
+ search: decodeURIComponent(url.search),
129
+ };
128
130
  const rsc = runRSC({ App, state, log, request, response });
129
131
  if (isRSCRequest) {
130
132
  const buffered = await bufferReadableStream(rsc.readable.getReader());
@@ -552,13 +554,13 @@ function tagOnWrite(response) {
552
554
  async function cacheResponse(response, request, chunks, revalidate) {
553
555
  const cache = getCache();
554
556
  /**
555
- * Only cache on cachable responses where response
557
+ * Only full page cache on cachable responses where response
556
558
  *
557
559
  * - have content to cache
558
560
  * - have status 200
559
561
  * - does not have no-store on cache-control header
560
562
  * - does not have set-cookie header
561
- * - is not a POST request
563
+ * - is a GET request
562
564
  * - does not have a session or does not have an active customer access token
563
565
  */
564
566
  if (cache &&
@@ -566,7 +568,7 @@ async function cacheResponse(response, request, chunks, revalidate) {
566
568
  response.status === 200 &&
567
569
  response.cache().mode !== NO_STORE &&
568
570
  !response.headers.has('Set-Cookie') &&
569
- !/post/i.test(request.method) &&
571
+ /get/i.test(request.method) &&
570
572
  !sessionHasCustomerAccessToken(request)) {
571
573
  if (revalidate) {
572
574
  await saveCacheResponse(response, request, chunks);
@@ -593,10 +595,9 @@ async function saveCacheResponse(response, request, chunks) {
593
595
  const cache = getCache();
594
596
  if (cache && chunks.length > 0) {
595
597
  const { headers, status, statusText } = getResponseOptions(response);
596
- const url = new URL(request.url);
597
598
  headers.set('cache-control', response.cacheControlHeader);
598
599
  const currentHeader = headers.get('Content-Type');
599
- if (!currentHeader && url.pathname !== RSC_PATHNAME) {
600
+ if (!currentHeader && !request.isRscRequest()) {
600
601
  headers.set('Content-Type', HTML_CONTENT_TYPE);
601
602
  }
602
603
  await setItemInCache(request.cacheKey(), new Response(chunks.join(''), {
@@ -18,7 +18,7 @@ export function PerformanceMetrics() {
18
18
  const initTime = new Date().getTime();
19
19
  ClientAnalytics.publish(ClientAnalytics.eventNames.PERFORMANCE, true, data);
20
20
  const pageData = ClientAnalytics.getPageAnalyticsData();
21
- const shopId = pageData.shopify.shopId || '';
21
+ const shopId = pageData.shopify?.shopId;
22
22
  fetch('https://monorail-edge.shopifysvc.com/v1/produce', {
23
23
  method: 'post',
24
24
  headers: {
@@ -28,7 +28,9 @@ export function PerformanceMetrics() {
28
28
  schema_id: 'hydrogen_buyer_performance/2.0',
29
29
  payload: {
30
30
  ...data,
31
- shop_id: shopId.substring(shopId.lastIndexOf('/') + 1) || '',
31
+ shop_id: shopId
32
+ ? shopId.substring(shopId.lastIndexOf('/') + 1)
33
+ : '',
32
34
  },
33
35
  metadata: {
34
36
  event_created_at_ms: initTime,
@@ -1,8 +1,8 @@
1
1
  import { useEffect } from 'react';
2
2
  import { parse, stringify } from 'worktop/cookie';
3
+ import { SHOPIFY_Y, SHOPIFY_S } from '../../../../constants';
3
4
  import { ClientAnalytics } from '../../ClientAnalytics';
4
5
  import { buildUUID, addDataIf } from './utils';
5
- import { SHOPIFY_S, SHOPIFY_Y } from './const';
6
6
  const longTermLength = 60 * 60 * 24 * 360 * 2; // ~2 year expiry
7
7
  const shortTermLength = 60 * 30; // 30 mins
8
8
  const myShopifyDomain = 'myshopify.com';
@@ -52,7 +52,7 @@ function updateCookie(cookieName, value, maxage, cookieDomain) {
52
52
  const cookieString = stringify(cookieName, value, {
53
53
  maxage,
54
54
  domain: getCookieDomain(cookieDomain),
55
- secure: process.env.NODE_ENV === 'production',
55
+ secure: import.meta.env.PROD,
56
56
  samesite: 'Lax',
57
57
  path: '/',
58
58
  });
@@ -4,11 +4,11 @@ import AnalyticsErrorBoundary from '../../../AnalyticsErrorBoundary.client';
4
4
  import { useServerRequest } from '../../../ServerRequestProvider';
5
5
  import { useServerAnalytics } from '../../hook';
6
6
  import { useShop } from '../../../useShop';
7
- import { SHOPIFY_S, SHOPIFY_Y } from './const';
8
7
  import { ShopifyAnalyticsClient } from './ShopifyAnalytics.client';
9
8
  import { useShopQuery } from '../../../../hooks/useShopQuery';
10
9
  import { CacheLong } from '../../../Cache/strategies';
11
10
  import { gql } from '../../../../utilities/graphql-tag';
11
+ import { SHOPIFY_Y, SHOPIFY_S } from '../../../../constants';
12
12
  export function ShopifyAnalytics({ cookieDomain }) {
13
13
  const { storeDomain } = useShop();
14
14
  const request = useServerRequest();
@@ -1,5 +1,3 @@
1
- export declare const SHOPIFY_Y = "_shopify_y";
2
- export declare const SHOPIFY_S = "_shopify_s";
3
1
  export declare const ShopifyAnalyticsConstants: {
4
2
  pageType: {
5
3
  article: string;
@@ -1,5 +1,3 @@
1
- export const SHOPIFY_Y = '_shopify_y';
2
- export const SHOPIFY_S = '_shopify_s';
3
1
  // Shopify analytics constants
4
2
  const article = 'article';
5
3
  const blog = 'blog';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  export function Heading({ linkText, url, children, }) {
3
- return (React.createElement("span", { style: { display: 'flex', alignItems: 'baseline', padding: '0 0 0.5em' } },
3
+ return (React.createElement("span", { style: { display: 'flex', alignItems: 'baseline', padding: '0 0 0em' } },
4
4
  React.createElement("span", { style: { paddingRight: '0em', flex: 1, fontWeight: 'bold' } },
5
5
  children,
6
6
  ' '),
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
2
2
  import { ClientAnalytics } from '../../Analytics';
3
3
  import { Performance } from './Performance.client';
4
4
  import { Settings } from './Settings.client';
5
+ const isComponentPanel = (panel) => panel.component !== undefined;
5
6
  export function Panels({ settings }) {
6
7
  const [selectedPanel, setSelectedPanel] = useState(0);
7
8
  const [navigations, setNavigations] = useState([]);
@@ -22,36 +23,41 @@ export function Panels({ settings }) {
22
23
  });
23
24
  }, [setNavigations, navigations]);
24
25
  const panels = getPanels({ settings, performance: { navigations } });
25
- const panelComponents = panels.map((obj, index) => (React.createElement("div", { key: obj.content, style: { display: selectedPanel === index ? 'block' : 'none' } }, obj.panel)));
26
+ const panelComponents = panels.map((obj, index) => isComponentPanel(obj) ? (React.createElement("div", { key: obj.content, style: { display: selectedPanel === index ? 'block' : 'none' } }, obj.component)) : null);
26
27
  return (React.createElement("div", { style: { display: 'flex', height: '100%' } },
27
- React.createElement("div", { style: { borderRight: '1px solid', padding: '1em 0em' } }, panels.map(({ content, icon, id }, index) => {
28
+ React.createElement("div", { style: { borderRight: '1px solid', padding: '1em 0em' } }, panels.map((panel, index) => {
28
29
  const active = selectedPanel === index;
29
- return (React.createElement("button", { key: id, type: "button", style: {
30
- lineHeight: 2,
31
- padding: '0em 1.25em',
32
- fontWeight: active ? 'bold' : 'normal',
33
- display: 'flex',
34
- alignItems: 'center',
35
- }, onClick: () => setSelectedPanel(index) },
36
- React.createElement("span", { style: { paddingRight: '0.4em' } }, icon),
37
- React.createElement("span", { style: { fontFamily: 'monospace' } }, content)));
30
+ const style = {
31
+ padding: '0em 1.25em',
32
+ fontWeight: 'bold',
33
+ textDecoration: active ? 'underline' : 'none',
34
+ display: 'flex',
35
+ justifyContent: 'space-between',
36
+ alignItems: 'center',
37
+ };
38
+ if (isComponentPanel(panel)) {
39
+ return (React.createElement("button", { key: panel.id, type: "button", style: style, onClick: () => setSelectedPanel(index) },
40
+ React.createElement("span", null, panel.content)));
41
+ }
42
+ return (React.createElement("a", { style: style, target: "_blank", rel: "noreferrer", href: panel.url, key: panel.url },
43
+ panel.content,
44
+ React.createElement("span", null, "\u2197")));
38
45
  })),
39
- React.createElement("div", { style: { padding: '1.25em', width: '100%' } }, panelComponents[selectedPanel ? selectedPanel : 0])));
40
- }
41
- function Panel({ children }) {
42
- return React.createElement("div", null, children);
46
+ React.createElement("div", { style: { padding: '1em', width: '100%' } }, panelComponents[selectedPanel ? selectedPanel : 0])));
43
47
  }
44
48
  function getPanels({ settings, performance }) {
45
49
  const panels = {
46
50
  settings: {
47
51
  content: 'Settings',
48
- panel: React.createElement(Settings, { ...settings }),
49
- icon: '🎛',
52
+ component: React.createElement(Settings, { ...settings }),
50
53
  },
51
54
  performance: {
52
55
  content: 'Performance',
53
- panel: React.createElement(Performance, { ...performance }),
54
- icon: '⏱',
56
+ component: React.createElement(Performance, { ...performance }),
57
+ },
58
+ graphiql: {
59
+ content: 'GraphiQL',
60
+ url: '/___graphql',
55
61
  },
56
62
  };
57
63
  return Object.keys(panels).map((key) => {
@@ -19,7 +19,6 @@ const Item = ({ label, value, unit }) => {
19
19
  return (React.createElement("span", { style: {
20
20
  fontFamily: 'monospace',
21
21
  padding: '0 2em 0 0',
22
- fontSize: '0.75em',
23
22
  } },
24
23
  label && label.padEnd(10),
25
24
  val));
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import { Heading } from './Heading';
3
2
  import { Table } from './Table';
4
3
  const KEY_MAP = {
5
4
  locale: 'Locale',
@@ -14,7 +13,5 @@ export function Settings(props) {
14
13
  type: typeof value,
15
14
  };
16
15
  });
17
- return (React.createElement(React.Fragment, null,
18
- React.createElement(Heading, null, "Config"),
19
- React.createElement(Table, { items: items })));
16
+ return React.createElement(Table, { items: items });
20
17
  }
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  export function Table({ items }) {
3
- const itemsMarkup = items.map(({ key, value }) => (React.createElement("div", { key: key, style: { display: 'flex' } },
4
- React.createElement("span", { style: { width: '30%', fontFamily: 'monospace', paddingRight: '1em' } }, key),
5
- React.createElement("span", { style: { width: '70%', fontFamily: 'monospace', fontWeight: 'bold' } }, value))));
3
+ const itemsMarkup = items.map(({ key, value }) => (React.createElement("div", { key: key, style: { display: 'flex', paddingBottom: '1em', flexDirection: 'column' } },
4
+ React.createElement("span", { style: { fontWeight: 'bold' } }, key),
5
+ React.createElement("span", { style: { width: '70%', fontFamily: 'monospace' } }, value))));
6
6
  return React.createElement("ul", null, itemsMarkup);
7
7
  }
@@ -1,10 +1,9 @@
1
- import type { ShopifyContextValue } from '../ShopifyProvider/types';
1
+ import type { ShopifyContextValue, LocalizationContextValue } from '../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
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';
8
7
  export declare type PreloadQueryEntry = {
9
8
  key: QueryKey;
10
9
  fetcher: (request: HydrogenRequest) => Promise<unknown>;
@@ -43,9 +43,7 @@ export class HydrogenRequest extends Request {
43
43
  }
44
44
  this.time = getTime();
45
45
  this.id = generateId();
46
- this.normalizedUrl = this.isRscRequest()
47
- ? normalizeUrl(this.url)
48
- : this.url;
46
+ this.normalizedUrl = decodeURIComponent(this.isRscRequest() ? normalizeUrl(this.url) : this.url);
49
47
  this.ctx = {
50
48
  cache: new Map(),
51
49
  head: new HeadData({}),
@@ -130,7 +128,7 @@ export class HydrogenRequest extends Request {
130
128
  }
131
129
  async formData() {
132
130
  // @ts-ignore
133
- if (!__HYDROGEN_WORKER__ && super.formData)
131
+ if (__HYDROGEN_WORKER__ || super.formData)
134
132
  return super.formData();
135
133
  const contentType = this.headers.get('Content-Type') || '';
136
134
  // If mimeType’s essence is "multipart/form-data", then:
@@ -13,12 +13,12 @@ export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc,
13
13
  setServerPropsForRsc((prev) => getNewValue(prev, input, propValue));
14
14
  });
15
15
  }, [setServerProps, setServerPropsForRsc]);
16
- const setLocationServerPropsCallback = useCallback((input, propValue) => {
16
+ const setLocationServerPropsCallback = useCallback((input) => {
17
17
  // Flush the existing user server state when location changes, leaving only the persisted state
18
18
  startTransition(() => {
19
- setServerPropsForRsc((prev) => getNewValue(prev, input, propValue));
19
+ setServerPropsForRsc(input);
20
20
  setServerProps({});
21
- setLocationServerProps((prev) => getNewValue(prev, input, propValue));
21
+ setLocationServerProps(input);
22
22
  });
23
23
  }, [setServerProps, setServerPropsForRsc, setLocationServerProps]);
24
24
  const getProposedLocationServerPropsCallback = useCallback((input, propValue) => {
@@ -1,2 +1 @@
1
- export { ServerPropsProvider, ServerPropsContext } from './ServerPropsProvider';
2
- export type { ServerProps, ServerPropsContextValue } from './ServerPropsProvider';
1
+ export { ServerPropsProvider, ServerPropsContext, type ServerProps, type ServerPropsContextValue, } from './ServerPropsProvider';
@@ -1 +1 @@
1
- export { ServerPropsProvider, ServerPropsContext } from './ServerPropsProvider';
1
+ export { ServerPropsProvider, ServerPropsContext, } from './ServerPropsProvider';
@@ -1,7 +1,9 @@
1
- import type { ShopifyContextValue } from './types';
1
+ import type { ShopifyContextValue, LocalizationContextValue } from './types';
2
2
  import React, { ReactNode } from 'react';
3
3
  export declare const ShopifyContext: React.Context<ShopifyContextValue | null>;
4
- export declare function ShopifyProviderClient({ children, shopifyConfig, }: {
4
+ export declare const LocalizationContext: React.Context<LocalizationContextValue | null>;
5
+ export declare function ShopifyProviderClient({ children, shopifyConfig, localization, }: {
5
6
  children: ReactNode;
6
7
  shopifyConfig: ShopifyContextValue;
8
+ localization: LocalizationContextValue;
7
9
  }): JSX.Element;