@shopify/hydrogen 1.2.0 → 1.3.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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/esnext/components/CartProvider/CartProvider.client.js +6 -0
  3. package/dist/esnext/components/CartProvider/cart-queries.d.ts +1 -1
  4. package/dist/esnext/components/CartProvider/cart-queries.js +1 -0
  5. package/dist/esnext/components/Image/Image.d.ts +1 -1
  6. package/dist/esnext/components/Image/Image.js +4 -4
  7. package/dist/esnext/components/Image/index.d.ts +1 -1
  8. package/dist/esnext/components/index.d.ts +1 -1
  9. package/dist/esnext/components/index.js +1 -1
  10. package/dist/esnext/constants.d.ts +1 -0
  11. package/dist/esnext/constants.js +1 -0
  12. package/dist/esnext/entry-server.js +10 -3
  13. package/dist/esnext/experimental.d.ts +1 -0
  14. package/dist/esnext/experimental.js +1 -0
  15. package/dist/esnext/foundation/Cache/cache.js +0 -1
  16. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +1 -0
  17. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +1 -0
  18. package/dist/esnext/foundation/HydrogenResponse/HydrogenResponse.server.d.ts +0 -2
  19. package/dist/esnext/foundation/HydrogenResponse/HydrogenResponse.server.js +2 -16
  20. package/dist/esnext/foundation/ServerPropsProvider/ServerPropsProvider.js +1 -3
  21. package/dist/esnext/foundation/session/session-types.d.ts +2 -0
  22. package/dist/esnext/foundation/session/session.d.ts +3 -0
  23. package/dist/esnext/foundation/session/session.js +16 -0
  24. package/dist/esnext/foundation/useQuery/hooks.d.ts +3 -0
  25. package/dist/esnext/foundation/useQuery/hooks.js +1 -1
  26. package/dist/esnext/foundation/useSession/useSession.d.ts +1 -0
  27. package/dist/esnext/foundation/useSession/useSession.js +13 -0
  28. package/dist/esnext/framework/plugin.js +4 -3
  29. package/dist/esnext/framework/plugins/vite-plugin-assets-version.d.ts +2 -0
  30. package/dist/esnext/framework/plugins/vite-plugin-assets-version.js +8 -0
  31. package/dist/esnext/framework/plugins/vite-plugin-css-rsc.js +0 -5
  32. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.d.ts +2 -1
  33. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +8 -4
  34. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-virtual-files.js +24 -6
  35. package/dist/esnext/framework/types.d.ts +1 -0
  36. package/dist/esnext/utilities/apiRoutes.js +11 -1
  37. package/dist/esnext/utilities/log/utils.js +1 -1
  38. package/dist/esnext/version.d.ts +1 -1
  39. package/dist/esnext/version.js +1 -1
  40. package/dist/node/foundation/session/session-types.d.ts +2 -0
  41. package/dist/node/framework/plugin.js +4 -3
  42. package/dist/node/framework/plugins/vite-plugin-assets-version.d.ts +2 -0
  43. package/dist/node/framework/plugins/vite-plugin-assets-version.js +11 -0
  44. package/dist/node/framework/plugins/vite-plugin-css-rsc.js +0 -5
  45. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.d.ts +2 -1
  46. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +11 -4
  47. package/dist/node/framework/plugins/vite-plugin-hydrogen-virtual-files.js +24 -6
  48. package/dist/node/framework/types.d.ts +1 -0
  49. package/package.json +4 -3
  50. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +5 -4
  51. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +5 -4
  52. package/dist/esnext/framework/plugins/vite-plugin-purge-query-cache.d.ts +0 -3
  53. package/dist/esnext/framework/plugins/vite-plugin-purge-query-cache.js +0 -11
  54. package/dist/node/framework/plugins/vite-plugin-purge-query-cache.d.ts +0 -3
  55. package/dist/node/framework/plugins/vite-plugin-purge-query-cache.js +0 -16
package/README.md CHANGED
@@ -8,7 +8,7 @@ Hydrogen is a React framework and SDK that you can use to build fast and dynamic
8
8
 
9
9
  **Requirements:**
10
10
 
11
- - Node.js version 16.5.0 or higher
11
+ - Node.js version 16.14.0 or higher
12
12
  - Yarn
13
13
 
14
14
  ```bash
@@ -219,6 +219,7 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
219
219
  ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
220
220
  addedCartLines: cart.lines,
221
221
  cart: data.cartCreate.cart,
222
+ prevCart: null,
222
223
  });
223
224
  }
224
225
  dispatch({
@@ -258,6 +259,7 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
258
259
  ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
259
260
  addedCartLines: lines,
260
261
  cart: data.cartLinesAdd.cart,
262
+ prevCart: state.cart,
261
263
  });
262
264
  dispatch({
263
265
  type: 'resolve',
@@ -289,6 +291,7 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
289
291
  ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
290
292
  removedCartLines: lines,
291
293
  cart: data.cartLinesRemove.cart,
294
+ prevCart: state.cart,
292
295
  });
293
296
  dispatch({
294
297
  type: 'resolve',
@@ -320,6 +323,8 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
320
323
  ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
321
324
  updatedCartLines: lines,
322
325
  oldCart: state.cart,
326
+ cart: data.cartLinesUpdate.cart,
327
+ prevCart: state.cart,
323
328
  });
324
329
  dispatch({
325
330
  type: 'resolve',
@@ -432,6 +437,7 @@ export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLi
432
437
  ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
433
438
  updatedDiscountCodes: discountCodes,
434
439
  cart: data.cartDiscountCodesUpdate.cart,
440
+ prevCart: state.cart,
435
441
  });
436
442
  dispatch({
437
443
  type: 'resolve',
@@ -7,4 +7,4 @@ export declare const CartBuyerIdentityUpdate: (cartFragment: string) => string;
7
7
  export declare const CartAttributesUpdate: (cartFragment: string) => string;
8
8
  export declare const CartDiscountCodesUpdate: (cartFragment: string) => string;
9
9
  export declare const CartQuery: (cartFragment: string) => string;
10
- export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
10
+ export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n id\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
@@ -154,6 +154,7 @@ fragment CartFragment on Cart {
154
154
  product {
155
155
  handle
156
156
  title
157
+ id
157
158
  }
158
159
  selectedOptions {
159
160
  name
@@ -79,7 +79,7 @@ declare type LoaderProps<GenericLoaderOpts> = {
79
79
  */
80
80
  loaderOptions?: GenericLoaderOpts;
81
81
  };
82
- declare type ExternalImageProps<GenericLoaderOpts> = SetRequired<HtmlImageProps, 'src' | 'width' | 'height' | 'alt'> & {
82
+ export declare type ExternalImageProps<GenericLoaderOpts> = SetRequired<HtmlImageProps, 'src' | 'width' | 'height' | 'alt'> & {
83
83
  /** A custom function that generates the image URL. Parameters passed in
84
84
  * are either `ShopifyLoaderParams` if using the `data` prop, or the
85
85
  * `LoaderOptions` object that you pass to `loaderOptions`.
@@ -27,7 +27,7 @@ export function Image(props) {
27
27
  return React.createElement(ExternalImage, { ...props });
28
28
  }
29
29
  }
30
- function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoader, loaderOptions, widths, ...rest }) {
30
+ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoader, loaderOptions, widths, decoding = 'async', ...rest }) {
31
31
  if (!data.url) {
32
32
  throw new Error(`<Image/>: the 'data' prop requires the 'url' property`);
33
33
  }
@@ -72,10 +72,10 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
72
72
  loader,
73
73
  });
74
74
  /* eslint-disable hydrogen/prefer-image-component */
75
- return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: imgElementWidth ?? undefined, height: imgElementHeight ?? undefined, srcSet: finalSrcset }));
75
+ return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: imgElementWidth ?? undefined, height: imgElementHeight ?? undefined, srcSet: finalSrcset, decoding: decoding }));
76
76
  /* eslint-enable hydrogen/prefer-image-component */
77
77
  }
78
- function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths, loading, ...rest }) {
78
+ function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths, loading, decoding = 'async', ...rest }) {
79
79
  if (!width || !height) {
80
80
  throw new Error(`<Image/>: when 'src' is provided, 'width' and 'height' are required and need to be valid values (i.e. greater than zero). Provided values: 'src': ${src}, 'width': ${width}, 'height': ${height}`);
81
81
  }
@@ -112,7 +112,7 @@ function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths,
112
112
  // @ts-expect-error TS doesn't understand that it could exist
113
113
  width: loaderOptions?.width ?? width,
114
114
  // @ts-expect-error TS doesn't understand that it could exist
115
- height: loaderOptions?.height ?? height, alt: alt ?? '', loading: loading ?? 'lazy', srcSet: finalSrcset }));
115
+ height: loaderOptions?.height ?? height, alt: alt ?? '', loading: loading ?? 'lazy', srcSet: finalSrcset, decoding: decoding }));
116
116
  /* eslint-enable hydrogen/prefer-image-component */
117
117
  }
118
118
  function internalImageSrcSet({ src, width, crop, scale, widths, loader, height, }) {
@@ -1,2 +1,2 @@
1
1
  export { Image } from './Image.js';
2
- export type { ShopifyLoaderParams, ShopifyLoaderOptions, ShopifyImageProps, } from './Image.js';
2
+ export type { ShopifyLoaderParams, ShopifyLoaderOptions, ShopifyImageProps, ExternalImageProps, } from './Image.js';
@@ -1,7 +1,7 @@
1
1
  export { Link } from './Link/index.js';
2
2
  export { MediaFile } from './MediaFile/index.js';
3
3
  export { Video } from './Video/index.js';
4
- export { Image } from './Image/index.js';
4
+ export { Image, type ExternalImageProps, type ShopifyImageProps, } from './Image/index.js';
5
5
  export { ExternalVideo } from './ExternalVideo/index.js';
6
6
  export { AddToCartButton } from './AddToCartButton/index.js';
7
7
  export { ModelViewer } from './ModelViewer/index.js';
@@ -1,7 +1,7 @@
1
1
  export { Link } from './Link/index.js';
2
2
  export { MediaFile } from './MediaFile/index.js';
3
3
  export { Video } from './Video/index.js';
4
- export { Image } from './Image/index.js';
4
+ export { Image, } from './Image/index.js';
5
5
  export { ExternalVideo } from './ExternalVideo/index.js';
6
6
  export { AddToCartButton } from './AddToCartButton/index.js';
7
7
  export { ModelViewer } from './ModelViewer/index.js';
@@ -4,6 +4,7 @@ export declare const EVENT_PATHNAME_REGEX: RegExp;
4
4
  export declare const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = "SHOPIFY_STOREFRONT_API_SECRET_TOKEN";
5
5
  export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Private-Token";
6
6
  export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
7
+ export declare const FORM_REDIRECT_COOKIE = "Hydrogen-Redirect";
7
8
  export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
8
9
  export declare const SHOPIFY_STOREFRONT_ID_VARIABLE = "SHOPIFY_STOREFRONT_ID";
9
10
  export declare const SHOPIFY_STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
@@ -4,6 +4,7 @@ export const EVENT_PATHNAME_REGEX = new RegExp(`^${EVENT_PATHNAME}/`);
4
4
  export const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = 'SHOPIFY_STOREFRONT_API_SECRET_TOKEN';
5
5
  export const STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-Token';
6
6
  export const STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
7
+ export const FORM_REDIRECT_COOKIE = 'Hydrogen-Redirect';
7
8
  export const STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
8
9
  export const SHOPIFY_STOREFRONT_ID_VARIABLE = 'SHOPIFY_STOREFRONT_ID';
9
10
  export const SHOPIFY_STOREFRONT_ID_HEADER = 'Shopify-Storefront-Id';
@@ -20,6 +20,7 @@ import { splitCookiesString } from 'set-cookie-parser';
20
20
  import { deleteItemFromCache, getItemFromCache, isStale, setItemInCache, } from './foundation/Cache/cache.js';
21
21
  import { CacheShort, NO_STORE } from './foundation/Cache/strategies/index.js';
22
22
  import { getBuiltInRoute } from './foundation/BuiltInRoutes/BuiltInRoutes.js';
23
+ import { FORM_REDIRECT_COOKIE } from './constants.js';
23
24
  const DOCTYPE = '<!DOCTYPE html>';
24
25
  const CONTENT_TYPE = 'Content-Type';
25
26
  const HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
@@ -29,9 +30,10 @@ export const renderHydrogen = (App) => {
29
30
  const request = new HydrogenRequest(rawRequest);
30
31
  const url = new URL(request.url);
31
32
  let sessionApi = options.sessionApi;
32
- const { default: inlineHydrogenConfig } = await import(
33
+ const { default: importedConfig } = await import(
33
34
  // @ts-ignore
34
35
  'virtual__hydrogen.config.ts');
36
+ const inlineHydrogenConfig = typeof importedConfig === 'function' ? importedConfig() : importedConfig;
35
37
  const { default: hydrogenRoutes } = await import(
36
38
  // @ts-ignore
37
39
  'virtual__hydrogen-routes.server.jsx');
@@ -46,6 +48,10 @@ export const renderHydrogen = (App) => {
46
48
  const response = new HydrogenResponse(null, {
47
49
  headers: headers || {},
48
50
  });
51
+ if (request.cookies.get(FORM_REDIRECT_COOKIE)) {
52
+ response.headers.set('SET-COOKIE', `${FORM_REDIRECT_COOKIE}=`);
53
+ response.doNotStream();
54
+ }
49
55
  if (hydrogenConfig.poweredByHeader ?? true) {
50
56
  // If undefined in the config, then always show the header
51
57
  response.headers.set('powered-by', 'Shopify-Hydrogen');
@@ -97,7 +103,7 @@ export const renderHydrogen = (App) => {
97
103
  }
98
104
  });
99
105
  // Asynchronously wait for it in workers
100
- request.ctx.runtime?.waitUntil(staleWhileRevalidatePromise);
106
+ request.ctx.runtime?.waitUntil?.(staleWhileRevalidatePromise);
101
107
  }
102
108
  return cachedResponse;
103
109
  }
@@ -137,6 +143,7 @@ async function processRequest(handleRequest, App, url, request, sessionApi, opti
137
143
  if (isRSCRequest) {
138
144
  const buffered = await bufferReadableStream(rsc.readable.getReader());
139
145
  postRequestTasks('rsc', 200, request, response);
146
+ response.headers.set('cache-control', response.cacheControlHeader);
140
147
  cacheResponse(response, request, [buffered], revalidate);
141
148
  return new Response(buffered, {
142
149
  headers: response.headers,
@@ -593,7 +600,7 @@ async function cacheResponse(response, request, chunks, revalidate) {
593
600
  }
594
601
  else {
595
602
  const cachePutPromise = Promise.resolve(true).then(() => saveCacheResponse(response, request, chunks));
596
- request.ctx.runtime?.waitUntil(cachePutPromise);
603
+ request.ctx.runtime?.waitUntil?.(cachePutPromise);
597
604
  }
598
605
  }
599
606
  }
@@ -1 +1,2 @@
1
1
  export { Form } from './foundation/Form/Form.client.js';
2
+ export { useFlashSession } from './foundation/useSession/useSession.js';
@@ -1 +1,2 @@
1
1
  export { Form } from './foundation/Form/Form.client.js';
2
+ export { useFlashSession } from './foundation/useSession/useSession.js';
@@ -88,7 +88,6 @@ export async function setItemInCache(request, response, userCacheOptions) {
88
88
  const cacheControlString = generateDefaultCacheControlHeader(getCacheControlSetting(cacheControl));
89
89
  // CF will override cache-control, so we need to keep a
90
90
  // non-modified real-cache-control
91
- response.headers.set('cache-control', cacheControlString);
92
91
  response.headers.set('real-cache-control', cacheControlString);
93
92
  response.headers.set('cache-put-date', new Date().toUTCString());
94
93
  logCacheApiStatus('PUT', request.url);
@@ -46,6 +46,7 @@ export declare class HydrogenRequest extends Request {
46
46
  router: RouterContextData;
47
47
  buyerIpHeader?: string;
48
48
  session?: SessionSyncApi;
49
+ flashSession: Record<string, any>;
49
50
  runtime?: RuntimeContext;
50
51
  scopes: Map<string, Record<string, any>>;
51
52
  localization?: LocalizationContextValue;
@@ -60,6 +60,7 @@ export class HydrogenRequest extends Request {
60
60
  },
61
61
  preloadQueries: new Map(),
62
62
  scopes: new Map(),
63
+ flashSession: {},
63
64
  };
64
65
  this.cookies = this.parseCookies();
65
66
  }
@@ -3,10 +3,8 @@ import React from 'react';
3
3
  export declare class HydrogenResponse extends Response {
4
4
  private wait;
5
5
  private cacheOptions;
6
- private proxy;
7
6
  status: number;
8
7
  statusText: string;
9
- constructor(...args: ConstructorParameters<typeof Response>);
10
8
  /**
11
9
  * Buffer the current response until all queries have resolved,
12
10
  * and prevent it from streaming back early.
@@ -4,22 +4,8 @@ import React from 'react';
4
4
  export class HydrogenResponse extends Response {
5
5
  wait = false;
6
6
  cacheOptions = CacheShort();
7
- proxy = Object.defineProperties(Object.create(null), {
8
- // Default values:
9
- status: { value: 200, writable: true },
10
- statusText: { value: '', writable: true },
11
- });
12
- // @ts-ignore
13
- status;
14
- // @ts-ignore
15
- statusText;
16
- constructor(...args) {
17
- super(...args);
18
- return new Proxy(this, {
19
- get: (target, key) => target.proxy[key] ?? Reflect.get(target, key),
20
- set: (target, key, value) => Reflect.set(key in target.proxy ? target.proxy : target, key, value),
21
- });
22
- }
7
+ status = 200;
8
+ statusText = '';
23
9
  /**
24
10
  * Buffer the current response until all queries have resolved,
25
11
  * and prevent it from streaming back early.
@@ -1,6 +1,4 @@
1
- import React, { createContext, useMemo, useCallback,
2
- // @ts-ignore
3
- useTransition, useState, } from 'react';
1
+ import React, { createContext, useMemo, useCallback, useTransition, useState, } from 'react';
4
2
  const PRIVATE_PROPS = ['request', 'response'];
5
3
  export const ServerPropsContext = createContext(null);
6
4
  export function ServerPropsProvider({ initialServerProps, setServerPropsForRsc, setRscResponseFromApiRoute, children, }) {
@@ -1,10 +1,12 @@
1
1
  export declare type SessionSyncApi = {
2
2
  get: () => Record<string, string>;
3
+ set: (data: Record<string, any>) => any;
3
4
  };
4
5
  export declare type SessionApi = {
5
6
  get: () => Promise<Record<string, string>>;
6
7
  set: (key: string, value: string) => Promise<void>;
7
8
  destroy: () => Promise<void>;
9
+ getFlash: (key: string) => any;
8
10
  };
9
11
  export declare type SessionStorageAdapter = {
10
12
  get: (request: Request) => Promise<Record<string, string>>;
@@ -4,12 +4,15 @@ import type { HydrogenRequest } from '../HydrogenRequest/HydrogenRequest.server.
4
4
  import type { SessionStorageAdapter } from './session-types.js';
5
5
  export declare function getSyncSessionApi(request: HydrogenRequest, componentResponse: HydrogenResponse, log: Logger, session?: SessionStorageAdapter): {
6
6
  get(): any;
7
+ set(data: Record<string, any>): any;
7
8
  };
8
9
  export declare const emptySessionImplementation: (log: Logger) => {
10
+ getFlash(key: string): Promise<null>;
9
11
  get(): Promise<{}>;
10
12
  set(key: string, value: string): Promise<void>;
11
13
  destroy(): Promise<void>;
12
14
  };
13
15
  export declare const emptySyncSessionImplementation: (log: Logger) => {
14
16
  get(): {};
17
+ set(data: Record<string, any>): null;
15
18
  };
@@ -9,11 +9,23 @@ export function getSyncSessionApi(request, componentResponse, log, session) {
9
9
  }
10
10
  return sessionPromises.getPromise.read();
11
11
  },
12
+ set(data) {
13
+ if (!sessionPromises.setPromise) {
14
+ sessionPromises.setPromise = wrapPromise(session.set(request, data));
15
+ }
16
+ const cookie = sessionPromises.setPromise.read();
17
+ componentResponse.headers.set('Set-Cookie', cookie);
18
+ return cookie;
19
+ },
12
20
  }
13
21
  : emptySyncSessionImplementation(log);
14
22
  }
15
23
  export const emptySessionImplementation = function (log) {
16
24
  return {
25
+ async getFlash(key) {
26
+ log.warn('No session adapter has been configured!');
27
+ return null;
28
+ },
17
29
  async get() {
18
30
  log.warn('No session adapter has been configured!');
19
31
  return {};
@@ -33,5 +45,9 @@ export const emptySyncSessionImplementation = function (log) {
33
45
  log.warn('No session adapter has been configured!');
34
46
  return {};
35
47
  },
48
+ set(data) {
49
+ log.warn('No session adapter has been configured!');
50
+ return null;
51
+ },
36
52
  };
37
53
  };
@@ -13,6 +13,9 @@ export interface HydrogenUseQueryOptions {
13
13
  */
14
14
  shouldCacheResponse?: (body: any) => boolean;
15
15
  }
16
+ declare global {
17
+ var __HYDROGEN_CACHE_ID__: string;
18
+ }
16
19
  /**
17
20
  * The `useQuery` hook executes an asynchronous operation like `fetch` in a way that
18
21
  * supports [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). You can use this
@@ -19,7 +19,7 @@ queryFn,
19
19
  queryOptions) {
20
20
  const request = useServerRequest();
21
21
  const withCacheIdKey = [
22
- '__QUERY_CACHE_ID__',
22
+ __HYDROGEN_CACHE_ID__,
23
23
  ...(typeof key === 'string' ? [key] : key),
24
24
  ];
25
25
  const fetcher = cachedQueryFnBuilder(withCacheIdKey, queryFn, queryOptions);
@@ -1,2 +1,3 @@
1
1
  /** The `useSession` hook reads session data in server components. */
2
2
  export declare const useSession: () => Record<string, string>;
3
+ export declare const useFlashSession: (key: string) => string;
@@ -5,3 +5,16 @@ export const useSession = function () {
5
5
  const session = request.ctx.session?.get() || {};
6
6
  return session;
7
7
  };
8
+ export const useFlashSession = function (key) {
9
+ const request = useServerRequest();
10
+ const data = request.ctx.session?.get() || {};
11
+ let value = data[key];
12
+ if (value) {
13
+ delete data[key];
14
+ request.ctx.flashSession[key] = value;
15
+ }
16
+ request.ctx.session?.set(data);
17
+ value = request.ctx.flashSession[key];
18
+ delete request.ctx.flashSession[key];
19
+ return value;
20
+ };
@@ -5,7 +5,6 @@ import hydrogenVirtualFiles from './plugins/vite-plugin-hydrogen-virtual-files.j
5
5
  import platformEntry from './plugins/vite-plugin-platform-entry.js';
6
6
  import rsc from './plugins/vite-plugin-hydrogen-rsc.js';
7
7
  import ssrInterop from './plugins/vite-plugin-ssr-interop.js';
8
- import purgeQueryCache from './plugins/vite-plugin-purge-query-cache.js';
9
8
  import hydrationAutoImport from './plugins/vite-plugin-hydration-auto-import.js';
10
9
  import inspect from 'vite-plugin-inspect';
11
10
  import react from '@vitejs/plugin-react';
@@ -13,10 +12,11 @@ import cssRsc from './plugins/vite-plugin-css-rsc.js';
13
12
  import cssModulesRsc from './plugins/vite-plugin-css-modules-rsc.js';
14
13
  import clientImports from './plugins/vite-plugin-client-imports.js';
15
14
  import suppressWarnings from './plugins/vite-plugin-hydrogen-suppress-warnings.js';
15
+ import assetsVersion from './plugins/vite-plugin-assets-version.js';
16
16
  const hydrogenPlugin = (pluginOptions = {}) => {
17
17
  return [
18
18
  process.env.VITE_INSPECT && inspect(),
19
- hydrogenConfig(),
19
+ hydrogenConfig(pluginOptions),
20
20
  hydrogenClientMiddleware(),
21
21
  clientImports(),
22
22
  hydrogenMiddleware(pluginOptions),
@@ -28,7 +28,8 @@ const hydrogenPlugin = (pluginOptions = {}) => {
28
28
  rsc(pluginOptions),
29
29
  platformEntry(),
30
30
  suppressWarnings(),
31
- pluginOptions.purgeQueryCacheOnBuild && purgeQueryCache(),
31
+ pluginOptions.assetHashVersion &&
32
+ assetsVersion(pluginOptions.assetHashVersion),
32
33
  ];
33
34
  };
34
35
  export default hydrogenPlugin; // For ESM
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'vite';
2
+ export default function assetsVersion(version?: string): Plugin;
@@ -0,0 +1,8 @@
1
+ export default function assetsVersion(version) {
2
+ return {
3
+ name: 'hydrogen:augment-with-version',
4
+ augmentChunkHash() {
5
+ return version ?? '';
6
+ },
7
+ };
8
+ }
@@ -35,7 +35,6 @@ export default function cssRsc() {
35
35
  if (server) {
36
36
  const tags = [];
37
37
  const foundCssFiles = new Set();
38
- const { browserHash = '' } = server._optimizedDeps?.metadata || {};
39
38
  for (const [key, value] of server.moduleGraph.idToModuleMap.entries()) {
40
39
  if (
41
40
  // Note: Some CSS-in-JS libraries use `.css.js`
@@ -55,10 +54,6 @@ export default function cssRsc() {
55
54
  ? url.replace('?', timestampQuery + '&')
56
55
  : url + timestampQuery;
57
56
  }
58
- if (browserHash && !url.includes('v=')) {
59
- // Append the hash at the end
60
- url += (url.includes('?') ? '&' : '?') + `v=${browserHash}`;
61
- }
62
57
  tags.push(value.type === 'css'
63
58
  ? { tag: 'link', attrs: { rel: 'stylesheet', href: url } }
64
59
  : { tag: 'script', attrs: { type: 'module', src: url } });
@@ -1,3 +1,4 @@
1
1
  import { Plugin } from 'vite';
2
- declare const _default: () => Plugin;
2
+ import type { HydrogenVitePluginOptions } from '../types.js';
3
+ declare const _default: (pluginOptions: HydrogenVitePluginOptions) => Plugin;
3
4
  export default _default;
@@ -1,4 +1,5 @@
1
- export default () => {
1
+ import Crypto from 'crypto';
2
+ export default (pluginOptions) => {
2
3
  const rollupOptions = {
3
4
  output: {},
4
5
  };
@@ -23,7 +24,7 @@ export default () => {
23
24
  };
24
25
  }
25
26
  return {
26
- name: 'vite-plugin-hydrogen-config',
27
+ name: 'hydrogen:config',
27
28
  config: async (config, env) => ({
28
29
  resolve: {
29
30
  alias: {
@@ -48,7 +49,7 @@ export default () => {
48
49
  * Tell Vite to bundle everything when we're building for Workers.
49
50
  * Otherwise, bundle RSC plugin as a workaround to apply the vendor alias above.
50
51
  */
51
- noExternal: isWorker || [/react-server-dom-vite/],
52
+ noExternal: isWorker || [/react-server-dom-vite/, /@shopify\/hydrogen/],
52
53
  target: isWorker ? 'webworker' : 'node',
53
54
  },
54
55
  // Reload when updating local Hydrogen lib
@@ -93,7 +94,10 @@ export default () => {
93
94
  define: {
94
95
  __HYDROGEN_DEV__: env.mode !== 'production',
95
96
  __HYDROGEN_WORKER__: isWorker,
96
- __HYDROGEN_TEST__: false, // Used in unit tests
97
+ __HYDROGEN_TEST__: false,
98
+ __HYDROGEN_CACHE_ID__: pluginOptions.purgeQueryCacheOnBuild
99
+ ? `"${Crypto.randomBytes(8).toString('hex').slice(0, 8)}"`
100
+ : '"__QUERY_CACHE_ID__"',
97
101
  },
98
102
  envPrefix: ['VITE_', 'PUBLIC_'],
99
103
  base: process.env.HYDROGEN_ASSET_BASE_URL,
@@ -2,6 +2,7 @@ import { normalizePath } from 'vite';
2
2
  import path from 'path';
3
3
  import { promises as fs } from 'fs';
4
4
  import { viteception } from '../viteception.js';
5
+ import MagicString from 'magic-string';
5
6
  export const HYDROGEN_DEFAULT_SERVER_ENTRY = process.env.HYDROGEN_SERVER_ENTRY || '/src/App.server';
6
7
  // The character ":" breaks Vite with Node >= 16.15. Use "_" instead
7
8
  const VIRTUAL_PREFIX = 'virtual__';
@@ -17,6 +18,7 @@ export const VIRTUAL_PROXY_HYDROGEN_ROUTES_ID = VIRTUAL_PREFIX + PROXY_PREFIX +
17
18
  export default (pluginOptions) => {
18
19
  let config;
19
20
  let server;
21
+ let resolvedConfigPath;
20
22
  return {
21
23
  name: 'hydrogen:virtual-files',
22
24
  configResolved(_config) {
@@ -27,10 +29,12 @@ export default (pluginOptions) => {
27
29
  },
28
30
  resolveId(source, importer) {
29
31
  if (source === VIRTUAL_HYDROGEN_CONFIG_ID) {
30
- return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) =>
31
- // This direct dependency on a real file
32
- // makes HMR work for the virtual module.
33
- this.resolve(hcPath, importer, { skipSelf: true }));
32
+ return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) => {
33
+ resolvedConfigPath = normalizePath(hcPath);
34
+ // This direct dependency on a real file
35
+ // makes HMR work for the virtual module.
36
+ return this.resolve(hcPath, importer, { skipSelf: true });
37
+ });
34
38
  }
35
39
  if ([
36
40
  VIRTUAL_PROXY_HYDROGEN_CONFIG_ID,
@@ -47,7 +51,7 @@ export default (pluginOptions) => {
47
51
  // Likely due to a bug in Vite, but virtual modules cannot be loaded
48
52
  // directly using ssrLoadModule from a Vite plugin. It needs to be proxied as follows:
49
53
  if (id === '\0' + VIRTUAL_PROXY_HYDROGEN_CONFIG_ID) {
50
- return `import hc from '${VIRTUAL_HYDROGEN_CONFIG_ID}'; export default hc;`;
54
+ return `import hc from '${VIRTUAL_HYDROGEN_CONFIG_ID}'; export default typeof hc === 'function' ? hc() : hc;`;
51
55
  }
52
56
  if (id === '\0' + VIRTUAL_PROXY_HYDROGEN_ROUTES_ID) {
53
57
  return `import hr from '${VIRTUAL_HYDROGEN_ROUTES_ID}'; export default hr;`;
@@ -82,13 +86,27 @@ export default (pluginOptions) => {
82
86
  });
83
87
  }
84
88
  },
89
+ transform(code, id) {
90
+ if (id === resolvedConfigPath) {
91
+ const s = new MagicString(code);
92
+ // Wrap in function to avoid evaluating `Oxygen.env`
93
+ // in the config until we have polyfilled it properly.
94
+ s.replace(/export\s+default\s+(\w+)\s*\(/g, (all, m1) => all.replace(m1, `() => ${m1}`));
95
+ return {
96
+ code: s.toString(),
97
+ map: s.generateMap({ file: id, source: id }),
98
+ };
99
+ }
100
+ },
85
101
  };
86
102
  async function importHydrogenConfig() {
87
103
  if (server) {
88
104
  const loaded = await server.ssrLoadModule(VIRTUAL_PROXY_HYDROGEN_CONFIG_ID);
89
105
  return loaded.default;
90
106
  }
91
- const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID]);
107
+ const { loaded } = await viteception([VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], {
108
+ root: config.root,
109
+ });
92
110
  return loaded[0].default;
93
111
  }
94
112
  };
@@ -3,6 +3,7 @@ export interface HydrogenVitePluginOptions {
3
3
  purgeQueryCacheOnBuild?: boolean;
4
4
  configPath?: string;
5
5
  optimizeBoundaries?: boolean | 'build';
6
+ assetHashVersion?: string;
6
7
  /**
7
8
  * Experimental features
8
9
  */
@@ -3,7 +3,7 @@ import { getLoggerWithContext, logServerResponse, } from '../utilities/log/index
3
3
  import { fetchBuilder, graphqlRequestBody } from './fetch.js';
4
4
  import { getStorefrontApiRequestHeaders } from './storefrontApi.js';
5
5
  import { emptySessionImplementation } from '../foundation/session/session.js';
6
- import { RSC_PATHNAME } from '../constants.js';
6
+ import { FORM_REDIRECT_COOKIE, RSC_PATHNAME } from '../constants.js';
7
7
  let memoizedApiRoutes = [];
8
8
  let memoizedRawRoutes = {};
9
9
  export function extractPathFromRoutesKey(routesKey, dirPrefix) {
@@ -109,6 +109,15 @@ export async function renderApiRoute(request, route, hydrogenConfig, { session,
109
109
  hydrogenConfig,
110
110
  session: session
111
111
  ? {
112
+ async getFlash(key) {
113
+ const data = await session.get(request);
114
+ const value = data[key];
115
+ if (value) {
116
+ delete data[key];
117
+ await session.set(request, data);
118
+ }
119
+ return value;
120
+ },
112
121
  async get() {
113
122
  return session.get(request);
114
123
  },
@@ -166,6 +175,7 @@ export async function renderApiRoute(request, route, hydrogenConfig, { session,
166
175
  // Doing so prevents odd refresh / back behavior. The redirect response also should *never* be cached.
167
176
  response.headers.set('Location', newUrl.href);
168
177
  response.headers.set('Cache-Control', 'no-store');
178
+ response.headers.append('Set-Cookie', `${FORM_REDIRECT_COOKIE}=1`);
169
179
  return new Response(null, {
170
180
  status: 303,
171
181
  headers: response.headers,
@@ -1,6 +1,6 @@
1
1
  export function findQueryName(key) {
2
2
  if (key.length < 100) {
3
- return key.replace('"__QUERY_CACHE_ID__"', '').replace(/"/g, '');
3
+ return key.replace(__HYDROGEN_CACHE_ID__, '').replace(/"/g, '');
4
4
  }
5
5
  return key.match(/query\s+([^\s({]+)/)?.[1] || '<unknown>';
6
6
  }
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.2.0";
1
+ export declare const LIB_VERSION = "1.3.0";
@@ -1 +1 @@
1
- export const LIB_VERSION = '1.2.0';
1
+ export const LIB_VERSION = '1.3.0';
@@ -1,10 +1,12 @@
1
1
  export declare type SessionSyncApi = {
2
2
  get: () => Record<string, string>;
3
+ set: (data: Record<string, any>) => any;
3
4
  };
4
5
  export declare type SessionApi = {
5
6
  get: () => Promise<Record<string, string>>;
6
7
  set: (key: string, value: string) => Promise<void>;
7
8
  destroy: () => Promise<void>;
9
+ getFlash: (key: string) => any;
8
10
  };
9
11
  export declare type SessionStorageAdapter = {
10
12
  get: (request: Request) => Promise<Record<string, string>>;
@@ -9,7 +9,6 @@ const vite_plugin_hydrogen_virtual_files_js_1 = __importDefault(require("./plugi
9
9
  const vite_plugin_platform_entry_js_1 = __importDefault(require("./plugins/vite-plugin-platform-entry.js"));
10
10
  const vite_plugin_hydrogen_rsc_js_1 = __importDefault(require("./plugins/vite-plugin-hydrogen-rsc.js"));
11
11
  const vite_plugin_ssr_interop_js_1 = __importDefault(require("./plugins/vite-plugin-ssr-interop.js"));
12
- const vite_plugin_purge_query_cache_js_1 = __importDefault(require("./plugins/vite-plugin-purge-query-cache.js"));
13
12
  const vite_plugin_hydration_auto_import_js_1 = __importDefault(require("./plugins/vite-plugin-hydration-auto-import.js"));
14
13
  const vite_plugin_inspect_1 = __importDefault(require("vite-plugin-inspect"));
15
14
  const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
@@ -17,10 +16,11 @@ const vite_plugin_css_rsc_js_1 = __importDefault(require("./plugins/vite-plugin-
17
16
  const vite_plugin_css_modules_rsc_js_1 = __importDefault(require("./plugins/vite-plugin-css-modules-rsc.js"));
18
17
  const vite_plugin_client_imports_js_1 = __importDefault(require("./plugins/vite-plugin-client-imports.js"));
19
18
  const vite_plugin_hydrogen_suppress_warnings_js_1 = __importDefault(require("./plugins/vite-plugin-hydrogen-suppress-warnings.js"));
19
+ const vite_plugin_assets_version_js_1 = __importDefault(require("./plugins/vite-plugin-assets-version.js"));
20
20
  const hydrogenPlugin = (pluginOptions = {}) => {
21
21
  return [
22
22
  process.env.VITE_INSPECT && (0, vite_plugin_inspect_1.default)(),
23
- (0, vite_plugin_hydrogen_config_js_1.default)(),
23
+ (0, vite_plugin_hydrogen_config_js_1.default)(pluginOptions),
24
24
  (0, vite_plugin_hydrogen_client_middleware_js_1.default)(),
25
25
  (0, vite_plugin_client_imports_js_1.default)(),
26
26
  (0, vite_plugin_hydrogen_middleware_js_1.default)(pluginOptions),
@@ -32,7 +32,8 @@ const hydrogenPlugin = (pluginOptions = {}) => {
32
32
  (0, vite_plugin_hydrogen_rsc_js_1.default)(pluginOptions),
33
33
  (0, vite_plugin_platform_entry_js_1.default)(),
34
34
  (0, vite_plugin_hydrogen_suppress_warnings_js_1.default)(),
35
- pluginOptions.purgeQueryCacheOnBuild && (0, vite_plugin_purge_query_cache_js_1.default)(),
35
+ pluginOptions.assetHashVersion &&
36
+ (0, vite_plugin_assets_version_js_1.default)(pluginOptions.assetHashVersion),
36
37
  ];
37
38
  };
38
39
  exports.default = hydrogenPlugin; // For ESM
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'vite';
2
+ export default function assetsVersion(version?: string): Plugin;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function assetsVersion(version) {
4
+ return {
5
+ name: 'hydrogen:augment-with-version',
6
+ augmentChunkHash() {
7
+ return version ?? '';
8
+ },
9
+ };
10
+ }
11
+ exports.default = assetsVersion;
@@ -40,7 +40,6 @@ function cssRsc() {
40
40
  if (server) {
41
41
  const tags = [];
42
42
  const foundCssFiles = new Set();
43
- const { browserHash = '' } = server._optimizedDeps?.metadata || {};
44
43
  for (const [key, value] of server.moduleGraph.idToModuleMap.entries()) {
45
44
  if (
46
45
  // Note: Some CSS-in-JS libraries use `.css.js`
@@ -60,10 +59,6 @@ function cssRsc() {
60
59
  ? url.replace('?', timestampQuery + '&')
61
60
  : url + timestampQuery;
62
61
  }
63
- if (browserHash && !url.includes('v=')) {
64
- // Append the hash at the end
65
- url += (url.includes('?') ? '&' : '?') + `v=${browserHash}`;
66
- }
67
62
  tags.push(value.type === 'css'
68
63
  ? { tag: 'link', attrs: { rel: 'stylesheet', href: url } }
69
64
  : { tag: 'script', attrs: { type: 'module', src: url } });
@@ -1,3 +1,4 @@
1
1
  import { Plugin } from 'vite';
2
- declare const _default: () => Plugin;
2
+ import type { HydrogenVitePluginOptions } from '../types.js';
3
+ declare const _default: (pluginOptions: HydrogenVitePluginOptions) => Plugin;
3
4
  export default _default;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = () => {
6
+ const crypto_1 = __importDefault(require("crypto"));
7
+ exports.default = (pluginOptions) => {
4
8
  const rollupOptions = {
5
9
  output: {},
6
10
  };
@@ -25,7 +29,7 @@ exports.default = () => {
25
29
  };
26
30
  }
27
31
  return {
28
- name: 'vite-plugin-hydrogen-config',
32
+ name: 'hydrogen:config',
29
33
  config: async (config, env) => ({
30
34
  resolve: {
31
35
  alias: {
@@ -50,7 +54,7 @@ exports.default = () => {
50
54
  * Tell Vite to bundle everything when we're building for Workers.
51
55
  * Otherwise, bundle RSC plugin as a workaround to apply the vendor alias above.
52
56
  */
53
- noExternal: isWorker || [/react-server-dom-vite/],
57
+ noExternal: isWorker || [/react-server-dom-vite/, /@shopify\/hydrogen/],
54
58
  target: isWorker ? 'webworker' : 'node',
55
59
  },
56
60
  // Reload when updating local Hydrogen lib
@@ -95,7 +99,10 @@ exports.default = () => {
95
99
  define: {
96
100
  __HYDROGEN_DEV__: env.mode !== 'production',
97
101
  __HYDROGEN_WORKER__: isWorker,
98
- __HYDROGEN_TEST__: false, // Used in unit tests
102
+ __HYDROGEN_TEST__: false,
103
+ __HYDROGEN_CACHE_ID__: pluginOptions.purgeQueryCacheOnBuild
104
+ ? `"${crypto_1.default.randomBytes(8).toString('hex').slice(0, 8)}"`
105
+ : '"__QUERY_CACHE_ID__"',
99
106
  },
100
107
  envPrefix: ['VITE_', 'PUBLIC_'],
101
108
  base: process.env.HYDROGEN_ASSET_BASE_URL,
@@ -8,6 +8,7 @@ const vite_1 = require("vite");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_1 = require("fs");
10
10
  const viteception_js_1 = require("../viteception.js");
11
+ const magic_string_1 = __importDefault(require("magic-string"));
11
12
  exports.HYDROGEN_DEFAULT_SERVER_ENTRY = process.env.HYDROGEN_SERVER_ENTRY || '/src/App.server';
12
13
  // The character ":" breaks Vite with Node >= 16.15. Use "_" instead
13
14
  const VIRTUAL_PREFIX = 'virtual__';
@@ -23,6 +24,7 @@ exports.VIRTUAL_PROXY_HYDROGEN_ROUTES_ID = VIRTUAL_PREFIX + PROXY_PREFIX + HYDRO
23
24
  exports.default = (pluginOptions) => {
24
25
  let config;
25
26
  let server;
27
+ let resolvedConfigPath;
26
28
  return {
27
29
  name: 'hydrogen:virtual-files',
28
30
  configResolved(_config) {
@@ -33,10 +35,12 @@ exports.default = (pluginOptions) => {
33
35
  },
34
36
  resolveId(source, importer) {
35
37
  if (source === VIRTUAL_HYDROGEN_CONFIG_ID) {
36
- return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) =>
37
- // This direct dependency on a real file
38
- // makes HMR work for the virtual module.
39
- this.resolve(hcPath, importer, { skipSelf: true }));
38
+ return findHydrogenConfigPath(config.root, pluginOptions.configPath).then((hcPath) => {
39
+ resolvedConfigPath = (0, vite_1.normalizePath)(hcPath);
40
+ // This direct dependency on a real file
41
+ // makes HMR work for the virtual module.
42
+ return this.resolve(hcPath, importer, { skipSelf: true });
43
+ });
40
44
  }
41
45
  if ([
42
46
  exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID,
@@ -53,7 +57,7 @@ exports.default = (pluginOptions) => {
53
57
  // Likely due to a bug in Vite, but virtual modules cannot be loaded
54
58
  // directly using ssrLoadModule from a Vite plugin. It needs to be proxied as follows:
55
59
  if (id === '\0' + exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID) {
56
- return `import hc from '${VIRTUAL_HYDROGEN_CONFIG_ID}'; export default hc;`;
60
+ return `import hc from '${VIRTUAL_HYDROGEN_CONFIG_ID}'; export default typeof hc === 'function' ? hc() : hc;`;
57
61
  }
58
62
  if (id === '\0' + exports.VIRTUAL_PROXY_HYDROGEN_ROUTES_ID) {
59
63
  return `import hr from '${VIRTUAL_HYDROGEN_ROUTES_ID}'; export default hr;`;
@@ -88,13 +92,27 @@ exports.default = (pluginOptions) => {
88
92
  });
89
93
  }
90
94
  },
95
+ transform(code, id) {
96
+ if (id === resolvedConfigPath) {
97
+ const s = new magic_string_1.default(code);
98
+ // Wrap in function to avoid evaluating `Oxygen.env`
99
+ // in the config until we have polyfilled it properly.
100
+ s.replace(/export\s+default\s+(\w+)\s*\(/g, (all, m1) => all.replace(m1, `() => ${m1}`));
101
+ return {
102
+ code: s.toString(),
103
+ map: s.generateMap({ file: id, source: id }),
104
+ };
105
+ }
106
+ },
91
107
  };
92
108
  async function importHydrogenConfig() {
93
109
  if (server) {
94
110
  const loaded = await server.ssrLoadModule(exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID);
95
111
  return loaded.default;
96
112
  }
97
- const { loaded } = await (0, viteception_js_1.viteception)([exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID]);
113
+ const { loaded } = await (0, viteception_js_1.viteception)([exports.VIRTUAL_PROXY_HYDROGEN_CONFIG_ID], {
114
+ root: config.root,
115
+ });
98
116
  return loaded[0].default;
99
117
  }
100
118
  };
@@ -3,6 +3,7 @@ export interface HydrogenVitePluginOptions {
3
3
  purgeQueryCacheOnBuild?: boolean;
4
4
  configPath?: string;
5
5
  optimizeBoundaries?: boolean | 'build';
6
+ assetHashVersion?: string;
6
7
  /**
7
8
  * Experimental features
8
9
  */
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "1.2.0",
10
+ "version": "1.3.0",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",
@@ -91,6 +91,7 @@
91
91
  "@rollup/plugin-graphql": "^1.0.0",
92
92
  "@testing-library/jest-dom": "^5.16.4",
93
93
  "@testing-library/react": "^13.3.0",
94
+ "@testing-library/user-event": "^14.3.0",
94
95
  "@types/body-parser": "^1.19.2",
95
96
  "@types/connect": "^3.4.34",
96
97
  "@types/graphql": "^14.5.0",
@@ -104,13 +105,13 @@
104
105
  "babel-loader": "^8.2.2",
105
106
  "cpy-cli": "^3.1.0",
106
107
  "eslint-plugin-import": "^2.26.0",
107
- "happy-dom": "^6.0.3",
108
+ "happy-dom": "^6.0.4",
108
109
  "mkdirp": "^1.0.4",
109
110
  "npm-run-all": "^4.1.5",
110
111
  "postcss": "^8",
111
112
  "raw-loader": "^4.0.2",
112
113
  "rimraf": "^3.0.2",
113
- "vitest": "^0.18.0"
114
+ "vitest": "^0.22.0"
114
115
  },
115
116
  "peerDependencies": {
116
117
  "body-parser": "^1.20.0",
@@ -128,10 +128,10 @@ function ReactFlightVitePlugin() {
128
128
  enforce: 'pre',
129
129
  buildStart: function () {
130
130
  // Let other plugins differentiate between pure SSR and RSC builds
131
- if (config?.build?.ssr) process.env.RSC_BUILD = 'true';
131
+ if (config?.build?.ssr) process.env.VITE_RSC_BUILD = 'true';
132
132
  },
133
133
  buildEnd: function () {
134
- if (config?.build?.ssr) delete process.env.RSC_BUILD;
134
+ if (config?.build?.ssr) delete process.env.VITE_RSC_BUILD;
135
135
  },
136
136
  configureServer: function (_server) {
137
137
  server = _server;
@@ -302,7 +302,7 @@ function ReactFlightVitePlugin() {
302
302
  throw new Error('[react-server-dom-vite] Parameter serverBuildEntries is required for client build');
303
303
  }
304
304
 
305
- return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false).then(injectGlobs);
305
+ return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false, config.root).then(injectGlobs);
306
306
  }
307
307
  },
308
308
  handleHotUpdate: function (_ref2) {
@@ -405,9 +405,10 @@ function findClientBoundaries(moduleGraph) {
405
405
  return clientBoundaries;
406
406
  }
407
407
 
408
- async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries) {
408
+ async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries, root) {
409
409
  // Viteception
410
410
  var server = await vite.createServer({
411
+ root: root,
411
412
  clearScreen: false,
412
413
  server: {
413
414
  middlewareMode: 'ssr'
@@ -124,10 +124,10 @@ function ReactFlightVitePlugin() {
124
124
  enforce: 'pre',
125
125
  buildStart: function () {
126
126
  // Let other plugins differentiate between pure SSR and RSC builds
127
- if (config?.build?.ssr) process.env.RSC_BUILD = 'true';
127
+ if (config?.build?.ssr) process.env.VITE_RSC_BUILD = 'true';
128
128
  },
129
129
  buildEnd: function () {
130
- if (config?.build?.ssr) delete process.env.RSC_BUILD;
130
+ if (config?.build?.ssr) delete process.env.VITE_RSC_BUILD;
131
131
  },
132
132
  configureServer: function (_server) {
133
133
  server = _server;
@@ -298,7 +298,7 @@ function ReactFlightVitePlugin() {
298
298
  throw new Error('[react-server-dom-vite] Parameter serverBuildEntries is required for client build');
299
299
  }
300
300
 
301
- return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false).then(injectGlobs);
301
+ return findClientBoundariesForClientBuild(serverBuildEntries, optimizeBoundaries !== false, config.root).then(injectGlobs);
302
302
  }
303
303
  },
304
304
  handleHotUpdate: function (_ref2) {
@@ -401,9 +401,10 @@ function findClientBoundaries(moduleGraph) {
401
401
  return clientBoundaries;
402
402
  }
403
403
 
404
- async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries) {
404
+ async function findClientBoundariesForClientBuild(serverEntries, optimizeBoundaries, root) {
405
405
  // Viteception
406
406
  var server = await createServer({
407
+ root: root,
407
408
  clearScreen: false,
408
409
  server: {
409
410
  middlewareMode: 'ssr'
@@ -1,3 +0,0 @@
1
- import type { Plugin } from 'vite';
2
- declare const _default: () => Plugin;
3
- export default _default;
@@ -1,11 +0,0 @@
1
- import Crypto from 'crypto';
2
- export default () => {
3
- const buildCacheId = Crypto.randomBytes(8).toString('hex').slice(0, 8);
4
- return {
5
- name: 'vite-plugin-purge-query-cache',
6
- enforce: 'pre',
7
- transform(code) {
8
- return code.replace('__QUERY_CACHE_ID__', buildCacheId);
9
- },
10
- };
11
- };
@@ -1,3 +0,0 @@
1
- import type { Plugin } from 'vite';
2
- declare const _default: () => Plugin;
3
- export default _default;
@@ -1,16 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const crypto_1 = __importDefault(require("crypto"));
7
- exports.default = () => {
8
- const buildCacheId = crypto_1.default.randomBytes(8).toString('hex').slice(0, 8);
9
- return {
10
- name: 'vite-plugin-purge-query-cache',
11
- enforce: 'pre',
12
- transform(code) {
13
- return code.replace('__QUERY_CACHE_ID__', buildCacheId);
14
- },
15
- };
16
- };