@shopify/hydrogen 0.17.1 → 0.17.2
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 +15 -1
- package/dist/esnext/components/ModelViewer/ModelViewer.client.d.ts +1 -1
- package/dist/esnext/components/ModelViewer/ModelViewer.client.js +1 -1
- package/dist/esnext/entry-server.js +6 -8
- package/dist/esnext/foundation/FileRoutes/FileRoutes.server.js +2 -18
- package/dist/esnext/foundation/Router/BrowserRouter.client.js +25 -5
- package/dist/esnext/framework/plugins/vite-plugin-platform-entry.js +2 -2
- package/dist/esnext/utilities/apiRoutes.d.ts +1 -0
- package/dist/esnext/utilities/apiRoutes.js +23 -18
- package/dist/esnext/utilities/bot-ua.js +3 -0
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/entry-server.js +6 -8
- package/dist/node/foundation/Router/BrowserRouter.client.js +25 -5
- package/dist/node/framework/plugins/vite-plugin-platform-entry.js +2 -2
- package/dist/node/utilities/apiRoutes.d.ts +1 -0
- package/dist/node/utilities/apiRoutes.js +25 -19
- package/dist/node/utilities/bot-ua.js +3 -0
- package/dist/node/version.d.ts +1 -1
- package/dist/node/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1161](https://github.com/Shopify/hydrogen/pull/1161) [`6b963fb1`](https://github.com/Shopify/hydrogen/commit/6b963fb1fdd2824683870c8ff3258447bf7fedea) Thanks [@merwan7](https://github.com/merwan7)! - Adds ability to add multiple cookies in one response
|
|
8
|
+
|
|
9
|
+
* [#1162](https://github.com/Shopify/hydrogen/pull/1162) [`5446d544`](https://github.com/Shopify/hydrogen/commit/5446d544f151e233e909e6a6f002e87863ae6151) Thanks [@arlyxiao](https://github.com/arlyxiao)! - Upgrade body-parser
|
|
10
|
+
|
|
11
|
+
- [#1200](https://github.com/Shopify/hydrogen/pull/1200) [`7fb7ee49`](https://github.com/Shopify/hydrogen/commit/7fb7ee497091df3177d53e8745edcae6ba99a87d) Thanks [@blittle](https://github.com/blittle)! - Add bot user agents for Seoradar and Adresults, resolves #1199
|
|
12
|
+
|
|
13
|
+
* [#1167](https://github.com/Shopify/hydrogen/pull/1167) [`0a5ac1cb`](https://github.com/Shopify/hydrogen/commit/0a5ac1cbec449eefe48041ed6aceaac375dfa601) Thanks [@benjaminsehl](https://github.com/benjaminsehl)! - Only warn in console on missing Model3D alt tag, do not throw error
|
|
14
|
+
|
|
15
|
+
- [#1152](https://github.com/Shopify/hydrogen/pull/1152) [`d3e3e695`](https://github.com/Shopify/hydrogen/commit/d3e3e695457e6eb2a3ebf9767e0f10cc3570e880) Thanks [@jplhomer](https://github.com/jplhomer)! - Fix scroll restoration when server props are changed
|
|
16
|
+
|
|
3
17
|
## 0.17.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -343,7 +357,7 @@
|
|
|
343
357
|
}, []);
|
|
344
358
|
```
|
|
345
359
|
|
|
346
|
-
See an example on how this could be done inside the Demo Store template [country selector](https://github.com/Shopify/hydrogen/blob/v1.x-2022-07/
|
|
360
|
+
See an example on how this could be done inside the Demo Store template [country selector](https://github.com/Shopify/hydrogen/blob/v1.x-2022-07/templates/template-hydrogen-default/src/components/CountrySelector.client.jsx)
|
|
347
361
|
|
|
348
362
|
- [#698](https://github.com/Shopify/hydrogen/pull/698) [`6f30b9a1`](https://github.com/Shopify/hydrogen/commit/6f30b9a1327f06d648a01dd94d539c7dcb3061e0) Thanks [@jplhomer](https://github.com/jplhomer)! - Basic end-to-end tests have been added to the default Hydrogen template. You can run tests in development:
|
|
349
363
|
|
|
@@ -112,7 +112,7 @@ interface ModelViewerProps {
|
|
|
112
112
|
/** The callback to invoke when the 'scene-graph-ready' event is triggered. Refer to [scene-graph-ready in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-scenegraph-events-sceneGraphReady). */
|
|
113
113
|
onSceneGraphReady?: (event: Event) => void;
|
|
114
114
|
}
|
|
115
|
-
declare type PropsWeControl = 'src' | '
|
|
115
|
+
declare type PropsWeControl = 'src' | 'poster';
|
|
116
116
|
declare global {
|
|
117
117
|
namespace JSX {
|
|
118
118
|
interface IntrinsicElements {
|
|
@@ -99,7 +99,7 @@ export function ModelViewer(props) {
|
|
|
99
99
|
throw new Error(`<ModelViewer/> requires 'data.sources' prop to be an array, with an object that has a property 'url' on it`);
|
|
100
100
|
}
|
|
101
101
|
if (!data.alt) {
|
|
102
|
-
|
|
102
|
+
console.warn(`<ModelViewer/> requires the 'data.alt' prop for accessibility`);
|
|
103
103
|
}
|
|
104
104
|
return (React.createElement("model-viewer", { ref: callbackRef, ...passthroughProps, class: className, id: id, src: data.sources[0].url, alt: data.alt, "camera-controls": (_c = passthroughProps.cameraControls) !== null && _c !== void 0 ? _c : true, poster: passthroughProps.poster || ((_d = data.previewImage) === null || _d === void 0 ? void 0 : _d.url), autoplay: (_e = passthroughProps.autoplay) !== null && _e !== void 0 ? _e : true, loading: passthroughProps.loading, reveal: passthroughProps.reveal, ar: passthroughProps.ar, "ar-modes": passthroughProps.arModes, "ar-scale": passthroughProps.arScale, "ar-placement": passthroughProps.arPlacement, "ios-src": passthroughProps.iosSrc, "touch-action": passthroughProps.touchAction, "disable-zoom": passthroughProps.disableZoom, "orbit-sensitivity": passthroughProps.orbitSensitivity, "auto-rotate": passthroughProps.autoRotate, "auto-rotate-delay": passthroughProps.autoRotateDelay, "rotation-per-second": passthroughProps.rotationPerSecond, "interaction-policy": passthroughProps.interactionPolicy, "interaction-prompt": passthroughProps.interactionPrompt, "interaction-prompt-style": passthroughProps.interactionPromptStyle, "interaction-prompt-threshold": passthroughProps.interactionPromptThreshold, "camera-orbit": passthroughProps.cameraOrbit, "camera-target": passthroughProps.cameraTarget, "field-of-view": passthroughProps.fieldOfView, "max-camera-orbit": passthroughProps.maxCameraOrbit, "min-camera-orbit": passthroughProps.minCameraOrbit, "max-field-of-view": passthroughProps.maxFieldOfView, "min-field-of-view": passthroughProps.minFieldOfView, bounds: passthroughProps.bounds, "interpolation-decay": (_f = passthroughProps.interpolationDecay) !== null && _f !== void 0 ? _f : 100, "skybox-image": passthroughProps.skyboxImage, "environment-image": passthroughProps.environmentImage, exposure: passthroughProps.exposure, "shadow-intensity": (_g = passthroughProps.shadowIntensity) !== null && _g !== void 0 ? _g : 0, "shadow-softness": (_h = passthroughProps.shadowSoftness) !== null && _h !== void 0 ? _h : 0, "animation-name": passthroughProps.animationName, "animation-crossfade-duration": passthroughProps.animationCrossfadeDuration, "variant-name": passthroughProps.variantName, orientation: passthroughProps.orientation, scale: passthroughProps.scale }, children));
|
|
105
105
|
}
|
|
@@ -121,7 +121,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
121
121
|
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
122
122
|
* we want to shard our full-page cache for all Hydrogen storefronts.
|
|
123
123
|
*/
|
|
124
|
-
headers
|
|
124
|
+
headers.set('cache-control', componentResponse.cacheControlHeader);
|
|
125
125
|
if (componentResponse.customBody) {
|
|
126
126
|
// This can be used to return sitemap.xml or any other custom response.
|
|
127
127
|
postRequestTasks('ssr', status, request, componentResponse);
|
|
@@ -131,7 +131,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
131
131
|
headers,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
-
headers
|
|
134
|
+
headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE);
|
|
135
135
|
html = applyHtmlHead(html, request.ctx.head, template);
|
|
136
136
|
if (flight) {
|
|
137
137
|
html = html.replace('</body>', () => `${flightContainer({
|
|
@@ -220,8 +220,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
220
220
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
221
221
|
* additional checks downstream?
|
|
222
222
|
*/
|
|
223
|
-
responseOptions.headers
|
|
224
|
-
componentResponse.cacheControlHeader;
|
|
223
|
+
responseOptions.headers.set('cache-control', componentResponse.cacheControlHeader);
|
|
225
224
|
if (isRedirect(responseOptions)) {
|
|
226
225
|
return false;
|
|
227
226
|
}
|
|
@@ -230,7 +229,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
230
229
|
writable.write(encoder.encode(await componentResponse.customBody));
|
|
231
230
|
return false;
|
|
232
231
|
}
|
|
233
|
-
responseOptions.headers
|
|
232
|
+
responseOptions.headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE);
|
|
234
233
|
writable.write(encoder.encode(DOCTYPE));
|
|
235
234
|
if (didError) {
|
|
236
235
|
// This error was delayed until the headers were properly sent.
|
|
@@ -487,8 +486,7 @@ function startWritingHtmlToServerResponse(response, error) {
|
|
|
487
486
|
function getResponseOptions({ headers, status, customStatus }, error) {
|
|
488
487
|
var _a, _b;
|
|
489
488
|
const responseInit = {};
|
|
490
|
-
|
|
491
|
-
responseInit.headers = Object.fromEntries(headers.entries());
|
|
489
|
+
responseInit.headers = headers;
|
|
492
490
|
if (error) {
|
|
493
491
|
responseInit.status = 500;
|
|
494
492
|
}
|
|
@@ -509,7 +507,7 @@ function writeHeadToServerResponse(response, serverComponentResponse, log, error
|
|
|
509
507
|
if (statusText) {
|
|
510
508
|
response.statusMessage = statusText;
|
|
511
509
|
}
|
|
512
|
-
Object.entries(headers).forEach(([key, value]) => response.setHeader(key, value));
|
|
510
|
+
Object.entries(headers.raw()).forEach(([key, value]) => response.setHeader(key, value));
|
|
513
511
|
}
|
|
514
512
|
function isRedirect(response) {
|
|
515
513
|
var _a, _b;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { matchPath } from '../../utilities/matchPath';
|
|
3
3
|
import { log } from '../../utilities/log';
|
|
4
|
+
import { extractPathFromRoutesKey } from '../../utilities/apiRoutes';
|
|
4
5
|
import { useServerRequest } from '../ServerRequestProvider';
|
|
5
6
|
import { RouteParamsProvider } from '../useRouteParams/RouteParamsProvider.client';
|
|
6
7
|
/**
|
|
@@ -34,24 +35,7 @@ export function createPageRoutes(pages, topLevelPath = '*', dirPrefix) {
|
|
|
34
35
|
const topLevelPrefix = topLevelPath.replace('*', '').replace(/\/$/, '');
|
|
35
36
|
const routes = Object.keys(pages)
|
|
36
37
|
.map((key) => {
|
|
37
|
-
|
|
38
|
-
.replace(dirPrefix, '')
|
|
39
|
-
.replace(/\.server\.(t|j)sx?$/, '')
|
|
40
|
-
/**
|
|
41
|
-
* Replace /index with /
|
|
42
|
-
*/
|
|
43
|
-
.replace(/\/index$/i, '/')
|
|
44
|
-
/**
|
|
45
|
-
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
46
|
-
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
47
|
-
*/
|
|
48
|
-
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
49
|
-
/**
|
|
50
|
-
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
51
|
-
*/
|
|
52
|
-
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
53
|
-
if (path.endsWith('/') && path !== '/')
|
|
54
|
-
path = path.substring(0, path.length - 1);
|
|
38
|
+
const path = extractPathFromRoutesKey(key, dirPrefix);
|
|
55
39
|
/**
|
|
56
40
|
* Catch-all routes [...handle].jsx don't need an exact match
|
|
57
41
|
* https://reactrouter.com/core/api/Route/exact-bool
|
|
@@ -10,8 +10,15 @@ export const BrowserRouter = ({ history: pHistory, children, }) => {
|
|
|
10
10
|
return React.createElement(React.Fragment, null, children);
|
|
11
11
|
const history = useMemo(() => pHistory || createBrowserHistory(), [pHistory]);
|
|
12
12
|
const [location, setLocation] = useState(history.location);
|
|
13
|
+
const [locationChanged, setLocationChanged] = useState(false);
|
|
13
14
|
const { pending, locationServerProps, setLocationServerProps } = useInternalServerProps();
|
|
14
|
-
useScrollRestoration(
|
|
15
|
+
useScrollRestoration({
|
|
16
|
+
location,
|
|
17
|
+
pending,
|
|
18
|
+
serverProps: locationServerProps,
|
|
19
|
+
locationChanged,
|
|
20
|
+
onFinishNavigating: () => setLocationChanged(false),
|
|
21
|
+
});
|
|
15
22
|
useLayoutEffect(() => {
|
|
16
23
|
const unlisten = history.listen(({ location: newLocation }) => {
|
|
17
24
|
positions[location.key] = window.scrollY;
|
|
@@ -20,9 +27,10 @@ export const BrowserRouter = ({ history: pHistory, children, }) => {
|
|
|
20
27
|
search: newLocation.search,
|
|
21
28
|
});
|
|
22
29
|
setLocation(newLocation);
|
|
30
|
+
setLocationChanged(true);
|
|
23
31
|
});
|
|
24
32
|
return () => unlisten();
|
|
25
|
-
}, [history, location]);
|
|
33
|
+
}, [history, location, setLocationChanged]);
|
|
26
34
|
return (React.createElement(RouterContext.Provider, { value: {
|
|
27
35
|
history,
|
|
28
36
|
location,
|
|
@@ -49,7 +57,7 @@ function useBeforeUnload(callback) {
|
|
|
49
57
|
};
|
|
50
58
|
}, [callback]);
|
|
51
59
|
}
|
|
52
|
-
function useScrollRestoration(location, pending, serverProps) {
|
|
60
|
+
function useScrollRestoration({ location, pending, serverProps, locationChanged, onFinishNavigating, }) {
|
|
53
61
|
/**
|
|
54
62
|
* Browsers have an API for scroll restoration. We wait for the page to load first,
|
|
55
63
|
* in case the browser is able to restore scroll position automatically, and then
|
|
@@ -66,7 +74,7 @@ function useScrollRestoration(location, pending, serverProps) {
|
|
|
66
74
|
}, []));
|
|
67
75
|
useLayoutEffect(() => {
|
|
68
76
|
// The app has just loaded
|
|
69
|
-
if (isFirstLoad) {
|
|
77
|
+
if (isFirstLoad || !locationChanged) {
|
|
70
78
|
isFirstLoad = false;
|
|
71
79
|
return;
|
|
72
80
|
}
|
|
@@ -87,15 +95,27 @@ function useScrollRestoration(location, pending, serverProps) {
|
|
|
87
95
|
const element = document.querySelector(location.hash);
|
|
88
96
|
if (element) {
|
|
89
97
|
element.scrollIntoView();
|
|
98
|
+
onFinishNavigating();
|
|
90
99
|
return;
|
|
91
100
|
}
|
|
92
101
|
}
|
|
93
102
|
// If we have a matching position, scroll to it
|
|
94
103
|
if (position) {
|
|
95
104
|
window.scrollTo(0, position);
|
|
105
|
+
onFinishNavigating();
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
98
108
|
// Scroll to the top of new pages
|
|
99
109
|
window.scrollTo(0, 0);
|
|
100
|
-
|
|
110
|
+
onFinishNavigating();
|
|
111
|
+
}, [
|
|
112
|
+
location.pathname,
|
|
113
|
+
location.search,
|
|
114
|
+
location.hash,
|
|
115
|
+
pending,
|
|
116
|
+
serverProps.pathname,
|
|
117
|
+
serverProps.search,
|
|
118
|
+
locationChanged,
|
|
119
|
+
onFinishNavigating,
|
|
120
|
+
]);
|
|
101
121
|
}
|
|
@@ -19,7 +19,7 @@ export default () => {
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
resolveId(source, importer) {
|
|
22
|
-
if (normalizePath(source).includes('
|
|
22
|
+
if (normalizePath(source).includes('/hydrogen/platforms/')) {
|
|
23
23
|
const hydrogenPath = path.dirname(require.resolve('@shopify/hydrogen/package.json'));
|
|
24
24
|
const platformEntryName = source.split(path.sep).pop() || '';
|
|
25
25
|
const platformEntryPath = path.resolve(hydrogenPath, 'dist', 'esnext', 'platforms', platformEntryName);
|
|
@@ -30,7 +30,7 @@ export default () => {
|
|
|
30
30
|
return null;
|
|
31
31
|
},
|
|
32
32
|
transform(code, id) {
|
|
33
|
-
if (normalizePath(id).includes('
|
|
33
|
+
if (normalizePath(id).includes('/hydrogen/dist/esnext/platforms/')) {
|
|
34
34
|
code = code
|
|
35
35
|
.replace('__SERVER_ENTRY__', process.env.HYDROGEN_SERVER_ENTRY || HYDROGEN_DEFAULT_SERVER_ENTRY)
|
|
36
36
|
.replace('__INDEX_TEMPLATE__', normalizePath(path.resolve(config.root, config.build.outDir, '..', 'client', 'index.html')));
|
|
@@ -19,6 +19,7 @@ export declare type ApiRouteMatch = {
|
|
|
19
19
|
hasServerComponent: boolean;
|
|
20
20
|
params: RouteParams;
|
|
21
21
|
};
|
|
22
|
+
export declare function extractPathFromRoutesKey(routesKey: string, dirPrefix: string): string;
|
|
22
23
|
export declare function getApiRoutes(pages: ImportGlobEagerOutput | undefined, topLevelPath?: string): Array<HydrogenApiRoute>;
|
|
23
24
|
export declare function getApiRouteFromURL(url: URL, routes: Array<HydrogenApiRoute>): ApiRouteMatch | null;
|
|
24
25
|
/** The `queryShop` utility is a function that helps you query the Storefront API.
|
|
@@ -5,6 +5,28 @@ import { getStorefrontApiRequestHeaders } from './storefrontApi';
|
|
|
5
5
|
import { emptySessionImplementation, } from '../foundation/session/session';
|
|
6
6
|
let memoizedRoutes = [];
|
|
7
7
|
let memoizedPages = {};
|
|
8
|
+
export function extractPathFromRoutesKey(routesKey, dirPrefix) {
|
|
9
|
+
let path = routesKey
|
|
10
|
+
.replace(dirPrefix, '')
|
|
11
|
+
.replace(/\.server\.(t|j)sx?$/, '')
|
|
12
|
+
/**
|
|
13
|
+
* Replace /index with /
|
|
14
|
+
*/
|
|
15
|
+
.replace(/\/index$/i, '/')
|
|
16
|
+
/**
|
|
17
|
+
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
18
|
+
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
19
|
+
*/
|
|
20
|
+
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
21
|
+
/**
|
|
22
|
+
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
23
|
+
*/
|
|
24
|
+
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
25
|
+
if (path.endsWith('/') && path !== '/') {
|
|
26
|
+
path = path.substring(0, path.length - 1);
|
|
27
|
+
}
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
8
30
|
export function getApiRoutes(pages, topLevelPath = '*') {
|
|
9
31
|
if (!pages || memoizedPages === pages)
|
|
10
32
|
return memoizedRoutes;
|
|
@@ -12,24 +34,7 @@ export function getApiRoutes(pages, topLevelPath = '*') {
|
|
|
12
34
|
const routes = Object.keys(pages)
|
|
13
35
|
.filter((key) => pages[key].api)
|
|
14
36
|
.map((key) => {
|
|
15
|
-
|
|
16
|
-
.replace('./routes', '')
|
|
17
|
-
.replace(/\.server\.(t|j)sx?$/, '')
|
|
18
|
-
/**
|
|
19
|
-
* Replace /index with /
|
|
20
|
-
*/
|
|
21
|
-
.replace(/\/index$/i, '/')
|
|
22
|
-
/**
|
|
23
|
-
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
24
|
-
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
25
|
-
*/
|
|
26
|
-
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
27
|
-
/**
|
|
28
|
-
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
29
|
-
*/
|
|
30
|
-
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
31
|
-
if (path.endsWith('/') && path !== '/')
|
|
32
|
-
path = path.substring(0, path.length - 1);
|
|
37
|
+
const path = extractPathFromRoutesKey(key, './routes');
|
|
33
38
|
/**
|
|
34
39
|
* Catch-all routes [...handle].jsx don't need an exact match
|
|
35
40
|
* https://reactrouter.com/core/api/Route/exact-bool
|
package/dist/esnext/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "0.17.
|
|
1
|
+
export declare const LIB_VERSION = "0.17.2";
|
package/dist/esnext/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LIB_VERSION = '0.17.
|
|
1
|
+
export const LIB_VERSION = '0.17.2';
|
|
@@ -148,7 +148,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
148
148
|
* TODO: Also add `Vary` headers for `accept-language` and any other keys
|
|
149
149
|
* we want to shard our full-page cache for all Hydrogen storefronts.
|
|
150
150
|
*/
|
|
151
|
-
headers
|
|
151
|
+
headers.set('cache-control', componentResponse.cacheControlHeader);
|
|
152
152
|
if (componentResponse.customBody) {
|
|
153
153
|
// This can be used to return sitemap.xml or any other custom response.
|
|
154
154
|
postRequestTasks('ssr', status, request, componentResponse);
|
|
@@ -158,7 +158,7 @@ async function render(url, { App, routes, request, componentResponse, log, templ
|
|
|
158
158
|
headers,
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
-
headers
|
|
161
|
+
headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE);
|
|
162
162
|
html = (0, Html_1.applyHtmlHead)(html, request.ctx.head, template);
|
|
163
163
|
if (flight) {
|
|
164
164
|
html = html.replace('</body>', () => `${flightContainer({
|
|
@@ -247,8 +247,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
247
247
|
* queries which might be caught behind Suspense. Clarify this or add
|
|
248
248
|
* additional checks downstream?
|
|
249
249
|
*/
|
|
250
|
-
responseOptions.headers
|
|
251
|
-
componentResponse.cacheControlHeader;
|
|
250
|
+
responseOptions.headers.set('cache-control', componentResponse.cacheControlHeader);
|
|
252
251
|
if (isRedirect(responseOptions)) {
|
|
253
252
|
return false;
|
|
254
253
|
}
|
|
@@ -257,7 +256,7 @@ async function stream(url, { App, routes, request, response, componentResponse,
|
|
|
257
256
|
writable.write(encoder.encode(await componentResponse.customBody));
|
|
258
257
|
return false;
|
|
259
258
|
}
|
|
260
|
-
responseOptions.headers
|
|
259
|
+
responseOptions.headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE);
|
|
261
260
|
writable.write(encoder.encode(DOCTYPE));
|
|
262
261
|
if (didError) {
|
|
263
262
|
// This error was delayed until the headers were properly sent.
|
|
@@ -514,8 +513,7 @@ function startWritingHtmlToServerResponse(response, error) {
|
|
|
514
513
|
function getResponseOptions({ headers, status, customStatus }, error) {
|
|
515
514
|
var _a, _b;
|
|
516
515
|
const responseInit = {};
|
|
517
|
-
|
|
518
|
-
responseInit.headers = Object.fromEntries(headers.entries());
|
|
516
|
+
responseInit.headers = headers;
|
|
519
517
|
if (error) {
|
|
520
518
|
responseInit.status = 500;
|
|
521
519
|
}
|
|
@@ -536,7 +534,7 @@ function writeHeadToServerResponse(response, serverComponentResponse, log, error
|
|
|
536
534
|
if (statusText) {
|
|
537
535
|
response.statusMessage = statusText;
|
|
538
536
|
}
|
|
539
|
-
Object.entries(headers).forEach(([key, value]) => response.setHeader(key, value));
|
|
537
|
+
Object.entries(headers.raw()).forEach(([key, value]) => response.setHeader(key, value));
|
|
540
538
|
}
|
|
541
539
|
function isRedirect(response) {
|
|
542
540
|
var _a, _b;
|
|
@@ -36,8 +36,15 @@ const BrowserRouter = ({ history: pHistory, children, }) => {
|
|
|
36
36
|
return react_1.default.createElement(react_1.default.Fragment, null, children);
|
|
37
37
|
const history = (0, react_1.useMemo)(() => pHistory || (0, history_1.createBrowserHistory)(), [pHistory]);
|
|
38
38
|
const [location, setLocation] = (0, react_1.useState)(history.location);
|
|
39
|
+
const [locationChanged, setLocationChanged] = (0, react_1.useState)(false);
|
|
39
40
|
const { pending, locationServerProps, setLocationServerProps } = (0, use_server_props_1.useInternalServerProps)();
|
|
40
|
-
useScrollRestoration(
|
|
41
|
+
useScrollRestoration({
|
|
42
|
+
location,
|
|
43
|
+
pending,
|
|
44
|
+
serverProps: locationServerProps,
|
|
45
|
+
locationChanged,
|
|
46
|
+
onFinishNavigating: () => setLocationChanged(false),
|
|
47
|
+
});
|
|
41
48
|
(0, react_1.useLayoutEffect)(() => {
|
|
42
49
|
const unlisten = history.listen(({ location: newLocation }) => {
|
|
43
50
|
positions[location.key] = window.scrollY;
|
|
@@ -46,9 +53,10 @@ const BrowserRouter = ({ history: pHistory, children, }) => {
|
|
|
46
53
|
search: newLocation.search,
|
|
47
54
|
});
|
|
48
55
|
setLocation(newLocation);
|
|
56
|
+
setLocationChanged(true);
|
|
49
57
|
});
|
|
50
58
|
return () => unlisten();
|
|
51
|
-
}, [history, location]);
|
|
59
|
+
}, [history, location, setLocationChanged]);
|
|
52
60
|
return (react_1.default.createElement(exports.RouterContext.Provider, { value: {
|
|
53
61
|
history,
|
|
54
62
|
location,
|
|
@@ -78,7 +86,7 @@ function useBeforeUnload(callback) {
|
|
|
78
86
|
};
|
|
79
87
|
}, [callback]);
|
|
80
88
|
}
|
|
81
|
-
function useScrollRestoration(location, pending, serverProps) {
|
|
89
|
+
function useScrollRestoration({ location, pending, serverProps, locationChanged, onFinishNavigating, }) {
|
|
82
90
|
/**
|
|
83
91
|
* Browsers have an API for scroll restoration. We wait for the page to load first,
|
|
84
92
|
* in case the browser is able to restore scroll position automatically, and then
|
|
@@ -95,7 +103,7 @@ function useScrollRestoration(location, pending, serverProps) {
|
|
|
95
103
|
}, []));
|
|
96
104
|
(0, react_1.useLayoutEffect)(() => {
|
|
97
105
|
// The app has just loaded
|
|
98
|
-
if (isFirstLoad) {
|
|
106
|
+
if (isFirstLoad || !locationChanged) {
|
|
99
107
|
isFirstLoad = false;
|
|
100
108
|
return;
|
|
101
109
|
}
|
|
@@ -116,15 +124,27 @@ function useScrollRestoration(location, pending, serverProps) {
|
|
|
116
124
|
const element = document.querySelector(location.hash);
|
|
117
125
|
if (element) {
|
|
118
126
|
element.scrollIntoView();
|
|
127
|
+
onFinishNavigating();
|
|
119
128
|
return;
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
// If we have a matching position, scroll to it
|
|
123
132
|
if (position) {
|
|
124
133
|
window.scrollTo(0, position);
|
|
134
|
+
onFinishNavigating();
|
|
125
135
|
return;
|
|
126
136
|
}
|
|
127
137
|
// Scroll to the top of new pages
|
|
128
138
|
window.scrollTo(0, 0);
|
|
129
|
-
|
|
139
|
+
onFinishNavigating();
|
|
140
|
+
}, [
|
|
141
|
+
location.pathname,
|
|
142
|
+
location.search,
|
|
143
|
+
location.hash,
|
|
144
|
+
pending,
|
|
145
|
+
serverProps.pathname,
|
|
146
|
+
serverProps.search,
|
|
147
|
+
locationChanged,
|
|
148
|
+
onFinishNavigating,
|
|
149
|
+
]);
|
|
130
150
|
}
|
|
@@ -24,7 +24,7 @@ exports.default = () => {
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
resolveId(source, importer) {
|
|
27
|
-
if ((0, vite_1.normalizePath)(source).includes('
|
|
27
|
+
if ((0, vite_1.normalizePath)(source).includes('/hydrogen/platforms/')) {
|
|
28
28
|
const hydrogenPath = path_1.default.dirname(require.resolve('@shopify/hydrogen/package.json'));
|
|
29
29
|
const platformEntryName = source.split(path_1.default.sep).pop() || '';
|
|
30
30
|
const platformEntryPath = path_1.default.resolve(hydrogenPath, 'dist', 'esnext', 'platforms', platformEntryName);
|
|
@@ -35,7 +35,7 @@ exports.default = () => {
|
|
|
35
35
|
return null;
|
|
36
36
|
},
|
|
37
37
|
transform(code, id) {
|
|
38
|
-
if ((0, vite_1.normalizePath)(id).includes('
|
|
38
|
+
if ((0, vite_1.normalizePath)(id).includes('/hydrogen/dist/esnext/platforms/')) {
|
|
39
39
|
code = code
|
|
40
40
|
.replace('__SERVER_ENTRY__', process.env.HYDROGEN_SERVER_ENTRY || vite_plugin_hydrogen_middleware_1.HYDROGEN_DEFAULT_SERVER_ENTRY)
|
|
41
41
|
.replace('__INDEX_TEMPLATE__', (0, vite_1.normalizePath)(path_1.default.resolve(config.root, config.build.outDir, '..', 'client', 'index.html')));
|
|
@@ -19,6 +19,7 @@ export declare type ApiRouteMatch = {
|
|
|
19
19
|
hasServerComponent: boolean;
|
|
20
20
|
params: RouteParams;
|
|
21
21
|
};
|
|
22
|
+
export declare function extractPathFromRoutesKey(routesKey: string, dirPrefix: string): string;
|
|
22
23
|
export declare function getApiRoutes(pages: ImportGlobEagerOutput | undefined, topLevelPath?: string): Array<HydrogenApiRoute>;
|
|
23
24
|
export declare function getApiRouteFromURL(url: URL, routes: Array<HydrogenApiRoute>): ApiRouteMatch | null;
|
|
24
25
|
/** The `queryShop` utility is a function that helps you query the Storefront API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renderApiRoute = exports.getApiRouteFromURL = exports.getApiRoutes = void 0;
|
|
3
|
+
exports.renderApiRoute = exports.getApiRouteFromURL = exports.getApiRoutes = exports.extractPathFromRoutesKey = void 0;
|
|
4
4
|
const matchPath_1 = require("./matchPath");
|
|
5
5
|
const log_1 = require("../utilities/log/");
|
|
6
6
|
const fetch_1 = require("./fetch");
|
|
@@ -8,6 +8,29 @@ const storefrontApi_1 = require("./storefrontApi");
|
|
|
8
8
|
const session_1 = require("../foundation/session/session");
|
|
9
9
|
let memoizedRoutes = [];
|
|
10
10
|
let memoizedPages = {};
|
|
11
|
+
function extractPathFromRoutesKey(routesKey, dirPrefix) {
|
|
12
|
+
let path = routesKey
|
|
13
|
+
.replace(dirPrefix, '')
|
|
14
|
+
.replace(/\.server\.(t|j)sx?$/, '')
|
|
15
|
+
/**
|
|
16
|
+
* Replace /index with /
|
|
17
|
+
*/
|
|
18
|
+
.replace(/\/index$/i, '/')
|
|
19
|
+
/**
|
|
20
|
+
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
21
|
+
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
22
|
+
*/
|
|
23
|
+
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
24
|
+
/**
|
|
25
|
+
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
26
|
+
*/
|
|
27
|
+
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
28
|
+
if (path.endsWith('/') && path !== '/') {
|
|
29
|
+
path = path.substring(0, path.length - 1);
|
|
30
|
+
}
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
exports.extractPathFromRoutesKey = extractPathFromRoutesKey;
|
|
11
34
|
function getApiRoutes(pages, topLevelPath = '*') {
|
|
12
35
|
if (!pages || memoizedPages === pages)
|
|
13
36
|
return memoizedRoutes;
|
|
@@ -15,24 +38,7 @@ function getApiRoutes(pages, topLevelPath = '*') {
|
|
|
15
38
|
const routes = Object.keys(pages)
|
|
16
39
|
.filter((key) => pages[key].api)
|
|
17
40
|
.map((key) => {
|
|
18
|
-
|
|
19
|
-
.replace('./routes', '')
|
|
20
|
-
.replace(/\.server\.(t|j)sx?$/, '')
|
|
21
|
-
/**
|
|
22
|
-
* Replace /index with /
|
|
23
|
-
*/
|
|
24
|
-
.replace(/\/index$/i, '/')
|
|
25
|
-
/**
|
|
26
|
-
* Only lowercase the first letter. This allows the developer to use camelCase
|
|
27
|
-
* dynamic paths while ensuring their standard routes are normalized to lowercase.
|
|
28
|
-
*/
|
|
29
|
-
.replace(/\b[A-Z]/, (firstLetter) => firstLetter.toLowerCase())
|
|
30
|
-
/**
|
|
31
|
-
* Convert /[handle].jsx and /[...handle].jsx to /:handle.jsx for react-router-dom
|
|
32
|
-
*/
|
|
33
|
-
.replace(/\[(?:[.]{3})?(\w+?)\]/g, (_match, param) => `:${param}`);
|
|
34
|
-
if (path.endsWith('/') && path !== '/')
|
|
35
|
-
path = path.substring(0, path.length - 1);
|
|
41
|
+
const path = extractPathFromRoutesKey(key, './routes');
|
|
36
42
|
/**
|
|
37
43
|
* Catch-all routes [...handle].jsx don't need an exact match
|
|
38
44
|
* https://reactrouter.com/core/api/Route/exact-bool
|
package/dist/node/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "0.17.
|
|
1
|
+
export declare const LIB_VERSION = "0.17.2";
|
package/dist/node/version.js
CHANGED
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=14"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.17.
|
|
10
|
+
"version": "0.17.2",
|
|
11
11
|
"description": "Modern custom Shopify storefronts",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"main": "dist/esnext/index.js",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"rimraf": "^3.0.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"body-parser": "^1.
|
|
84
|
+
"body-parser": "^1.20.0",
|
|
85
85
|
"compression": "^1.7.4",
|
|
86
86
|
"react": "0.0.0-experimental-2bf7c02f0-20220314",
|
|
87
87
|
"react-dom": "0.0.0-experimental-2bf7c02f0-20220314",
|