@shopify/hydrogen 0.26.0 → 0.27.1

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 (52) hide show
  1. package/CHANGELOG.md +61 -5
  2. package/dist/esnext/components/Link/Link.client.js +7 -4
  3. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +1 -2
  4. package/dist/esnext/components/Money/Money.client.d.ts +1 -1
  5. package/dist/esnext/components/Money/Money.client.js +1 -1
  6. package/dist/esnext/entry-server.js +19 -6
  7. package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.js +31 -16
  8. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetrics.client.js +20 -3
  9. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/ServerAnalyticsConnector.d.ts +1 -1
  10. package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/ServerAnalyticsConnector.js +3 -23
  11. package/dist/esnext/foundation/Analytics/connectors/Shopify/ServerAnalyticsConnector.d.ts +1 -1
  12. package/dist/esnext/foundation/Analytics/connectors/Shopify/ServerAnalyticsConnector.js +3 -17
  13. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +4 -12
  14. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server.js +20 -0
  15. package/dist/esnext/foundation/DevTools/DevTools.server.js +2 -2
  16. package/dist/esnext/foundation/Router/BrowserRouter.client.d.ts +1 -1
  17. package/dist/esnext/foundation/Router/BrowserRouter.client.js +7 -5
  18. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +5 -5
  19. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +3 -3
  20. package/dist/esnext/foundation/constants.d.ts +2 -1
  21. package/dist/esnext/foundation/constants.js +2 -1
  22. package/dist/esnext/foundation/useNavigate/useNavigate.js +8 -2
  23. package/dist/esnext/foundation/useUrl/useUrl.js +5 -3
  24. package/dist/esnext/framework/load-config.d.ts +0 -1
  25. package/dist/esnext/framework/load-config.js +1 -4
  26. package/dist/esnext/framework/plugin.js +1 -1
  27. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +2 -2
  28. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +2 -1
  29. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -1
  30. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-virtual-files.js +4 -21
  31. package/dist/esnext/framework/types.d.ts +1 -0
  32. package/dist/esnext/hooks/useMoney/hooks.d.ts +1 -1
  33. package/dist/esnext/shared-types.d.ts +2 -1
  34. package/dist/esnext/types.d.ts +1 -1
  35. package/dist/esnext/utilities/image_size.js +11 -0
  36. package/dist/esnext/version.d.ts +1 -1
  37. package/dist/esnext/version.js +1 -1
  38. package/dist/node/framework/load-config.d.ts +0 -1
  39. package/dist/node/framework/load-config.js +1 -4
  40. package/dist/node/framework/plugin.js +1 -1
  41. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +2 -2
  42. package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +2 -1
  43. package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -1
  44. package/dist/node/framework/plugins/vite-plugin-hydrogen-virtual-files.js +4 -21
  45. package/dist/node/framework/types.d.ts +1 -0
  46. package/dist/node/shared-types.d.ts +2 -1
  47. package/package.json +1 -1
  48. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +26 -12
  49. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite.development.js +18 -3
  50. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite.production.min.js +2 -2
  51. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +26 -12
  52. package/vendor/react-server-dom-vite/esm/react-server-dom-vite.js +18 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.27.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1728](https://github.com/Shopify/hydrogen/pull/1728) [`e8d4980a`](https://github.com/Shopify/hydrogen/commit/e8d4980a691c68e71c56762596a59415f59600c6) Thanks [@jplhomer](https://github.com/jplhomer)! - This is an example of backporting a fix. You can ignore this version.
8
+
9
+ ## 0.27.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#1697](https://github.com/Shopify/hydrogen/pull/1697) [`85aab092`](https://github.com/Shopify/hydrogen/commit/85aab092b2f47d77bb917659918a011783cd8c34) Thanks [@blittle](https://github.com/blittle)! - Remove `defaultLocale` from the Hydrogen Config and instead add `defaultCountryCode` and `defaultLanguageCode`. Both of which are also now available by the `useShop()` hook:
14
+
15
+ ```diff
16
+ export default defineConfig({
17
+ shopify: {
18
+ - defaultLocale: 'EN-US',
19
+ + defaultCountryCode: 'US',
20
+ + defaultLanguageCode: 'EN',
21
+ storeDomain: 'hydrogen-preview.myshopify.com',
22
+ storefrontToken: '3b580e70970c4528da70c98e097c2fa0',
23
+ storefrontApiVersion: '2022-07',
24
+ },
25
+ }
26
+ ```
27
+
28
+ * [#1662](https://github.com/Shopify/hydrogen/pull/1662) [`4262b319`](https://github.com/Shopify/hydrogen/commit/4262b3196afb96415d3b0f8f874f351030e6a734) Thanks [@wizardlyhel](https://github.com/wizardlyhel)! - Fix server analytics route
29
+
30
+ - Fix ServerAnalyticsRoute so that it does complete all async work
31
+ - Move Performance and Shopify analytic reporting to client side
32
+ - Make sure `ShopifyAnalytics` make its own query for shop id and currency
33
+ - Remove query for shop id and currency from `DefaultSeo` component
34
+ - Make Performance and Shopify server analytics connector do nothing
35
+
36
+ ### Deprecated components
37
+
38
+ Remove the following components from `hydrogen.config.js`
39
+
40
+ - `PerformanceMetricsServerAnalyticsConnector`
41
+ - `ShopifyServerAnalyticsConnector`
42
+
43
+ ## 0.26.1
44
+
45
+ ### Patch Changes
46
+
47
+ - [#1663](https://github.com/Shopify/hydrogen/pull/1663) [`66200d6b`](https://github.com/Shopify/hydrogen/commit/66200d6b7d8e54b0746a048e950f067d4b8e0609) Thanks [@jplhomer](https://github.com/jplhomer)! - Default to 'US' CountryCode if locale cannot be parsed correctly
48
+
49
+ * [#1690](https://github.com/Shopify/hydrogen/pull/1690) [`afde8989`](https://github.com/Shopify/hydrogen/commit/afde8989ae03e842de65ac698ab86033e56e7ee2) Thanks [@frehner](https://github.com/frehner)! - Add scale to the filename part of the url in `shopifyImageLoader()`
50
+
51
+ - [#1676](https://github.com/Shopify/hydrogen/pull/1676) [`0149cbf6`](https://github.com/Shopify/hydrogen/commit/0149cbf60b331461ae0c97bb3e18d3f27e143d0a) Thanks [@frandiox](https://github.com/frandiox)! - Avoid writing to Node response if it has been closed early.
52
+
53
+ * [#1674](https://github.com/Shopify/hydrogen/pull/1674) [`8068d3ce`](https://github.com/Shopify/hydrogen/commit/8068d3ce14f44ea83bde8f3729ae2a8cbbf8a52e) Thanks [@frandiox](https://github.com/frandiox)! - Throw error when `<Link>` component is used outside of `<Router>` component.
54
+
55
+ - [#1680](https://github.com/Shopify/hydrogen/pull/1680) [`acf5223f`](https://github.com/Shopify/hydrogen/commit/acf5223fe34cfdd483ae9b0e714445c8cbf11a9d) Thanks [@blittle](https://github.com/blittle)! - Fix basepath to not apply to external URLs in the `<Link` component. Also default the attribute `rel="noreferrer noopener` for external URLs.
56
+
57
+ * [#1670](https://github.com/Shopify/hydrogen/pull/1670) [`8b26f7a6`](https://github.com/Shopify/hydrogen/commit/8b26f7a6f034eaa36bb11974ff3dc5d992e2e97b) Thanks [@frandiox](https://github.com/frandiox)! - Optimize client boundaries only during build by default.
58
+
3
59
  ## 0.26.0
4
60
 
5
61
  ### Minor Changes
@@ -1525,11 +1581,11 @@
1525
1581
 
1526
1582
  `queryShop` accepts a single argument object with the following properties:
1527
1583
 
1528
- | Property | Type | Required |
1529
- | ----------- | -------------------------------------- | -------- |
1530
- | `query` | `string \| ASTNode` | Yes |
1531
- | `variables` | `Record<string, any>` | No |
1532
- | `locale` | `string` (defaults to `defaultLocale`) | No |
1584
+ | Property | Type | Required |
1585
+ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
1586
+ | `query` | `string \| ASTNode` | Yes |
1587
+ | `variables` | `Record<string, any>` | No |
1588
+ | `locale` | `string`. Defaults to the locale value from the [LocalizationProvider](https://shopify.dev/api/hydrogen/components/localization/localizationprovider) component. | No |
1533
1589
 
1534
1590
  **Important**: In order to use `queryShop`, you should pass `shopifyConfig` to `renderHydrogen` inside `App.server.jsx`:
1535
1591
 
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
- import { useRouter } from '../../foundation/Router/BrowserRouter.client';
2
+ import { useLocation } from '../../foundation/Router/BrowserRouter.client';
3
3
  import { createPath } from 'history';
4
4
  import { buildPath, useNavigate } from '../../foundation/useNavigate/useNavigate';
5
5
  import { RSC_PATHNAME } from '../../constants';
@@ -12,7 +12,7 @@ import { useBasePath } from '../../foundation/useRouteParams/RouteParamsProvider
12
12
  */
13
13
  export const Link = React.forwardRef(function Link(props, ref) {
14
14
  const navigate = useNavigate();
15
- const { location } = useRouter();
15
+ const location = useLocation();
16
16
  const [_, startTransition] = React.useTransition();
17
17
  const routeBasePath = useBasePath();
18
18
  /**
@@ -98,12 +98,15 @@ export const Link = React.forwardRef(function Link(props, ref) {
98
98
  'reloadDocument',
99
99
  'prefetch',
100
100
  'scroll',
101
- ]), ref: ref, onClick: internalClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, onTouchStart: onTouchStart, href: to }, props.children),
101
+ ]), ref: ref, rel: props.rel ??
102
+ (to.startsWith('http') || to.startsWith('//')
103
+ ? 'noreferrer noopener'
104
+ : undefined), onClick: internalClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, onTouchStart: onTouchStart, href: to }, props.children),
102
105
  shouldPrefetch && React.createElement(Prefetch, { pathname: to })));
103
106
  });
104
107
  function Prefetch({ pathname }) {
105
108
  const { getProposedLocationServerProps } = useInternalServerProps();
106
- const { location } = useRouter();
109
+ const location = useLocation();
107
110
  const newPath = createPath({ pathname });
108
111
  if (pathname.startsWith('http') || newPath === createPath(location)) {
109
112
  return null;
@@ -10,8 +10,7 @@ import { useServerRequest } from '../../foundation/ServerRequestProvider';
10
10
  * Any descendents of this provider can use the `useLocalization` hook.
11
11
  */
12
12
  export function LocalizationProvider(props) {
13
- const { languageCode: defaultLanguageCode, locale } = useShop();
14
- const defaultCountryCode = locale.split(/[-_]/)[1] || '';
13
+ const { defaultLanguageCode, defaultCountryCode } = useShop();
15
14
  const languageCode = (props.languageCode ?? defaultLanguageCode).toUpperCase();
16
15
  const countryCode = (props.countryCode ?? defaultCountryCode).toUpperCase();
17
16
  const request = useServerRequest();
@@ -19,7 +19,7 @@ declare type MoneyProps<ComponentGeneric extends React.ElementType> = CustomProp
19
19
  /**
20
20
  * The `Money` component renders a string of the Storefront API's
21
21
  * [MoneyV2 object](https://shopify.dev/api/storefront/reference/common-objects/moneyv2) according to the
22
- * `defaultLocale` in [the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
22
+ * `locale` in [the `LocalizationProvider` component](https://shopify.dev/api/hydrogen/components/localization/localizationprovider).
23
23
  */
24
24
  export declare function Money<TTag extends React.ElementType>({ data, as, withoutCurrency, withoutTrailingZeros, measurement, measurementSeparator, ...passthroughProps }: MoneyProps<TTag>): JSX.Element;
25
25
  export {};
@@ -3,7 +3,7 @@ import { useMoney } from '../../hooks';
3
3
  /**
4
4
  * The `Money` component renders a string of the Storefront API's
5
5
  * [MoneyV2 object](https://shopify.dev/api/storefront/reference/common-objects/moneyv2) according to the
6
- * `defaultLocale` in [the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
6
+ * `locale` in [the `LocalizationProvider` component](https://shopify.dev/api/hydrogen/components/localization/localizationprovider).
7
7
  */
8
8
  export function Money({ data, as, withoutCurrency, withoutTrailingZeros, measurement, measurementSeparator = '/', ...passthroughProps }) {
9
9
  if (!isMoney(data)) {
@@ -337,11 +337,13 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
337
337
  startWritingToNodeResponse(nodeResponse, dev ? didError() : undefined);
338
338
  setTimeout(() => {
339
339
  log.trace('node pipe response');
340
- pipe(nodeResponse);
340
+ if (!nodeResponse.writableEnded)
341
+ pipe(nodeResponse);
341
342
  }, 0);
342
343
  bufferReadableStream(rscReadable.getReader(), (chunk) => {
343
344
  log.trace('rsc chunk');
344
- return nodeResponse.write(chunk);
345
+ if (!nodeResponse.writableEnded)
346
+ nodeResponse.write(chunk);
345
347
  });
346
348
  },
347
349
  async onAllReady() {
@@ -370,8 +372,10 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
370
372
  html = assembleHtml({ ssrHtml, rscPayload, request, template });
371
373
  postRequestTasks('ssr', nodeResponse.statusCode, request, response);
372
374
  }
373
- nodeResponse.write(html);
374
- nodeResponse.end();
375
+ if (!nodeResponse.writableEnded) {
376
+ nodeResponse.write(html);
377
+ nodeResponse.end();
378
+ }
375
379
  });
376
380
  pipe(bufferedResponse);
377
381
  },
@@ -385,8 +389,15 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
385
389
  }
386
390
  },
387
391
  onError(error) {
392
+ if (error.message?.includes('stream closed early')) {
393
+ // This seems to happen when Fizz is still streaming
394
+ // but nodeResponse has been closed by the browser.
395
+ // This is common in tests and during development
396
+ // due to frequent page refresh.
397
+ return;
398
+ }
388
399
  ssrDidError = error;
389
- if (dev && nodeResponse.headersSent) {
400
+ if (dev && nodeResponse.headersSent && !nodeResponse.writableEnded) {
390
401
  // Calling write would flush headers automatically.
391
402
  // Delay this error until headers are properly sent.
392
403
  nodeResponse.write(getErrorMarkup(error));
@@ -420,6 +431,8 @@ function runRSC({ App, state, log, request, response }) {
420
431
  }
421
432
  export default renderHydrogen;
422
433
  function startWritingToNodeResponse(nodeResponse, error) {
434
+ if (nodeResponse.writableEnded)
435
+ return;
423
436
  if (!nodeResponse.headersSent) {
424
437
  nodeResponse.setHeader(CONTENT_TYPE, HTML_CONTENT_TYPE);
425
438
  nodeResponse.write(DOCTYPE);
@@ -483,7 +496,7 @@ function postRequestTasks(type, status, request, response) {
483
496
  function handleFetchResponseInNode(fetchResponsePromise, nodeResponse) {
484
497
  if (nodeResponse) {
485
498
  fetchResponsePromise.then((response) => {
486
- if (!response)
499
+ if (!response || nodeResponse.writableEnded)
487
500
  return;
488
501
  setNodeHeaders(response.headers, nodeResponse);
489
502
  nodeResponse.statusCode = response.status;
@@ -1,36 +1,51 @@
1
1
  import { log } from '../../utilities/log';
2
+ const analyticsDefaultResponse = new Response(null, {
3
+ status: 200,
4
+ });
2
5
  export async function ServerAnalyticsRoute(request, { hydrogenConfig }) {
6
+ const serverAnalyticsConnectors = hydrogenConfig.serverAnalyticsConnectors;
7
+ if (!serverAnalyticsConnectors) {
8
+ return analyticsDefaultResponse;
9
+ }
3
10
  const requestHeader = request.headers;
4
11
  const requestUrl = request.url;
5
- const serverAnalyticsConnectors = hydrogenConfig.serverAnalyticsConnectors;
12
+ let analyticsPromise;
6
13
  if (requestHeader.get('Content-Length') === '0') {
7
- serverAnalyticsConnectors?.forEach((connector) => {
8
- connector.request(requestUrl, request.headers);
14
+ analyticsPromise = Promise.resolve(true)
15
+ .then(async () => {
16
+ return Promise.all(serverAnalyticsConnectors.map(async (connector) => {
17
+ return await connector.request(requestUrl, requestHeader);
18
+ }));
19
+ })
20
+ .catch((error) => {
21
+ log.warn('Failed to resolve server analytics (no content length): ', error);
9
22
  });
10
23
  }
11
24
  else if (requestHeader.get('Content-Type') === 'application/json') {
12
- Promise.resolve(request.json())
25
+ analyticsPromise = Promise.resolve(request.json())
13
26
  .then((data) => {
14
- serverAnalyticsConnectors?.forEach((connector) => {
15
- connector.request(requestUrl, requestHeader, data, 'json');
16
- });
27
+ return Promise.all(serverAnalyticsConnectors.map(async (connector) => {
28
+ return await connector.request(requestUrl, requestHeader, data, 'json');
29
+ }));
17
30
  })
18
31
  .catch((error) => {
19
- log.warn('Fail to resolve server analytics: ', error);
32
+ log.warn('Fail to resolve server analytics (json): ', error);
20
33
  });
21
34
  }
22
35
  else {
23
- Promise.resolve(request.text())
24
- .then((data) => {
25
- serverAnalyticsConnectors?.forEach((connector) => {
26
- connector.request(requestUrl, requestHeader, data, 'text');
36
+ analyticsPromise = Promise.resolve(request.text())
37
+ .then(async (data) => {
38
+ await serverAnalyticsConnectors.forEach(async (connector) => {
39
+ await connector.request(requestUrl, requestHeader, data, 'text');
27
40
  });
41
+ return Promise.all(serverAnalyticsConnectors.map(async (connector) => {
42
+ return await connector.request(requestUrl, requestHeader, data, 'text');
43
+ }));
28
44
  })
29
45
  .catch((error) => {
30
- log.warn('Fail to resolve server analytics: ', error);
46
+ log.warn('Failed to resolve server analytics (text): ', error);
31
47
  });
32
48
  }
33
- return new Response(null, {
34
- status: 200,
35
- });
49
+ await analyticsPromise;
50
+ return analyticsDefaultResponse;
36
51
  }
@@ -15,10 +15,27 @@ export function PerformanceMetrics() {
15
15
  // Executes only on first mount
16
16
  window.BOOMR = window.BOOMR || {};
17
17
  window.BOOMR.hydrogenPerformanceEvent = (data) => {
18
+ const initTime = new Date().getTime();
18
19
  ClientAnalytics.publish(ClientAnalytics.eventNames.PERFORMANCE, true, data);
19
- ClientAnalytics.pushToServer({
20
- body: JSON.stringify(data),
21
- }, ClientAnalytics.eventNames.PERFORMANCE);
20
+ const pageData = ClientAnalytics.getPageAnalyticsData();
21
+ const shopId = pageData.shopify.shopId || '';
22
+ fetch('https://monorail-edge.shopifysvc.com/v1/produce', {
23
+ method: 'post',
24
+ headers: {
25
+ 'content-type': 'text/plain',
26
+ },
27
+ body: JSON.stringify({
28
+ schema_id: 'hydrogen_buyer_performance/2.0',
29
+ payload: {
30
+ ...data,
31
+ shop_id: shopId.substring(shopId.lastIndexOf('/') + 1) || '',
32
+ },
33
+ metadata: {
34
+ event_created_at_ms: initTime,
35
+ event_sent_at_ms: new Date().getTime(),
36
+ },
37
+ }),
38
+ });
22
39
  };
23
40
  window.BOOMR.storeDomain = storeDomain;
24
41
  function boomerangSaveLoadTime(e) {
@@ -1,3 +1,3 @@
1
1
  export declare const PerformanceMetricsServerAnalyticsConnector: {
2
- request(requestUrl: string, requestHeader: Headers, data?: any, contentType?: string): void;
2
+ request(): Promise<any>;
3
3
  };
@@ -1,27 +1,7 @@
1
1
  import { log } from '../../../../utilities/log';
2
2
  export const PerformanceMetricsServerAnalyticsConnector = {
3
- request(requestUrl, requestHeader, data, contentType) {
4
- const url = new URL(requestUrl);
5
- if (url.search === '?performance' && contentType === 'json') {
6
- const initTime = new Date().getTime();
7
- fetch('https://monorail-edge.shopifysvc.com/v1/produce', {
8
- method: 'post',
9
- headers: {
10
- 'content-type': 'text/plain',
11
- 'x-forwarded-for': requestHeader.get('x-forwarded-for') || '',
12
- 'user-agent': requestHeader.get('user-agent') || '',
13
- },
14
- body: JSON.stringify({
15
- schema_id: 'hydrogen_buyer_performance/2.0',
16
- payload: data,
17
- metadata: {
18
- event_created_at_ms: initTime,
19
- event_sent_at_ms: new Date().getTime(),
20
- },
21
- }),
22
- }).catch((err) => {
23
- log.error(err);
24
- });
25
- }
3
+ request() {
4
+ log.warn('PerformanceMetricsServerAnalyticsConnector has been removed - please remove its reference from hydrogen.config.js');
5
+ return Promise.resolve();
26
6
  },
27
7
  };
@@ -1,3 +1,3 @@
1
1
  export declare const ShopifyServerAnalyticsConnector: {
2
- request(requestUrl: string, requestHeader: Headers, data?: any, contentType?: string): void;
2
+ request(): Promise<any>;
3
3
  };
@@ -1,21 +1,7 @@
1
1
  import { log } from '../../../../utilities/log';
2
2
  export const ShopifyServerAnalyticsConnector = {
3
- request(requestUrl, requestHeader, data, contentType) {
4
- const url = new URL(requestUrl);
5
- if (url.search === '?shopify' && contentType === 'json') {
6
- data.events.forEach((event) => {
7
- event.payload.client_ip_address = requestHeader.get('x-forwarded-for');
8
- event.payload.client_user_agent = requestHeader.get('user-agent');
9
- });
10
- fetch('https://monorail-edge.shopifysvc.com/unstable/produce_batch', {
11
- method: 'post',
12
- headers: {
13
- 'content-type': 'text/plain',
14
- },
15
- body: JSON.stringify(data),
16
- }).catch((err) => {
17
- log.error(err);
18
- });
19
- }
3
+ request() {
4
+ log.warn('ShopifyServerAnalyticsConnector has been removed - please remove its reference from hydrogen.config.js');
5
+ return Promise.resolve();
20
6
  },
21
7
  };
@@ -165,19 +165,8 @@ function sendToServer(data) {
165
165
  };
166
166
  batchedData = [];
167
167
  batchedTimeout = null;
168
- // Send to server
168
+ // Send to Shopify
169
169
  try {
170
- fetch('/__event?shopify', {
171
- method: 'post',
172
- headers: {
173
- 'cache-control': 'no-cache',
174
- 'Content-Type': 'application/json',
175
- },
176
- body: JSON.stringify(batchedDataToBeSent),
177
- });
178
- }
179
- catch (error) {
180
- // Fallback to client-side
181
170
  fetch('https://monorail-edge.shopifysvc.com/unstable/produce_batch', {
182
171
  method: 'post',
183
172
  headers: {
@@ -186,5 +175,8 @@ function sendToServer(data) {
186
175
  body: JSON.stringify(batchedDataToBeSent),
187
176
  });
188
177
  }
178
+ catch (error) {
179
+ // Do nothing
180
+ }
189
181
  }, BATCH_SENT_TIMEOUT);
190
182
  }
@@ -6,13 +6,23 @@ import { useServerAnalytics } from '../../hook';
6
6
  import { useShop } from '../../../useShop';
7
7
  import { SHOPIFY_S, SHOPIFY_Y } from './const';
8
8
  import { ShopifyAnalyticsClient } from './ShopifyAnalytics.client';
9
+ import { useShopQuery } from '../../../../hooks/useShopQuery';
10
+ import { CacheLong } from '../../../Cache/strategies';
11
+ import { gql } from '../../../../utilities/graphql-tag';
9
12
  export function ShopifyAnalytics({ cookieDomain }) {
10
13
  const { storeDomain } = useShop();
11
14
  const request = useServerRequest();
12
15
  const cookies = parse(request.headers.get('Cookie') || '');
13
16
  const domain = cookieDomain || storeDomain;
17
+ const { data: { shop: { id, paymentSettings: { currencyCode }, }, }, } = useShopQuery({
18
+ query: SHOP_QUERY,
19
+ cache: CacheLong(),
20
+ preload: '*',
21
+ });
14
22
  useServerAnalytics({
15
23
  shopify: {
24
+ shopId: id,
25
+ currency: currencyCode,
16
26
  storefrontId: globalThis.Oxygen?.env?.SHOPIFY_STOREFRONT_ID || '0',
17
27
  acceptedLanguage: request.headers.get('Accept-Language')?.replace(/-.*/, '') || 'en',
18
28
  isPersistentCookie: !!cookies[SHOPIFY_S] || !!cookies[SHOPIFY_Y],
@@ -21,3 +31,13 @@ export function ShopifyAnalytics({ cookieDomain }) {
21
31
  return (React.createElement(AnalyticsErrorBoundary, null,
22
32
  React.createElement(ShopifyAnalyticsClient, { cookieDomain: domain })));
23
33
  }
34
+ const SHOP_QUERY = gql `
35
+ query shopAnalyticsInfo {
36
+ shop {
37
+ id
38
+ paymentSettings {
39
+ currencyCode
40
+ }
41
+ }
42
+ }
43
+ `;
@@ -4,9 +4,9 @@ import { useServerRequest } from '../ServerRequestProvider';
4
4
  export function DevTools() {
5
5
  const serverRequest = useServerRequest();
6
6
  const { shopifyConfig } = serverRequest.ctx;
7
- const { locale, storeDomain, storefrontApiVersion } = shopifyConfig || {};
7
+ const { defaultLanguageCode: languageCode, defaultCountryCode: countryCode, storeDomain, storefrontApiVersion, } = shopifyConfig || {};
8
8
  const settings = {
9
- locale,
9
+ locale: `${languageCode}-${countryCode}`,
10
10
  storeDomain,
11
11
  storefrontApiVersion,
12
12
  };
@@ -4,7 +4,7 @@ declare type RouterContextValue = {
4
4
  history: BrowserHistory;
5
5
  location: Location;
6
6
  };
7
- export declare const RouterContext: React.Context<{} | RouterContextValue>;
7
+ export declare const RouterContext: React.Context<RouterContextValue | undefined>;
8
8
  export declare const BrowserRouter: FC<{
9
9
  history?: BrowserHistory;
10
10
  children: ReactNode;
@@ -2,7 +2,7 @@ import { createBrowserHistory } from 'history';
2
2
  import React, { createContext, useContext, useMemo, useState, useEffect, useLayoutEffect, useCallback, } from 'react';
3
3
  import { META_ENV_SSR } from '../ssr-interop';
4
4
  import { useInternalServerProps } from '../useServerProps/use-server-props';
5
- export const RouterContext = createContext({});
5
+ export const RouterContext = createContext(undefined);
6
6
  let isFirstLoad = true;
7
7
  const positions = {};
8
8
  export const BrowserRouter = ({ history: pHistory, children }) => {
@@ -51,11 +51,13 @@ export const BrowserRouter = ({ history: pHistory, children }) => {
51
51
  } }, children));
52
52
  };
53
53
  export function useRouter() {
54
+ if (META_ENV_SSR)
55
+ return { location: {}, history: {} };
56
+ // eslint-disable-next-line react-hooks/rules-of-hooks
54
57
  const router = useContext(RouterContext);
55
- if (!router && META_ENV_SSR) {
56
- throw new Error('useRouter must be used within a <Router> component');
57
- }
58
- return router;
58
+ if (router)
59
+ return router;
60
+ throw new Error('Router hooks and <Link> component must be used within a <Router> component');
59
61
  }
60
62
  export function useLocation() {
61
63
  return useRouter().location;
@@ -1,16 +1,16 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { ShopifyProviderClient } from './ShopifyProvider.client';
3
- import { DEFAULT_LOCALE } from '../constants';
3
+ import { DEFAULT_COUNTRY, DEFAULT_LANGUAGE } from '../constants';
4
4
  import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
5
5
  import { getOxygenVariable } from '../../utilities/storefrontApi';
6
6
  import { SHOPIFY_STOREFRONT_ID_VARIABLE } from '../../constants';
7
7
  function makeShopifyContext(shopifyConfig) {
8
- const locale = shopifyConfig.defaultLocale ?? DEFAULT_LOCALE;
9
- const languageCode = locale.split(/[-_]/)[0];
8
+ const countryCode = shopifyConfig.defaultCountryCode ?? DEFAULT_COUNTRY;
9
+ const languageCode = shopifyConfig.defaultLanguageCode ?? DEFAULT_LANGUAGE;
10
10
  const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
11
11
  return {
12
- locale: locale.toUpperCase(),
13
- languageCode: languageCode.toUpperCase(),
12
+ defaultCountryCode: countryCode.toUpperCase(),
13
+ defaultLanguageCode: languageCode.toUpperCase(),
14
14
  storeDomain: shopifyConfig?.storeDomain?.replace(/^https?:\/\//, ''),
15
15
  storefrontToken: shopifyConfig.storefrontToken,
16
16
  storefrontApiVersion: shopifyConfig.storefrontApiVersion,
@@ -1,9 +1,9 @@
1
1
  import type { CountryCode, LanguageCode } from '../../storefront-api-types';
2
2
  import type { ReactNode } from 'react';
3
3
  import type { ShopifyConfigFetcher, ShopifyConfig } from '../../types';
4
- export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLocale'> {
5
- locale: `${LanguageCode}-${CountryCode}`;
6
- languageCode: `${LanguageCode}`;
4
+ export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLanguageCode' | 'defaultCountryCode'> {
5
+ defaultLanguageCode: `${LanguageCode}`;
6
+ defaultCountryCode: `${CountryCode}`;
7
7
  storefrontId: string | null;
8
8
  }
9
9
  export declare type ShopifyProviderProps = {
@@ -1 +1,2 @@
1
- export declare const DEFAULT_LOCALE = "EN-US";
1
+ export declare const DEFAULT_COUNTRY = "US";
2
+ export declare const DEFAULT_LANGUAGE = "EN";
@@ -1,3 +1,4 @@
1
1
  // Note: do not mix this export with other app-only logic
2
2
  // to avoid importing unnecessary code in the plugins.
3
- export const DEFAULT_LOCALE = 'EN-US';
3
+ export const DEFAULT_COUNTRY = 'US';
4
+ export const DEFAULT_LANGUAGE = 'EN';
@@ -22,12 +22,18 @@ export function useNavigate() {
22
22
  };
23
23
  }
24
24
  export function buildPath(basePath, path) {
25
+ if (path.startsWith('http') || path.startsWith('//'))
26
+ return path;
25
27
  let builtPath = path;
26
28
  if (basePath !== '/') {
29
+ const pathFirstChar = path.charAt(0);
30
+ const basePathLastChar = basePath.charAt(basePath.length - 1);
27
31
  builtPath =
28
- path.charAt(0) === '/' && basePath.charAt(0) === '/'
32
+ pathFirstChar === '/' && basePathLastChar === '/'
29
33
  ? basePath + path.substring(1)
30
- : basePath + path;
34
+ : basePathLastChar !== '/' && pathFirstChar !== '/'
35
+ ? basePath + '/' + path
36
+ : basePath + path;
31
37
  }
32
38
  return builtPath;
33
39
  }
@@ -1,7 +1,7 @@
1
- import { useMemo } from 'react';
1
+ import { useContext, useMemo } from 'react';
2
2
  import { RSC_PATHNAME } from '../../constants';
3
3
  import { parseJSON } from '../../utilities/parse';
4
- import { useLocation } from '../Router/BrowserRouter.client';
4
+ import { RouterContext } from '../Router/BrowserRouter.client';
5
5
  import { useEnvContext, META_ENV_SSR } from '../ssr-interop';
6
6
  /**
7
7
  * The `useUrl` hook retrieves the current URL in a server or client component.
@@ -20,8 +20,10 @@ export function useUrl() {
20
20
  /**
21
21
  * We return a `URL` object instead of passing through `location` because
22
22
  * the URL object contains important info like hostname, etc.
23
+ * Note: do not call `useLocation` directly here to avoid throwing errors
24
+ * when `useUrl` is used outside of a Router component (e.g. in <Seo>).
23
25
  */
24
- const location = useLocation(); // eslint-disable-line react-hooks/rules-of-hooks
26
+ const location = useContext(RouterContext); // eslint-disable-line react-hooks/rules-of-hooks
25
27
  // eslint-disable-next-line react-hooks/rules-of-hooks
26
28
  return useMemo(() => new URL(window.location.href), [location]); // eslint-disable-line react-hooks/exhaustive-deps
27
29
  }
@@ -2,5 +2,4 @@ export declare function loadConfig(options?: {
2
2
  root: string;
3
3
  }): Promise<{
4
4
  configuration: any;
5
- configurationPath: any;
6
5
  }>;
@@ -3,8 +3,5 @@ import { VIRTUAL_PROXY_HYDROGEN_CONFIG_ID } from './plugins/vite-plugin-hydrogen
3
3
  import { viteception } from './viteception';
4
4
  export async function loadConfig(options = { root: process.cwd() }) {
5
5
  const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], options);
6
- return {
7
- configuration: loaded[0].default,
8
- configurationPath: loaded[0].configPath,
9
- };
6
+ return { configuration: loaded[0].default };
10
7
  }
@@ -24,7 +24,7 @@ const hydrogenPlugin = (pluginOptions = {}) => {
24
24
  hydrationAutoImport(),
25
25
  ssrInterop(),
26
26
  cssModulesRsc(),
27
- rsc(),
27
+ rsc(pluginOptions),
28
28
  platformEntry(),
29
29
  suppressWarnings(),
30
30
  pluginOptions.purgeQueryCacheOnBuild && purgeQueryCache(),