@shopify/hydrogen 0.11.1-experimental.1 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,24 +1,30 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ## 0.11.1
4
4
 
5
- The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
- and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
5
+ ### Patch Changes
7
6
 
8
- <!-- ## [Unreleased] -->
7
+ - [#770](https://github.com/Shopify/hydrogen/pull/770) [`71e0255`](https://github.com/Shopify/hydrogen/commit/71e0255ea48dc1caa34d2c05a1556cc0ce6d4ce9) Thanks [@mcvinci](https://github.com/mcvinci)! - Hydrogen docs: Framework and global hooks content updates
9
8
 
10
- ## 0.11.1-experimental.1
9
+ * [#761](https://github.com/Shopify/hydrogen/pull/761) [`1142647`](https://github.com/Shopify/hydrogen/commit/114264716bc8f3027e3e6395d523714adbc92014) Thanks [@frehner](https://github.com/frehner)! - Fix issue with components that take in the `as` prop not validating other props when a component is passed to `as`.
11
10
 
12
- ### Patch Changes
11
+ - [#752](https://github.com/Shopify/hydrogen/pull/752) [`b96179f`](https://github.com/Shopify/hydrogen/commit/b96179fdf960da52332a981e29a742b677826834) Thanks [@jplhomer](https://github.com/jplhomer)! - Ensure ProductSeo knows how to handle `featuredImage = null`
13
12
 
14
- - 11426471: Fix issue with components that take in the `as` prop not validating other props when a component is passed to `as`.
15
- - b96179fd: Ensure ProductSeo knows how to handle `featuredImage = null`
16
- - 2e487b7e: Switch to using Changesets for changelogs.
17
- - 2d8ab7e2: Hydrogen docs: Preloaded queries and query timing
13
+ * [#774](https://github.com/Shopify/hydrogen/pull/774) [`052f148`](https://github.com/Shopify/hydrogen/commit/052f148e0d33029cdc2540afa5ead32825962f3a) Thanks [@frandiox](https://github.com/frandiox)! - Fix internal url usage in platforms like Vercel, which already provides protocol and host in `request.url`.
18
14
 
19
- ### Fixed
15
+ - [#744](https://github.com/Shopify/hydrogen/pull/744) [`2e487b7`](https://github.com/Shopify/hydrogen/commit/2e487b7e70fe0572538dc2a24b6b6b36ba9fc804) Thanks [@jplhomer](https://github.com/jplhomer)! - Switch to using Changesets for changelogs.
16
+
17
+ * [#775](https://github.com/Shopify/hydrogen/pull/775) [`d5b7ee1`](https://github.com/Shopify/hydrogen/commit/d5b7ee1d8312f64922d1f78afc82ec5ad4a3f457) Thanks [@cartogram](https://github.com/cartogram)! - In cases where the `initialVariantId` is missing on the `<ProductProvider />`, the `selectedVariantId` in the returned `object` from `useProduct()` will now use the first available variant _or_ the first variant (if non are available).
18
+
19
+ - [#773](https://github.com/Shopify/hydrogen/pull/773) [`b6a053e`](https://github.com/Shopify/hydrogen/commit/b6a053e774da443b5692dec51546f5558b3018ad) Thanks [@frandiox](https://github.com/frandiox)! - Fix server bundle name in cases where CSS or images are imported in server components.
20
+
21
+ * [#764](https://github.com/Shopify/hydrogen/pull/764) [`5e55da4`](https://github.com/Shopify/hydrogen/commit/5e55da4090692369ff6a3d57fbc6d29124bdf2e9) Thanks [@wizardlyhel](https://github.com/wizardlyhel)! - Preload queries breaking fetch on Cloudfare [#764](https://github.com/Shopify/hydrogen/pull/764)
22
+
23
+ - [#763](https://github.com/Shopify/hydrogen/pull/763) [`ea2857a`](https://github.com/Shopify/hydrogen/commit/ea2857a515866f37f392bca5da8be1139c055a64) Thanks [@frehner](https://github.com/frehner)! - Client-side apps now have React's `StrictMode` component wrapping the whole app, with an option to disable it. If you do turn it off, it is recommended that you still include the `StrictMode` component at as high of a level as possible in your React tree.
24
+
25
+ See also [React 17's docs](https://reactjs.org/docs/strict-mode.html) on `StrictMode`, and [React 18's updates](https://github.com/reactwg/react-18/discussions/19) to `StrictMode`.
20
26
 
21
- - Make `featuredImage` optional and handle it in <Seo />
27
+ * [#747](https://github.com/Shopify/hydrogen/pull/747) [`2d8ab7e`](https://github.com/Shopify/hydrogen/commit/2d8ab7e2a8038ff8b43e6e9398e0bb2da72220a0) Thanks [@mcvinci](https://github.com/mcvinci)! - Hydrogen docs: Preloaded queries and query timing
22
28
 
23
29
  ## [0.11.0] - 2022-02-24
24
30
 
@@ -6,12 +6,12 @@ import { useProduct } from '../ProductProvider';
6
6
  * It must be a descendent of the `CartProvider` component.
7
7
  */
8
8
  export function AddToCartButton(props) {
9
- var _a, _b, _c, _d, _e, _f, _g, _h;
9
+ var _a, _b;
10
10
  const [addingItem, setAddingItem] = useState(false);
11
11
  const { variantId: explicitVariantId, quantity = 1, attributes, children, onAdd, accessibleAddingToCartLabel, ...passthroughProps } = props;
12
12
  const { status, id, cartCreate, linesAdd } = useCart();
13
13
  const product = useProduct();
14
- const variantId = (_h = (_e = (_b = explicitVariantId !== null && explicitVariantId !== void 0 ? explicitVariantId : (_a = product === null || product === void 0 ? void 0 : product.selectedVariant) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_d = (_c = product === null || product === void 0 ? void 0 : product.variants) === null || _c === void 0 ? void 0 : _c.find((variant) => variant.availableForSale)) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : (_g = (_f = product === null || product === void 0 ? void 0 : product.variants) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.id) !== null && _h !== void 0 ? _h : '';
14
+ const variantId = (_b = explicitVariantId !== null && explicitVariantId !== void 0 ? explicitVariantId : (_a = product === null || product === void 0 ? void 0 : product.selectedVariant) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '';
15
15
  const disabled = explicitVariantId === null ||
16
16
  variantId === '' ||
17
17
  (product === null || product === void 0 ? void 0 : product.selectedVariant) === null ||
@@ -1,19 +1,22 @@
1
- import React, { Suspense, useState } from 'react';
2
- // @ts-ignore
1
+ import React, { Suspense, useState, StrictMode, Fragment, } from 'react';
2
+ // @ts-expect-error hydrateRoot isn't on the TS types yet, but we're using React 18 so it exists
3
3
  import { hydrateRoot } from 'react-dom';
4
4
  import { ErrorBoundary } from 'react-error-boundary';
5
5
  import { useServerResponse } from './framework/Hydration/rsc';
6
6
  import { ServerStateProvider } from './client';
7
7
  import { Router } from './foundation/Router/Router.client';
8
- const renderHydrogen = async (ClientWrapper) => {
8
+ const renderHydrogen = async (ClientWrapper, config) => {
9
9
  const root = document.getElementById('root');
10
10
  if (!root) {
11
11
  console.error(`Could not find a root element <div id="root"></div> to render.`);
12
12
  return;
13
13
  }
14
- hydrateRoot(root, React.createElement(ErrorBoundary, { FallbackComponent: Error },
15
- React.createElement(Suspense, { fallback: null },
16
- React.createElement(Content, { clientWrapper: ClientWrapper }))));
14
+ // default to StrictMode on, unless explicitly turned off
15
+ const RootComponent = (config === null || config === void 0 ? void 0 : config.strictMode) !== false ? StrictMode : Fragment;
16
+ hydrateRoot(root, React.createElement(RootComponent, null,
17
+ React.createElement(ErrorBoundary, { FallbackComponent: Error },
18
+ React.createElement(Suspense, { fallback: null },
19
+ React.createElement(Content, { clientWrapper: ClientWrapper })))));
17
20
  };
18
21
  export default renderHydrogen;
19
22
  function Content({ clientWrapper: ClientWrapper = ({ children }) => children, }) {
@@ -96,7 +96,14 @@ export function preloadRequestCacheData(request, preloadQueries) {
96
96
  promise = preloadQuery.fetcher().then((r) => {
97
97
  data = { data: r };
98
98
  collectQueryTimings(request, preloadQuery.key, 'resolved', getTime() - startApiTime);
99
- }, (e) => (data = { error: e }));
99
+ }, (e) => {
100
+ // The preload query failed for some reason:
101
+ // On Cloudfare, this happens when a Cache item has expired at max-age
102
+ //
103
+ // We need to remove this entry from cache so that render cycle will retry on its own
104
+ cache.delete(cacheKey);
105
+ collectQueryTimings(request, preloadQuery.key, 'expired', getTime() - startApiTime);
106
+ });
100
107
  }
101
108
  return promise;
102
109
  });
@@ -11,9 +11,10 @@ export interface HydrogenUseQueryOptions {
11
11
  preload?: PreloadOptions;
12
12
  }
13
13
  /**
14
- * The `useQuery` hook is a wrapper around Suspense calls and
15
- * global runtime's Cache if it exists.
16
- * It supports Suspense calls on the server and on the client.
14
+ * The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
15
+ * supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). It's based
16
+ * on [react-query](https://react-query.tanstack.com/reference/useQuery). You can use this
17
+ * hook to call any third-party APIs.
17
18
  */
18
19
  export declare function useQuery<T>(
19
20
  /** A string or array to uniquely identify the current query. */
@@ -23,7 +24,9 @@ queryFn: () => Promise<T>,
23
24
  /** The options to manage the cache behavior of the sub-request. */
24
25
  queryOptions?: HydrogenUseQueryOptions): {
25
26
  data?: undefined;
26
- error: Response;
27
+ error: Response; /**
28
+ * Attempt to read the query from cache. If it doesn't exist or if it's stale, regenerate it.
29
+ */
27
30
  } | {
28
31
  data: T;
29
32
  error?: undefined;
@@ -3,9 +3,10 @@ import { deleteItemFromCache, generateSubRequestCacheControlHeader, getItemFromC
3
3
  import { runDelayedFunction } from '../../framework/runtime';
4
4
  import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
5
5
  /**
6
- * The `useQuery` hook is a wrapper around Suspense calls and
7
- * global runtime's Cache if it exists.
8
- * It supports Suspense calls on the server and on the client.
6
+ * The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
7
+ * supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). It's based
8
+ * on [react-query](https://react-query.tanstack.com/reference/useQuery). You can use this
9
+ * hook to call any third-party APIs.
9
10
  */
10
11
  export function useQuery(
11
12
  /** A string or array to uniquely identify the current query. */
@@ -100,7 +100,10 @@ function saveToPreloadAllPreload(query) {
100
100
  */
101
101
  function getUrlFromNodeRequest(request) {
102
102
  var _a;
103
+ const url = (_a = request.originalUrl) !== null && _a !== void 0 ? _a : request.url;
104
+ if (url && !url.startsWith('/'))
105
+ return url;
103
106
  // TODO: Find out how to determine https from `request` object without forwarded proto
104
107
  const secure = request.headers['x-forwarded-proto'] === 'https';
105
- return new URL(`${secure ? 'https' : 'http'}://${request.headers.host + ((_a = request.originalUrl) !== null && _a !== void 0 ? _a : request.url)}`).toString();
108
+ return new URL(`${secure ? 'https' : 'http'}://${request.headers.host + url}`).toString();
106
109
  }
@@ -43,7 +43,7 @@ export default () => {
43
43
  },
44
44
  generateBundle(options, bundle) {
45
45
  if (config.build.ssr) {
46
- const [[key, value]] = Object.entries(bundle);
46
+ const [key, value] = Object.entries(bundle).find(([, value]) => value.type === 'chunk' && value.isEntry);
47
47
  delete bundle[key];
48
48
  value.fileName = SSR_BUNDLE_NAME;
49
49
  bundle[SSR_BUNDLE_NAME] = value;
@@ -4,7 +4,7 @@ import { GraphQLConnection } from '../../types';
4
4
  * The `useProductOptions` hook returns an object that enables you to keep track of the
5
5
  * selected variant and/or selling plan state, as well as callbacks for modifying the state.
6
6
  */
7
- export declare function useProductOptions({ variants: variantsConnection, sellingPlanGroups: sellingPlanGroupsConnection, initialVariantId, }: {
7
+ export declare function useProductOptions({ variants: variantsConnection, sellingPlanGroups: sellingPlanGroupsConnection, initialVariantId: explicitVariantId, }: {
8
8
  /** The product's `VariantConnection`. */
9
9
  variants?: GraphQLConnection<Variant>;
10
10
  /** The product's `SellingPlanGroups`. */
@@ -5,18 +5,21 @@ import { getOptions, getSelectedVariant } from './helpers';
5
5
  * The `useProductOptions` hook returns an object that enables you to keep track of the
6
6
  * selected variant and/or selling plan state, as well as callbacks for modifying the state.
7
7
  */
8
- export function useProductOptions({ variants: variantsConnection, sellingPlanGroups: sellingPlanGroupsConnection, initialVariantId, }) {
8
+ export function useProductOptions({ variants: variantsConnection, sellingPlanGroups: sellingPlanGroupsConnection, initialVariantId: explicitVariantId, }) {
9
9
  // The flattened variants
10
10
  const variants = useMemo(() => (variantsConnection ? flattenConnection(variantsConnection) : []), [variantsConnection]);
11
11
  // All the options available for a product, based on all the variants
12
12
  const options = useMemo(() => getOptions(variants), [variants]);
13
+ const initialVariantId = explicitVariantId === null
14
+ ? explicitVariantId
15
+ : variants.find((variant) => variant.id === explicitVariantId) ||
16
+ variants.find((variant) => variant.availableForSale) ||
17
+ variants[0];
13
18
  /**
14
19
  * Track the selectedVariant within the hook. If `initialVariantId`
15
20
  * is passed, use that as an initial value.
16
21
  */
17
- const [selectedVariant, setSelectedVariant] = useState(initialVariantId == null
18
- ? initialVariantId
19
- : variants.find((variant) => variant.id === initialVariantId));
22
+ const [selectedVariant, setSelectedVariant] = useState(initialVariantId);
20
23
  /**
21
24
  * Track the selectedOptions within the hook. If a `initialVariantId`
22
25
  * is passed, use that to select initial options.
@@ -34,10 +37,7 @@ export function useProductOptions({ variants: variantsConnection, sellingPlanGro
34
37
  * values.
35
38
  */
36
39
  useEffect(() => {
37
- const selectedVariant = initialVariantId == null
38
- ? initialVariantId
39
- : variants.find((variant) => variant.id === initialVariantId);
40
- setSelectedVariant(selectedVariant);
40
+ setSelectedVariant(initialVariantId);
41
41
  const selectedOptions = (selectedVariant === null || selectedVariant === void 0 ? void 0 : selectedVariant.selectedOptions)
42
42
  ? selectedVariant.selectedOptions.reduce((memo, optionSet) => {
43
43
  memo[optionSet.name] = optionSet.value;
@@ -41,8 +41,10 @@ export declare type ServerHandlerConfig = {
41
41
  };
42
42
  export declare type ClientHandlerConfig = {
43
43
  shopifyConfig: ShopifyConfig;
44
+ /** React's StrictMode is on by default for your client side app; if you want to turn it off (not recommended), you can pass `false` */
45
+ strictMode?: boolean;
44
46
  };
45
- export declare type ClientHandler = (App: any, config: ClientHandlerConfig) => Promise<void>;
47
+ export declare type ClientHandler = (App: React.ElementType, config: ClientHandlerConfig) => Promise<void>;
46
48
  export interface GraphQLConnection<T> {
47
49
  edges?: {
48
50
  node: T;
@@ -1,7 +1,7 @@
1
1
  import { ServerComponentRequest } from '../../framework/Hydration/ServerComponentRequest.server';
2
2
  import { QueryKey } from '../../types';
3
3
  import type { RenderType } from './log';
4
- export declare type TimingType = 'requested' | 'resolved' | 'rendered' | 'preload';
4
+ export declare type TimingType = 'requested' | 'resolved' | 'rendered' | 'preload' | 'expired';
5
5
  export declare type QueryTiming = {
6
6
  name: string;
7
7
  timingType: TimingType;
@@ -9,6 +9,7 @@ const TIMING_MAPPING = {
9
9
  rendered: 'Rendered',
10
10
  resolved: 'Resolved',
11
11
  preload: 'Preload',
12
+ expired: 'Expired',
12
13
  };
13
14
  export function collectQueryTimings(request, queryKey, timingType, duration) {
14
15
  request.ctx.queryTimings.push({
@@ -31,7 +32,6 @@ export function logQueryTimings(type, request) {
31
32
  const detectMultipleDataLoad = {};
32
33
  let suspenseWaterfallDetectedCount = 0;
33
34
  queryList.forEach((query, index) => {
34
- var _a;
35
35
  if (query.timingType === 'requested' || query.timingType === 'preload') {
36
36
  detectSuspenseWaterfall[query.name] = true;
37
37
  }
@@ -44,8 +44,9 @@ export function logQueryTimings(type, request) {
44
44
  : 1;
45
45
  }
46
46
  const loadColor = query.timingType === 'preload' ? green : color;
47
- log.debug(color(`│ ${`${(query.timestamp - requestStartTime).toFixed(2)}ms`.padEnd(10)} ${loadColor(TIMING_MAPPING[query.timingType].padEnd(10))} ${query.name}${query.timingType === 'resolved'
48
- ? ` (Took ${(_a = query.duration) === null || _a === void 0 ? void 0 : _a.toFixed(2)}ms)`
47
+ const duration = query.duration;
48
+ log.debug(color(`│ ${`${(query.timestamp - requestStartTime).toFixed(2)}ms`.padEnd(10)} ${loadColor(TIMING_MAPPING[query.timingType].padEnd(10))} ${query.name}${query.timingType === 'resolved' || query.timingType === 'expired'
49
+ ? ` (Took ${duration === null || duration === void 0 ? void 0 : duration.toFixed(2)}ms)`
49
50
  : ''}`));
50
51
  // SSR + RSC render path generates 2 `load` and `render` for each query
51
52
  // We want to avoid falsely identifying a suspense waterfall near the end
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.11.0";
1
+ export declare const LIB_VERSION = "0.11.1";
@@ -1 +1 @@
1
- export const LIB_VERSION = '0.11.0';
1
+ export const LIB_VERSION = '0.11.1';
@@ -121,7 +121,14 @@ function preloadRequestCacheData(request, preloadQueries) {
121
121
  promise = preloadQuery.fetcher().then((r) => {
122
122
  data = { data: r };
123
123
  (0, log_1.collectQueryTimings)(request, preloadQuery.key, 'resolved', (0, timing_1.getTime)() - startApiTime);
124
- }, (e) => (data = { error: e }));
124
+ }, (e) => {
125
+ // The preload query failed for some reason:
126
+ // On Cloudfare, this happens when a Cache item has expired at max-age
127
+ //
128
+ // We need to remove this entry from cache so that render cycle will retry on its own
129
+ cache.delete(cacheKey);
130
+ (0, log_1.collectQueryTimings)(request, preloadQuery.key, 'expired', (0, timing_1.getTime)() - startApiTime);
131
+ });
125
132
  }
126
133
  return promise;
127
134
  });
@@ -104,7 +104,10 @@ function saveToPreloadAllPreload(query) {
104
104
  */
105
105
  function getUrlFromNodeRequest(request) {
106
106
  var _a;
107
+ const url = (_a = request.originalUrl) !== null && _a !== void 0 ? _a : request.url;
108
+ if (url && !url.startsWith('/'))
109
+ return url;
107
110
  // TODO: Find out how to determine https from `request` object without forwarded proto
108
111
  const secure = request.headers['x-forwarded-proto'] === 'https';
109
- return new URL(`${secure ? 'https' : 'http'}://${request.headers.host + ((_a = request.originalUrl) !== null && _a !== void 0 ? _a : request.url)}`).toString();
112
+ return new URL(`${secure ? 'https' : 'http'}://${request.headers.host + url}`).toString();
110
113
  }
@@ -48,7 +48,7 @@ exports.default = () => {
48
48
  },
49
49
  generateBundle(options, bundle) {
50
50
  if (config.build.ssr) {
51
- const [[key, value]] = Object.entries(bundle);
51
+ const [key, value] = Object.entries(bundle).find(([, value]) => value.type === 'chunk' && value.isEntry);
52
52
  delete bundle[key];
53
53
  value.fileName = SSR_BUNDLE_NAME;
54
54
  bundle[SSR_BUNDLE_NAME] = value;
@@ -41,8 +41,10 @@ export declare type ServerHandlerConfig = {
41
41
  };
42
42
  export declare type ClientHandlerConfig = {
43
43
  shopifyConfig: ShopifyConfig;
44
+ /** React's StrictMode is on by default for your client side app; if you want to turn it off (not recommended), you can pass `false` */
45
+ strictMode?: boolean;
44
46
  };
45
- export declare type ClientHandler = (App: any, config: ClientHandlerConfig) => Promise<void>;
47
+ export declare type ClientHandler = (App: React.ElementType, config: ClientHandlerConfig) => Promise<void>;
46
48
  export interface GraphQLConnection<T> {
47
49
  edges?: {
48
50
  node: T;
@@ -1,7 +1,7 @@
1
1
  import { ServerComponentRequest } from '../../framework/Hydration/ServerComponentRequest.server';
2
2
  import { QueryKey } from '../../types';
3
3
  import type { RenderType } from './log';
4
- export declare type TimingType = 'requested' | 'resolved' | 'rendered' | 'preload';
4
+ export declare type TimingType = 'requested' | 'resolved' | 'rendered' | 'preload' | 'expired';
5
5
  export declare type QueryTiming = {
6
6
  name: string;
7
7
  timingType: TimingType;
@@ -12,6 +12,7 @@ const TIMING_MAPPING = {
12
12
  rendered: 'Rendered',
13
13
  resolved: 'Resolved',
14
14
  preload: 'Preload',
15
+ expired: 'Expired',
15
16
  };
16
17
  function collectQueryTimings(request, queryKey, timingType, duration) {
17
18
  request.ctx.queryTimings.push({
@@ -35,7 +36,6 @@ function logQueryTimings(type, request) {
35
36
  const detectMultipleDataLoad = {};
36
37
  let suspenseWaterfallDetectedCount = 0;
37
38
  queryList.forEach((query, index) => {
38
- var _a;
39
39
  if (query.timingType === 'requested' || query.timingType === 'preload') {
40
40
  detectSuspenseWaterfall[query.name] = true;
41
41
  }
@@ -48,8 +48,9 @@ function logQueryTimings(type, request) {
48
48
  : 1;
49
49
  }
50
50
  const loadColor = query.timingType === 'preload' ? kolorist_1.green : color;
51
- log.debug(color(`│ ${`${(query.timestamp - requestStartTime).toFixed(2)}ms`.padEnd(10)} ${loadColor(TIMING_MAPPING[query.timingType].padEnd(10))} ${query.name}${query.timingType === 'resolved'
52
- ? ` (Took ${(_a = query.duration) === null || _a === void 0 ? void 0 : _a.toFixed(2)}ms)`
51
+ const duration = query.duration;
52
+ log.debug(color(`│ ${`${(query.timestamp - requestStartTime).toFixed(2)}ms`.padEnd(10)} ${loadColor(TIMING_MAPPING[query.timingType].padEnd(10))} ${query.name}${query.timingType === 'resolved' || query.timingType === 'expired'
53
+ ? ` (Took ${duration === null || duration === void 0 ? void 0 : duration.toFixed(2)}ms)`
53
54
  : ''}`));
54
55
  // SSR + RSC render path generates 2 `load` and `render` for each query
55
56
  // We want to avoid falsely identifying a suspense waterfall near the end
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "0.11.1-experimental.1",
10
+ "version": "0.11.1",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",