@shopify/hydrogen 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/dist/esnext/client.d.ts +3 -0
- package/dist/esnext/client.js +3 -0
- package/dist/esnext/components/CartProvider/CartProvider.client.js +23 -0
- package/dist/esnext/components/DevTools.d.ts +1 -0
- package/dist/esnext/components/DevTools.js +128 -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/constants.d.ts +2 -0
- package/dist/esnext/constants.js +2 -0
- package/dist/esnext/entry-client.js +20 -4
- package/dist/esnext/entry-server.d.ts +1 -1
- package/dist/esnext/entry-server.js +42 -23
- package/dist/esnext/foundation/Analytics/Analytics.client.d.ts +3 -0
- package/dist/esnext/foundation/Analytics/Analytics.client.js +28 -0
- package/dist/esnext/foundation/Analytics/Analytics.server.d.ts +1 -0
- package/dist/esnext/foundation/Analytics/Analytics.server.js +38 -0
- package/dist/esnext/foundation/Analytics/ClientAnalytics.d.ts +24 -0
- package/dist/esnext/foundation/Analytics/ClientAnalytics.js +91 -0
- package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
- package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.js +33 -0
- package/dist/esnext/foundation/Analytics/const.d.ts +8 -0
- package/dist/esnext/foundation/Analytics/const.js +8 -0
- package/dist/esnext/foundation/Analytics/hook.d.ts +1 -0
- package/dist/esnext/foundation/Analytics/hook.js +7 -0
- package/dist/esnext/foundation/Analytics/index.d.ts +2 -0
- package/dist/esnext/foundation/Analytics/index.js +2 -0
- package/dist/esnext/foundation/Analytics/types.d.ts +5 -0
- package/dist/esnext/{components/LocalizationProvider/LocalizationQuery.js → foundation/Analytics/types.js} +0 -0
- package/dist/esnext/foundation/Analytics/utils.d.ts +1 -0
- package/dist/esnext/foundation/Analytics/utils.js +8 -0
- package/dist/esnext/foundation/Boomerang/Boomerang.client.js +3 -1
- package/dist/esnext/foundation/Route/Route.server.js +4 -0
- package/dist/esnext/foundation/Router/BrowserRouter.client.js +68 -15
- 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 +5 -6
- package/dist/esnext/foundation/constants.d.ts +1 -1
- package/dist/esnext/foundation/constants.js +1 -1
- package/dist/esnext/foundation/fetchSync/client/fetchSync.d.ts +10 -0
- package/dist/esnext/foundation/fetchSync/client/fetchSync.js +27 -0
- package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +8 -0
- package/dist/esnext/foundation/fetchSync/server/fetchSync.js +27 -0
- package/dist/esnext/foundation/fetchSync/types.d.ts +5 -0
- package/dist/esnext/foundation/fetchSync/types.js +1 -0
- package/dist/esnext/foundation/useQuery/hooks.d.ts +4 -2
- package/dist/esnext/foundation/useQuery/hooks.js +10 -6
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +14 -7
- package/dist/esnext/framework/cache/in-memory.js +5 -5
- package/dist/esnext/framework/cache.d.ts +1 -10
- package/dist/esnext/framework/cache.js +67 -30
- package/dist/esnext/framework/plugin.js +10 -0
- package/dist/esnext/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +9 -2
- 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 +47 -16
- package/dist/esnext/index.d.ts +2 -0
- package/dist/esnext/index.js +2 -0
- package/dist/esnext/storefront-api-types.d.ts +150 -3
- package/dist/esnext/storefront-api-types.js +16 -0
- package/dist/esnext/types.d.ts +6 -1
- package/dist/esnext/utilities/apiRoutes.d.ts +1 -1
- package/dist/esnext/utilities/apiRoutes.js +3 -2
- package/dist/esnext/utilities/hash.d.ts +2 -0
- package/dist/esnext/utilities/hash.js +7 -0
- package/dist/esnext/utilities/log/log-cache-api-status.js +1 -1
- package/dist/esnext/utilities/log/log-cache-header.js +1 -1
- package/dist/esnext/utilities/log/log-query-timeline.js +1 -1
- package/dist/esnext/utilities/suspense.d.ts +5 -0
- package/dist/esnext/utilities/suspense.js +32 -0
- package/dist/esnext/utilities/template.js +1 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/constants.d.ts +2 -0
- package/dist/node/constants.js +3 -1
- package/dist/node/entry-server.d.ts +1 -1
- package/dist/node/entry-server.js +41 -25
- package/dist/node/foundation/Analytics/Analytics.client.d.ts +3 -0
- package/dist/node/foundation/Analytics/Analytics.client.js +32 -0
- package/dist/node/foundation/Analytics/Analytics.server.d.ts +1 -0
- package/dist/node/foundation/Analytics/Analytics.server.js +45 -0
- package/dist/node/foundation/Analytics/ClientAnalytics.d.ts +24 -0
- package/dist/node/foundation/Analytics/ClientAnalytics.js +94 -0
- package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
- package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.js +37 -0
- package/dist/node/foundation/Analytics/const.d.ts +8 -0
- package/dist/node/foundation/Analytics/const.js +11 -0
- package/dist/node/foundation/Analytics/hook.d.ts +1 -0
- package/dist/node/foundation/Analytics/hook.js +11 -0
- package/dist/node/foundation/Analytics/index.d.ts +2 -0
- package/dist/node/foundation/Analytics/index.js +7 -0
- package/dist/node/foundation/Analytics/types.d.ts +5 -0
- package/dist/node/foundation/Analytics/types.js +2 -0
- package/dist/node/foundation/Analytics/utils.d.ts +1 -0
- package/dist/node/foundation/Analytics/utils.js +12 -0
- package/dist/node/foundation/Router/BrowserRouter.client.js +67 -14
- package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +2 -2
- 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 +5 -6
- package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +16 -9
- package/dist/node/framework/cache/in-memory.js +5 -5
- package/dist/node/framework/cache.d.ts +1 -10
- package/dist/node/framework/cache.js +71 -36
- package/dist/node/framework/plugin.js +10 -0
- package/dist/node/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +9 -2
- package/dist/node/storefront-api-types.d.ts +150 -3
- package/dist/node/storefront-api-types.js +17 -1
- package/dist/node/types.d.ts +6 -1
- package/dist/node/utilities/apiRoutes.d.ts +1 -1
- package/dist/node/utilities/apiRoutes.js +3 -2
- package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +6 -0
- package/dist/node/utilities/flattenConnection/flattenConnection.js +15 -0
- package/dist/node/utilities/flattenConnection/index.d.ts +1 -0
- package/dist/node/utilities/flattenConnection/index.js +5 -0
- package/dist/node/utilities/hash.d.ts +2 -0
- package/dist/node/utilities/hash.js +11 -0
- package/dist/node/utilities/image_size.d.ts +30 -0
- package/dist/node/utilities/image_size.js +110 -0
- package/dist/node/utilities/index.d.ts +11 -0
- package/dist/node/utilities/index.js +32 -0
- package/dist/node/utilities/isClient/index.d.ts +1 -0
- package/dist/node/utilities/isClient/index.js +5 -0
- package/dist/node/utilities/isClient/isClient.d.ts +4 -0
- package/dist/node/utilities/isClient/isClient.js +10 -0
- package/dist/node/utilities/isServer/index.d.ts +1 -0
- package/dist/node/utilities/isServer/index.js +5 -0
- package/dist/node/utilities/isServer/isServer.d.ts +4 -0
- package/dist/node/utilities/isServer/isServer.js +11 -0
- package/dist/node/utilities/load_script.d.ts +3 -0
- package/dist/node/utilities/load_script.js +27 -0
- package/dist/node/utilities/log/log-cache-api-status.js +1 -1
- package/dist/node/utilities/log/log-cache-header.js +2 -2
- package/dist/node/utilities/log/log-query-timeline.js +2 -2
- package/dist/node/utilities/measurement.d.ts +3 -0
- package/dist/node/utilities/measurement.js +103 -0
- package/dist/node/utilities/parseMetafieldValue/index.d.ts +1 -0
- package/dist/node/utilities/parseMetafieldValue/index.js +5 -0
- package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.d.ts +6 -0
- package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.js +39 -0
- package/dist/node/utilities/suspense.d.ts +12 -0
- package/dist/node/utilities/suspense.js +64 -0
- package/dist/node/utilities/template.js +1 -1
- package/dist/node/utilities/video_parameters.d.ts +47 -0
- package/dist/node/utilities/video_parameters.js +27 -0
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/package.json +3 -3
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +9 -21
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.development.server.js +51 -47
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.production.min.server.js +30 -29
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.development.server.js +51 -47
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.production.min.server.js +17 -17
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +55 -45
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +9 -21
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.browser.server.js +51 -47
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.node.server.js +51 -47
- package/vendor/react-server-dom-vite/package.json +3 -3
- package/dist/esnext/components/LocalizationProvider/LocalizationQuery.d.ts +0 -23
|
@@ -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,11 +1,10 @@
|
|
|
1
|
+
import type { CountryCode, LanguageCode } from '../../storefront-api-types';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type { ShopifyConfig } from '../../types';
|
|
3
|
-
export
|
|
4
|
-
locale:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
storefrontApiVersion: string;
|
|
8
|
-
};
|
|
4
|
+
export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLocale'> {
|
|
5
|
+
locale: `${LanguageCode}-${CountryCode}`;
|
|
6
|
+
languageCode: `${LanguageCode}`;
|
|
7
|
+
}
|
|
9
8
|
export declare type ShopifyProviderProps = {
|
|
10
9
|
/** The contents of the `shopify.config.js` file. */
|
|
11
10
|
shopifyConfig: ShopifyConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const DEFAULT_LOCALE = "
|
|
1
|
+
export declare const DEFAULT_LOCALE = "EN-US";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FetchResponse } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch a URL for use in a client component Suspense boundary.
|
|
4
|
+
*/
|
|
5
|
+
export declare function fetchSync(url: string, options?: RequestInit): FetchResponse;
|
|
6
|
+
/**
|
|
7
|
+
* Preload a URL for use in a client component Suspense boundary.
|
|
8
|
+
* Useful for placing higher in the tree to avoid waterfalls.
|
|
9
|
+
*/
|
|
10
|
+
export declare function preload(url: string, options?: RequestInit): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { suspendFunction, preloadFunction } from '../../../utilities/suspense';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch a URL for use in a client component Suspense boundary.
|
|
4
|
+
*/
|
|
5
|
+
export function fetchSync(url, options) {
|
|
6
|
+
const [text, response] = suspendFunction([url, options], async () => {
|
|
7
|
+
const response = await globalThis.fetch(url, options);
|
|
8
|
+
const text = await response.text();
|
|
9
|
+
return [text, response];
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
response,
|
|
13
|
+
json: () => JSON.parse(text),
|
|
14
|
+
text: () => text,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Preload a URL for use in a client component Suspense boundary.
|
|
19
|
+
* Useful for placing higher in the tree to avoid waterfalls.
|
|
20
|
+
*/
|
|
21
|
+
export function preload(url, options) {
|
|
22
|
+
preloadFunction([url, options], async () => {
|
|
23
|
+
const response = await globalThis.fetch(url, options);
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
return [text, response];
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type HydrogenUseQueryOptions } from '../../useQuery/hooks';
|
|
2
|
+
import type { FetchResponse } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* The `fetchSync` hook makes third-party API requests and is the recommended way to make simple fetch calls on the server.
|
|
5
|
+
* It's designed similar to the [Web API's `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), only in a way
|
|
6
|
+
* that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchSync(url: string, options?: Omit<RequestInit, 'cache'> & HydrogenUseQueryOptions): FetchResponse;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useQuery } from '../../useQuery/hooks';
|
|
2
|
+
/**
|
|
3
|
+
* The `fetchSync` hook makes third-party API requests and is the recommended way to make simple fetch calls on the server.
|
|
4
|
+
* It's designed similar to the [Web API's `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), only in a way
|
|
5
|
+
* that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
6
|
+
*/
|
|
7
|
+
export function fetchSync(url, options) {
|
|
8
|
+
const { cache, preload, shouldCacheResponse, ...requestInit } = options !== null && options !== void 0 ? options : {};
|
|
9
|
+
const { data: useQueryResponse, error } = useQuery([url, requestInit], async () => {
|
|
10
|
+
const response = await globalThis.fetch(url, requestInit);
|
|
11
|
+
const text = await response.text();
|
|
12
|
+
return [text, response];
|
|
13
|
+
}, {
|
|
14
|
+
cache,
|
|
15
|
+
preload,
|
|
16
|
+
shouldCacheResponse,
|
|
17
|
+
});
|
|
18
|
+
if (error) {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
const [data, response] = useQueryResponse;
|
|
22
|
+
return {
|
|
23
|
+
response,
|
|
24
|
+
json: () => JSON.parse(data),
|
|
25
|
+
text: () => data,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -15,9 +15,11 @@ export interface HydrogenUseQueryOptions {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
|
|
18
|
-
* supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
19
|
-
* on [react-query](https://react-query.tanstack.com/reference/useQuery). You can use this
|
|
18
|
+
* supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). You can use this
|
|
20
19
|
* hook to call any third-party APIs from a server component.
|
|
20
|
+
*
|
|
21
|
+
* \> Note:
|
|
22
|
+
* \> If you're making a simple fetch call on the server, then we recommend using the [`fetchSync`](/api/hydrogen/hooks/global/fetchsync) hook instead.
|
|
21
23
|
*/
|
|
22
24
|
export declare function useQuery<T>(
|
|
23
25
|
/** A string or array to uniquely identify the current query. */
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { getLoggerWithContext, collectQueryCacheControlHeaders, collectQueryTimings, } from '../../utilities/log';
|
|
1
|
+
import { getLoggerWithContext, collectQueryCacheControlHeaders, collectQueryTimings, logCacheApiStatus, } from '../../utilities/log';
|
|
2
2
|
import { deleteItemFromCache, generateSubRequestCacheControlHeader, getItemFromCache, isStale, setItemInCache, } from '../../framework/cache';
|
|
3
|
+
import { hashKey } from '../../utilities/hash';
|
|
3
4
|
import { runDelayedFunction } from '../../framework/runtime';
|
|
4
5
|
import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
|
|
5
6
|
/**
|
|
6
7
|
* The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
|
|
7
|
-
* supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
8
|
-
* on [react-query](https://react-query.tanstack.com/reference/useQuery). You can use this
|
|
8
|
+
* supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). You can use this
|
|
9
9
|
* hook to call any third-party APIs from a server component.
|
|
10
|
+
*
|
|
11
|
+
* \> Note:
|
|
12
|
+
* \> If you're making a simple fetch call on the server, then we recommend using the [`fetchSync`](/api/hydrogen/hooks/global/fetchsync) hook instead.
|
|
10
13
|
*/
|
|
11
14
|
export function useQuery(
|
|
12
15
|
/** A string or array to uniquely identify the current query. */
|
|
@@ -45,6 +48,7 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
45
48
|
// to prevent losing the current React cycle.
|
|
46
49
|
const request = useServerRequest();
|
|
47
50
|
const log = getLoggerWithContext(request);
|
|
51
|
+
const hashedKey = hashKey(key);
|
|
48
52
|
const cacheResponse = await getItemFromCache(key);
|
|
49
53
|
async function generateNewOutput() {
|
|
50
54
|
return await queryFn();
|
|
@@ -55,11 +59,11 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
55
59
|
/**
|
|
56
60
|
* Important: Do this async
|
|
57
61
|
*/
|
|
58
|
-
if (isStale(response)) {
|
|
59
|
-
|
|
62
|
+
if (isStale(response, resolvedQueryOptions === null || resolvedQueryOptions === void 0 ? void 0 : resolvedQueryOptions.cache)) {
|
|
63
|
+
logCacheApiStatus('STALE', hashedKey);
|
|
60
64
|
const lockKey = `lock-${key}`;
|
|
61
65
|
runDelayedFunction(async () => {
|
|
62
|
-
|
|
66
|
+
logCacheApiStatus('UPDATING', hashedKey);
|
|
63
67
|
const lockExists = await getItemFromCache(lockKey);
|
|
64
68
|
if (lockExists)
|
|
65
69
|
return;
|
|
@@ -34,6 +34,7 @@ export declare class ServerComponentRequest extends Request {
|
|
|
34
34
|
queryCacheControl: Array<QueryCacheControlHeaders>;
|
|
35
35
|
queryTimings: Array<QueryTiming>;
|
|
36
36
|
preloadQueries: PreloadQueriesByURL;
|
|
37
|
+
analyticsData: any;
|
|
37
38
|
router: RouterContextData;
|
|
38
39
|
buyerIpHeader?: string;
|
|
39
40
|
[key: string]: any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getTime } from '../../utilities/timing';
|
|
2
|
-
import { hashKey } from '
|
|
2
|
+
import { hashKey } from '../../utilities/hash';
|
|
3
3
|
import { HelmetData as HeadData } from 'react-helmet-async';
|
|
4
4
|
import { RSC_PATHNAME } from '../../constants';
|
|
5
5
|
let reqCounter = 0; // For debugging
|
|
@@ -27,8 +27,11 @@ export class ServerComponentRequest extends Request {
|
|
|
27
27
|
else {
|
|
28
28
|
super(getUrlFromNodeRequest(input), getInitFromNodeRequest(input));
|
|
29
29
|
}
|
|
30
|
+
const referer = this.headers.get('referer');
|
|
30
31
|
this.time = getTime();
|
|
31
32
|
this.id = generateId();
|
|
33
|
+
this.preloadURL =
|
|
34
|
+
this.isRscRequest() && referer && referer !== '' ? referer : this.url;
|
|
32
35
|
this.ctx = {
|
|
33
36
|
cache: new Map(),
|
|
34
37
|
head: new HeadData({}),
|
|
@@ -39,18 +42,21 @@ export class ServerComponentRequest extends Request {
|
|
|
39
42
|
},
|
|
40
43
|
queryCacheControl: [],
|
|
41
44
|
queryTimings: [],
|
|
45
|
+
analyticsData: {
|
|
46
|
+
url: this.url,
|
|
47
|
+
normalizedRscUrl: this.preloadURL,
|
|
48
|
+
},
|
|
42
49
|
preloadQueries: new Map(),
|
|
43
50
|
};
|
|
44
51
|
this.cookies = this.parseCookies();
|
|
45
|
-
const referer = this.headers.get('referer');
|
|
46
|
-
this.preloadURL =
|
|
47
|
-
this.isRscRequest() && referer && referer !== '' ? referer : this.url;
|
|
48
52
|
}
|
|
49
53
|
parseCookies() {
|
|
50
54
|
const cookieString = this.headers.get('cookie') || '';
|
|
51
55
|
return new Map(cookieString
|
|
52
56
|
.split(';')
|
|
53
|
-
.map((chunk) => chunk.trim()
|
|
57
|
+
.map((chunk) => chunk.trim())
|
|
58
|
+
.filter((chunk) => chunk !== '')
|
|
59
|
+
.map((chunk) => chunk.split(/=(.+)/)));
|
|
54
60
|
}
|
|
55
61
|
isRscRequest() {
|
|
56
62
|
const url = new URL(this.url);
|
|
@@ -123,8 +129,9 @@ function getInitFromNodeRequest(request) {
|
|
|
123
129
|
? request.body
|
|
124
130
|
: undefined,
|
|
125
131
|
};
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
const remoteAddress = request.socket.remoteAddress;
|
|
133
|
+
if (!init.headers.has('x-forwarded-for') && remoteAddress) {
|
|
134
|
+
init.headers.set('x-forwarded-for', remoteAddress);
|
|
128
135
|
}
|
|
129
136
|
return init;
|
|
130
137
|
}
|
|
@@ -8,7 +8,7 @@ export class InMemoryCache {
|
|
|
8
8
|
this.store = new Map();
|
|
9
9
|
}
|
|
10
10
|
put(request, response) {
|
|
11
|
-
logCacheApiStatus('PUT', request.url);
|
|
11
|
+
logCacheApiStatus('PUT-dev', request.url);
|
|
12
12
|
this.store.set(request.url, {
|
|
13
13
|
value: response,
|
|
14
14
|
date: new Date(),
|
|
@@ -18,7 +18,7 @@ export class InMemoryCache {
|
|
|
18
18
|
var _a, _b;
|
|
19
19
|
const match = this.store.get(request.url);
|
|
20
20
|
if (!match) {
|
|
21
|
-
logCacheApiStatus('MISS', request.url);
|
|
21
|
+
logCacheApiStatus('MISS-dev', request.url);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
const { value, date } = match;
|
|
@@ -28,7 +28,7 @@ export class InMemoryCache {
|
|
|
28
28
|
const age = (new Date().valueOf() - date.valueOf()) / 1000;
|
|
29
29
|
const isMiss = age > maxAge + swr;
|
|
30
30
|
if (isMiss) {
|
|
31
|
-
logCacheApiStatus('MISS', request.url);
|
|
31
|
+
logCacheApiStatus('MISS-dev', request.url);
|
|
32
32
|
this.store.delete(request.url);
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
@@ -36,7 +36,7 @@ export class InMemoryCache {
|
|
|
36
36
|
const headers = new Headers(value.headers);
|
|
37
37
|
headers.set('cache', isStale ? 'STALE' : 'HIT');
|
|
38
38
|
headers.set('date', date.toUTCString());
|
|
39
|
-
logCacheApiStatus(headers.get('cache')
|
|
39
|
+
logCacheApiStatus(`${headers.get('cache')}-dev`, request.url);
|
|
40
40
|
const response = new Response(value.body, {
|
|
41
41
|
headers,
|
|
42
42
|
});
|
|
@@ -44,7 +44,7 @@ export class InMemoryCache {
|
|
|
44
44
|
}
|
|
45
45
|
delete(request) {
|
|
46
46
|
this.store.delete(request.url);
|
|
47
|
-
logCacheApiStatus('DELETE', request.url);
|
|
47
|
+
logCacheApiStatus('DELETE-dev', request.url);
|
|
48
48
|
}
|
|
49
49
|
keys(request) {
|
|
50
50
|
const cacheKeys = [];
|
|
@@ -1,14 +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
|
-
export declare function hashKey(key: QueryKey): string;
|
|
12
3
|
/**
|
|
13
4
|
* Get an item from the cache. If a match is found, returns a tuple
|
|
14
5
|
* containing the `JSON.parse` version of the response as well
|
|
@@ -23,4 +14,4 @@ export declare function deleteItemFromCache(key: QueryKey): Promise<void>;
|
|
|
23
14
|
/**
|
|
24
15
|
* Manually check the response to see if it's stale.
|
|
25
16
|
*/
|
|
26
|
-
export declare function isStale(response: Response): boolean;
|
|
17
|
+
export declare function isStale(response: Response, userCacheOptions?: CachingStrategy): boolean;
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { getCache } from './runtime';
|
|
2
2
|
import { CacheSeconds, generateCacheControlHeader, } from '../framework/CachingStrategy';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { hashKey } from '../utilities/hash';
|
|
4
|
+
import { logCacheApiStatus } from '../utilities/log';
|
|
5
|
+
function getCacheControlSetting(userCacheOptions, options) {
|
|
6
|
+
if (userCacheOptions && options) {
|
|
7
|
+
return {
|
|
8
|
+
...userCacheOptions,
|
|
9
|
+
...options,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return userCacheOptions || CacheSeconds();
|
|
14
|
+
}
|
|
13
15
|
}
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* TODO: Smarter hash
|
|
18
|
-
*/
|
|
19
|
-
return rawKey.map((k) => JSON.stringify(k)).join('');
|
|
16
|
+
export function generateSubRequestCacheControlHeader(userCacheOptions) {
|
|
17
|
+
return generateCacheControlHeader(getCacheControlSetting(userCacheOptions));
|
|
20
18
|
}
|
|
21
19
|
/**
|
|
22
20
|
* Cache API is weird. We just need a full URL, so we make one up.
|
|
@@ -37,8 +35,11 @@ export async function getItemFromCache(key) {
|
|
|
37
35
|
const url = getKeyUrl(hashKey(key));
|
|
38
36
|
const request = new Request(url);
|
|
39
37
|
const response = await cache.match(request);
|
|
40
|
-
if (!response)
|
|
38
|
+
if (!response) {
|
|
39
|
+
logCacheApiStatus('MISS', url);
|
|
41
40
|
return;
|
|
41
|
+
}
|
|
42
|
+
logCacheApiStatus('HIT', url);
|
|
42
43
|
return [await response.json(), response];
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
@@ -51,14 +52,53 @@ export async function setItemInCache(key, value, userCacheOptions) {
|
|
|
51
52
|
}
|
|
52
53
|
const url = getKeyUrl(hashKey(key));
|
|
53
54
|
const request = new Request(url);
|
|
55
|
+
/**
|
|
56
|
+
* We are manually managing staled request by adding this workaround.
|
|
57
|
+
* Why? cache control header support is dependent on hosting platform
|
|
58
|
+
*
|
|
59
|
+
* For example:
|
|
60
|
+
*
|
|
61
|
+
* Cloudflare's Cache API does not support `stale-while-revalidate`.
|
|
62
|
+
* Cloudflare cache control header has a very odd behaviour.
|
|
63
|
+
* Say we have the following cache control header on a request:
|
|
64
|
+
*
|
|
65
|
+
* public, max-age=15, stale-while-revalidate=30
|
|
66
|
+
*
|
|
67
|
+
* When there is a cache.match HIT, the cache control header would become
|
|
68
|
+
*
|
|
69
|
+
* public, max-age=14400, stale-while-revalidate=30
|
|
70
|
+
*
|
|
71
|
+
* == `stale-while-revalidate` workaround ==
|
|
72
|
+
* Update response max-age so that:
|
|
73
|
+
*
|
|
74
|
+
* max-age = max-age + stale-while-revalidate
|
|
75
|
+
*
|
|
76
|
+
* For example:
|
|
77
|
+
*
|
|
78
|
+
* public, max-age=1, stale-while-revalidate=9
|
|
79
|
+
* |
|
|
80
|
+
* V
|
|
81
|
+
* public, max-age=10, stale-while-revalidate=9
|
|
82
|
+
*
|
|
83
|
+
* Store the following information in the response header:
|
|
84
|
+
*
|
|
85
|
+
* cache-put-date - UTC time string of when this request is PUT into cache
|
|
86
|
+
*
|
|
87
|
+
* Note on `cache-put-date`: The `response.headers.get('date')` isn't static. I am
|
|
88
|
+
* not positive what date this is returning but it is never over 500 ms
|
|
89
|
+
* after subtracting from the current timestamp.
|
|
90
|
+
*
|
|
91
|
+
* `isStale` function will use the above information to test for stale-ness of a cached response
|
|
92
|
+
*/
|
|
93
|
+
const cacheControl = getCacheControlSetting(userCacheOptions);
|
|
54
94
|
const headers = new Headers({
|
|
55
|
-
'cache-control': generateSubRequestCacheControlHeader(
|
|
95
|
+
'cache-control': generateSubRequestCacheControlHeader(getCacheControlSetting(cacheControl, {
|
|
96
|
+
maxAge: (cacheControl.maxAge || 0) + (cacheControl.staleWhileRevalidate || 0),
|
|
97
|
+
})),
|
|
98
|
+
'cache-put-date': new Date().toUTCString(),
|
|
56
99
|
});
|
|
57
100
|
const response = new Response(JSON.stringify(value), { headers });
|
|
58
|
-
|
|
59
|
-
* WARNING: Cloudflare's Cache API does not support `stale-while-revalidate`
|
|
60
|
-
* so this implementation will not work as expected on that platform.
|
|
61
|
-
*/
|
|
101
|
+
logCacheApiStatus('PUT', url);
|
|
62
102
|
await cache.put(request, response);
|
|
63
103
|
}
|
|
64
104
|
export async function deleteItemFromCache(key) {
|
|
@@ -67,20 +107,17 @@ export async function deleteItemFromCache(key) {
|
|
|
67
107
|
return;
|
|
68
108
|
const url = getKeyUrl(hashKey(key));
|
|
69
109
|
const request = new Request(url);
|
|
110
|
+
logCacheApiStatus('DELETE', url);
|
|
70
111
|
await cache.delete(request);
|
|
71
112
|
}
|
|
72
113
|
/**
|
|
73
114
|
* Manually check the response to see if it's stale.
|
|
74
115
|
*/
|
|
75
|
-
export function isStale(response) {
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
if (!responseDate
|
|
79
|
-
return false;
|
|
80
|
-
const responseMaxAgeMatch = responseCacheControl.match(/max-age=(\d+)/);
|
|
81
|
-
if (!responseMaxAgeMatch)
|
|
116
|
+
export function isStale(response, userCacheOptions) {
|
|
117
|
+
const responseMaxAge = getCacheControlSetting(userCacheOptions).maxAge || 0;
|
|
118
|
+
const responseDate = response.headers.get('cache-put-date');
|
|
119
|
+
if (!responseDate)
|
|
82
120
|
return false;
|
|
83
|
-
const responseMaxAge = parseInt(responseMaxAgeMatch[1]);
|
|
84
121
|
const ageInMs = new Date().valueOf() - new Date(responseDate).valueOf();
|
|
85
122
|
const age = ageInMs / 1000;
|
|
86
123
|
return age > responseMaxAge;
|
|
@@ -12,6 +12,15 @@ import react from '@vitejs/plugin-react';
|
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import cssModulesRsc from './plugins/vite-plugin-css-modules-rsc';
|
|
14
14
|
export default (shopifyConfig, pluginOptions = {}) => {
|
|
15
|
+
let hydrogenUiPath;
|
|
16
|
+
try {
|
|
17
|
+
hydrogenUiPath = path.join(
|
|
18
|
+
// eslint-disable-next-line node/no-missing-require
|
|
19
|
+
path.dirname(require.resolve('@shopify/hydrogen-ui/client')));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// hydrogen-ui isn't installed, so don't worry about it
|
|
23
|
+
}
|
|
15
24
|
return [
|
|
16
25
|
process.env.VITE_INSPECT && inspect(),
|
|
17
26
|
hydrogenConfig(),
|
|
@@ -24,6 +33,7 @@ export default (shopifyConfig, pluginOptions = {}) => {
|
|
|
24
33
|
rsc({
|
|
25
34
|
clientComponentPaths: [
|
|
26
35
|
path.join(path.dirname(require.resolve('@shopify/hydrogen/package.json'))),
|
|
36
|
+
...[hydrogenUiPath].filter(Boolean),
|
|
27
37
|
],
|
|
28
38
|
isServerComponentImporterAllowed(importer, source) {
|
|
29
39
|
// Always allow the entry server (e.g. App.server.jsx) to be imported
|
|
@@ -20,7 +20,7 @@ export default function cssModulesRsc() {
|
|
|
20
20
|
enforce: 'post',
|
|
21
21
|
transform(code, id) {
|
|
22
22
|
if (id.includes('.module.') && cssMap.has(id)) {
|
|
23
|
-
return code.replace(/export default .*$/gms, `import React from 'react'; export const StyleTag = () => React.createElement('style', {
|
|
23
|
+
return code.replace(/export default .*$/gms, `import React from 'react'; export const StyleTag = () => React.createElement('style', {dangerouslySetInnerHTML: {__html: ${JSON.stringify(cssMap.get(id))}}});`);
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
},
|
|
@@ -36,11 +36,18 @@ export default () => {
|
|
|
36
36
|
// Reload when updating local Hydrogen lib
|
|
37
37
|
server: process.env.LOCAL_DEV && {
|
|
38
38
|
watch: {
|
|
39
|
-
ignored: [
|
|
39
|
+
ignored: [
|
|
40
|
+
'!**/node_modules/@shopify/hydrogen/**',
|
|
41
|
+
'!**/node_modules/@shopify/hydrogen-ui/**',
|
|
42
|
+
],
|
|
40
43
|
},
|
|
41
44
|
},
|
|
42
45
|
optimizeDeps: {
|
|
43
|
-
exclude: [
|
|
46
|
+
exclude: [
|
|
47
|
+
'@shopify/hydrogen/client',
|
|
48
|
+
'@shopify/hydrogen/entry-client',
|
|
49
|
+
'@shopify/hydrogen-ui',
|
|
50
|
+
],
|
|
44
51
|
include: [
|
|
45
52
|
/**
|
|
46
53
|
* Additionally, the following dependencies have trouble loading the
|
|
@@ -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
|
*/
|