@shopify/hydrogen 0.13.0 → 0.14.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.
- package/CHANGELOG.md +51 -5
- package/dist/esnext/components/Link/Link.client.d.ts +6 -0
- package/dist/esnext/components/Link/Link.client.js +85 -3
- package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +1 -1
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +16 -0
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +7 -2
- package/dist/esnext/components/Seo/DefaultPageSeo.client.js +1 -2
- package/dist/esnext/entry-client.js +14 -1
- package/dist/esnext/entry-server.d.ts +1 -0
- package/dist/esnext/entry-server.js +15 -9
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +1 -1
- package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.d.ts +6 -1
- package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.js +27 -22
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +4 -1
- package/dist/esnext/foundation/ShopifyProvider/types.d.ts +3 -1
- package/dist/esnext/foundation/constants.d.ts +1 -1
- package/dist/esnext/foundation/constants.js +1 -1
- package/dist/esnext/foundation/useQuery/hooks.d.ts +3 -0
- package/dist/esnext/foundation/useQuery/hooks.js +8 -2
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +7 -0
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +24 -7
- package/dist/esnext/framework/cache/in-memory.d.ts +1 -0
- package/dist/esnext/framework/cache/in-memory.js +15 -5
- package/dist/esnext/framework/cache.d.ts +0 -8
- package/dist/esnext/framework/cache.js +0 -8
- package/dist/esnext/hooks/useParsedMetafields/useParsedMetafields.d.ts +1 -1
- package/dist/esnext/hooks/useShopQuery/hooks.d.ts +1 -1
- package/dist/esnext/hooks/useShopQuery/hooks.js +33 -12
- package/dist/esnext/platforms/node.d.ts +2 -3
- package/dist/esnext/platforms/node.js +5 -3
- package/dist/esnext/platforms/worker.js +2 -1
- package/dist/esnext/storefront-api-types.d.ts +150 -3
- package/dist/esnext/storefront-api-types.js +16 -0
- package/dist/esnext/utilities/apiRoutes.js +3 -1
- package/dist/esnext/utilities/flattenConnection/flattenConnection.js +2 -5
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/entry-server.d.ts +1 -0
- package/dist/node/entry-server.js +15 -9
- package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +1 -1
- package/dist/node/foundation/ServerStateProvider/ServerStateProvider.d.ts +6 -1
- package/dist/node/foundation/ServerStateProvider/ServerStateProvider.js +27 -22
- package/dist/node/foundation/ShopifyProvider/types.d.ts +3 -1
- package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +7 -0
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +24 -7
- package/dist/node/framework/cache/in-memory.d.ts +1 -0
- package/dist/node/framework/cache/in-memory.js +15 -5
- package/dist/node/framework/cache.d.ts +0 -8
- package/dist/node/framework/cache.js +1 -10
- package/dist/node/storefront-api-types.d.ts +150 -3
- package/dist/node/storefront-api-types.js +17 -1
- package/dist/node/utilities/apiRoutes.js +3 -1
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/package.json +3 -3
- package/dist/esnext/components/LocalizationProvider/LocalizationQuery.d.ts +0 -23
- package/dist/esnext/components/LocalizationProvider/LocalizationQuery.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1028](https://github.com/Shopify/hydrogen/pull/1028) [`ba174588`](https://github.com/Shopify/hydrogen/commit/ba174588d8f4a9f1054779a9bf32a92e8d2c921c) Thanks [@michenly](https://github.com/michenly)! - Starting from SF API version `2022-04`, the preferred way to request translatable resources is using the `@inContext` directive. See the [API docs](https://shopify.dev/api/examples/multiple-languages#retrieve-translations-with-the-storefront-api) on how to do this and which resources have translatable properties.
|
|
8
|
+
|
|
9
|
+
This causes a breaking change to the `useShopQuery` hook. The `locale` property has been removed from the argument object; `Accept-Language` is no longer being send with every request, and we are no longer using locale as part of the cache key.
|
|
10
|
+
|
|
11
|
+
The `useShop` hook will now return the `languageCode` key, which is the first two characters of the existing `locale` key.
|
|
12
|
+
|
|
13
|
+
Both `locale` & `languageCode` values are also now capitalized to make it easier to pass into a GraphQL `@inContext` directive.
|
|
14
|
+
|
|
15
|
+
* [#1020](https://github.com/Shopify/hydrogen/pull/1020) [`e9529bc8`](https://github.com/Shopify/hydrogen/commit/e9529bc81410e0d99f9d3dbdb138ae61d00f876b) Thanks [@jplhomer](https://github.com/jplhomer)! - Preload `Link` URLs by default when a user signals intent to visit the URL. This includes hovering or focusing on the URL. To disable preloading, pass `<Link preload={false} />` to the component.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#1017](https://github.com/Shopify/hydrogen/pull/1017) [`4c87fb63`](https://github.com/Shopify/hydrogen/commit/4c87fb639a79da883f99c58acde0d17c713c7620) Thanks [@frandiox](https://github.com/frandiox)! - Do not cache Storefront API responses that contain GraphQL errors (amend previous fix).
|
|
20
|
+
|
|
21
|
+
* [#1039](https://github.com/Shopify/hydrogen/pull/1039) [`3a297862`](https://github.com/Shopify/hydrogen/commit/3a29786202947fab0bfe876042b37a91923ed637) Thanks [@frandiox](https://github.com/frandiox)! - Update to Vite 2.9
|
|
22
|
+
|
|
23
|
+
- [#1026](https://github.com/Shopify/hydrogen/pull/1026) [`836b064d`](https://github.com/Shopify/hydrogen/commit/836b064d1648fb1a9f209a08a82ee5c20f7dfba9) Thanks [@frehner](https://github.com/frehner)! - Updated the Typescript types and GraphQL schema to the newest updates from Storefront API 2022-04. Of note in this update is the ability to skip `edges` and go directly to `node`, for example: `product.nodes[0]` instead of `product.edges[0].node`
|
|
24
|
+
|
|
25
|
+
* [#1032](https://github.com/Shopify/hydrogen/pull/1032) [`03488083`](https://github.com/Shopify/hydrogen/commit/034880833dc500f66f9b67417c00099c283dfa67) Thanks [@jplhomer](https://github.com/jplhomer)! - Catch hydration errors related to experimental server components bugs and prevent them from being logged in production.
|
|
26
|
+
|
|
27
|
+
- [#1037](https://github.com/Shopify/hydrogen/pull/1037) [`13376efb`](https://github.com/Shopify/hydrogen/commit/13376efbe4db93efd705b6900a6198708bc37e69) Thanks [@jplhomer](https://github.com/jplhomer)! - Use new header for private Storefront token
|
|
28
|
+
|
|
29
|
+
## 0.13.2
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- [#1013](https://github.com/Shopify/hydrogen/pull/1013) [`94dc94ae`](https://github.com/Shopify/hydrogen/commit/94dc94aeb9dfd5e0120cab610203fdb4f0c61d3c) Thanks [@jplhomer](https://github.com/jplhomer)! - Fix CORS issue in StackBlitz
|
|
34
|
+
|
|
35
|
+
## 0.13.1
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [#1008](https://github.com/Shopify/hydrogen/pull/1008) [`ca1de82b`](https://github.com/Shopify/hydrogen/commit/ca1de82bc38c1c02caa451fb52065da499555e6f) Thanks [@frandiox](https://github.com/frandiox)! - Allow passing `cache` parameter to `createServer` in Node entry.
|
|
40
|
+
|
|
41
|
+
* [#997](https://github.com/Shopify/hydrogen/pull/997) [`fffdc08f`](https://github.com/Shopify/hydrogen/commit/fffdc08f87f71592352a2eb67a63e80704054db2) Thanks [@frandiox](https://github.com/frandiox)! - Allow empty array values in flattenConnection utility.
|
|
42
|
+
|
|
43
|
+
- [#1007](https://github.com/Shopify/hydrogen/pull/1007) [`7cfca7b0`](https://github.com/Shopify/hydrogen/commit/7cfca7b09289e028a463ababb51e69b4e3943d94) Thanks [@scottdixon](https://github.com/scottdixon)! - Fix API index routes https://github.com/Shopify/hydrogen/issues/562
|
|
44
|
+
|
|
45
|
+
* [#1000](https://github.com/Shopify/hydrogen/pull/1000) [`6d0d5068`](https://github.com/Shopify/hydrogen/commit/6d0d50686029c3d66d9dc0ceb0b5f71456c7b19e) Thanks [@frandiox](https://github.com/frandiox)! - Do not cache Storefront API responses that contain GraphQL errors.
|
|
46
|
+
|
|
47
|
+
- [#1003](https://github.com/Shopify/hydrogen/pull/1003) [`d8a9c929`](https://github.com/Shopify/hydrogen/commit/d8a9c9290aaf7c9d058b2c08567294822bea5396) Thanks [@jplhomer](https://github.com/jplhomer)! - Update useShopQuery to accept a custom Storefront API secret token, and forward the Buyer IP.
|
|
48
|
+
|
|
3
49
|
## 0.13.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
|
@@ -29,7 +75,7 @@
|
|
|
29
75
|
|
|
30
76
|
These fragments have been removed to reduce the chances of over-fetching (in other words, querying for fields you don't use) in your GraphQL queries. Please refer to the [Storefront API documentation](https://shopify.dev/api/storefront) for information and guides.
|
|
31
77
|
|
|
32
|
-
* [#912](https://github.com/Shopify/hydrogen/pull/912) [`de0e0d6a`](https://github.com/Shopify/hydrogen/commit/de0e0d6a6652463243ee09013cd30830ce2a246a) Thanks [@blittle](https://github.com/blittle)! - Change the country selector to lazy load available countries. The motivation to do so is that a _lot_ of countries come with the
|
|
78
|
+
* [#912](https://github.com/Shopify/hydrogen/pull/912) [`de0e0d6a`](https://github.com/Shopify/hydrogen/commit/de0e0d6a6652463243ee09013cd30830ce2a246a) Thanks [@blittle](https://github.com/blittle)! - Change the country selector to lazy load available countries. The motivation to do so is that a _lot_ of countries come with the Demo Store template. The problem is 1) the graphql query to fetch them all is relatively slow and 2) all of them get serialized to the browser in each RSC response.
|
|
33
79
|
|
|
34
80
|
This change removes `availableCountries` from the `LocalizationProvider`. As a result, the `useAvailableCountries` hook is also gone. Instead, the available countries are loaded on demand from an API route.
|
|
35
81
|
|
|
@@ -79,7 +125,7 @@
|
|
|
79
125
|
}, []);
|
|
80
126
|
```
|
|
81
127
|
|
|
82
|
-
See an example on how this could be done inside the
|
|
128
|
+
See an example on how this could be done inside the Demo Store template [country selector](https://github.com/Shopify/hydrogen/blob/v1.x-2022-07/examples/template-hydrogen-default/src/components/CountrySelector.client.jsx)
|
|
83
129
|
|
|
84
130
|
- [#698](https://github.com/Shopify/hydrogen/pull/698) [`6f30b9a1`](https://github.com/Shopify/hydrogen/commit/6f30b9a1327f06d648a01dd94d539c7dcb3061e0) Thanks [@jplhomer](https://github.com/jplhomer)! - Basic end-to-end tests have been added to the default Hydrogen template. You can run tests in development:
|
|
85
131
|
|
|
@@ -239,7 +285,7 @@
|
|
|
239
285
|
|
|
240
286
|
- [#981](https://github.com/Shopify/hydrogen/pull/981) [`8dda8a86`](https://github.com/Shopify/hydrogen/commit/8dda8a860bc1cf58511756b6fff999fb7caa6081) Thanks [@michenly](https://github.com/michenly)! - Fix useUrl() when it is in RSC mode
|
|
241
287
|
|
|
242
|
-
* [#965](https://github.com/Shopify/hydrogen/pull/965) [`cdad13ed`](https://github.com/Shopify/hydrogen/commit/cdad13ed85ff17b84981367f39c7d2fe45e72dcf) Thanks [@blittle](https://github.com/blittle)! - Fix server redirects to work properly with RSC responses. For example, the redirect component within the
|
|
288
|
+
* [#965](https://github.com/Shopify/hydrogen/pull/965) [`cdad13ed`](https://github.com/Shopify/hydrogen/commit/cdad13ed85ff17b84981367f39c7d2fe45e72dcf) Thanks [@blittle](https://github.com/blittle)! - Fix server redirects to work properly with RSC responses. For example, the redirect component within the Demo Store template needs to change:
|
|
243
289
|
|
|
244
290
|
```diff
|
|
245
291
|
export default function Redirect({response}) {
|
|
@@ -289,7 +335,7 @@
|
|
|
289
335
|
|
|
290
336
|
### Minor Changes
|
|
291
337
|
|
|
292
|
-
- [`8271be8`](https://github.com/Shopify/hydrogen/commit/8271be83331c99f27a258e6532983da4fe4f0b5b) Thanks [@michenly](https://github.com/michenly)! - Export Seo components Fragement and use them in the
|
|
338
|
+
- [`8271be8`](https://github.com/Shopify/hydrogen/commit/8271be83331c99f27a258e6532983da4fe4f0b5b) Thanks [@michenly](https://github.com/michenly)! - Export Seo components Fragement and use them in the Demo Store template.
|
|
293
339
|
|
|
294
340
|
* [#827](https://github.com/Shopify/hydrogen/pull/827) [`745e8c0`](https://github.com/Shopify/hydrogen/commit/745e8c0a87a7c41803934565e5a756295ff629c2) Thanks [@michenly](https://github.com/michenly)! - Move any static `Fragment` properties on components to the entry point `@shopify/hydrogen/fragments`.
|
|
295
341
|
The migration diff are as follows:
|
|
@@ -925,7 +971,7 @@ function SomeComponent() {
|
|
|
925
971
|
|
|
926
972
|
### Fixed
|
|
927
973
|
|
|
928
|
-
-
|
|
974
|
+
- Demo Store template GalleryPreview unique key warning
|
|
929
975
|
- Mitigation for upcoming breaking minor Vite update
|
|
930
976
|
|
|
931
977
|
## 0.2.0 - 2021-10-08
|
|
@@ -8,6 +8,8 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
|
|
|
8
8
|
clientState?: any;
|
|
9
9
|
/** Whether to reload the whole document on navigation. */
|
|
10
10
|
reloadDocument?: boolean;
|
|
11
|
+
/** Whether to prefetch the link source when the user signals intent. Defaults to `true`. For more information, refer to [Prefetching a link source](/custom-storefronts/hydrogen/framework/routes#prefetching-a-link-source). */
|
|
12
|
+
prefetch?: boolean;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* The `Link` component is used to navigate between routes. Because it renders an underlying `<a>` element, all
|
|
@@ -15,3 +17,7 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
|
|
|
15
17
|
* For more information, refer to the [`<a>` element documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes).
|
|
16
18
|
*/
|
|
17
19
|
export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
20
|
+
/**
|
|
21
|
+
* Credit: Remix's <Link> component.
|
|
22
|
+
*/
|
|
23
|
+
export declare function composeEventHandlers<EventType extends React.SyntheticEvent | Event>(theirHandler: ((event: EventType) => any) | undefined, ourHandler: (event: EventType) => any): (event: EventType) => any;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useRouter } from '../../foundation/Router/BrowserRouter.client';
|
|
3
3
|
import { createPath } from 'history';
|
|
4
4
|
import { useNavigate } from '../../foundation/useNavigate/useNavigate';
|
|
5
|
+
import { useServerState } from '../../foundation/useServerState';
|
|
6
|
+
import { RSC_PATHNAME } from '../../constants';
|
|
5
7
|
/**
|
|
6
8
|
* The `Link` component is used to navigate between routes. Because it renders an underlying `<a>` element, all
|
|
7
9
|
* properties available to the `<a>` element are also available to the `Link` component.
|
|
@@ -10,7 +12,13 @@ import { useNavigate } from '../../foundation/useNavigate/useNavigate';
|
|
|
10
12
|
export const Link = React.forwardRef(function Link(props, ref) {
|
|
11
13
|
const navigate = useNavigate();
|
|
12
14
|
const { location } = useRouter();
|
|
13
|
-
const
|
|
15
|
+
const [_, startTransition] = React.useTransition();
|
|
16
|
+
/**
|
|
17
|
+
* Inspired by Remix's Link component
|
|
18
|
+
*/
|
|
19
|
+
const [shouldPrefetch, setShouldPrefetch] = useState(false);
|
|
20
|
+
const [maybePrefetch, setMaybePrefetch] = useState(false);
|
|
21
|
+
const { reloadDocument, target, replace: _replace, to, onClick, clientState, prefetch = true, } = props;
|
|
14
22
|
const internalClick = useCallback((e) => {
|
|
15
23
|
if (onClick)
|
|
16
24
|
onClick(e);
|
|
@@ -28,8 +36,82 @@ export const Link = React.forwardRef(function Link(props, ref) {
|
|
|
28
36
|
});
|
|
29
37
|
}
|
|
30
38
|
}, [reloadDocument, target, _replace, to, clientState, onClick, location]);
|
|
31
|
-
|
|
39
|
+
const signalPrefetchIntent = () => {
|
|
40
|
+
/**
|
|
41
|
+
* startTransition to yield to more important updates
|
|
42
|
+
*/
|
|
43
|
+
startTransition(() => {
|
|
44
|
+
if (prefetch) {
|
|
45
|
+
setMaybePrefetch(true);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const cancelPrefetchIntent = () => {
|
|
50
|
+
/**
|
|
51
|
+
* startTransition to yield to more important updates
|
|
52
|
+
*/
|
|
53
|
+
startTransition(() => {
|
|
54
|
+
if (prefetch) {
|
|
55
|
+
setMaybePrefetch(false);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Wrapping `maybePrefetch` inside useEffect allows the user to quickly graze over
|
|
61
|
+
* a link without triggering a prefetch.
|
|
62
|
+
*/
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (maybePrefetch) {
|
|
65
|
+
const id = setTimeout(() => {
|
|
66
|
+
setShouldPrefetch(true);
|
|
67
|
+
}, 100);
|
|
68
|
+
return () => {
|
|
69
|
+
clearTimeout(id);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}, [maybePrefetch]);
|
|
73
|
+
const onMouseEnter = composeEventHandlers(props.onMouseEnter, signalPrefetchIntent);
|
|
74
|
+
const onMouseLeave = composeEventHandlers(props.onMouseLeave, cancelPrefetchIntent);
|
|
75
|
+
const onFocus = composeEventHandlers(props.onFocus, signalPrefetchIntent);
|
|
76
|
+
const onBlur = composeEventHandlers(props.onBlur, cancelPrefetchIntent);
|
|
77
|
+
const onTouchStart = composeEventHandlers(props.onTouchStart, signalPrefetchIntent);
|
|
78
|
+
return (React.createElement(React.Fragment, null,
|
|
79
|
+
React.createElement("a", { ...without(props, [
|
|
80
|
+
'to',
|
|
81
|
+
'replace',
|
|
82
|
+
'clientState',
|
|
83
|
+
'reloadDocument',
|
|
84
|
+
'prefetch',
|
|
85
|
+
]), ref: ref, onClick: internalClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onFocus: onFocus, onBlur: onBlur, onTouchStart: onTouchStart, href: props.to }, props.children),
|
|
86
|
+
shouldPrefetch && React.createElement(Prefetch, { pathname: to })));
|
|
32
87
|
});
|
|
88
|
+
function Prefetch({ pathname }) {
|
|
89
|
+
const { getProposedServerState } = useServerState();
|
|
90
|
+
const { location } = useRouter();
|
|
91
|
+
const newPath = createPath({ pathname });
|
|
92
|
+
if (pathname.startsWith('http') || newPath === createPath(location)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const newLocation = new URL(newPath, window.location.href);
|
|
96
|
+
const proposedServerState = getProposedServerState({
|
|
97
|
+
pathname: newLocation.pathname,
|
|
98
|
+
search: newLocation.search || undefined,
|
|
99
|
+
});
|
|
100
|
+
const href = `${RSC_PATHNAME}?state=` +
|
|
101
|
+
encodeURIComponent(JSON.stringify(proposedServerState));
|
|
102
|
+
return React.createElement("link", { rel: "prefetch", as: "fetch", href: href });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Credit: Remix's <Link> component.
|
|
106
|
+
*/
|
|
107
|
+
export function composeEventHandlers(theirHandler, ourHandler) {
|
|
108
|
+
return (event) => {
|
|
109
|
+
theirHandler === null || theirHandler === void 0 ? void 0 : theirHandler(event);
|
|
110
|
+
if (!event.defaultPrevented) {
|
|
111
|
+
ourHandler(event);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
33
115
|
function isModifiedEvent(event) {
|
|
34
116
|
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
|
35
117
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LocalizationQuery } from './
|
|
1
|
+
import type { LocalizationQuery } from './LocalizationProvider.server';
|
|
2
2
|
export declare type Localization = LocalizationQuery['localization'];
|
|
3
3
|
export interface LocalizationContextValue {
|
|
4
4
|
country?: Localization['country'];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { PreloadOptions } from '../../types';
|
|
3
|
+
import { Country, Currency } from '../../storefront-api-types';
|
|
3
4
|
export interface LocalizationProviderProps {
|
|
4
5
|
/** A `ReactNode` element. */
|
|
5
6
|
children: ReactNode;
|
|
@@ -19,3 +20,18 @@ export interface LocalizationProviderProps {
|
|
|
19
20
|
* `@inContext` directive as the `country` value.
|
|
20
21
|
*/
|
|
21
22
|
export declare function LocalizationProvider(props: LocalizationProviderProps): JSX.Element;
|
|
23
|
+
export declare type LocalizationQuery = {
|
|
24
|
+
__typename?: 'QueryRoot';
|
|
25
|
+
} & {
|
|
26
|
+
localization: {
|
|
27
|
+
__typename?: 'Localization';
|
|
28
|
+
} & {
|
|
29
|
+
country: {
|
|
30
|
+
__typename?: 'Country';
|
|
31
|
+
} & Pick<Country, 'isoCode' | 'name'> & {
|
|
32
|
+
currency: {
|
|
33
|
+
__typename?: 'Currency';
|
|
34
|
+
} & Pick<Currency, 'isoCode'>;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import LocalizationClientProvider from './LocalizationClientProvider.client';
|
|
3
|
+
import { useShop } from '../../foundation/useShop';
|
|
3
4
|
import { useShopQuery } from '../../hooks/useShopQuery';
|
|
4
5
|
import { CacheDays } from '../../framework/CachingStrategy';
|
|
5
6
|
/**
|
|
@@ -12,15 +13,19 @@ import { CacheDays } from '../../framework/CachingStrategy';
|
|
|
12
13
|
* `@inContext` directive as the `country` value.
|
|
13
14
|
*/
|
|
14
15
|
export function LocalizationProvider(props) {
|
|
16
|
+
const { languageCode } = useShop();
|
|
15
17
|
const { data: { localization }, } = useShopQuery({
|
|
16
18
|
query: query,
|
|
19
|
+
variables: { language: languageCode },
|
|
17
20
|
cache: CacheDays(),
|
|
18
21
|
preload: props.preload,
|
|
19
22
|
});
|
|
20
23
|
return (React.createElement(LocalizationClientProvider, { localization: localization }, props.children));
|
|
21
24
|
}
|
|
22
|
-
const query = `
|
|
23
|
-
|
|
25
|
+
const query = `
|
|
26
|
+
query Localization($language: LanguageCode)
|
|
27
|
+
@inContext(language: $language) {
|
|
28
|
+
localization {
|
|
24
29
|
country {
|
|
25
30
|
isoCode
|
|
26
31
|
name
|
|
@@ -5,8 +5,7 @@ import { TitleSeo } from './TitleSeo.client';
|
|
|
5
5
|
import { DescriptionSeo } from './DescriptionSeo.client';
|
|
6
6
|
import { TwitterSeo } from './TwitterSeo.client';
|
|
7
7
|
export function DefaultPageSeo({ title, description, url, titleTemplate, lang, }) {
|
|
8
|
-
const {
|
|
9
|
-
const fallBacklang = locale.split(/[-_]/)[0];
|
|
8
|
+
const { languageCode: fallBacklang } = useShop();
|
|
10
9
|
return (React.createElement(React.Fragment, null,
|
|
11
10
|
React.createElement(Head, { defaultTitle: title !== null && title !== void 0 ? title : '', titleTemplate: titleTemplate !== null && titleTemplate !== void 0 ? titleTemplate : `%s - ${title}` },
|
|
12
11
|
React.createElement("html", { lang: lang !== null && lang !== void 0 ? lang : fallBacklang }),
|
|
@@ -19,10 +19,23 @@ const renderHydrogen = async (ClientWrapper, config) => {
|
|
|
19
19
|
}
|
|
20
20
|
// default to StrictMode on, unless explicitly turned off
|
|
21
21
|
const RootComponent = (config === null || config === void 0 ? void 0 : config.strictMode) !== false ? StrictMode : Fragment;
|
|
22
|
+
let hasCaughtError = false;
|
|
22
23
|
hydrateRoot(root, React.createElement(RootComponent, null,
|
|
23
24
|
React.createElement(ErrorBoundary, { FallbackComponent: Error },
|
|
24
25
|
React.createElement(Suspense, { fallback: null },
|
|
25
|
-
React.createElement(Content, { clientWrapper: ClientWrapper }))))
|
|
26
|
+
React.createElement(Content, { clientWrapper: ClientWrapper })))), {
|
|
27
|
+
onRecoverableError(e) {
|
|
28
|
+
if (__DEV__ && !hasCaughtError) {
|
|
29
|
+
hasCaughtError = true;
|
|
30
|
+
console.log(`React encountered an error while attempting to hydrate the application. ` +
|
|
31
|
+
`This is likely due to a bug in React's Suspense behavior related to experimental server components, ` +
|
|
32
|
+
`and it is safe to ignore this error.\n` +
|
|
33
|
+
`Visit this issue to learn more: https://github.com/Shopify/hydrogen/issues/920.\n\n` +
|
|
34
|
+
`The original error is printed below:`);
|
|
35
|
+
console.log(e);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
26
39
|
};
|
|
27
40
|
export default renderHydrogen;
|
|
28
41
|
function Content({ clientWrapper: ClientWrapper = ({ children }) => children, }) {
|
|
@@ -14,6 +14,7 @@ interface RequestHandlerOptions {
|
|
|
14
14
|
dev?: boolean;
|
|
15
15
|
context?: RuntimeContext;
|
|
16
16
|
nonce?: string;
|
|
17
|
+
buyerIpHeader?: string;
|
|
17
18
|
}
|
|
18
19
|
export interface RequestHandler {
|
|
19
20
|
(request: Request | IncomingMessage, options: RequestHandlerOptions): Promise<Response | undefined>;
|
|
@@ -5,7 +5,6 @@ import { defer } from './utilities/defer';
|
|
|
5
5
|
import { Html, applyHtmlHead } from './framework/Hydration/Html';
|
|
6
6
|
import { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
|
|
7
7
|
import { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
|
|
8
|
-
import { getCacheControlHeader } from './framework/cache';
|
|
9
8
|
import { preloadRequestCacheData, ServerRequestProvider, } from './foundation/ServerRequestProvider';
|
|
10
9
|
import { getApiRouteFromURL, renderApiRoute, getApiRoutes, } from './utilities/apiRoutes';
|
|
11
10
|
import { ServerStateProvider } from './foundation/ServerStateProvider';
|
|
@@ -19,8 +18,9 @@ const DOCTYPE = '<!DOCTYPE html>';
|
|
|
19
18
|
const CONTENT_TYPE = 'Content-Type';
|
|
20
19
|
const HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
|
|
21
20
|
export const renderHydrogen = (App, { shopifyConfig, routes }) => {
|
|
22
|
-
const handleRequest = async function (rawRequest, { indexTemplate, streamableResponse, dev, cache, context, nonce }) {
|
|
21
|
+
const handleRequest = async function (rawRequest, { indexTemplate, streamableResponse, dev, cache, context, nonce, buyerIpHeader, }) {
|
|
23
22
|
const request = new ServerComponentRequest(rawRequest);
|
|
23
|
+
request.ctx.buyerIpHeader = buyerIpHeader;
|
|
24
24
|
const url = new URL(request.url);
|
|
25
25
|
const log = getLoggerWithContext(request);
|
|
26
26
|
const componentResponse = new ServerComponentResponse();
|
|
@@ -108,7 +108,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
108
108
|
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
109
109
|
* we want to shard our full-page cache for all Hydrogen storefronts.
|
|
110
110
|
*/
|
|
111
|
-
headers[
|
|
111
|
+
headers['cache-control'] = componentResponse.cacheControlHeader;
|
|
112
112
|
if (componentResponse.customBody) {
|
|
113
113
|
// This can be used to return sitemap.xml or any other custom response.
|
|
114
114
|
postRequestTasks('ssr', status, request, componentResponse);
|
|
@@ -203,7 +203,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
203
203
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
204
204
|
* additional checks downstream?
|
|
205
205
|
*/
|
|
206
|
-
responseOptions.headers[
|
|
206
|
+
responseOptions.headers['cache-control'] =
|
|
207
207
|
componentResponse.cacheControlHeader;
|
|
208
208
|
if (isRedirect(responseOptions)) {
|
|
209
209
|
return false;
|
|
@@ -271,7 +271,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
271
271
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
272
272
|
* additional checks downstream?
|
|
273
273
|
*/
|
|
274
|
-
response.setHeader(
|
|
274
|
+
response.setHeader('cache-control', componentResponse.cacheControlHeader);
|
|
275
275
|
writeHeadToServerResponse(response, componentResponse, log, didError);
|
|
276
276
|
if (isRedirect(response)) {
|
|
277
277
|
// Return redirects early without further rendering/streaming
|
|
@@ -355,15 +355,21 @@ async function hydrate(url, { App, routes, request, response, componentResponse,
|
|
|
355
355
|
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
356
356
|
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
357
357
|
postRequestTasks('rsc', 200, request, componentResponse);
|
|
358
|
-
return new Response(bufferedBody
|
|
358
|
+
return new Response(bufferedBody, {
|
|
359
|
+
headers: {
|
|
360
|
+
'cache-control': componentResponse.cacheControlHeader,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
359
363
|
}
|
|
360
364
|
else if (response) {
|
|
361
365
|
const rscWriter = await import(
|
|
362
366
|
// @ts-ignore
|
|
363
367
|
'@shopify/hydrogen/vendor/react-server-dom-vite/writer.node.server');
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
.
|
|
368
|
+
const streamer = rscWriter.renderToPipeableStream(AppRSC);
|
|
369
|
+
response.writeHead(200, 'ok', {
|
|
370
|
+
'cache-control': componentResponse.cacheControlHeader,
|
|
371
|
+
});
|
|
372
|
+
const stream = streamer.pipe(response);
|
|
367
373
|
stream.on('finish', function () {
|
|
368
374
|
postRequestTasks('rsc', response.statusCode, request, componentResponse);
|
|
369
375
|
});
|
|
@@ -30,7 +30,7 @@ export function useServerRequest() {
|
|
|
30
30
|
const cache = React.unstable_getCacheForType(requestCacheRSC);
|
|
31
31
|
request = cache ? cache.get(requestCacheRSC.key) : null;
|
|
32
32
|
}
|
|
33
|
-
catch (
|
|
33
|
+
catch (_a) {
|
|
34
34
|
// If RSC cache failed it means this is not an RSC request.
|
|
35
35
|
// Try getting SSR context instead:
|
|
36
36
|
request = useContext(RequestContextSSR);
|
|
@@ -7,13 +7,18 @@ export interface ServerState {
|
|
|
7
7
|
search: string;
|
|
8
8
|
[key: string]: any;
|
|
9
9
|
}
|
|
10
|
+
declare type ServerStateSetterInput = ((prev: ServerState) => Partial<ServerState>) | Partial<ServerState> | string;
|
|
10
11
|
export interface ServerStateSetter {
|
|
11
|
-
(input:
|
|
12
|
+
(input: ServerStateSetterInput, propValue?: any): void;
|
|
13
|
+
}
|
|
14
|
+
interface ProposedServerStateSetter {
|
|
15
|
+
(input: ServerStateSetterInput, propValue?: any): ServerState;
|
|
12
16
|
}
|
|
13
17
|
export interface ServerStateContextValue {
|
|
14
18
|
pending: boolean;
|
|
15
19
|
serverState: ServerState;
|
|
16
20
|
setServerState: ServerStateSetter;
|
|
21
|
+
getProposedServerState: ProposedServerStateSetter;
|
|
17
22
|
}
|
|
18
23
|
export declare const ServerStateContext: React.Context<ServerStateContextValue>;
|
|
19
24
|
interface ServerStateProviderProps {
|
|
@@ -14,34 +14,39 @@ export function ServerStateProvider({ serverState, setServerState, children, })
|
|
|
14
14
|
* the `pending` flag also provided by the hook to display in the UI.
|
|
15
15
|
*/
|
|
16
16
|
startTransition(() => {
|
|
17
|
-
return setServerState((prev) =>
|
|
18
|
-
let newValue;
|
|
19
|
-
if (typeof input === 'function') {
|
|
20
|
-
newValue = input(prev);
|
|
21
|
-
}
|
|
22
|
-
else if (typeof input === 'string') {
|
|
23
|
-
newValue = { [input]: propValue };
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
newValue = input;
|
|
27
|
-
}
|
|
28
|
-
if (__DEV__) {
|
|
29
|
-
const privateProp = PRIVATE_PROPS.find((prop) => prop in newValue);
|
|
30
|
-
if (privateProp) {
|
|
31
|
-
console.warn(`Custom "${privateProp}" property in server state is ignored. Use a different name.`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
...prev,
|
|
36
|
-
...newValue,
|
|
37
|
-
};
|
|
38
|
-
});
|
|
17
|
+
return setServerState((prev) => getNewServerState(prev, input, propValue));
|
|
39
18
|
});
|
|
40
19
|
}, [setServerState, startTransition]);
|
|
20
|
+
const getProposedServerStateCallback = useCallback((input, propValue) => {
|
|
21
|
+
return getNewServerState(serverState, input, propValue);
|
|
22
|
+
}, [serverState]);
|
|
23
|
+
function getNewServerState(prev, input, propValue) {
|
|
24
|
+
let newValue;
|
|
25
|
+
if (typeof input === 'function') {
|
|
26
|
+
newValue = input(prev);
|
|
27
|
+
}
|
|
28
|
+
else if (typeof input === 'string') {
|
|
29
|
+
newValue = { [input]: propValue };
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
newValue = input;
|
|
33
|
+
}
|
|
34
|
+
if (__DEV__) {
|
|
35
|
+
const privateProp = PRIVATE_PROPS.find((prop) => prop in newValue);
|
|
36
|
+
if (privateProp) {
|
|
37
|
+
console.warn(`Custom "${privateProp}" property in server state is ignored. Use a different name.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
...prev,
|
|
42
|
+
...newValue,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
41
45
|
const value = useMemo(() => ({
|
|
42
46
|
pending,
|
|
43
47
|
serverState,
|
|
44
48
|
setServerState: setServerStateCallback,
|
|
49
|
+
getProposedServerState: getProposedServerStateCallback,
|
|
45
50
|
}), [serverState, setServerStateCallback, pending]);
|
|
46
51
|
return (React.createElement(ServerStateContext.Provider, { value: value }, children));
|
|
47
52
|
}
|
|
@@ -4,8 +4,11 @@ import { DEFAULT_LOCALE } from '../constants';
|
|
|
4
4
|
import { useServerRequest } from '../ServerRequestProvider';
|
|
5
5
|
function makeShopifyContext(shopifyConfig) {
|
|
6
6
|
var _a, _b;
|
|
7
|
+
const locale = (_a = shopifyConfig.defaultLocale) !== null && _a !== void 0 ? _a : DEFAULT_LOCALE;
|
|
8
|
+
const languageCode = locale.split(/[-_]/)[0];
|
|
7
9
|
return {
|
|
8
|
-
locale: (
|
|
10
|
+
locale: locale.toUpperCase(),
|
|
11
|
+
languageCode: languageCode.toUpperCase(),
|
|
9
12
|
storeDomain: (_b = shopifyConfig === null || shopifyConfig === void 0 ? void 0 : shopifyConfig.storeDomain) === null || _b === void 0 ? void 0 : _b.replace(/^https?:\/\//, ''),
|
|
10
13
|
storefrontToken: shopifyConfig.storefrontToken,
|
|
11
14
|
storefrontApiVersion: shopifyConfig.storefrontApiVersion,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { CountryCode, LanguageCode } from '../../storefront-api-types';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type { ShopifyConfig } from '../../types';
|
|
3
4
|
export declare type ShopifyContextValue = {
|
|
4
|
-
locale:
|
|
5
|
+
locale: `${LanguageCode}-${CountryCode}`;
|
|
6
|
+
languageCode: `${LanguageCode}`;
|
|
5
7
|
storeDomain: ShopifyConfig['storeDomain'];
|
|
6
8
|
storefrontToken: ShopifyConfig['storefrontToken'];
|
|
7
9
|
storefrontApiVersion: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const DEFAULT_LOCALE = "
|
|
1
|
+
export declare const DEFAULT_LOCALE = "EN-US";
|
|
@@ -9,6 +9,9 @@ export interface HydrogenUseQueryOptions {
|
|
|
9
9
|
* to preload the query for all requests.
|
|
10
10
|
*/
|
|
11
11
|
preload?: PreloadOptions;
|
|
12
|
+
/** A function that inspects the response body to determine if it should be cached.
|
|
13
|
+
*/
|
|
14
|
+
shouldCacheResponse?: (body: any) => boolean;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
|
|
@@ -32,9 +32,11 @@ queryOptions) {
|
|
|
32
32
|
return useRequestCacheData(withCacheIdKey, fetcher);
|
|
33
33
|
}
|
|
34
34
|
function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
35
|
+
var _a;
|
|
35
36
|
const resolvedQueryOptions = {
|
|
36
37
|
...(queryOptions !== null && queryOptions !== void 0 ? queryOptions : {}),
|
|
37
38
|
};
|
|
39
|
+
const shouldCacheResponse = (_a = queryOptions === null || queryOptions === void 0 ? void 0 : queryOptions.shouldCacheResponse) !== null && _a !== void 0 ? _a : (() => true);
|
|
38
40
|
/**
|
|
39
41
|
* Attempt to read the query from cache. If it doesn't exist or if it's stale, regenerate it.
|
|
40
42
|
*/
|
|
@@ -64,7 +66,9 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
64
66
|
await setItemInCache(lockKey, true);
|
|
65
67
|
try {
|
|
66
68
|
const output = await generateNewOutput();
|
|
67
|
-
|
|
69
|
+
if (shouldCacheResponse(output)) {
|
|
70
|
+
await setItemInCache(key, output, resolvedQueryOptions === null || resolvedQueryOptions === void 0 ? void 0 : resolvedQueryOptions.cache);
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
catch (e) {
|
|
70
74
|
log.error(`Error generating async response: ${e.message}`);
|
|
@@ -80,7 +84,9 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
80
84
|
/**
|
|
81
85
|
* Important: Do this async
|
|
82
86
|
*/
|
|
83
|
-
|
|
87
|
+
if (shouldCacheResponse(newOutput)) {
|
|
88
|
+
runDelayedFunction(() => setItemInCache(key, newOutput, resolvedQueryOptions === null || resolvedQueryOptions === void 0 ? void 0 : resolvedQueryOptions.cache));
|
|
89
|
+
}
|
|
84
90
|
collectQueryCacheControlHeaders(request, key, generateSubRequestCacheControlHeader(resolvedQueryOptions === null || resolvedQueryOptions === void 0 ? void 0 : resolvedQueryOptions.cache));
|
|
85
91
|
return newOutput;
|
|
86
92
|
}
|
|
@@ -35,6 +35,7 @@ export declare class ServerComponentRequest extends Request {
|
|
|
35
35
|
queryTimings: Array<QueryTiming>;
|
|
36
36
|
preloadQueries: PreloadQueriesByURL;
|
|
37
37
|
router: RouterContextData;
|
|
38
|
+
buyerIpHeader?: string;
|
|
38
39
|
[key: string]: any;
|
|
39
40
|
};
|
|
40
41
|
constructor(input: any);
|
|
@@ -44,4 +45,10 @@ export declare class ServerComponentRequest extends Request {
|
|
|
44
45
|
savePreloadQuery(query: PreloadQueryEntry): void;
|
|
45
46
|
getPreloadQueries(): PreloadQueriesByURL | undefined;
|
|
46
47
|
savePreloadQueries(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Buyer IP varies by hosting provider and runtime. The developer should provide this
|
|
50
|
+
* as an argument to the `handleRequest` function for their runtime.
|
|
51
|
+
* Defaults to `x-forwarded-for` header value.
|
|
52
|
+
*/
|
|
53
|
+
getBuyerIp(): string | null;
|
|
47
54
|
}
|