@shopify/hydrogen 0.7.0 → 0.8.2

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 (132) hide show
  1. package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.d.ts +1 -1
  2. package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.js +1 -1
  3. package/dist/esnext/components/CartLineProvider/context.d.ts +3 -3
  4. package/dist/esnext/components/CartLineQuantityAdjustButton/CartLineQuantityAdjustButton.js +4 -0
  5. package/dist/esnext/components/CartProvider/CartProvider.client.js +0 -1
  6. package/dist/esnext/components/ExternalVideo/ExternalVideo.d.ts +1 -1
  7. package/dist/esnext/components/Image/Image.d.ts +1 -1
  8. package/dist/esnext/components/LocalizationProvider/index.d.ts +1 -0
  9. package/dist/esnext/components/LocalizationProvider/index.js +1 -0
  10. package/dist/esnext/components/MediaFile/MediaFile.d.ts +1 -1
  11. package/dist/esnext/components/Metafield/Metafield.client.d.ts +1 -1
  12. package/dist/esnext/components/Metafield/components/StarRating/StarRating.d.ts +1 -1
  13. package/dist/esnext/components/Metafield/index.d.ts +2 -2
  14. package/dist/esnext/components/Model3D/Model3D.client.d.ts +1 -1
  15. package/dist/esnext/components/Money/Money.client.d.ts +1 -1
  16. package/dist/esnext/components/ProductPrice/ProductPrice.client.d.ts +1 -3
  17. package/dist/esnext/components/ProductPrice/ProductPrice.client.js +1 -3
  18. package/dist/esnext/components/ProductProvider/ProductProvider.client.d.ts +1 -1
  19. package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +1 -1
  20. package/dist/esnext/components/UnitPrice/UnitPrice.client.d.ts +2 -3
  21. package/dist/esnext/components/UnitPrice/UnitPrice.client.js +1 -2
  22. package/dist/esnext/components/Video/Video.d.ts +1 -1
  23. package/dist/esnext/components/index.d.ts +3 -2
  24. package/dist/esnext/components/index.js +1 -0
  25. package/dist/esnext/entry-client.js +4 -6
  26. package/dist/esnext/entry-server.js +77 -47
  27. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.d.ts +2 -0
  28. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheContext.js +4 -0
  29. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.d.ts +2 -0
  30. package/dist/esnext/foundation/RenderCacheProvider/RenderCacheProvider.js +5 -0
  31. package/dist/esnext/foundation/RenderCacheProvider/hook.d.ts +11 -0
  32. package/dist/esnext/foundation/RenderCacheProvider/hook.js +34 -0
  33. package/dist/esnext/foundation/RenderCacheProvider/index.d.ts +1 -0
  34. package/dist/esnext/foundation/RenderCacheProvider/index.js +1 -0
  35. package/dist/esnext/foundation/RenderCacheProvider/types.d.ts +18 -0
  36. package/dist/esnext/foundation/RenderCacheProvider/types.js +1 -0
  37. package/dist/esnext/foundation/Router/DefaultRoutes.d.ts +3 -1
  38. package/dist/esnext/foundation/Router/DefaultRoutes.js +2 -2
  39. package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.js +4 -2
  40. package/dist/esnext/foundation/ServerStateProvider/index.d.ts +2 -1
  41. package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.d.ts +1 -3
  42. package/dist/esnext/foundation/ShopifyProvider/ShopifyServerProvider.server.js +2 -4
  43. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +0 -5
  44. package/dist/esnext/foundation/useQuery/hooks.d.ts +8 -6
  45. package/dist/esnext/foundation/useQuery/hooks.js +13 -14
  46. package/dist/esnext/foundation/useQuery/index.d.ts +0 -1
  47. package/dist/esnext/foundation/useQuery/index.js +0 -1
  48. package/dist/esnext/foundation/useServerState/use-server-state.d.ts +1 -1
  49. package/dist/esnext/foundation/useServerState/use-server-state.js +1 -1
  50. package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
  51. package/dist/esnext/foundation/useShop/use-shop.js +1 -1
  52. package/dist/esnext/framework/ClientMarker/ClientMarker.js +0 -1
  53. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  54. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +2 -0
  55. package/dist/esnext/framework/Hydration/writer.server.js +1 -0
  56. package/dist/esnext/framework/cache.d.ts +2 -2
  57. package/dist/esnext/framework/cache.js +1 -1
  58. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
  59. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
  60. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
  61. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +15 -1
  62. package/dist/esnext/framework/plugins/vite-plugin-react-server-components-shim.js +1 -23
  63. package/dist/esnext/framework/server-components.js +25 -47
  64. package/dist/esnext/graphql/graphql-constants.d.ts +23 -23
  65. package/dist/esnext/handle-event.d.ts +1 -1
  66. package/dist/esnext/handle-event.js +68 -10
  67. package/dist/esnext/hooks/index.d.ts +1 -1
  68. package/dist/esnext/hooks/index.js +1 -1
  69. package/dist/esnext/hooks/useCartLine/useCartLine.d.ts +3 -3
  70. package/dist/esnext/hooks/useShopQuery/hooks.d.ts +5 -5
  71. package/dist/esnext/hooks/useShopQuery/hooks.js +45 -17
  72. package/dist/esnext/index.d.ts +2 -1
  73. package/dist/esnext/index.js +2 -1
  74. package/dist/esnext/types.d.ts +2 -1
  75. package/dist/esnext/utilities/fetch.js +3 -6
  76. package/dist/esnext/utilities/index.d.ts +1 -0
  77. package/dist/esnext/utilities/index.js +1 -0
  78. package/dist/esnext/utilities/log/index.d.ts +1 -0
  79. package/dist/esnext/utilities/log/index.js +1 -0
  80. package/dist/esnext/utilities/log/log.d.ts +17 -0
  81. package/dist/esnext/utilities/log/log.js +70 -0
  82. package/dist/esnext/utilities/suspense.d.ts +2 -2
  83. package/dist/esnext/utilities/suspense.js +0 -6
  84. package/dist/esnext/utilities/timing.d.ts +7 -0
  85. package/dist/esnext/utilities/timing.js +14 -0
  86. package/dist/esnext/version.d.ts +1 -1
  87. package/dist/esnext/version.js +1 -1
  88. package/dist/node/framework/ClientMarker/ClientMarker.js +2 -3
  89. package/dist/node/framework/Hydration/Cache.client.js +4 -4
  90. package/dist/node/framework/Hydration/HydrationContext.server.js +1 -1
  91. package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  92. package/dist/node/framework/Hydration/ServerComponentRequest.server.js +2 -0
  93. package/dist/node/framework/Hydration/ServerComponentResponse.server.js +2 -2
  94. package/dist/node/framework/Hydration/react-utils.js +3 -3
  95. package/dist/node/framework/Hydration/wire.server.js +1 -1
  96. package/dist/node/framework/Hydration/writer.server.js +1 -0
  97. package/dist/node/framework/cache.d.ts +2 -2
  98. package/dist/node/framework/cache.js +5 -4
  99. package/dist/node/framework/middleware.js +2 -2
  100. package/dist/node/framework/plugin.js +5 -5
  101. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.d.ts +1 -1
  102. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +13 -1
  103. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
  104. package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +17 -3
  105. package/dist/node/framework/plugins/vite-plugin-react-server-components-shim.js +8 -30
  106. package/dist/node/framework/server-components.js +27 -49
  107. package/dist/node/handle-event.d.ts +1 -1
  108. package/dist/node/handle-event.js +72 -14
  109. package/dist/node/types.d.ts +2 -1
  110. package/dist/node/utilities/fetch.js +4 -7
  111. package/dist/node/utilities/index.d.ts +1 -0
  112. package/dist/node/utilities/index.js +3 -1
  113. package/dist/node/utilities/isServer/isServer.js +1 -1
  114. package/dist/node/utilities/suspense.d.ts +2 -2
  115. package/dist/node/utilities/suspense.js +0 -6
  116. package/dist/node/utilities/timing.d.ts +7 -0
  117. package/dist/node/utilities/timing.js +18 -0
  118. package/dist/node/utilities/video_parameters.js +1 -1
  119. package/dist/node/version.d.ts +1 -1
  120. package/dist/node/version.js +1 -1
  121. package/dist/worker/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  122. package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +2 -0
  123. package/dist/worker/framework/cache.d.ts +2 -2
  124. package/dist/worker/framework/cache.js +1 -1
  125. package/dist/worker/handle-event.d.ts +1 -1
  126. package/dist/worker/handle-event.js +68 -10
  127. package/dist/worker/types.d.ts +2 -1
  128. package/dist/worker/utilities/timing.d.ts +7 -0
  129. package/dist/worker/utilities/timing.js +14 -0
  130. package/package.json +10 -10
  131. package/dist/esnext/foundation/useQuery/QueryProvider.d.ts +0 -6
  132. package/dist/esnext/foundation/useQuery/QueryProvider.js +0 -13
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { ServerResponse } from 'http';
2
+ import { ServerResponse } from 'http';
3
3
  import type { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
4
4
  import { RuntimeContext } from './framework/runtime';
5
5
  interface HydrogenFetchEvent {
@@ -9,9 +9,9 @@ async function handleEvent(event, { request, entrypoint, indexTemplate, assetHan
9
9
  /**
10
10
  * Inject the cache & context into the module loader so we can pull it out for subrequests.
11
11
  */
12
- runtime_1.setCache(cache);
13
- runtime_1.setContext(context);
14
- config_1.setConfig({ dev });
12
+ (0, runtime_1.setCache)(cache);
13
+ (0, runtime_1.setContext)(context);
14
+ (0, config_1.setConfig)({ dev });
15
15
  const isReactHydrationRequest = url.pathname === '/react';
16
16
  const template = typeof indexTemplate === 'function'
17
17
  ? await indexTemplate(url.toString())
@@ -29,7 +29,8 @@ async function handleEvent(event, { request, entrypoint, indexTemplate, assetHan
29
29
  throw new Error(`entry-server.jsx could not be loaded. This likely occurred because of a Vite compilation error.\n` +
30
30
  `Please check your server logs for more information.`);
31
31
  }
32
- const isStreamable = streamableResponse && isStreamableRequest(url);
32
+ const userAgent = request.headers.get('user-agent');
33
+ const isStreamable = streamableResponse && !isBotUA(url, userAgent);
33
34
  /**
34
35
  * Stream back real-user responses, but for bots/etc,
35
36
  * use `render` instead. This is because we need to inject <head>
@@ -37,7 +38,12 @@ async function handleEvent(event, { request, entrypoint, indexTemplate, assetHan
37
38
  */
38
39
  if (isStreamable) {
39
40
  if (isReactHydrationRequest) {
40
- hydrate(url, { context: {}, request, response: streamableResponse, dev });
41
+ hydrate(url, {
42
+ context: {},
43
+ request,
44
+ response: streamableResponse,
45
+ dev,
46
+ });
41
47
  }
42
48
  else {
43
49
  stream(url, {
@@ -50,13 +56,18 @@ async function handleEvent(event, { request, entrypoint, indexTemplate, assetHan
50
56
  }
51
57
  return;
52
58
  }
53
- const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, { request, context: {}, isReactHydrationRequest, dev });
59
+ const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, {
60
+ request,
61
+ context: {},
62
+ isReactHydrationRequest,
63
+ dev,
64
+ });
54
65
  const headers = componentResponse.headers;
55
66
  /**
56
67
  * TODO: Also add `Vary` headers for `accept-language` and any other keys
57
68
  * we want to shard our full-page cache for all Hydrogen storefronts.
58
69
  */
59
- headers.set(cache_1.getCacheControlHeader({ dev }), componentResponse.cacheControlHeader);
70
+ headers.set((0, cache_1.getCacheControlHeader)({ dev }), componentResponse.cacheControlHeader);
60
71
  if (componentResponse.customBody) {
61
72
  const { status, customStatus } = componentResponse;
62
73
  return new Response(await componentResponse.customBody, {
@@ -89,13 +100,6 @@ async function handleEvent(event, { request, entrypoint, indexTemplate, assetHan
89
100
  return response;
90
101
  }
91
102
  exports.default = handleEvent;
92
- function isStreamableRequest(url) {
93
- /**
94
- * TODO: Add UA detection.
95
- */
96
- const isBot = url.searchParams.has('_bot');
97
- return !isBot;
98
- }
99
103
  /**
100
104
  * Generate the contents of the `head` tag, and update the existing `<title>` tag
101
105
  * if one exists, and if a title is passed.
@@ -120,3 +124,57 @@ function generateHeadTag(head) {
120
124
  return `<head>${headHtml}</head>`;
121
125
  };
122
126
  }
127
+ /**
128
+ * Determines if the request is from a bot, using the URL and User Agent
129
+ */
130
+ function isBotUA(url, userAgent) {
131
+ return (url.searchParams.has('_bot') || (!!userAgent && botUARegex.test(userAgent)));
132
+ }
133
+ /**
134
+ * An alphabetized list of User Agents of known bots, combined from lists found at:
135
+ * https://github.com/vercel/next.js/blob/d87dc2b5a0b3fdbc0f6806a47be72bad59564bd0/packages/next/server/utils.ts#L18-L22
136
+ * https://github.com/GoogleChrome/rendertron/blob/6f681688737846b28754fbfdf5db173846a826df/middleware/src/middleware.ts#L24-L41
137
+ */
138
+ const botUserAgents = [
139
+ 'AdsBot-Google',
140
+ 'applebot',
141
+ 'Baiduspider',
142
+ 'baiduspider',
143
+ 'bingbot',
144
+ 'Bingbot',
145
+ 'BingPreview',
146
+ 'bitlybot',
147
+ 'Discordbot',
148
+ 'DuckDuckBot',
149
+ 'Embedly',
150
+ 'facebookcatalog',
151
+ 'facebookexternalhit',
152
+ 'Google-PageRenderer',
153
+ 'Googlebot',
154
+ 'googleweblight',
155
+ 'ia_archive',
156
+ 'LinkedInBot',
157
+ 'Mediapartners-Google',
158
+ 'outbrain',
159
+ 'pinterest',
160
+ 'quora link preview',
161
+ 'redditbot',
162
+ 'rogerbot',
163
+ 'showyoubot',
164
+ 'SkypeUriPreview',
165
+ 'Slackbot',
166
+ 'Slurp',
167
+ 'sogou',
168
+ 'Storebot-Google',
169
+ 'TelegramBot',
170
+ 'tumblr',
171
+ 'Twitterbot',
172
+ 'vkShare',
173
+ 'W3C_Validator',
174
+ 'WhatsApp',
175
+ 'yandex',
176
+ ];
177
+ /**
178
+ * Creates a regex based on the botUserAgents array
179
+ */
180
+ const botUARegex = new RegExp(botUserAgents.join('|'), 'i');
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { ServerResponse } from 'http';
2
+ import { ServerResponse } from 'http';
3
3
  import type { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
4
4
  import type { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
5
5
  import type { Metafield, Image, MediaContentType } from './graphql/types/types';
@@ -78,6 +78,7 @@ export interface Measurement {
78
78
  unit: string;
79
79
  value: number;
80
80
  }
81
+ export declare type QueryKey = string | readonly unknown[];
81
82
  export interface CacheOptions {
82
83
  private?: boolean;
83
84
  maxAge?: number;
@@ -41,13 +41,16 @@ function fetchBuilder(request) {
41
41
  headers,
42
42
  method: clonedRequest.method,
43
43
  });
44
+ if (!response.ok) {
45
+ throw response;
46
+ }
44
47
  const data = await response.json();
45
48
  return data;
46
49
  };
47
50
  }
48
51
  exports.fetchBuilder = fetchBuilder;
49
52
  function graphqlRequestBody(query, variables) {
50
- const queryString = typeof query === 'string' ? query : graphql_1.print(query);
53
+ const queryString = typeof query === 'string' ? query : (0, graphql_1.print)(query);
51
54
  return JSON.stringify({
52
55
  query: queryString,
53
56
  variables,
@@ -55,12 +58,6 @@ function graphqlRequestBody(query, variables) {
55
58
  }
56
59
  exports.graphqlRequestBody = graphqlRequestBody;
57
60
  function decodeShopifyId(id) {
58
- if (!id.startsWith('gid://')) {
59
- id =
60
- typeof btoa !== 'undefined'
61
- ? btoa(id)
62
- : Buffer.from(id, 'base64').toString('ascii');
63
- }
64
61
  if (!id.startsWith('gid://')) {
65
62
  throw new Error('invalid Shopify ID');
66
63
  }
@@ -8,3 +8,4 @@ export { isServer } from './isServer';
8
8
  export { getMeasurementAsParts, getMeasurementAsString } from './measurement';
9
9
  export { parseMetafieldValue } from './parseMetafieldValue';
10
10
  export { fetchBuilder, graphqlRequestBody, decodeShopifyId } from './fetch';
11
+ export { getTime } from './timing';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decodeShopifyId = exports.graphqlRequestBody = exports.fetchBuilder = exports.parseMetafieldValue = exports.getMeasurementAsString = exports.getMeasurementAsParts = exports.isServer = exports.isClient = exports.flattenConnection = exports.wrapPromise = exports.loadScript = exports.useEmbeddedVideoUrl = exports.addParametersToEmbeddedVideoUrl = exports.shopifyImageLoader = exports.getShopifyImageDimensions = exports.useImageUrl = exports.addImageSizeParametersToUrl = void 0;
3
+ exports.getTime = exports.decodeShopifyId = exports.graphqlRequestBody = exports.fetchBuilder = exports.parseMetafieldValue = exports.getMeasurementAsString = exports.getMeasurementAsParts = exports.isServer = exports.isClient = exports.flattenConnection = exports.wrapPromise = exports.loadScript = exports.useEmbeddedVideoUrl = exports.addParametersToEmbeddedVideoUrl = exports.shopifyImageLoader = exports.getShopifyImageDimensions = exports.useImageUrl = exports.addImageSizeParametersToUrl = void 0;
4
4
  var image_size_1 = require("./image_size");
5
5
  Object.defineProperty(exports, "addImageSizeParametersToUrl", { enumerable: true, get: function () { return image_size_1.addImageSizeParametersToUrl; } });
6
6
  Object.defineProperty(exports, "useImageUrl", { enumerable: true, get: function () { return image_size_1.useImageUrl; } });
@@ -28,3 +28,5 @@ var fetch_1 = require("./fetch");
28
28
  Object.defineProperty(exports, "fetchBuilder", { enumerable: true, get: function () { return fetch_1.fetchBuilder; } });
29
29
  Object.defineProperty(exports, "graphqlRequestBody", { enumerable: true, get: function () { return fetch_1.graphqlRequestBody; } });
30
30
  Object.defineProperty(exports, "decodeShopifyId", { enumerable: true, get: function () { return fetch_1.decodeShopifyId; } });
31
+ var timing_1 = require("./timing");
32
+ Object.defineProperty(exports, "getTime", { enumerable: true, get: function () { return timing_1.getTime; } });
@@ -6,6 +6,6 @@ const isClient_1 = require("../isClient");
6
6
  * if the code was run on the server.
7
7
  */
8
8
  function isServer() {
9
- return !isClient_1.isClient();
9
+ return !(0, isClient_1.isClient)();
10
10
  }
11
11
  exports.isServer = isServer;
@@ -2,6 +2,6 @@
2
2
  * Wrap the fetch promise in a way that React Suspense understands.
3
3
  * Essentially, keep throwing something until you have legit data.
4
4
  */
5
- export declare function wrapPromise(promise: Promise<any>): {
6
- read: () => any;
5
+ export declare function wrapPromise<T>(promise: Promise<T>): {
6
+ read: () => T;
7
7
  };
@@ -14,14 +14,8 @@ function wrapPromise(promise) {
14
14
  }, (err) => {
15
15
  status = 'error';
16
16
  response = err;
17
- throw err;
18
17
  });
19
18
  const read = () => {
20
- /**
21
- * TODO: This logic doesn't hold up when an error is thrown. For some reason.
22
- * We instead throw the exception above in the suspender. We should revisit
23
- * this and add a better server fetch implementation.
24
- */
25
19
  switch (status) {
26
20
  case 'pending':
27
21
  throw suspender;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Not all environments have access to Performance.now(). This is to prevent
3
+ * timing side channel attacks.
4
+ *
5
+ * See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
6
+ */
7
+ export declare function getTime(): number;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTime = void 0;
4
+ /**
5
+ * Not all environments have access to Performance.now(). This is to prevent
6
+ * timing side channel attacks.
7
+ *
8
+ * See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
9
+ */
10
+ function getTime() {
11
+ if (typeof performance !== 'undefined' && performance.now) {
12
+ return performance.now();
13
+ }
14
+ else {
15
+ return Date.now();
16
+ }
17
+ }
18
+ exports.getTime = getTime;
@@ -17,7 +17,7 @@ function addParametersToEmbeddedVideoUrl(url, parameters) {
17
17
  }
18
18
  exports.addParametersToEmbeddedVideoUrl = addParametersToEmbeddedVideoUrl;
19
19
  function useEmbeddedVideoUrl(url, parameters) {
20
- return react_1.useMemo(() => {
20
+ return (0, react_1.useMemo)(() => {
21
21
  if (!parameters) {
22
22
  return url;
23
23
  }
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.7.0";
1
+ export declare const LIB_VERSION = "0.8.2";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIB_VERSION = void 0;
4
- exports.LIB_VERSION = '0.7.0';
4
+ exports.LIB_VERSION = '0.8.2';
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export declare class ServerComponentRequest extends Request {
9
9
  cookies: Map<string, string>;
10
+ time: number;
10
11
  constructor(input: any);
11
12
  constructor(input: RequestInfo, init?: RequestInit);
12
13
  private parseCookies;
@@ -1,3 +1,4 @@
1
+ import { getTime } from '../../utilities/timing';
1
2
  /**
2
3
  * This augments the `Request` object from the Fetch API:
3
4
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Request
@@ -16,6 +17,7 @@ export class ServerComponentRequest extends Request {
16
17
  method: input.method,
17
18
  });
18
19
  }
20
+ this.time = getTime();
19
21
  this.cookies = this.parseCookies();
20
22
  }
21
23
  parseCookies() {
@@ -1,5 +1,4 @@
1
- import type { QueryKey } from 'react-query';
2
- import type { CacheOptions } from '../types';
1
+ import type { CacheOptions, QueryKey } from '../types';
3
2
  export declare function generateCacheControlHeader(options: CacheOptions): string;
4
3
  /**
5
4
  * Use a preview header during development.
@@ -9,6 +8,7 @@ export declare function generateCacheControlHeader(options: CacheOptions): strin
9
8
  export declare function getCacheControlHeader({ dev }: {
10
9
  dev?: boolean;
11
10
  }): "cache-control-preview" | "cache-control";
11
+ export declare function hashKey(key: QueryKey): string;
12
12
  /**
13
13
  * Get an item from the cache. If a match is found, returns a tuple
14
14
  * containing the `JSON.parse` version of the response as well
@@ -21,7 +21,7 @@ export function generateCacheControlHeader(options) {
21
21
  export function getCacheControlHeader({ dev }) {
22
22
  return dev ? 'cache-control-preview' : 'cache-control';
23
23
  }
24
- function hashKey(key) {
24
+ export function hashKey(key) {
25
25
  const rawKey = key instanceof Array ? key : [key];
26
26
  /**
27
27
  * TODO: Smarter hash
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@types/node" />
2
- import type { ServerResponse } from 'http';
2
+ import { ServerResponse } from 'http';
3
3
  import type { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
4
4
  import { RuntimeContext } from './framework/runtime';
5
5
  interface HydrogenFetchEvent {
@@ -27,7 +27,8 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
27
27
  throw new Error(`entry-server.jsx could not be loaded. This likely occurred because of a Vite compilation error.\n` +
28
28
  `Please check your server logs for more information.`);
29
29
  }
30
- const isStreamable = streamableResponse && isStreamableRequest(url);
30
+ const userAgent = request.headers.get('user-agent');
31
+ const isStreamable = streamableResponse && !isBotUA(url, userAgent);
31
32
  /**
32
33
  * Stream back real-user responses, but for bots/etc,
33
34
  * use `render` instead. This is because we need to inject <head>
@@ -35,7 +36,12 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
35
36
  */
36
37
  if (isStreamable) {
37
38
  if (isReactHydrationRequest) {
38
- hydrate(url, { context: {}, request, response: streamableResponse, dev });
39
+ hydrate(url, {
40
+ context: {},
41
+ request,
42
+ response: streamableResponse,
43
+ dev,
44
+ });
39
45
  }
40
46
  else {
41
47
  stream(url, {
@@ -48,7 +54,12 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
48
54
  }
49
55
  return;
50
56
  }
51
- const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, { request, context: {}, isReactHydrationRequest, dev });
57
+ const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, {
58
+ request,
59
+ context: {},
60
+ isReactHydrationRequest,
61
+ dev,
62
+ });
52
63
  const headers = componentResponse.headers;
53
64
  /**
54
65
  * TODO: Also add `Vary` headers for `accept-language` and any other keys
@@ -86,13 +97,6 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
86
97
  }
87
98
  return response;
88
99
  }
89
- function isStreamableRequest(url) {
90
- /**
91
- * TODO: Add UA detection.
92
- */
93
- const isBot = url.searchParams.has('_bot');
94
- return !isBot;
95
- }
96
100
  /**
97
101
  * Generate the contents of the `head` tag, and update the existing `<title>` tag
98
102
  * if one exists, and if a title is passed.
@@ -117,3 +121,57 @@ function generateHeadTag(head) {
117
121
  return `<head>${headHtml}</head>`;
118
122
  };
119
123
  }
124
+ /**
125
+ * Determines if the request is from a bot, using the URL and User Agent
126
+ */
127
+ function isBotUA(url, userAgent) {
128
+ return (url.searchParams.has('_bot') || (!!userAgent && botUARegex.test(userAgent)));
129
+ }
130
+ /**
131
+ * An alphabetized list of User Agents of known bots, combined from lists found at:
132
+ * https://github.com/vercel/next.js/blob/d87dc2b5a0b3fdbc0f6806a47be72bad59564bd0/packages/next/server/utils.ts#L18-L22
133
+ * https://github.com/GoogleChrome/rendertron/blob/6f681688737846b28754fbfdf5db173846a826df/middleware/src/middleware.ts#L24-L41
134
+ */
135
+ const botUserAgents = [
136
+ 'AdsBot-Google',
137
+ 'applebot',
138
+ 'Baiduspider',
139
+ 'baiduspider',
140
+ 'bingbot',
141
+ 'Bingbot',
142
+ 'BingPreview',
143
+ 'bitlybot',
144
+ 'Discordbot',
145
+ 'DuckDuckBot',
146
+ 'Embedly',
147
+ 'facebookcatalog',
148
+ 'facebookexternalhit',
149
+ 'Google-PageRenderer',
150
+ 'Googlebot',
151
+ 'googleweblight',
152
+ 'ia_archive',
153
+ 'LinkedInBot',
154
+ 'Mediapartners-Google',
155
+ 'outbrain',
156
+ 'pinterest',
157
+ 'quora link preview',
158
+ 'redditbot',
159
+ 'rogerbot',
160
+ 'showyoubot',
161
+ 'SkypeUriPreview',
162
+ 'Slackbot',
163
+ 'Slurp',
164
+ 'sogou',
165
+ 'Storebot-Google',
166
+ 'TelegramBot',
167
+ 'tumblr',
168
+ 'Twitterbot',
169
+ 'vkShare',
170
+ 'W3C_Validator',
171
+ 'WhatsApp',
172
+ 'yandex',
173
+ ];
174
+ /**
175
+ * Creates a regex based on the botUserAgents array
176
+ */
177
+ const botUARegex = new RegExp(botUserAgents.join('|'), 'i');
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@types/node" />
2
- import type { ServerResponse } from 'http';
2
+ import { ServerResponse } from 'http';
3
3
  import type { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
4
4
  import type { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
5
5
  import type { Metafield, Image, MediaContentType } from './graphql/types/types';
@@ -78,6 +78,7 @@ export interface Measurement {
78
78
  unit: string;
79
79
  value: number;
80
80
  }
81
+ export declare type QueryKey = string | readonly unknown[];
81
82
  export interface CacheOptions {
82
83
  private?: boolean;
83
84
  maxAge?: number;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Not all environments have access to Performance.now(). This is to prevent
3
+ * timing side channel attacks.
4
+ *
5
+ * See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
6
+ */
7
+ export declare function getTime(): number;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Not all environments have access to Performance.now(). This is to prevent
3
+ * timing side channel attacks.
4
+ *
5
+ * See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
6
+ */
7
+ export function getTime() {
8
+ if (typeof performance !== 'undefined' && performance.now) {
9
+ return performance.now();
10
+ }
11
+ else {
12
+ return Date.now();
13
+ }
14
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "0.7.0",
7
+ "version": "0.8.2",
8
8
  "description": "Modern custom Shopify storefronts",
9
9
  "license": "MIT",
10
10
  "main": "dist/esnext/index.js",
@@ -68,36 +68,36 @@
68
68
  "@types/react": "^17.0.3",
69
69
  "@types/react-dom": "^17.0.3",
70
70
  "@types/react-router-dom": "^5.1.7",
71
+ "@types/ws": "^8.2.0",
71
72
  "babel-loader": "^8.2.2",
72
73
  "cpy-cli": "3.1.1",
73
74
  "mkdirp": "^1.0.4",
74
75
  "postcss": "^8",
75
76
  "raw-loader": "^4.0.2",
76
- "rimraf": "^3.0.2",
77
- "tailwindcss": "^2.1.1"
77
+ "rimraf": "^3.0.2"
78
78
  },
79
79
  "peerDependencies": {
80
- "react": "^18",
81
- "react-dom": "^18",
80
+ "react": "0.0.0-experimental-0cc724c77-20211125",
81
+ "react-dom": "0.0.0-experimental-0cc724c77-20211125",
82
82
  "react-router-dom": "^5.2.0",
83
- "vite": "^2.6.14"
83
+ "vite": "^2.7.0"
84
84
  },
85
85
  "dependencies": {
86
- "@vitejs/plugin-react": "^1.0.8",
86
+ "@vitejs/plugin-react": "^1.1.1",
87
87
  "connect": "^3.7.0",
88
88
  "es-module-lexer": "^0.9.0",
89
89
  "fast-glob": "^3.2.5",
90
- "graphql": "^15.5.0",
90
+ "graphql": "^16.0.1",
91
91
  "html-dom-parser": "^1.0.1",
92
92
  "html-react-parser": "^1.2.6",
93
93
  "isomorphic-dompurify": "^0.13.0",
94
+ "kolorist": "^1.5.1",
94
95
  "magic-string": "^0.25.7",
95
96
  "node-fetch": "^2.6.1",
96
97
  "react-error-boundary": "^3.1.3",
97
98
  "react-helmet-async": "^1.0.9",
98
- "react-query": "^3.18.1",
99
99
  "react-ssr-prepass": "^1.4.0",
100
100
  "vite-plugin-inspect": "^0.3.6"
101
101
  },
102
- "gitHead": "29de6d971edb5f91199362723594d29ed4765260"
102
+ "gitHead": "587f117221ab94a06165e69602658c4e561f5723"
103
103
  }
@@ -1,6 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import type { ReactQueryHydrationContext } from '../../foundation/ShopifyProvider/types';
3
- export declare function QueryProvider({ children, hydrationContext, }: {
4
- children: ReactNode;
5
- hydrationContext?: ReactQueryHydrationContext;
6
- }): JSX.Element;
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- import { QueryClientProvider, QueryClient } from 'react-query';
3
- import { Hydrate } from 'react-query/hydration';
4
- export function QueryProvider({ children, hydrationContext, }) {
5
- const queryClient = new QueryClient({
6
- defaultOptions: { queries: { suspense: true } },
7
- });
8
- if (hydrationContext) {
9
- hydrationContext.queryClient = queryClient;
10
- }
11
- return (React.createElement(QueryClientProvider, { client: queryClient },
12
- React.createElement(Hydrate, { state: hydrationContext === null || hydrationContext === void 0 ? void 0 : hydrationContext.dehydratedState }, children)));
13
- }