@shopify/hydrogen 0.25.1 → 0.26.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 +72 -0
- package/dist/esnext/components/{CartEstimatedCost/CartEstimatedCost.client.d.ts → CartCost/CartCost.client.d.ts} +3 -3
- package/dist/esnext/components/{CartEstimatedCost/CartEstimatedCost.client.js → CartCost/CartCost.client.js} +7 -7
- package/dist/esnext/components/CartCost/index.d.ts +1 -0
- package/dist/esnext/components/CartCost/index.js +1 -0
- package/dist/esnext/components/CartLinePrice/CartLinePrice.client.js +2 -2
- package/dist/esnext/components/CartLineProvider/context.d.ts +3 -3
- package/dist/esnext/components/CartProvider/CartProvider.client.js +37 -27
- package/dist/esnext/components/CartProvider/cart-queries.d.ts +1 -1
- package/dist/esnext/components/CartProvider/cart-queries.js +3 -3
- package/dist/esnext/components/CartProvider/graphql/CartAttributesUpdateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartBuyerIdentityUpdateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartCreateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartDiscountCodesUpdateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartFragment.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartLineAddMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartLineRemoveMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartLineUpdateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartNoteUpdateMutation.d.ts +5 -5
- package/dist/esnext/components/CartProvider/graphql/CartQuery.d.ts +5 -5
- package/dist/esnext/components/CartProvider/hooks.client.d.ts +1 -1
- package/dist/esnext/components/CartProvider/hooks.client.js +16 -11
- package/dist/esnext/components/CartProvider/types.d.ts +3 -3
- package/dist/esnext/components/CartShopPayButton/CartShopPayButton.client.d.ts +1 -3
- package/dist/esnext/components/CartShopPayButton/CartShopPayButton.client.js +2 -4
- package/dist/esnext/components/Link/Link.client.d.ts +2 -0
- package/dist/esnext/components/Link/Link.client.js +8 -4
- package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.d.ts +2 -2
- package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +1 -1
- package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +6 -3
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +8 -20
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +17 -33
- package/dist/esnext/components/LocalizationProvider/index.d.ts +0 -1
- package/dist/esnext/components/LocalizationProvider/index.js +0 -1
- package/dist/esnext/components/Metafield/Metafield.client.js +3 -3
- package/dist/esnext/components/Seo/DefaultPageSeo.client.js +3 -3
- package/dist/esnext/components/Seo/DescriptionSeo.client.js +1 -1
- package/dist/esnext/components/Seo/HomePageSeo.client.d.ts +1 -1
- package/dist/esnext/components/Seo/HomePageSeo.client.js +3 -3
- package/dist/esnext/components/Seo/ImageSeo.client.js +1 -1
- package/dist/esnext/components/Seo/NoIndexSeo.client.js +3 -3
- package/dist/esnext/components/Seo/ProductSeo.client.js +2 -2
- package/dist/esnext/components/Seo/Seo.client.js +1 -1
- package/dist/esnext/components/Seo/TitleSeo.client.js +1 -1
- package/dist/esnext/components/Seo/TwitterSeo.client.js +1 -1
- package/dist/esnext/components/Seo/seo-types.d.ts +1 -0
- package/dist/esnext/components/ShopPayButton/ShopPayButton.client.d.ts +3 -1
- package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +8 -3
- package/dist/esnext/components/index.d.ts +2 -2
- package/dist/esnext/components/index.js +2 -2
- package/dist/esnext/constants.d.ts +1 -1
- package/dist/esnext/constants.js +1 -1
- package/dist/esnext/entry-client.js +7 -5
- package/dist/esnext/entry-server.js +21 -34
- package/dist/esnext/foundation/Analytics/connectors/PerformanceMetrics/PerformanceMetricsDebug.client.js +1 -1
- package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +1 -1
- package/dist/esnext/foundation/FileRoutes/FileRoutes.server.js +1 -1
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +9 -1
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +6 -0
- package/dist/esnext/foundation/Route/Route.server.js +1 -1
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +5 -4
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +9 -7
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +4 -0
- package/dist/esnext/foundation/ShopifyProvider/types.d.ts +1 -0
- package/dist/esnext/foundation/useNavigate/useNavigate.d.ts +2 -0
- package/dist/esnext/foundation/useNavigate/useNavigate.js +13 -0
- package/dist/esnext/foundation/useQuery/hooks.js +1 -4
- package/dist/esnext/foundation/useRouteParams/RouteParamsProvider.client.d.ts +3 -0
- package/dist/esnext/foundation/useRouteParams/RouteParamsProvider.client.js +8 -3
- package/dist/esnext/framework/cache/in-memory.d.ts +12 -7
- package/dist/esnext/framework/cache/in-memory.js +49 -21
- package/dist/esnext/framework/load-config.d.ts +6 -0
- package/dist/esnext/framework/load-config.js +10 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-client-middleware.js +2 -3
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-virtual-files.js +21 -4
- package/dist/esnext/framework/viteception.d.ts +2 -1
- package/dist/esnext/framework/viteception.js +2 -1
- package/dist/esnext/hooks/useCartLine/useCartLine.d.ts +3 -3
- package/dist/esnext/hooks/useLocalization/useLocalization.d.ts +4 -0
- package/dist/esnext/hooks/useLocalization/useLocalization.js +14 -0
- package/dist/esnext/hooks/useMeasurement/hooks.js +2 -2
- package/dist/esnext/hooks/useMoney/hooks.js +2 -2
- package/dist/esnext/index.d.ts +3 -0
- package/dist/esnext/index.js +1 -0
- package/dist/esnext/platforms/node.d.ts +1 -1
- package/dist/esnext/platforms/node.js +2 -1
- package/dist/esnext/storefront-api-types.d.ts +105 -3
- package/dist/esnext/storefront-api-types.js +8 -0
- package/dist/esnext/types.d.ts +10 -0
- package/dist/esnext/utilities/apiRoutes.d.ts +4 -3
- package/dist/esnext/utilities/log/log-cache-api-status.js +3 -0
- package/dist/esnext/utilities/storefrontApi.d.ts +1 -0
- package/dist/esnext/utilities/storefrontApi.js +1 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/framework/cache/in-memory.d.ts +12 -7
- package/dist/node/framework/cache/in-memory.js +49 -21
- package/dist/node/framework/load-config.d.ts +6 -0
- package/dist/node/framework/load-config.js +14 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-client-middleware.js +2 -3
- package/dist/node/framework/plugins/vite-plugin-hydrogen-virtual-files.js +21 -4
- package/dist/node/framework/viteception.d.ts +2 -1
- package/dist/node/framework/viteception.js +2 -1
- package/package.json +13 -3
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +21 -6
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +2 -1
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +21 -6
- package/dist/esnext/components/CartEstimatedCost/index.d.ts +0 -1
- package/dist/esnext/components/CartEstimatedCost/index.js +0 -1
- package/dist/esnext/hooks/useCountry/index.d.ts +0 -1
- package/dist/esnext/hooks/useCountry/index.js +0 -1
- package/dist/esnext/hooks/useCountry/useCountry.d.ts +0 -7
- package/dist/esnext/hooks/useCountry/useCountry.js +0 -17
|
@@ -6,4 +6,4 @@ export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Pr
|
|
|
6
6
|
export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
|
|
7
7
|
export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
|
|
8
8
|
export declare const SHOPIFY_STOREFRONT_ID_VARIABLE = "SHOPIFY_STOREFRONT_ID";
|
|
9
|
-
export declare const SHOPIFY_STOREFRONT_ID_HEADER = "
|
|
9
|
+
export declare const SHOPIFY_STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
|
package/dist/esnext/constants.js
CHANGED
|
@@ -6,4 +6,4 @@ export const STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-To
|
|
|
6
6
|
export const STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
|
|
7
7
|
export const STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
|
|
8
8
|
export const SHOPIFY_STOREFRONT_ID_VARIABLE = 'SHOPIFY_STOREFRONT_ID';
|
|
9
|
-
export const SHOPIFY_STOREFRONT_ID_HEADER = '
|
|
9
|
+
export const SHOPIFY_STOREFRONT_ID_HEADER = 'Shopify-Storefront-Id';
|
|
@@ -89,11 +89,13 @@ const renderHydrogen = async (ClientWrapper) => {
|
|
|
89
89
|
const RootComponent =
|
|
90
90
|
// Default to StrictMode on, unless explicitly turned off
|
|
91
91
|
config.strictMode !== false ? StrictMode : Fragment;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
// Fixes hydration in `useId`: https://github.com/Shopify/hydrogen/issues/1589
|
|
93
|
+
const ServerRequestProviderMock = () => null;
|
|
94
|
+
hydrateRoot(root, React.createElement(RootComponent, null,
|
|
95
|
+
React.createElement(ServerRequestProviderMock, null),
|
|
96
|
+
React.createElement(ErrorBoundary, { FallbackComponent: Error },
|
|
97
|
+
React.createElement(Suspense, { fallback: null },
|
|
98
|
+
React.createElement(Content, { clientWrapper: ClientWrapper })))));
|
|
97
99
|
};
|
|
98
100
|
export default renderHydrogen;
|
|
99
101
|
function Content({ clientWrapper: ClientWrapper = ({ children }) => children, }) {
|
|
@@ -47,6 +47,10 @@ export const renderHydrogen = (App) => {
|
|
|
47
47
|
setLogger(hydrogenConfig.logger);
|
|
48
48
|
const log = getLoggerWithContext(request);
|
|
49
49
|
const response = new HydrogenResponse();
|
|
50
|
+
if (hydrogenConfig.poweredByHeader ?? true) {
|
|
51
|
+
// If not defined in the config, always show the header
|
|
52
|
+
response.headers.set('powered-by', 'Shopify-Hydrogen');
|
|
53
|
+
}
|
|
50
54
|
const sessionApi = hydrogenConfig.session
|
|
51
55
|
? hydrogenConfig.session(log)
|
|
52
56
|
: undefined;
|
|
@@ -55,14 +59,6 @@ export const renderHydrogen = (App) => {
|
|
|
55
59
|
* Inject the cache & context into the module loader so we can pull it out for subrequests.
|
|
56
60
|
*/
|
|
57
61
|
request.ctx.runtime = context;
|
|
58
|
-
if (!context?.waitUntil) {
|
|
59
|
-
const runtimeContext = {
|
|
60
|
-
waitUntil: (fn) => {
|
|
61
|
-
setTimeout(() => fn, 0);
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
request.ctx.runtime = runtimeContext;
|
|
65
|
-
}
|
|
66
62
|
setCache(cache);
|
|
67
63
|
const builtInRouteResource = getBuiltInRoute(url);
|
|
68
64
|
if (builtInRouteResource) {
|
|
@@ -84,7 +80,7 @@ export const renderHydrogen = (App) => {
|
|
|
84
80
|
if (cachedResponse) {
|
|
85
81
|
if (isStale(request, cachedResponse)) {
|
|
86
82
|
const lockCacheKey = request.cacheKey(true);
|
|
87
|
-
const
|
|
83
|
+
const staleWhileRevalidatePromise = getItemFromCache(lockCacheKey).then(async (lockExists) => {
|
|
88
84
|
if (lockExists)
|
|
89
85
|
return;
|
|
90
86
|
try {
|
|
@@ -98,13 +94,9 @@ export const renderHydrogen = (App) => {
|
|
|
98
94
|
catch (e) {
|
|
99
95
|
log.error('Cache revalidate error', e);
|
|
100
96
|
}
|
|
101
|
-
|
|
102
|
-
await deleteItemFromCache(lockCacheKey);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const revalidatingPromise = getItemFromCache(lockCacheKey).then(staleWhileRevalidate);
|
|
97
|
+
});
|
|
106
98
|
// Asynchronously wait for it in workers
|
|
107
|
-
request.ctx.runtime?.waitUntil(
|
|
99
|
+
request.ctx.runtime?.waitUntil(staleWhileRevalidatePromise);
|
|
108
100
|
}
|
|
109
101
|
return cachedResponse;
|
|
110
102
|
}
|
|
@@ -195,11 +187,8 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
|
|
|
195
187
|
const AppSSR = (React.createElement(Html, { template: response.canStream() ? noScriptTemplate : template, hydrogenConfig: request.ctx.hydrogenConfig },
|
|
196
188
|
React.createElement(ServerRequestProvider, { request: request },
|
|
197
189
|
React.createElement(ServerPropsProvider, { initialServerProps: state, setServerPropsForRsc: () => { } },
|
|
198
|
-
React.createElement(
|
|
199
|
-
React.createElement(
|
|
200
|
-
React.createElement(RscConsumer, null)),
|
|
201
|
-
React.createElement(Suspense, { fallback: null },
|
|
202
|
-
React.createElement(Analytics, null)))))));
|
|
190
|
+
React.createElement(Suspense, { fallback: null },
|
|
191
|
+
React.createElement(RscConsumer, null))))));
|
|
203
192
|
log.trace('start ssr');
|
|
204
193
|
const rscReadable = response.canStream()
|
|
205
194
|
? new ReadableStream({
|
|
@@ -324,6 +313,9 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
|
|
|
324
313
|
}
|
|
325
314
|
else if (nodeResponse) {
|
|
326
315
|
const savedChunks = tagOnWrite(nodeResponse);
|
|
316
|
+
nodeResponse.on('finish', () => {
|
|
317
|
+
cacheResponse(response, request, savedChunks, revalidate);
|
|
318
|
+
});
|
|
327
319
|
const { pipe } = ssrRenderToPipeableStream(AppSSR, {
|
|
328
320
|
nonce,
|
|
329
321
|
bootstrapScripts,
|
|
@@ -357,7 +349,6 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
|
|
|
357
349
|
if (!revalidate &&
|
|
358
350
|
(response.canStream() || nodeResponse.writableEnded)) {
|
|
359
351
|
postRequestTasks('str', nodeResponse.statusCode, request, response);
|
|
360
|
-
cacheResponse(response, request, savedChunks, revalidate);
|
|
361
352
|
return;
|
|
362
353
|
}
|
|
363
354
|
writeHeadToNodeResponse(nodeResponse, response, log, didError());
|
|
@@ -378,7 +369,6 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
|
|
|
378
369
|
if (!error) {
|
|
379
370
|
html = assembleHtml({ ssrHtml, rscPayload, request, template });
|
|
380
371
|
postRequestTasks('ssr', nodeResponse.statusCode, request, response);
|
|
381
|
-
cacheResponse(response, request, [html], revalidate);
|
|
382
372
|
}
|
|
383
373
|
nodeResponse.write(html);
|
|
384
374
|
nodeResponse.end();
|
|
@@ -412,13 +402,13 @@ async function runSSR({ rsc, state, request, response, nodeResponse, template, n
|
|
|
412
402
|
function runRSC({ App, state, log, request, response }) {
|
|
413
403
|
const serverProps = { ...state, request, response, log };
|
|
414
404
|
request.ctx.router.serverProps = serverProps;
|
|
405
|
+
preloadRequestCacheData(request);
|
|
415
406
|
const AppRSC = (React.createElement(ServerRequestProvider, { request: request },
|
|
416
|
-
React.createElement(
|
|
417
|
-
|
|
418
|
-
React.createElement(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
React.createElement(DevTools, null))))));
|
|
407
|
+
React.createElement(App, { ...serverProps }),
|
|
408
|
+
React.createElement(Suspense, { fallback: null },
|
|
409
|
+
React.createElement(Analytics, null)),
|
|
410
|
+
request.ctx.hydrogenConfig?.__EXPERIMENTAL__devTools && (React.createElement(Suspense, { fallback: null },
|
|
411
|
+
React.createElement(DevTools, null)))));
|
|
422
412
|
let rscDidError;
|
|
423
413
|
const rscReadable = rscRenderToReadableStream(AppRSC, {
|
|
424
414
|
onError(e) {
|
|
@@ -428,11 +418,6 @@ function runRSC({ App, state, log, request, response }) {
|
|
|
428
418
|
});
|
|
429
419
|
return { readable: rscReadable, didError: () => rscDidError };
|
|
430
420
|
}
|
|
431
|
-
function PreloadQueries({ request, children, }) {
|
|
432
|
-
const preloadQueries = request.getPreloadQueries();
|
|
433
|
-
preloadRequestCacheData(request, preloadQueries);
|
|
434
|
-
return React.createElement(React.Fragment, null, children);
|
|
435
|
-
}
|
|
436
421
|
export default renderHydrogen;
|
|
437
422
|
function startWritingToNodeResponse(nodeResponse, error) {
|
|
438
423
|
if (!nodeResponse.headersSent) {
|
|
@@ -574,7 +559,8 @@ async function cacheResponse(response, request, chunks, revalidate) {
|
|
|
574
559
|
await saveCacheResponse(response, request, chunks);
|
|
575
560
|
}
|
|
576
561
|
else {
|
|
577
|
-
|
|
562
|
+
const cachePutPromise = Promise.resolve(true).then(() => saveCacheResponse(response, request, chunks));
|
|
563
|
+
request.ctx.runtime?.waitUntil(cachePutPromise);
|
|
578
564
|
}
|
|
579
565
|
}
|
|
580
566
|
}
|
|
@@ -605,5 +591,6 @@ async function saveCacheResponse(response, request, chunks) {
|
|
|
605
591
|
statusText,
|
|
606
592
|
headers,
|
|
607
593
|
}), response.cache());
|
|
594
|
+
deleteItemFromCache(request.cacheKey(true));
|
|
608
595
|
}
|
|
609
596
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { parse, stringify } from 'worktop/cookie';
|
|
3
|
-
import { ClientAnalytics } from '../../
|
|
3
|
+
import { ClientAnalytics } from '../../ClientAnalytics';
|
|
4
4
|
import { buildUUID, addDataIf } from './utils';
|
|
5
5
|
import { SHOPIFY_S, SHOPIFY_Y } from './const';
|
|
6
6
|
const longTermLength = 60 * 60 * 24 * 360 * 2; // ~2 year expiry
|
|
@@ -33,7 +33,7 @@ export function FileRoutes({ routes, basePath, dirPrefix }) {
|
|
|
33
33
|
if (foundRoute) {
|
|
34
34
|
request.ctx.router.routeRendered = true;
|
|
35
35
|
request.ctx.router.routeParams = foundRouteDetails.params;
|
|
36
|
-
return (React.createElement(RouteParamsProvider, { routeParams: foundRouteDetails.params },
|
|
36
|
+
return (React.createElement(RouteParamsProvider, { routeParams: foundRouteDetails.params, basePath: basePath },
|
|
37
37
|
React.createElement(foundRoute.component, { params: foundRouteDetails.params, ...serverProps })));
|
|
38
38
|
}
|
|
39
39
|
return null;
|
|
@@ -4,9 +4,10 @@ import type { QueryTiming } from '../../utilities/log/log-query-timeline';
|
|
|
4
4
|
import type { ResolvedHydrogenConfig, PreloadOptions, QueryKey, RuntimeContext } from '../../types';
|
|
5
5
|
import { HelmetData as HeadData } from 'react-helmet-async';
|
|
6
6
|
import { SessionSyncApi } from '../session/session';
|
|
7
|
+
import { LocalizationContextValue } from '../../components/LocalizationProvider/LocalizationContext.client';
|
|
7
8
|
export declare type PreloadQueryEntry = {
|
|
8
9
|
key: QueryKey;
|
|
9
|
-
fetcher: () => Promise<unknown>;
|
|
10
|
+
fetcher: (request: HydrogenRequest) => Promise<unknown>;
|
|
10
11
|
preload?: PreloadOptions;
|
|
11
12
|
};
|
|
12
13
|
export declare type PreloadQueriesByURL = Map<string, PreloadQueryEntry>;
|
|
@@ -24,9 +25,15 @@ export declare type RouterContextData = {
|
|
|
24
25
|
* - Adds a static constructor to convert a Node.js `IncomingMessage` to a Request.
|
|
25
26
|
*/
|
|
26
27
|
export declare class HydrogenRequest extends Request {
|
|
28
|
+
/**
|
|
29
|
+
* A Map of cookies for easy access.
|
|
30
|
+
*/
|
|
27
31
|
cookies: Map<string, string>;
|
|
28
32
|
id: string;
|
|
29
33
|
time: number;
|
|
34
|
+
/**
|
|
35
|
+
* Get the canonical URL for the current page, across SSR and RSC requests.
|
|
36
|
+
*/
|
|
30
37
|
normalizedUrl: string;
|
|
31
38
|
ctx: {
|
|
32
39
|
cache: Map<string, any>;
|
|
@@ -42,6 +49,7 @@ export declare class HydrogenRequest extends Request {
|
|
|
42
49
|
session?: SessionSyncApi;
|
|
43
50
|
runtime?: RuntimeContext;
|
|
44
51
|
scopes: Map<string, Record<string, any>>;
|
|
52
|
+
localization?: LocalizationContextValue;
|
|
45
53
|
[key: string]: any;
|
|
46
54
|
};
|
|
47
55
|
constructor(input: any);
|
|
@@ -22,9 +22,15 @@ const PRELOAD_ALL = '*';
|
|
|
22
22
|
* - Adds a static constructor to convert a Node.js `IncomingMessage` to a Request.
|
|
23
23
|
*/
|
|
24
24
|
export class HydrogenRequest extends Request {
|
|
25
|
+
/**
|
|
26
|
+
* A Map of cookies for easy access.
|
|
27
|
+
*/
|
|
25
28
|
cookies;
|
|
26
29
|
id;
|
|
27
30
|
time;
|
|
31
|
+
/**
|
|
32
|
+
* Get the canonical URL for the current page, across SSR and RSC requests.
|
|
33
|
+
*/
|
|
28
34
|
normalizedUrl;
|
|
29
35
|
// CFW Request has a reserved 'context' property, use 'ctx' instead.
|
|
30
36
|
ctx;
|
|
@@ -22,7 +22,7 @@ export function Route({ path, page }) {
|
|
|
22
22
|
if (match) {
|
|
23
23
|
request.ctx.router.routeRendered = true;
|
|
24
24
|
request.ctx.router.routeParams = match.params;
|
|
25
|
-
return (React.createElement(RouteParamsProvider, { routeParams: match.params }, cloneElement(page, { params: match.params || {}, ...serverProps })));
|
|
25
|
+
return (React.createElement(RouteParamsProvider, { routeParams: match.params, basePath: '/' }, cloneElement(page, { params: match.params || {}, ...serverProps })));
|
|
26
26
|
}
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { HydrogenRequest } from '../HydrogenRequest/HydrogenRequest.server';
|
|
2
3
|
import type { QueryKey } from '../../types';
|
|
3
4
|
declare type ServerRequestProviderProps = {
|
|
4
5
|
request: HydrogenRequest;
|
|
5
|
-
children:
|
|
6
|
+
children: React.ReactNode;
|
|
6
7
|
};
|
|
7
8
|
export declare function ServerRequestProvider({ request, children, }: ServerRequestProviderProps): JSX.Element;
|
|
8
9
|
export declare function useServerRequest(): HydrogenRequest;
|
|
@@ -17,6 +18,6 @@ declare type RequestCacheResult<T> = {
|
|
|
17
18
|
* Returns data stored in the request cache.
|
|
18
19
|
* It will throw the promise if data is not ready.
|
|
19
20
|
*/
|
|
20
|
-
export declare function useRequestCacheData<T>(key: QueryKey, fetcher: () => T | Promise<T>): RequestCacheResult<T>;
|
|
21
|
-
export declare function preloadRequestCacheData(request: HydrogenRequest
|
|
21
|
+
export declare function useRequestCacheData<T>(key: QueryKey, fetcher: (request: HydrogenRequest) => T | Promise<T>): RequestCacheResult<T>;
|
|
22
|
+
export declare function preloadRequestCacheData(request: HydrogenRequest): void;
|
|
22
23
|
export {};
|
|
@@ -10,9 +10,10 @@ function requestCacheRSC() {
|
|
|
10
10
|
}
|
|
11
11
|
requestCacheRSC.key = Symbol.for('HYDROGEN_REQUEST');
|
|
12
12
|
function getInternalReactDispatcher() {
|
|
13
|
+
return (
|
|
13
14
|
// @ts-ignore
|
|
14
|
-
|
|
15
|
-
.ReactCurrentDispatcher.current;
|
|
15
|
+
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
|
16
|
+
.ReactCurrentDispatcher.current || {});
|
|
16
17
|
}
|
|
17
18
|
function isRsc() {
|
|
18
19
|
// This flag is added by RSC Vite plugin
|
|
@@ -36,7 +37,7 @@ export function ServerRequestProvider({ request, children, }) {
|
|
|
36
37
|
// scoped to this current rendering.
|
|
37
38
|
const requestCache = getCacheForType(requestCacheRSC);
|
|
38
39
|
requestCache.set(requestCacheRSC.key, request);
|
|
39
|
-
return children;
|
|
40
|
+
return React.createElement(React.Fragment, null, children);
|
|
40
41
|
}
|
|
41
42
|
// Use a normal provider in SSR to make the request object
|
|
42
43
|
// available in the current rendering.
|
|
@@ -74,7 +75,7 @@ export function useRequestCacheData(key, fetcher) {
|
|
|
74
75
|
}
|
|
75
76
|
if (!promise) {
|
|
76
77
|
const startApiTime = getTime();
|
|
77
|
-
const maybePromise = fetcher();
|
|
78
|
+
const maybePromise = fetcher(request);
|
|
78
79
|
if (!(maybePromise instanceof Promise)) {
|
|
79
80
|
result = { data: maybePromise };
|
|
80
81
|
return result;
|
|
@@ -94,8 +95,9 @@ export function useRequestCacheData(key, fetcher) {
|
|
|
94
95
|
throw result;
|
|
95
96
|
return result;
|
|
96
97
|
}
|
|
97
|
-
export function preloadRequestCacheData(request
|
|
98
|
-
const
|
|
98
|
+
export function preloadRequestCacheData(request) {
|
|
99
|
+
const preloadQueries = request.getPreloadQueries();
|
|
100
|
+
const { cache } = request.ctx;
|
|
99
101
|
preloadQueries?.forEach((preloadQuery, cacheKey) => {
|
|
100
102
|
collectQueryTimings(request, preloadQuery.key, 'preload');
|
|
101
103
|
if (!cache.has(cacheKey)) {
|
|
@@ -108,7 +110,7 @@ export function preloadRequestCacheData(request, preloadQueries) {
|
|
|
108
110
|
}
|
|
109
111
|
if (!promise) {
|
|
110
112
|
const startApiTime = getTime();
|
|
111
|
-
promise = preloadQuery.fetcher().then((data) => {
|
|
113
|
+
promise = preloadQuery.fetcher(request).then((data) => {
|
|
112
114
|
result = { data };
|
|
113
115
|
collectQueryTimings(request, preloadQuery.key, 'resolved', getTime() - startApiTime);
|
|
114
116
|
}, (error) => {
|
|
@@ -2,9 +2,12 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { ShopifyProviderClient } from './ShopifyProvider.client';
|
|
3
3
|
import { DEFAULT_LOCALE } from '../constants';
|
|
4
4
|
import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
|
|
5
|
+
import { getOxygenVariable } from '../../utilities/storefrontApi';
|
|
6
|
+
import { SHOPIFY_STOREFRONT_ID_VARIABLE } from '../../constants';
|
|
5
7
|
function makeShopifyContext(shopifyConfig) {
|
|
6
8
|
const locale = shopifyConfig.defaultLocale ?? DEFAULT_LOCALE;
|
|
7
9
|
const languageCode = locale.split(/[-_]/)[0];
|
|
10
|
+
const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
|
|
8
11
|
return {
|
|
9
12
|
locale: locale.toUpperCase(),
|
|
10
13
|
languageCode: languageCode.toUpperCase(),
|
|
@@ -12,6 +15,7 @@ function makeShopifyContext(shopifyConfig) {
|
|
|
12
15
|
storefrontToken: shopifyConfig.storefrontToken,
|
|
13
16
|
storefrontApiVersion: shopifyConfig.storefrontApiVersion,
|
|
14
17
|
multipassSecret: shopifyConfig.multipassSecret,
|
|
18
|
+
storefrontId,
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
21
|
export const SHOPIFY_PROVIDER_CONTEXT_KEY = Symbol.for('SHOPIFY_PROVIDER_RSC');
|
|
@@ -4,6 +4,7 @@ import type { ShopifyConfigFetcher, ShopifyConfig } from '../../types';
|
|
|
4
4
|
export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLocale'> {
|
|
5
5
|
locale: `${LanguageCode}-${CountryCode}`;
|
|
6
6
|
languageCode: `${LanguageCode}`;
|
|
7
|
+
storefrontId: string | null;
|
|
7
8
|
}
|
|
8
9
|
export declare type ShopifyProviderProps = {
|
|
9
10
|
/** Shopify connection information. Defaults to the `shopify` property in the `hydrogen.config.js` file. */
|
|
@@ -7,9 +7,11 @@ declare type NavigationOptions = {
|
|
|
7
7
|
clientState?: any;
|
|
8
8
|
/** Whether to emulate natural browser behavior and restore scroll position on navigation. Defaults to true. */
|
|
9
9
|
scroll?: any;
|
|
10
|
+
basePath?: string;
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
13
|
* The useNavigate hook imperatively navigates between routes.
|
|
13
14
|
*/
|
|
14
15
|
export declare function useNavigate(): (path: string, options?: NavigationOptions) => void;
|
|
16
|
+
export declare function buildPath(basePath: string, path: string): string;
|
|
15
17
|
export {};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { useRouter } from '../Router/BrowserRouter.client';
|
|
2
|
+
import { useBasePath } from '../useRouteParams/RouteParamsProvider.client';
|
|
2
3
|
/**
|
|
3
4
|
* The useNavigate hook imperatively navigates between routes.
|
|
4
5
|
*/
|
|
5
6
|
export function useNavigate() {
|
|
6
7
|
const router = useRouter();
|
|
8
|
+
const routeBasePath = useBasePath();
|
|
7
9
|
return (path, options = { replace: false, reloadDocument: false }) => {
|
|
10
|
+
path = buildPath(options.basePath ?? routeBasePath, path);
|
|
8
11
|
const state = {
|
|
9
12
|
...options?.clientState,
|
|
10
13
|
scroll: options?.scroll ?? true,
|
|
@@ -18,3 +21,13 @@ export function useNavigate() {
|
|
|
18
21
|
}
|
|
19
22
|
};
|
|
20
23
|
}
|
|
24
|
+
export function buildPath(basePath, path) {
|
|
25
|
+
let builtPath = path;
|
|
26
|
+
if (basePath !== '/') {
|
|
27
|
+
builtPath =
|
|
28
|
+
path.charAt(0) === '/' && basePath.charAt(0) === '/'
|
|
29
|
+
? basePath + path.substring(1)
|
|
30
|
+
: basePath + path;
|
|
31
|
+
}
|
|
32
|
+
return builtPath;
|
|
33
|
+
}
|
|
@@ -54,10 +54,7 @@ function cachedQueryFnBuilder(key, generateNewOutput, queryOptions) {
|
|
|
54
54
|
/**
|
|
55
55
|
* Attempt to read the query from cache. If it doesn't exist or if it's stale, regenerate it.
|
|
56
56
|
*/
|
|
57
|
-
async function useCachedQueryFn() {
|
|
58
|
-
// Call this hook before running any async stuff
|
|
59
|
-
// to prevent losing the current React cycle.
|
|
60
|
-
const request = useServerRequest();
|
|
57
|
+
async function useCachedQueryFn(request) {
|
|
61
58
|
const log = getLoggerWithContext(request);
|
|
62
59
|
const cacheResponse = await getItemFromCache(key);
|
|
63
60
|
if (cacheResponse) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React, { FC, ReactNode } from 'react';
|
|
2
2
|
declare type RouteParamsContextValue = {
|
|
3
3
|
routeParams: Record<string, string>;
|
|
4
|
+
basePath: string;
|
|
4
5
|
};
|
|
5
6
|
export declare const RouteParamsContext: React.Context<RouteParamsContextValue>;
|
|
6
7
|
export declare const RouteParamsProvider: FC<{
|
|
7
8
|
routeParams: Record<string, string>;
|
|
9
|
+
basePath: string;
|
|
8
10
|
children: ReactNode;
|
|
9
11
|
}>;
|
|
12
|
+
export declare function useBasePath(): string;
|
|
10
13
|
export {};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import React, { createContext } from 'react';
|
|
1
|
+
import React, { useContext, createContext } from 'react';
|
|
2
2
|
export const RouteParamsContext = createContext({
|
|
3
3
|
routeParams: {},
|
|
4
|
+
basePath: '/',
|
|
4
5
|
});
|
|
5
|
-
export const RouteParamsProvider = ({ children, routeParams }) => {
|
|
6
|
-
return (React.createElement(RouteParamsContext.Provider, { value: { routeParams } }, children));
|
|
6
|
+
export const RouteParamsProvider = ({ children, routeParams, basePath }) => {
|
|
7
|
+
return (React.createElement(RouteParamsContext.Provider, { value: { routeParams, basePath } }, children));
|
|
7
8
|
};
|
|
9
|
+
export function useBasePath() {
|
|
10
|
+
const router = useContext(RouteParamsContext);
|
|
11
|
+
return router.basePath;
|
|
12
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This is
|
|
3
|
-
*
|
|
2
|
+
* This is a limited implementation of an in-memory cache.
|
|
3
|
+
* It only supports the `cache-control` header.
|
|
4
|
+
* It does NOT support `age` or `expires` headers.
|
|
5
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Cache
|
|
4
6
|
*/
|
|
5
|
-
export declare class InMemoryCache {
|
|
6
|
-
private
|
|
7
|
+
export declare class InMemoryCache implements Cache {
|
|
8
|
+
#private;
|
|
7
9
|
constructor();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
add(request: RequestInfo): Promise<void>;
|
|
11
|
+
addAll(requests: RequestInfo[]): Promise<void>;
|
|
12
|
+
matchAll(request?: RequestInfo, options?: CacheQueryOptions): Promise<readonly Response[]>;
|
|
13
|
+
put(request: Request, response: Response): Promise<void>;
|
|
14
|
+
match(request: Request): Promise<Response | undefined>;
|
|
15
|
+
delete(request: Request): Promise<boolean>;
|
|
11
16
|
keys(request?: Request): Promise<Request[]>;
|
|
12
17
|
}
|
|
@@ -1,48 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This is
|
|
3
|
-
*
|
|
2
|
+
* This is a limited implementation of an in-memory cache.
|
|
3
|
+
* It only supports the `cache-control` header.
|
|
4
|
+
* It does NOT support `age` or `expires` headers.
|
|
5
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Cache
|
|
4
6
|
*/
|
|
5
7
|
export class InMemoryCache {
|
|
6
|
-
store;
|
|
8
|
+
#store;
|
|
7
9
|
constructor() {
|
|
8
|
-
this
|
|
10
|
+
this.#store = new Map();
|
|
9
11
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
add(request) {
|
|
13
|
+
throw new Error('Method not implemented. Use `put` instead.');
|
|
14
|
+
}
|
|
15
|
+
addAll(requests) {
|
|
16
|
+
throw new Error('Method not implemented. Use `put` instead.');
|
|
17
|
+
}
|
|
18
|
+
matchAll(request, options) {
|
|
19
|
+
throw new Error('Method not implemented. Use `match` instead.');
|
|
20
|
+
}
|
|
21
|
+
async put(request, response) {
|
|
22
|
+
if (request.method !== 'GET') {
|
|
23
|
+
throw new TypeError('Cannot cache response to non-GET request.');
|
|
24
|
+
}
|
|
25
|
+
if (response.status === 206) {
|
|
26
|
+
throw new TypeError('Cannot cache response to a range request (206 Partial Content).');
|
|
27
|
+
}
|
|
28
|
+
if (response.headers.get('vary')?.includes('*')) {
|
|
29
|
+
throw new TypeError("Cannot cache response with 'Vary: *' header.");
|
|
30
|
+
}
|
|
31
|
+
this.#store.set(request.url, {
|
|
32
|
+
body: new Uint8Array(await response.arrayBuffer()),
|
|
33
|
+
status: response.status,
|
|
34
|
+
headers: [...response.headers],
|
|
35
|
+
timestamp: Date.now(),
|
|
14
36
|
});
|
|
15
37
|
}
|
|
16
|
-
match(request) {
|
|
17
|
-
|
|
38
|
+
async match(request) {
|
|
39
|
+
if (request.method !== 'GET')
|
|
40
|
+
return;
|
|
41
|
+
const match = this.#store.get(request.url);
|
|
18
42
|
if (!match) {
|
|
19
43
|
return;
|
|
20
44
|
}
|
|
21
|
-
const {
|
|
22
|
-
const
|
|
45
|
+
const { body, timestamp, ...metadata } = match;
|
|
46
|
+
const headers = new Headers(metadata.headers);
|
|
47
|
+
const cacheControl = headers.get('cache-control') || '';
|
|
23
48
|
const maxAge = parseInt(cacheControl.match(/max-age=(\d+)/)?.[1] || '0', 10);
|
|
24
49
|
const swr = parseInt(cacheControl.match(/stale-while-revalidate=(\d+)/)?.[1] || '0', 10);
|
|
25
|
-
const age = (
|
|
50
|
+
const age = (Date.now() - timestamp) / 1000;
|
|
26
51
|
const isMiss = age > maxAge + swr;
|
|
27
52
|
if (isMiss) {
|
|
28
|
-
this
|
|
53
|
+
this.#store.delete(request.url);
|
|
29
54
|
return;
|
|
30
55
|
}
|
|
31
56
|
const isStale = age > maxAge;
|
|
32
|
-
const headers = new Headers(value.headers);
|
|
33
57
|
headers.set('cache', isStale ? 'STALE' : 'HIT');
|
|
34
|
-
headers.set('date',
|
|
35
|
-
|
|
58
|
+
headers.set('date', new Date(timestamp).toUTCString());
|
|
59
|
+
return new Response(body, {
|
|
60
|
+
status: metadata.status ?? 200,
|
|
36
61
|
headers,
|
|
37
62
|
});
|
|
38
|
-
return response;
|
|
39
63
|
}
|
|
40
|
-
delete(request) {
|
|
41
|
-
this
|
|
64
|
+
async delete(request) {
|
|
65
|
+
if (this.#store.has(request.url)) {
|
|
66
|
+
this.#store.delete(request.url);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
42
70
|
}
|
|
43
71
|
keys(request) {
|
|
44
72
|
const cacheKeys = [];
|
|
45
|
-
for (const url of this
|
|
73
|
+
for (const url of this.#store.keys()) {
|
|
46
74
|
if (!request || request.url === url) {
|
|
47
75
|
cacheKeys.push(new Request(url));
|
|
48
76
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Provide Hydrogen config loader to external tools like the CLI
|
|
2
|
+
import { VIRTUAL_PROXY_HYDROGEN_CONFIG_ID } from './plugins/vite-plugin-hydrogen-virtual-files';
|
|
3
|
+
import { viteception } from './viteception';
|
|
4
|
+
export async function loadConfig(options = { root: process.cwd() }) {
|
|
5
|
+
const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], options);
|
|
6
|
+
return {
|
|
7
|
+
configuration: loaded[0].default,
|
|
8
|
+
configurationPath: loaded[0].configPath,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -13,9 +13,8 @@ export default () => {
|
|
|
13
13
|
server.middlewares.use(async (req, res, next) => {
|
|
14
14
|
const url = req.url;
|
|
15
15
|
try {
|
|
16
|
-
if (url
|
|
17
|
-
url
|
|
18
|
-
!url.includes('node_modules')) {
|
|
16
|
+
if (/\.client\.[jt]sx?\?v=/.test(url) &&
|
|
17
|
+
!/\/node_modules\//.test(url)) {
|
|
19
18
|
const result = await server.transformRequest(url, { html: false });
|
|
20
19
|
if (result) {
|
|
21
20
|
return send(req, res, result.code, 'js', {
|