@shopify/hydrogen 0.18.0 → 0.21.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 +200 -0
- package/config.d.ts +1 -0
- package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.d.ts +1 -1
- package/dist/esnext/components/CartLineImage/CartLineImage.client.d.ts +4 -7
- package/dist/esnext/components/CartLineImage/CartLineImage.client.js +1 -2
- package/dist/esnext/components/CartLinePrice/CartLinePrice.client.d.ts +1 -1
- package/dist/esnext/components/CartProvider/CartProvider.client.d.ts +3 -1
- package/dist/esnext/components/CartProvider/CartProvider.client.js +22 -20
- package/dist/esnext/components/CartProvider/cart-queries.d.ts +10 -9
- package/dist/esnext/components/CartProvider/cart-queries.js +58 -743
- package/dist/esnext/components/CartProvider/hooks.client.js +4 -2
- package/dist/esnext/components/CartProvider/types.d.ts +2 -0
- package/dist/esnext/components/Image/Image.d.ts +78 -34
- package/dist/esnext/components/Image/Image.js +54 -51
- package/dist/esnext/components/Image/index.d.ts +1 -0
- package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +2 -15
- package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +0 -1
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +2 -6
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +10 -4
- package/dist/esnext/components/MediaFile/MediaFile.d.ts +2 -2
- package/dist/esnext/components/MediaFile/MediaFile.js +2 -2
- package/dist/esnext/components/Money/Money.client.d.ts +11 -5
- package/dist/esnext/components/Money/Money.client.js +16 -3
- package/dist/esnext/components/ProductPrice/ProductPrice.client.d.ts +1 -2
- package/dist/esnext/components/ProductPrice/ProductPrice.client.js +1 -2
- package/dist/esnext/components/Video/Video.d.ts +3 -3
- package/dist/esnext/components/Video/Video.js +7 -4
- package/dist/esnext/components/index.d.ts +0 -3
- package/dist/esnext/components/index.js +0 -3
- package/dist/esnext/entry-server.d.ts +13 -1
- package/dist/esnext/entry-server.js +18 -51
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +18 -3
- package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.js +2 -0
- package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +1 -1
- package/dist/esnext/foundation/fetchSync/server/fetchSync.js +1 -1
- package/dist/esnext/foundation/useQuery/hooks.js +8 -9
- package/dist/esnext/foundation/useSession/useSession.d.ts +1 -1
- package/dist/esnext/foundation/useSession/useSession.js +1 -1
- package/dist/esnext/framework/Hydration/Html.js +3 -1
- package/dist/esnext/framework/Hydration/ServerComponentResponse.server.d.ts +1 -1
- package/dist/esnext/framework/Hydration/ServerComponentResponse.server.js +2 -1
- package/dist/esnext/framework/Hydration/rsc.d.ts +0 -3
- package/dist/esnext/framework/Hydration/rsc.js +40 -12
- package/dist/esnext/framework/cache/in-memory.js +0 -6
- package/dist/esnext/framework/cache-sub-request.d.ts +17 -0
- package/dist/esnext/framework/cache-sub-request.js +64 -0
- package/dist/esnext/framework/cache.d.ts +6 -6
- package/dist/esnext/framework/cache.js +36 -33
- package/dist/esnext/framework/plugin.js +5 -30
- package/dist/esnext/framework/plugins/vite-plugin-client-imports.d.ts +2 -0
- package/dist/esnext/framework/plugins/vite-plugin-client-imports.js +25 -0
- package/dist/esnext/framework/plugins/vite-plugin-css-modules-rsc.d.ts +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-css-modules-rsc.js +73 -3
- package/dist/esnext/framework/plugins/vite-plugin-hydration-auto-import.js +1 -4
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +6 -4
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +2 -3
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +1 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.js +35 -0
- package/dist/esnext/framework/plugins/vite-plugin-platform-entry.js +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-ssr-interop.js +6 -3
- package/dist/esnext/hooks/useCountry/useCountry.d.ts +1 -11
- package/dist/esnext/hooks/useCountry/useCountry.js +1 -1
- package/dist/esnext/index.d.ts +4 -0
- package/dist/esnext/index.js +4 -0
- package/dist/esnext/node.d.ts +1 -0
- package/dist/esnext/node.js +1 -0
- package/dist/esnext/storefront-api-types.d.ts +5 -3
- package/dist/esnext/storefront-api-types.js +5 -3
- package/dist/esnext/types.d.ts +4 -3
- package/dist/esnext/utilities/bot-ua.js +4 -0
- package/dist/esnext/utilities/html-encoding.d.ts +2 -0
- package/dist/esnext/utilities/html-encoding.js +16 -0
- package/dist/esnext/utilities/image_size.d.ts +4 -22
- package/dist/esnext/utilities/image_size.js +15 -33
- package/dist/esnext/utilities/index.d.ts +2 -1
- package/dist/esnext/utilities/index.js +2 -1
- package/dist/esnext/utilities/log/log-cache-api-status.js +5 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/components/Image/Image.d.ts +84 -0
- package/dist/node/components/Image/Image.js +86 -0
- package/dist/node/components/Image/index.d.ts +2 -0
- package/dist/node/components/Image/index.js +5 -0
- package/dist/node/entry-server.d.ts +13 -1
- package/dist/node/entry-server.js +18 -51
- package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +18 -3
- package/dist/node/framework/Hydration/Html.js +3 -1
- package/dist/node/framework/Hydration/ServerComponentResponse.server.d.ts +1 -1
- package/dist/node/framework/Hydration/ServerComponentResponse.server.js +2 -1
- package/dist/node/framework/Hydration/rsc.d.ts +0 -3
- package/dist/node/framework/Hydration/rsc.js +40 -12
- package/dist/node/framework/cache/in-memory.js +0 -6
- package/dist/node/framework/cache-sub-request.d.ts +17 -0
- package/dist/node/framework/cache-sub-request.js +95 -0
- package/dist/node/framework/cache.d.ts +6 -6
- package/dist/node/framework/cache.js +38 -35
- package/dist/node/framework/plugin.js +5 -53
- package/dist/node/framework/plugins/vite-plugin-client-imports.d.ts +2 -0
- package/dist/node/framework/plugins/vite-plugin-client-imports.js +28 -0
- package/dist/node/framework/plugins/vite-plugin-css-modules-rsc.d.ts +1 -1
- package/dist/node/framework/plugins/vite-plugin-css-modules-rsc.js +76 -3
- package/dist/node/framework/plugins/vite-plugin-hydration-auto-import.js +1 -4
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +6 -4
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +1 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +2 -3
- package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.d.ts +1 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.js +41 -0
- package/dist/node/framework/plugins/vite-plugin-platform-entry.js +1 -1
- package/dist/node/framework/plugins/vite-plugin-ssr-interop.js +6 -3
- package/dist/node/storefront-api-types.d.ts +5 -3
- package/dist/node/storefront-api-types.js +5 -3
- package/dist/node/types.d.ts +4 -3
- package/dist/node/utilities/bot-ua.js +4 -0
- package/dist/node/utilities/html-encoding.d.ts +2 -0
- package/dist/node/utilities/html-encoding.js +21 -0
- package/dist/node/utilities/image_size.d.ts +4 -22
- package/dist/node/utilities/image_size.js +16 -58
- package/dist/node/utilities/index.d.ts +2 -1
- package/dist/node/utilities/index.js +4 -2
- package/dist/node/utilities/log/log-cache-api-status.js +5 -1
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/entry-server.d.ts +1 -1
- package/package.json +3 -3
- package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +200 -31
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +2 -0
- package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +200 -31
- package/vendor/react-server-dom-vite/package.json +2 -1
- package/dist/esnext/components/ProductDescription/ProductDescription.client.d.ts +0 -13
- package/dist/esnext/components/ProductDescription/ProductDescription.client.js +0 -16
- package/dist/esnext/components/ProductDescription/index.d.ts +0 -1
- package/dist/esnext/components/ProductDescription/index.js +0 -1
- package/dist/esnext/components/ProductMetafield/ProductMetafield.client.d.ts +0 -21
- package/dist/esnext/components/ProductMetafield/ProductMetafield.client.js +0 -42
- package/dist/esnext/components/ProductMetafield/index.d.ts +0 -2
- package/dist/esnext/components/ProductMetafield/index.js +0 -1
- package/dist/esnext/components/ProductTitle/ProductTitle.client.d.ts +0 -13
- package/dist/esnext/components/ProductTitle/ProductTitle.client.js +0 -16
- package/dist/esnext/components/ProductTitle/index.d.ts +0 -1
- package/dist/esnext/components/ProductTitle/index.js +0 -1
- package/dist/esnext/components/UnitPrice/UnitPrice.client.d.ts +0 -15
- package/dist/esnext/components/UnitPrice/UnitPrice.client.js +0 -22
- package/dist/esnext/components/UnitPrice/index.d.ts +0 -1
- package/dist/esnext/components/UnitPrice/index.js +0 -1
|
@@ -18,6 +18,7 @@ import { Analytics } from './foundation/Analytics/Analytics.server';
|
|
|
18
18
|
import { ServerAnalyticsRoute } from './foundation/Analytics/ServerAnalyticsRoute.server';
|
|
19
19
|
import { getSyncSessionApi } from './foundation/session/session';
|
|
20
20
|
import { parseJSON } from './utilities/parse';
|
|
21
|
+
import { htmlEncode } from './utilities';
|
|
21
22
|
const DOCTYPE = '<!DOCTYPE html>';
|
|
22
23
|
const CONTENT_TYPE = 'Content-Type';
|
|
23
24
|
const HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
|
|
@@ -63,7 +64,10 @@ export const renderHydrogen = (App, hydrogenConfig) => {
|
|
|
63
64
|
: apiResponse;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
const isStreamable =
|
|
67
|
+
const isStreamable = (hydrogenConfig.enableStreaming
|
|
68
|
+
? hydrogenConfig.enableStreaming(request)
|
|
69
|
+
: true) &&
|
|
70
|
+
!isBotUA(url, request.headers.get('user-agent')) &&
|
|
67
71
|
(!!streamableResponse || (await isStreamingSupported()));
|
|
68
72
|
let template = typeof indexTemplate === 'function'
|
|
69
73
|
? await indexTemplate(url.toString())
|
|
@@ -142,11 +146,7 @@ async function render(url, { App, request, template, componentResponse, nonce, l
|
|
|
142
146
|
headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE);
|
|
143
147
|
html = applyHtmlHead(html, request.ctx.head, template);
|
|
144
148
|
if (flight) {
|
|
145
|
-
html = html.replace('</body>', () =>
|
|
146
|
-
init: true,
|
|
147
|
-
nonce,
|
|
148
|
-
chunk: flight,
|
|
149
|
-
})}</body>`);
|
|
149
|
+
html = html.replace('</body>', () => flightContainer(flight) + '</body>');
|
|
150
150
|
}
|
|
151
151
|
postRequestTasks('ssr', status, request, componentResponse);
|
|
152
152
|
return new Response(html, {
|
|
@@ -174,12 +174,10 @@ async function stream(url, { App, request, response, componentResponse, template
|
|
|
174
174
|
const rscToScriptTagReadable = new ReadableStream({
|
|
175
175
|
start(controller) {
|
|
176
176
|
log.trace('rsc start chunks');
|
|
177
|
-
let init = true;
|
|
178
177
|
const encoder = new TextEncoder();
|
|
179
178
|
bufferReadableStream(rscReadable.getReader(), (chunk) => {
|
|
180
|
-
const
|
|
181
|
-
controller.enqueue(encoder.encode(
|
|
182
|
-
init = false;
|
|
179
|
+
const metaTag = flightContainer(chunk);
|
|
180
|
+
controller.enqueue(encoder.encode(metaTag));
|
|
183
181
|
}).then(() => {
|
|
184
182
|
log.trace('rsc finish chunks');
|
|
185
183
|
return controller.close();
|
|
@@ -370,34 +368,15 @@ async function hydrate(url, { App, log, request, response, isStreamable, compone
|
|
|
370
368
|
request,
|
|
371
369
|
response: componentResponse,
|
|
372
370
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
380
|
-
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
381
|
-
postRequestTasks('rsc', 200, request, componentResponse);
|
|
382
|
-
return new Response(bufferedBody, {
|
|
383
|
-
headers: {
|
|
384
|
-
'cache-control': componentResponse.cacheControlHeader,
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
else if (response) {
|
|
389
|
-
const rscWriter = await import(
|
|
390
|
-
// @ts-ignore
|
|
391
|
-
'@shopify/hydrogen/vendor/react-server-dom-vite/writer.node.server');
|
|
392
|
-
const streamer = rscWriter.renderToPipeableStream(AppRSC);
|
|
393
|
-
response.writeHead(200, 'ok', {
|
|
371
|
+
const rscReadable = rscRenderToReadableStream(AppRSC);
|
|
372
|
+
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
373
|
+
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
374
|
+
postRequestTasks('rsc', 200, request, componentResponse);
|
|
375
|
+
return new Response(bufferedBody, {
|
|
376
|
+
headers: {
|
|
394
377
|
'cache-control': componentResponse.cacheControlHeader,
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
stream.on('finish', function () {
|
|
398
|
-
postRequestTasks('rsc', response.statusCode, request, componentResponse);
|
|
399
|
-
});
|
|
400
|
-
}
|
|
378
|
+
},
|
|
379
|
+
});
|
|
401
380
|
}
|
|
402
381
|
function buildAppRSC({ App, log, state, request, response }) {
|
|
403
382
|
const hydrogenServerProps = { request, response, log };
|
|
@@ -528,20 +507,8 @@ async function createNodeWriter() {
|
|
|
528
507
|
const { PassThrough } = await import(streamImport);
|
|
529
508
|
return new PassThrough();
|
|
530
509
|
}
|
|
531
|
-
function flightContainer(
|
|
532
|
-
|
|
533
|
-
if (init) {
|
|
534
|
-
script += 'var __flight=[];';
|
|
535
|
-
}
|
|
536
|
-
if (chunk) {
|
|
537
|
-
const normalizedChunk = chunk
|
|
538
|
-
// 1. Duplicate the escape char (\) for already escaped characters (e.g. \n or \").
|
|
539
|
-
.replace(/\\/g, String.raw `\\`)
|
|
540
|
-
// 2. Escape existing backticks to allow wrapping the whole thing in `...`.
|
|
541
|
-
.replace(/`/g, String.raw `\``);
|
|
542
|
-
script += `__flight.push(\`${normalizedChunk}\`)`;
|
|
543
|
-
}
|
|
544
|
-
return script + '</script>';
|
|
510
|
+
function flightContainer(chunk) {
|
|
511
|
+
return `<meta data-flight="${htmlEncode(chunk)}" />`;
|
|
545
512
|
}
|
|
546
513
|
function postRequestTasks(type, status, request, componentResponse) {
|
|
547
514
|
logServerResponse(type, request, status);
|
|
@@ -9,12 +9,27 @@ function requestCacheRSC() {
|
|
|
9
9
|
return new Map();
|
|
10
10
|
}
|
|
11
11
|
requestCacheRSC.key = Symbol.for('HYDROGEN_REQUEST');
|
|
12
|
+
// Note: use this only during RSC/Flight rendering. The React dispatcher
|
|
13
|
+
// for SSR/Fizz rendering does not implement getCacheForType.
|
|
14
|
+
function getCacheForType(resource) {
|
|
15
|
+
var _a;
|
|
16
|
+
const dispatcher =
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
|
19
|
+
.ReactCurrentDispatcher.current;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
if (__DEV__ && typeof jest !== 'undefined' && !dispatcher.getCacheForType) {
|
|
22
|
+
// Jest does not have access to the RSC runtime, mock it here:
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
return ((_a = globalThis.__jestRscCache) !== null && _a !== void 0 ? _a : (globalThis.__jestRscCache = resource()));
|
|
25
|
+
}
|
|
26
|
+
return dispatcher.getCacheForType(resource);
|
|
27
|
+
}
|
|
12
28
|
export function ServerRequestProvider({ isRSC, request, children, }) {
|
|
13
29
|
if (isRSC) {
|
|
14
30
|
// Save the request object in a React cache that is
|
|
15
31
|
// scoped to this current rendering.
|
|
16
|
-
|
|
17
|
-
const requestCache = React.unstable_getCacheForType(requestCacheRSC);
|
|
32
|
+
const requestCache = getCacheForType(requestCacheRSC);
|
|
18
33
|
requestCache.set(requestCacheRSC.key, request);
|
|
19
34
|
return children;
|
|
20
35
|
}
|
|
@@ -27,7 +42,7 @@ export function useServerRequest() {
|
|
|
27
42
|
try {
|
|
28
43
|
// This cache only works during RSC rendering:
|
|
29
44
|
// @ts-ignore
|
|
30
|
-
const cache =
|
|
45
|
+
const cache = getCacheForType(requestCacheRSC);
|
|
31
46
|
request = cache ? cache.get(requestCacheRSC.key) : null;
|
|
32
47
|
}
|
|
33
48
|
catch (_a) {
|
|
@@ -31,6 +31,8 @@ export function ServerStateProvider({ serverState, setServerState, children, })
|
|
|
31
31
|
else {
|
|
32
32
|
newValue = input;
|
|
33
33
|
}
|
|
34
|
+
if (!newValue)
|
|
35
|
+
return { ...prev };
|
|
34
36
|
if (__DEV__) {
|
|
35
37
|
const privateProp = PRIVATE_PROPS.find((prop) => prop in newValue);
|
|
36
38
|
if (privateProp) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type HydrogenUseQueryOptions } from '../../useQuery/hooks';
|
|
2
2
|
import type { FetchResponse } from '../types';
|
|
3
3
|
/**
|
|
4
|
-
* The `fetchSync` hook makes
|
|
4
|
+
* The `fetchSync` hook makes API requests and is the recommended way to make simple fetch calls on the server and the client.
|
|
5
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
6
|
* that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
7
7
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseJSON } from '../../../utilities/parse';
|
|
2
2
|
import { useQuery } from '../../useQuery/hooks';
|
|
3
3
|
/**
|
|
4
|
-
* The `fetchSync` hook makes
|
|
4
|
+
* The `fetchSync` hook makes API requests and is the recommended way to make simple fetch calls on the server and the client.
|
|
5
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
6
|
* that supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html).
|
|
7
7
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { getLoggerWithContext, collectQueryCacheControlHeaders, collectQueryTimings,
|
|
2
|
-
import { deleteItemFromCache, generateSubRequestCacheControlHeader, getItemFromCache, isStale, setItemInCache, } from '../../framework/cache';
|
|
3
|
-
import { hashKey } from '../../utilities/hash';
|
|
1
|
+
import { getLoggerWithContext, collectQueryCacheControlHeaders, collectQueryTimings, } from '../../utilities/log';
|
|
2
|
+
import { deleteItemFromCache, generateSubRequestCacheControlHeader, getItemFromCache, isStale, setItemInCache, } from '../../framework/cache-sub-request';
|
|
4
3
|
import { runDelayedFunction } from '../../framework/runtime';
|
|
5
4
|
import { useRequestCacheData, useServerRequest } from '../ServerRequestProvider';
|
|
5
|
+
import { CacheSeconds } from '../../framework/CachingStrategy';
|
|
6
6
|
/**
|
|
7
7
|
* The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
|
|
8
8
|
* supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). You can use this
|
|
@@ -48,7 +48,6 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
48
48
|
// to prevent losing the current React cycle.
|
|
49
49
|
const request = useServerRequest();
|
|
50
50
|
const log = getLoggerWithContext(request);
|
|
51
|
-
const hashedKey = hashKey(key);
|
|
52
51
|
const cacheResponse = await getItemFromCache(key);
|
|
53
52
|
async function generateNewOutput() {
|
|
54
53
|
return await queryFn();
|
|
@@ -59,15 +58,15 @@ function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
|
59
58
|
/**
|
|
60
59
|
* Important: Do this async
|
|
61
60
|
*/
|
|
62
|
-
if (isStale(
|
|
63
|
-
|
|
64
|
-
const lockKey = `lock-${key}`;
|
|
61
|
+
if (isStale(key, response)) {
|
|
62
|
+
const lockKey = ['lock', ...(typeof key === 'string' ? [key] : key)];
|
|
65
63
|
runDelayedFunction(async () => {
|
|
66
|
-
logCacheApiStatus('UPDATING', hashedKey);
|
|
67
64
|
const lockExists = await getItemFromCache(lockKey);
|
|
68
65
|
if (lockExists)
|
|
69
66
|
return;
|
|
70
|
-
await setItemInCache(lockKey, true
|
|
67
|
+
await setItemInCache(lockKey, true, CacheSeconds({
|
|
68
|
+
maxAge: 10,
|
|
69
|
+
}));
|
|
71
70
|
try {
|
|
72
71
|
const output = await generateNewOutput();
|
|
73
72
|
if (shouldCacheResponse(output)) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The `useSession` hook reads session data in server components. */
|
|
2
|
-
export declare const useSession: () => Record<string, string
|
|
2
|
+
export declare const useSession: () => Record<string, string>;
|
|
@@ -3,6 +3,6 @@ import { useServerRequest } from '../ServerRequestProvider';
|
|
|
3
3
|
export const useSession = function () {
|
|
4
4
|
var _a;
|
|
5
5
|
const request = useServerRequest();
|
|
6
|
-
const session = (_a = request.ctx.session) === null || _a === void 0 ? void 0 : _a.get();
|
|
6
|
+
const session = ((_a = request.ctx.session) === null || _a === void 0 ? void 0 : _a.get()) || {};
|
|
7
7
|
return session;
|
|
8
8
|
};
|
|
@@ -27,7 +27,9 @@ export function Html({ children, template, htmlAttrs, bodyAttrs }) {
|
|
|
27
27
|
if (import.meta.env.DEV) {
|
|
28
28
|
// Fix React Refresh for async scripts.
|
|
29
29
|
// https://github.com/vitejs/vite/issues/6759
|
|
30
|
-
head =
|
|
30
|
+
head =
|
|
31
|
+
'<script></script>' + // Fix for Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1737882
|
|
32
|
+
head.replace(/>(\s*?import[\s\w]+?['"]\/@react-refresh)/, ' async="">$1');
|
|
31
33
|
}
|
|
32
34
|
return (React.createElement("html", { ...attrsToProps(getHtmlAttrs(template)), ...htmlAttrs },
|
|
33
35
|
React.createElement("head", { dangerouslySetInnerHTML: { __html: head } }),
|
|
@@ -6,6 +6,7 @@ export class ServerComponentResponse extends Response {
|
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments);
|
|
8
8
|
this.wait = false;
|
|
9
|
+
this.cacheOptions = CacheSeconds();
|
|
9
10
|
/**
|
|
10
11
|
* Allow custom body to be a string or a Promise.
|
|
11
12
|
*/
|
|
@@ -25,7 +26,7 @@ export class ServerComponentResponse extends Response {
|
|
|
25
26
|
this.cacheOptions = options;
|
|
26
27
|
}
|
|
27
28
|
get cacheControlHeader() {
|
|
28
|
-
return generateCacheControlHeader(this.cacheOptions
|
|
29
|
+
return generateCacheControlHeader(this.cacheOptions);
|
|
29
30
|
}
|
|
30
31
|
writeHead({ status, statusText, headers, } = {}) {
|
|
31
32
|
if (status || statusText) {
|
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
// TODO should we move this file to src/foundation
|
|
2
2
|
// so it is considered ESM instead of CJS?
|
|
3
|
-
// @ts-ignore
|
|
4
|
-
import { unstable_getCacheForType, unstable_useCacheRefresh } from 'react';
|
|
5
3
|
import { createFromFetch, createFromReadableStream,
|
|
6
4
|
// @ts-ignore
|
|
7
5
|
} from '@shopify/hydrogen/vendor/react-server-dom-vite';
|
|
8
6
|
import { RSC_PATHNAME } from '../../constants';
|
|
7
|
+
import { htmlDecode } from '../../utilities';
|
|
9
8
|
let rscReader;
|
|
10
|
-
|
|
9
|
+
// Hydrate an SSR response from <meta> tags placed in the DOM.
|
|
10
|
+
const flightChunks = [];
|
|
11
|
+
const FLIGHT_ATTRIBUTE = 'data-flight';
|
|
12
|
+
function addElementToFlightChunks(el) {
|
|
13
|
+
const chunk = el.getAttribute(FLIGHT_ATTRIBUTE);
|
|
14
|
+
if (chunk) {
|
|
15
|
+
flightChunks.push(htmlDecode(chunk));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Get initial payload
|
|
19
|
+
document
|
|
20
|
+
.querySelectorAll('[' + FLIGHT_ATTRIBUTE + ']')
|
|
21
|
+
.forEach(addElementToFlightChunks);
|
|
22
|
+
// Create a mutation observer on the document to detect when new
|
|
23
|
+
// <meta data-flight> tags are added, and add them to the array.
|
|
24
|
+
const observer = new MutationObserver((mutations) => {
|
|
25
|
+
mutations.forEach((mutation) => {
|
|
26
|
+
mutation.addedNodes.forEach((node) => {
|
|
27
|
+
if (node instanceof HTMLElement &&
|
|
28
|
+
node.tagName === 'META' &&
|
|
29
|
+
node.hasAttribute(FLIGHT_ATTRIBUTE)) {
|
|
30
|
+
addElementToFlightChunks(node);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
observer.observe(document.documentElement, {
|
|
36
|
+
childList: true,
|
|
37
|
+
subtree: true,
|
|
38
|
+
});
|
|
39
|
+
if (flightChunks.length > 0) {
|
|
11
40
|
const contentLoaded = new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve));
|
|
12
41
|
try {
|
|
13
42
|
rscReader = new ReadableStream({
|
|
@@ -17,9 +46,12 @@ if (globalThis.__flight && __flight.length > 0) {
|
|
|
17
46
|
controller.enqueue(encoder.encode(chunk));
|
|
18
47
|
return 0;
|
|
19
48
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
contentLoaded.then(() =>
|
|
49
|
+
flightChunks.forEach(write);
|
|
50
|
+
flightChunks.push = write;
|
|
51
|
+
contentLoaded.then(() => {
|
|
52
|
+
controller.close();
|
|
53
|
+
observer.disconnect();
|
|
54
|
+
});
|
|
23
55
|
},
|
|
24
56
|
});
|
|
25
57
|
}
|
|
@@ -27,9 +59,7 @@ if (globalThis.__flight && __flight.length > 0) {
|
|
|
27
59
|
// Old browser, will try a new hydration request later
|
|
28
60
|
}
|
|
29
61
|
}
|
|
30
|
-
|
|
31
|
-
return new Map();
|
|
32
|
-
}
|
|
62
|
+
const cache = new Map();
|
|
33
63
|
/**
|
|
34
64
|
* Much of this is borrowed from React's demo implementation:
|
|
35
65
|
* @see https://github.com/reactjs/server-components-demo/blob/main/src/Cache.client.js
|
|
@@ -38,7 +68,6 @@ function createResponseCache() {
|
|
|
38
68
|
*/
|
|
39
69
|
export function useServerResponse(state) {
|
|
40
70
|
const key = JSON.stringify(state);
|
|
41
|
-
const cache = unstable_getCacheForType(createResponseCache);
|
|
42
71
|
let response = cache.get(key);
|
|
43
72
|
if (response) {
|
|
44
73
|
return response;
|
|
@@ -67,6 +96,5 @@ export function useServerResponse(state) {
|
|
|
67
96
|
return response;
|
|
68
97
|
}
|
|
69
98
|
export function useRefresh() {
|
|
70
|
-
|
|
71
|
-
refreshCache();
|
|
99
|
+
cache.clear();
|
|
72
100
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { logCacheApiStatus } from '../../utilities/log';
|
|
2
1
|
/**
|
|
3
2
|
* This is an in-memory implementation of `Cache` that *barely*
|
|
4
3
|
* works and is only meant to be used during development.
|
|
@@ -8,7 +7,6 @@ export class InMemoryCache {
|
|
|
8
7
|
this.store = new Map();
|
|
9
8
|
}
|
|
10
9
|
put(request, response) {
|
|
11
|
-
logCacheApiStatus('PUT-dev', request.url);
|
|
12
10
|
this.store.set(request.url, {
|
|
13
11
|
value: response,
|
|
14
12
|
date: new Date(),
|
|
@@ -18,7 +16,6 @@ export class InMemoryCache {
|
|
|
18
16
|
var _a, _b;
|
|
19
17
|
const match = this.store.get(request.url);
|
|
20
18
|
if (!match) {
|
|
21
|
-
logCacheApiStatus('MISS-dev', request.url);
|
|
22
19
|
return;
|
|
23
20
|
}
|
|
24
21
|
const { value, date } = match;
|
|
@@ -28,7 +25,6 @@ export class InMemoryCache {
|
|
|
28
25
|
const age = (new Date().valueOf() - date.valueOf()) / 1000;
|
|
29
26
|
const isMiss = age > maxAge + swr;
|
|
30
27
|
if (isMiss) {
|
|
31
|
-
logCacheApiStatus('MISS-dev', request.url);
|
|
32
28
|
this.store.delete(request.url);
|
|
33
29
|
return;
|
|
34
30
|
}
|
|
@@ -36,7 +32,6 @@ export class InMemoryCache {
|
|
|
36
32
|
const headers = new Headers(value.headers);
|
|
37
33
|
headers.set('cache', isStale ? 'STALE' : 'HIT');
|
|
38
34
|
headers.set('date', date.toUTCString());
|
|
39
|
-
logCacheApiStatus(`${headers.get('cache')}-dev`, request.url);
|
|
40
35
|
const response = new Response(value.body, {
|
|
41
36
|
headers,
|
|
42
37
|
});
|
|
@@ -44,7 +39,6 @@ export class InMemoryCache {
|
|
|
44
39
|
}
|
|
45
40
|
delete(request) {
|
|
46
41
|
this.store.delete(request.url);
|
|
47
|
-
logCacheApiStatus('DELETE-dev', request.url);
|
|
48
42
|
}
|
|
49
43
|
keys(request) {
|
|
50
44
|
const cacheKeys = [];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QueryKey, CachingStrategy } from '../types';
|
|
2
|
+
export declare function generateSubRequestCacheControlHeader(userCacheOptions?: CachingStrategy): string;
|
|
3
|
+
/**
|
|
4
|
+
* Get an item from the cache. If a match is found, returns a tuple
|
|
5
|
+
* containing the `JSON.parse` version of the response as well
|
|
6
|
+
* as the response itself so it can be checked for staleness.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getItemFromCache(key: QueryKey): Promise<undefined | [any, Response]>;
|
|
9
|
+
/**
|
|
10
|
+
* Put an item into the cache.
|
|
11
|
+
*/
|
|
12
|
+
export declare function setItemInCache(key: QueryKey, value: any, userCacheOptions?: CachingStrategy): Promise<void>;
|
|
13
|
+
export declare function deleteItemFromCache(key: QueryKey): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Manually check the response to see if it's stale.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isStale(key: QueryKey, response: Response): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getCache } from './runtime';
|
|
2
|
+
import { hashKey } from '../utilities/hash';
|
|
3
|
+
import * as CacheApi from './cache';
|
|
4
|
+
import { CacheSeconds } from './CachingStrategy';
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper Cache functions for sub queries
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Cache API is weird. We just need a full URL, so we make one up.
|
|
10
|
+
*/
|
|
11
|
+
function getKeyUrl(key) {
|
|
12
|
+
return `https://shopify.dev/?${key}`;
|
|
13
|
+
}
|
|
14
|
+
function getCacheOption(userCacheOptions) {
|
|
15
|
+
return userCacheOptions || CacheSeconds();
|
|
16
|
+
}
|
|
17
|
+
export function generateSubRequestCacheControlHeader(userCacheOptions) {
|
|
18
|
+
return CacheApi.generateDefaultCacheControlHeader(getCacheOption(userCacheOptions));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get an item from the cache. If a match is found, returns a tuple
|
|
22
|
+
* containing the `JSON.parse` version of the response as well
|
|
23
|
+
* as the response itself so it can be checked for staleness.
|
|
24
|
+
*/
|
|
25
|
+
export async function getItemFromCache(key) {
|
|
26
|
+
const cache = getCache();
|
|
27
|
+
if (!cache) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const url = getKeyUrl(hashKey(key));
|
|
31
|
+
const request = new Request(url);
|
|
32
|
+
const response = await CacheApi.getItemFromCache(request);
|
|
33
|
+
if (!response) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
return [await response.json(), response];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Put an item into the cache.
|
|
40
|
+
*/
|
|
41
|
+
export async function setItemInCache(key, value, userCacheOptions) {
|
|
42
|
+
const cache = getCache();
|
|
43
|
+
if (!cache) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const url = getKeyUrl(hashKey(key));
|
|
47
|
+
const request = new Request(url);
|
|
48
|
+
const response = new Response(JSON.stringify(value));
|
|
49
|
+
await CacheApi.setItemInCache(request, response, getCacheOption(userCacheOptions));
|
|
50
|
+
}
|
|
51
|
+
export async function deleteItemFromCache(key) {
|
|
52
|
+
const cache = getCache();
|
|
53
|
+
if (!cache)
|
|
54
|
+
return;
|
|
55
|
+
const url = getKeyUrl(hashKey(key));
|
|
56
|
+
const request = new Request(url);
|
|
57
|
+
await CacheApi.deleteItemFromCache(request);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Manually check the response to see if it's stale.
|
|
61
|
+
*/
|
|
62
|
+
export function isStale(key, response) {
|
|
63
|
+
return CacheApi.isStale(new Request(getKeyUrl(hashKey(key))), response);
|
|
64
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function
|
|
1
|
+
import type { CachingStrategy } from '../types';
|
|
2
|
+
export declare function generateDefaultCacheControlHeader(userCacheOptions?: CachingStrategy): string;
|
|
3
3
|
/**
|
|
4
4
|
* Get an item from the cache. If a match is found, returns a tuple
|
|
5
5
|
* containing the `JSON.parse` version of the response as well
|
|
6
6
|
* as the response itself so it can be checked for staleness.
|
|
7
7
|
*/
|
|
8
|
-
export declare function getItemFromCache(
|
|
8
|
+
export declare function getItemFromCache(request: Request): Promise<Response | undefined>;
|
|
9
9
|
/**
|
|
10
10
|
* Put an item into the cache.
|
|
11
11
|
*/
|
|
12
|
-
export declare function setItemInCache(
|
|
13
|
-
export declare function deleteItemFromCache(
|
|
12
|
+
export declare function setItemInCache(request: Request, response: Response, userCacheOptions: CachingStrategy): Promise<void>;
|
|
13
|
+
export declare function deleteItemFromCache(request: Request): Promise<void>;
|
|
14
14
|
/**
|
|
15
15
|
* Manually check the response to see if it's stale.
|
|
16
16
|
*/
|
|
17
|
-
export declare function isStale(
|
|
17
|
+
export declare function isStale(request: Request, response: Response): boolean;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getCache } from './runtime';
|
|
2
2
|
import { CacheSeconds, generateCacheControlHeader, } from '../framework/CachingStrategy';
|
|
3
|
-
import { hashKey } from '../utilities/hash';
|
|
4
3
|
import { logCacheApiStatus } from '../utilities/log';
|
|
5
4
|
function getCacheControlSetting(userCacheOptions, options) {
|
|
6
5
|
if (userCacheOptions && options) {
|
|
@@ -13,45 +12,35 @@ function getCacheControlSetting(userCacheOptions, options) {
|
|
|
13
12
|
return userCacheOptions || CacheSeconds();
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
export function
|
|
15
|
+
export function generateDefaultCacheControlHeader(userCacheOptions) {
|
|
17
16
|
return generateCacheControlHeader(getCacheControlSetting(userCacheOptions));
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Cache API is weird. We just need a full URL, so we make one up.
|
|
21
|
-
*/
|
|
22
|
-
function getKeyUrl(key) {
|
|
23
|
-
return `https://shopify.dev/?${key}`;
|
|
24
|
-
}
|
|
25
18
|
/**
|
|
26
19
|
* Get an item from the cache. If a match is found, returns a tuple
|
|
27
20
|
* containing the `JSON.parse` version of the response as well
|
|
28
21
|
* as the response itself so it can be checked for staleness.
|
|
29
22
|
*/
|
|
30
|
-
export async function getItemFromCache(
|
|
23
|
+
export async function getItemFromCache(request) {
|
|
31
24
|
const cache = getCache();
|
|
32
25
|
if (!cache) {
|
|
33
26
|
return;
|
|
34
27
|
}
|
|
35
|
-
const url = getKeyUrl(hashKey(key));
|
|
36
|
-
const request = new Request(url);
|
|
37
28
|
const response = await cache.match(request);
|
|
38
29
|
if (!response) {
|
|
39
|
-
logCacheApiStatus('MISS', url);
|
|
30
|
+
logCacheApiStatus('MISS', request.url);
|
|
40
31
|
return;
|
|
41
32
|
}
|
|
42
|
-
logCacheApiStatus('HIT', url);
|
|
43
|
-
return
|
|
33
|
+
logCacheApiStatus('HIT', request.url);
|
|
34
|
+
return response;
|
|
44
35
|
}
|
|
45
36
|
/**
|
|
46
37
|
* Put an item into the cache.
|
|
47
38
|
*/
|
|
48
|
-
export async function setItemInCache(
|
|
39
|
+
export async function setItemInCache(request, response, userCacheOptions) {
|
|
49
40
|
const cache = getCache();
|
|
50
41
|
if (!cache) {
|
|
51
42
|
return;
|
|
52
43
|
}
|
|
53
|
-
const url = getKeyUrl(hashKey(key));
|
|
54
|
-
const request = new Request(url);
|
|
55
44
|
/**
|
|
56
45
|
* We are manually managing staled request by adding this workaround.
|
|
57
46
|
* Why? cache control header support is dependent on hosting platform
|
|
@@ -91,34 +80,48 @@ export async function setItemInCache(key, value, userCacheOptions) {
|
|
|
91
80
|
* `isStale` function will use the above information to test for stale-ness of a cached response
|
|
92
81
|
*/
|
|
93
82
|
const cacheControl = getCacheControlSetting(userCacheOptions);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
83
|
+
// The padded cache-control to mimic stale-while-revalidate
|
|
84
|
+
request.headers.set('cache-control', generateDefaultCacheControlHeader(getCacheControlSetting(cacheControl, {
|
|
85
|
+
maxAge: (cacheControl.maxAge || 0) + (cacheControl.staleWhileRevalidate || 0),
|
|
86
|
+
})));
|
|
87
|
+
// The cache-control we want to set on response
|
|
88
|
+
const cacheControlString = generateDefaultCacheControlHeader(getCacheControlSetting(cacheControl));
|
|
89
|
+
// CF will override cache-control, so we need to keep a
|
|
90
|
+
// non-modified real-cache-control
|
|
91
|
+
response.headers.set('cache-control', cacheControlString);
|
|
92
|
+
response.headers.set('real-cache-control', cacheControlString);
|
|
93
|
+
response.headers.set('cache-put-date', new Date().toUTCString());
|
|
94
|
+
logCacheApiStatus('PUT', request.url);
|
|
102
95
|
await cache.put(request, response);
|
|
103
96
|
}
|
|
104
|
-
export async function deleteItemFromCache(
|
|
97
|
+
export async function deleteItemFromCache(request) {
|
|
105
98
|
const cache = getCache();
|
|
106
99
|
if (!cache)
|
|
107
100
|
return;
|
|
108
|
-
|
|
109
|
-
const request = new Request(url);
|
|
110
|
-
logCacheApiStatus('DELETE', url);
|
|
101
|
+
logCacheApiStatus('DELETE', request.url);
|
|
111
102
|
await cache.delete(request);
|
|
112
103
|
}
|
|
113
104
|
/**
|
|
114
105
|
* Manually check the response to see if it's stale.
|
|
115
106
|
*/
|
|
116
|
-
export function isStale(
|
|
117
|
-
const responseMaxAge = getCacheControlSetting(userCacheOptions).maxAge || 0;
|
|
107
|
+
export function isStale(request, response) {
|
|
118
108
|
const responseDate = response.headers.get('cache-put-date');
|
|
119
|
-
|
|
109
|
+
const cacheControl = response.headers.get('real-cache-control');
|
|
110
|
+
let responseMaxAge = 0;
|
|
111
|
+
if (cacheControl) {
|
|
112
|
+
const maxAgeMatch = cacheControl.match(/max-age=(\d*)/);
|
|
113
|
+
if (maxAgeMatch && maxAgeMatch.length > 1) {
|
|
114
|
+
responseMaxAge = parseFloat(maxAgeMatch[1]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!responseDate) {
|
|
120
118
|
return false;
|
|
119
|
+
}
|
|
121
120
|
const ageInMs = new Date().valueOf() - new Date(responseDate).valueOf();
|
|
122
121
|
const age = ageInMs / 1000;
|
|
123
|
-
|
|
122
|
+
const result = age > responseMaxAge;
|
|
123
|
+
if (result) {
|
|
124
|
+
logCacheApiStatus('STALE', request.url);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
124
127
|
}
|