@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 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/examples/template-hydrogen-default/src/components/CountrySelector.client.jsx)
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' | 'alt' | 'poster';
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
- throw new Error(`<ModelViewer/> requires the 'data.alt' prop`);
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['cache-control'] = componentResponse.cacheControlHeader;
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[CONTENT_TYPE] = HTML_CONTENT_TYPE;
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['cache-control'] =
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[CONTENT_TYPE] = HTML_CONTENT_TYPE;
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
- // @ts-ignore
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
- let path = key
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(location, pending, locationServerProps);
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
- }, [location, pending, serverProps]);
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('@shopify/hydrogen/platforms/')) {
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('@shopify/hydrogen/dist/esnext/platforms/')) {
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
- let path = key
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
@@ -41,6 +41,9 @@ const botUserAgents = [
41
41
  'W3C_Validator',
42
42
  'WhatsApp',
43
43
  'yandex',
44
+ // SEO Tools
45
+ 'Seoradar',
46
+ 'W3C html2txt',
44
47
  ];
45
48
  /**
46
49
  * Creates a regex based on the botUserAgents array
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.17.1";
1
+ export declare const LIB_VERSION = "0.17.2";
@@ -1 +1 @@
1
- export const LIB_VERSION = '0.17.1';
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['cache-control'] = componentResponse.cacheControlHeader;
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[CONTENT_TYPE] = HTML_CONTENT_TYPE;
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['cache-control'] =
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[CONTENT_TYPE] = HTML_CONTENT_TYPE;
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
- // @ts-ignore
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(location, pending, locationServerProps);
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
- }, [location, pending, serverProps]);
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('@shopify/hydrogen/platforms/')) {
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('@shopify/hydrogen/dist/esnext/platforms/')) {
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
- let path = key
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
@@ -44,6 +44,9 @@ const botUserAgents = [
44
44
  'W3C_Validator',
45
45
  'WhatsApp',
46
46
  'yandex',
47
+ // SEO Tools
48
+ 'Seoradar',
49
+ 'W3C html2txt',
47
50
  ];
48
51
  /**
49
52
  * Creates a regex based on the botUserAgents array
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.17.1";
1
+ export declare const LIB_VERSION = "0.17.2";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIB_VERSION = void 0;
4
- exports.LIB_VERSION = '0.17.1';
4
+ exports.LIB_VERSION = '0.17.2';
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "0.17.1",
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.19.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",