@shopify/hydrogen 0.11.0-experimental.1 → 0.11.1-experimental.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 +504 -0
- package/dist/esnext/client.d.ts +1 -1
- package/dist/esnext/client.js +1 -1
- package/dist/esnext/components/AddToCartButton/AddToCartButton.client.d.ts +1 -1
- package/dist/esnext/components/AddToCartButton/AddToCartButton.client.js +11 -2
- package/dist/esnext/components/CartEstimatedCost/CartEstimatedCost.client.js +1 -1
- package/dist/esnext/components/CartLineImage/CartLineImage.client.js +2 -2
- package/dist/esnext/components/CartLinePrice/CartLinePrice.client.d.ts +1 -1
- package/dist/esnext/components/CartLinePrice/CartLinePrice.client.js +1 -1
- package/dist/esnext/components/CartProvider/CartProvider.client.d.ts +2 -2
- package/dist/esnext/components/CartProvider/CartProvider.client.js +1 -1
- package/dist/esnext/components/ExternalVideo/ExternalVideo.d.ts +1 -1
- package/dist/esnext/components/ExternalVideo/ExternalVideo.js +2 -2
- package/dist/esnext/components/Image/Image.d.ts +1 -1
- package/dist/esnext/components/Image/Image.js +9 -9
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +13 -4
- package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +5 -3
- package/dist/esnext/components/MediaFile/MediaFile.d.ts +8 -6
- package/dist/esnext/components/MediaFile/MediaFile.js +6 -6
- package/dist/esnext/components/Metafield/Metafield.client.d.ts +1 -1
- package/dist/esnext/components/Metafield/Metafield.client.js +16 -16
- package/dist/esnext/components/ModelViewer/ModelViewer.client.d.ts +1 -1
- package/dist/esnext/components/ModelViewer/ModelViewer.client.js +1 -1
- package/dist/esnext/components/Money/Money.client.d.ts +1 -1
- package/dist/esnext/components/Money/Money.client.js +2 -2
- package/dist/esnext/components/ProductMetafield/ProductMetafield.client.d.ts +3 -1
- package/dist/esnext/components/ProductMetafield/ProductMetafield.client.js +21 -4
- package/dist/esnext/components/ProductPrice/ProductPrice.client.d.ts +5 -3
- package/dist/esnext/components/ProductPrice/ProductPrice.client.js +30 -9
- package/dist/esnext/components/ProductProvider/ProductOptionsProvider.client.d.ts +8 -0
- package/dist/esnext/components/ProductProvider/ProductOptionsProvider.client.js +12 -0
- package/dist/esnext/components/ProductProvider/ProductProvider.client.d.ts +8 -7
- package/dist/esnext/components/ProductProvider/ProductProvider.client.js +9 -35
- package/dist/esnext/components/ProductProvider/context.d.ts +10 -6
- package/dist/esnext/components/ProductProvider/context.js +1 -0
- package/dist/esnext/components/Seo/DefaultPageSeo.client.js +2 -2
- package/dist/esnext/components/Seo/DescriptionSeo.client.js +2 -2
- package/dist/esnext/components/Seo/HomePageSeo.client.js +2 -2
- package/dist/esnext/components/Seo/ImageSeo.client.js +2 -2
- package/dist/esnext/components/Seo/ProductSeo.client.js +5 -3
- package/dist/esnext/components/Seo/TitleSeo.client.js +2 -2
- package/dist/esnext/components/Seo/TwitterSeo.client.js +2 -2
- package/dist/esnext/components/Seo/types.d.ts +1 -1
- package/dist/esnext/components/UnitPrice/UnitPrice.client.d.ts +2 -2
- package/dist/esnext/components/UnitPrice/UnitPrice.client.js +3 -3
- package/dist/esnext/components/Video/Video.d.ts +1 -1
- package/dist/esnext/components/Video/Video.js +3 -3
- package/dist/esnext/components/index.d.ts +0 -8
- package/dist/esnext/components/index.js +0 -8
- package/dist/esnext/entry-client.js +1 -1
- package/dist/esnext/entry-server.d.ts +18 -2
- package/dist/esnext/entry-server.js +347 -329
- package/dist/esnext/foundation/Head/Head.client.d.ts +5 -0
- package/dist/esnext/foundation/Head/Head.client.js +10 -0
- package/dist/esnext/foundation/Head/index.d.ts +1 -0
- package/dist/esnext/foundation/Head/index.js +1 -0
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +2 -1
- package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +43 -4
- package/dist/esnext/foundation/useQuery/hooks.d.ts +11 -3
- package/dist/esnext/foundation/useQuery/hooks.js +21 -5
- package/dist/esnext/framework/CachingStrategy/index.d.ts +10 -0
- package/dist/esnext/framework/CachingStrategy/index.js +96 -0
- package/dist/esnext/framework/Hydration/Html.js +5 -12
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +20 -2
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +51 -2
- package/dist/esnext/framework/Hydration/ServerComponentResponse.server.d.ts +2 -2
- package/dist/esnext/framework/Hydration/ServerComponentResponse.server.js +2 -11
- package/dist/esnext/framework/cache/in-memory.js +6 -5
- package/dist/esnext/framework/cache.d.ts +3 -3
- package/dist/esnext/framework/cache.js +4 -18
- package/dist/esnext/framework/middleware.d.ts +7 -7
- package/dist/esnext/framework/middleware.js +15 -43
- package/dist/esnext/framework/plugin.js +14 -3
- package/dist/esnext/framework/plugins/vite-plugin-hydration-auto-import.d.ts +3 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydration-auto-import.js +38 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-client-middleware.d.ts +9 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-client-middleware.js +37 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +2 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +3 -1
- package/dist/esnext/framework/plugins/vite-plugin-platform-entry.d.ts +3 -0
- package/dist/esnext/framework/plugins/vite-plugin-platform-entry.js +70 -0
- package/dist/esnext/hooks/useProduct/useProduct.d.ts +150 -1
- package/dist/esnext/hooks/useProduct/useProduct.js +36 -3
- package/dist/esnext/hooks/useProductOptions/types.d.ts +1 -1
- package/dist/esnext/hooks/useProductOptions/useProductOptions.d.ts +1 -1
- package/dist/esnext/hooks/useProductOptions/useProductOptions.js +6 -6
- package/dist/esnext/hooks/useShopQuery/hooks.d.ts +11 -4
- package/dist/esnext/hooks/useShopQuery/hooks.js +6 -3
- package/dist/esnext/index.d.ts +2 -1
- package/dist/esnext/index.js +2 -1
- package/dist/esnext/platforms/node.d.ts +10 -0
- package/dist/esnext/platforms/node.js +38 -0
- package/dist/esnext/platforms/worker-event.d.ts +9 -0
- package/dist/esnext/platforms/worker-event.js +24 -0
- package/dist/esnext/streaming.server.d.ts +17 -7
- package/dist/esnext/streaming.server.js +44 -0
- package/dist/esnext/types.d.ts +25 -25
- package/dist/esnext/utilities/apiRoutes.d.ts +1 -2
- package/dist/esnext/utilities/apiRoutes.js +4 -3
- package/dist/esnext/utilities/bot-ua.d.ts +4 -0
- package/dist/esnext/utilities/bot-ua.js +54 -0
- package/dist/esnext/utilities/fetch.js +1 -26
- package/dist/esnext/utilities/log/index.d.ts +4 -1
- package/dist/esnext/utilities/log/index.js +4 -1
- package/dist/esnext/utilities/log/log-cache-api-status.d.ts +1 -0
- package/dist/esnext/utilities/log/log-cache-api-status.js +9 -0
- package/dist/esnext/utilities/log/log-cache-header.d.ts +10 -0
- package/dist/esnext/utilities/log/log-cache-header.js +30 -0
- package/dist/esnext/utilities/log/log-query-timeline.d.ts +12 -0
- package/dist/esnext/utilities/log/log-query-timeline.js +83 -0
- package/dist/esnext/utilities/log/log.d.ts +11 -8
- package/dist/esnext/utilities/log/log.js +24 -35
- package/dist/esnext/utilities/log/utils.d.ts +3 -0
- package/dist/esnext/utilities/log/utils.js +13 -0
- package/dist/esnext/utilities/template.d.ts +9 -0
- package/dist/esnext/utilities/template.js +23 -0
- package/dist/{worker/foundation/ShopifyProvider/types.js → esnext/utilities/web-api-polyfill.d.ts} +0 -0
- package/dist/esnext/utilities/web-api-polyfill.js +19 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/entry-server.d.ts +22 -0
- package/dist/node/entry-server.js +581 -0
- package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.d.ts +23 -0
- package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +132 -0
- package/dist/node/foundation/ServerRequestProvider/index.d.ts +1 -0
- package/dist/node/foundation/ServerRequestProvider/index.js +13 -0
- package/dist/node/foundation/ServerStateProvider/ServerStateProvider.client.d.ts +25 -0
- package/dist/node/foundation/ServerStateProvider/ServerStateProvider.client.js +68 -0
- package/dist/node/foundation/ServerStateProvider/index.d.ts +2 -0
- package/dist/node/foundation/ServerStateProvider/index.js +6 -0
- package/dist/node/framework/CachingStrategy/index.d.ts +10 -0
- package/dist/node/framework/CachingStrategy/index.js +108 -0
- package/dist/node/framework/Hydration/Html.js +5 -12
- package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +20 -2
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +50 -1
- package/dist/node/framework/Hydration/ServerComponentResponse.server.d.ts +2 -2
- package/dist/node/framework/Hydration/ServerComponentResponse.server.js +2 -11
- package/dist/node/framework/cache/in-memory.js +6 -5
- package/dist/node/framework/cache.d.ts +3 -3
- package/dist/node/framework/cache.js +6 -20
- package/dist/node/framework/middleware.d.ts +7 -7
- package/dist/node/framework/middleware.js +14 -46
- package/dist/node/framework/plugin.js +33 -3
- package/dist/node/framework/plugins/vite-plugin-hydration-auto-import.d.ts +3 -0
- package/dist/node/framework/plugins/vite-plugin-hydration-auto-import.js +43 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-client-middleware.d.ts +9 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-client-middleware.js +39 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +2 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +4 -1
- package/dist/node/framework/plugins/vite-plugin-platform-entry.d.ts +3 -0
- package/dist/node/framework/plugins/vite-plugin-platform-entry.js +75 -0
- package/dist/node/streaming.server.d.ts +21 -0
- package/dist/node/streaming.server.js +59 -0
- package/dist/node/types.d.ts +25 -25
- package/dist/node/utilities/apiRoutes.d.ts +1 -2
- package/dist/node/utilities/apiRoutes.js +4 -3
- package/dist/node/utilities/bot-ua.d.ts +4 -0
- package/dist/node/utilities/bot-ua.js +58 -0
- package/dist/node/utilities/defer.d.ts +6 -0
- package/dist/node/utilities/defer.js +18 -0
- package/dist/node/utilities/error.d.ts +1 -0
- package/dist/node/utilities/error.js +10 -0
- package/dist/node/utilities/log/index.d.ts +4 -0
- package/dist/node/utilities/log/index.js +18 -0
- package/dist/node/utilities/log/log-cache-api-status.d.ts +1 -0
- package/dist/node/utilities/log/log-cache-api-status.js +13 -0
- package/dist/node/utilities/log/log-cache-header.d.ts +10 -0
- package/dist/node/utilities/log/log-cache-header.js +35 -0
- package/dist/node/utilities/log/log-query-timeline.d.ts +12 -0
- package/dist/node/utilities/log/log-query-timeline.js +88 -0
- package/dist/node/utilities/log/log.d.ts +11 -8
- package/dist/node/utilities/log/log.js +27 -37
- package/dist/node/utilities/log/utils.d.ts +3 -0
- package/dist/node/utilities/log/utils.js +18 -0
- package/dist/node/utilities/template.d.ts +9 -0
- package/dist/node/utilities/template.js +27 -0
- package/dist/{worker/types.js → node/utilities/web-api-polyfill.d.ts} +0 -0
- package/dist/node/utilities/web-api-polyfill.js +43 -0
- package/package.json +20 -10
- package/web-polyfills.js +1 -0
- package/LICENSE.md +0 -7
- package/dist/esnext/components/CartLineAttributes/CartLineAttributes.client.d.ts +0 -22
- package/dist/esnext/components/CartLineAttributes/CartLineAttributes.client.js +0 -13
- package/dist/esnext/components/CartLineAttributes/index.d.ts +0 -1
- package/dist/esnext/components/CartLineAttributes/index.js +0 -1
- package/dist/esnext/components/CartLineSelectedOptions/CartLineSelectedOptions.client.d.ts +0 -21
- package/dist/esnext/components/CartLineSelectedOptions/CartLineSelectedOptions.client.js +0 -13
- package/dist/esnext/components/CartLineSelectedOptions/index.d.ts +0 -1
- package/dist/esnext/components/CartLineSelectedOptions/index.js +0 -1
- package/dist/esnext/components/SelectedVariantAddToCartButton/SelectedVariantAddToCartButton.client.d.ts +0 -11
- package/dist/esnext/components/SelectedVariantAddToCartButton/SelectedVariantAddToCartButton.client.js +0 -17
- package/dist/esnext/components/SelectedVariantAddToCartButton/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantAddToCartButton/index.js +0 -1
- package/dist/esnext/components/SelectedVariantBuyNowButton/SelectedVariantBuyNowButton.d.ts +0 -9
- package/dist/esnext/components/SelectedVariantBuyNowButton/SelectedVariantBuyNowButton.js +0 -17
- package/dist/esnext/components/SelectedVariantBuyNowButton/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantBuyNowButton/index.js +0 -1
- package/dist/esnext/components/SelectedVariantImage/SelectedVariantImage.client.d.ts +0 -10
- package/dist/esnext/components/SelectedVariantImage/SelectedVariantImage.client.js +0 -16
- package/dist/esnext/components/SelectedVariantImage/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantImage/index.js +0 -1
- package/dist/esnext/components/SelectedVariantMetafield/SelectedVariantMetafield.client.d.ts +0 -15
- package/dist/esnext/components/SelectedVariantMetafield/SelectedVariantMetafield.client.js +0 -24
- package/dist/esnext/components/SelectedVariantMetafield/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantMetafield/index.js +0 -1
- package/dist/esnext/components/SelectedVariantPrice/SelectedVariantPrice.client.d.ts +0 -12
- package/dist/esnext/components/SelectedVariantPrice/SelectedVariantPrice.client.js +0 -22
- package/dist/esnext/components/SelectedVariantPrice/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantPrice/index.js +0 -1
- package/dist/esnext/components/SelectedVariantShopPayButton/SelectedVariantShopPayButton.client.d.ts +0 -8
- package/dist/esnext/components/SelectedVariantShopPayButton/SelectedVariantShopPayButton.client.js +0 -18
- package/dist/esnext/components/SelectedVariantShopPayButton/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantShopPayButton/index.js +0 -1
- package/dist/esnext/components/SelectedVariantUnitPrice/SelectedVariantUnitPrice.client.d.ts +0 -7
- package/dist/esnext/components/SelectedVariantUnitPrice/SelectedVariantUnitPrice.client.js +0 -16
- package/dist/esnext/components/SelectedVariantUnitPrice/index.d.ts +0 -1
- package/dist/esnext/components/SelectedVariantUnitPrice/index.js +0 -1
- package/dist/esnext/foundation/Helmet/Helmet.client.d.ts +0 -10
- package/dist/esnext/foundation/Helmet/Helmet.client.js +0 -10
- package/dist/esnext/foundation/Helmet/index.d.ts +0 -1
- package/dist/esnext/foundation/Helmet/index.js +0 -1
- package/dist/esnext/handle-event.d.ts +0 -23
- package/dist/esnext/handle-event.js +0 -132
- package/dist/esnext/handle-worker-event.d.ts +0 -11
- package/dist/esnext/handle-worker-event.js +0 -14
- package/dist/node/foundation/Helmet/Helmet.client.d.ts +0 -10
- package/dist/node/foundation/Helmet/Helmet.client.js +0 -17
- package/dist/node/foundation/Helmet/index.d.ts +0 -1
- package/dist/node/foundation/Helmet/index.js +0 -5
- package/dist/node/foundation/ssr-interop.d.ts +0 -29
- package/dist/node/foundation/ssr-interop.js +0 -39
- package/dist/node/handle-event.d.ts +0 -23
- package/dist/node/handle-event.js +0 -135
- package/dist/worker/constants.d.ts +0 -1
- package/dist/worker/constants.js +0 -1
- package/dist/worker/foundation/Helmet/Helmet.client.d.ts +0 -10
- package/dist/worker/foundation/Helmet/Helmet.client.js +0 -10
- package/dist/worker/foundation/Helmet/index.d.ts +0 -1
- package/dist/worker/foundation/Helmet/index.js +0 -1
- package/dist/worker/foundation/ShopifyProvider/types.d.ts +0 -15
- package/dist/worker/foundation/ssr-interop.d.ts +0 -29
- package/dist/worker/foundation/ssr-interop.js +0 -35
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.d.ts +0 -23
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +0 -57
- package/dist/worker/framework/config.d.ts +0 -6
- package/dist/worker/framework/config.js +0 -6
- package/dist/worker/framework/runtime.d.ts +0 -13
- package/dist/worker/framework/runtime.js +0 -27
- package/dist/worker/graphql/types/types.d.ts +0 -5761
- package/dist/worker/graphql/types/types.js +0 -1436
- package/dist/worker/handle-event.d.ts +0 -23
- package/dist/worker/handle-event.js +0 -132
- package/dist/worker/handle-worker-event.d.ts +0 -11
- package/dist/worker/handle-worker-event.js +0 -14
- package/dist/worker/types.d.ts +0 -96
- package/dist/worker/utilities/apiRoutes.d.ts +0 -21
- package/dist/worker/utilities/apiRoutes.js +0 -87
- package/dist/worker/utilities/log/log.d.ts +0 -18
- package/dist/worker/utilities/log/log.js +0 -77
- package/dist/worker/utilities/matchPath.d.ts +0 -10
- package/dist/worker/utilities/matchPath.js +0 -54
- package/dist/worker/utilities/timing.d.ts +0 -7
- package/dist/worker/utilities/timing.js +0 -14
- package/worker.js +0 -1
|
@@ -1,330 +1,365 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { logServerResponse,
|
|
2
|
+
import { logServerResponse, logCacheControlHeaders, logQueryTimings, getLoggerWithContext, } from './utilities/log';
|
|
3
3
|
import { getErrorMarkup } from './utilities/error';
|
|
4
4
|
import { defer } from './utilities/defer';
|
|
5
5
|
import { Html } from './framework/Hydration/Html';
|
|
6
6
|
import { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
|
|
7
|
+
import { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
|
|
7
8
|
import { getCacheControlHeader } from './framework/cache';
|
|
8
|
-
import { ServerRequestProvider } from './foundation/ServerRequestProvider';
|
|
9
|
-
import { getApiRouteFromURL, getApiRoutesFromPages } from './utilities/apiRoutes';
|
|
9
|
+
import { preloadRequestCacheData, ServerRequestProvider, } from './foundation/ServerRequestProvider';
|
|
10
|
+
import { getApiRouteFromURL, renderApiRoute, getApiRoutesFromPages, } from './utilities/apiRoutes';
|
|
10
11
|
import { ServerStateProvider } from './foundation/ServerStateProvider';
|
|
11
|
-
import {
|
|
12
|
+
import { isBotUA } from './utilities/bot-ua';
|
|
13
|
+
import { setContext, setCache } from './framework/runtime';
|
|
14
|
+
import { setConfig } from './framework/config';
|
|
15
|
+
import { ssrRenderToPipeableStream, ssrRenderToReadableStream, rscRenderToReadableStream, createFromReadableStream, supportsReadableStream, isStreamingSupported, bufferReadableStream, } from './streaming.server';
|
|
12
16
|
import { RSC_PATHNAME } from './constants';
|
|
13
|
-
|
|
14
|
-
* If a query is taking too long, or something else went wrong,
|
|
15
|
-
* send back a response containing the Suspense fallback and rely
|
|
16
|
-
* on the client to hydrate and build the React tree.
|
|
17
|
-
*/
|
|
18
|
-
const STREAM_ABORT_TIMEOUT_MS = 3000;
|
|
17
|
+
import { stripScriptsFromTemplate } from './utilities/template';
|
|
19
18
|
const HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
|
|
20
|
-
const renderHydrogen = (App, { pages }) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
export const renderHydrogen = (App, { pages }) => {
|
|
20
|
+
const handleRequest = async function (rawRequest, { indexTemplate, streamableResponse, dev, cache, context, nonce }) {
|
|
21
|
+
const request = new ServerComponentRequest(rawRequest);
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
const log = getLoggerWithContext(request);
|
|
24
|
+
const componentResponse = new ServerComponentResponse();
|
|
25
|
+
/**
|
|
26
|
+
* Inject the cache & context into the module loader so we can pull it out for subrequests.
|
|
27
|
+
*/
|
|
28
|
+
setCache(cache);
|
|
29
|
+
setContext(context);
|
|
30
|
+
setConfig({ dev });
|
|
31
|
+
const isReactHydrationRequest = url.pathname === RSC_PATHNAME;
|
|
32
|
+
let template = typeof indexTemplate === 'function'
|
|
33
|
+
? await indexTemplate(url.toString())
|
|
34
|
+
: indexTemplate;
|
|
35
|
+
if (template && typeof template !== 'string') {
|
|
36
|
+
template = template.default;
|
|
37
|
+
}
|
|
38
|
+
if (!isReactHydrationRequest && pages) {
|
|
39
|
+
const apiRoute = getApiRoute(url, { pages });
|
|
40
|
+
// The API Route might have a default export, making it also a server component
|
|
41
|
+
// If it does, only render the API route if the request method is GET
|
|
42
|
+
if (apiRoute &&
|
|
43
|
+
(!apiRoute.hasServerComponent || request.method !== 'GET')) {
|
|
44
|
+
return renderApiRoute(request, apiRoute);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const isStreamable = !isBotUA(url, request.headers.get('user-agent')) &&
|
|
48
|
+
(!!streamableResponse || supportsReadableStream());
|
|
49
|
+
const params = {
|
|
29
50
|
App,
|
|
30
|
-
state,
|
|
31
|
-
request,
|
|
32
|
-
response: componentResponse,
|
|
33
|
-
pages,
|
|
34
51
|
log,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
dev,
|
|
53
|
+
pages,
|
|
54
|
+
nonce,
|
|
55
|
+
request,
|
|
56
|
+
template,
|
|
57
|
+
isStreamable,
|
|
58
|
+
componentResponse,
|
|
59
|
+
response: streamableResponse,
|
|
60
|
+
};
|
|
61
|
+
if (isReactHydrationRequest) {
|
|
62
|
+
return hydrate(url, params);
|
|
63
|
+
}
|
|
41
64
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
65
|
+
* Stream back real-user responses, but for bots/etc,
|
|
66
|
+
* use `render` instead. This is because we need to inject <head>
|
|
67
|
+
* things for SEO reasons.
|
|
44
68
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (componentResponse.customBody) {
|
|
48
|
-
// This can be used to return sitemap.xml or any other custom response.
|
|
49
|
-
logServerResponse('ssr', log, request, status);
|
|
50
|
-
return new Response(await componentResponse.customBody, {
|
|
51
|
-
status,
|
|
52
|
-
statusText,
|
|
53
|
-
headers,
|
|
54
|
-
});
|
|
69
|
+
if (isStreamable) {
|
|
70
|
+
return stream(url, params);
|
|
55
71
|
}
|
|
56
|
-
|
|
57
|
-
const { bodyAttributes, htmlAttributes, ...head } = extractHeadElements(request.ctx.helmet);
|
|
58
|
-
html = html
|
|
59
|
-
.replace(/<head>(.*?)<\/head>/s, generateHeadTag(head))
|
|
60
|
-
.replace('<html', htmlAttributes ? `<html ${htmlAttributes}` : '$&')
|
|
61
|
-
.replace('<body', bodyAttributes ? `<body ${bodyAttributes}` : '$&')
|
|
62
|
-
.replace('</body>', flight
|
|
63
|
-
? `${flightContainer({ init: true, nonce, chunk: flight })}</body>`
|
|
64
|
-
: '$&');
|
|
65
|
-
logServerResponse('ssr', log, request, status);
|
|
66
|
-
return new Response(html, {
|
|
67
|
-
status,
|
|
68
|
-
statusText,
|
|
69
|
-
headers,
|
|
70
|
-
});
|
|
72
|
+
return render(url, params);
|
|
71
73
|
};
|
|
74
|
+
return handleRequest;
|
|
75
|
+
};
|
|
76
|
+
function getApiRoute(url, { pages }) {
|
|
77
|
+
const routes = getApiRoutesFromPages(pages);
|
|
78
|
+
return getApiRouteFromURL(url, routes);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The render function is responsible for turning the provided `App` into an HTML string,
|
|
82
|
+
* and returning any initial state that needs to be hydrated into the client version of the app.
|
|
83
|
+
* NOTE: This is currently only used for SEO bots or Worker runtime (where Stream is not yet supported).
|
|
84
|
+
*/
|
|
85
|
+
async function render(url, { App, pages, request, componentResponse, log, template, nonce, dev, }) {
|
|
86
|
+
const state = { pathname: url.pathname, search: url.search };
|
|
87
|
+
const { AppSSR, rscReadable } = buildAppSSR({
|
|
88
|
+
App,
|
|
89
|
+
state,
|
|
90
|
+
request,
|
|
91
|
+
response: componentResponse,
|
|
92
|
+
pages,
|
|
93
|
+
log,
|
|
94
|
+
}, { template });
|
|
95
|
+
let [html, flight] = await Promise.all([
|
|
96
|
+
renderToBufferedString(AppSSR, { log, nonce }),
|
|
97
|
+
bufferReadableStream(rscReadable.getReader()),
|
|
98
|
+
]);
|
|
99
|
+
const { headers, status, statusText } = getResponseOptions(componentResponse);
|
|
72
100
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
101
|
+
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
102
|
+
* we want to shard our full-page cache for all Hydrogen storefronts.
|
|
75
103
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
log,
|
|
85
|
-
pages,
|
|
86
|
-
}, {
|
|
87
|
-
template,
|
|
88
|
-
htmlAttrs: { lang: 'en' },
|
|
89
|
-
});
|
|
90
|
-
const rscToScriptTagReadable = new ReadableStream({
|
|
91
|
-
start(controller) {
|
|
92
|
-
log.trace('rsc start chunks');
|
|
93
|
-
let init = true;
|
|
94
|
-
const encoder = new TextEncoder();
|
|
95
|
-
bufferReadableStream(rscReadable.getReader(), (chunk) => {
|
|
96
|
-
const scriptTag = flightContainer({ init, chunk, nonce });
|
|
97
|
-
controller.enqueue(encoder.encode(scriptTag));
|
|
98
|
-
init = false;
|
|
99
|
-
}).then(() => {
|
|
100
|
-
log.trace('rsc finish chunks');
|
|
101
|
-
return controller.close();
|
|
102
|
-
});
|
|
103
|
-
},
|
|
104
|
+
headers[getCacheControlHeader({ dev })] = componentResponse.cacheControlHeader;
|
|
105
|
+
if (componentResponse.customBody) {
|
|
106
|
+
// This can be used to return sitemap.xml or any other custom response.
|
|
107
|
+
postRequestTasks('ssr', status, request, componentResponse);
|
|
108
|
+
return new Response(await componentResponse.customBody, {
|
|
109
|
+
status,
|
|
110
|
+
statusText,
|
|
111
|
+
headers,
|
|
104
112
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
}
|
|
114
|
+
headers['Content-type'] = HTML_CONTENT_TYPE;
|
|
115
|
+
const { bodyAttributes, htmlAttributes, ...head } = extractHeadElements(request.ctx.head);
|
|
116
|
+
html = html
|
|
117
|
+
.replace(/<head>(.*?)<\/head>/s, generateHeadTag(head))
|
|
118
|
+
.replace('<html', htmlAttributes ? `<html ${htmlAttributes}` : '$&')
|
|
119
|
+
.replace('<body', bodyAttributes ? `<body ${bodyAttributes}` : '$&')
|
|
120
|
+
.replace('</body>', flight
|
|
121
|
+
? `${flightContainer({ init: true, nonce, chunk: flight })}</body>`
|
|
122
|
+
: '$&');
|
|
123
|
+
postRequestTasks('ssr', status, request, componentResponse);
|
|
124
|
+
return new Response(html, {
|
|
125
|
+
status,
|
|
126
|
+
statusText,
|
|
127
|
+
headers,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Stream a response to the client. NOTE: This omits custom `<head>`
|
|
132
|
+
* information, so this method should not be used by crawlers.
|
|
133
|
+
*/
|
|
134
|
+
async function stream(url, { App, pages, request, response, componentResponse, log, template, nonce, dev, }) {
|
|
135
|
+
const state = { pathname: url.pathname, search: url.search };
|
|
136
|
+
log.trace('start stream');
|
|
137
|
+
const { noScriptTemplate, bootstrapScripts, bootstrapModules } = stripScriptsFromTemplate(template);
|
|
138
|
+
const { AppSSR, rscReadable } = buildAppSSR({
|
|
139
|
+
App,
|
|
140
|
+
state,
|
|
141
|
+
request,
|
|
142
|
+
response: componentResponse,
|
|
143
|
+
log,
|
|
144
|
+
pages,
|
|
145
|
+
}, {
|
|
146
|
+
template: noScriptTemplate,
|
|
147
|
+
htmlAttrs: { lang: 'en' },
|
|
148
|
+
});
|
|
149
|
+
const rscToScriptTagReadable = new ReadableStream({
|
|
150
|
+
start(controller) {
|
|
151
|
+
log.trace('rsc start chunks');
|
|
152
|
+
let init = true;
|
|
108
153
|
const encoder = new TextEncoder();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Object.assign(responseOptions, getResponseOptions(componentResponse, didError));
|
|
117
|
-
/**
|
|
118
|
-
* TODO: This assumes `response.cache()` has been called _before_ any
|
|
119
|
-
* queries which might be caught behind Suspense. Clarify this or add
|
|
120
|
-
* additional checks downstream?
|
|
121
|
-
*/
|
|
122
|
-
responseOptions.headers[getCacheControlHeader({ dev })] =
|
|
123
|
-
componentResponse.cacheControlHeader;
|
|
124
|
-
if (isRedirect(responseOptions)) {
|
|
125
|
-
// Return redirects early without further rendering/streaming
|
|
126
|
-
return deferredShouldReturnApp.resolve(false);
|
|
127
|
-
}
|
|
128
|
-
if (!componentResponse.canStream())
|
|
129
|
-
return;
|
|
130
|
-
startWritingHtmlToStream(responseOptions, writable, encoder, dev ? didError : undefined);
|
|
131
|
-
deferredShouldReturnApp.resolve(true);
|
|
132
|
-
},
|
|
133
|
-
async onCompleteAll() {
|
|
134
|
-
log.trace('worker complete stream');
|
|
135
|
-
if (componentResponse.canStream())
|
|
136
|
-
return;
|
|
137
|
-
Object.assign(responseOptions, getResponseOptions(componentResponse, didError));
|
|
138
|
-
if (isRedirect(responseOptions)) {
|
|
139
|
-
// Redirects found after any async code
|
|
140
|
-
return deferredShouldReturnApp.resolve(false);
|
|
141
|
-
}
|
|
142
|
-
if (componentResponse.customBody) {
|
|
143
|
-
writable.write(encoder.encode(await componentResponse.customBody));
|
|
144
|
-
return deferredShouldReturnApp.resolve(false);
|
|
145
|
-
}
|
|
146
|
-
startWritingHtmlToStream(responseOptions, writable, encoder, dev ? didError : undefined);
|
|
147
|
-
deferredShouldReturnApp.resolve(true);
|
|
148
|
-
},
|
|
149
|
-
onError(error) {
|
|
150
|
-
didError = error;
|
|
151
|
-
if (dev && deferredShouldReturnApp.status === 'pending') {
|
|
152
|
-
writable.write(getErrorMarkup(error));
|
|
153
|
-
}
|
|
154
|
-
console.error(error);
|
|
155
|
-
},
|
|
154
|
+
bufferReadableStream(rscReadable.getReader(), (chunk) => {
|
|
155
|
+
const scriptTag = flightContainer({ init, chunk, nonce });
|
|
156
|
+
controller.enqueue(encoder.encode(scriptTag));
|
|
157
|
+
init = false;
|
|
158
|
+
}).then(() => {
|
|
159
|
+
log.trace('rsc finish chunks');
|
|
160
|
+
return controller.close();
|
|
156
161
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
let didError;
|
|
165
|
+
if (__WORKER__) {
|
|
166
|
+
const deferredShouldReturnApp = defer();
|
|
167
|
+
const encoder = new TextEncoder();
|
|
168
|
+
const transform = new TransformStream();
|
|
169
|
+
const writable = transform.writable.getWriter();
|
|
170
|
+
const responseOptions = {};
|
|
171
|
+
const ssrReadable = ssrRenderToReadableStream(AppSSR, {
|
|
172
|
+
nonce,
|
|
173
|
+
bootstrapScripts,
|
|
174
|
+
bootstrapModules,
|
|
175
|
+
onCompleteShell() {
|
|
176
|
+
log.trace('worker ready to stream');
|
|
177
|
+
Object.assign(responseOptions, getResponseOptions(componentResponse, didError));
|
|
178
|
+
/**
|
|
179
|
+
* TODO: This assumes `response.cache()` has been called _before_ any
|
|
180
|
+
* queries which might be caught behind Suspense. Clarify this or add
|
|
181
|
+
* additional checks downstream?
|
|
182
|
+
*/
|
|
183
|
+
responseOptions.headers[getCacheControlHeader({ dev })] =
|
|
184
|
+
componentResponse.cacheControlHeader;
|
|
185
|
+
if (isRedirect(responseOptions)) {
|
|
186
|
+
// Return redirects early without further rendering/streaming
|
|
187
|
+
return deferredShouldReturnApp.resolve(false);
|
|
188
|
+
}
|
|
189
|
+
if (!componentResponse.canStream())
|
|
190
|
+
return;
|
|
191
|
+
startWritingHtmlToStream(responseOptions, writable, encoder, dev ? didError : undefined);
|
|
192
|
+
deferredShouldReturnApp.resolve(true);
|
|
193
|
+
},
|
|
194
|
+
async onCompleteAll() {
|
|
195
|
+
log.trace('worker complete stream');
|
|
196
|
+
if (componentResponse.canStream())
|
|
197
|
+
return;
|
|
198
|
+
Object.assign(responseOptions, getResponseOptions(componentResponse, didError));
|
|
199
|
+
if (isRedirect(responseOptions)) {
|
|
200
|
+
// Redirects found after any async code
|
|
201
|
+
return deferredShouldReturnApp.resolve(false);
|
|
202
|
+
}
|
|
203
|
+
if (componentResponse.customBody) {
|
|
204
|
+
writable.write(encoder.encode(await componentResponse.customBody));
|
|
205
|
+
return deferredShouldReturnApp.resolve(false);
|
|
206
|
+
}
|
|
207
|
+
startWritingHtmlToStream(responseOptions, writable, encoder, dev ? didError : undefined);
|
|
208
|
+
deferredShouldReturnApp.resolve(true);
|
|
209
|
+
},
|
|
210
|
+
onError(error) {
|
|
211
|
+
didError = error;
|
|
212
|
+
if (dev && deferredShouldReturnApp.status === 'pending') {
|
|
213
|
+
writable.write(getErrorMarkup(error));
|
|
214
|
+
}
|
|
215
|
+
log.error(error);
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
if (await deferredShouldReturnApp.promise) {
|
|
219
|
+
let bufferedSsr = '';
|
|
220
|
+
let isPendingSsrWrite = false;
|
|
221
|
+
const writingSSR = bufferReadableStream(ssrReadable.getReader(), (chunk) => {
|
|
222
|
+
bufferedSsr += chunk;
|
|
223
|
+
if (!isPendingSsrWrite) {
|
|
224
|
+
isPendingSsrWrite = true;
|
|
214
225
|
setTimeout(() => {
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
isPendingSsrWrite = false;
|
|
227
|
+
// React can write fractional chunks synchronously.
|
|
228
|
+
// This timeout ensures we only write full HTML tags
|
|
229
|
+
// in order to allow RSC writing concurrently.
|
|
230
|
+
if (bufferedSsr) {
|
|
231
|
+
writable.write(encoder.encode(bufferedSsr));
|
|
232
|
+
bufferedSsr = '';
|
|
233
|
+
}
|
|
217
234
|
}, 0);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
clearTimeout(streamTimeout);
|
|
226
|
-
if (componentResponse.canStream() || response.writableEnded)
|
|
227
|
-
return;
|
|
228
|
-
writeHeadToServerResponse(response, componentResponse, log, didError);
|
|
229
|
-
logServerResponse('str', log, request, response.statusCode);
|
|
230
|
-
if (isRedirect(response)) {
|
|
231
|
-
// Redirects found after any async code
|
|
232
|
-
return response.end();
|
|
233
|
-
}
|
|
234
|
-
if (componentResponse.customBody) {
|
|
235
|
-
return response.end(await componentResponse.customBody);
|
|
236
|
-
}
|
|
237
|
-
startWritingHtmlToServerResponse(response, dev ? didError : undefined);
|
|
238
|
-
bufferReadableStream(rscToScriptTagReadable.getReader()).then((scriptTags) => {
|
|
239
|
-
// Piping ends the response so script tags
|
|
240
|
-
// must be written before that.
|
|
241
|
-
response.write(scriptTags);
|
|
242
|
-
pipe(response);
|
|
243
|
-
});
|
|
244
|
-
},
|
|
245
|
-
onError(error) {
|
|
246
|
-
didError = error;
|
|
247
|
-
if (dev && response.headersSent) {
|
|
248
|
-
// Calling write would flush headers automatically.
|
|
249
|
-
// Delay this error until headers are properly sent.
|
|
250
|
-
response.write(getErrorMarkup(error));
|
|
251
|
-
}
|
|
252
|
-
log.error(error);
|
|
253
|
-
},
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
const writingRSC = bufferReadableStream(rscToScriptTagReadable.getReader(), (scriptTag) => writable.write(encoder.encode(scriptTag)));
|
|
238
|
+
Promise.all([writingSSR, writingRSC]).then(() => {
|
|
239
|
+
// Last SSR write might be pending, delay closing the writable one tick
|
|
240
|
+
setTimeout(() => writable.close(), 0);
|
|
241
|
+
postRequestTasks('str', responseOptions.status, request, componentResponse);
|
|
254
242
|
});
|
|
255
|
-
const streamTimeout = setTimeout(() => {
|
|
256
|
-
log.warn(`The app failed to stream after ${STREAM_ABORT_TIMEOUT_MS} ms`);
|
|
257
|
-
}, STREAM_ABORT_TIMEOUT_MS);
|
|
258
243
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
*/
|
|
263
|
-
const hydrate = async function (url, { request, response, isStreamable }) {
|
|
264
|
-
const { log, state, componentResponse } = setupCurrentRequest(url, request);
|
|
265
|
-
const { AppRSC } = buildAppRSC({
|
|
266
|
-
App,
|
|
267
|
-
state,
|
|
268
|
-
request,
|
|
269
|
-
response: componentResponse,
|
|
270
|
-
log,
|
|
271
|
-
pages,
|
|
272
|
-
});
|
|
273
|
-
if (__WORKER__) {
|
|
274
|
-
const rscReadable = rscRenderToReadableStream(AppRSC);
|
|
275
|
-
if (isStreamable && (await isStreamingSupported())) {
|
|
276
|
-
logServerResponse('rsc', log, request, 200);
|
|
277
|
-
return new Response(rscReadable);
|
|
278
|
-
}
|
|
279
|
-
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
280
|
-
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
281
|
-
logServerResponse('rsc', log, request, 200);
|
|
282
|
-
return new Response(bufferedBody);
|
|
244
|
+
else {
|
|
245
|
+
writable.close();
|
|
246
|
+
postRequestTasks('str', responseOptions.status, request, componentResponse);
|
|
283
247
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const rscWriter = await import(
|
|
287
|
-
// @ts-ignore
|
|
288
|
-
'@shopify/hydrogen/vendor/react-server-dom-vite/writer.node.server');
|
|
289
|
-
const stream = rscWriter
|
|
290
|
-
.renderToPipeableStream(AppRSC)
|
|
291
|
-
.pipe(response);
|
|
292
|
-
stream.on('finish', function () {
|
|
293
|
-
logServerResponse('rsc', log, request, response.statusCode);
|
|
294
|
-
});
|
|
248
|
+
if (await isStreamingSupported()) {
|
|
249
|
+
return new Response(transform.readable, responseOptions);
|
|
295
250
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const routes = getApiRoutesFromPages(pages);
|
|
299
|
-
return getApiRouteFromURL(url, routes);
|
|
251
|
+
const bufferedBody = await bufferReadableStream(transform.readable.getReader());
|
|
252
|
+
return new Response(bufferedBody, responseOptions);
|
|
300
253
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
254
|
+
else if (response) {
|
|
255
|
+
response.socket.on('error', log.fatal);
|
|
256
|
+
const { pipe } = ssrRenderToPipeableStream(AppSSR, {
|
|
257
|
+
nonce,
|
|
258
|
+
bootstrapScripts,
|
|
259
|
+
bootstrapModules,
|
|
260
|
+
onCompleteShell() {
|
|
261
|
+
log.trace('node ready to stream');
|
|
262
|
+
/**
|
|
263
|
+
* TODO: This assumes `response.cache()` has been called _before_ any
|
|
264
|
+
* queries which might be caught behind Suspense. Clarify this or add
|
|
265
|
+
* additional checks downstream?
|
|
266
|
+
*/
|
|
267
|
+
response.setHeader(getCacheControlHeader({ dev }), componentResponse.cacheControlHeader);
|
|
268
|
+
writeHeadToServerResponse(response, componentResponse, log, didError);
|
|
269
|
+
logServerResponse('str', request, response.statusCode);
|
|
270
|
+
if (isRedirect(response)) {
|
|
271
|
+
// Return redirects early without further rendering/streaming
|
|
272
|
+
return response.end();
|
|
273
|
+
}
|
|
274
|
+
if (!componentResponse.canStream())
|
|
275
|
+
return;
|
|
276
|
+
startWritingHtmlToServerResponse(response, dev ? didError : undefined);
|
|
277
|
+
setTimeout(() => {
|
|
278
|
+
log.trace('node pipe response');
|
|
279
|
+
pipe(response);
|
|
280
|
+
}, 0);
|
|
281
|
+
bufferReadableStream(rscToScriptTagReadable.getReader(), (chunk) => {
|
|
282
|
+
log.trace('rsc chunk');
|
|
283
|
+
return response.write(chunk);
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
async onCompleteAll() {
|
|
287
|
+
log.trace('node complete stream');
|
|
288
|
+
if (componentResponse.canStream() || response.writableEnded) {
|
|
289
|
+
postRequestTasks('str', response.statusCode, request, componentResponse);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
writeHeadToServerResponse(response, componentResponse, log, didError);
|
|
293
|
+
postRequestTasks('str', response.statusCode, request, componentResponse);
|
|
294
|
+
if (isRedirect(response)) {
|
|
295
|
+
// Redirects found after any async code
|
|
296
|
+
return response.end();
|
|
297
|
+
}
|
|
298
|
+
if (componentResponse.customBody) {
|
|
299
|
+
return response.end(await componentResponse.customBody);
|
|
300
|
+
}
|
|
301
|
+
startWritingHtmlToServerResponse(response, dev ? didError : undefined);
|
|
302
|
+
bufferReadableStream(rscToScriptTagReadable.getReader()).then((scriptTags) => {
|
|
303
|
+
// Piping ends the response so script tags
|
|
304
|
+
// must be written before that.
|
|
305
|
+
response.write(scriptTags);
|
|
306
|
+
pipe(response);
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
onError(error) {
|
|
310
|
+
didError = error;
|
|
311
|
+
if (dev && response.headersSent) {
|
|
312
|
+
// Calling write would flush headers automatically.
|
|
313
|
+
// Delay this error until headers are properly sent.
|
|
314
|
+
response.write(getErrorMarkup(error));
|
|
315
|
+
}
|
|
316
|
+
log.error(error);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Stream a hydration response to the client.
|
|
323
|
+
*/
|
|
324
|
+
async function hydrate(url, { App, pages, request, response, componentResponse, isStreamable, log, }) {
|
|
325
|
+
const state = JSON.parse(url.searchParams.get('state') || '{}');
|
|
326
|
+
const { AppRSC } = buildAppRSC({
|
|
327
|
+
App,
|
|
328
|
+
state,
|
|
329
|
+
request,
|
|
330
|
+
response: componentResponse,
|
|
331
|
+
log,
|
|
332
|
+
pages,
|
|
333
|
+
});
|
|
334
|
+
if (__WORKER__) {
|
|
335
|
+
const rscReadable = rscRenderToReadableStream(AppRSC);
|
|
336
|
+
if (isStreamable && (await isStreamingSupported())) {
|
|
337
|
+
postRequestTasks('rsc', 200, request, componentResponse);
|
|
338
|
+
return new Response(rscReadable);
|
|
320
339
|
}
|
|
340
|
+
// Note: CFW does not support reader.piteTo nor iterable syntax
|
|
341
|
+
const bufferedBody = await bufferReadableStream(rscReadable.getReader());
|
|
342
|
+
postRequestTasks('rsc', 200, request, componentResponse);
|
|
343
|
+
return new Response(bufferedBody);
|
|
344
|
+
}
|
|
345
|
+
else if (response) {
|
|
346
|
+
response.socket.on('error', log.fatal);
|
|
347
|
+
const rscWriter = await import(
|
|
348
|
+
// @ts-ignore
|
|
349
|
+
'@shopify/hydrogen/vendor/react-server-dom-vite/writer.node.server');
|
|
350
|
+
const stream = rscWriter
|
|
351
|
+
.renderToPipeableStream(AppRSC)
|
|
352
|
+
.pipe(response);
|
|
353
|
+
stream.on('finish', function () {
|
|
354
|
+
postRequestTasks('rsc', response.statusCode, request, componentResponse);
|
|
355
|
+
});
|
|
321
356
|
}
|
|
322
|
-
return result;
|
|
323
357
|
}
|
|
324
358
|
function buildAppRSC({ App, state, request, response, log, pages, }) {
|
|
325
359
|
const hydrogenServerProps = { request, response, log };
|
|
326
360
|
const AppRSC = (React.createElement(ServerRequestProvider, { request: request, isRSC: true },
|
|
327
|
-
React.createElement(
|
|
361
|
+
React.createElement(PreloadQueries, { request: request },
|
|
362
|
+
React.createElement(App, { ...state, ...hydrogenServerProps, pages: pages }))));
|
|
328
363
|
return { AppRSC };
|
|
329
364
|
}
|
|
330
365
|
function buildAppSSR({ App, state, request, response, log, pages }, htmlOptions) {
|
|
@@ -342,10 +377,16 @@ function buildAppSSR({ App, state, request, response, log, pages }, htmlOptions)
|
|
|
342
377
|
const AppSSR = (React.createElement(Html, { ...htmlOptions },
|
|
343
378
|
React.createElement(ServerRequestProvider, { request: request, isRSC: false },
|
|
344
379
|
React.createElement(ServerStateProvider, { serverState: state, setServerState: () => { } },
|
|
345
|
-
React.createElement(
|
|
346
|
-
React.createElement(
|
|
380
|
+
React.createElement(PreloadQueries, { request: request },
|
|
381
|
+
React.createElement(React.Suspense, { fallback: null },
|
|
382
|
+
React.createElement(RscConsumer, null)))))));
|
|
347
383
|
return { AppSSR, rscReadable: rscReadableForFlight };
|
|
348
384
|
}
|
|
385
|
+
function PreloadQueries({ request, children, }) {
|
|
386
|
+
const preloadQueries = request.getPreloadQueries();
|
|
387
|
+
preloadRequestCacheData(request, preloadQueries);
|
|
388
|
+
return children;
|
|
389
|
+
}
|
|
349
390
|
function extractHeadElements({ context: { helmet } }) {
|
|
350
391
|
return helmet
|
|
351
392
|
? {
|
|
@@ -363,15 +404,11 @@ function extractHeadElements({ context: { helmet } }) {
|
|
|
363
404
|
}
|
|
364
405
|
async function renderToBufferedString(ReactApp, { log, nonce }) {
|
|
365
406
|
return new Promise(async (resolve, reject) => {
|
|
366
|
-
const errorTimeout = setTimeout(() => {
|
|
367
|
-
log.warn(`The app failed to SSR after ${STREAM_ABORT_TIMEOUT_MS} ms`);
|
|
368
|
-
}, STREAM_ABORT_TIMEOUT_MS);
|
|
369
407
|
if (__WORKER__) {
|
|
370
408
|
const deferred = defer();
|
|
371
409
|
const readable = ssrRenderToReadableStream(ReactApp, {
|
|
372
410
|
nonce,
|
|
373
411
|
onCompleteAll() {
|
|
374
|
-
clearTimeout(errorTimeout);
|
|
375
412
|
/**
|
|
376
413
|
* We want to wait until `onCompleteAll` has been called before fetching the
|
|
377
414
|
* stream body. Otherwise, React 18's streaming JS script/template tags
|
|
@@ -397,7 +434,6 @@ async function renderToBufferedString(ReactApp, { log, nonce }) {
|
|
|
397
434
|
* `template` and `script` tags inserted and rendered as part of the hydration response.
|
|
398
435
|
*/
|
|
399
436
|
onCompleteAll() {
|
|
400
|
-
clearTimeout(errorTimeout);
|
|
401
437
|
let data = '';
|
|
402
438
|
writer.on('data', (chunk) => (data += chunk.toString()));
|
|
403
439
|
writer.once('error', reject);
|
|
@@ -502,36 +538,18 @@ function flightContainer({ init, chunk, nonce, }) {
|
|
|
502
538
|
script += 'var __flight=[];';
|
|
503
539
|
}
|
|
504
540
|
if (chunk) {
|
|
505
|
-
const normalizedChunk = chunk
|
|
541
|
+
const normalizedChunk = chunk
|
|
542
|
+
// 1. Duplicate the escape char (\) for already escaped characters (e.g. \n or \").
|
|
543
|
+
.replace(/\\/g, String.raw `\\`)
|
|
544
|
+
// 2. Escape existing backticks to allow wrapping the whole thing in `...`.
|
|
545
|
+
.replace(/`/g, String.raw `\``);
|
|
506
546
|
script += `__flight.push(\`${normalizedChunk}\`)`;
|
|
507
547
|
}
|
|
508
548
|
return script + '</script>';
|
|
509
549
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
start(controller) {
|
|
516
|
-
controller.close();
|
|
517
|
-
},
|
|
518
|
-
});
|
|
519
|
-
// This will throw in CFW until streaming
|
|
520
|
-
// is supported. It works in Miniflare.
|
|
521
|
-
await new Response(rs).text();
|
|
522
|
-
cachedStreamingSupport = true;
|
|
523
|
-
}
|
|
524
|
-
catch (_) {
|
|
525
|
-
cachedStreamingSupport = false;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
return cachedStreamingSupport;
|
|
529
|
-
}
|
|
530
|
-
function setupCurrentRequest(url, request) {
|
|
531
|
-
const log = getLoggerFromContext(request);
|
|
532
|
-
const state = url.pathname === RSC_PATHNAME
|
|
533
|
-
? JSON.parse(url.searchParams.get('state') || '{}')
|
|
534
|
-
: { pathname: url.pathname, search: url.search };
|
|
535
|
-
const componentResponse = new ServerComponentResponse();
|
|
536
|
-
return { log, state, componentResponse };
|
|
550
|
+
function postRequestTasks(type, status, request, componentResponse) {
|
|
551
|
+
logServerResponse(type, request, status);
|
|
552
|
+
logCacheControlHeaders(type, request, componentResponse);
|
|
553
|
+
logQueryTimings(type, request);
|
|
554
|
+
request.savePreloadQueries();
|
|
537
555
|
}
|