@shopify/hydrogen 0.8.0 → 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/CartLineQuantityAdjustButton/CartLineQuantityAdjustButton.js +4 -0
- package/dist/esnext/components/ProductProvider/ProductProvider.client.d.ts +3 -2
- package/dist/esnext/components/ShopPayButton/ShopPayButton.client.js +1 -1
- package/dist/esnext/entry-server.js +76 -36
- package/dist/esnext/foundation/RenderCacheProvider/hook.js +1 -1
- package/dist/esnext/foundation/RenderCacheProvider/types.d.ts +9 -2
- package/dist/esnext/foundation/Router/DefaultRoutes.d.ts +4 -2
- package/dist/esnext/foundation/Router/DefaultRoutes.js +8 -4
- package/dist/esnext/foundation/ServerStateProvider/ServerStateProvider.client.js +4 -2
- package/dist/esnext/foundation/useQuery/hooks.js +6 -4
- package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
- package/dist/esnext/foundation/useShop/use-shop.js +1 -1
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +5 -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-config.d.ts +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +2 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +17 -1
- 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 +80 -12
- package/dist/esnext/hooks/useShopQuery/hooks.d.ts +4 -4
- package/dist/esnext/hooks/useShopQuery/hooks.js +32 -11
- package/dist/esnext/index.d.ts +2 -1
- package/dist/esnext/index.js +2 -1
- 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/fetch.js +3 -6
- package/dist/esnext/utilities/flattenConnection/flattenConnection.d.ts +1 -1
- package/dist/esnext/utilities/flattenConnection/flattenConnection.js +1 -1
- package/dist/esnext/utilities/index.d.ts +1 -0
- package/dist/esnext/utilities/index.js +1 -0
- package/dist/esnext/utilities/log/index.d.ts +1 -0
- package/dist/esnext/utilities/log/index.js +1 -0
- package/dist/esnext/utilities/log/log.d.ts +17 -0
- package/dist/esnext/utilities/log/log.js +76 -0
- package/dist/esnext/utilities/matchPath.d.ts +10 -0
- package/dist/esnext/utilities/matchPath.js +54 -0
- package/dist/esnext/utilities/timing.d.ts +7 -0
- package/dist/esnext/utilities/timing.js +14 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/node/framework/Hydration/ServerComponentRequest.server.js +5 -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-config.d.ts +1 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +2 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.d.ts +7 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +17 -1
- 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 +80 -12
- 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/fetch.js +3 -6
- package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +1 -1
- package/dist/node/utilities/flattenConnection/flattenConnection.js +1 -1
- package/dist/node/utilities/index.d.ts +1 -0
- package/dist/node/utilities/index.js +3 -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/utilities/timing.d.ts +7 -0
- package/dist/node/utilities/timing.js +18 -0
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
- package/dist/worker/framework/Hydration/ServerComponentRequest.server.js +5 -0
- package/dist/worker/handle-event.js +80 -12
- 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/dist/worker/utilities/timing.d.ts +7 -0
- package/dist/worker/utilities/timing.js +14 -0
- package/package.json +15 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decodeShopifyId = exports.graphqlRequestBody = exports.fetchBuilder = exports.parseMetafieldValue = exports.getMeasurementAsString = exports.getMeasurementAsParts = exports.isServer = exports.isClient = exports.flattenConnection = exports.wrapPromise = exports.loadScript = exports.useEmbeddedVideoUrl = exports.addParametersToEmbeddedVideoUrl = exports.shopifyImageLoader = exports.getShopifyImageDimensions = exports.useImageUrl = exports.addImageSizeParametersToUrl = void 0;
|
|
3
|
+
exports.getTime = exports.decodeShopifyId = exports.graphqlRequestBody = exports.fetchBuilder = exports.parseMetafieldValue = exports.getMeasurementAsString = exports.getMeasurementAsParts = exports.isServer = exports.isClient = exports.flattenConnection = exports.wrapPromise = exports.loadScript = exports.useEmbeddedVideoUrl = exports.addParametersToEmbeddedVideoUrl = exports.shopifyImageLoader = exports.getShopifyImageDimensions = exports.useImageUrl = exports.addImageSizeParametersToUrl = void 0;
|
|
4
4
|
var image_size_1 = require("./image_size");
|
|
5
5
|
Object.defineProperty(exports, "addImageSizeParametersToUrl", { enumerable: true, get: function () { return image_size_1.addImageSizeParametersToUrl; } });
|
|
6
6
|
Object.defineProperty(exports, "useImageUrl", { enumerable: true, get: function () { return image_size_1.useImageUrl; } });
|
|
@@ -28,3 +28,5 @@ var fetch_1 = require("./fetch");
|
|
|
28
28
|
Object.defineProperty(exports, "fetchBuilder", { enumerable: true, get: function () { return fetch_1.fetchBuilder; } });
|
|
29
29
|
Object.defineProperty(exports, "graphqlRequestBody", { enumerable: true, get: function () { return fetch_1.graphqlRequestBody; } });
|
|
30
30
|
Object.defineProperty(exports, "decodeShopifyId", { enumerable: true, get: function () { return fetch_1.decodeShopifyId; } });
|
|
31
|
+
var timing_1 = require("./timing");
|
|
32
|
+
Object.defineProperty(exports, "getTime", { enumerable: true, get: function () { return timing_1.getTime; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ServerComponentRequest } from '../../framework/Hydration/ServerComponentRequest.server';
|
|
2
|
+
/** A utility for logging debugging, warning, and error information about the application.
|
|
3
|
+
* Use by importing `log` `@shopify/hydrogen` or by using a `log` prop passed to each page
|
|
4
|
+
* component. Using the latter is ideal, because it will ty your log to the current request in progress.
|
|
5
|
+
*/
|
|
6
|
+
export interface Logger {
|
|
7
|
+
trace: (...args: Array<any>) => void;
|
|
8
|
+
debug: (...args: Array<any>) => void;
|
|
9
|
+
warn: (...args: Array<any>) => void;
|
|
10
|
+
error: (...args: Array<any>) => void;
|
|
11
|
+
fatal: (...args: Array<any>) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function getLoggerFromContext(context: any): Logger;
|
|
14
|
+
export declare function setLogger(newLogger: Logger): void;
|
|
15
|
+
export declare function resetLogger(): void;
|
|
16
|
+
export declare const log: Logger;
|
|
17
|
+
export declare function logServerResponse(type: 'str' | 'rsc' | 'ssr' | 'api', log: Logger, request: ServerComponentRequest, responseStatus: number): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logServerResponse = exports.log = exports.resetLogger = exports.setLogger = exports.getLoggerFromContext = void 0;
|
|
4
|
+
const kolorist_1 = require("kolorist");
|
|
5
|
+
const timing_1 = require("../timing");
|
|
6
|
+
const defaultLogger = {
|
|
7
|
+
trace(context, ...args) {
|
|
8
|
+
console.log(...args);
|
|
9
|
+
},
|
|
10
|
+
debug(context, ...args) {
|
|
11
|
+
console.log(...args);
|
|
12
|
+
},
|
|
13
|
+
warn(context, ...args) {
|
|
14
|
+
console.warn((0, kolorist_1.yellow)('WARN: '), ...args);
|
|
15
|
+
},
|
|
16
|
+
error(context, ...args) {
|
|
17
|
+
console.error((0, kolorist_1.red)('ERROR: '), ...args);
|
|
18
|
+
},
|
|
19
|
+
fatal(context, ...args) {
|
|
20
|
+
console.error((0, kolorist_1.red)('FATAL: '), ...args);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
let logger = defaultLogger;
|
|
24
|
+
function getLoggerFromContext(context) {
|
|
25
|
+
return {
|
|
26
|
+
trace: (...args) => logger.trace(context, ...args),
|
|
27
|
+
debug: (...args) => logger.debug(context, ...args),
|
|
28
|
+
warn: (...args) => logger.warn(context, ...args),
|
|
29
|
+
error: (...args) => logger.error(context, ...args),
|
|
30
|
+
fatal: (...args) => logger.fatal(context, ...args),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
exports.getLoggerFromContext = getLoggerFromContext;
|
|
34
|
+
function setLogger(newLogger) {
|
|
35
|
+
logger = newLogger;
|
|
36
|
+
}
|
|
37
|
+
exports.setLogger = setLogger;
|
|
38
|
+
function resetLogger() {
|
|
39
|
+
logger = defaultLogger;
|
|
40
|
+
}
|
|
41
|
+
exports.resetLogger = resetLogger;
|
|
42
|
+
exports.log = {
|
|
43
|
+
trace(...args) {
|
|
44
|
+
return logger.trace({}, ...args);
|
|
45
|
+
},
|
|
46
|
+
debug(...args) {
|
|
47
|
+
return logger.debug({}, ...args);
|
|
48
|
+
},
|
|
49
|
+
warn(...args) {
|
|
50
|
+
return logger.warn({}, ...args);
|
|
51
|
+
},
|
|
52
|
+
error(...args) {
|
|
53
|
+
return logger.error({}, ...args);
|
|
54
|
+
},
|
|
55
|
+
fatal(...args) {
|
|
56
|
+
return logger.fatal({}, ...args);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const SERVER_RESPONSE_MAP = {
|
|
60
|
+
str: 'streaming SSR',
|
|
61
|
+
rsc: 'server Components',
|
|
62
|
+
ssr: 'buffered SSR',
|
|
63
|
+
};
|
|
64
|
+
function logServerResponse(type, log, request, responseStatus) {
|
|
65
|
+
const coloredResponseStatus = responseStatus >= 500
|
|
66
|
+
? (0, kolorist_1.red)(responseStatus)
|
|
67
|
+
: responseStatus >= 400
|
|
68
|
+
? (0, kolorist_1.yellow)(responseStatus)
|
|
69
|
+
: responseStatus >= 300
|
|
70
|
+
? (0, kolorist_1.lightBlue)(responseStatus)
|
|
71
|
+
: (0, kolorist_1.green)(responseStatus);
|
|
72
|
+
const fullType = SERVER_RESPONSE_MAP[type] || type;
|
|
73
|
+
const styledType = (0, kolorist_1.italic)(pad(fullType, ' '));
|
|
74
|
+
const paddedTiming = pad(((0, timing_1.getTime)() - request.time).toFixed(2) + ' ms', ' ');
|
|
75
|
+
const url = type === 'rsc'
|
|
76
|
+
? decodeURIComponent(request.url.substring(request.url.indexOf('=') + 1))
|
|
77
|
+
: request.url;
|
|
78
|
+
log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${paddedTiming} ${url}`);
|
|
79
|
+
}
|
|
80
|
+
exports.logServerResponse = logServerResponse;
|
|
81
|
+
function pad(str, _pad) {
|
|
82
|
+
return (str + _pad).substring(0, _pad.length);
|
|
83
|
+
}
|
|
@@ -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,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchPath = void 0;
|
|
4
|
+
const path_to_regexp_1 = require("path-to-regexp");
|
|
5
|
+
// Modified from React Router v5
|
|
6
|
+
// https://github.com/remix-run/react-router/blob/v5/packages/react-router/modules/matchPath.js
|
|
7
|
+
const cache = {};
|
|
8
|
+
const cacheLimit = 10000;
|
|
9
|
+
let cacheCount = 0;
|
|
10
|
+
function compilePath(path, options) {
|
|
11
|
+
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
|
|
12
|
+
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
|
|
13
|
+
if (pathCache[path])
|
|
14
|
+
return pathCache[path];
|
|
15
|
+
const keys = [];
|
|
16
|
+
const regexp = (0, path_to_regexp_1.pathToRegexp)(path, keys, options);
|
|
17
|
+
const result = { regexp, keys };
|
|
18
|
+
if (cacheCount < cacheLimit) {
|
|
19
|
+
pathCache[path] = result;
|
|
20
|
+
cacheCount++;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Public API for matching a URL pathname to a path.
|
|
26
|
+
*/
|
|
27
|
+
function matchPath(pathname, options = {}) {
|
|
28
|
+
const { path, exact = false, strict = false, sensitive = false } = options;
|
|
29
|
+
const paths = [].concat(path);
|
|
30
|
+
return paths.reduce((matched, path) => {
|
|
31
|
+
if (!path && path !== '')
|
|
32
|
+
return null;
|
|
33
|
+
if (matched)
|
|
34
|
+
return matched;
|
|
35
|
+
const { regexp, keys } = compilePath(path, {
|
|
36
|
+
end: exact,
|
|
37
|
+
strict,
|
|
38
|
+
sensitive,
|
|
39
|
+
});
|
|
40
|
+
const match = regexp.exec(pathname);
|
|
41
|
+
if (!match)
|
|
42
|
+
return null;
|
|
43
|
+
const [url, ...values] = match;
|
|
44
|
+
const isExact = pathname === url;
|
|
45
|
+
if (exact && !isExact)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
path,
|
|
49
|
+
url: path === '/' && url === '' ? '/' : url,
|
|
50
|
+
isExact,
|
|
51
|
+
params: keys.reduce((memo, key, index) => {
|
|
52
|
+
memo[key.name] = values[index];
|
|
53
|
+
return memo;
|
|
54
|
+
}, {}),
|
|
55
|
+
};
|
|
56
|
+
}, null);
|
|
57
|
+
}
|
|
58
|
+
exports.matchPath = matchPath;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Not all environments have access to Performance.now(). This is to prevent
|
|
3
|
+
* timing side channel attacks.
|
|
4
|
+
*
|
|
5
|
+
* See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
|
|
6
|
+
*/
|
|
7
|
+
export declare function getTime(): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTime = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Not all environments have access to Performance.now(). This is to prevent
|
|
6
|
+
* timing side channel attacks.
|
|
7
|
+
*
|
|
8
|
+
* See: https://community.cloudflare.com/t/cloudflare-workers-how-do-i-measure-execution-time-of-my-method/69672
|
|
9
|
+
*/
|
|
10
|
+
function getTime() {
|
|
11
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
12
|
+
return performance.now();
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return Date.now();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.getTime = getTime;
|
package/dist/node/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "0.
|
|
1
|
+
export declare const LIB_VERSION = "0.9.0";
|
package/dist/node/version.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getTime } from '../../utilities/timing';
|
|
1
2
|
/**
|
|
2
3
|
* This augments the `Request` object from the Fetch API:
|
|
3
4
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Request
|
|
@@ -14,8 +15,12 @@ export class ServerComponentRequest extends Request {
|
|
|
14
15
|
super(getUrlFromNodeRequest(input), {
|
|
15
16
|
headers: new Headers(input.headers),
|
|
16
17
|
method: input.method,
|
|
18
|
+
body: input.method !== 'GET' && input.method !== 'HEAD'
|
|
19
|
+
? input.body
|
|
20
|
+
: undefined,
|
|
17
21
|
});
|
|
18
22
|
}
|
|
23
|
+
this.time = getTime();
|
|
19
24
|
this.cookies = this.parseCookies();
|
|
20
25
|
}
|
|
21
26
|
parseCookies() {
|
|
@@ -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,13 +22,23 @@ 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
|
}
|
|
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
|
+
}
|
|
40
|
+
const userAgent = request.headers.get('user-agent');
|
|
41
|
+
const isStreamable = streamableResponse && !isBotUA(url, userAgent);
|
|
31
42
|
/**
|
|
32
43
|
* Stream back real-user responses, but for bots/etc,
|
|
33
44
|
* use `render` instead. This is because we need to inject <head>
|
|
@@ -35,7 +46,12 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
|
|
|
35
46
|
*/
|
|
36
47
|
if (isStreamable) {
|
|
37
48
|
if (isReactHydrationRequest) {
|
|
38
|
-
hydrate(url, {
|
|
49
|
+
hydrate(url, {
|
|
50
|
+
context: {},
|
|
51
|
+
request,
|
|
52
|
+
response: streamableResponse,
|
|
53
|
+
dev,
|
|
54
|
+
});
|
|
39
55
|
}
|
|
40
56
|
else {
|
|
41
57
|
stream(url, {
|
|
@@ -48,7 +64,12 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
|
|
|
48
64
|
}
|
|
49
65
|
return;
|
|
50
66
|
}
|
|
51
|
-
const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, {
|
|
67
|
+
const { body, bodyAttributes, htmlAttributes, componentResponse, ...head } = await render(url, {
|
|
68
|
+
request,
|
|
69
|
+
context: {},
|
|
70
|
+
isReactHydrationRequest,
|
|
71
|
+
dev,
|
|
72
|
+
});
|
|
52
73
|
const headers = componentResponse.headers;
|
|
53
74
|
/**
|
|
54
75
|
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
@@ -86,13 +107,6 @@ export default async function handleEvent(event, { request, entrypoint, indexTem
|
|
|
86
107
|
}
|
|
87
108
|
return response;
|
|
88
109
|
}
|
|
89
|
-
function isStreamableRequest(url) {
|
|
90
|
-
/**
|
|
91
|
-
* TODO: Add UA detection.
|
|
92
|
-
*/
|
|
93
|
-
const isBot = url.searchParams.has('_bot');
|
|
94
|
-
return !isBot;
|
|
95
|
-
}
|
|
96
110
|
/**
|
|
97
111
|
* Generate the contents of the `head` tag, and update the existing `<title>` tag
|
|
98
112
|
* if one exists, and if a title is passed.
|
|
@@ -117,3 +131,57 @@ function generateHeadTag(head) {
|
|
|
117
131
|
return `<head>${headHtml}</head>`;
|
|
118
132
|
};
|
|
119
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Determines if the request is from a bot, using the URL and User Agent
|
|
136
|
+
*/
|
|
137
|
+
function isBotUA(url, userAgent) {
|
|
138
|
+
return (url.searchParams.has('_bot') || (!!userAgent && botUARegex.test(userAgent)));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* An alphabetized list of User Agents of known bots, combined from lists found at:
|
|
142
|
+
* https://github.com/vercel/next.js/blob/d87dc2b5a0b3fdbc0f6806a47be72bad59564bd0/packages/next/server/utils.ts#L18-L22
|
|
143
|
+
* https://github.com/GoogleChrome/rendertron/blob/6f681688737846b28754fbfdf5db173846a826df/middleware/src/middleware.ts#L24-L41
|
|
144
|
+
*/
|
|
145
|
+
const botUserAgents = [
|
|
146
|
+
'AdsBot-Google',
|
|
147
|
+
'applebot',
|
|
148
|
+
'Baiduspider',
|
|
149
|
+
'baiduspider',
|
|
150
|
+
'bingbot',
|
|
151
|
+
'Bingbot',
|
|
152
|
+
'BingPreview',
|
|
153
|
+
'bitlybot',
|
|
154
|
+
'Discordbot',
|
|
155
|
+
'DuckDuckBot',
|
|
156
|
+
'Embedly',
|
|
157
|
+
'facebookcatalog',
|
|
158
|
+
'facebookexternalhit',
|
|
159
|
+
'Google-PageRenderer',
|
|
160
|
+
'Googlebot',
|
|
161
|
+
'googleweblight',
|
|
162
|
+
'ia_archive',
|
|
163
|
+
'LinkedInBot',
|
|
164
|
+
'Mediapartners-Google',
|
|
165
|
+
'outbrain',
|
|
166
|
+
'pinterest',
|
|
167
|
+
'quora link preview',
|
|
168
|
+
'redditbot',
|
|
169
|
+
'rogerbot',
|
|
170
|
+
'showyoubot',
|
|
171
|
+
'SkypeUriPreview',
|
|
172
|
+
'Slackbot',
|
|
173
|
+
'Slurp',
|
|
174
|
+
'sogou',
|
|
175
|
+
'Storebot-Google',
|
|
176
|
+
'TelegramBot',
|
|
177
|
+
'tumblr',
|
|
178
|
+
'Twitterbot',
|
|
179
|
+
'vkShare',
|
|
180
|
+
'W3C_Validator',
|
|
181
|
+
'WhatsApp',
|
|
182
|
+
'yandex',
|
|
183
|
+
];
|
|
184
|
+
/**
|
|
185
|
+
* Creates a regex based on the botUserAgents array
|
|
186
|
+
*/
|
|
187
|
+
const botUARegex = new RegExp(botUserAgents.join('|'), 'i');
|
package/dist/worker/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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ServerComponentRequest } from '../../framework/Hydration/ServerComponentRequest.server';
|
|
2
|
+
/** A utility for logging debugging, warning, and error information about the application.
|
|
3
|
+
* Use by importing `log` `@shopify/hydrogen` or by using a `log` prop passed to each page
|
|
4
|
+
* component. Using the latter is ideal, because it will ty your log to the current request in progress.
|
|
5
|
+
*/
|
|
6
|
+
export interface Logger {
|
|
7
|
+
trace: (...args: Array<any>) => void;
|
|
8
|
+
debug: (...args: Array<any>) => void;
|
|
9
|
+
warn: (...args: Array<any>) => void;
|
|
10
|
+
error: (...args: Array<any>) => void;
|
|
11
|
+
fatal: (...args: Array<any>) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function getLoggerFromContext(context: any): Logger;
|
|
14
|
+
export declare function setLogger(newLogger: Logger): void;
|
|
15
|
+
export declare function resetLogger(): void;
|
|
16
|
+
export declare const log: Logger;
|
|
17
|
+
export declare function logServerResponse(type: 'str' | 'rsc' | 'ssr' | 'api', log: Logger, request: ServerComponentRequest, responseStatus: number): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { yellow, red, green, italic, lightBlue } from 'kolorist';
|
|
2
|
+
import { getTime } from '../timing';
|
|
3
|
+
const defaultLogger = {
|
|
4
|
+
trace(context, ...args) {
|
|
5
|
+
console.log(...args);
|
|
6
|
+
},
|
|
7
|
+
debug(context, ...args) {
|
|
8
|
+
console.log(...args);
|
|
9
|
+
},
|
|
10
|
+
warn(context, ...args) {
|
|
11
|
+
console.warn(yellow('WARN: '), ...args);
|
|
12
|
+
},
|
|
13
|
+
error(context, ...args) {
|
|
14
|
+
console.error(red('ERROR: '), ...args);
|
|
15
|
+
},
|
|
16
|
+
fatal(context, ...args) {
|
|
17
|
+
console.error(red('FATAL: '), ...args);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
let logger = defaultLogger;
|
|
21
|
+
export function getLoggerFromContext(context) {
|
|
22
|
+
return {
|
|
23
|
+
trace: (...args) => logger.trace(context, ...args),
|
|
24
|
+
debug: (...args) => logger.debug(context, ...args),
|
|
25
|
+
warn: (...args) => logger.warn(context, ...args),
|
|
26
|
+
error: (...args) => logger.error(context, ...args),
|
|
27
|
+
fatal: (...args) => logger.fatal(context, ...args),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function setLogger(newLogger) {
|
|
31
|
+
logger = newLogger;
|
|
32
|
+
}
|
|
33
|
+
export function resetLogger() {
|
|
34
|
+
logger = defaultLogger;
|
|
35
|
+
}
|
|
36
|
+
export const log = {
|
|
37
|
+
trace(...args) {
|
|
38
|
+
return logger.trace({}, ...args);
|
|
39
|
+
},
|
|
40
|
+
debug(...args) {
|
|
41
|
+
return logger.debug({}, ...args);
|
|
42
|
+
},
|
|
43
|
+
warn(...args) {
|
|
44
|
+
return logger.warn({}, ...args);
|
|
45
|
+
},
|
|
46
|
+
error(...args) {
|
|
47
|
+
return logger.error({}, ...args);
|
|
48
|
+
},
|
|
49
|
+
fatal(...args) {
|
|
50
|
+
return logger.fatal({}, ...args);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const SERVER_RESPONSE_MAP = {
|
|
54
|
+
str: 'streaming SSR',
|
|
55
|
+
rsc: 'server Components',
|
|
56
|
+
ssr: 'buffered SSR',
|
|
57
|
+
};
|
|
58
|
+
export function logServerResponse(type, log, request, responseStatus) {
|
|
59
|
+
const coloredResponseStatus = responseStatus >= 500
|
|
60
|
+
? red(responseStatus)
|
|
61
|
+
: responseStatus >= 400
|
|
62
|
+
? yellow(responseStatus)
|
|
63
|
+
: responseStatus >= 300
|
|
64
|
+
? lightBlue(responseStatus)
|
|
65
|
+
: green(responseStatus);
|
|
66
|
+
const fullType = SERVER_RESPONSE_MAP[type] || type;
|
|
67
|
+
const styledType = italic(pad(fullType, ' '));
|
|
68
|
+
const paddedTiming = pad((getTime() - request.time).toFixed(2) + ' ms', ' ');
|
|
69
|
+
const url = type === 'rsc'
|
|
70
|
+
? decodeURIComponent(request.url.substring(request.url.indexOf('=') + 1))
|
|
71
|
+
: request.url;
|
|
72
|
+
log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${paddedTiming} ${url}`);
|
|
73
|
+
}
|
|
74
|
+
function pad(str, _pad) {
|
|
75
|
+
return (str + _pad).substring(0, _pad.length);
|
|
76
|
+
}
|
|
@@ -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 {};
|