@shopify/hydrogen 0.14.0 → 0.15.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 (150) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/esnext/client.d.ts +3 -0
  3. package/dist/esnext/client.js +3 -0
  4. package/dist/esnext/components/CartProvider/CartProvider.client.js +23 -0
  5. package/dist/esnext/components/DevTools.d.ts +1 -0
  6. package/dist/esnext/components/DevTools.js +128 -0
  7. package/dist/esnext/components/Link/Link.client.js +1 -1
  8. package/dist/esnext/constants.d.ts +2 -0
  9. package/dist/esnext/constants.js +2 -0
  10. package/dist/esnext/entry-client.js +7 -4
  11. package/dist/esnext/entry-server.d.ts +1 -1
  12. package/dist/esnext/entry-server.js +29 -15
  13. package/dist/esnext/foundation/Analytics/Analytics.client.d.ts +3 -0
  14. package/dist/esnext/foundation/Analytics/Analytics.client.js +28 -0
  15. package/dist/esnext/foundation/Analytics/Analytics.server.d.ts +1 -0
  16. package/dist/esnext/foundation/Analytics/Analytics.server.js +38 -0
  17. package/dist/esnext/foundation/Analytics/ClientAnalytics.d.ts +24 -0
  18. package/dist/esnext/foundation/Analytics/ClientAnalytics.js +91 -0
  19. package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
  20. package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.js +33 -0
  21. package/dist/esnext/foundation/Analytics/const.d.ts +8 -0
  22. package/dist/esnext/foundation/Analytics/const.js +8 -0
  23. package/dist/esnext/foundation/Analytics/hook.d.ts +1 -0
  24. package/dist/esnext/foundation/Analytics/hook.js +7 -0
  25. package/dist/esnext/foundation/Analytics/index.d.ts +2 -0
  26. package/dist/esnext/foundation/Analytics/index.js +2 -0
  27. package/dist/esnext/foundation/Analytics/types.d.ts +5 -0
  28. package/dist/esnext/foundation/Analytics/types.js +1 -0
  29. package/dist/esnext/foundation/Analytics/utils.d.ts +1 -0
  30. package/dist/esnext/foundation/Analytics/utils.js +8 -0
  31. package/dist/esnext/foundation/Boomerang/Boomerang.client.js +3 -1
  32. package/dist/esnext/foundation/Route/Route.server.js +4 -0
  33. package/dist/esnext/foundation/Router/BrowserRouter.client.js +68 -15
  34. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +1 -1
  35. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +2 -5
  36. package/dist/esnext/foundation/fetchSync/client/fetchSync.d.ts +10 -0
  37. package/dist/esnext/foundation/fetchSync/client/fetchSync.js +27 -0
  38. package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +8 -0
  39. package/dist/esnext/foundation/fetchSync/server/fetchSync.js +27 -0
  40. package/dist/esnext/foundation/fetchSync/types.d.ts +5 -0
  41. package/dist/esnext/foundation/fetchSync/types.js +1 -0
  42. package/dist/esnext/foundation/useQuery/hooks.d.ts +4 -2
  43. package/dist/esnext/foundation/useQuery/hooks.js +10 -6
  44. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  45. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +11 -5
  46. package/dist/esnext/framework/cache/in-memory.js +5 -5
  47. package/dist/esnext/framework/cache.d.ts +1 -2
  48. package/dist/esnext/framework/cache.js +67 -22
  49. package/dist/esnext/framework/plugin.js +10 -0
  50. package/dist/esnext/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
  51. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +9 -2
  52. package/dist/esnext/hooks/useShopQuery/hooks.js +27 -8
  53. package/dist/esnext/index.d.ts +2 -0
  54. package/dist/esnext/index.js +2 -0
  55. package/dist/esnext/types.d.ts +6 -1
  56. package/dist/esnext/utilities/apiRoutes.d.ts +1 -1
  57. package/dist/esnext/utilities/apiRoutes.js +3 -2
  58. package/dist/esnext/utilities/hash.d.ts +2 -0
  59. package/dist/esnext/utilities/hash.js +7 -0
  60. package/dist/esnext/utilities/log/log-cache-api-status.js +1 -1
  61. package/dist/esnext/utilities/log/log-cache-header.js +1 -1
  62. package/dist/esnext/utilities/log/log-query-timeline.js +1 -1
  63. package/dist/esnext/utilities/suspense.d.ts +5 -0
  64. package/dist/esnext/utilities/suspense.js +32 -0
  65. package/dist/esnext/utilities/template.js +1 -1
  66. package/dist/esnext/version.d.ts +1 -1
  67. package/dist/esnext/version.js +1 -1
  68. package/dist/node/constants.d.ts +2 -0
  69. package/dist/node/constants.js +3 -1
  70. package/dist/node/entry-server.d.ts +1 -1
  71. package/dist/node/entry-server.js +28 -17
  72. package/dist/node/foundation/Analytics/Analytics.client.d.ts +3 -0
  73. package/dist/node/foundation/Analytics/Analytics.client.js +32 -0
  74. package/dist/node/foundation/Analytics/Analytics.server.d.ts +1 -0
  75. package/dist/node/foundation/Analytics/Analytics.server.js +45 -0
  76. package/dist/node/foundation/Analytics/ClientAnalytics.d.ts +24 -0
  77. package/dist/node/foundation/Analytics/ClientAnalytics.js +94 -0
  78. package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
  79. package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.js +37 -0
  80. package/dist/node/foundation/Analytics/const.d.ts +8 -0
  81. package/dist/node/foundation/Analytics/const.js +11 -0
  82. package/dist/node/foundation/Analytics/hook.d.ts +1 -0
  83. package/dist/node/foundation/Analytics/hook.js +11 -0
  84. package/dist/node/foundation/Analytics/index.d.ts +2 -0
  85. package/dist/node/foundation/Analytics/index.js +7 -0
  86. package/dist/node/foundation/Analytics/types.d.ts +5 -0
  87. package/dist/node/foundation/Analytics/types.js +2 -0
  88. package/dist/node/foundation/Analytics/utils.d.ts +1 -0
  89. package/dist/node/foundation/Analytics/utils.js +12 -0
  90. package/dist/node/foundation/Router/BrowserRouter.client.js +67 -14
  91. package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +2 -2
  92. package/dist/node/foundation/ShopifyProvider/types.d.ts +2 -5
  93. package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  94. package/dist/node/framework/Hydration/ServerComponentRequest.server.js +13 -7
  95. package/dist/node/framework/cache/in-memory.js +5 -5
  96. package/dist/node/framework/cache.d.ts +1 -2
  97. package/dist/node/framework/cache.js +71 -27
  98. package/dist/node/framework/plugin.js +10 -0
  99. package/dist/node/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
  100. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +9 -2
  101. package/dist/node/types.d.ts +6 -1
  102. package/dist/node/utilities/apiRoutes.d.ts +1 -1
  103. package/dist/node/utilities/apiRoutes.js +3 -2
  104. package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +6 -0
  105. package/dist/node/utilities/flattenConnection/flattenConnection.js +15 -0
  106. package/dist/node/utilities/flattenConnection/index.d.ts +1 -0
  107. package/dist/node/utilities/flattenConnection/index.js +5 -0
  108. package/dist/node/utilities/hash.d.ts +2 -0
  109. package/dist/node/utilities/hash.js +11 -0
  110. package/dist/node/utilities/image_size.d.ts +30 -0
  111. package/dist/node/utilities/image_size.js +110 -0
  112. package/dist/node/utilities/index.d.ts +11 -0
  113. package/dist/node/utilities/index.js +32 -0
  114. package/dist/node/utilities/isClient/index.d.ts +1 -0
  115. package/dist/node/utilities/isClient/index.js +5 -0
  116. package/dist/node/utilities/isClient/isClient.d.ts +4 -0
  117. package/dist/node/utilities/isClient/isClient.js +10 -0
  118. package/dist/node/utilities/isServer/index.d.ts +1 -0
  119. package/dist/node/utilities/isServer/index.js +5 -0
  120. package/dist/node/utilities/isServer/isServer.d.ts +4 -0
  121. package/dist/node/utilities/isServer/isServer.js +11 -0
  122. package/dist/node/utilities/load_script.d.ts +3 -0
  123. package/dist/node/utilities/load_script.js +27 -0
  124. package/dist/node/utilities/log/log-cache-api-status.js +1 -1
  125. package/dist/node/utilities/log/log-cache-header.js +2 -2
  126. package/dist/node/utilities/log/log-query-timeline.js +2 -2
  127. package/dist/node/utilities/measurement.d.ts +3 -0
  128. package/dist/node/utilities/measurement.js +103 -0
  129. package/dist/node/utilities/parseMetafieldValue/index.d.ts +1 -0
  130. package/dist/node/utilities/parseMetafieldValue/index.js +5 -0
  131. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.d.ts +6 -0
  132. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.js +39 -0
  133. package/dist/node/utilities/suspense.d.ts +12 -0
  134. package/dist/node/utilities/suspense.js +64 -0
  135. package/dist/node/utilities/template.js +1 -1
  136. package/dist/node/utilities/video_parameters.d.ts +47 -0
  137. package/dist/node/utilities/video_parameters.js +27 -0
  138. package/dist/node/version.d.ts +1 -1
  139. package/dist/node/version.js +1 -1
  140. package/package.json +1 -1
  141. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +9 -21
  142. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.development.server.js +51 -47
  143. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.production.min.server.js +30 -29
  144. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.development.server.js +51 -47
  145. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.production.min.server.js +17 -17
  146. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +55 -45
  147. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +9 -21
  148. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.browser.server.js +51 -47
  149. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.node.server.js +51 -47
  150. package/vendor/react-server-dom-vite/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#983](https://github.com/Shopify/hydrogen/pull/983) [`52af261b`](https://github.com/Shopify/hydrogen/commit/52af261ba2bf6ed08e232b9fb2d75e69905f4cc6) Thanks [@jplhomer](https://github.com/jplhomer)! - Introduce Suspense-friendly `fetchSync` API for server and client components.
8
+
9
+ When using `fetchSync` in server components, you provide options for caching and preloading. This is similar to the [`useQuery` hook](<[/api/hydrogen/hooks/global/useQuery](https://shopify.dev/api/hydrogen/hooks/global/usequery)>):
10
+
11
+ ```jsx
12
+ import {fetchSync, CacheMinutes} from '@shopify/hydrogen';
13
+ import {Suspense} from 'react';
14
+
15
+ export function MyServerComponent() {
16
+ return (
17
+ <Suspense fallback="Loading...">
18
+ <MyThings />
19
+ </Suspense>
20
+ );
21
+ }
22
+
23
+ function MyThings() {
24
+ const things = fetchSync('https://3p.api.com/things.json', {
25
+ preload: true,
26
+ cache: CacheMinutes(),
27
+ }).json();
28
+
29
+ return <h2>{things.title}</h2>;
30
+ }
31
+ ```
32
+
33
+ When using `fetchSync` in client components, you cannot provide options for caching and preloading. You must import `fetchSync` from `@shopify/hydrogen/client`:
34
+
35
+ ```jsx
36
+ import {fetchSync} from '@shopify/hydrogen/client';
37
+ import {Suspense} from 'react';
38
+
39
+ export function MyClientComponent() {
40
+ return (
41
+ <Suspense fallback="Loading...">
42
+ <MyThings />
43
+ </Suspense>
44
+ );
45
+ }
46
+
47
+ function MyThings() {
48
+ const things = fetchSync('https://3p.api.com/things.json').json();
49
+
50
+ return <h2>{things.title}</h2>;
51
+ }
52
+ ```
53
+
54
+ * [#890](https://github.com/Shopify/hydrogen/pull/890) [`a4c6d6c4`](https://github.com/Shopify/hydrogen/commit/a4c6d6c4d31337cecbd4d5afb76887bcd31ceb65) Thanks [@wizardlyhel](https://github.com/wizardlyhel)! - Analytics instrumentation - this provides integration points for both server
55
+ and client side analytics instrumentations
56
+
57
+ - [Usage documentation](https://shopify.dev/custom-storefronts/hydrogen/framework/analytics)
58
+
59
+ ### Patch Changes
60
+
61
+ - [#1061](https://github.com/Shopify/hydrogen/pull/1061) [`a4aa3887`](https://github.com/Shopify/hydrogen/commit/a4aa3887be9f448ec1f4322fadb9821e0d19a0b5) Thanks [@jplhomer](https://github.com/jplhomer)! - Support script tags in index.html that contain line breaks
62
+
63
+ * [#1057](https://github.com/Shopify/hydrogen/pull/1057) [`06d92ddc`](https://github.com/Shopify/hydrogen/commit/06d92ddc44e03d37d2dd8a9bbeaa5fab4c4bbbd1) Thanks [@frandiox](https://github.com/frandiox)! - Ability to concatenate requests in API route handlers without leaving the server by returning a new Request instance.
64
+
65
+ ```jsx
66
+ // src/routes/my-page.server.jsx
67
+
68
+ export async function api(request) {
69
+ if (request.method === 'POST') {
70
+ // do some work here...
71
+ }
72
+
73
+ return new Request(request.url, {method: 'GET'});
74
+ }
75
+
76
+ export default function Page() {
77
+ return (
78
+ <form action="/my-page" method="POST">
79
+ ...
80
+ </form>
81
+ );
82
+ }
83
+ ```
84
+
85
+ In the previous example, a POST request to `/my-page` would run the API handler and automatically continue with the server component rendering (GET). This is useful for handling HTML forms without waterfall requests.
86
+
87
+ - [#1049](https://github.com/Shopify/hydrogen/pull/1049) [`b88a885d`](https://github.com/Shopify/hydrogen/commit/b88a885d6b062209497a97d8ce7bcd438787d53c) Thanks [@wizardlyhel](https://github.com/wizardlyhel)! - Support sub request cache control header `stale-while-revalidate` everywhere
88
+
89
+ * [#1047](https://github.com/Shopify/hydrogen/pull/1047) [`5268bf85`](https://github.com/Shopify/hydrogen/commit/5268bf85f61f8abf0e97788b7ae925ad4f3183b2) Thanks [@jplhomer](https://github.com/jplhomer)! - Restore scroll position when navigating using the back and forward buttons.
90
+
91
+ - [#1062](https://github.com/Shopify/hydrogen/pull/1062) [`cc172ae7`](https://github.com/Shopify/hydrogen/commit/cc172ae778bad0d654adcd2f41d4a548d1d94a0a) Thanks [@jplhomer](https://github.com/jplhomer)! - Fix encoding of quotes in CSS Modules which caused hydration errors
92
+
93
+ * [#1046](https://github.com/Shopify/hydrogen/pull/1046) [`3947d53a`](https://github.com/Shopify/hydrogen/commit/3947d53a99868a1e218bfab958b824ce0484615a) Thanks [@michenly](https://github.com/michenly)! - Fixed server Cookie bug where initializing with empty string will resulted in 1 item in the Cookies Map.
94
+
95
+ - [#1059](https://github.com/Shopify/hydrogen/pull/1059) [`401f329d`](https://github.com/Shopify/hydrogen/commit/401f329d331bebc4842204d4df39c4dd6797b4e1) Thanks [@frandiox](https://github.com/frandiox)! - Fix link prefetch mismatch due to query-string
96
+
97
+ * [#1072](https://github.com/Shopify/hydrogen/pull/1072) [`47c0c184`](https://github.com/Shopify/hydrogen/commit/47c0c18411eb20fa6652a981b09fd65cbed38304) Thanks [@michenly](https://github.com/michenly)! - Improve type for ShopifyContextValue to be based on ShopifyConfig.
98
+
3
99
  ## 0.14.0
4
100
 
5
101
  ### Minor Changes
@@ -5,5 +5,8 @@ export * from './foundation/useShop';
5
5
  export * from './foundation/ServerStateProvider';
6
6
  export { Head } from './foundation/Head';
7
7
  export * from './utilities';
8
+ export { ClientAnalytics } from './foundation/Analytics';
8
9
  export { useRouteParams } from './foundation/useRouteParams/useRouteParams';
9
10
  export { useNavigate } from './foundation/useNavigate/useNavigate';
11
+ export { fetchSync } from './foundation/fetchSync/client/fetchSync';
12
+ export { suspendFunction, preloadFunction } from './utilities/suspense';
@@ -5,5 +5,8 @@ export * from './foundation/useShop';
5
5
  export * from './foundation/ServerStateProvider';
6
6
  export { Head } from './foundation/Head';
7
7
  export * from './utilities';
8
+ export { ClientAnalytics } from './foundation/Analytics';
8
9
  export { useRouteParams } from './foundation/useRouteParams/useRouteParams';
9
10
  export { useNavigate } from './foundation/useNavigate/useNavigate';
11
+ export { fetchSync } from './foundation/fetchSync/client/fetchSync';
12
+ export { suspendFunction, preloadFunction } from './utilities/suspense';
@@ -5,6 +5,7 @@ import { useCartFetch } from './hooks';
5
5
  import { CartContext } from './context';
6
6
  import { CART_ID_STORAGE_KEY } from './constants';
7
7
  import { useServerState } from '../../foundation/useServerState';
8
+ import { ClientAnalytics } from '../../foundation/Analytics';
8
9
  function cartReducer(state, action) {
9
10
  switch (action.type) {
10
11
  case 'cartFetch': {
@@ -200,6 +201,12 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
200
201
  });
201
202
  }
202
203
  if ((_b = data === null || data === void 0 ? void 0 : data.cartCreate) === null || _b === void 0 ? void 0 : _b.cart) {
204
+ if (cart.lines) {
205
+ ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
206
+ addedCartLines: cart.lines,
207
+ cart: data.cartCreate.cart,
208
+ });
209
+ }
203
210
  dispatch({
204
211
  type: 'resolve',
205
212
  cart: cartFromGraphQL(data.cartCreate.cart),
@@ -228,6 +235,10 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
228
235
  });
229
236
  }
230
237
  if ((_a = data === null || data === void 0 ? void 0 : data.cartLinesAdd) === null || _a === void 0 ? void 0 : _a.cart) {
238
+ ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
239
+ addedCartLines: lines,
240
+ cart: data.cartLinesAdd.cart,
241
+ });
231
242
  dispatch({
232
243
  type: 'resolve',
233
244
  cart: cartFromGraphQL(data.cartLinesAdd.cart),
@@ -256,6 +267,10 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
256
267
  });
257
268
  }
258
269
  if ((_a = data === null || data === void 0 ? void 0 : data.cartLinesRemove) === null || _a === void 0 ? void 0 : _a.cart) {
270
+ ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
271
+ removedCartLines: lines,
272
+ cart: data.cartLinesRemove.cart,
273
+ });
259
274
  dispatch({
260
275
  type: 'resolve',
261
276
  cart: cartFromGraphQL(data.cartLinesRemove.cart),
@@ -284,6 +299,10 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
284
299
  });
285
300
  }
286
301
  if ((_a = data === null || data === void 0 ? void 0 : data.cartLinesUpdate) === null || _a === void 0 ? void 0 : _a.cart) {
302
+ ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
303
+ updatedCartLines: lines,
304
+ oldCart: state.cart,
305
+ });
287
306
  dispatch({
288
307
  type: 'resolve',
289
308
  cart: cartFromGraphQL(data.cartLinesUpdate.cart),
@@ -396,6 +415,10 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
396
415
  });
397
416
  }
398
417
  if ((_a = data === null || data === void 0 ? void 0 : data.cartDiscountCodesUpdate) === null || _a === void 0 ? void 0 : _a.cart) {
418
+ ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
419
+ updatedDiscountCodes: discountCodes,
420
+ cart: data.cartDiscountCodesUpdate.cart,
421
+ });
399
422
  dispatch({
400
423
  type: 'resolve',
401
424
  cart: cartFromGraphQL(data.cartDiscountCodesUpdate.cart),
@@ -0,0 +1 @@
1
+ export default function DevTools(): JSX.Element | null;
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ export default function DevTools() {
3
+ const [warnings, setWarnings] = useState(null);
4
+ const [open, setOpen] = useState(false);
5
+ const [activePanel, setActivePanel] = useState('warnings');
6
+ const toggleOpen = useCallback(() => setOpen((state) => !state), []);
7
+ const [hasMounted, setHasMounted] = React.useState(false);
8
+ useEffect(() => {
9
+ setHasMounted(true);
10
+ if (import.meta.hot) {
11
+ import.meta.hot.on('hydrogen', ({ type, data }) => {
12
+ if (type === 'warn') {
13
+ setWarnings((state) => [...(state || []), data]);
14
+ }
15
+ });
16
+ }
17
+ }, []);
18
+ const perfData = performance.getEntriesByType('navigation');
19
+ const entry = perfData[0];
20
+ let activePanelContent = null;
21
+ switch (activePanel) {
22
+ case 'warnings':
23
+ const warningsMarkup = warnings
24
+ ? warnings.map((war, i) => React.createElement("li", { key: war + i }, war))
25
+ : null;
26
+ activePanelContent = (React.createElement(React.Fragment, null,
27
+ React.createElement(PanelHeading, null, "Overfetched graphQL fields"),
28
+ React.createElement("ul", { style: {
29
+ fontFamily: 'monospace',
30
+ paddingTop: '1em',
31
+ fontSize: '0.9em',
32
+ } }, warningsMarkup)));
33
+ break;
34
+ case 'network':
35
+ activePanelContent = (React.createElement(React.Fragment, null,
36
+ React.createElement(PanelHeading, null, "Metrics"),
37
+ React.createElement("ul", { style: {
38
+ fontFamily: 'monospace',
39
+ paddingTop: '1em',
40
+ fontSize: '0.9em',
41
+ } }, Object.entries(entry.toJSON())
42
+ .filter(([key]) => ['duration', 'domInteractive'].includes(key))
43
+ .map(([key, value]) => (React.createElement("li", { key: key },
44
+ React.createElement("strong", null, key),
45
+ " ",
46
+ (value / 1000).toFixed(2),
47
+ "s"))))));
48
+ break;
49
+ }
50
+ const buttonText = (React.createElement("svg", { style: {
51
+ height: '3em',
52
+ width: '3em',
53
+ }, width: "131", height: "130", viewBox: "0 0 131 130", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
54
+ React.createElement("path", { d: "M64.9548 106.281L27.1377 86.1894L40.0714 79.3723L54.6329 87.1049L66.851 80.6638L52.2895 72.9313L65.2231 66.0979L103.04 86.1894L90.1065 93.0064L76.35 85.6989L64.114 92.1563L77.8884 99.4638L64.9548 106.281Z", fill: "white" }),
55
+ React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M65.2247 25L105.178 46.2267L90.105 54.1716L76.3488 46.8642L66.2525 52.1924L80.028 59.5005L64.9532 67.446L25 46.2196L40.0734 38.2748L54.6349 46.0073L64.713 40.6944L50.1533 32.9628L65.2247 25ZM54.4262 32.9673L68.9896 40.7008L54.6315 48.27L40.0699 40.5374L29.276 46.2267L64.9569 65.1833L75.7495 59.4947L61.9761 52.1878L76.3518 44.6012L90.1087 51.9088L100.902 46.2196L65.2221 27.2634L54.4262 32.9673Z", fill: "white" })));
56
+ if (import.meta.env.DEV && hasMounted) {
57
+ return (React.createElement("div", { style: {
58
+ position: 'fixed',
59
+ right: open ? 0 : '2em',
60
+ left: open ? 0 : '2em',
61
+ bottom: open ? 0 : '-2em',
62
+ borderRadius: open ? 0 : '2em',
63
+ top: '100%',
64
+ zIndex: 10000,
65
+ display: 'flex',
66
+ flexDirection: 'column',
67
+ overflow: 'hidden',
68
+ height: open ? '75%' : '4em',
69
+ transform: open ? 'translateY(-100%)' : 'translateY(-5em)',
70
+ } },
71
+ React.createElement("button", { style: {
72
+ position: 'absolute',
73
+ top: '0.35em',
74
+ right: '1em',
75
+ overflow: 'hidden',
76
+ zIndex: 10,
77
+ }, onClick: toggleOpen }, buttonText),
78
+ React.createElement("div", { style: {
79
+ overflow: 'scroll',
80
+ color: 'white',
81
+ height: '100%',
82
+ padding: '2em',
83
+ background: 'rgba(0, 0, 0, 0.95)',
84
+ borderRadius: open ? 0 : '2em',
85
+ } },
86
+ React.createElement("div", { style: {
87
+ position: 'absolute',
88
+ padding: '1.2em 2em',
89
+ top: 0,
90
+ left: '-0.5em',
91
+ right: 0,
92
+ background: 'rgba(0, 0, 0, 0.95)',
93
+ borderRadius: open ? 0 : '2em',
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ } },
97
+ warnings && warnings.length > 0 && (React.createElement("button", { onClick: () => {
98
+ setOpen(true);
99
+ setActivePanel('warnings');
100
+ }, style: {
101
+ margin: '0 0.5em',
102
+ textDecoration: open && activePanel === 'warnings' ? 'underline' : 'none',
103
+ } },
104
+ "Warnings",
105
+ ' ',
106
+ warnings && (React.createElement("strong", { style: {
107
+ borderRadius: '2em',
108
+ padding: '.25em 0.5em',
109
+ background: 'white',
110
+ margin: '0 0.25em',
111
+ color: 'black',
112
+ textAlign: 'center',
113
+ } }, warnings.length)))),
114
+ React.createElement("button", { onClick: () => {
115
+ setOpen(true);
116
+ setActivePanel('network');
117
+ }, disabled: activePanel === 'network', style: {
118
+ margin: '0 0.5em',
119
+ textDecoration: open && activePanel === 'network' ? 'underline' : 'none',
120
+ } }, "Network")),
121
+ React.createElement("div", { style: { paddingTop: '2em' } }, activePanelContent))));
122
+ }
123
+ return null;
124
+ }
125
+ const PanelHeading = ({ children }) => (React.createElement("h1", { style: {
126
+ paddingTop: '1em',
127
+ fontWeight: 'bold',
128
+ } }, children));
@@ -95,7 +95,7 @@ function Prefetch({ pathname }) {
95
95
  const newLocation = new URL(newPath, window.location.href);
96
96
  const proposedServerState = getProposedServerState({
97
97
  pathname: newLocation.pathname,
98
- search: newLocation.search || undefined,
98
+ search: newLocation.search,
99
99
  });
100
100
  const href = `${RSC_PATHNAME}?state=` +
101
101
  encodeURIComponent(JSON.stringify(proposedServerState));
@@ -1 +1,3 @@
1
1
  export declare const RSC_PATHNAME = "/__rsc";
2
+ export declare const EVENT_PATHNAME = "/__event";
3
+ export declare const EVENT_PATHNAME_REGEX: RegExp;
@@ -1 +1,3 @@
1
1
  export const RSC_PATHNAME = '/__rsc';
2
+ export const EVENT_PATHNAME = '/__event';
3
+ export const EVENT_PATHNAME_REGEX = new RegExp(`^${EVENT_PATHNAME}\/`);
@@ -4,6 +4,7 @@ import { hydrateRoot } from 'react-dom/client';
4
4
  import { ErrorBoundary } from 'react-error-boundary';
5
5
  import { useServerResponse } from './framework/Hydration/rsc';
6
6
  import { ServerStateProvider } from './foundation/ServerStateProvider';
7
+ const DevTools = React.lazy(() => import('./components/DevTools'));
7
8
  const renderHydrogen = async (ClientWrapper, config) => {
8
9
  const root = document.getElementById('root');
9
10
  if (!root) {
@@ -20,10 +21,12 @@ const renderHydrogen = async (ClientWrapper, config) => {
20
21
  // default to StrictMode on, unless explicitly turned off
21
22
  const RootComponent = (config === null || config === void 0 ? void 0 : config.strictMode) !== false ? StrictMode : Fragment;
22
23
  let hasCaughtError = false;
23
- hydrateRoot(root, React.createElement(RootComponent, null,
24
- React.createElement(ErrorBoundary, { FallbackComponent: Error },
25
- React.createElement(Suspense, { fallback: null },
26
- React.createElement(Content, { clientWrapper: ClientWrapper })))), {
24
+ hydrateRoot(root, React.createElement(React.Fragment, null,
25
+ React.createElement(RootComponent, null,
26
+ React.createElement(ErrorBoundary, { FallbackComponent: Error },
27
+ React.createElement(Suspense, { fallback: null },
28
+ React.createElement(Content, { clientWrapper: ClientWrapper })))),
29
+ typeof DevTools !== 'undefined' && (config === null || config === void 0 ? void 0 : config.showDevTools) ? (React.createElement(DevTools, null)) : null), {
27
30
  onRecoverableError(e) {
28
31
  if (__DEV__ && !hasCaughtError) {
29
32
  hasCaughtError = true;
@@ -19,5 +19,5 @@ interface RequestHandlerOptions {
19
19
  export interface RequestHandler {
20
20
  (request: Request | IncomingMessage, options: RequestHandlerOptions): Promise<Response | undefined>;
21
21
  }
22
- export declare const renderHydrogen: (App: any, { shopifyConfig, routes }: ServerHandlerConfig) => RequestHandler;
22
+ export declare const renderHydrogen: (App: any, { shopifyConfig, routes, serverAnalyticsConnectors }: ServerHandlerConfig) => RequestHandler;
23
23
  export default renderHydrogen;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { Suspense } from 'react';
2
2
  import { logServerResponse, logCacheControlHeaders, logQueryTimings, getLoggerWithContext, } from './utilities/log';
3
3
  import { getErrorMarkup } from './utilities/error';
4
4
  import { defer } from './utilities/defer';
@@ -12,13 +12,16 @@ import { isBotUA } from './utilities/bot-ua';
12
12
  import { setContext, setCache } from './framework/runtime';
13
13
  import { setConfig } from './framework/config';
14
14
  import { ssrRenderToPipeableStream, ssrRenderToReadableStream, rscRenderToReadableStream, createFromReadableStream, isStreamingSupported, bufferReadableStream, } from './streaming.server';
15
- import { RSC_PATHNAME } from './constants';
15
+ import { RSC_PATHNAME, EVENT_PATHNAME, EVENT_PATHNAME_REGEX } from './constants';
16
16
  import { stripScriptsFromTemplate } from './utilities/template';
17
+ import { Analytics } from './foundation/Analytics/Analytics.server';
18
+ import { ServerAnalyticsRoute } from './foundation/Analytics/ServerAnalyticsRoute.server';
17
19
  const DOCTYPE = '<!DOCTYPE html>';
18
20
  const CONTENT_TYPE = 'Content-Type';
19
21
  const HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
20
- export const renderHydrogen = (App, { shopifyConfig, routes }) => {
21
- const handleRequest = async function (rawRequest, { indexTemplate, streamableResponse, dev, cache, context, nonce, buyerIpHeader, }) {
22
+ export const renderHydrogen = (App, { shopifyConfig, routes, serverAnalyticsConnectors }) => {
23
+ const handleRequest = async function (rawRequest, options) {
24
+ const { indexTemplate, streamableResponse, dev, cache, context, nonce, buyerIpHeader, } = options;
22
25
  const request = new ServerComponentRequest(rawRequest);
23
26
  request.ctx.buyerIpHeader = buyerIpHeader;
24
27
  const url = new URL(request.url);
@@ -30,24 +33,31 @@ export const renderHydrogen = (App, { shopifyConfig, routes }) => {
30
33
  setCache(cache);
31
34
  setContext(context);
32
35
  setConfig({ dev });
33
- const isReactHydrationRequest = url.pathname === RSC_PATHNAME;
34
- let template = typeof indexTemplate === 'function'
35
- ? await indexTemplate(url.toString())
36
- : indexTemplate;
37
- if (template && typeof template !== 'string') {
38
- template = template.default;
36
+ if (url.pathname === EVENT_PATHNAME ||
37
+ EVENT_PATHNAME_REGEX.test(url.pathname)) {
38
+ return ServerAnalyticsRoute(request, serverAnalyticsConnectors);
39
39
  }
40
+ const isReactHydrationRequest = url.pathname === RSC_PATHNAME;
40
41
  if (!isReactHydrationRequest && routes) {
41
42
  const apiRoute = getApiRoute(url, { routes });
42
43
  // The API Route might have a default export, making it also a server component
43
44
  // If it does, only render the API route if the request method is GET
44
45
  if (apiRoute &&
45
46
  (!apiRoute.hasServerComponent || request.method !== 'GET')) {
46
- return renderApiRoute(request, apiRoute, shopifyConfig);
47
+ const apiResponse = await renderApiRoute(request, apiRoute, shopifyConfig);
48
+ return apiResponse instanceof Request
49
+ ? handleRequest(apiResponse, options)
50
+ : apiResponse;
47
51
  }
48
52
  }
49
53
  const isStreamable = !isBotUA(url, request.headers.get('user-agent')) &&
50
54
  (!!streamableResponse || (await isStreamingSupported()));
55
+ let template = typeof indexTemplate === 'function'
56
+ ? await indexTemplate(url.toString())
57
+ : indexTemplate;
58
+ if (template && typeof template !== 'string') {
59
+ template = template.default;
60
+ }
51
61
  const params = {
52
62
  App,
53
63
  log,
@@ -381,7 +391,9 @@ function buildAppRSC({ App, state, request, response, log, routes, }) {
381
391
  request.ctx.router.serverProps = serverProps;
382
392
  const AppRSC = (React.createElement(ServerRequestProvider, { request: request, isRSC: true },
383
393
  React.createElement(PreloadQueries, { request: request },
384
- React.createElement(App, { ...serverProps }))));
394
+ React.createElement(App, { ...serverProps }),
395
+ React.createElement(Suspense, { fallback: null },
396
+ React.createElement(Analytics, null)))));
385
397
  return { AppRSC };
386
398
  }
387
399
  function buildAppSSR({ App, state, request, response, log, routes }, htmlOptions) {
@@ -400,14 +412,16 @@ function buildAppSSR({ App, state, request, response, log, routes }, htmlOptions
400
412
  React.createElement(ServerRequestProvider, { request: request, isRSC: false },
401
413
  React.createElement(ServerStateProvider, { serverState: state, setServerState: () => { } },
402
414
  React.createElement(PreloadQueries, { request: request },
403
- React.createElement(React.Suspense, { fallback: null },
404
- React.createElement(RscConsumer, null)))))));
415
+ React.createElement(Suspense, { fallback: null },
416
+ React.createElement(RscConsumer, null)),
417
+ React.createElement(Suspense, { fallback: null },
418
+ React.createElement(Analytics, null)))))));
405
419
  return { AppSSR, rscReadable: rscReadableForFlight };
406
420
  }
407
421
  function PreloadQueries({ request, children, }) {
408
422
  const preloadQueries = request.getPreloadQueries();
409
423
  preloadRequestCacheData(request, preloadQueries);
410
- return children;
424
+ return React.createElement(React.Fragment, null, children);
411
425
  }
412
426
  async function renderToBufferedString(ReactApp, { log, nonce }) {
413
427
  return new Promise(async (resolve, reject) => {
@@ -0,0 +1,3 @@
1
+ export declare function Analytics({ analyticsDataFromServer, }: {
2
+ analyticsDataFromServer: any;
3
+ }): null;
@@ -0,0 +1,28 @@
1
+ import { useEffect } from 'react';
2
+ import { ClientAnalytics } from './index';
3
+ export function Analytics({ analyticsDataFromServer, }) {
4
+ useEffect(() => {
5
+ const urlParams = new URLSearchParams(window.location.search);
6
+ if (urlParams.has('utm_source')) {
7
+ ClientAnalytics.pushToPageAnalyticsData({
8
+ id: urlParams.get('utm_id'),
9
+ source: urlParams.get('utm_source'),
10
+ campaign: urlParams.get('utm_campaign'),
11
+ medium: urlParams.get('utm_medium'),
12
+ content: urlParams.get('utm_content'),
13
+ term: urlParams.get('utm_term'),
14
+ }, 'utm');
15
+ }
16
+ ClientAnalytics.pushToPageAnalyticsData(analyticsDataFromServer);
17
+ ClientAnalytics.publish(ClientAnalytics.eventNames.PAGE_VIEW, true);
18
+ if (analyticsDataFromServer.publishEventsOnNavigate) {
19
+ analyticsDataFromServer.publishEventsOnNavigate.forEach((eventName) => {
20
+ ClientAnalytics.publish(eventName, true);
21
+ });
22
+ }
23
+ return function cleanup() {
24
+ ClientAnalytics.resetPageAnalyticsData();
25
+ };
26
+ }, [analyticsDataFromServer]);
27
+ return null;
28
+ }
@@ -0,0 +1 @@
1
+ export declare function Analytics(): JSX.Element;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { useServerAnalytics } from './hook';
3
+ import { Analytics as AnalyticsClient } from './Analytics.client';
4
+ import { useServerRequest } from '../ServerRequestProvider';
5
+ const DELAY_KEY = 'analytics-delay';
6
+ export function Analytics() {
7
+ const cache = useServerRequest().ctx.cache;
8
+ // If render cache is empty, create a 50 ms delay so that React doesn't resolve this
9
+ // component too early and potentially cause a mismatch in hydration
10
+ if (cache.size === 0 && !cache.has(DELAY_KEY)) {
11
+ let result;
12
+ let promise;
13
+ cache.set(DELAY_KEY, () => {
14
+ if (result !== undefined) {
15
+ return result;
16
+ }
17
+ if (!promise) {
18
+ promise = new Promise((resolve) => {
19
+ setTimeout(() => {
20
+ result = true;
21
+ resolve(true);
22
+ }, 50);
23
+ });
24
+ }
25
+ throw promise;
26
+ });
27
+ }
28
+ // Make sure all queries have returned before rendering the Analytics server component
29
+ cache.forEach((cacheFn) => {
30
+ if (cacheFn && typeof cacheFn === 'function') {
31
+ const result = cacheFn.call();
32
+ if (result instanceof Promise)
33
+ throw result;
34
+ }
35
+ });
36
+ const analyticsData = useServerAnalytics();
37
+ return React.createElement(AnalyticsClient, { analyticsDataFromServer: analyticsData });
38
+ }
@@ -0,0 +1,24 @@
1
+ import type { Subscriber, SubscriberFunction } from './types';
2
+ declare function pushToPageAnalyticsData(data: any, namespace?: string): void;
3
+ declare function getPageAnalyticsData(): any;
4
+ declare function resetPageAnalyticsData(): void;
5
+ declare function publish(eventname: string, guardDup?: boolean, payload?: any): void;
6
+ declare function subscribe(eventname: string, callbackFunction: SubscriberFunction): Subscriber;
7
+ declare function pushToServer(init?: RequestInit, searchParam?: string): Promise<Response>;
8
+ export declare const ClientAnalytics: {
9
+ pushToPageAnalyticsData: typeof pushToPageAnalyticsData;
10
+ getPageAnalyticsData: typeof getPageAnalyticsData;
11
+ resetPageAnalyticsData: typeof resetPageAnalyticsData;
12
+ publish: typeof publish;
13
+ subscribe: typeof subscribe;
14
+ pushToServer: typeof pushToServer;
15
+ eventNames: {
16
+ PAGE_VIEW: string;
17
+ VIEWED_PRODUCT: string;
18
+ ADD_TO_CART: string;
19
+ REMOVE_FROM_CART: string;
20
+ UPDATE_CART: string;
21
+ DISCOUNT_CODE_UPDATED: string;
22
+ };
23
+ };
24
+ export {};