@shopify/hydrogen 0.13.2 → 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 +26 -0
- 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.js +13 -8
- 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/framework/Hydration/ServerComponentRequest.server.js +3 -2
- 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 +17 -9
- package/dist/esnext/storefront-api-types.d.ts +150 -3
- package/dist/esnext/storefront-api-types.js +16 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/entry-server.js +13 -8
- 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.js +3 -2
- 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/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,31 @@
|
|
|
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
|
+
|
|
3
29
|
## 0.13.2
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -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, }) {
|
|
@@ -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';
|
|
@@ -109,7 +108,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
109
108
|
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
110
109
|
* we want to shard our full-page cache for all Hydrogen storefronts.
|
|
111
110
|
*/
|
|
112
|
-
headers[
|
|
111
|
+
headers['cache-control'] = componentResponse.cacheControlHeader;
|
|
113
112
|
if (componentResponse.customBody) {
|
|
114
113
|
// This can be used to return sitemap.xml or any other custom response.
|
|
115
114
|
postRequestTasks('ssr', status, request, componentResponse);
|
|
@@ -204,7 +203,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
204
203
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
205
204
|
* additional checks downstream?
|
|
206
205
|
*/
|
|
207
|
-
responseOptions.headers[
|
|
206
|
+
responseOptions.headers['cache-control'] =
|
|
208
207
|
componentResponse.cacheControlHeader;
|
|
209
208
|
if (isRedirect(responseOptions)) {
|
|
210
209
|
return false;
|
|
@@ -272,7 +271,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
272
271
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
273
272
|
* additional checks downstream?
|
|
274
273
|
*/
|
|
275
|
-
response.setHeader(
|
|
274
|
+
response.setHeader('cache-control', componentResponse.cacheControlHeader);
|
|
276
275
|
writeHeadToServerResponse(response, componentResponse, log, didError);
|
|
277
276
|
if (isRedirect(response)) {
|
|
278
277
|
// Return redirects early without further rendering/streaming
|
|
@@ -356,15 +355,21 @@ async function hydrate(url, { App, routes, request, response, componentResponse,
|
|
|
356
355
|
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
357
356
|
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
358
357
|
postRequestTasks('rsc', 200, request, componentResponse);
|
|
359
|
-
return new Response(bufferedBody
|
|
358
|
+
return new Response(bufferedBody, {
|
|
359
|
+
headers: {
|
|
360
|
+
'cache-control': componentResponse.cacheControlHeader,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
360
363
|
}
|
|
361
364
|
else if (response) {
|
|
362
365
|
const rscWriter = await import(
|
|
363
366
|
// @ts-ignore
|
|
364
367
|
'@shopify/hydrogen/vendor/react-server-dom-vite/writer.node.server');
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
.
|
|
368
|
+
const streamer = rscWriter.renderToPipeableStream(AppRSC);
|
|
369
|
+
response.writeHead(200, 'ok', {
|
|
370
|
+
'cache-control': componentResponse.cacheControlHeader,
|
|
371
|
+
});
|
|
372
|
+
const stream = streamer.pipe(response);
|
|
368
373
|
stream.on('finish', function () {
|
|
369
374
|
postRequestTasks('rsc', response.statusCode, request, componentResponse);
|
|
370
375
|
});
|
|
@@ -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";
|
|
@@ -123,8 +123,9 @@ function getInitFromNodeRequest(request) {
|
|
|
123
123
|
? request.body
|
|
124
124
|
: undefined,
|
|
125
125
|
};
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
const remoteAddress = request.socket.remoteAddress;
|
|
127
|
+
if (!init.headers.has('x-forwarded-for') && remoteAddress) {
|
|
128
|
+
init.headers.set('x-forwarded-for', remoteAddress);
|
|
128
129
|
}
|
|
129
130
|
return init;
|
|
130
131
|
}
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import type { QueryKey, CachingStrategy } from '../types';
|
|
2
2
|
export declare function generateSubRequestCacheControlHeader(userCacheOptions?: CachingStrategy): string;
|
|
3
|
-
/**
|
|
4
|
-
* Use a preview header during development.
|
|
5
|
-
* TODO: Support an override of this to force the cache
|
|
6
|
-
* header to be present during dev. ENV var maybe?
|
|
7
|
-
*/
|
|
8
|
-
export declare function getCacheControlHeader({ dev }: {
|
|
9
|
-
dev?: boolean;
|
|
10
|
-
}): "cache-control-preview" | "cache-control";
|
|
11
3
|
export declare function hashKey(key: QueryKey): string;
|
|
12
4
|
/**
|
|
13
5
|
* Get an item from the cache. If a match is found, returns a tuple
|
|
@@ -3,14 +3,6 @@ import { CacheSeconds, generateCacheControlHeader, } from '../framework/CachingS
|
|
|
3
3
|
export function generateSubRequestCacheControlHeader(userCacheOptions) {
|
|
4
4
|
return generateCacheControlHeader(userCacheOptions || CacheSeconds());
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Use a preview header during development.
|
|
8
|
-
* TODO: Support an override of this to force the cache
|
|
9
|
-
* header to be present during dev. ENV var maybe?
|
|
10
|
-
*/
|
|
11
|
-
export function getCacheControlHeader({ dev }) {
|
|
12
|
-
return dev ? 'cache-control-preview' : 'cache-control';
|
|
13
|
-
}
|
|
14
6
|
export function hashKey(key) {
|
|
15
7
|
const rawKey = key instanceof Array ? key : [key];
|
|
16
8
|
/**
|
|
@@ -15,7 +15,7 @@ metafields?: PartialDeep<MetafieldConnection>): {
|
|
|
15
15
|
key?: string | undefined;
|
|
16
16
|
namespace?: string | undefined;
|
|
17
17
|
parentResource?: import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").ProductVariant> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Product> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Blog> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Article> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Customer> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Order> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Collection> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Page> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Shop> | undefined;
|
|
18
|
-
reference?: import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").MediaImage> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").ProductVariant> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Product> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Page> | null | undefined;
|
|
18
|
+
reference?: import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Video> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").MediaImage> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").ProductVariant> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Product> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").Page> | import("type-fest/source/partial-deep").PartialObjectDeep<import("../../storefront-api-types").GenericFile> | null | undefined;
|
|
19
19
|
type?: string | undefined;
|
|
20
20
|
updatedAt?: string | undefined;
|
|
21
21
|
}[];
|
|
@@ -8,7 +8,7 @@ export interface UseShopQueryResponse<T> {
|
|
|
8
8
|
/**
|
|
9
9
|
* The `useShopQuery` hook allows you to make server-only GraphQL queries to the Storefront API. It must be a descendent of a `ShopifyProvider` component.
|
|
10
10
|
*/
|
|
11
|
-
export declare function useShopQuery<T>({ query, variables, cache,
|
|
11
|
+
export declare function useShopQuery<T>({ query, variables, cache, preload, }: {
|
|
12
12
|
/** A string of the GraphQL query.
|
|
13
13
|
* If no query is provided, useShopQuery will make no calls to the Storefront API.
|
|
14
14
|
*/
|
|
@@ -8,11 +8,12 @@ import { injectGraphQLTracker } from '../../utilities/graphql-tracker';
|
|
|
8
8
|
import { sendMessageToClient } from '../../utilities/devtools';
|
|
9
9
|
import { META_ENV_SSR } from '../../foundation/ssr-interop';
|
|
10
10
|
// Check if the response body has GraphQL errors
|
|
11
|
-
|
|
11
|
+
// https://spec.graphql.org/June2018/#sec-Response-Format
|
|
12
|
+
const shouldCacheResponse = (body) => !(body === null || body === void 0 ? void 0 : body.errors);
|
|
12
13
|
/**
|
|
13
14
|
* The `useShopQuery` hook allows you to make server-only GraphQL queries to the Storefront API. It must be a descendent of a `ShopifyProvider` component.
|
|
14
15
|
*/
|
|
15
|
-
export function useShopQuery({ query, variables = {}, cache,
|
|
16
|
+
export function useShopQuery({ query, variables = {}, cache, preload = false, }) {
|
|
16
17
|
var _a;
|
|
17
18
|
if (!META_ENV_SSR) {
|
|
18
19
|
throw new Error('Shopify Storefront API requests should only be made from the server.');
|
|
@@ -20,7 +21,7 @@ export function useShopQuery({ query, variables = {}, cache, locale = '', preloa
|
|
|
20
21
|
const serverRequest = useServerRequest();
|
|
21
22
|
const log = getLoggerWithContext(serverRequest);
|
|
22
23
|
const body = query ? graphqlRequestBody(query, variables) : '';
|
|
23
|
-
const { key, url, requestInit } = useCreateShopRequest(body
|
|
24
|
+
const { key, url, requestInit } = useCreateShopRequest(body);
|
|
24
25
|
const { data, error: useQueryError } = useQuery(key, query
|
|
25
26
|
? fetchBuilder(url, requestInit)
|
|
26
27
|
: // If no query, avoid calling SFAPI & return nothing
|
|
@@ -93,30 +94,37 @@ export function useShopQuery({ query, variables = {}, cache, locale = '', preloa
|
|
|
93
94
|
}
|
|
94
95
|
return data;
|
|
95
96
|
}
|
|
96
|
-
function useCreateShopRequest(body
|
|
97
|
-
var _a
|
|
98
|
-
const { storeDomain, storefrontToken, storefrontApiVersion
|
|
97
|
+
function useCreateShopRequest(body) {
|
|
98
|
+
var _a;
|
|
99
|
+
const { storeDomain, storefrontToken, storefrontApiVersion } = useShop();
|
|
99
100
|
const request = useServerRequest();
|
|
100
101
|
const secretToken = typeof Oxygen !== 'undefined'
|
|
101
102
|
? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a.SHOPIFY_STOREFRONT_API_SECRET_TOKEN
|
|
102
103
|
: null;
|
|
103
104
|
const buyerIp = request.getBuyerIp();
|
|
104
105
|
const extraHeaders = {};
|
|
106
|
+
/**
|
|
107
|
+
* Only pass one type of storefront token at a time.
|
|
108
|
+
*/
|
|
109
|
+
if (secretToken) {
|
|
110
|
+
extraHeaders['Shopify-Storefront-Private-Token'] = secretToken;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
extraHeaders['X-Shopify-Storefront-Access-Token'] = storefrontToken;
|
|
114
|
+
}
|
|
105
115
|
if (buyerIp) {
|
|
106
116
|
extraHeaders['Shopify-Storefront-Buyer-IP'] = buyerIp;
|
|
107
117
|
}
|
|
108
118
|
return {
|
|
109
|
-
key: [storeDomain, storefrontApiVersion, body
|
|
119
|
+
key: [storeDomain, storefrontApiVersion, body],
|
|
110
120
|
url: `https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`,
|
|
111
121
|
requestInit: {
|
|
112
122
|
body,
|
|
113
123
|
method: 'POST',
|
|
114
124
|
headers: {
|
|
115
|
-
'X-Shopify-Storefront-Access-Token': secretToken !== null && secretToken !== void 0 ? secretToken : storefrontToken,
|
|
116
125
|
'X-SDK-Variant': 'hydrogen',
|
|
117
126
|
'X-SDK-Version': storefrontApiVersion,
|
|
118
127
|
'content-type': 'application/json',
|
|
119
|
-
'Accept-Language': (_b = locale) !== null && _b !== void 0 ? _b : defaultLocale,
|
|
120
128
|
...extraHeaders,
|
|
121
129
|
},
|
|
122
130
|
},
|