@shopify/hydrogen 0.8.3 → 0.9.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/dist/esnext/components/ProductProvider/ProductProvider.client.d.ts +3 -2
- package/dist/esnext/entry-server.js +14 -4
- package/dist/esnext/foundation/Router/DefaultRoutes.d.ts +2 -2
- package/dist/esnext/foundation/Router/DefaultRoutes.js +6 -2
- package/dist/esnext/foundation/useQuery/hooks.js +2 -1
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +3 -0
- package/dist/esnext/framework/middleware.js +20 -22
- package/dist/esnext/framework/plugin.d.ts +1 -1
- package/dist/esnext/framework/plugin.js +3 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +2 -0
- package/dist/esnext/framework/plugins/vite-plugin-purge-query-cache.d.ts +3 -0
- package/dist/esnext/framework/plugins/vite-plugin-purge-query-cache.js +11 -0
- package/dist/esnext/handle-event.js +12 -2
- package/dist/esnext/types.d.ts +9 -4
- package/dist/esnext/utilities/apiRoutes.d.ts +21 -0
- package/dist/esnext/utilities/apiRoutes.js +73 -0
- package/dist/esnext/utilities/flattenConnection/flattenConnection.d.ts +1 -1
- package/dist/esnext/utilities/flattenConnection/flattenConnection.js +1 -1
- package/dist/esnext/utilities/log/log.d.ts +1 -1
- package/dist/esnext/utilities/log/log.js +6 -5
- package/dist/esnext/utilities/matchPath.d.ts +10 -0
- package/dist/esnext/utilities/matchPath.js +54 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +3 -0
- package/dist/node/framework/middleware.js +20 -22
- package/dist/node/framework/plugin.d.ts +1 -1
- package/dist/node/framework/plugin.js +3 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +2 -0
- package/dist/node/framework/plugins/vite-plugin-purge-query-cache.d.ts +3 -0
- package/dist/node/framework/plugins/vite-plugin-purge-query-cache.js +16 -0
- package/dist/node/handle-event.js +12 -2
- package/dist/node/types.d.ts +9 -4
- package/dist/node/utilities/apiRoutes.d.ts +21 -0
- package/dist/node/utilities/apiRoutes.js +79 -0
- package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +1 -1
- package/dist/node/utilities/flattenConnection/flattenConnection.js +1 -1
- package/dist/node/utilities/log/log.d.ts +17 -0
- package/dist/node/utilities/log/log.js +83 -0
- package/dist/node/utilities/matchPath.d.ts +10 -0
- package/dist/node/utilities/matchPath.js +58 -0
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +3 -0
- package/dist/worker/handle-event.js +12 -2
- package/dist/worker/types.d.ts +9 -4
- package/dist/worker/utilities/apiRoutes.d.ts +21 -0
- package/dist/worker/utilities/apiRoutes.js +73 -0
- package/dist/worker/utilities/log/log.d.ts +17 -0
- package/dist/worker/utilities/log/log.js +76 -0
- package/dist/worker/utilities/matchPath.d.ts +10 -0
- package/dist/worker/utilities/matchPath.js +54 -0
- package/package.json +10 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
+
import { useProductOptions } from '../../hooks';
|
|
2
3
|
import { Product } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* The `ProductProvider` component sets up a context with product details. Descendents of
|
|
@@ -9,8 +10,8 @@ export declare function ProductProvider({ children, product, initialVariantId, }
|
|
|
9
10
|
children: ReactNode;
|
|
10
11
|
/** A [Product object](/api/storefront/reference/products/product). */
|
|
11
12
|
product: Product;
|
|
12
|
-
/** The initially selected variant.
|
|
13
|
-
initialVariantId
|
|
13
|
+
/** The initially selected variant. This is required only if you're using a `SelectedVariantX` hook in the `ProductProvider` component.*/
|
|
14
|
+
initialVariantId?: Parameters<typeof useProductOptions>['0']['initialVariantId'];
|
|
14
15
|
}): JSX.Element;
|
|
15
16
|
export declare namespace ProductProvider {
|
|
16
17
|
var Fragment: string;
|
|
@@ -5,7 +5,7 @@ renderToPipeableStream, // Only available in Node context
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
renderToReadableStream, // Only available in Browser/Worker context
|
|
7
7
|
} from 'react-dom/server';
|
|
8
|
-
import { logServerResponse, getLoggerFromContext, } from './utilities/log/log';
|
|
8
|
+
import { logServerResponse, getLoggerFromContext, log, } from './utilities/log/log';
|
|
9
9
|
import { renderToString } from 'react-dom/server';
|
|
10
10
|
import { getErrorMarkup } from './utilities/error';
|
|
11
11
|
import ssrPrepass from 'react-ssr-prepass';
|
|
@@ -18,6 +18,7 @@ import { HydrationWriter } from './framework/Hydration/writer.server';
|
|
|
18
18
|
import { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
|
|
19
19
|
import { getCacheControlHeader } from './framework/cache';
|
|
20
20
|
import { RenderCacheProvider } from './foundation/RenderCacheProvider';
|
|
21
|
+
import { getApiRouteFromURL, getApiRoutesFromPages } from './utilities/apiRoutes';
|
|
21
22
|
/**
|
|
22
23
|
* react-dom/unstable-fizz provides different entrypoints based on runtime:
|
|
23
24
|
* - `renderToReadableStream` for "browser" (aka worker)
|
|
@@ -30,7 +31,7 @@ const isWorker = Boolean(renderToReadableStream);
|
|
|
30
31
|
* on the client to hydrate and build the React tree.
|
|
31
32
|
*/
|
|
32
33
|
const STREAM_ABORT_TIMEOUT_MS = 3000;
|
|
33
|
-
const renderHydrogen = (App, hook) => {
|
|
34
|
+
const renderHydrogen = (App, { pages } = {}, hook) => {
|
|
34
35
|
/**
|
|
35
36
|
* The render function is responsible for turning the provided `App` into an HTML string,
|
|
36
37
|
* and returning any initial state that needs to be hydrated into the client version of the app.
|
|
@@ -49,6 +50,7 @@ const renderHydrogen = (App, hook) => {
|
|
|
49
50
|
request,
|
|
50
51
|
dev,
|
|
51
52
|
log,
|
|
53
|
+
pages,
|
|
52
54
|
});
|
|
53
55
|
const body = await renderApp(ReactApp, state, log, isReactHydrationRequest);
|
|
54
56
|
logServerResponse('ssr', log, request, (_f = (_d = (_c = componentResponse.customStatus) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : componentResponse.status) !== null && _f !== void 0 ? _f : 200);
|
|
@@ -78,6 +80,7 @@ const renderHydrogen = (App, hook) => {
|
|
|
78
80
|
request,
|
|
79
81
|
dev,
|
|
80
82
|
log,
|
|
83
|
+
pages,
|
|
81
84
|
});
|
|
82
85
|
response.socket.on('error', (error) => {
|
|
83
86
|
log.fatal(error);
|
|
@@ -151,6 +154,7 @@ const renderHydrogen = (App, hook) => {
|
|
|
151
154
|
request,
|
|
152
155
|
dev,
|
|
153
156
|
log,
|
|
157
|
+
pages,
|
|
154
158
|
});
|
|
155
159
|
response.socket.on('error', (error) => {
|
|
156
160
|
log.fatal(error);
|
|
@@ -183,13 +187,19 @@ const renderHydrogen = (App, hook) => {
|
|
|
183
187
|
log.error(`The app failed to render RSC after ${STREAM_ABORT_TIMEOUT_MS} ms`);
|
|
184
188
|
}, STREAM_ABORT_TIMEOUT_MS);
|
|
185
189
|
};
|
|
190
|
+
function getApiRoute(url) {
|
|
191
|
+
const routes = getApiRoutesFromPages(pages);
|
|
192
|
+
return getApiRouteFromURL(url, routes);
|
|
193
|
+
}
|
|
186
194
|
return {
|
|
187
195
|
render,
|
|
188
196
|
stream,
|
|
189
197
|
hydrate,
|
|
198
|
+
getApiRoute,
|
|
199
|
+
log,
|
|
190
200
|
};
|
|
191
201
|
};
|
|
192
|
-
function buildReactApp({ App, state, context, request, dev, log, }) {
|
|
202
|
+
function buildReactApp({ App, state, context, request, dev, log, pages, }) {
|
|
193
203
|
const renderCache = {};
|
|
194
204
|
const helmetContext = {};
|
|
195
205
|
const componentResponse = new ServerComponentResponse();
|
|
@@ -201,7 +211,7 @@ function buildReactApp({ App, state, context, request, dev, log, }) {
|
|
|
201
211
|
const ReactApp = (props) => (React.createElement(RenderCacheProvider, { cache: renderCache },
|
|
202
212
|
React.createElement(StaticRouter, { location: { pathname: state.pathname, search: state.search }, context: context },
|
|
203
213
|
React.createElement(HelmetProvider, { context: helmetContext },
|
|
204
|
-
React.createElement(App, { ...props, ...hydrogenServerProps })))));
|
|
214
|
+
React.createElement(App, { ...props, ...hydrogenServerProps, pages: pages })))));
|
|
205
215
|
return { helmetContext, ReactApp, componentResponse };
|
|
206
216
|
}
|
|
207
217
|
function extractHeadElements(helmetContext) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import { Logger } from '../../utilities/log/log';
|
|
3
|
+
import type { ImportGlobEagerOutput } from '../../types';
|
|
4
4
|
/**
|
|
5
5
|
* Build a set of default Hydrogen routes based on the output provided by Vite's
|
|
6
6
|
* import.meta.globEager method.
|
|
@@ -16,7 +16,8 @@ export function DefaultRoutes({ pages, serverState, fallback, log, }) {
|
|
|
16
16
|
}
|
|
17
17
|
export function createRoutesFromPages(pages, topLevelPath = '*') {
|
|
18
18
|
const topLevelPrefix = topLevelPath.replace('*', '').replace(/\/$/, '');
|
|
19
|
-
const routes = Object.keys(pages)
|
|
19
|
+
const routes = Object.keys(pages)
|
|
20
|
+
.map((key) => {
|
|
20
21
|
const path = key
|
|
21
22
|
.replace('./pages', '')
|
|
22
23
|
.replace(/\.server\.(t|j)sx?$/, '')
|
|
@@ -38,12 +39,15 @@ export function createRoutesFromPages(pages, topLevelPath = '*') {
|
|
|
38
39
|
* https://reactrouter.com/core/api/Route/exact-bool
|
|
39
40
|
*/
|
|
40
41
|
const exact = !/\[(?:[.]{3})(\w+?)\]/.test(key);
|
|
42
|
+
if (!pages[key].default && !pages[key].api)
|
|
43
|
+
throw new Error(`${key} doesn't export a default React component or an API function`);
|
|
41
44
|
return {
|
|
42
45
|
path: topLevelPrefix + path,
|
|
43
46
|
component: pages[key].default,
|
|
44
47
|
exact,
|
|
45
48
|
};
|
|
46
|
-
})
|
|
49
|
+
})
|
|
50
|
+
.filter((route) => route.component);
|
|
47
51
|
/**
|
|
48
52
|
* Place static paths BEFORE dynamic paths to grant priority.
|
|
49
53
|
*/
|
|
@@ -14,7 +14,8 @@ key,
|
|
|
14
14
|
queryFn,
|
|
15
15
|
/** Options including `cache` to manage the cache behavior of the sub-request. */
|
|
16
16
|
queryOptions) {
|
|
17
|
-
|
|
17
|
+
const withCacheIdKey = ['__QUERY_CACHE_ID__', ...key];
|
|
18
|
+
return useRenderCacheData(withCacheIdKey, cachedQueryFnBuilder(withCacheIdKey, queryFn, queryOptions));
|
|
18
19
|
}
|
|
19
20
|
function cachedQueryFnBuilder(key, queryFn, queryOptions) {
|
|
20
21
|
const resolvedQueryOptions = {
|
|
@@ -15,6 +15,9 @@ export class ServerComponentRequest extends Request {
|
|
|
15
15
|
super(getUrlFromNodeRequest(input), {
|
|
16
16
|
headers: new Headers(input.headers),
|
|
17
17
|
method: input.method,
|
|
18
|
+
body: input.method !== 'GET' && input.method !== 'HEAD'
|
|
19
|
+
? input.body
|
|
20
|
+
: undefined,
|
|
18
21
|
});
|
|
19
22
|
}
|
|
20
23
|
this.time = getTime();
|
|
@@ -16,32 +16,24 @@ export function graphiqlMiddleware({ shopifyConfig, dev, }) {
|
|
|
16
16
|
export function hydrogenMiddleware({ dev, cache, indexTemplate, getServerEntrypoint, devServer, }) {
|
|
17
17
|
return async function (request, response, next) {
|
|
18
18
|
const url = new URL('http://' + request.headers.host + request.originalUrl);
|
|
19
|
-
const isReactHydrationRequest = url.pathname === '/react';
|
|
20
|
-
/**
|
|
21
|
-
* If it's a dev environment, it's assumed that Vite's dev server is handling
|
|
22
|
-
* any static or JS requests, so we need to ensure that we don't try to handle them.
|
|
23
|
-
*
|
|
24
|
-
* If it's a product environment, it's assumed that the developer is handling
|
|
25
|
-
* static requests with e.g. static middleware.
|
|
26
|
-
*/
|
|
27
|
-
if (dev && !shouldInterceptRequest(request, isReactHydrationRequest)) {
|
|
28
|
-
return next();
|
|
29
|
-
}
|
|
30
19
|
try {
|
|
31
20
|
/**
|
|
32
21
|
* We're running in the Node.js runtime without access to `fetch`,
|
|
33
22
|
* which is needed for proxy requests and server-side API requests.
|
|
34
23
|
*/
|
|
35
24
|
if (!globalThis.fetch) {
|
|
36
|
-
const fetch = await import('
|
|
25
|
+
const { fetch, Request, Response, Headers } = await import('undici');
|
|
26
|
+
const { default: AbortController } = await import('abort-controller');
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
globalThis.fetch = fetch;
|
|
37
29
|
// @ts-ignore
|
|
38
|
-
globalThis.
|
|
30
|
+
globalThis.Request = Request;
|
|
39
31
|
// @ts-ignore
|
|
40
|
-
globalThis.
|
|
32
|
+
globalThis.Response = Response;
|
|
41
33
|
// @ts-ignore
|
|
42
|
-
globalThis.
|
|
34
|
+
globalThis.Headers = Headers;
|
|
43
35
|
// @ts-ignore
|
|
44
|
-
globalThis.
|
|
36
|
+
globalThis.AbortController = AbortController;
|
|
45
37
|
}
|
|
46
38
|
/**
|
|
47
39
|
* Dynamically import ServerComponentResponse after the `fetch`
|
|
@@ -69,7 +61,18 @@ export function hydrogenMiddleware({ dev, cache, indexTemplate, getServerEntrypo
|
|
|
69
61
|
response.setHeader(key, value);
|
|
70
62
|
});
|
|
71
63
|
response.statusCode = eventResponse.status;
|
|
72
|
-
|
|
64
|
+
if (eventResponse.body) {
|
|
65
|
+
const reader = eventResponse.body.getReader();
|
|
66
|
+
while (true) {
|
|
67
|
+
const { done, value } = await reader.read();
|
|
68
|
+
if (done)
|
|
69
|
+
return response.end();
|
|
70
|
+
response.write(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
response.end();
|
|
75
|
+
}
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
catch (e) {
|
|
@@ -99,11 +102,6 @@ export function hydrogenMiddleware({ dev, cache, indexTemplate, getServerEntrypo
|
|
|
99
102
|
}
|
|
100
103
|
};
|
|
101
104
|
}
|
|
102
|
-
function shouldInterceptRequest(request, isReactHydrationRequest) {
|
|
103
|
-
var _a;
|
|
104
|
-
return (/text\/html|application\/hydrogen/.test((_a = request.headers['accept']) !== null && _a !== void 0 ? _a : '') ||
|
|
105
|
-
isReactHydrationRequest);
|
|
106
|
-
}
|
|
107
105
|
/**
|
|
108
106
|
* /graphiql and /___graphql are supported
|
|
109
107
|
*/
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { HydrogenVitePluginOptions, ShopifyConfig } from '../types';
|
|
2
|
-
declare const _default: (shopifyConfig: ShopifyConfig, pluginOptions
|
|
2
|
+
declare const _default: (shopifyConfig: ShopifyConfig, pluginOptions?: HydrogenVitePluginOptions) => (false | "" | import("vite").Plugin | import("vite").PluginOption[] | undefined)[];
|
|
3
3
|
export default _default;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import hydrogenConfig from './plugins/vite-plugin-hydrogen-config';
|
|
2
2
|
import hydrogenMiddleware from './plugins/vite-plugin-hydrogen-middleware';
|
|
3
3
|
import reactServerComponentShim from './plugins/vite-plugin-react-server-components-shim';
|
|
4
|
+
import purgeQueryCache from './plugins/vite-plugin-purge-query-cache';
|
|
4
5
|
import inspect from 'vite-plugin-inspect';
|
|
5
6
|
import react from '@vitejs/plugin-react';
|
|
6
|
-
export default (shopifyConfig, pluginOptions) => {
|
|
7
|
+
export default (shopifyConfig, pluginOptions = {}) => {
|
|
7
8
|
return [
|
|
8
9
|
process.env.VITE_INSPECT && inspect(),
|
|
9
10
|
hydrogenConfig(),
|
|
10
11
|
hydrogenMiddleware(shopifyConfig, pluginOptions),
|
|
11
12
|
reactServerComponentShim(),
|
|
12
13
|
react(),
|
|
14
|
+
pluginOptions.purgeQueryCacheOnBuild && purgeQueryCache(),
|
|
13
15
|
];
|
|
14
16
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadEnv } from 'vite';
|
|
2
|
+
import bodyParser from 'body-parser';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { promises as fs } from 'fs';
|
|
4
5
|
import { hydrogenMiddleware, graphiqlMiddleware } from '../middleware';
|
|
@@ -25,6 +26,7 @@ export default (shopifyConfig, pluginOptions) => {
|
|
|
25
26
|
shopifyConfig,
|
|
26
27
|
dev: true,
|
|
27
28
|
}));
|
|
29
|
+
server.middlewares.use(bodyParser.raw({ type: '*/*' }));
|
|
28
30
|
return () => server.middlewares.use(hydrogenMiddleware({
|
|
29
31
|
dev: true,
|
|
30
32
|
shopifyConfig,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Crypto from 'crypto';
|
|
2
|
+
export default () => {
|
|
3
|
+
const buildCacheId = Crypto.randomBytes(8).toString('hex').slice(0, 8);
|
|
4
|
+
return {
|
|
5
|
+
name: 'vite-plugin-purge-query-cache',
|
|
6
|
+
enforce: 'pre',
|
|
7
|
+
transform(code) {
|
|
8
|
+
return code.replace('__QUERY_CACHE_ID__', buildCacheId);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCacheControlHeader } from './framework/cache';
|
|
2
2
|
import { setContext, setCache } from './framework/runtime';
|
|
3
3
|
import { setConfig } from './framework/config';
|
|
4
|
+
import { renderApiRoute } from './utilities/apiRoutes';
|
|
4
5
|
export default async function handleEvent(event, { request, entrypoint, indexTemplate, assetHandler, streamableResponse, dev, cache, context, }) {
|
|
5
6
|
var _a, _b, _c, _d, _e;
|
|
6
7
|
const url = new URL(request.url);
|
|
@@ -21,12 +22,21 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
|
|
|
21
22
|
assetHandler) {
|
|
22
23
|
return assetHandler(event, url);
|
|
23
24
|
}
|
|
24
|
-
const { render, hydrate, stream } = entrypoint.default || entrypoint;
|
|
25
|
+
const { render, hydrate, stream, getApiRoute, log } = entrypoint.default || entrypoint;
|
|
25
26
|
// @ts-ignore
|
|
26
|
-
if (dev && !(render && hydrate && stream)) {
|
|
27
|
+
if (dev && !(render && hydrate && stream && getApiRoute)) {
|
|
27
28
|
throw new Error(`entry-server.jsx could not be loaded. This likely occurred because of a Vite compilation error.\n` +
|
|
28
29
|
`Please check your server logs for more information.`);
|
|
29
30
|
}
|
|
31
|
+
if (!isReactHydrationRequest) {
|
|
32
|
+
const apiRoute = getApiRoute(url);
|
|
33
|
+
// The API Route might have a default export, making it also a server component
|
|
34
|
+
// If it does, only render the API route if the request method is GET
|
|
35
|
+
if (apiRoute &&
|
|
36
|
+
(!apiRoute.hasServerComponent || request.method !== 'GET')) {
|
|
37
|
+
return renderApiRoute(request, apiRoute, log);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
30
40
|
const userAgent = request.headers.get('user-agent');
|
|
31
41
|
const isStreamable = streamableResponse && !isBotUA(url, userAgent);
|
|
32
42
|
/**
|
package/dist/esnext/types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { ServerResponse } from 'http';
|
|
|
3
3
|
import type { ServerComponentResponse } from './framework/Hydration/ServerComponentResponse.server';
|
|
4
4
|
import type { ServerComponentRequest } from './framework/Hydration/ServerComponentRequest.server';
|
|
5
5
|
import type { Metafield, Image, MediaContentType } from './graphql/types/types';
|
|
6
|
+
import { ApiRouteMatch } from './utilities/apiRoutes';
|
|
7
|
+
import { Logger } from './utilities/log/log';
|
|
6
8
|
export declare type Renderer = (url: URL, options: {
|
|
7
9
|
request: ServerComponentRequest;
|
|
8
10
|
context?: Record<string, any>;
|
|
@@ -29,6 +31,8 @@ export declare type EntryServerHandler = {
|
|
|
29
31
|
render: Renderer;
|
|
30
32
|
stream: Streamer;
|
|
31
33
|
hydrate: Hydrator;
|
|
34
|
+
getApiRoute: (url: URL) => ApiRouteMatch | null;
|
|
35
|
+
log: Logger;
|
|
32
36
|
};
|
|
33
37
|
export declare type ShopifyConfig = {
|
|
34
38
|
locale?: string;
|
|
@@ -39,11 +43,11 @@ export declare type ShopifyConfig = {
|
|
|
39
43
|
export declare type Hook = (params: {
|
|
40
44
|
url: URL;
|
|
41
45
|
} & Record<string, any>) => any | Promise<any>;
|
|
42
|
-
export declare type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
hydrate: Hydrator;
|
|
46
|
+
export declare type ImportGlobEagerOutput = Record<string, Record<'default' | 'api', any>>;
|
|
47
|
+
export declare type ServerHandlerConfig = {
|
|
48
|
+
pages?: ImportGlobEagerOutput;
|
|
46
49
|
};
|
|
50
|
+
export declare type ServerHandler = (App: any, config?: ServerHandlerConfig, hook?: Hook) => EntryServerHandler;
|
|
47
51
|
export declare type ClientHandler = (App: any, hook?: Hook) => Promise<void>;
|
|
48
52
|
export interface GraphQLConnection<T> {
|
|
49
53
|
edges?: {
|
|
@@ -87,5 +91,6 @@ export interface CacheOptions {
|
|
|
87
91
|
}
|
|
88
92
|
export interface HydrogenVitePluginOptions {
|
|
89
93
|
devCache?: boolean;
|
|
94
|
+
purgeQueryCacheOnBuild?: boolean;
|
|
90
95
|
}
|
|
91
96
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ImportGlobEagerOutput } from '../types';
|
|
2
|
+
import { Logger } from '../utilities/log/log';
|
|
3
|
+
declare type RouteParams = Record<string, string>;
|
|
4
|
+
declare type RequestOptions = {
|
|
5
|
+
params: RouteParams;
|
|
6
|
+
};
|
|
7
|
+
declare type ResourceGetter = (request: Request, requestOptions: RequestOptions) => Promise<Response>;
|
|
8
|
+
interface HydrogenApiRoute {
|
|
9
|
+
path: string;
|
|
10
|
+
resource: ResourceGetter;
|
|
11
|
+
hasServerComponent: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare type ApiRouteMatch = {
|
|
14
|
+
resource: ResourceGetter;
|
|
15
|
+
hasServerComponent: boolean;
|
|
16
|
+
params: RouteParams;
|
|
17
|
+
};
|
|
18
|
+
export declare function getApiRoutesFromPages(pages: ImportGlobEagerOutput | undefined, topLevelPath?: string): Array<HydrogenApiRoute>;
|
|
19
|
+
export declare function getApiRouteFromURL(url: URL, routes: Array<HydrogenApiRoute>): ApiRouteMatch | null;
|
|
20
|
+
export declare function renderApiRoute(request: Request, route: ApiRouteMatch, log: Logger): Promise<Response>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { matchPath } from './matchPath';
|
|
2
|
+
import { logServerResponse } from '../utilities/log/log';
|
|
3
|
+
let cachedRoutes = [];
|
|
4
|
+
export function getApiRoutesFromPages(pages, topLevelPath = '*') {
|
|
5
|
+
if ((cachedRoutes === null || cachedRoutes === void 0 ? void 0 : cachedRoutes.length) || !pages)
|
|
6
|
+
return cachedRoutes;
|
|
7
|
+
const topLevelPrefix = topLevelPath.replace('*', '').replace(/\/$/, '');
|
|
8
|
+
const routes = Object.keys(pages)
|
|
9
|
+
.filter((key) => pages[key].api)
|
|
10
|
+
.map((key) => {
|
|
11
|
+
const path = key
|
|
12
|
+
.replace('./pages', '')
|
|
13
|
+
.replace(/\.server\.(t|j)sx?$/, '')
|
|
14
|
+
/**
|
|
15
|
+
* Replace /index with /
|
|
16
|
+
*/
|
|
17
|
+
.replace(/\/index$/i, '/')
|
|
18
|
+
/**
|
|
19
|
+
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
20
|
+
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
21
|
+
*/
|
|
22
|
+
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
23
|
+
/**
|
|
24
|
+
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
25
|
+
*/
|
|
26
|
+
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
27
|
+
/**
|
|
28
|
+
* Catch-all routes [...handle].jsx don't need an exact match
|
|
29
|
+
* https://reactrouter.com/core/api/Route/exact-bool
|
|
30
|
+
*/
|
|
31
|
+
const exact = !/\[(?:[.]{3})(\w+?)\]/.test(key);
|
|
32
|
+
return {
|
|
33
|
+
path: topLevelPrefix + path,
|
|
34
|
+
resource: pages[key].api,
|
|
35
|
+
hasServerComponent: !!pages[key].default,
|
|
36
|
+
exact,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
cachedRoutes = [
|
|
40
|
+
...routes.filter((route) => !route.path.includes(':')),
|
|
41
|
+
...routes.filter((route) => route.path.includes(':')),
|
|
42
|
+
];
|
|
43
|
+
return cachedRoutes;
|
|
44
|
+
}
|
|
45
|
+
export function getApiRouteFromURL(url, routes) {
|
|
46
|
+
let foundRoute, foundRouteDetails;
|
|
47
|
+
for (let i = 0; i < routes.length; i++) {
|
|
48
|
+
foundRouteDetails = matchPath(url.pathname, routes[i]);
|
|
49
|
+
if (foundRouteDetails) {
|
|
50
|
+
foundRoute = routes[i];
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!foundRoute)
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
resource: foundRoute.resource,
|
|
58
|
+
params: foundRouteDetails.params,
|
|
59
|
+
hasServerComponent: foundRoute.hasServerComponent,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export async function renderApiRoute(request, route, log) {
|
|
63
|
+
let response;
|
|
64
|
+
try {
|
|
65
|
+
response = await route.resource(request, { params: route.params });
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
log.error(e);
|
|
69
|
+
response = new Response('Error processing: ' + request.url, { status: 500 });
|
|
70
|
+
}
|
|
71
|
+
logServerResponse('api', log, request, response.status);
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GraphQLConnection } from '../../types';
|
|
2
2
|
/**
|
|
3
|
-
* The `flattenConnection` utility transforms a connection object from the Storefront API (for example, [Product-related connections](api/storefront/reference/products/product
|
|
3
|
+
* The `flattenConnection` utility transforms a connection object from the Storefront API (for example, [Product-related connections](/api/storefront/reference/products/product)) into a flat array of nodes.
|
|
4
4
|
*/
|
|
5
5
|
export declare function flattenConnection<T>(connection: GraphQLConnection<T>): T[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `flattenConnection` utility transforms a connection object from the Storefront API (for example, [Product-related connections](api/storefront/reference/products/product
|
|
2
|
+
* The `flattenConnection` utility transforms a connection object from the Storefront API (for example, [Product-related connections](/api/storefront/reference/products/product)) into a flat array of nodes.
|
|
3
3
|
*/
|
|
4
4
|
export function flattenConnection(connection) {
|
|
5
5
|
var _a;
|
|
@@ -14,4 +14,4 @@ export declare function getLoggerFromContext(context: any): Logger;
|
|
|
14
14
|
export declare function setLogger(newLogger: Logger): void;
|
|
15
15
|
export declare function resetLogger(): void;
|
|
16
16
|
export declare const log: Logger;
|
|
17
|
-
export declare function logServerResponse(type: 'str' | 'rsc' | 'ssr', log: Logger, request: ServerComponentRequest, responseStatus: number): void;
|
|
17
|
+
export declare function logServerResponse(type: 'str' | 'rsc' | 'ssr' | 'api', log: Logger, request: ServerComponentRequest, responseStatus: number): void;
|
|
@@ -50,6 +50,11 @@ export const log = {
|
|
|
50
50
|
return logger.fatal({}, ...args);
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
|
+
const SERVER_RESPONSE_MAP = {
|
|
54
|
+
str: 'streaming SSR',
|
|
55
|
+
rsc: 'server Components',
|
|
56
|
+
ssr: 'buffered SSR',
|
|
57
|
+
};
|
|
53
58
|
export function logServerResponse(type, log, request, responseStatus) {
|
|
54
59
|
const coloredResponseStatus = responseStatus >= 500
|
|
55
60
|
? red(responseStatus)
|
|
@@ -58,11 +63,7 @@ export function logServerResponse(type, log, request, responseStatus) {
|
|
|
58
63
|
: responseStatus >= 300
|
|
59
64
|
? lightBlue(responseStatus)
|
|
60
65
|
: green(responseStatus);
|
|
61
|
-
const fullType = type
|
|
62
|
-
? 'streaming SSR'
|
|
63
|
-
: type === 'rsc'
|
|
64
|
-
? 'server components'
|
|
65
|
-
: 'buffered SSR';
|
|
66
|
+
const fullType = SERVER_RESPONSE_MAP[type] || type;
|
|
66
67
|
const styledType = italic(pad(fullType, ' '));
|
|
67
68
|
const paddedTiming = pad((getTime() - request.time).toFixed(2) + ' ms', ' ');
|
|
68
69
|
const url = type === 'rsc'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TokensToRegexpOptions } from 'path-to-regexp';
|
|
2
|
+
interface MatchPathOptions extends TokensToRegexpOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
exact?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Public API for matching a URL pathname to a path.
|
|
8
|
+
*/
|
|
9
|
+
export declare function matchPath(pathname: string, options?: MatchPathOptions): any;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { pathToRegexp } from 'path-to-regexp';
|
|
2
|
+
// Modified from React Router v5
|
|
3
|
+
// https://github.com/remix-run/react-router/blob/v5/packages/react-router/modules/matchPath.js
|
|
4
|
+
const cache = {};
|
|
5
|
+
const cacheLimit = 10000;
|
|
6
|
+
let cacheCount = 0;
|
|
7
|
+
function compilePath(path, options) {
|
|
8
|
+
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
|
|
9
|
+
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
|
|
10
|
+
if (pathCache[path])
|
|
11
|
+
return pathCache[path];
|
|
12
|
+
const keys = [];
|
|
13
|
+
const regexp = pathToRegexp(path, keys, options);
|
|
14
|
+
const result = { regexp, keys };
|
|
15
|
+
if (cacheCount < cacheLimit) {
|
|
16
|
+
pathCache[path] = result;
|
|
17
|
+
cacheCount++;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Public API for matching a URL pathname to a path.
|
|
23
|
+
*/
|
|
24
|
+
export function matchPath(pathname, options = {}) {
|
|
25
|
+
const { path, exact = false, strict = false, sensitive = false } = options;
|
|
26
|
+
const paths = [].concat(path);
|
|
27
|
+
return paths.reduce((matched, path) => {
|
|
28
|
+
if (!path && path !== '')
|
|
29
|
+
return null;
|
|
30
|
+
if (matched)
|
|
31
|
+
return matched;
|
|
32
|
+
const { regexp, keys } = compilePath(path, {
|
|
33
|
+
end: exact,
|
|
34
|
+
strict,
|
|
35
|
+
sensitive,
|
|
36
|
+
});
|
|
37
|
+
const match = regexp.exec(pathname);
|
|
38
|
+
if (!match)
|
|
39
|
+
return null;
|
|
40
|
+
const [url, ...values] = match;
|
|
41
|
+
const isExact = pathname === url;
|
|
42
|
+
if (exact && !isExact)
|
|
43
|
+
return null;
|
|
44
|
+
return {
|
|
45
|
+
path,
|
|
46
|
+
url: path === '/' && url === '' ? '/' : url,
|
|
47
|
+
isExact,
|
|
48
|
+
params: keys.reduce((memo, key, index) => {
|
|
49
|
+
memo[key.name] = values[index];
|
|
50
|
+
return memo;
|
|
51
|
+
}, {}),
|
|
52
|
+
};
|
|
53
|
+
}, null);
|
|
54
|
+
}
|
package/dist/esnext/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "0.
|
|
1
|
+
export declare const LIB_VERSION = "0.9.0";
|
package/dist/esnext/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LIB_VERSION = '0.
|
|
1
|
+
export const LIB_VERSION = '0.9.0';
|
|
@@ -18,6 +18,9 @@ class ServerComponentRequest extends Request {
|
|
|
18
18
|
super(getUrlFromNodeRequest(input), {
|
|
19
19
|
headers: new Headers(input.headers),
|
|
20
20
|
method: input.method,
|
|
21
|
+
body: input.method !== 'GET' && input.method !== 'HEAD'
|
|
22
|
+
? input.body
|
|
23
|
+
: undefined,
|
|
21
24
|
});
|
|
22
25
|
}
|
|
23
26
|
this.time = (0, timing_1.getTime)();
|